@alikhalilll/a-skeleton 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,351 @@
1
+ import { CSSProperties, HTMLAttributes, PropType, Ref, VNode, VNodeArrayChildren, VNodeChild } from "vue";
2
+
3
+ //#region src/types.d.ts
4
+ type ShapeNodeType = 'block' | 'text' | 'image' | 'circle';
5
+ /** A single shimmer block in a captured skeleton — positioned absolutely inside the layer. */
6
+ interface ShapeNode {
7
+ type: ShapeNodeType;
8
+ x: number;
9
+ y: number;
10
+ w: number;
11
+ h: number;
12
+ radius: number;
13
+ /** For multi-line text leaves, how many lines to render. */
14
+ lines?: number;
15
+ /** Line-height used when expanding `lines` into stacked bars. */
16
+ lineHeight?: number;
17
+ /**
18
+ * Pre-computed (and frozen) inline style for the block. Built once during
19
+ * capture so the render path never allocates a style object per node per
20
+ * render — meaningful when a cache has hundreds of nodes.
21
+ */
22
+ style?: Readonly<CSSProperties>;
23
+ /**
24
+ * Pre-computed line styles for multi-line text. `lines` long when set. Same
25
+ * reason as `style`: avoids `textLineStyle(node, i)` calls in the template.
26
+ */
27
+ lineStyles?: ReadonlyArray<Readonly<CSSProperties>>;
28
+ }
29
+ interface CachedShape {
30
+ nodes: ReadonlyArray<ShapeNode>;
31
+ width: number;
32
+ height: number;
33
+ /** Was the walk cut short by `maxNodes`? Surface so consumers can tune the budget. */
34
+ truncated?: boolean;
35
+ }
36
+ type SkeletonAnimation = 'shimmer' | 'pulse' | 'none';
37
+ type SkeletonFallback = 'shimmer' | 'block';
38
+ interface ASkeletonProps {
39
+ /** When true, render the captured skeleton (or `fallback` slot) instead of the default slot. */
40
+ loading: boolean;
41
+ /**
42
+ * Identifier used to look up + persist the captured shape. Falls back to the
43
+ * slot's first child component name, then `'anonymous'`. Pass explicitly when
44
+ * the same component renders different shapes depending on props.
45
+ */
46
+ cacheKey?: string;
47
+ /** Max recursion depth during shape capture. Default 6. */
48
+ maxDepth?: number;
49
+ /**
50
+ * Hard cap on captured / structurally rendered nodes. Hit this and the walk
51
+ * bails out with `truncated: true`. Default 500 — enough for cards, lists,
52
+ * full forms; cut deliberately for screens with hundreds of repeated rows.
53
+ */
54
+ maxNodes?: number;
55
+ /**
56
+ * Skip elements smaller than this many CSS pixels on either axis during
57
+ * capture. Default 4. Filters out hairlines / inline padding shims that
58
+ * inflate the node count without adding visual signal.
59
+ */
60
+ minNodeSize?: number;
61
+ /** Persist captured shapes to `localStorage` so first-visit skeletons survive reloads. Default false. */
62
+ persist?: boolean;
63
+ /** Animation variant applied to skeleton blocks. Default `'shimmer'`. */
64
+ animation?: SkeletonAnimation;
65
+ /** What to render when no cached shape is available yet. Default `'shimmer'`. */
66
+ fallback?: SkeletonFallback;
67
+ /** Class on the outer wrapper. */
68
+ class?: HTMLAttributes['class'];
69
+ }
70
+ interface ASkeletonSlots {
71
+ /** The real content. Rendered when `loading` is false; measured to build the skeleton. */
72
+ default?: () => unknown;
73
+ /** Custom UI to render on a cache miss. Defaults to a single shimmer block. */
74
+ fallback?: () => unknown;
75
+ }
76
+ interface ASkeletonLayerProps {
77
+ /** Shape captured by `walkDom` / `useSkeleton`. Renders nothing when undefined. */
78
+ shape?: CachedShape;
79
+ /** Animation variant. Default `'shimmer'`. */
80
+ animation?: SkeletonAnimation;
81
+ /** Class on the layer wrapper. */
82
+ class?: HTMLAttributes['class'];
83
+ }
84
+ interface ASkeletonBlockProps {
85
+ /** Visual variant. Default `'block'`. */
86
+ type?: ShapeNodeType;
87
+ /** Width as CSS length. Number = pixels. Falls back to whatever the surrounding layout gives. */
88
+ w?: number | string;
89
+ /** Height as CSS length. Number = pixels. */
90
+ h?: number | string;
91
+ /**
92
+ * Border radius as CSS length. Number = pixels. For `type='circle'`, this
93
+ * defaults to `'50%'` if not provided.
94
+ */
95
+ radius?: number | string;
96
+ /** For `type='text'`, render N stacked bars. Last bar is 70% width. Default 1. */
97
+ lines?: number;
98
+ /** Animation variant. Default `'shimmer'`. */
99
+ animation?: SkeletonAnimation;
100
+ /** Class on the root (the single block, or the stack wrapper for multi-line text). */
101
+ class?: HTMLAttributes['class'];
102
+ }
103
+ //#endregion
104
+ //#region src/components/ASkeleton.vue.d.ts
105
+ type __VLS_Slots = ASkeletonSlots;
106
+ declare const __VLS_base: import("vue").DefineComponent<ASkeletonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonProps> & Readonly<{}>, {
107
+ animation: SkeletonAnimation;
108
+ maxDepth: number;
109
+ maxNodes: number;
110
+ minNodeSize: number;
111
+ persist: boolean;
112
+ fallback: SkeletonFallback;
113
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
114
+ declare const __VLS_export$2: typeof __VLS_base;
115
+ declare const _default: typeof __VLS_export$2;
116
+ //#endregion
117
+ //#region src/components/ASkeletonLayer.vue.d.ts
118
+ declare const __VLS_export$1: import("vue").DefineComponent<ASkeletonLayerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonLayerProps> & Readonly<{}>, {
119
+ animation: SkeletonAnimation;
120
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
121
+ declare const _default$2: typeof __VLS_export$1;
122
+ //#endregion
123
+ //#region src/components/ASkeletonBlock.vue.d.ts
124
+ declare const __VLS_export: import("vue").DefineComponent<ASkeletonBlockProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonBlockProps> & Readonly<{}>, {
125
+ type: ShapeNodeType;
126
+ lines: number;
127
+ animation: SkeletonAnimation;
128
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
129
+ declare const _default$1: typeof __VLS_export;
130
+ //#endregion
131
+ //#region src/components/StructuralSkeleton.d.ts
132
+ /**
133
+ * Renders a structural skeleton derived from a slot's vnode tree. Pure render
134
+ * function — no template, no scoped styles — so the parent's class strings
135
+ * pass through unchanged and Tailwind utilities continue to drive layout.
136
+ *
137
+ * `maxNodes` is forwarded to the walker; cap defaults to 300 (see
138
+ * `buildStructuralSkeleton`). Beyond that the walk stops emitting and the cap
139
+ * propagates back as a clipped tree, keeping first-paint bounded.
140
+ */
141
+ declare const StructuralSkeleton: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
142
+ vnodes: {
143
+ type: PropType<VNodeArrayChildren>;
144
+ required: true;
145
+ };
146
+ animation: {
147
+ type: PropType<string | null>;
148
+ default: null;
149
+ };
150
+ maxDepth: {
151
+ type: NumberConstructor;
152
+ default: number;
153
+ };
154
+ maxNodes: {
155
+ type: NumberConstructor;
156
+ default: number;
157
+ };
158
+ }>, () => VNode[], {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
159
+ vnodes: {
160
+ type: PropType<VNodeArrayChildren>;
161
+ required: true;
162
+ };
163
+ animation: {
164
+ type: PropType<string | null>;
165
+ default: null;
166
+ };
167
+ maxDepth: {
168
+ type: NumberConstructor;
169
+ default: number;
170
+ };
171
+ maxNodes: {
172
+ type: NumberConstructor;
173
+ default: number;
174
+ };
175
+ }>> & Readonly<{}>, {
176
+ animation: string | null;
177
+ maxDepth: number;
178
+ maxNodes: number;
179
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
180
+ //#endregion
181
+ //#region src/composables/useSkeleton.d.ts
182
+ interface UseSkeletonOptions {
183
+ /**
184
+ * Identifier for the shape cache. Pass the same key wherever the same visual
185
+ * structure appears so the captured shape replays everywhere.
186
+ */
187
+ cacheKey: string;
188
+ /**
189
+ * Getter for the element to measure. When it returns `null` (e.g. during the
190
+ * loading state), no measurement happens. The probe re-arms automatically
191
+ * once the getter returns an element again.
192
+ *
193
+ * Typical pattern: return `null` while loading so the real content is the
194
+ * only thing ever measured, then the cache feeds the skeleton on the next
195
+ * load.
196
+ */
197
+ target?: () => HTMLElement | null;
198
+ /** Persist to `localStorage` so first-paint after reload skips the cold start. Default false. */
199
+ persist?: boolean;
200
+ /** Forwarded to `walkDom`. Default 6. */
201
+ maxDepth?: number;
202
+ /** Forwarded to `walkDom`. Default 500. */
203
+ maxNodes?: number;
204
+ /** Forwarded to `walkDom`. Default 4. */
205
+ minSize?: number;
206
+ /** Forwarded to `useShapeProbe`. Default 150 ms. */
207
+ resizeDebounceMs?: number;
208
+ }
209
+ interface UseSkeletonReturn {
210
+ /** Reactive captured shape — `undefined` on cache miss. Replace your skeleton render path. */
211
+ shape: Readonly<Ref<CachedShape | undefined>>;
212
+ /**
213
+ * Synchronously measure the current target and write to cache. Returns the
214
+ * captured shape, or `undefined` if the target wasn't available / nothing
215
+ * worth measuring was rendered. Use when you want to force a capture outside
216
+ * the automatic `ResizeObserver` flow (e.g. after an animation settles).
217
+ */
218
+ captureNow: () => CachedShape | undefined;
219
+ /** Drop the cache entry for this `cacheKey`. The reactive `shape` flips to `undefined`. */
220
+ clear: () => void;
221
+ }
222
+ /**
223
+ * High-level building block for custom skeleton UIs. Wires up the probe + cache
224
+ * + reactivity so the consumer just renders something using the reactive shape:
225
+ *
226
+ * ```ts
227
+ * const containerRef = ref<HTMLElement | null>(null);
228
+ * const { shape } = useSkeleton({
229
+ * cacheKey: 'user-card',
230
+ * target: () => (loading.value ? null : containerRef.value),
231
+ * });
232
+ * ```
233
+ *
234
+ * ```vue
235
+ * <div ref="containerRef">
236
+ * <ASkeletonLayer v-if="loading && shape" :shape="shape" />
237
+ * <UserCard v-else :data="user" />
238
+ * </div>
239
+ * ```
240
+ *
241
+ * For more control, drop down to `useShapeProbe` + `getCached`/`setCached` and
242
+ * compose your own.
243
+ */
244
+ declare function useSkeleton(options: UseSkeletonOptions): UseSkeletonReturn;
245
+ //#endregion
246
+ //#region src/composables/useShapeProbe.d.ts
247
+ interface ShapeProbeOptions {
248
+ maxDepth: number;
249
+ /** Forwarded to `walkDom`. Default 500. */
250
+ maxNodes?: number;
251
+ /** Forwarded to `walkDom`. Default 4. */
252
+ minSize?: number;
253
+ /**
254
+ * Debounce window for `ResizeObserver`-triggered re-captures, in ms.
255
+ * Default 150. Prevents a re-walk every animation frame while the user is
256
+ * actively dragging the viewport edge. The first capture (initial mount) is
257
+ * always immediate via `requestAnimationFrame`.
258
+ */
259
+ resizeDebounceMs?: number;
260
+ onCapture: (shape: CachedShape) => void;
261
+ }
262
+ /**
263
+ * Observe `getTarget()` and capture its rendered shape whenever the element
264
+ * appears or resizes.
265
+ *
266
+ * Performance:
267
+ * - Initial capture runs via `requestAnimationFrame` so it sneaks into the
268
+ * first idle window after mount — no synchronous layout from inside the
269
+ * render queue.
270
+ * - Subsequent `ResizeObserver` callbacks are debounced (default 150 ms) so a
271
+ * drag-resize doesn't trigger a fresh DOM walk per frame.
272
+ * - `walkDom` itself enforces `maxNodes` so even a worst-case capture (10k
273
+ * descendants) returns in bounded time.
274
+ */
275
+ declare function useShapeProbe(getTarget: () => HTMLElement | null, options: ShapeProbeOptions): void;
276
+ //#endregion
277
+ //#region src/composables/useSkeletonCache.d.ts
278
+ /**
279
+ * Lookup a captured shape by key. Reads in-memory first, then `localStorage` if
280
+ * `persist` is enabled. SSR-safe — bypasses `window` access on the server.
281
+ * Rehydrates pre-computed styles for shapes deserialized from older sessions
282
+ * (Object.freeze + style/lineStyles don't survive `JSON.stringify` round-trip).
283
+ */
284
+ declare function getCached(key: string, persist: boolean): CachedShape | undefined;
285
+ /** Store a captured shape. `persist=true` mirrors to `localStorage`. */
286
+ declare function setCached(key: string, value: CachedShape, persist: boolean): void;
287
+ /** Drop a single entry (or all entries when no key). Exposed for tests + manual invalidation. */
288
+ declare function clearCached(key?: string): void;
289
+ //#endregion
290
+ //#region src/utils/walkDom.d.ts
291
+ interface WalkOptions {
292
+ maxDepth: number;
293
+ /** Hard cap on captured nodes. Default 500. */
294
+ maxNodes?: number;
295
+ /** Min CSS-pixel size (either axis) for an element to be emitted. Default 4. */
296
+ minSize?: number;
297
+ }
298
+ /**
299
+ * Walk `root`'s descendants and produce a list of shimmer blocks that mirror its
300
+ * rendered layout. Coordinates are relative to `root`'s top-left so the result can
301
+ * be replayed in any container of the same size.
302
+ *
303
+ * Performance:
304
+ * - `maxNodes` caps the walk so a 5000-row table doesn't lock up the main thread.
305
+ * - `minSize` filters out hairlines (1-2 px paddings, decorative dots) that
306
+ * inflate node count without adding visual signal.
307
+ * - All `getBoundingClientRect` / `getComputedStyle` reads happen in a single
308
+ * top-down pass with no intervening writes, so the browser does one layout
309
+ * up front and serves cached values from then on (no layout thrashing).
310
+ * - Each emitted `ShapeNode` has a frozen pre-computed `style` (and `lineStyles`
311
+ * for multi-line text) so the render path is allocation-free.
312
+ */
313
+ declare function walkDom(root: HTMLElement, options: WalkOptions): CachedShape;
314
+ //#endregion
315
+ //#region src/utils/buildStructuralSkeleton.d.ts
316
+ interface BuildOptions {
317
+ animationClass: string | null;
318
+ /** Max recursion depth — guards runaway templates. Default 8. */
319
+ maxDepth?: number;
320
+ /**
321
+ * Hard cap on emitted skeleton nodes. Default 300. A 200-row table doesn't
322
+ * need 200 distinct skeleton rows on first paint; cap and stop early.
323
+ */
324
+ maxNodes?: number;
325
+ }
326
+ /**
327
+ * Walk a slot's vnode tree and produce a skeleton that mirrors its rendered
328
+ * structure: same wrapping tags, same `class` strings (so flex/grid/spacing/
329
+ * sizing utilities still apply), but text/atomic leaves replaced with shimmer
330
+ * blocks. The result renders correctly on the FIRST paint without any DOM
331
+ * measurement, as long as the slot's template renders structure even when its
332
+ * data is empty (use `v-if`/`v-else` to swap content, not to gate the wrapper).
333
+ *
334
+ * Performance: `maxNodes` caps the work. When the cap is hit we stop emitting
335
+ * — the caller still gets a valid skeleton, just clipped at the budget. A 1000-
336
+ * row list renders ~300 skeleton rows on first paint and then the measured cache
337
+ * takes over for subsequent loads.
338
+ */
339
+ declare function buildStructuralSkeleton(vnodes: VNodeChild | VNodeArrayChildren | undefined | null, opts: BuildOptions): VNode[];
340
+ //#endregion
341
+ //#region src/utils/fingerprint.d.ts
342
+ /**
343
+ * Derive a default cache key from a slot's vnode tree. Returns the first
344
+ * non-comment child's component name (or HTML tag). When the slot only contains
345
+ * text / comments / unknown content, returns `'anonymous'` so the caller can
346
+ * still cache, but with no useful identity — encourage an explicit `cacheKey`.
347
+ */
348
+ declare function fingerprintSlot(vnodes: VNode[] | undefined): string;
349
+ //#endregion
350
+ export { _default as ASkeleton, _default$1 as ASkeletonBlock, type ASkeletonBlockProps, _default$2 as ASkeletonLayer, type ASkeletonLayerProps, type ASkeletonProps, type ASkeletonSlots, type BuildOptions, type CachedShape, type ShapeNode, type ShapeNodeType, type ShapeProbeOptions, type SkeletonAnimation, type SkeletonFallback, StructuralSkeleton, type UseSkeletonOptions, type UseSkeletonReturn, type WalkOptions, buildStructuralSkeleton, clearCached, fingerprintSlot, getCached, setCached, useShapeProbe, useSkeleton, walkDom };
351
+ //# sourceMappingURL=index.d.cts.map