refactor(toolkit): type source any with proper types

Genuinely type composable any usages (useStepper/useStorage/useForm/
createEventHook/useSorted/etc.) as proper generics/unknown; keep idiomatic
any-function and overload-impl signatures with comments; skipped test -> .todo.
This commit is contained in:
2026-06-15 16:55:07 +07:00
parent 44848bc9e6
commit aa2938cb34
283 changed files with 3505 additions and 3482 deletions
@@ -27,51 +27,51 @@ function reset() {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">DOM removal watcher</span>
<span class="demo-label">DOM removal watcher</span>
<span
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)"
class="demo-badge"
>
<span class="size-1.5 rounded-full transition" :class="mounted ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
<span class="size-1.5 rounded-full transition" :class="mounted ? 'bg-emerald-500' : 'bg-fg-subtle'" />
{{ mounted ? 'In DOM' : 'Removed' }}
</span>
</div>
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 min-h-28 flex items-center justify-center">
<div class="demo-card p-4 min-h-28 flex items-center justify-center">
<div
v-if="mounted"
ref="watched"
class="flex w-full flex-col items-center gap-1 rounded-lg border border-(--accent) bg-(--accent-subtle) px-4 py-6 text-center transition"
class="flex w-full flex-col items-center gap-1 rounded-lg border border-accent bg-accent-subtle px-4 py-6 text-center transition"
>
<span class="text-sm font-medium text-(--accent-text)">Watched element</span>
<span class="text-xs text-(--fg-muted)">Remove me and the callback fires</span>
<span class="text-sm font-medium text-accent-text">Watched element</span>
<span class="text-xs text-fg-muted">Remove me and the callback fires</span>
</div>
<span v-else class="text-sm text-(--fg-subtle)">Element detached from the document</span>
<span v-else class="text-sm text-fg-subtle">Element detached from the document</span>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ removals }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">removals fired</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ removals }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">removals fired</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center flex flex-col justify-center">
<div class="font-mono text-sm font-medium tabular-nums text-(--fg)">{{ lastEvent ?? '—' }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">last fired</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center flex flex-col justify-center">
<div class="font-mono text-sm font-medium tabular-nums text-fg">{{ lastEvent ?? '—' }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">last fired</div>
</div>
</div>
<div class="flex gap-2">
<button
type="button"
class="inline-flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
class="demo-btn-primary flex-1"
@click="toggle"
>
{{ mounted ? 'Remove element' : 'Mount element' }}
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
class="demo-btn disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
:disabled="removals === 0"
@click="reset"
>
@@ -15,9 +15,9 @@ const activeTag = computed(() => activeElement.value?.tagName.toLowerCase() ?? n
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="space-y-3">
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<p class="demo-label">
Focus a field
</p>
<div
@@ -27,39 +27,39 @@ const activeTag = computed(() => activeElement.value?.tagName.toLowerCase() ?? n
>
<label
:for="field.id"
class="text-sm font-medium text-(--fg-muted)"
class="text-sm font-medium text-fg-muted"
>{{ field.label }}</label>
<input
:id="field.id"
:type="field.type"
:placeholder="field.placeholder"
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) placeholder:text-(--fg-subtle) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
class="demo-input"
>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
>
A focusable button
</button>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
<div class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums">
<div class="flex items-center justify-between gap-3">
<span class="text-(--fg-subtle)">activeElement</span>
<span class="text-fg-subtle">activeElement</span>
<span
v-if="activeTag"
class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-elevated) px-2 py-0.5 text-xs font-medium text-(--accent-text)"
class="inline-flex items-center gap-1.5 rounded-md border border-border bg-bg-elevated px-2 py-0.5 text-xs font-medium text-accent-text"
>
&lt;{{ activeTag }}&gt;
</span>
<span
v-else
class="text-xs text-(--fg-subtle)"
class="text-xs text-fg-subtle"
>none</span>
</div>
<div class="mt-2 flex items-center justify-between gap-3">
<span class="text-(--fg-subtle)">id</span>
<span class="text-fg-subtle">id</span>
<span class="truncate">{{ activeId ?? '—' }}</span>
</div>
</div>
@@ -14,9 +14,9 @@ const activeIndex = computed(() => stages.findIndex(s => s.state === readyState.
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<span class="demo-label">
document.readyState
</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-emerald-500/30 bg-emerald-500/10 px-2 py-0.5 text-xs font-medium text-emerald-600 dark:text-emerald-400">
@@ -31,24 +31,24 @@ const activeIndex = computed(() => stages.findIndex(s => s.state === readyState.
:key="stage.state"
class="flex items-center gap-3 rounded-lg border p-3 transition"
:class="i <= activeIndex
? 'border-(--accent) bg-(--accent-subtle)'
: 'border-(--border) bg-(--bg-elevated)'"
? 'border-accent bg-accent-subtle'
: 'border-border bg-bg-elevated'"
>
<span
class="flex size-6 shrink-0 items-center justify-center rounded-full font-mono text-xs font-bold tabular-nums transition"
:class="i < activeIndex
? 'bg-(--accent) text-(--accent-fg)'
? 'bg-accent text-accent-fg'
: i === activeIndex
? 'bg-(--accent) text-(--accent-fg) ring-4 ring-(--ring)'
: 'bg-(--bg-inset) text-(--fg-subtle)'"
? 'bg-accent text-accent-fg ring-4 ring-ring'
: 'bg-bg-inset text-fg-subtle'"
>
{{ i + 1 }}
</span>
<div class="min-w-0">
<p class="font-mono text-sm font-medium text-(--fg)">
<p class="font-mono text-sm font-medium text-fg">
{{ stage.label }}
</p>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
{{ stage.hint }}
</p>
</div>
@@ -18,8 +18,8 @@ const isVisible = computed(() => visibility.value === 'visible');
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col items-center gap-3 text-center">
<div class="demo-stack max-w-sm">
<div class="demo-card p-4 flex flex-col items-center gap-3 text-center">
<span
class="flex size-14 items-center justify-center rounded-full text-2xl transition"
:class="isVisible
@@ -29,33 +29,33 @@ const isVisible = computed(() => visibility.value === 'visible');
{{ isVisible ? '👁️' : '💤' }}
</span>
<div>
<p class="font-mono text-2xl font-bold tabular-nums text-(--fg)">
<p class="demo-stat text-2xl">
{{ visibility }}
</p>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
{{ isVisible ? 'This tab is in the foreground' : 'This tab is hidden' }}
</p>
</div>
</div>
<p class="text-center text-xs text-(--fg-subtle)">
<p class="text-center text-xs text-fg-subtle">
Switch to another tab or minimize the window to watch this update.
</p>
<div class="grid grid-cols-2 gap-3">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<p class="font-mono text-3xl font-bold tabular-nums text-(--fg)">
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<p class="demo-stat text-3xl">
{{ switches }}
</p>
<p class="mt-0.5 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<p class="demo-label mt-0.5">
Times hidden
</p>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<p class="font-mono text-sm font-medium tabular-nums text-(--fg) truncate">
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<p class="font-mono text-sm font-medium tabular-nums text-fg truncate">
{{ lastHidden ?? '—' }}
</p>
<p class="mt-0.5 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<p class="demo-label mt-0.5">
Last hidden at
</p>
</div>
@@ -16,20 +16,20 @@ const { x, y, isDragging, style } = useDraggable(
</script>
<template>
<div class="w-full max-w-md flex flex-col gap-4">
<div class="demo-stack max-w-md">
<div class="flex items-center justify-between gap-3">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<span class="demo-label">
Draggable, clamped to container
</span>
<span
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
:class="isDragging
? 'border-(--accent) bg-(--accent-subtle) text-(--accent-text)'
: 'border-(--border) bg-(--bg-inset) text-(--fg-muted)'"
? 'border-accent bg-accent-subtle text-accent-text'
: 'border-border bg-bg-inset text-fg-muted'"
>
<span
class="size-1.5 rounded-full transition"
:class="isDragging ? 'bg-(--accent)' : 'bg-(--fg-subtle)'"
:class="isDragging ? 'bg-accent' : 'bg-fg-subtle'"
/>
{{ isDragging ? 'dragging' : 'idle' }}
</span>
@@ -37,29 +37,29 @@ const { x, y, isDragging, style } = useDraggable(
<div
ref="container"
class="relative h-56 w-full overflow-hidden rounded-xl border border-(--border) bg-(--bg-inset)"
class="relative h-56 w-full overflow-hidden rounded-xl border border-border bg-bg-inset"
>
<div
ref="card"
:style="style"
class="absolute w-36 select-none rounded-lg border border-(--border-strong) bg-(--bg-elevated) shadow-lg"
:class="isDragging ? 'ring-2 ring-(--ring)' : ''"
class="absolute w-36 select-none rounded-lg border border-border-strong bg-bg-elevated shadow-lg"
:class="isDragging ? 'ring-2 ring-ring' : ''"
>
<div
ref="handle"
class="flex items-center gap-1.5 rounded-t-lg border-b border-(--border) bg-(--bg-subtle) px-3 py-2 cursor-grab active:cursor-grabbing"
class="flex items-center gap-1.5 rounded-t-lg border-b border-border bg-bg-subtle px-3 py-2 cursor-grab active:cursor-grabbing"
>
<span class="text-(--fg-subtle)"></span>
<span class="text-xs font-medium text-(--fg-muted)">Drag me</span>
<span class="text-fg-subtle"></span>
<span class="text-xs font-medium text-fg-muted">Drag me</span>
</div>
<div class="p-3 font-mono text-xs tabular-nums text-(--fg)">
<div class="p-3 font-mono text-xs tabular-nums text-fg">
<div>x: {{ Math.round(x) }}</div>
<div>y: {{ Math.round(y) }}</div>
</div>
</div>
</div>
<p class="text-center text-xs text-(--fg-subtle)">
<p class="text-center text-xs text-fg-subtle">
Drag from the header. Movement is clamped to the container bounds.
</p>
</div>
@@ -1,6 +1,6 @@
import { computed, shallowRef, toValue } from 'vue';
import type { ComputedRef, MaybeRefOrGetter, Ref } from 'vue';
import { noop } from '@robonen/stdlib';
import { clamp, noop } from '@robonen/stdlib';
import { defaultWindow } from '@/types';
import { useEventListener } from '@/composables/browser/useEventListener';
import { unrefElement } from '@/composables/component/unrefElement';
@@ -259,13 +259,13 @@ export function useDraggable(
if (axis === 'x' || axis === 'both') {
x = event.clientX - pressedDelta.value.x;
if (container && targetRect)
x = Math.min(Math.max(0, x), container.scrollWidth - targetRect.width);
x = clamp(x, 0, container.scrollWidth - targetRect.width);
}
if (axis === 'y' || axis === 'both') {
y = event.clientY - pressedDelta.value.y;
if (container && targetRect)
y = Math.min(Math.max(0, y), container.scrollHeight - targetRect.height);
y = clamp(y, 0, container.scrollHeight - targetRect.height);
}
position.value = { x, y };
@@ -24,7 +24,7 @@ function formatSize(bytes: number): string {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div
v-if="!isSupported"
class="rounded-xl border border-amber-500/30 bg-amber-500/10 p-4 text-center text-sm text-amber-700 dark:text-amber-300"
@@ -37,33 +37,33 @@ function formatSize(bytes: number): string {
ref="dropZone"
class="flex flex-col items-center justify-center gap-2 rounded-xl border-2 border-dashed p-8 text-center transition"
:class="isOverDropZone
? 'border-(--accent) bg-(--accent-subtle)'
: 'border-(--border-strong) bg-(--bg-elevated)'"
? 'border-accent bg-accent-subtle'
: 'border-border-strong bg-bg-elevated'"
>
<span class="text-3xl transition" :class="isOverDropZone ? 'scale-110' : ''">
{{ isOverDropZone ? '📥' : '🖼️' }}
</span>
<p class="text-sm font-medium text-(--fg)">
<p class="text-sm font-medium text-fg">
{{ isOverDropZone ? 'Release to drop' : 'Drop image files here' }}
</p>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
Only <span class="font-mono">image/*</span> files are accepted
</p>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
<div class="rounded-lg border border-border bg-bg-inset p-3">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<span class="demo-label">
Dropped files
</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-elevated) px-2 py-0.5 text-xs font-medium text-(--fg-muted) tabular-nums">
<span class="inline-flex items-center gap-1.5 rounded-md border border-border bg-bg-elevated px-2 py-0.5 text-xs font-medium text-fg-muted tabular-nums">
{{ fileList.length }}
</span>
</div>
<p
v-if="fileList.length === 0"
class="mt-3 text-center text-sm text-(--fg-subtle)"
class="mt-3 text-center text-sm text-fg-subtle"
>
Nothing dropped yet.
</p>
@@ -72,10 +72,10 @@ function formatSize(bytes: number): string {
<li
v-for="file in fileList"
:key="file.name"
class="flex items-center justify-between gap-3 rounded-md bg-(--bg-elevated) px-2.5 py-1.5"
class="flex items-center justify-between gap-3 rounded-md bg-bg-elevated px-2.5 py-1.5"
>
<span class="truncate text-sm text-(--fg)">{{ file.name }}</span>
<span class="shrink-0 font-mono text-xs tabular-nums text-(--fg-subtle)">
<span class="truncate text-sm text-fg">{{ file.name }}</span>
<span class="shrink-0 font-mono text-xs tabular-nums text-fg-subtle">
{{ formatSize(file.size) }}
</span>
</li>
@@ -34,20 +34,20 @@ function fmt(n: number) {
</script>
<template>
<div class="w-full max-w-md flex flex-col gap-4">
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-4">
<div class="demo-stack max-w-md">
<div class="demo-card p-4 flex flex-col gap-4">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">getBoundingClientRect</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-label">getBoundingClientRect</span>
<span class="demo-badge">
{{ timing }}
</span>
</div>
<!-- The measured target. Resizing it mutates the reactive bounds. -->
<div class="relative h-40 overflow-hidden rounded-lg border border-(--border) bg-(--bg-inset)">
<div class="relative h-40 overflow-hidden rounded-lg border border-border bg-bg-inset">
<div
ref="target"
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 grid place-items-center rounded-lg bg-(--accent) text-(--accent-fg) shadow transition-[width,height] duration-150"
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 grid place-items-center rounded-lg bg-accent text-accent-fg shadow transition-[width,height] duration-150"
:style="{ width: `${size}px`, height: `${size}px` }"
>
<span class="font-mono text-xs font-semibold tabular-nums">{{ fmt(bounds.width.value) }}×{{ fmt(bounds.height.value) }}</span>
@@ -58,18 +58,18 @@ function fmt(n: number) {
<div
v-for="m in metrics"
:key="m.label"
class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center"
class="rounded-lg border border-border bg-bg-inset p-2 text-center"
>
<div class="font-mono text-sm font-bold tabular-nums text-(--fg)">{{ fmt(m.value) }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">{{ m.label }}</div>
<div class="demo-stat text-sm">{{ fmt(m.value) }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">{{ m.label }}</div>
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="bound-size">Size</label>
<span class="font-mono text-xs tabular-nums text-(--fg-muted)">{{ size }}px</span>
<label class="demo-label" for="bound-size">Size</label>
<span class="font-mono text-xs tabular-nums text-fg-muted">{{ size }}px</span>
</div>
<input
id="bound-size"
@@ -78,7 +78,7 @@ function fmt(n: number) {
min="32"
max="140"
step="2"
class="w-full accent-(--accent) cursor-pointer"
class="w-full accent-accent cursor-pointer"
>
</div>
@@ -89,22 +89,22 @@ function fmt(n: number) {
type="button"
class="flex-1 inline-flex items-center justify-center gap-1.5 rounded-lg border px-3 py-1.5 text-sm font-medium transition active:scale-[0.98] cursor-pointer"
:class="timing === opt
? 'border-transparent bg-(--accent) text-(--accent-fg) hover:bg-(--accent-hover)'
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:bg-(--bg-inset) hover:border-(--border-strong)'"
? 'border-transparent bg-accent text-accent-fg hover:bg-accent-hover'
: 'border-border bg-bg-elevated text-fg hover:bg-bg-inset hover:border-border-strong'"
@click="timing = opt"
>
{{ opt }}
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="bounds.update()"
>
Update
</button>
</div>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
<span class="font-mono">next-frame</span> batches rapid scroll/resize reads into one measurement per animation frame.
</p>
</div>
@@ -25,12 +25,12 @@ function fmt(n: number) {
</script>
<template>
<div class="w-full max-w-md flex flex-col gap-4">
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-4">
<div class="demo-stack max-w-md">
<div class="demo-card p-4 flex flex-col gap-4">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">ResizeObserver</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="size-1.5 rounded-full" :class="observing ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
<span class="demo-label">ResizeObserver</span>
<span class="demo-badge">
<span class="size-1.5 rounded-full" :class="observing ? 'bg-emerald-500' : 'bg-fg-subtle'" />
{{ observing ? 'Observing' : 'Stopped' }}
</span>
</div>
@@ -40,25 +40,25 @@ function fmt(n: number) {
ref="target"
readonly
:style="{ padding: `${padding}px` }"
class="w-full min-h-24 resize rounded-lg border border-(--border-strong) bg-(--bg-inset) text-sm leading-relaxed text-(--fg-muted) focus:outline-none focus:ring-2 focus:ring-(--ring)"
class="w-full min-h-24 resize rounded-lg border border-border-strong bg-bg-inset text-sm leading-relaxed text-fg-muted focus:outline-none focus:ring-2 focus:ring-ring"
>Drag the bottom-right corner to resize me. The width and height update live as the ResizeObserver fires. Border-box sizing includes the padding below.</textarea>
<div class="grid grid-cols-2 gap-3">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ fmt(width) }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">width px</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ fmt(width) }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">width px</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ fmt(height) }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">height px</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ fmt(height) }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">height px</div>
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="size-padding">Padding (border-box)</label>
<span class="font-mono text-xs tabular-nums text-(--fg-muted)">{{ padding }}px</span>
<label class="demo-label" for="size-padding">Padding (border-box)</label>
<span class="font-mono text-xs tabular-nums text-fg-muted">{{ padding }}px</span>
</div>
<input
id="size-padding"
@@ -67,20 +67,20 @@ function fmt(n: number) {
min="0"
max="40"
step="2"
class="w-full accent-(--accent) cursor-pointer"
class="w-full accent-accent cursor-pointer"
>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
class="demo-btn disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
:disabled="!observing"
@click="toggleObserver"
>
{{ observing ? 'Stop observing' : 'Observer stopped' }}
</button>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
With <span class="font-mono">box: 'border-box'</span> the reported size includes padding, so the slider changes the numbers without resizing the element.
</p>
</div>
@@ -22,16 +22,16 @@ watch(isVisible, (visible, was) => {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">IntersectionObserver</span>
<span class="demo-label">IntersectionObserver</span>
<span
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
:class="isVisible
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'border-(--border) bg-(--bg-inset) text-(--fg-muted)'"
: 'border-border bg-bg-inset text-fg-muted'"
>
<span class="size-1.5 rounded-full" :class="isVisible ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
<span class="size-1.5 rounded-full" :class="isVisible ? 'bg-emerald-500' : 'bg-fg-subtle'" />
{{ isVisible ? 'In view' : 'Hidden' }}
</span>
</div>
@@ -39,38 +39,38 @@ watch(isVisible, (visible, was) => {
<!-- Scrollable root; the target card sits below the fold until scrolled. -->
<div
ref="root"
class="h-44 overflow-y-auto rounded-xl border border-(--border) bg-(--bg-inset) p-3"
class="h-44 overflow-y-auto rounded-xl border border-border bg-bg-inset p-3"
>
<p class="text-sm text-(--fg-subtle)">Scroll down inside this box</p>
<p class="text-sm text-fg-subtle">Scroll down inside this box</p>
<div class="h-40" />
<div
ref="target"
class="rounded-lg border border-(--border) bg-(--accent) p-4 text-center text-(--accent-fg) shadow transition"
class="rounded-lg border border-border bg-accent p-4 text-center text-accent-fg shadow transition"
:class="isVisible ? 'opacity-100 scale-100' : 'opacity-60 scale-95'"
>
<div class="text-sm font-semibold">Target element</div>
<div class="text-xs opacity-80">at least 50% visible to count</div>
</div>
<div class="h-24" />
<p class="text-sm text-(--fg-subtle)">and back up to hide it again.</p>
<p class="text-sm text-fg-subtle">and back up to hide it again.</p>
</div>
<div class="grid grid-cols-2 gap-3">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ seenCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">times seen</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ seenCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">times seen</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center flex flex-col items-center justify-center">
<div class="text-sm font-semibold" :class="isActive ? 'text-emerald-600 dark:text-emerald-400' : 'text-(--fg-muted)'">
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center flex flex-col items-center justify-center">
<div class="text-sm font-semibold" :class="isActive ? 'text-emerald-600 dark:text-emerald-400' : 'text-fg-muted'">
{{ isActive ? 'Active' : 'Paused' }}
</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">observer</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">observer</div>
</div>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="isActive ? pause() : resume()"
>
{{ isActive ? 'Pause observer' : 'Resume observer' }}
@@ -26,31 +26,31 @@ const fields = [
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<component :is="GuardHost" v-if="enabled" />
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Focus guards</span>
<span class="demo-label">Focus guards</span>
<span
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
:class="enabled
? 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'border-(--border) bg-(--bg-inset) text-(--fg-muted)'"
: 'border-border bg-bg-inset text-fg-muted'"
>
<span class="size-1.5 rounded-full" :class="enabled ? 'bg-emerald-500' : 'bg-(--fg-subtle)'" />
<span class="size-1.5 rounded-full" :class="enabled ? 'bg-emerald-500' : 'bg-fg-subtle'" />
{{ enabled ? 'Mounted' : 'Off' }}
</span>
</div>
<!-- A small focusable form to feel tab order with the guards active. -->
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-3">
<div class="demo-card p-4 flex flex-col gap-3">
<div v-for="f in fields" :key="f.id" class="flex flex-col gap-1">
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" :for="f.id">{{ f.label }}</label>
<label class="demo-label" :for="f.id">{{ f.label }}</label>
<input
:id="f.id"
:value="f.value"
type="text"
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) placeholder:text-(--fg-subtle) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
class="demo-input"
@focus="focused = f.label"
@blur="focused = null"
>
@@ -58,7 +58,7 @@ const fields = [
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
class="demo-btn-primary"
@focus="focused = 'Submit'"
@blur="focused = null"
>
@@ -66,19 +66,19 @@ const fields = [
</button>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
<div class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums">
focused: {{ focused ?? 'nothing' }}
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="enabled = !enabled"
>
{{ enabled ? 'Remove focus guards' : 'Mount focus guards' }}
</button>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
Guards are invisible <span class="font-mono">tabindex="0"</span> sentinels inserted at the page boundaries. Tab past the last field with them on to feel focus wrap around for overlays and modals.
</p>
</div>
@@ -48,10 +48,10 @@ const activeId = computed(() => {
</script>
<template>
<div class="w-full max-w-md flex flex-col gap-4">
<div class="demo-stack max-w-md">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Scroll spy</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-label">Scroll spy</span>
<span class="demo-badge">
active: {{ activeId ?? '—' }}
</span>
</div>
@@ -64,8 +64,8 @@ const activeId = computed(() => {
:key="s.id"
class="flex items-center gap-2 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition"
:class="activeId === s.id
? 'border-(--border-strong) bg-(--bg-inset) text-(--fg)'
: 'border-transparent text-(--fg-muted)'"
? 'border-border-strong bg-bg-inset text-fg'
: 'border-transparent text-fg-muted'"
>
<span class="size-2 rounded-full transition" :class="[s.color, activeId === s.id ? 'opacity-100' : 'opacity-30']" />
{{ s.label }}
@@ -75,29 +75,29 @@ const activeId = computed(() => {
<!-- Scrollable content; each section is an observed target. -->
<div
ref="root"
class="h-56 flex-1 overflow-y-auto rounded-xl border border-(--border) bg-(--bg-inset) p-3 flex flex-col gap-3 scroll-smooth"
class="h-56 flex-1 overflow-y-auto rounded-xl border border-border bg-bg-inset p-3 flex flex-col gap-3 scroll-smooth"
>
<section
v-for="s in sections"
:key="s.id"
ref="itemEls"
:data-id="s.id"
class="rounded-lg border border-(--border) bg-(--bg-elevated) p-4 transition"
:class="activeId === s.id ? 'ring-2 ring-(--ring)' : ''"
class="rounded-lg border border-border bg-bg-elevated p-4 transition"
:class="activeId === s.id ? 'ring-2 ring-ring' : ''"
>
<h3 class="text-sm font-semibold text-(--fg)">{{ s.label }}</h3>
<p class="mt-1 text-xs text-(--fg-subtle)">
<h3 class="text-sm font-semibold text-fg">{{ s.label }}</h3>
<p class="mt-1 text-xs text-fg-subtle">
Visibility: {{ Math.round((ratios[s.id] ?? 0) * 100) }}%
</p>
<div class="mt-2 h-16 rounded bg-(--bg-inset)" />
<div class="mt-2 h-16 rounded bg-bg-inset" />
</section>
</div>
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between">
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="io-threshold">Active threshold</label>
<span class="font-mono text-xs tabular-nums text-(--fg-muted)">{{ Math.round(threshold * 100) }}%</span>
<label class="demo-label" for="io-threshold">Active threshold</label>
<span class="font-mono text-xs tabular-nums text-fg-muted">{{ Math.round(threshold * 100) }}%</span>
</div>
<input
id="io-threshold"
@@ -106,9 +106,9 @@ const activeId = computed(() => {
min="0.1"
max="0.9"
step="0.1"
class="w-full accent-(--accent) cursor-pointer"
class="w-full accent-accent cursor-pointer"
>
<p class="text-xs text-(--fg-subtle)">A section becomes active once at least this much of it is visible inside the scroll root.</p>
<p class="text-xs text-fg-subtle">A section becomes active once at least this much of it is visible inside the scroll root.</p>
</div>
</div>
</template>
@@ -45,10 +45,10 @@ function removeLabel() {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">MutationObserver</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-label">MutationObserver</span>
<span class="demo-badge">
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-amber-500'" />
{{ isActive ? 'Observing' : 'Paused' }}
</span>
@@ -63,46 +63,46 @@ function removeLabel() {
<div
ref="target"
class="rounded-xl border p-4 transition"
:class="accent ? 'border-(--accent) bg-(--accent-subtle)' : 'border-(--border) bg-(--bg-elevated)'"
:class="accent ? 'border-accent bg-accent-subtle' : 'border-border bg-bg-elevated'"
:style="{ fontSize: `${fontSize}px` }"
>
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="accent ? 'text-(--accent-text)' : 'text-(--fg-subtle)'">
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="accent ? 'text-accent-text' : 'text-fg-subtle'">
Observed element
</div>
<div class="flex flex-wrap gap-1.5">
<span
v-for="(label, i) in labels"
:key="`${label}-${i}`"
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)"
class="inline-flex items-center rounded-md border border-border bg-bg-inset px-2 py-0.5 text-xs font-medium text-fg-muted"
>
{{ label }}
</span>
<span v-if="!labels.length" class="text-xs text-(--fg-subtle)">No labels</span>
<span v-if="!labels.length" class="text-xs text-fg-subtle">No labels</span>
</div>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ mutationCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">mutations</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ mutationCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">mutations</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="truncate font-mono text-sm font-bold text-(--fg)">{{ lastType }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">last record type</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="truncate font-mono text-sm font-bold text-fg">{{ lastType }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">last record type</div>
</div>
</div>
<div class="grid grid-cols-2 gap-2">
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="addLabel"
>
Add child
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
class="demo-btn disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
:disabled="!labels.length"
@click="removeLabel"
>
@@ -110,31 +110,31 @@ function removeLabel() {
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="accent = !accent"
>
Toggle class
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="fontSize = fontSize === 14 ? 18 : 14"
>
Toggle style
</button>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
<div class="mb-1.5 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Recent records</div>
<ul v-if="log.length" class="flex flex-col gap-1 font-mono text-xs text-(--fg)">
<div class="rounded-lg border border-border bg-bg-inset p-3">
<div class="demo-label mb-1.5">Recent records</div>
<ul v-if="log.length" class="flex flex-col gap-1 font-mono text-xs text-fg">
<li v-for="(entry, i) in log" :key="i" class="truncate">{{ entry }}</li>
</ul>
<p v-else class="font-mono text-xs text-(--fg-subtle)">Mutate the element above to record changes.</p>
<p v-else class="font-mono text-xs text-fg-subtle">Mutate the element above to record changes.</p>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
class="demo-btn-primary"
@click="isActive ? pause() : resume()"
>
{{ isActive ? 'Pause observer' : 'Resume observer' }}
@@ -26,73 +26,73 @@ const parentInfo = computed(() => {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">parentElement</span>
<div class="demo-stack max-w-sm">
<span class="demo-label">parentElement</span>
<!-- Two candidate parents; the child is teleported between them via v-if -->
<div class="flex flex-col gap-3">
<div
id="wrapper-a"
class="rounded-xl border border-dashed p-3 transition"
:class="!inSecond ? 'border-(--accent) bg-(--accent-subtle)' : 'border-(--border) bg-(--bg-elevated)'"
:class="!inSecond ? 'border-accent bg-accent-subtle' : 'border-border bg-bg-elevated'"
>
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="!inSecond ? 'text-(--accent-text)' : 'text-(--fg-subtle)'">
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="!inSecond ? 'text-accent-text' : 'text-fg-subtle'">
#wrapper-a
</div>
<div
v-if="!inSecond"
ref="child"
class="rounded-lg border border-(--border) bg-(--bg-inset) px-3 py-2 text-sm font-medium text-(--fg)"
class="rounded-lg border border-border bg-bg-inset px-3 py-2 text-sm font-medium text-fg"
>
Tracked child
</div>
<div v-else class="text-xs text-(--fg-subtle)">empty</div>
<div v-else class="text-xs text-fg-subtle">empty</div>
</div>
<div
id="wrapper-b"
class="rounded-xl border border-dashed p-3 transition"
:class="inSecond ? 'border-(--accent) bg-(--accent-subtle)' : 'border-(--border) bg-(--bg-elevated)'"
:class="inSecond ? 'border-accent bg-accent-subtle' : 'border-border bg-bg-elevated'"
>
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="inSecond ? 'text-(--accent-text)' : 'text-(--fg-subtle)'">
<div class="mb-2 text-xs font-medium uppercase tracking-wide" :class="inSecond ? 'text-accent-text' : 'text-fg-subtle'">
#wrapper-b
</div>
<div
v-if="inSecond"
ref="child"
class="rounded-lg border border-(--border) bg-(--bg-inset) px-3 py-2 text-sm font-medium text-(--fg)"
class="rounded-lg border border-border bg-bg-inset px-3 py-2 text-sm font-medium text-fg"
>
Tracked child
</div>
<div v-else class="text-xs text-(--fg-subtle)">empty</div>
<div v-else class="text-xs text-fg-subtle">empty</div>
</div>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
class="demo-btn-primary"
@click="inSecond = !inSecond"
>
Move child to #wrapper-{{ inSecond ? 'a' : 'b' }}
</button>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
<div class="mb-2 font-sans text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Resolved parent</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums">
<div class="demo-label mb-2 font-sans">Resolved parent</div>
<template v-if="parentInfo">
<div class="flex justify-between gap-2">
<span class="text-(--fg-subtle)">tag</span>
<span class="text-(--accent-text)">&lt;{{ parentInfo.tag }}&gt;</span>
<span class="text-fg-subtle">tag</span>
<span class="text-accent-text">&lt;{{ parentInfo.tag }}&gt;</span>
</div>
<div class="flex justify-between gap-2">
<span class="text-(--fg-subtle)">id</span>
<span class="text-fg-subtle">id</span>
<span class="truncate">{{ parentInfo.id }}</span>
</div>
<div class="flex justify-between gap-2">
<span class="text-(--fg-subtle)">size</span>
<span class="text-fg-subtle">size</span>
<span>{{ parentInfo.width }} × {{ parentInfo.height }}</span>
</div>
</template>
<div v-else class="font-sans text-xs text-(--fg-subtle)">Resolving parent on the client</div>
<div v-else class="font-sans text-xs text-fg-subtle">Resolving parent on the client</div>
</div>
</div>
</template>
@@ -22,10 +22,10 @@ const { isSupported, isActive, pause, resume } = useResizeObserver(
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">ResizeObserver</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-label">ResizeObserver</span>
<span class="demo-badge">
<span class="size-1.5 rounded-full transition" :class="isActive ? 'bg-emerald-500' : 'bg-amber-500'" />
{{ isActive ? 'Observing' : 'Paused' }}
</span>
@@ -39,40 +39,40 @@ const { isSupported, isActive, pause, resume } = useResizeObserver(
<!-- Drag the bottom-right handle to resize; the observer reports new dimensions -->
<div
ref="target"
class="relative grid min-h-32 min-w-40 max-w-full resize overflow-auto rounded-xl border border-(--border) bg-(--bg-elevated) p-4 place-items-center"
class="demo-card relative grid min-h-32 min-w-40 max-w-full resize overflow-auto p-4 place-items-center"
style="width: 16rem; height: 8rem;"
>
<div class="pointer-events-none select-none text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">
{{ size.width }}<span class="text-(--fg-subtle)"> × </span>{{ size.height }}
<div class="demo-stat text-3xl">
{{ size.width }}<span class="text-fg-subtle"> × </span>{{ size.height }}
</div>
<div class="mt-1 text-xs text-(--fg-subtle)">drag the bottom-right corner</div>
<div class="mt-1 text-xs text-fg-subtle">drag the bottom-right corner</div>
</div>
</div>
<div class="grid grid-cols-3 gap-2">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ size.width }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">width px</div>
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
<div class="demo-stat text-lg">{{ size.width }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">width px</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ size.height }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">height px</div>
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
<div class="demo-stat text-lg">{{ size.height }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">height px</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-2 text-center">
<div class="font-mono text-lg font-bold tabular-nums text-(--fg)">{{ callbacks }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">callbacks</div>
<div class="rounded-lg border border-border bg-bg-inset p-2 text-center">
<div class="demo-stat text-lg">{{ callbacks }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">callbacks</div>
</div>
</div>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
class="demo-btn-primary"
@click="isActive ? pause() : resume()"
>
{{ isActive ? 'Pause observer' : 'Resume observer' }}
</button>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
While paused, resizing won't update the readout until you resume.
</p>
</template>
@@ -14,39 +14,39 @@ watch(focused, (isFocused) => {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Window focus</span>
<div class="demo-stack max-w-sm">
<span class="demo-label">Window focus</span>
<div
class="rounded-xl border p-6 text-center transition"
:class="focused
? 'border-emerald-500/30 bg-emerald-500/10'
: 'border-(--border) bg-(--bg-inset)'"
: 'border-border bg-bg-inset'"
>
<div
class="mx-auto flex size-14 items-center justify-center rounded-full transition"
:class="focused
? 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400'
: 'bg-(--bg-elevated) text-(--fg-subtle)'"
: 'bg-bg-elevated text-fg-subtle'"
>
<span class="size-3 rounded-full transition" :class="focused ? 'bg-emerald-500 animate-pulse' : 'bg-(--fg-subtle)'" />
<span class="size-3 rounded-full transition" :class="focused ? 'bg-emerald-500 animate-pulse' : 'bg-fg-subtle'" />
</div>
<div class="mt-3 text-lg font-semibold text-(--fg)">
<div class="mt-3 text-lg font-semibold text-fg">
{{ focused ? 'Window focused' : 'Window blurred' }}
</div>
<p class="mt-1 text-sm text-(--fg-muted)">
<p class="mt-1 text-sm text-fg-muted">
{{ focused ? 'Click outside or switch apps to blur.' : 'Click back into this window to refocus.' }}
</p>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ focused ? 'on' : 'off' }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">focused</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ focused ? 'on' : 'off' }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">focused</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ blurCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">times blurred</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ blurCount }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">times blurred</div>
</div>
</div>
</div>
@@ -25,53 +25,53 @@ function scrollToBottom() {
</script>
<template>
<div class="w-full max-w-sm flex flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Window scroll</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="size-1.5 rounded-full transition" :class="isScrolling ? 'bg-emerald-500 animate-pulse' : 'bg-(--fg-subtle)'" />
<span class="demo-label">Window scroll</span>
<span class="demo-badge">
<span class="size-1.5 rounded-full transition" :class="isScrolling ? 'bg-emerald-500 animate-pulse' : 'bg-fg-subtle'" />
{{ isScrolling ? 'Scrolling' : 'Idle' }}
</span>
</div>
<div class="grid grid-cols-2 gap-2">
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ Math.round(x) }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">scroll x</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ Math.round(x) }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">scroll x</div>
</div>
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 text-center">
<div class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ Math.round(y) }}</div>
<div class="text-[10px] uppercase tracking-wide text-(--fg-subtle)">scroll y</div>
<div class="rounded-lg border border-border bg-bg-inset p-3 text-center">
<div class="demo-stat text-3xl">{{ Math.round(y) }}</div>
<div class="text-[10px] uppercase tracking-wide text-fg-subtle">scroll y</div>
</div>
</div>
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-3">
<div class="demo-card p-4 flex flex-col gap-3">
<div class="flex items-center justify-between">
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Direction</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-label">Direction</span>
<span class="demo-badge">
{{ verticalDirection }}
</span>
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between text-sm">
<span class="text-(--fg-muted)">Arrived at top</span>
<span class="text-fg-muted">Arrived at top</span>
<span
class="inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
:class="arrivedState.top
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'bg-(--bg-inset) text-(--fg-subtle)'"
: 'bg-bg-inset text-fg-subtle'"
>
{{ arrivedState.top ? 'yes' : 'no' }}
</span>
</div>
<div class="flex items-center justify-between text-sm">
<span class="text-(--fg-muted)">Arrived at bottom</span>
<span class="text-fg-muted">Arrived at bottom</span>
<span
class="inline-flex items-center gap-1.5 rounded-md px-2 py-0.5 text-xs font-medium"
:class="arrivedState.bottom
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
: 'bg-(--bg-inset) text-(--fg-subtle)'"
: 'bg-bg-inset text-fg-subtle'"
>
{{ arrivedState.bottom ? 'yes' : 'no' }}
</span>
@@ -82,7 +82,7 @@ function scrollToBottom() {
<div class="grid grid-cols-2 gap-2">
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
class="demo-btn-primary disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
:disabled="arrivedState.top"
@click="scrollToTop"
>
@@ -90,7 +90,7 @@ function scrollToBottom() {
</button>
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-1.5 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
class="demo-btn-primary disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
:disabled="arrivedState.bottom"
@click="scrollToBottom"
>
@@ -100,12 +100,12 @@ function scrollToBottom() {
<button
type="button"
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg-elevated) px-3 py-1.5 text-sm font-medium text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
class="demo-btn"
@click="measure"
>
Re-measure
</button>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
Scroll the documentation page to watch the position and arrived edges update live.
</p>
</div>
@@ -13,57 +13,57 @@ const ratioPercent = computed(() => {
</script>
<template>
<div class="flex w-full max-w-sm flex-col gap-4">
<div class="demo-stack max-w-sm">
<div class="grid grid-cols-2 gap-3">
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 text-center">
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<div class="demo-card p-4 text-center">
<p class="demo-label">
Width
</p>
<p class="font-mono text-3xl font-bold tabular-nums text-(--fg)">
<p class="demo-stat text-3xl">
{{ width }}
</p>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
px
</p>
</div>
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 text-center">
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
<div class="demo-card p-4 text-center">
<p class="demo-label">
Height
</p>
<p class="font-mono text-3xl font-bold tabular-nums text-(--fg)">
<p class="demo-stat text-3xl">
{{ height }}
</p>
<p class="text-xs text-(--fg-subtle)">
<p class="text-xs text-fg-subtle">
px
</p>
</div>
</div>
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
<div class="demo-card p-4">
<div
class="relative flex items-center justify-center overflow-hidden rounded-lg border border-(--border-strong) bg-(--bg-inset) transition-all"
class="relative flex items-center justify-center overflow-hidden rounded-lg border border-border-strong bg-bg-inset transition-all"
:style="{ aspectRatio: `${Math.max(width, 1)} / ${Math.max(height, 1)}` }"
>
<div
class="absolute inset-y-0 left-0 bg-(--accent-subtle) transition-all"
class="absolute inset-y-0 left-0 bg-accent-subtle transition-all"
:style="{ width: `${ratioPercent}%` }"
/>
<span class="relative font-mono text-xs text-(--fg-muted) tabular-nums">
<span class="relative font-mono text-xs text-fg-muted tabular-nums">
{{ width }} × {{ height }}
</span>
</div>
</div>
<div class="flex flex-wrap items-center justify-center gap-2">
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-badge">
{{ orientation }}
</span>
<span class="inline-flex items-center gap-1.5 rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 text-xs font-medium text-(--fg-muted)">
<span class="demo-badge">
ratio {{ aspect }}
</span>
</div>
<p class="text-center text-xs text-(--fg-subtle)">
<p class="text-center text-xs text-fg-subtle">
Resize your browser window to watch the values update live.
</p>
</div>