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:
@@ -58,9 +58,9 @@ const ids = [1, 2, 3, 4, 99];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Fetch user by id</span>
|
||||
<span class="demo-label">Fetch user by id</span>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="id in ids"
|
||||
@@ -68,8 +68,8 @@ const ids = [1, 2, 3, 4, 99];
|
||||
type="button"
|
||||
class="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="userId === id
|
||||
? '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="userId = id"
|
||||
>
|
||||
#{{ id }}
|
||||
@@ -77,34 +77,34 @@ const ids = [1, 2, 3, 4, 99];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 min-h-32 flex flex-col justify-center">
|
||||
<div class="demo-card p-4 min-h-32 flex flex-col justify-center">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<div v-if="evaluating" key="loading" class="flex items-center gap-2 text-sm text-(--fg-muted)">
|
||||
<span class="size-4 animate-spin rounded-full border-2 border-(--border) border-t-(--accent)" />
|
||||
<div v-if="evaluating" key="loading" class="flex items-center gap-2 text-sm text-fg-muted">
|
||||
<span class="size-4 animate-spin rounded-full border-2 border-border border-t-accent" />
|
||||
Resolving promise…
|
||||
</div>
|
||||
|
||||
<div v-else-if="error" key="error" class="flex flex-col gap-1">
|
||||
<span class="text-sm font-medium text-red-600 dark:text-red-400">Evaluation failed</span>
|
||||
<span class="font-mono text-xs text-(--fg-muted)">{{ error }}</span>
|
||||
<span class="font-mono text-xs text-fg-muted">{{ error }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="user" key="user" class="flex flex-col gap-1">
|
||||
<span class="text-lg font-semibold text-(--fg)">{{ user.name }}</span>
|
||||
<span class="text-sm text-(--fg-muted)">{{ user.role }}</span>
|
||||
<span class="inline-flex w-fit 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="text-lg font-semibold text-fg">{{ user.name }}</span>
|
||||
<span class="text-sm text-fg-muted">{{ user.role }}</span>
|
||||
<span class="demo-badge w-fit">
|
||||
{{ user.city }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-else key="empty" class="text-sm text-(--fg-subtle)">
|
||||
<div v-else key="empty" class="text-sm text-fg-subtle">
|
||||
Awaiting first resolution…
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">evaluating</span>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex items-center justify-between">
|
||||
<span class="demo-label">evaluating</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 font-mono text-sm tabular-nums"
|
||||
:class="evaluating ? 'text-amber-600 dark:text-amber-400' : 'text-emerald-600 dark:text-emerald-400'"
|
||||
|
||||
@@ -26,7 +26,7 @@ const rules = computedEager(() => [
|
||||
]);
|
||||
|
||||
const tones = [
|
||||
'bg-(--border)',
|
||||
'bg-border',
|
||||
'bg-red-500',
|
||||
'bg-amber-500',
|
||||
'bg-sky-500',
|
||||
@@ -35,45 +35,45 @@ const tones = [
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="pwd">Password</label>
|
||||
<label class="demo-label" for="pwd">Password</label>
|
||||
<input
|
||||
id="pwd"
|
||||
v-model="password"
|
||||
type="text"
|
||||
placeholder="Type a password…"
|
||||
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>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm text-(--fg-muted)">Strength</span>
|
||||
<span class="font-mono text-sm font-medium tabular-nums text-(--fg)">{{ label }}</span>
|
||||
<span class="text-sm text-fg-muted">Strength</span>
|
||||
<span class="font-mono text-sm font-medium tabular-nums text-fg">{{ label }}</span>
|
||||
</div>
|
||||
<div class="flex gap-1.5">
|
||||
<div
|
||||
v-for="i in 4"
|
||||
:key="i"
|
||||
class="h-1.5 flex-1 rounded-full transition-colors duration-300"
|
||||
:class="i <= score ? tones[score] : 'bg-(--bg-inset)'"
|
||||
:class="i <= score ? tones[score] : 'bg-bg-inset'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-2">
|
||||
<div class="demo-card p-4 flex flex-col gap-2">
|
||||
<div
|
||||
v-for="rule in rules"
|
||||
:key="rule.text"
|
||||
class="flex items-center gap-2 text-sm transition-colors"
|
||||
:class="rule.ok ? 'text-(--fg)' : 'text-(--fg-subtle)'"
|
||||
:class="rule.ok ? 'text-fg' : 'text-fg-subtle'"
|
||||
>
|
||||
<span
|
||||
class="grid size-4 place-items-center rounded-full text-[10px] transition-colors"
|
||||
:class="rule.ok
|
||||
? 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-400'
|
||||
: 'bg-(--bg-inset) text-(--fg-subtle)'"
|
||||
: 'bg-bg-inset text-fg-subtle'"
|
||||
>
|
||||
{{ rule.ok ? '✓' : '○' }}
|
||||
</span>
|
||||
@@ -81,8 +81,8 @@ const tones = [
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">length</span>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums flex items-center justify-between">
|
||||
<span class="text-fg-muted">length</span>
|
||||
<span>{{ length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -27,12 +27,12 @@ function stop() {
|
||||
</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 gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Base price (tracked)</span>
|
||||
<span class="font-mono text-sm tabular-nums text-(--accent-text)">{{ source }}</span>
|
||||
<span class="demo-label">Base price (tracked)</span>
|
||||
<span class="font-mono text-sm tabular-nums text-accent-text">{{ source }}</span>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="source"
|
||||
@@ -40,14 +40,14 @@ function stop() {
|
||||
min="0"
|
||||
max="500"
|
||||
step="10"
|
||||
class="w-full accent-(--accent)"
|
||||
class="w-full accent-accent"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Tax % (untracked)</span>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg-muted)">{{ tax }}</span>
|
||||
<span class="demo-label">Tax % (untracked)</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg-muted">{{ tax }}</span>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="tax"
|
||||
@@ -55,37 +55,37 @@ function stop() {
|
||||
min="0"
|
||||
max="30"
|
||||
step="1"
|
||||
class="w-full accent-(--fg-muted)"
|
||||
class="w-full accent-fg-muted"
|
||||
>
|
||||
<span class="text-xs text-(--fg-subtle)">
|
||||
<span class="text-xs text-fg-subtle">
|
||||
Changing tax alone won't recompute — trigger to pull it in.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Total</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ total }}</span>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex items-center justify-between">
|
||||
<span class="demo-label">Total</span>
|
||||
<span class="demo-stat text-3xl">{{ total }}</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-1.5">
|
||||
<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="total.trigger()"
|
||||
>
|
||||
Trigger
|
||||
</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="peek"
|
||||
>
|
||||
Peek
|
||||
</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="detached"
|
||||
@click="stop"
|
||||
>
|
||||
@@ -95,12 +95,12 @@ function stop() {
|
||||
|
||||
<div class="flex flex-col gap-1.5 text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">Getter runs</span>
|
||||
<span class="font-mono tabular-nums text-(--fg)">{{ recomputes }}</span>
|
||||
<span class="text-fg-muted">Getter runs</span>
|
||||
<span class="font-mono tabular-nums text-fg">{{ recomputes }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">Last peek</span>
|
||||
<span class="font-mono tabular-nums text-(--fg)">{{ peeked ?? '—' }}</span>
|
||||
<span class="text-fg-muted">Last peek</span>
|
||||
<span class="font-mono tabular-nums text-fg">{{ peeked ?? '—' }}</span>
|
||||
</div>
|
||||
<div v-if="detached" class="text-xs text-amber-600 dark:text-amber-400">
|
||||
Source watcher stopped — only Trigger updates the total now.
|
||||
|
||||
@@ -2,14 +2,14 @@ import { customRef, watch } from 'vue';
|
||||
import type {
|
||||
ComputedGetter,
|
||||
ComputedRef,
|
||||
MultiWatchSources,
|
||||
WatchOptions,
|
||||
WatchSource,
|
||||
WatchStopHandle,
|
||||
WritableComputedOptions,
|
||||
WritableComputedRef,
|
||||
} from 'vue';
|
||||
|
||||
type MultiWatchSources = Array<WatchSource<unknown> | object>;
|
||||
import { isFunction } from '@robonen/stdlib';
|
||||
|
||||
export interface ComputedWithControlExtra<T> {
|
||||
/**
|
||||
@@ -146,7 +146,7 @@ export function computedWithControl<T>(
|
||||
function isGetter<T>(
|
||||
fn: ComputedGetter<T> | WritableComputedOptions<T>,
|
||||
): fn is ComputedGetter<T> {
|
||||
return typeof fn === 'function';
|
||||
return isFunction(fn);
|
||||
}
|
||||
|
||||
function noopTrack(): void {}
|
||||
|
||||
@@ -27,13 +27,13 @@ function toggleMute() {
|
||||
</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 gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<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)">Volume</span>
|
||||
<span class="demo-label">Volume</span>
|
||||
<span
|
||||
class="font-mono text-3xl font-bold tabular-nums transition-colors"
|
||||
:class="volume.isMuted ? 'text-(--fg-subtle)' : 'text-(--fg)'"
|
||||
:class="volume.isMuted ? 'text-fg-subtle' : 'text-fg'"
|
||||
>{{ volume.percent }}</span>
|
||||
</div>
|
||||
|
||||
@@ -43,20 +43,20 @@ function toggleMute() {
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
class="w-full accent-(--accent)"
|
||||
class="w-full accent-accent"
|
||||
>
|
||||
|
||||
<div class="flex gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg) 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="inline-flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-border bg-bg 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"
|
||||
@click="toggleMute"
|
||||
>
|
||||
{{ volume.isMuted ? 'Unmute' : 'Mute' }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-(--border) bg-(--bg) 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="inline-flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-border bg-bg 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"
|
||||
@click="volume.max()"
|
||||
>
|
||||
Max
|
||||
@@ -64,24 +64,24 @@ function toggleMute() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex flex-col gap-2 font-mono text-sm tabular-nums">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex flex-col gap-2 font-mono text-sm tabular-nums">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">volume.value</span>
|
||||
<span class="text-(--fg)">{{ volume.value }}</span>
|
||||
<span class="text-fg-muted">volume.value</span>
|
||||
<span class="text-fg">{{ volume.value }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">volume.percent</span>
|
||||
<span class="text-(--fg)">{{ volume.percent }}</span>
|
||||
<span class="text-fg-muted">volume.percent</span>
|
||||
<span class="text-fg">{{ volume.percent }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-muted)">volume.isMuted</span>
|
||||
<span class="text-fg-muted">volume.isMuted</span>
|
||||
<span :class="volume.isMuted ? 'text-amber-600 dark:text-amber-400' : 'text-emerald-600 dark:text-emerald-400'">
|
||||
{{ volume.isMuted }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
One ref carries its value, derived properties, and methods — no destructuring of an object needed.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -28,31 +28,31 @@ function setDiscount(value: number) {
|
||||
</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 gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-4">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Unit price</span>
|
||||
<span class="demo-label">Unit price</span>
|
||||
<input
|
||||
v-model.number="price"
|
||||
type="number"
|
||||
min="0"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) tabular-nums transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="w-full rounded-lg border border-border bg-bg px-3 py-2 text-sm text-fg tabular-nums transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Quantity</span>
|
||||
<span class="demo-label">Quantity</span>
|
||||
<input
|
||||
v-model.number="quantity"
|
||||
type="number"
|
||||
min="1"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-sm text-(--fg) tabular-nums transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="w-full rounded-lg border border-border bg-bg px-3 py-2 text-sm text-fg tabular-nums transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Discount</span>
|
||||
<span class="demo-label">Discount</span>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<button
|
||||
v-for="p in presets"
|
||||
@@ -60,8 +60,8 @@ function setDiscount(value: number) {
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center rounded-lg border px-3 py-1.5 text-sm font-medium tabular-nums transition active:scale-[0.98] cursor-pointer"
|
||||
:class="cart.discount === p
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg) hover:bg-(--accent-hover)'
|
||||
: 'border-(--border) bg-(--bg) 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 text-fg hover:bg-bg-inset hover:border-border-strong'"
|
||||
@click="setDiscount(p)"
|
||||
>
|
||||
{{ p }}%
|
||||
@@ -70,24 +70,24 @@ function setDiscount(value: number) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex flex-col gap-2 font-mono text-sm tabular-nums">
|
||||
<div class="flex items-center justify-between text-(--fg-muted)">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex flex-col gap-2 font-mono text-sm tabular-nums">
|
||||
<div class="flex items-center justify-between text-fg-muted">
|
||||
<span>Subtotal</span>
|
||||
<span class="text-(--fg)">${{ cart.subtotal }}</span>
|
||||
<span class="text-fg">${{ cart.subtotal }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-(--fg-muted)">
|
||||
<div class="flex items-center justify-between text-fg-muted">
|
||||
<span>Saved ({{ cart.discount }}%)</span>
|
||||
<span class="text-emerald-600 dark:text-emerald-400">-${{ cart.saved }}</span>
|
||||
</div>
|
||||
<div class="h-px bg-(--border)" />
|
||||
<div class="h-px bg-border" />
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg)">Total</span>
|
||||
<span class="text-2xl font-bold text-(--fg)">${{ cart.total }}</span>
|
||||
<span class="text-fg">Total</span>
|
||||
<span class="text-2xl font-bold text-fg">${{ cart.total }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
Each field reads from a single cached getter; writing <code class="text-(--fg-muted)">cart.discount</code> flows back to the source ref.
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Each field reads from a single cached getter; writing <code class="text-fg-muted">cart.discount</code> flows back to the source ref.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,22 +20,22 @@ const roles = ['admin', 'editor', 'viewer'] as const;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="space-y-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card space-y-3 p-4">
|
||||
<p class="demo-label">
|
||||
Source object
|
||||
</p>
|
||||
|
||||
<label class="flex flex-col gap-1">
|
||||
<span class="text-xs font-medium text-(--fg-muted)">name</span>
|
||||
<span class="text-xs font-medium text-fg-muted">name</span>
|
||||
<input
|
||||
v-model="user.name"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="text-xs font-medium text-(--fg-muted)">role</span>
|
||||
<span class="text-xs font-medium text-fg-muted">role</span>
|
||||
<div class="flex gap-1.5">
|
||||
<button
|
||||
v-for="r in roles"
|
||||
@@ -43,8 +43,8 @@ const roles = ['admin', 'editor', 'viewer'] as const;
|
||||
type="button"
|
||||
class="rounded-md border px-2 py-0.5 text-xs font-medium transition cursor-pointer"
|
||||
:class="user.role === r
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg)'
|
||||
: 'border-(--border) bg-(--bg-inset) text-(--fg-muted) hover:border-(--border-strong)'"
|
||||
? 'border-transparent bg-accent text-accent-fg'
|
||||
: 'border-border bg-bg-inset text-fg-muted hover:border-border-strong'"
|
||||
@click="user.role = r"
|
||||
>
|
||||
{{ r }}
|
||||
@@ -53,11 +53,11 @@ const roles = ['admin', 'editor', 'viewer'] as const;
|
||||
</div>
|
||||
|
||||
<label class="flex items-center justify-between gap-3">
|
||||
<span class="text-xs font-medium text-(--fg-muted)">active</span>
|
||||
<span class="text-xs font-medium text-fg-muted">active</span>
|
||||
<button
|
||||
type="button"
|
||||
class="relative h-5 w-9 rounded-full transition cursor-pointer"
|
||||
:class="user.active ? 'bg-(--accent)' : 'bg-(--bg-inset)'"
|
||||
:class="user.active ? 'bg-accent' : 'bg-bg-inset'"
|
||||
role="switch"
|
||||
:aria-checked="user.active"
|
||||
@click="user.active = !user.active"
|
||||
@@ -70,18 +70,18 @@ const roles = ['admin', 'editor', 'viewer'] as const;
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
Omit <code class="text-(--accent-text)">token</code>, <code class="text-(--accent-text)">email</code>
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label mb-2">
|
||||
Omit <code class="text-accent-text">token</code>, <code class="text-accent-text">email</code>
|
||||
</p>
|
||||
<pre class="overflow-x-auto rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-xs text-(--fg)">{{ safeUser }}</pre>
|
||||
<pre class="overflow-x-auto rounded-lg border border-border bg-bg-inset p-3 font-mono text-xs text-fg">{{ safeUser }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label mb-2">
|
||||
Predicate: drop booleans
|
||||
</p>
|
||||
<pre class="overflow-x-auto rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-xs text-(--fg)">{{ noFlags }}</pre>
|
||||
<pre class="overflow-x-auto rounded-lg border border-border bg-bg-inset p-3 font-mono text-xs text-fg">{{ noFlags }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { toValue } from 'vue';
|
||||
import { omit } from '@robonen/stdlib';
|
||||
import { isFunction, omit } from '@robonen/stdlib';
|
||||
import { reactiveComputed } from '@/composables/reactivity/reactiveComputed';
|
||||
|
||||
/**
|
||||
@@ -67,7 +67,7 @@ export function reactiveOmit<T extends object, K extends keyof T>(
|
||||
...keys: Array<K | K[] | ReactiveOmitPredicate<T>>
|
||||
): ReactiveOmitReturn<T, K> {
|
||||
const first = keys[0];
|
||||
const predicate = typeof first === 'function'
|
||||
const predicate = isFunction(first)
|
||||
? first as ReactiveOmitPredicate<T>
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -15,47 +15,47 @@ const display = reactivePick(settings, 'brightness', 'contrast');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="space-y-4 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card space-y-4 p-4">
|
||||
<p class="demo-label">
|
||||
Picked view (two-way)
|
||||
</p>
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="flex items-center justify-between text-sm text-(--fg)">
|
||||
<span class="flex items-center justify-between text-sm text-fg">
|
||||
<span>brightness</span>
|
||||
<span class="font-mono tabular-nums text-(--fg-muted)">{{ display.brightness }}</span>
|
||||
<span class="font-mono tabular-nums text-fg-muted">{{ display.brightness }}</span>
|
||||
</span>
|
||||
<input
|
||||
v-model.number="display.brightness"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="flex items-center justify-between text-sm text-(--fg)">
|
||||
<span class="flex items-center justify-between text-sm text-fg">
|
||||
<span>contrast</span>
|
||||
<span class="font-mono tabular-nums text-(--fg-muted)">{{ display.contrast }}</span>
|
||||
<span class="font-mono tabular-nums text-fg-muted">{{ display.contrast }}</span>
|
||||
</span>
|
||||
<input
|
||||
v-model.number="display.contrast"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label mb-2">
|
||||
Full source object
|
||||
</p>
|
||||
<pre class="overflow-x-auto rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-xs text-(--fg)">{{ settings }}</pre>
|
||||
<p class="mt-2 text-xs text-(--fg-subtle)">
|
||||
<pre class="overflow-x-auto rounded-lg border border-border bg-bg-inset p-3 font-mono text-xs text-fg">{{ settings }}</pre>
|
||||
<p class="mt-2 text-xs text-fg-subtle">
|
||||
Editing the picked view above writes straight back to the source.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -19,17 +19,17 @@ async function copy() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="space-y-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card space-y-3 p-4">
|
||||
<p class="demo-label">
|
||||
Live value
|
||||
</p>
|
||||
<div class="flex items-center justify-between rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<span class="font-mono text-lg font-semibold tabular-nums text-(--fg)">{{ status }}</span>
|
||||
<div class="flex items-center justify-between rounded-lg border border-border bg-bg-inset p-3">
|
||||
<span class="font-mono text-lg font-semibold tabular-nums text-fg">{{ status }}</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
|
||||
:class="status === 'Idle'
|
||||
? 'border-(--border) bg-(--bg-elevated) text-(--fg-muted)'
|
||||
? 'border-border bg-bg-elevated text-fg-muted'
|
||||
: 'border-emerald-500/30 bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'"
|
||||
>
|
||||
{{ status === 'Idle' ? 'reset' : 'active' }}
|
||||
@@ -39,21 +39,21 @@ async function copy() {
|
||||
<div class="flex flex-wrap 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="flash('Saved')"
|
||||
>
|
||||
Save
|
||||
</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="flash('Synced')"
|
||||
>
|
||||
Sync
|
||||
</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="flash('Uploading…')"
|
||||
>
|
||||
Upload
|
||||
@@ -61,10 +61,10 @@ async function copy() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="flex flex-col gap-1.5 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<span class="flex items-center justify-between text-sm text-(--fg)">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">reset delay</span>
|
||||
<span class="font-mono tabular-nums text-(--fg-muted)">{{ delay }}ms</span>
|
||||
<label class="demo-card flex flex-col gap-1.5 p-4">
|
||||
<span class="flex items-center justify-between text-sm text-fg">
|
||||
<span class="demo-label">reset delay</span>
|
||||
<span class="font-mono tabular-nums text-fg-muted">{{ delay }}ms</span>
|
||||
</span>
|
||||
<input
|
||||
v-model.number="delay"
|
||||
@@ -72,15 +72,15 @@ async function copy() {
|
||||
min="500"
|
||||
max="4000"
|
||||
step="250"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center justify-between rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<span class="text-sm text-(--fg)">Copy-to-clipboard pattern</span>
|
||||
<div class="demo-card flex items-center justify-between p-4">
|
||||
<span class="text-sm text-fg">Copy-to-clipboard pattern</span>
|
||||
<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="copy"
|
||||
>
|
||||
{{ copied ? 'Copied!' : 'Copy' }}
|
||||
|
||||
@@ -12,22 +12,22 @@ const pending = computed(() => search.value !== debounced.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<span class="demo-label">
|
||||
Type to search
|
||||
</span>
|
||||
<input
|
||||
v-model="search"
|
||||
placeholder="Start typing…"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1.5 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<span class="flex items-center justify-between text-sm text-(--fg)">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">debounce</span>
|
||||
<span class="font-mono tabular-nums text-(--fg-muted)">{{ ms }}ms</span>
|
||||
<label class="demo-card flex flex-col gap-1.5 p-4">
|
||||
<span class="flex items-center justify-between text-sm text-fg">
|
||||
<span class="demo-label">debounce</span>
|
||||
<span class="font-mono tabular-nums text-fg-muted">{{ ms }}ms</span>
|
||||
</span>
|
||||
<input
|
||||
v-model.number="ms"
|
||||
@@ -35,19 +35,19 @@ const pending = computed(() => search.value !== debounced.value);
|
||||
min="100"
|
||||
max="1500"
|
||||
step="50"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-3">
|
||||
<p class="mb-1 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-card p-3">
|
||||
<p class="demo-label mb-1">
|
||||
Source
|
||||
</p>
|
||||
<p class="truncate font-mono text-sm text-(--fg)">{{ search || '—' }}</p>
|
||||
<p class="truncate font-mono text-sm text-fg">{{ search || '—' }}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-3">
|
||||
<p class="mb-1 flex items-center gap-1.5 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-card p-3">
|
||||
<p class="demo-label mb-1 flex items-center gap-1.5">
|
||||
Debounced
|
||||
<span
|
||||
v-if="pending"
|
||||
@@ -55,7 +55,7 @@ const pending = computed(() => search.value !== debounced.value);
|
||||
aria-label="pending"
|
||||
/>
|
||||
</p>
|
||||
<p class="truncate font-mono text-sm text-(--accent-text)">{{ debounced || '—' }}</p>
|
||||
<p class="truncate font-mono text-sm text-accent-text">{{ debounced || '—' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,18 +11,18 @@ const name = refDefault(raw, fallback);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="space-y-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card space-y-3 p-4">
|
||||
<p class="demo-label">
|
||||
Resolved value
|
||||
</p>
|
||||
<div class="flex items-center justify-between rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<span class="font-mono text-lg font-semibold text-(--fg)">{{ name }}</span>
|
||||
<div class="flex items-center justify-between rounded-lg border border-border bg-bg-inset p-3">
|
||||
<span class="font-mono text-lg font-semibold text-fg">{{ name }}</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium"
|
||||
:class="raw == null
|
||||
? 'border-amber-500/30 bg-amber-500/10 text-amber-600 dark:text-amber-400'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg-muted)'"
|
||||
: 'border-border bg-bg-elevated text-fg-muted'"
|
||||
>
|
||||
{{ raw == null ? 'using default' : 'from source' }}
|
||||
</span>
|
||||
@@ -30,38 +30,38 @@ const name = refDefault(raw, fallback);
|
||||
</div>
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<span class="demo-label">
|
||||
Source ref (writes pass through)
|
||||
</span>
|
||||
<div class="flex gap-2">
|
||||
<input
|
||||
v-model="name"
|
||||
placeholder="Type a name…"
|
||||
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"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex shrink-0 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 shrink-0 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="raw == null"
|
||||
@click="raw = null"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-(--fg-subtle)">
|
||||
<span class="font-mono text-xs text-fg-subtle">
|
||||
raw.value = {{ raw === null ? 'null' : `"${raw}"` }}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="flex flex-col gap-1.5 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<label class="demo-card flex flex-col gap-1.5 p-4">
|
||||
<span class="demo-label">
|
||||
Reactive fallback
|
||||
</span>
|
||||
<input
|
||||
v-model="fallback"
|
||||
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"
|
||||
>
|
||||
<span class="text-xs text-(--fg-subtle)">
|
||||
<span class="text-xs text-fg-subtle">
|
||||
Changes here update the resolved value while the source is empty.
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -53,27 +53,27 @@ onUnmounted(stop);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex 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 flex flex-col gap-1">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Source</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ source }}</span>
|
||||
<span class="text-xs text-(--fg-muted)">{{ sourceUpdates }} updates</span>
|
||||
<div class="demo-card p-4 flex flex-col gap-1">
|
||||
<span class="demo-label">Source</span>
|
||||
<span class="demo-stat text-3xl">{{ source }}</span>
|
||||
<span class="text-xs text-fg-muted">{{ sourceUpdates }} updates</span>
|
||||
</div>
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-1">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Throttled</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--accent-text)">{{ throttled }}</span>
|
||||
<span class="text-xs text-(--fg-muted)">{{ throttledUpdates }} updates</span>
|
||||
<div class="demo-card p-4 flex flex-col gap-1">
|
||||
<span class="demo-label">Throttled</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-accent-text">{{ throttled }}</span>
|
||||
<span class="text-xs text-fg-muted">{{ throttledUpdates }} updates</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Lag behind source</span>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg)">+{{ lag }}</span>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex items-center justify-between">
|
||||
<span class="demo-label">Lag behind source</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg">+{{ lag }}</span>
|
||||
</div>
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<span class="demo-label">
|
||||
Throttle window: {{ delay }}ms
|
||||
</span>
|
||||
<input
|
||||
@@ -82,16 +82,16 @@ onUnmounted(stop);
|
||||
min="100"
|
||||
max="1500"
|
||||
step="100"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
<span class="text-xs text-(--fg-subtle)">Reset to apply a new window</span>
|
||||
<span class="text-xs text-fg-subtle">Reset to apply a new window</span>
|
||||
</label>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
:disabled="running"
|
||||
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 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
class="demo-btn-primary flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
@click="start"
|
||||
>
|
||||
Run (60ms)
|
||||
@@ -99,14 +99,14 @@ onUnmounted(stop);
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!running"
|
||||
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"
|
||||
@click="stop"
|
||||
>
|
||||
Pause
|
||||
</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="reset"
|
||||
>
|
||||
Reset
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ref, toValue, watch } from 'vue';
|
||||
import type { MaybeRefOrGetter, Ref } from 'vue';
|
||||
import { createFilterWrapper, throttleFilter } from '@/utils/filters';
|
||||
|
||||
export type RefThrottledReturn<T = any> = Ref<T>;
|
||||
export type RefThrottledReturn<T = unknown> = Ref<T>;
|
||||
|
||||
/**
|
||||
* @name refThrottled
|
||||
@@ -28,7 +28,7 @@ export type RefThrottledReturn<T = any> = Ref<T>;
|
||||
*
|
||||
* @since 0.0.15
|
||||
*/
|
||||
export function refThrottled<T = any>(
|
||||
export function refThrottled<T = unknown>(
|
||||
source: MaybeRefOrGetter<T>,
|
||||
delay = 200,
|
||||
trailing = true,
|
||||
|
||||
@@ -43,30 +43,30 @@ function silentBump() {
|
||||
</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 gap-3">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-3">
|
||||
<div class="flex items-baseline justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Volume (tracked)</span>
|
||||
<span class="font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ volume }}</span>
|
||||
<span class="demo-label">Volume (tracked)</span>
|
||||
<span class="demo-stat text-3xl">{{ volume }}</span>
|
||||
</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-(--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 flex-1"
|
||||
@click="volume.value--"
|
||||
>
|
||||
- 1
|
||||
</button>
|
||||
<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="volume.value++"
|
||||
>
|
||||
+ 1
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex flex-1 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 flex-1"
|
||||
@click="volume.value = 99"
|
||||
>
|
||||
Set 99
|
||||
@@ -74,25 +74,25 @@ function silentBump() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 flex items-center justify-between">
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 flex items-center justify-between">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">peek() snapshot</span>
|
||||
<span class="text-xs text-(--fg-subtle)">untracked read; lay() writes silently</span>
|
||||
<span class="demo-label">peek() snapshot</span>
|
||||
<span class="text-xs text-fg-subtle">untracked read; lay() writes silently</span>
|
||||
</div>
|
||||
<span class="font-mono text-2xl font-bold tabular-nums text-(--fg)">{{ peeked }}</span>
|
||||
<span class="demo-stat text-2xl">{{ peeked }}</span>
|
||||
</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-(--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 flex-1"
|
||||
@click="peek"
|
||||
>
|
||||
peek()
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex flex-1 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 flex-1"
|
||||
@click="silentBump"
|
||||
>
|
||||
lay() +1 silent
|
||||
@@ -100,16 +100,16 @@ function silentBump() {
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Hooks log</span>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 min-h-24 flex flex-col gap-1">
|
||||
<p v-if="!log.length" class="text-xs text-(--fg-subtle)">
|
||||
<span class="demo-label">Hooks log</span>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3 min-h-24 flex flex-col gap-1">
|
||||
<p v-if="!log.length" class="text-xs text-fg-subtle">
|
||||
Try setting volume to 99 to see onBeforeChange veto.
|
||||
</p>
|
||||
<p
|
||||
v-for="entry in log"
|
||||
:key="entry.id"
|
||||
class="font-mono text-xs tabular-nums"
|
||||
:class="entry.vetoed ? 'text-red-600 dark:text-red-400' : 'text-(--fg-muted)'"
|
||||
:class="entry.vetoed ? 'text-red-600 dark:text-red-400' : 'text-fg-muted'"
|
||||
>
|
||||
{{ entry.vetoed ? '✗' : '✓' }} {{ entry.message }}
|
||||
</p>
|
||||
|
||||
@@ -26,43 +26,43 @@ onUnmounted(stop);
|
||||
</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)">Two-way sync + transform</span>
|
||||
<span class="demo-label">Two-way sync + transform</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium"
|
||||
:class="synced
|
||||
? '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="synced ? 'bg-emerald-500' : 'bg-(--fg-subtle)'"
|
||||
:class="synced ? 'bg-emerald-500' : 'bg-fg-subtle'"
|
||||
/>
|
||||
{{ synced ? 'live' : 'stopped' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<label class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Celsius</span>
|
||||
<label class="demo-card p-4 flex flex-col gap-2">
|
||||
<span class="demo-label">Celsius</span>
|
||||
<input
|
||||
v-model.number="celsius"
|
||||
type="number"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-lg font-mono tabular-nums text-(--fg) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="w-full rounded-lg border border-border bg-bg px-3 py-2 text-lg font-mono tabular-nums text-fg transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
</label>
|
||||
<label class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4 flex flex-col gap-2">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Fahrenheit</span>
|
||||
<label class="demo-card p-4 flex flex-col gap-2">
|
||||
<span class="demo-label">Fahrenheit</span>
|
||||
<input
|
||||
v-model.number="fahrenheit"
|
||||
type="number"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 text-lg font-mono tabular-nums text-(--fg) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="w-full rounded-lg border border-border bg-bg px-3 py-2 text-lg font-mono tabular-nums text-fg transition focus:border-accent focus:outline-none focus:ring-2 focus:ring-ring"
|
||||
>
|
||||
</label>
|
||||
</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">
|
||||
{{ celsius }}°C ⇄ {{ fahrenheit }}°F
|
||||
</div>
|
||||
|
||||
@@ -72,19 +72,19 @@ onUnmounted(stop);
|
||||
min="-20"
|
||||
max="40"
|
||||
step="1"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
aria-label="Celsius slider"
|
||||
>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
:disabled="!synced"
|
||||
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"
|
||||
@click="toggleSync"
|
||||
>
|
||||
Stop synchronization
|
||||
</button>
|
||||
<p v-if="!synced" class="text-xs text-(--fg-subtle) -mt-2">
|
||||
<p v-if="!synced" class="text-xs text-fg-subtle -mt-2">
|
||||
Watchers torn down — the two refs now drift independently.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -26,26 +26,26 @@ function loadPreset(p: Profile) {
|
||||
</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 gap-3">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-3">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Name</span>
|
||||
<span class="demo-label">Name</span>
|
||||
<input
|
||||
v-model="profile.name"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Role</span>
|
||||
<span class="demo-label">Role</span>
|
||||
<input
|
||||
v-model="profile.role"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<span class="demo-label">
|
||||
Level: {{ profile.level }}
|
||||
</span>
|
||||
<input
|
||||
@@ -54,30 +54,30 @@ function loadPreset(p: Profile) {
|
||||
min="1"
|
||||
max="10"
|
||||
step="1"
|
||||
class="w-full accent-(--accent) cursor-pointer"
|
||||
class="w-full accent-accent cursor-pointer"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">source.value (the backing ref)</span>
|
||||
<pre class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-xs text-(--fg) overflow-x-auto">{{ JSON.stringify(source, null, 2) }}</pre>
|
||||
<span class="demo-label">source.value (the backing ref)</span>
|
||||
<pre class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-xs text-fg overflow-x-auto">{{ JSON.stringify(source, null, 2) }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Reassign the whole ref</span>
|
||||
<span class="demo-label">Reassign the whole ref</span>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="preset in presets"
|
||||
:key="preset.name"
|
||||
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="loadPreset(preset)"
|
||||
>
|
||||
{{ preset.name.split(' ')[0] }}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
The proxy survives reassignment — fields above update without re-binding.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -19,14 +19,14 @@ const samples = ['Hello', 'hello', 'HELLO', 'World', 'world!'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Source ref</span>
|
||||
<span class="demo-label">Source ref</span>
|
||||
<input
|
||||
v-model="source"
|
||||
type="text"
|
||||
placeholder="Type to change the source"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
|
||||
@@ -35,32 +35,32 @@ const samples = ['Hello', 'hello', 'HELLO', 'World', 'world!'];
|
||||
v-for="sample in samples"
|
||||
:key="sample"
|
||||
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="source = sample"
|
||||
>
|
||||
{{ sample }}
|
||||
</button>
|
||||
</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 gap-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Default cache</span>
|
||||
<span class="text-xs text-(--fg-subtle)">a === b</span>
|
||||
<span class="demo-label">Default cache</span>
|
||||
<span class="text-xs text-fg-subtle">a === b</span>
|
||||
</div>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg) truncate">"{{ cachedDefault }}"</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg truncate">"{{ cachedDefault }}"</span>
|
||||
</div>
|
||||
<div class="h-px bg-(--border)" />
|
||||
<div class="h-px bg-border" />
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Case-insensitive cache</span>
|
||||
<span class="text-xs text-(--fg-subtle)">toLowerCase() match</span>
|
||||
<span class="demo-label">Case-insensitive cache</span>
|
||||
<span class="text-xs text-fg-subtle">toLowerCase() match</span>
|
||||
</div>
|
||||
<span class="font-mono text-sm tabular-nums text-(--accent-text) truncate">"{{ cachedInsensitive }}"</span>
|
||||
<span class="font-mono text-sm tabular-nums text-accent-text truncate">"{{ cachedInsensitive }}"</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Toggle between <span class="font-mono">Hello</span>, <span class="font-mono">hello</span> and
|
||||
<span class="font-mono">HELLO</span>: the default cache follows every change, while the
|
||||
case-insensitive cache keeps its first stored casing.
|
||||
|
||||
@@ -22,9 +22,9 @@ function addTag() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md 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)">useCloned</span>
|
||||
<span class="demo-label">useCloned</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
|
||||
:class="isModified
|
||||
@@ -40,40 +40,40 @@ function addTag() {
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Source</p>
|
||||
<dl class="space-y-1.5 text-sm text-(--fg)">
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label mb-2">Source</p>
|
||||
<dl class="space-y-1.5 text-sm text-fg">
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-(--fg-muted)">name</dt>
|
||||
<dt class="text-fg-muted">name</dt>
|
||||
<dd class="truncate font-medium">{{ original.name }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-(--fg-muted)">role</dt>
|
||||
<dt class="text-fg-muted">role</dt>
|
||||
<dd class="font-medium">{{ original.role }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-(--fg-muted)">tags</dt>
|
||||
<dd class="font-mono text-xs text-(--fg-muted)">{{ original.tags.length }}</dd>
|
||||
<dt class="text-fg-muted">tags</dt>
|
||||
<dd class="font-mono text-xs text-fg-muted">{{ original.tags.length }}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Cloned (editable)</p>
|
||||
<dl class="space-y-1.5 text-sm text-(--fg)">
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label mb-2">Cloned (editable)</p>
|
||||
<dl class="space-y-1.5 text-sm text-fg">
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-(--fg-muted)">name</dt>
|
||||
<dt class="text-fg-muted">name</dt>
|
||||
<dd class="truncate font-medium">{{ cloned.name }}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-2">
|
||||
<dt class="text-(--fg-muted)">role</dt>
|
||||
<dt class="text-fg-muted">role</dt>
|
||||
<dd class="font-medium">{{ cloned.role }}</dd>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-end gap-1">
|
||||
<span
|
||||
v-for="tag in cloned.tags"
|
||||
:key="tag"
|
||||
class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-inset) px-1.5 py-0.5 font-mono text-[0.65rem] text-(--fg-muted)"
|
||||
class="inline-flex items-center rounded-md border border-border bg-bg-inset px-1.5 py-0.5 font-mono text-[0.65rem] text-fg-muted"
|
||||
>
|
||||
{{ tag }}
|
||||
</span>
|
||||
@@ -85,21 +85,21 @@ function addTag() {
|
||||
<div class="flex flex-wrap 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:border-(--border-strong) hover:bg-(--bg-inset) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="bumpOriginal"
|
||||
>
|
||||
Cycle source role
|
||||
</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:border-(--border-strong) hover:bg-(--bg-inset) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="addTag"
|
||||
>
|
||||
Edit clone
|
||||
</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="!isModified"
|
||||
@click="sync()"
|
||||
>
|
||||
@@ -107,7 +107,7 @@ function addTag() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Editing the source auto-resyncs the clone. Editing the clone marks it modified without touching the source.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -23,25 +23,25 @@ function onInput(event: Event) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md 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)">useDebounceFn</span>
|
||||
<span class="demo-label">useDebounceFn</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs font-medium transition"
|
||||
:class="runSearch.isPending.value
|
||||
? 'border-sky-500/30 bg-sky-500/10 text-sky-600 dark:text-sky-400'
|
||||
: 'border-(--border) bg-(--bg-inset) text-(--fg-muted)'"
|
||||
: 'border-border bg-bg-inset text-fg-muted'"
|
||||
>
|
||||
<span
|
||||
class="size-1.5 rounded-full transition"
|
||||
:class="runSearch.isPending.value ? 'animate-pulse bg-sky-500' : 'bg-(--fg-subtle)'"
|
||||
:class="runSearch.isPending.value ? 'animate-pulse bg-sky-500' : 'bg-fg-subtle'"
|
||||
/>
|
||||
{{ runSearch.isPending.value ? 'Pending' : 'Settled' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="search">
|
||||
<label class="demo-label" for="search">
|
||||
Type to search
|
||||
</label>
|
||||
<input
|
||||
@@ -49,13 +49,13 @@ function onInput(event: Event) {
|
||||
:value="query"
|
||||
type="text"
|
||||
placeholder="Search the docs…"
|
||||
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"
|
||||
@input="onInput"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="delay">
|
||||
<label class="demo-label" for="delay">
|
||||
Delay
|
||||
</label>
|
||||
<input
|
||||
@@ -65,26 +65,26 @@ function onInput(event: Event) {
|
||||
min="0"
|
||||
max="1500"
|
||||
step="100"
|
||||
class="flex-1 accent-(--accent)"
|
||||
class="flex-1 accent-accent"
|
||||
>
|
||||
<span class="w-16 text-right font-mono text-sm tabular-nums text-(--fg)">{{ delay }}ms</span>
|
||||
<span class="w-16 text-right font-mono text-sm tabular-nums text-fg">{{ delay }}ms</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Invocations</p>
|
||||
<p class="mt-1 font-mono text-3xl font-bold tabular-nums text-(--fg)">{{ calls }}</p>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label">Invocations</p>
|
||||
<p class="demo-stat mt-1 text-3xl">{{ calls }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Last result</p>
|
||||
<p class="mt-1 truncate font-mono text-sm text-(--fg)">{{ lastResult || '—' }}</p>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label">Last result</p>
|
||||
<p class="mt-1 truncate font-mono text-sm text-fg">{{ lastResult || '—' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap 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:border-(--border-strong) hover:bg-(--bg-inset) 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="!runSearch.isPending.value"
|
||||
@click="runSearch.flush()"
|
||||
>
|
||||
@@ -92,7 +92,7 @@ function onInput(event: Event) {
|
||||
</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:border-(--border-strong) hover:bg-(--bg-inset) 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="!runSearch.isPending.value"
|
||||
@click="runSearch.cancel()"
|
||||
>
|
||||
@@ -100,9 +100,9 @@ function onInput(event: Event) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="log.length" class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="mb-2 text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Recent calls</p>
|
||||
<ul class="space-y-1 font-mono text-xs text-(--fg-muted)">
|
||||
<div v-if="log.length" class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label mb-2">Recent calls</p>
|
||||
<ul class="space-y-1 font-mono text-xs text-fg-muted">
|
||||
<li v-for="(entry, i) in log" :key="i" class="truncate">{{ entry }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -12,22 +12,22 @@ function pick(theme: string) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">usePrevious</span>
|
||||
<div class="demo-stack max-w-sm">
|
||||
<span class="demo-label">usePrevious</span>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Current</p>
|
||||
<p class="mt-1 truncate text-2xl font-bold text-(--fg)">{{ selected }}</p>
|
||||
<div class="demo-card p-4">
|
||||
<p class="demo-label">Current</p>
|
||||
<p class="mt-1 truncate text-2xl font-bold text-fg">{{ selected }}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-inset) p-4">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Previous</p>
|
||||
<p class="mt-1 truncate text-2xl font-bold text-(--fg-muted)">{{ previous }}</p>
|
||||
<div class="rounded-xl border border-border bg-bg-inset p-4">
|
||||
<p class="demo-label">Previous</p>
|
||||
<p class="mt-1 truncate text-2xl font-bold text-fg-muted">{{ previous }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Select a theme</p>
|
||||
<p class="demo-label">Select a theme</p>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
v-for="theme in themes"
|
||||
@@ -35,8 +35,8 @@ function pick(theme: string) {
|
||||
type="button"
|
||||
class="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="theme === selected
|
||||
? 'border-transparent bg-(--accent) text-(--accent-fg) hover:bg-(--accent-hover)'
|
||||
: 'border-(--border) bg-(--bg-elevated) text-(--fg) hover:border-(--border-strong) hover:bg-(--bg-inset)'"
|
||||
? 'border-transparent bg-accent text-accent-fg hover:bg-accent-hover'
|
||||
: 'border-border bg-bg-elevated text-fg hover:border-border-strong hover:bg-bg-inset'"
|
||||
@click="pick(theme)"
|
||||
>
|
||||
{{ theme }}
|
||||
@@ -44,8 +44,8 @@ function pick(theme: string) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
Seeded with <span class="font-mono text-(--fg-muted)">"None"</span> as the initial previous value until the source first changes.
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Seeded with <span class="font-mono text-fg-muted">"None"</span> as the initial previous value until the source first changes.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -15,11 +15,11 @@ const presets = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#0ea5e9'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md flex-col gap-4">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">useSyncRefs</span>
|
||||
<div class="demo-stack max-w-md">
|
||||
<span class="demo-label">useSyncRefs</span>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="color">
|
||||
<label class="demo-label" for="color">
|
||||
Source ref
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -27,33 +27,33 @@ const presets = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#0ea5e9'];
|
||||
id="color"
|
||||
v-model="source"
|
||||
type="color"
|
||||
class="h-9 w-12 shrink-0 cursor-pointer rounded-lg border border-(--border) bg-(--bg)"
|
||||
class="h-9 w-12 shrink-0 cursor-pointer rounded-lg border border-border bg-bg"
|
||||
>
|
||||
<input
|
||||
v-model="source"
|
||||
type="text"
|
||||
class="w-full rounded-lg border border-(--border) bg-(--bg) px-3 py-2 font-mono text-sm text-(--fg) placeholder:text-(--fg-subtle) transition focus:border-(--accent) focus:outline-none focus:ring-2 focus:ring-(--ring)"
|
||||
class="demo-input font-mono"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">Three independent target refs stay synced to the source:</p>
|
||||
<p class="text-xs text-fg-subtle">Three independent target refs stay synced to the source:</p>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="flex flex-col items-center gap-2 rounded-xl border border-(--border) bg-(--bg-elevated) p-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">swatch</span>
|
||||
<div class="demo-card flex flex-col items-center gap-2 p-3">
|
||||
<span class="demo-label">swatch</span>
|
||||
<span
|
||||
class="size-10 rounded-lg border border-(--border-strong) shadow-sm transition"
|
||||
class="size-10 rounded-lg border border-border-strong shadow-sm transition"
|
||||
:style="{ backgroundColor: swatch }"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center gap-2 rounded-xl border border-(--border) bg-(--bg-elevated) p-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">label</span>
|
||||
<span class="font-mono text-xs text-(--fg)">{{ label }}</span>
|
||||
<div class="demo-card flex flex-col items-center justify-center gap-2 p-3">
|
||||
<span class="demo-label">label</span>
|
||||
<span class="font-mono text-xs text-fg">{{ label }}</span>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center gap-2 rounded-xl border border-(--border) bg-(--bg-inset) p-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">hex</span>
|
||||
<span class="font-mono text-xs uppercase text-(--fg)">{{ hex }}</span>
|
||||
<div class="flex flex-col items-center justify-center gap-2 rounded-xl border border-border bg-bg-inset p-3">
|
||||
<span class="demo-label">hex</span>
|
||||
<span class="font-mono text-xs uppercase text-fg">{{ hex }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +62,7 @@ const presets = ['#6366f1', '#10b981', '#f59e0b', '#ef4444', '#0ea5e9'];
|
||||
v-for="preset in presets"
|
||||
:key="preset"
|
||||
type="button"
|
||||
class="size-7 rounded-md border border-(--border) transition hover:scale-110 active:scale-95 cursor-pointer"
|
||||
class="size-7 rounded-md border border-border transition hover:scale-110 active:scale-95 cursor-pointer"
|
||||
:style="{ backgroundColor: preset }"
|
||||
:aria-label="`Set source to ${preset}`"
|
||||
@click="source = preset"
|
||||
|
||||
@@ -24,24 +24,24 @@ const ratio = () => (moves.value ? Math.round((fires.value / moves.value) * 100)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-md flex-col gap-4">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">useThrottleFn</span>
|
||||
<div class="demo-stack max-w-md">
|
||||
<span class="demo-label">useThrottleFn</span>
|
||||
|
||||
<div
|
||||
class="relative h-40 w-full overflow-hidden rounded-xl border border-(--border) bg-(--bg-inset) touch-none"
|
||||
class="relative h-40 w-full overflow-hidden rounded-xl border border-border bg-bg-inset touch-none"
|
||||
@pointermove="handlePointer"
|
||||
>
|
||||
<span
|
||||
class="pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-(--accent-fg) bg-(--accent) shadow-md transition-all duration-150 ease-out"
|
||||
class="pointer-events-none absolute size-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-accent-fg bg-accent shadow-md transition-all duration-150 ease-out"
|
||||
:style="{ left: `${position.x}%`, top: `${position.y}%` }"
|
||||
/>
|
||||
<span class="pointer-events-none absolute inset-x-0 bottom-2 text-center text-xs text-(--fg-subtle)">
|
||||
<span class="pointer-events-none absolute inset-x-0 bottom-2 text-center text-xs text-fg-subtle">
|
||||
Move your pointer over this area
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<label class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)" for="window">
|
||||
<label class="demo-label" for="window">
|
||||
Window
|
||||
</label>
|
||||
<input
|
||||
@@ -51,40 +51,40 @@ const ratio = () => (moves.value ? Math.round((fires.value / moves.value) * 100)
|
||||
min="0"
|
||||
max="1000"
|
||||
step="50"
|
||||
class="flex-1 accent-(--accent)"
|
||||
class="flex-1 accent-accent"
|
||||
>
|
||||
<span class="w-16 text-right font-mono text-sm tabular-nums text-(--fg)">{{ delay }}ms</span>
|
||||
<span class="w-16 text-right font-mono text-sm tabular-nums text-fg">{{ delay }}ms</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 gap-3">
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Events</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold tabular-nums text-(--fg)">{{ moves }}</p>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label">Events</p>
|
||||
<p class="demo-stat mt-1 text-2xl">{{ moves }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Fired</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold tabular-nums text-(--accent-text)">{{ fires }}</p>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label">Fired</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold tabular-nums text-accent-text">{{ fires }}</p>
|
||||
</div>
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Ran</p>
|
||||
<p class="mt-1 font-mono text-2xl font-bold tabular-nums text-(--fg)">{{ ratio() }}%</p>
|
||||
<div class="rounded-lg border border-border bg-bg-inset p-3">
|
||||
<p class="demo-label">Ran</p>
|
||||
<p class="demo-stat mt-1 text-2xl">{{ ratio() }}%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="rounded-md border border-(--border) bg-(--bg-inset) px-2 py-0.5 font-mono text-xs text-(--fg-muted)">
|
||||
<span class="rounded-md border border-border bg-bg-inset px-2 py-0.5 font-mono text-xs text-fg-muted">
|
||||
x: {{ position.x }} · y: {{ position.y }}
|
||||
</span>
|
||||
<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:border-(--border-strong) hover:bg-(--bg-inset) active:scale-[0.98] cursor-pointer"
|
||||
class="demo-btn"
|
||||
@click="onMove.flush()"
|
||||
>
|
||||
Flush trailing
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Leading + trailing throttling caps the handler to once per window — drag faster and watch the fired count lag behind events.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { isRef, toValue } from 'vue';
|
||||
import type { MaybeRefOrGetter } from 'vue';
|
||||
import { isFunction, isObject, throttle } from '@robonen/stdlib';
|
||||
import type { AnyFunction } from '@robonen/stdlib';
|
||||
import { tryOnScopeDispose } from '@/composables/lifecycle/tryOnScopeDispose';
|
||||
|
||||
export interface UseThrottleFnOptions {
|
||||
/**
|
||||
@@ -181,5 +182,9 @@ export function useThrottleFn<T extends AnyFunction>(
|
||||
throttled.flush();
|
||||
};
|
||||
|
||||
// Cancel any pending trailing invocation when the scope is disposed, so it
|
||||
// can't fire after the component unmounts (mirrors useDebounceFn).
|
||||
tryOnScopeDispose(wrapper.cancel);
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ const presets = ['42.50', '3.14159', '255.9', 'abc', '-12'];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full max-w-sm flex flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Source value</span>
|
||||
<span class="demo-label">Source value</span>
|
||||
<input
|
||||
v-model="source"
|
||||
type="text"
|
||||
placeholder="Type a number…"
|
||||
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"
|
||||
>
|
||||
</label>
|
||||
|
||||
@@ -39,36 +39,36 @@ const presets = ['42.50', '3.14159', '255.9', 'abc', '-12'];
|
||||
v-for="v in presets"
|
||||
:key="v"
|
||||
type="button"
|
||||
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) transition hover:bg-(--bg-elevated) hover:border-(--border-strong) cursor-pointer"
|
||||
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 transition hover:bg-bg-elevated hover:border-border-strong cursor-pointer"
|
||||
@click="source = v"
|
||||
>
|
||||
{{ v }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="rounded-xl border border-(--border) bg-(--bg-elevated) divide-y divide-(--border)">
|
||||
<div class="demo-card divide-y divide-border">
|
||||
<div
|
||||
v-for="variant in variants"
|
||||
:key="variant.label"
|
||||
class="flex items-center justify-between gap-3 px-4 py-2.5"
|
||||
>
|
||||
<div class="flex flex-col">
|
||||
<span class="font-mono text-sm text-(--fg)">{{ variant.label }}</span>
|
||||
<span class="text-xs text-(--fg-subtle)">{{ variant.desc }}</span>
|
||||
<span class="font-mono text-sm text-fg">{{ variant.label }}</span>
|
||||
<span class="text-xs text-fg-subtle">{{ variant.desc }}</span>
|
||||
</div>
|
||||
<span
|
||||
class="font-mono text-lg font-semibold tabular-nums"
|
||||
:class="Number.isNaN(variant.value) ? 'text-amber-600 dark:text-amber-400' : 'text-(--fg)'"
|
||||
:class="Number.isNaN(variant.value) ? 'text-amber-600 dark:text-amber-400' : 'text-fg'"
|
||||
>
|
||||
{{ Number.isNaN(variant.value) ? 'NaN' : variant.value }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle) leading-relaxed">
|
||||
One reactive source, four <span class="font-mono text-(--accent-text)">useToNumber</span>
|
||||
instances. Try <span class="font-mono text-(--fg-muted)">abc</span> to see how
|
||||
<span class="font-mono text-(--fg-muted)">nanToZero</span> and clamping tame invalid input.
|
||||
<p class="text-xs text-fg-subtle leading-relaxed">
|
||||
One reactive source, four <span class="font-mono text-accent-text">useToNumber</span>
|
||||
instances. Try <span class="font-mono text-fg-muted">abc</span> to see how
|
||||
<span class="font-mono text-fg-muted">nanToZero</span> and clamping tame invalid input.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -26,54 +26,54 @@ const rows = [
|
||||
</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 gap-3">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Live source values</span>
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card p-4 flex flex-col gap-3">
|
||||
<span class="demo-label">Live source values</span>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<label class="text-sm text-(--fg)">count</label>
|
||||
<label class="text-sm text-fg">count</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-(--border) bg-(--bg-elevated) text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-border bg-bg-elevated text-fg transition hover:bg-bg-inset hover:border-border-strong active:scale-[0.98] cursor-pointer"
|
||||
@click="count--"
|
||||
>−</button>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg) w-6 text-center">{{ count }}</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg w-6 text-center">{{ count }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-(--border) bg-(--bg-elevated) text-(--fg) transition hover:bg-(--bg-inset) hover:border-(--border-strong) active:scale-[0.98] cursor-pointer"
|
||||
class="inline-flex h-7 w-7 items-center justify-center rounded-lg border border-border bg-bg-elevated text-fg transition hover:bg-bg-inset hover:border-border-strong active:scale-[0.98] cursor-pointer"
|
||||
@click="count++"
|
||||
>+</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<label class="text-sm text-(--fg)" for="ratio">ratio</label>
|
||||
<input id="ratio" v-model.number="ratio" type="range" min="0" max="1" step="0.01" class="accent-(--accent) cursor-pointer">
|
||||
<label class="text-sm text-fg" for="ratio">ratio</label>
|
||||
<input id="ratio" v-model.number="ratio" type="range" min="0" max="1" step="0.01" class="accent-accent cursor-pointer">
|
||||
</div>
|
||||
|
||||
<label class="flex items-center justify-between gap-3 cursor-pointer">
|
||||
<span class="text-sm text-(--fg)">enabled</span>
|
||||
<input v-model="enabled" type="checkbox" class="size-4 accent-(--accent) cursor-pointer">
|
||||
<span class="text-sm text-fg">enabled</span>
|
||||
<input v-model="enabled" type="checkbox" class="size-4 accent-accent cursor-pointer">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="rounded-lg border border-(--border) bg-(--bg-inset) divide-y divide-(--border) font-mono text-sm">
|
||||
<div class="rounded-lg border border-border bg-bg-inset divide-y divide-border font-mono text-sm">
|
||||
<div
|
||||
v-for="row in rows"
|
||||
:key="row.type"
|
||||
class="flex items-center gap-3 px-3 py-2"
|
||||
>
|
||||
<span class="inline-flex items-center rounded-md border border-(--border) bg-(--bg-elevated) px-2 py-0.5 text-xs font-medium text-(--fg-muted) shrink-0">
|
||||
<span class="inline-flex items-center rounded-md border border-border bg-bg-elevated px-2 py-0.5 text-xs font-medium text-fg-muted shrink-0">
|
||||
{{ row.type }}
|
||||
</span>
|
||||
<span class="text-(--fg) truncate">"{{ row.value }}"</span>
|
||||
<span class="text-fg truncate">"{{ row.value }}"</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle) leading-relaxed">
|
||||
<span class="font-mono text-(--accent-text)">useToString</span> is
|
||||
<span class="font-mono text-(--fg-muted)">computed(() => String(toValue(v)))</span> —
|
||||
<p class="text-xs text-fg-subtle leading-relaxed">
|
||||
<span class="font-mono text-accent-text">useToString</span> is
|
||||
<span class="font-mono text-fg-muted">computed(() => String(toValue(v)))</span> —
|
||||
it stringifies refs, getters, and reactive objects alike.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user