mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Preact: Refactor mobile support
Scrollable width is now set on `<html>` (and is inherited by `<body>`). It's my hope that this fixes some scroll snap bugs on mobile. We're also now looking at pointer events to decide whether to autofocus text boxes. Fixes #2419
This commit is contained in:
parent
07217f22ba
commit
b5865585e4
|
|
@ -2073,7 +2073,7 @@ export const PS = new class extends PSModel {
|
|||
}
|
||||
calculateLeftPanelWidth() {
|
||||
const available = document.body.offsetWidth;
|
||||
if (available < 800 || this.prefs.onepanel === 'vertical') {
|
||||
if (document.documentElement.clientWidth < 800 || this.prefs.onepanel === 'vertical') {
|
||||
return null;
|
||||
}
|
||||
// If we don't have both a left room and a right room, obviously
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
import preact from "../js/lib/preact";
|
||||
import { Config, PS, type PSRoom, type RoomID } from "./client-main";
|
||||
import { PSView } from "./panels";
|
||||
import { NARROW_MODE_HEADER_WIDTH, PSView, VERTICAL_HEADER_WIDTH } from "./panels";
|
||||
import type { Battle } from "./battle";
|
||||
import { BattleLog } from "./battle-log"; // optional
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ window.addEventListener('dragover', e => {
|
|||
e.preventDefault();
|
||||
});
|
||||
|
||||
export class PSHeader extends preact.Component<{ style: object }> {
|
||||
export class PSHeader extends preact.Component {
|
||||
static toggleMute = (e: Event) => {
|
||||
PS.prefs.set('mute', !PS.prefs.mute);
|
||||
PS.update();
|
||||
|
|
@ -168,8 +168,31 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
{closeButton}
|
||||
</li>;
|
||||
}
|
||||
handleRoomTabOverflow = () => {
|
||||
if (PS.leftPanelWidth === null || !this.base) return;
|
||||
handleResize = () => {
|
||||
if (!this.base) return;
|
||||
|
||||
if (PS.leftPanelWidth === null) {
|
||||
const width = document.documentElement.clientWidth;
|
||||
const oldNarrowMode = PSView.narrowMode;
|
||||
PSView.narrowMode = width <= 700;
|
||||
PSView.verticalHeaderWidth = PSView.narrowMode ? NARROW_MODE_HEADER_WIDTH : VERTICAL_HEADER_WIDTH;
|
||||
document.documentElement.style.width = PSView.narrowMode ? `${width + NARROW_MODE_HEADER_WIDTH}px` : 'auto';
|
||||
if (oldNarrowMode !== PSView.narrowMode) {
|
||||
if (PSView.narrowMode) {
|
||||
if (!PSView.textboxFocused) {
|
||||
document.documentElement.classList?.add('scroll-snap-enabled');
|
||||
}
|
||||
} else {
|
||||
document.documentElement.classList?.remove('scroll-snap-enabled');
|
||||
}
|
||||
PS.update();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (PSView.narrowMode) {
|
||||
document.documentElement.classList?.remove('scroll-snap-enabled');
|
||||
PSView.narrowMode = false;
|
||||
}
|
||||
|
||||
const userbarLeft = this.base.querySelector('div.userbar')?.getBoundingClientRect()?.left;
|
||||
const plusTabRight = this.base.querySelector('a.roomtab[aria-label="Join chat"]')?.getBoundingClientRect()?.right;
|
||||
|
|
@ -187,11 +210,11 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
PS.user.subscribe(() => {
|
||||
this.forceUpdate();
|
||||
});
|
||||
window.addEventListener('resize', this.handleRoomTabOverflow);
|
||||
this.handleRoomTabOverflow();
|
||||
window.addEventListener('resize', this.handleResize);
|
||||
this.handleResize();
|
||||
}
|
||||
override componentDidUpdate() {
|
||||
this.handleRoomTabOverflow();
|
||||
this.handleResize();
|
||||
}
|
||||
renderUser() {
|
||||
if (!PS.connected) {
|
||||
|
|
@ -210,7 +233,8 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
}
|
||||
renderVertical() {
|
||||
return <div
|
||||
id="header" class="header-vertical" style={this.props.style} onClick={PSView.scrollToHeader} role="navigation"
|
||||
id="header" class="header-vertical" role="navigation"
|
||||
style={`width:${PSView.verticalHeaderWidth - 7}px`} onClick={PSView.scrollToHeader}
|
||||
>
|
||||
<div class="maintabbarbottom"></div>
|
||||
<div class="scrollable-part">
|
||||
|
|
@ -232,6 +256,7 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{null /* overflow */}
|
||||
<div class="userbar">
|
||||
{this.renderUser()} {}
|
||||
<div style="float:right">
|
||||
|
|
@ -247,15 +272,9 @@ export class PSHeader extends preact.Component<{ style: object }> {
|
|||
}
|
||||
override render() {
|
||||
if (PS.leftPanelWidth === null) {
|
||||
if (!PSView.textboxFocused) {
|
||||
document.documentElement.classList?.add('scroll-snap-enabled');
|
||||
}
|
||||
return this.renderVertical();
|
||||
} else {
|
||||
document.documentElement.classList?.remove('scroll-snap-enabled');
|
||||
}
|
||||
|
||||
return <div id="header" class="header" style={this.props.style} role="navigation">
|
||||
return <div id="header" class="header" role="navigation">
|
||||
<div class="maintabbarbottom"></div>
|
||||
<div class="tabbar maintabbar"><div class="inner-1" role={PS.leftPanelWidth ? 'none' : 'tablist'}><div class="inner-2">
|
||||
<ul class="maintabbar-left" style={{ width: `${PS.leftPanelWidth}px` }} role={PS.leftPanelWidth ? 'tablist' : 'none'}>
|
||||
|
|
@ -305,25 +324,24 @@ export class PSMiniHeader extends preact.Component {
|
|||
override render() {
|
||||
if (PS.leftPanelWidth !== null) return null;
|
||||
|
||||
const minWidth = Math.min(500, Math.max(320, document.body.offsetWidth - 9));
|
||||
const { icon, title } = PSHeader.roomInfo(PS.panel);
|
||||
const userColor = window.BattleLog && `color:${BattleLog.usernameColor(PS.user.userid)}`;
|
||||
const showMenuButton = document.documentElement.offsetWidth >= document.documentElement.scrollWidth;
|
||||
const showMenuButton = PSView.narrowMode;
|
||||
const notifying = (
|
||||
showMenuButton && !window.scrollX && Object.values(PS.rooms).some(room => room!.notifications.length)
|
||||
!showMenuButton && !window.scrollX && Object.values(PS.rooms).some(room => room!.notifications.length)
|
||||
) ? ' notifying' : '';
|
||||
const menuButton = showMenuButton ? (
|
||||
const menuButton = !showMenuButton ? (
|
||||
null
|
||||
) : window.scrollX ? (
|
||||
<button onClick={PSView.scrollToHeader} class={`mini-header-left ${notifying}`} aria-label="Menu">
|
||||
<i class="fa fa-arrow-left" aria-hidden></i>
|
||||
<i class="fa fa-bars" aria-hidden></i>
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={PSView.scrollToRoom} class="mini-header-left" aria-label="Menu">
|
||||
<i class="fa fa-arrow-right" aria-hidden></i>
|
||||
</button>
|
||||
);
|
||||
return <div class="mini-header" style={{ minWidth: `${minWidth}px` }}>
|
||||
return <div class="mini-header" style={`left:${PSView.verticalHeaderWidth + (PSView.narrowMode ? 0 : -1)}px;`}>
|
||||
{menuButton}
|
||||
{icon} {title}
|
||||
<button data-href="options" class="mini-header-right" aria-label="Options">
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ import { PS, type PSRoom, type RoomID } from "./client-main";
|
|||
import type { ChatRoom } from "./panel-chat";
|
||||
import { PSHeader, PSMiniHeader } from "./panel-topbar";
|
||||
|
||||
export const VERTICAL_HEADER_WIDTH = 240;
|
||||
export const NARROW_MODE_HEADER_WIDTH = 280;
|
||||
|
||||
export class PSRouter {
|
||||
roomid = '' as RoomID;
|
||||
panelState = '';
|
||||
|
|
@ -259,8 +262,7 @@ export class PSRoomPanel<T extends PSRoom = PSRoom> extends preact.Component<{ r
|
|||
PS.closePopup();
|
||||
}
|
||||
focus() {
|
||||
// mobile probably
|
||||
if (document.body.offsetWidth < 500) return;
|
||||
if (PSView.hasTapped) return;
|
||||
|
||||
const autofocus = this.base?.querySelector<HTMLElement>('.autofocus');
|
||||
autofocus?.focus();
|
||||
|
|
@ -317,8 +319,14 @@ export class PSView extends preact.Component {
|
|||
static readonly isMac = navigator.platform?.startsWith('Mac');
|
||||
static textboxFocused = false;
|
||||
static dragend: ((ev: DragEvent) => void) | null = null;
|
||||
/** was the last click event a tap? heristic for mobile/desktop */
|
||||
static hasTapped = false;
|
||||
/** mode where the tabbar is opened rather than always being there */
|
||||
static narrowMode = false;
|
||||
static verticalHeaderWidth = VERTICAL_HEADER_WIDTH;
|
||||
static setTextboxFocused(focused: boolean) {
|
||||
if (!PSView.isChrome || PS.leftPanelWidth !== null) return;
|
||||
if (!PSView.narrowMode) return;
|
||||
if (!PSView.isChrome && !PSView.isSafari) return;
|
||||
// Chrome bug: on Android, it insistently scrolls everything leftmost when scroll snap is enabled
|
||||
|
||||
this.textboxFocused = focused;
|
||||
|
|
@ -369,7 +377,7 @@ export class PSView extends preact.Component {
|
|||
super();
|
||||
PS.subscribe(() => this.forceUpdate());
|
||||
|
||||
if (PSView.isIOS) {
|
||||
if (PSView.isSafari) {
|
||||
// I don't want to prevent users from being able to zoom, but iOS Safari
|
||||
// auto-zooms when focusing textboxes (unless the font size is 16px),
|
||||
// and this apparently fixes it while still allowing zooming.
|
||||
|
|
@ -397,6 +405,11 @@ export class PSView extends preact.Component {
|
|||
}
|
||||
});
|
||||
|
||||
window.addEventListener('pointerdown', ev => {
|
||||
// can't be part of the click event because Safari pretends the pointer is a mouse
|
||||
PSView.hasTapped = ev.pointerType === 'touch' || ev.pointerType === 'pen';
|
||||
});
|
||||
|
||||
window.addEventListener('click', ev => {
|
||||
let elem = ev.target as HTMLElement | null;
|
||||
const clickedRoom = PS.getRoom(elem);
|
||||
|
|
@ -588,23 +601,32 @@ export class PSView extends preact.Component {
|
|||
});
|
||||
}
|
||||
static scrollToHeader() {
|
||||
if (window.scrollX > 0) {
|
||||
window.scrollTo({ left: 0 });
|
||||
if (PSView.narrowMode && window.scrollX > 0) {
|
||||
if (PSView.isSafari || PSView.isFirefox) {
|
||||
// Safari bug: `scrollBy` doesn't actually work when scroll snap is enabled
|
||||
// note: interferes with the `PSView.textboxFocused` workaround for a Chrome bug
|
||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||
window.scrollTo(0, 0);
|
||||
setTimeout(() => {
|
||||
if (!PSView.textboxFocused) document.documentElement.classList.add('scroll-snap-enabled');
|
||||
}, 1);
|
||||
} else {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
static scrollToRoom() {
|
||||
if (document.documentElement.scrollWidth > document.documentElement.clientWidth && window.scrollX === 0) {
|
||||
if ((PSView.isIOS || PSView.isFirefox) && PS.leftPanelWidth === null) {
|
||||
if (PSView.narrowMode && window.scrollX === 0) {
|
||||
if (PSView.isSafari || PSView.isFirefox) {
|
||||
// Safari bug: `scrollBy` doesn't actually work when scroll snap is enabled
|
||||
// note: interferes with the `PSMain.textboxFocused` workaround for a Chrome bug
|
||||
// note: interferes with the `PSView.textboxFocused` workaround for a Chrome bug
|
||||
document.documentElement.classList.remove('scroll-snap-enabled');
|
||||
window.scrollBy(400, 0);
|
||||
window.scrollTo(NARROW_MODE_HEADER_WIDTH, 0);
|
||||
setTimeout(() => {
|
||||
document.documentElement.classList.add('scroll-snap-enabled');
|
||||
}, 0);
|
||||
if (!PSView.textboxFocused) document.documentElement.classList.add('scroll-snap-enabled');
|
||||
}, 1);
|
||||
} else {
|
||||
// intentionally around twice as big as necessary
|
||||
window.scrollBy(400, 0);
|
||||
window.scrollTo(NARROW_MODE_HEADER_WIDTH, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -709,8 +731,8 @@ export class PSView extends preact.Component {
|
|||
if (PS.leftPanelWidth === null) {
|
||||
// vertical mode
|
||||
if (room === PS.panel) {
|
||||
const minWidth = Math.min(500, Math.max(320, document.body.offsetWidth - 9));
|
||||
return { top: '25px', left: '200px', minWidth: `${minWidth}px` };
|
||||
// const minWidth = Math.min(500, Math.max(320, document.body.offsetWidth - 9));
|
||||
return { top: '30px', left: `${PSView.verticalHeaderWidth}px`, minWidth: `none` };
|
||||
}
|
||||
} else if (PS.leftPanelWidth === 0) {
|
||||
// one panel visible
|
||||
|
|
@ -799,12 +821,12 @@ export class PSView extends preact.Component {
|
|||
if (availableHeight > sourceTop + sourceHeight + height + 5 &&
|
||||
(sourceTop + sourceHeight < availableHeight * 2 / 3 || sourceTop + sourceHeight + 200 < availableHeight)) {
|
||||
style.top = sourceTop + sourceHeight;
|
||||
} else if (height + 5 <= sourceTop) {
|
||||
} else if (height + 30 <= sourceTop) {
|
||||
style.bottom = Math.max(availableHeight - sourceTop, 0);
|
||||
} else if (height + 10 < availableHeight) {
|
||||
} else if (height + 35 < availableHeight) {
|
||||
style.bottom = 5;
|
||||
} else {
|
||||
style.top = 0;
|
||||
style.top = 25;
|
||||
}
|
||||
|
||||
const availableAlignedWidth = availableWidth - sourceLeft;
|
||||
|
|
@ -846,7 +868,7 @@ export class PSView extends preact.Component {
|
|||
}
|
||||
}
|
||||
return <div class="ps-frame" role="none">
|
||||
<PSHeader style={{}} />
|
||||
<PSHeader />
|
||||
<PSMiniHeader />
|
||||
{rooms}
|
||||
{PS.popups.map(roomid => this.renderPopup(PS.rooms[roomid]!))}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,11 @@
|
|||
padding: 0;
|
||||
height: 100%;
|
||||
overflow: visible;
|
||||
overscroll-behavior-x: contain;
|
||||
}
|
||||
html {
|
||||
scrollbar-width: none;
|
||||
position: relative;
|
||||
}
|
||||
body {
|
||||
color: white;
|
||||
|
|
@ -96,7 +101,7 @@ li::marker {
|
|||
scroll-snap-align: start;
|
||||
}
|
||||
.scroll-snap-enabled .mini-header {
|
||||
scroll-snap-align: end;
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
|
@ -117,7 +122,7 @@ li::marker {
|
|||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 193px;
|
||||
width: 243px;
|
||||
padding-right: 7px;
|
||||
background: rgba(255,255,255,.3);
|
||||
}
|
||||
|
|
@ -135,7 +140,7 @@ li::marker {
|
|||
top: auto;
|
||||
left: 12px;
|
||||
bottom: 12px;
|
||||
width: 170px;
|
||||
right: 17px;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.userbar .username {
|
||||
|
|
@ -191,12 +196,12 @@ li::marker {
|
|||
}
|
||||
.mini-header {
|
||||
position: absolute;
|
||||
left: 199px;
|
||||
left: 249px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
height: 29px;
|
||||
line-height: 29px;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
border-top: 0;
|
||||
|
|
@ -229,7 +234,7 @@ li::marker {
|
|||
border: 0;
|
||||
border: 1px solid #777;
|
||||
border-width: 0 0 0 1px;
|
||||
padding: 0 6px;
|
||||
padding: 0 9px;
|
||||
}
|
||||
.mini-header-left {
|
||||
float: left;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user