10d48884ac
- item 5: move container-type to the zoom-scaled .scaler so cqw labels grow WITH the board (magnifying-glass zoom); new e2e measures the font grows ~1.85x - item 8: confirm popovers anchor to the trigger's right edge (no longer run off-screen) - item 9: last-word flash runs 2 cycles then settles to normal (was infinite)
109 lines
2.3 KiB
Svelte
109 lines
2.3 KiB
Svelte
<script lang="ts">
|
|
import type { Snippet } from 'svelte';
|
|
// A press-and-hold control: a short tap opens a popover (the consumer renders its
|
|
// buttons), a ~holdMs hold runs `onhold` immediately. Reused by MakeMove and the
|
|
// game tab-bar confirmations. The popover snippet receives a `close` callback.
|
|
let {
|
|
onhold,
|
|
holdMs = 700,
|
|
disabled = false,
|
|
triggerClass = '',
|
|
trigger,
|
|
popover,
|
|
}: {
|
|
onhold: () => void;
|
|
holdMs?: number;
|
|
disabled?: boolean;
|
|
triggerClass?: string;
|
|
trigger: Snippet;
|
|
popover: Snippet<[() => void]>;
|
|
} = $props();
|
|
|
|
let open = $state(false);
|
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
let held = false;
|
|
|
|
function clear() {
|
|
if (timer) {
|
|
clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
}
|
|
function down() {
|
|
if (disabled) return;
|
|
held = false;
|
|
clear();
|
|
timer = setTimeout(() => {
|
|
held = true;
|
|
open = false;
|
|
onhold();
|
|
}, holdMs);
|
|
}
|
|
function up() {
|
|
clear();
|
|
if (!held && !disabled) open = true;
|
|
}
|
|
function leave() {
|
|
clear();
|
|
}
|
|
const close = () => (open = false);
|
|
</script>
|
|
|
|
<div class="hc">
|
|
<button
|
|
class="trigger {triggerClass}"
|
|
{disabled}
|
|
onpointerdown={down}
|
|
onpointerup={up}
|
|
onpointerleave={leave}
|
|
onpointercancel={leave}
|
|
>
|
|
{@render trigger()}
|
|
</button>
|
|
|
|
{#if open}
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div class="backdrop" onclick={close}></div>
|
|
<div class="popover">{@render popover(close)}</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<style>
|
|
.hc {
|
|
position: relative;
|
|
display: flex;
|
|
}
|
|
.trigger {
|
|
width: 100%;
|
|
background: none;
|
|
border: none;
|
|
padding: 0;
|
|
color: inherit;
|
|
touch-action: none;
|
|
user-select: none;
|
|
-webkit-user-select: none;
|
|
}
|
|
.backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 18;
|
|
}
|
|
.popover {
|
|
position: absolute;
|
|
bottom: calc(100% + 6px);
|
|
right: 0;
|
|
z-index: 19;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
white-space: nowrap;
|
|
background: var(--surface);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius-sm);
|
|
box-shadow: var(--shadow);
|
|
padding: 4px;
|
|
min-width: 132px;
|
|
}
|
|
</style>
|