Stage 8 polish: keyboard-aware modals, consistent select pickers, required game type
Tests · UI / test (push) Successful in 17s

Third owner-review pass (iPhone):
- Modals (and the chat) size their backdrop to window.visualViewport, so they stay
  fully above the software keyboard (dvh alone left the sheet partly behind it).
- On the owner's call, every profile / new-game picker is a native <select> for
  consistent cross-platform behaviour: the away window returns to hour + 10-minute
  selects (which also avoids the iOS time-wheel "clear" button), alongside the offset
  timezone and the game-type / move-time / hints selects. Native time/wheel inputs
  render differently per OS and cannot be forced to match.
- New-game "play with friends" has no preselected game type — an explicit, required
  pick (empty placeholder); Send invitation stays disabled until both a type and a
  friend are chosen. A smart default (from play history / language) is TODO-6.
This commit is contained in:
Ilia Denisov
2026-06-03 23:14:51 +02:00
parent 8b83543632
commit b7d469a06e
4 changed files with 93 additions and 32 deletions
+35 -2
View File
@@ -6,11 +6,38 @@
onclose,
children,
}: { title?: string; onclose?: () => void; children?: Snippet } = $props();
// Track the visual viewport so the backdrop covers only the area above an open
// mobile keyboard: dvh alone shrinks the sheet but the fixed, layout-viewport
// backdrop still centres it behind the keyboard. Sizing the backdrop to
// visualViewport keeps the sheet (and the start of a chat) fully on screen.
let vh = $state(0);
let top = $state(0);
$effect(() => {
const vv = typeof window !== 'undefined' ? window.visualViewport : null;
if (!vv) return;
const update = () => {
vh = vv.height;
top = vv.offsetTop;
};
update();
vv.addEventListener('resize', update);
vv.addEventListener('scroll', update);
return () => {
vv.removeEventListener('resize', update);
vv.removeEventListener('scroll', update);
};
});
</script>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="backdrop" onclick={() => onclose?.()}>
<div
class="backdrop"
style:height={vh ? `${vh}px` : null}
style:top={vh ? `${top}px` : null}
onclick={() => onclose?.()}
>
<div class="sheet" role="dialog" aria-modal="true" tabindex="-1" onclick={(e) => e.stopPropagation()}>
{#if title}<h2>{title}</h2>{/if}
{@render children?.()}
@@ -20,7 +47,13 @@
<style>
.backdrop {
position: fixed;
inset: 0;
left: 0;
right: 0;
top: 0;
/* Base fallback; overridden inline to the visual-viewport height/top so the
backdrop (and the centred sheet) stay above an open mobile keyboard. */
height: 100dvh;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.45);
display: flex;
align-items: center;