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:
@@ -26,14 +26,14 @@ function toggle(member: Member) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<!-- Templates are captured here, rendered wherever Reuse* appears -->
|
||||
<DefineStat v-slot="{ label, value }">
|
||||
<div class="flex-1 rounded-lg border border-(--border) bg-(--bg-inset) p-3">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="flex-1 rounded-lg border border-border bg-bg-inset p-3">
|
||||
<div class="demo-label">
|
||||
{{ label }}
|
||||
</div>
|
||||
<div class="mt-1 font-mono text-2xl font-bold tabular-nums text-(--fg)">
|
||||
<div class="demo-stat mt-1 text-2xl">
|
||||
{{ value }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,14 +43,14 @@ function toggle(member: Member) {
|
||||
<div class="flex items-center gap-3">
|
||||
<span
|
||||
class="inline-block size-2 shrink-0 rounded-full transition"
|
||||
:class="online ? 'bg-emerald-500' : 'bg-(--border-strong)'"
|
||||
:class="online ? 'bg-emerald-500' : 'bg-border-strong'"
|
||||
/>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="truncate text-sm font-medium text-(--fg)">{{ name }}</div>
|
||||
<div class="text-xs text-(--fg-subtle)">{{ role }}</div>
|
||||
<div class="truncate text-sm font-medium text-fg">{{ name }}</div>
|
||||
<div class="text-xs text-fg-subtle">{{ role }}</div>
|
||||
</div>
|
||||
<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"
|
||||
>
|
||||
{{ online ? 'Online' : 'Away' }}
|
||||
</span>
|
||||
@@ -62,14 +62,14 @@ function toggle(member: Member) {
|
||||
<ReuseStat label="Online" :value="String(team.filter(m => m.online).length)" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-card flex flex-col gap-3 p-4">
|
||||
<div class="demo-label">
|
||||
Team — click a row to toggle status
|
||||
</div>
|
||||
<button
|
||||
v-for="member in team"
|
||||
:key="member.name"
|
||||
class="rounded-lg p-2 text-left transition hover:bg-(--bg-inset) active:scale-[0.99] cursor-pointer"
|
||||
class="rounded-lg p-2 text-left transition hover:bg-bg-inset active:scale-[0.99] cursor-pointer"
|
||||
@click="toggle(member)"
|
||||
>
|
||||
<ReuseMember
|
||||
@@ -80,7 +80,7 @@ function toggle(member: Member) {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Both card and row markup are declared once via <code class="font-mono">DefineTemplate</code> and
|
||||
rendered from multiple <code class="font-mono">ReuseTemplate</code> call sites.
|
||||
</p>
|
||||
|
||||
@@ -1,16 +1,26 @@
|
||||
import type { ComponentObjectPropsOptions, DefineComponent, Slot } from 'vue';
|
||||
import { camelize, defineComponent, shallowRef } from 'vue';
|
||||
|
||||
/** Map of slot name -> slot props object (or `undefined` for prop-less slots) */
|
||||
/**
|
||||
* Map of slot name -> slot props object (or `undefined` for prop-less slots).
|
||||
* The inner `Record<string, any>` is the idiomatic "any slot-props shape" bound:
|
||||
* interface-typed slot props (which lack an implicit index signature) must satisfy
|
||||
* it, so `Record<string, unknown>` would wrongly reject legitimate callers.
|
||||
*/
|
||||
type SlotPropsMap = Record<string, Record<string, any> | undefined>;
|
||||
|
||||
/** Turn a {@link SlotPropsMap} into a record of typed `Slot`s */
|
||||
type GenerateSlotsFromSlotMap<T extends SlotPropsMap>
|
||||
= { [K in keyof T]: Slot<T[K]> };
|
||||
|
||||
// `Bindings extends Record<string, any>` is the idiomatic "any object shape" bound,
|
||||
// matching Vue/Reka's own component-binding generics: an interface-typed `Bindings`
|
||||
// must satisfy it, which `Record<string, unknown>` would reject. Applies to every
|
||||
// `extends Record<string, any>` constraint in this file.
|
||||
export type DefineTemplateComponent<Bindings extends Record<string, any>, Slots extends SlotPropsMap>
|
||||
= DefineComponent & (new () => {
|
||||
$slots: {
|
||||
// Slot render fn: returns `any` to match Vue's own `Slot` return type.
|
||||
default: (_: Bindings & { $slots: GenerateSlotsFromSlotMap<Slots> }) => any;
|
||||
};
|
||||
});
|
||||
@@ -49,8 +59,8 @@ export interface CreateReusableTemplateOptions<Props extends Record<string, any>
|
||||
}
|
||||
|
||||
/** Re-key an attrs object so every key is camelCased */
|
||||
function keysToCamelCase(obj: Record<string, any>): Record<string, any> {
|
||||
const result: Record<string, any> = {};
|
||||
function keysToCamelCase(obj: Record<string, unknown>): Record<string, unknown> {
|
||||
const result: Record<string, unknown> = {};
|
||||
|
||||
for (const key in obj)
|
||||
result[camelize(key)] = obj[key];
|
||||
|
||||
@@ -21,10 +21,10 @@ function measure() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div
|
||||
ref="box"
|
||||
class="flex items-center justify-center rounded-xl border border-dashed border-(--border-strong) bg-(--bg-inset) py-8 text-sm font-medium text-(--fg-muted) transition-[width] duration-300 ease-out"
|
||||
class="flex items-center justify-center rounded-xl border border-dashed border-border-strong bg-bg-inset py-8 text-sm font-medium text-fg-muted transition-[width] duration-300 ease-out"
|
||||
:style="{ width: `${width}px` }"
|
||||
>
|
||||
Target element
|
||||
@@ -32,37 +32,37 @@ function measure() {
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Width</span>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg-muted)">{{ width }}px</span>
|
||||
<span class="demo-label">Width</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg-muted">{{ width }}px</span>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="width"
|
||||
type="range"
|
||||
min="120"
|
||||
max="340"
|
||||
class="w-full accent-(--accent)"
|
||||
class="w-full accent-accent"
|
||||
>
|
||||
</label>
|
||||
|
||||
<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="measure"
|
||||
>
|
||||
Read element via unrefElement
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
|
||||
<div class="flex flex-col gap-2 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">
|
||||
<span class="text-(--fg-subtle)">tagName</span>
|
||||
<span class="text-fg-subtle">tagName</span>
|
||||
<span>{{ tag }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-subtle)">boundingRect</span>
|
||||
<span class="text-fg-subtle">boundingRect</span>
|
||||
<span>{{ rect ? `${rect.w} × ${rect.h}` : '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
Resize the box, then measure. <code class="font-mono">unrefElement</code> unwraps the template
|
||||
ref to the real DOM node — it also resolves a component ref to its <code class="font-mono">$el</code>.
|
||||
</p>
|
||||
|
||||
@@ -29,10 +29,10 @@ const chips = ['vue', 'reactivity', 'composables', 'ssr', 'typescript', 'dom'];
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="flex w-full max-w-sm flex-col gap-4 rounded-xl border border-(--border) bg-(--bg-elevated)"
|
||||
class="demo-stack demo-card max-w-sm"
|
||||
:style="{ padding: `${padding}px` }"
|
||||
>
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-label">
|
||||
Live measurement of this component's root
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,7 @@ const chips = ['vue', 'reactivity', 'composables', 'ssr', 'typescript', 'dom'];
|
||||
v-for="chip in chips.slice(0, childCount)"
|
||||
:key="chip"
|
||||
data-chip
|
||||
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"
|
||||
>
|
||||
{{ chip }}
|
||||
</span>
|
||||
@@ -49,28 +49,28 @@ const chips = ['vue', 'reactivity', 'composables', 'ssr', 'typescript', 'dom'];
|
||||
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Root padding</span>
|
||||
<span class="font-mono text-sm tabular-nums text-(--fg-muted)">{{ padding }}px</span>
|
||||
<span class="demo-label">Root padding</span>
|
||||
<span class="font-mono text-sm tabular-nums text-fg-muted">{{ padding }}px</span>
|
||||
</div>
|
||||
<input
|
||||
v-model.number="padding"
|
||||
type="range"
|
||||
min="8"
|
||||
max="40"
|
||||
class="w-full accent-(--accent)"
|
||||
class="w-full accent-accent"
|
||||
>
|
||||
</label>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex-1 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 flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="childCount <= 1"
|
||||
@click="childCount--"
|
||||
>
|
||||
Remove chip
|
||||
</button>
|
||||
<button
|
||||
class="flex-1 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 flex-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100"
|
||||
:disabled="childCount >= chips.length"
|
||||
@click="childCount++"
|
||||
>
|
||||
@@ -78,22 +78,22 @@ const chips = ['vue', 'reactivity', 'composables', 'ssr', 'typescript', 'dom'];
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
|
||||
<div class="flex flex-col gap-2 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">
|
||||
<span class="text-(--fg-subtle)">el.value</span>
|
||||
<span class="text-fg-subtle">el.value</span>
|
||||
<span>{{ info ? `<${info.tag}>` : 'undefined' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-subtle)">chips in DOM</span>
|
||||
<span class="text-fg-subtle">chips in DOM</span>
|
||||
<span>{{ info?.children ?? '—' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-(--fg-subtle)">root height</span>
|
||||
<span class="text-fg-subtle">root height</span>
|
||||
<span>{{ info ? `${info.height}px` : '—' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
The computed re-reads <code class="font-mono">$el</code> on every update, so the readout tracks
|
||||
padding and chip changes automatically.
|
||||
</p>
|
||||
|
||||
@@ -19,7 +19,7 @@ const FieldWrapper = defineComponent({
|
||||
// expose the resolved element so the demo can show it changed live
|
||||
'data-tag': currentElement.value?.tagName.toLowerCase(),
|
||||
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)',
|
||||
'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',
|
||||
});
|
||||
},
|
||||
});
|
||||
@@ -49,13 +49,13 @@ function fillSample() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex w-full max-w-sm flex-col gap-4">
|
||||
<div class="flex flex-col gap-3 rounded-xl border border-(--border) bg-(--bg-elevated) p-4">
|
||||
<div class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">
|
||||
<div class="demo-stack max-w-sm">
|
||||
<div class="demo-card flex flex-col gap-3 p-4">
|
||||
<div class="demo-label">
|
||||
Wrapper component
|
||||
</div>
|
||||
<FieldWrapper ref="field" />
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
<code class="font-mono"><FieldWrapper></code> renders an inner input, but
|
||||
<code class="font-mono">useForwardExpose</code> makes its <code class="font-mono">$el</code>
|
||||
resolve to that input.
|
||||
@@ -64,31 +64,31 @@ function fillSample() {
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
class="flex-1 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 flex-1"
|
||||
@click="focusForwarded"
|
||||
>
|
||||
Focus forwarded $el
|
||||
</button>
|
||||
<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="fillSample"
|
||||
>
|
||||
Fill sample
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2 rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums">
|
||||
<div class="flex flex-col gap-2 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">
|
||||
<span class="text-(--fg-subtle)">field.$el</span>
|
||||
<span class="text-fg-subtle">field.$el</span>
|
||||
<span>{{ resolved ? `<${resolved.tag}>` : 'undefined' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-3">
|
||||
<span class="text-(--fg-subtle)">value</span>
|
||||
<span class="text-fg-subtle">value</span>
|
||||
<span class="truncate">{{ resolved?.value || '""' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-(--fg-subtle)">
|
||||
<p class="text-xs text-fg-subtle">
|
||||
The parent never touches the input directly — it holds a ref to the wrapper, whose
|
||||
<code class="font-mono">$el</code> is forwarded to the inner element.
|
||||
</p>
|
||||
|
||||
@@ -53,7 +53,7 @@ export function useForwardExpose<T extends ComponentPublicInstance>(): UseForwar
|
||||
|
||||
// localExpose should only be assigned once else will create infinite loop
|
||||
const localExpose = instance.exposed;
|
||||
const ret: Record<string, any> = {};
|
||||
const ret: Record<string, unknown> = {};
|
||||
|
||||
// Collect all property descriptors in a single pass
|
||||
const descriptors: PropertyDescriptorMap = {};
|
||||
|
||||
@@ -50,62 +50,62 @@ const collected = computed(() => refs.value.length);
|
||||
</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)">Playlist</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">Playlist</span>
|
||||
<span class="demo-badge">
|
||||
{{ collected }} refs collected
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul class="flex flex-col gap-2 max-h-56 overflow-y-auto rounded-xl border border-(--border) bg-(--bg-elevated) p-2">
|
||||
<ul class="demo-card flex flex-col gap-2 max-h-56 overflow-y-auto p-2">
|
||||
<li
|
||||
v-for="(track, index) in tracks"
|
||||
:key="track.id"
|
||||
:ref="set"
|
||||
class="group flex items-center gap-3 rounded-lg border border-(--border) bg-(--bg-inset) px-3 py-2 transition"
|
||||
:class="lastMeasured?.index === index ? 'border-(--accent) ring-2 ring-(--ring)' : 'hover:border-(--border-strong)'"
|
||||
class="group flex items-center gap-3 rounded-lg border border-border bg-bg-inset px-3 py-2 transition"
|
||||
:class="lastMeasured?.index === index ? 'border-accent ring-2 ring-ring' : 'hover:border-border-strong'"
|
||||
>
|
||||
<span class="font-mono text-xs tabular-nums text-(--fg-subtle) w-5 text-right">{{ index + 1 }}</span>
|
||||
<span class="font-mono text-xs tabular-nums text-fg-subtle w-5 text-right">{{ index + 1 }}</span>
|
||||
<span class="flex min-w-0 flex-1 flex-col">
|
||||
<span class="truncate text-sm font-medium text-(--fg)">{{ track.title }}</span>
|
||||
<span class="truncate text-xs text-(--fg-muted)">{{ track.artist }}</span>
|
||||
<span class="truncate text-sm font-medium text-fg">{{ track.title }}</span>
|
||||
<span class="truncate text-xs text-fg-muted">{{ track.artist }}</span>
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Remove track"
|
||||
class="rounded-md px-1.5 py-0.5 text-xs text-(--fg-subtle) opacity-0 transition hover:text-(--fg) group-hover:opacity-100 cursor-pointer"
|
||||
class="rounded-md px-1.5 py-0.5 text-xs text-fg-subtle opacity-0 transition hover:text-fg group-hover:opacity-100 cursor-pointer"
|
||||
@click="removeTrack(track.id)"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</li>
|
||||
<li v-if="tracks.length === 0" class="px-3 py-6 text-center text-sm text-(--fg-subtle)">
|
||||
<li v-if="tracks.length === 0" class="px-3 py-6 text-center text-sm text-fg-subtle">
|
||||
No tracks — add one to collect a ref.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="lastMeasured"
|
||||
class="rounded-lg border border-(--border) bg-(--bg-inset) p-3 font-mono text-sm text-(--fg) tabular-nums"
|
||||
class="rounded-lg border border-border bg-bg-inset p-3 font-mono text-sm text-fg tabular-nums"
|
||||
>
|
||||
refs[{{ lastMeasured.index }}].width = {{ lastMeasured.width }}px
|
||||
</div>
|
||||
<p v-else class="text-xs text-(--fg-subtle)">
|
||||
<p v-else class="text-xs text-fg-subtle">
|
||||
Add a track to measure the newest collected element directly from the DOM.
|
||||
</p>
|
||||
|
||||
<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="addTrack"
|
||||
>
|
||||
Add track
|
||||
</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="collected === 0"
|
||||
@click="measureLast"
|
||||
>
|
||||
|
||||
@@ -36,54 +36,54 @@ const visibleRange = computed(() => {
|
||||
</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)">Virtual list</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">Virtual list</span>
|
||||
<span class="demo-badge">
|
||||
{{ total.toLocaleString() }} rows
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-bind="containerProps"
|
||||
class="h-64 rounded-xl border border-(--border) bg-(--bg-elevated)"
|
||||
class="demo-card h-64"
|
||||
>
|
||||
<div v-bind="wrapperProps">
|
||||
<div
|
||||
v-for="{ data, index } in list"
|
||||
:key="index"
|
||||
class="flex items-center gap-3 border-b border-(--border) px-3"
|
||||
class="flex items-center gap-3 border-b border-border px-3"
|
||||
:style="{ height: `${itemHeight}px` }"
|
||||
>
|
||||
<span
|
||||
class="size-6 shrink-0 rounded-md border border-(--border)"
|
||||
class="size-6 shrink-0 rounded-md border border-border"
|
||||
:style="{ backgroundColor: `hsl(${data.hue} 65% 55%)` }"
|
||||
/>
|
||||
<span class="flex-1 truncate font-mono text-sm text-(--fg) tabular-nums">{{ data.label }}</span>
|
||||
<span class="text-xs text-(--fg-subtle)">idx {{ index }}</span>
|
||||
<span class="flex-1 truncate font-mono text-sm text-fg tabular-nums">{{ data.label }}</span>
|
||||
<span class="text-xs text-fg-subtle">idx {{ index }}</span>
|
||||
</div>
|
||||
</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)">rendered</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">rendered</span>
|
||||
<span>{{ list.length }} nodes · idx {{ visibleRange }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-end gap-2">
|
||||
<label class="flex flex-1 flex-col gap-1">
|
||||
<span class="text-xs font-medium uppercase tracking-wide text-(--fg-subtle)">Scroll to index</span>
|
||||
<span class="demo-label">Scroll to index</span>
|
||||
<input
|
||||
v-model.number="jumpTo"
|
||||
type="number"
|
||||
:min="0"
|
||||
:max="total - 1"
|
||||
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>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-(--accent) px-3 py-2 text-sm font-medium text-(--accent-fg) transition hover:bg-(--accent-hover) active:scale-[0.98] cursor-pointer"
|
||||
class="inline-flex items-center justify-center gap-1.5 rounded-lg border border-transparent bg-accent px-3 py-2 text-sm font-medium text-accent-fg transition hover:bg-accent-hover active:scale-[0.98] cursor-pointer"
|
||||
@click="go"
|
||||
>
|
||||
Jump
|
||||
|
||||
@@ -200,7 +200,7 @@ function createMetrics(length: number, itemSize: UseVirtualListItemSize): UseVir
|
||||
*
|
||||
* @since 0.0.15
|
||||
*/
|
||||
export function useVirtualList<T = any>(
|
||||
export function useVirtualList<T = unknown>(
|
||||
list: MaybeRefOrGetter<readonly T[]>,
|
||||
options: UseVirtualListOptions,
|
||||
): UseVirtualListReturn<T> {
|
||||
|
||||
Reference in New Issue
Block a user