@alikhalilll/a-skeleton 1.1.0 → 1.2.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.
Files changed (53) hide show
  1. package/.media/hero.png +0 -0
  2. package/.media/hero.svg +232 -0
  3. package/README.md +458 -172
  4. package/dist/index.cjs +3685 -840
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +527 -40
  7. package/dist/index.d.ts +527 -40
  8. package/dist/index.js +3666 -842
  9. package/dist/index.js.map +1 -1
  10. package/dist/nuxt/index.cjs +16 -1
  11. package/dist/nuxt/index.cjs.map +1 -1
  12. package/dist/nuxt/index.js +16 -1
  13. package/dist/nuxt/index.js.map +1 -1
  14. package/dist/resolver/index.cjs +16 -1
  15. package/dist/resolver/index.cjs.map +1 -1
  16. package/dist/resolver/index.js +16 -1
  17. package/dist/resolver/index.js.map +1 -1
  18. package/dist/styles.css +56 -11
  19. package/package.json +8 -2
  20. package/src/components/ASkeleton.vue +212 -113
  21. package/src/components/ASkeletonClone.vue +106 -0
  22. package/src/components/ASkeletonLayer.vue +20 -32
  23. package/src/components/CloneNode.ts +161 -0
  24. package/src/components/StructuralLayerNode.ts +157 -0
  25. package/src/components/icons.ts +45 -0
  26. package/src/components/variants/ASkeletonArticle.vue +33 -0
  27. package/src/components/variants/ASkeletonAvatar.vue +42 -0
  28. package/src/components/variants/ASkeletonButton.vue +37 -0
  29. package/src/components/variants/ASkeletonCard.vue +47 -0
  30. package/src/components/variants/ASkeletonChart.vue +56 -0
  31. package/src/components/variants/ASkeletonChip.vue +32 -0
  32. package/src/components/variants/ASkeletonDivider.vue +26 -0
  33. package/src/components/variants/ASkeletonForm.vue +32 -0
  34. package/src/components/variants/ASkeletonHeading.vue +47 -0
  35. package/src/components/variants/ASkeletonImage.vue +57 -0
  36. package/src/components/variants/ASkeletonInput.vue +33 -0
  37. package/src/components/variants/ASkeletonListItem.vue +40 -0
  38. package/src/components/variants/ASkeletonTable.vue +49 -0
  39. package/src/components/variants/ASkeletonText.vue +49 -0
  40. package/src/components/variants/ASkeletonVideo.vue +55 -0
  41. package/src/composables/useShapeProbe.ts +33 -9
  42. package/src/composables/useSkeleton.ts +33 -21
  43. package/src/composables/useSkeletonCache.ts +251 -22
  44. package/src/index.ts +48 -2
  45. package/src/nuxt/index.ts +16 -0
  46. package/src/resolver/index.ts +16 -0
  47. package/src/types.ts +118 -2
  48. package/src/utils/buildStructuralSkeleton.ts +400 -103
  49. package/src/utils/captureStyles.ts +378 -0
  50. package/src/utils/domRead.ts +143 -0
  51. package/src/utils/walkDom.ts +261 -16
  52. package/src/utils/walkStructural.ts +418 -0
  53. package/web-types.json +9 -3
package/dist/index.d.ts CHANGED
@@ -2,6 +2,13 @@ import { CSSProperties, HTMLAttributes, PropType, Ref, VNode, VNodeArrayChildren
2
2
 
3
3
  //#region src/types.d.ts
4
4
  type ShapeNodeType = 'block' | 'text' | 'image' | 'circle';
5
+ /** A single per-line text rect captured via the `Range` API — root-relative. */
6
+ interface TextLineRect$1 {
7
+ x: number;
8
+ y: number;
9
+ w: number;
10
+ h: number;
11
+ }
5
12
  /** A single shimmer block in a captured skeleton — positioned absolutely inside the layer. */
6
13
  interface ShapeNode {
7
14
  type: ShapeNodeType;
@@ -14,6 +21,23 @@ interface ShapeNode {
14
21
  lines?: number;
15
22
  /** Line-height used when expanding `lines` into stacked bars. */
16
23
  lineHeight?: number;
24
+ /**
25
+ * Per-line text rects captured via `Range.getClientRects()` — when present, each
26
+ * rendered text line gets its own bar at the exact position and width of the
27
+ * actual rendered text, so wrapped/centered/RTL/short-last-line all replay correctly
28
+ * without heuristics. Supersedes `lines` + `lineHeight` for text nodes.
29
+ */
30
+ textRects?: ReadonlyArray<TextLineRect$1>;
31
+ /** Background colour captured via `getComputedStyle()` — applied inline so e.g. a white button stays white. */
32
+ bg?: string;
33
+ /** Shorthand border (`"<width>px solid <color>"`) — captured when the element has a visible border. */
34
+ border?: string;
35
+ /** Box-shadow captured verbatim when non-trivial — preserves elevation on cards. */
36
+ boxShadow?: string;
37
+ /** Element opacity when < 1 — captured so semi-transparent surfaces replay correctly. */
38
+ opacity?: number;
39
+ /** Resolved `text-align` (only meaningful for `type='text'`). */
40
+ textAlign?: 'left' | 'right' | 'center' | 'justify' | 'start' | 'end';
17
41
  /**
18
42
  * Pre-computed (and frozen) inline style for the block. Built once during
19
43
  * capture so the render path never allocates a style object per node per
@@ -33,11 +57,74 @@ interface CachedShape {
33
57
  /** Was the walk cut short by `maxNodes`? Surface so consumers can tune the budget. */
34
58
  truncated?: boolean;
35
59
  }
60
+ /** Per-line text rect on a text leaf — coordinates are leaf-relative. */
61
+ interface StructuralTextLineRect {
62
+ x: number;
63
+ y: number;
64
+ w: number;
65
+ h: number;
66
+ }
67
+ type StructuralNode = ContainerNode | LeafNode;
68
+ interface ContainerNode {
69
+ kind: 'container';
70
+ /** Lowercased tag — preserved so semantic CSS / a11y still applies. */
71
+ tag: string;
72
+ /** Original `class` attribute, verbatim. Empty string when the element has no class. */
73
+ className: string;
74
+ /** Captured layout + visual CSS (frozen). Layout props belong on containers; visual signals optional. */
75
+ style: Readonly<CSSProperties>;
76
+ children: ReadonlyArray<StructuralNode>;
77
+ }
78
+ type LeafKind = 'block' | 'text' | 'image' | 'media';
79
+ interface LeafNode {
80
+ kind: 'leaf';
81
+ leafKind: LeafKind;
82
+ /** Original `class` attribute of the real element, verbatim. Empty string when absent. */
83
+ className: string;
84
+ /**
85
+ * Captured inline style: width, height, plus the same comprehensive
86
+ * layout + visual capture used on containers (display, margin, padding,
87
+ * border, background, transform, …). Frozen.
88
+ */
89
+ style: Readonly<CSSProperties>;
90
+ /**
91
+ * Per-line rects for text leaves (`leafKind === 'text'`), captured via
92
+ * `Range.getClientRects()`. Coordinates are relative to the leaf's own box,
93
+ * so the renderer can lay them out with `position: absolute` inside a
94
+ * normal-flow text host.
95
+ */
96
+ textLines?: ReadonlyArray<StructuralTextLineRect>;
97
+ }
98
+ interface StructuralShape {
99
+ /** Captured root rect — used by consumers for sanity checks; not for replay sizing. */
100
+ width: number;
101
+ height: number;
102
+ nodes: ReadonlyArray<StructuralNode>;
103
+ /** Was the walk cut short by `maxNodes`? */
104
+ truncated: boolean;
105
+ /** ms since epoch — useful for cache invalidation policies. */
106
+ capturedAt: number;
107
+ /** Persisted-shape schema version. Currently 3. */
108
+ v: 3;
109
+ }
36
110
  type SkeletonAnimation = 'shimmer' | 'pulse' | 'none';
37
111
  type SkeletonFallback = 'shimmer' | 'block';
112
+ type ASkeletonMode = 'mirror' | 'clone';
38
113
  interface ASkeletonProps {
39
114
  /** When true, render the captured skeleton (or `fallback` slot) instead of the default slot. */
40
115
  loading: boolean;
116
+ /**
117
+ * Rendering strategy. Default `'mirror'`.
118
+ * - `'mirror'`: walks the slot's vnode tree and preserves every element
119
+ * with its real class / inline style. Pure-Vue, SSR-safe, no DOM read.
120
+ * - `'clone'`: mounts the slot off-screen once, takes a comprehensive
121
+ * `getComputedStyle()` snapshot of every leaf, then replays the
122
+ * snapshot as positioned divs carrying every captured CSS property.
123
+ * Pixel-identical to the real render, regardless of styling system
124
+ * (Tailwind, CSS-in-JS, DaisyUI, computed inline styles). Requires
125
+ * a DOM (client-side only).
126
+ */
127
+ mode?: ASkeletonMode;
41
128
  /**
42
129
  * Identifier used to look up + persist the captured shape. Defaults to
43
130
  * `"<slot-fingerprint>:<useId()>"` so every `<ASkeleton>` instance gets its
@@ -77,8 +164,14 @@ interface ASkeletonSlots {
77
164
  fallback?: () => unknown;
78
165
  }
79
166
  interface ASkeletonLayerProps {
80
- /** Shape captured by `walkDom` / `useSkeleton`. Renders nothing when undefined. */
81
- shape?: CachedShape;
167
+ /**
168
+ * Structural shape captured by `useSkeleton()` (Recipe 3) or `walkStructural()`
169
+ * directly. Renders nothing when `undefined`. The layer drops into the
170
+ * consumer's container as a transparent shell — captured containers preserve
171
+ * their tag/class/layout so the skeleton flows naturally inside the
172
+ * surrounding layout rather than being absolutely positioned.
173
+ */
174
+ shape?: StructuralShape;
82
175
  /** Animation variant. Default `'shimmer'`. */
83
176
  animation?: SkeletonAnimation;
84
177
  /** Class on the layer wrapper. */
@@ -108,28 +201,131 @@ interface ASkeletonBlockProps {
108
201
  type __VLS_Slots = ASkeletonSlots;
109
202
  declare const __VLS_base: import("vue").DefineComponent<ASkeletonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonProps> & Readonly<{}>, {
110
203
  animation: SkeletonAnimation;
204
+ mode: ASkeletonMode;
111
205
  maxDepth: number;
112
206
  maxNodes: number;
113
207
  minNodeSize: number;
114
208
  persist: boolean;
115
209
  fallback: SkeletonFallback;
116
210
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
117
- declare const __VLS_export$2: typeof __VLS_base;
118
- declare const _default: typeof __VLS_export$2;
211
+ declare const __VLS_export$18: typeof __VLS_base;
212
+ declare const _default: typeof __VLS_export$18;
119
213
  //#endregion
120
214
  //#region src/components/ASkeletonLayer.vue.d.ts
121
- declare const __VLS_export$1: import("vue").DefineComponent<ASkeletonLayerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonLayerProps> & Readonly<{}>, {
215
+ declare const __VLS_export$17: import("vue").DefineComponent<ASkeletonLayerProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonLayerProps> & Readonly<{}>, {
122
216
  animation: SkeletonAnimation;
123
217
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
124
- declare const _default$2: typeof __VLS_export$1;
218
+ declare const _default$14: typeof __VLS_export$17;
125
219
  //#endregion
126
220
  //#region src/components/ASkeletonBlock.vue.d.ts
127
- declare const __VLS_export: import("vue").DefineComponent<ASkeletonBlockProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonBlockProps> & Readonly<{}>, {
221
+ declare const __VLS_export$16: import("vue").DefineComponent<ASkeletonBlockProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ASkeletonBlockProps> & Readonly<{}>, {
128
222
  type: ShapeNodeType;
129
223
  lines: number;
130
224
  animation: SkeletonAnimation;
131
225
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
132
- declare const _default$1: typeof __VLS_export;
226
+ declare const _default$3: typeof __VLS_export$16;
227
+ //#endregion
228
+ //#region src/utils/domRead.d.ts
229
+ /**
230
+ * Shared DOM-read helpers used by both capture strategies:
231
+ * - `captureStyles.ts` (clone-mode comprehensive style snapshot)
232
+ * - `walkStructural.ts` (Recipe 3 tree-shaped capture)
233
+ *
234
+ * One source of truth for "what counts as a default CSS value", how to read a
235
+ * computed-style subset into a frozen camelCased object, and how to measure
236
+ * per-line text rectangles. Keeping both walkers behind these helpers prevents
237
+ * silent drift between the two pipelines.
238
+ */
239
+ /** A single rendered text-line rectangle, expressed in root-relative pixels. */
240
+ interface TextLineRect {
241
+ x: number;
242
+ y: number;
243
+ w: number;
244
+ h: number;
245
+ }
246
+ //#endregion
247
+ //#region src/utils/captureStyles.d.ts
248
+ /** A captured element — geometry + comprehensive style snapshot + children. */
249
+ interface CapturedNode {
250
+ /** Tag name lowercased — used by the replay to decide content-type treatment. */
251
+ tag: string;
252
+ /** Root-relative position + size in CSS pixels. */
253
+ x: number;
254
+ y: number;
255
+ w: number;
256
+ h: number;
257
+ /**
258
+ * Frozen, ready-to-apply `style` object. Only non-default visual
259
+ * properties are included — the snapshot for a default `<div>` is tiny.
260
+ */
261
+ style: Readonly<Record<string, string>>;
262
+ /**
263
+ * Content classification — drives how `<ASkeletonClone>` renders the leaf:
264
+ * - `text` → shimmer text bar (optionally with per-line rects)
265
+ * - `image` → solid surface with image-placeholder icon
266
+ * - `video` → solid surface with play-icon
267
+ * - `media` → atomic block (svg/canvas/iframe — no icon)
268
+ * - `block` → opaque shimmer block (default for unrecognised leaves)
269
+ * - `container` → has children; rendered as a positioned wrapper
270
+ */
271
+ kind: 'text' | 'image' | 'video' | 'media' | 'block' | 'container';
272
+ /**
273
+ * Per-line text rects (only set when `kind === 'text'`). Replaces the
274
+ * single-rect bar with one bar per rendered text line at the exact width
275
+ * of that line — handles wrapping, RTL, centered headings 1:1.
276
+ */
277
+ textLines?: ReadonlyArray<TextLineRect>;
278
+ /** Children (only set when `kind === 'container'`). */
279
+ children?: ReadonlyArray<CapturedNode>;
280
+ }
281
+ interface CaptureSnapshot {
282
+ /** Overall bounding box. */
283
+ width: number;
284
+ height: number;
285
+ /** Top-level captured nodes (siblings of the root's direct children). */
286
+ nodes: ReadonlyArray<CapturedNode>;
287
+ /** True if `maxNodes` was hit and the walk bailed out early. */
288
+ truncated: boolean;
289
+ /** When the snapshot was taken (ms since epoch). For cache invalidation policies. */
290
+ capturedAt: number;
291
+ }
292
+ interface CaptureOptions {
293
+ /** Max recursion depth. Default 12. */
294
+ maxDepth?: number;
295
+ /** Hard cap on captured nodes. Default 800. */
296
+ maxNodes?: number;
297
+ /** Skip elements smaller than this many CSS pixels (either axis). Default 4. */
298
+ minSize?: number;
299
+ /**
300
+ * When true, capture child elements even inside leaves that we'd normally
301
+ * treat atomically. Default false (atomic = single block).
302
+ */
303
+ walkAtomic?: boolean;
304
+ }
305
+ /**
306
+ * Snapshot the rendered DOM under `root`, returning a frozen tree of every
307
+ * visible element + its computed visual styles. Replaying the snapshot
308
+ * produces a surface visually identical to `root`.
309
+ */
310
+ declare function captureSnapshot(root: HTMLElement, options?: CaptureOptions): CaptureSnapshot;
311
+ //#endregion
312
+ //#region src/components/ASkeletonClone.vue.d.ts
313
+ /**
314
+ * `<ASkeletonClone>` — replays a `CaptureSnapshot` produced by
315
+ * `captureSnapshot()` as a tree of positioned divs each carrying its
316
+ * captured inline style. Pure render component; the recursive per-node
317
+ * rendering lives in `CloneNode` to keep the strategy table isolated from
318
+ * the layer-level concerns (sizing, animation, a11y).
319
+ */
320
+ interface Props$15 {
321
+ shape: CaptureSnapshot;
322
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
323
+ class?: string | string[] | Record<string, boolean>;
324
+ }
325
+ declare const __VLS_export$15: import("vue").DefineComponent<Props$15, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$15> & Readonly<{}>, {
326
+ animation: "pulse" | "shimmer" | "wave" | "none";
327
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
328
+ declare const _default$8: typeof __VLS_export$15;
133
329
  //#endregion
134
330
  //#region src/components/StructuralSkeleton.d.ts
135
331
  /**
@@ -181,6 +377,232 @@ declare const StructuralSkeleton: import("vue").DefineComponent<import("vue").Ex
181
377
  maxNodes: number;
182
378
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
183
379
  //#endregion
380
+ //#region src/components/variants/ASkeletonText.vue.d.ts
381
+ interface Props$14 {
382
+ lines?: number;
383
+ width?: number | string;
384
+ /** Animation variant. Default `'pulse'`. */
385
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
386
+ class?: HTMLAttributes['class'];
387
+ }
388
+ declare const __VLS_export$14: import("vue").DefineComponent<Props$14, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$14> & Readonly<{}>, {
389
+ lines: number;
390
+ animation: "pulse" | "shimmer" | "wave" | "none";
391
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
392
+ declare const _default$17: typeof __VLS_export$14;
393
+ //#endregion
394
+ //#region src/components/variants/ASkeletonHeading.vue.d.ts
395
+ interface Props$13 {
396
+ level?: 1 | 2 | 3 | 4 | 5 | 6;
397
+ width?: number | string;
398
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
399
+ class?: HTMLAttributes['class'];
400
+ }
401
+ declare const __VLS_export$13: import("vue").DefineComponent<Props$13, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$13> & Readonly<{}>, {
402
+ animation: "pulse" | "shimmer" | "wave" | "none";
403
+ level: 1 | 2 | 3 | 4 | 5 | 6;
404
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
405
+ declare const _default$11: typeof __VLS_export$13;
406
+ //#endregion
407
+ //#region src/components/variants/ASkeletonAvatar.vue.d.ts
408
+ interface Props$12 {
409
+ size?: number | string;
410
+ shape?: 'circle' | 'square' | 'rounded';
411
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
412
+ class?: HTMLAttributes['class'];
413
+ }
414
+ declare const __VLS_export$12: import("vue").DefineComponent<Props$12, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$12> & Readonly<{}>, {
415
+ animation: "pulse" | "shimmer" | "wave" | "none";
416
+ shape: "circle" | "square" | "rounded";
417
+ size: number | string;
418
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
419
+ declare const _default$2: typeof __VLS_export$12;
420
+ //#endregion
421
+ //#region src/components/variants/ASkeletonImage.vue.d.ts
422
+ interface Props$11 {
423
+ ratio?: string;
424
+ width?: number | string;
425
+ height?: number | string;
426
+ showIcon?: boolean;
427
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
428
+ class?: HTMLAttributes['class'];
429
+ }
430
+ declare const __VLS_export$11: import("vue").DefineComponent<Props$11, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$11> & Readonly<{}>, {
431
+ animation: "pulse" | "shimmer" | "wave" | "none";
432
+ ratio: string;
433
+ showIcon: boolean;
434
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
435
+ declare const _default$12: typeof __VLS_export$11;
436
+ //#endregion
437
+ //#region src/components/variants/ASkeletonVideo.vue.d.ts
438
+ interface Props$10 {
439
+ ratio?: string;
440
+ width?: number | string;
441
+ height?: number | string;
442
+ showIcon?: boolean;
443
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
444
+ class?: HTMLAttributes['class'];
445
+ }
446
+ declare const __VLS_export$10: import("vue").DefineComponent<Props$10, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$10> & Readonly<{}>, {
447
+ animation: "pulse" | "shimmer" | "wave" | "none";
448
+ ratio: string;
449
+ showIcon: boolean;
450
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
451
+ declare const _default$18: typeof __VLS_export$10;
452
+ //#endregion
453
+ //#region src/components/variants/ASkeletonButton.vue.d.ts
454
+ interface Props$9 {
455
+ width?: number | string;
456
+ height?: number | string;
457
+ outlined?: boolean;
458
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
459
+ class?: HTMLAttributes['class'];
460
+ }
461
+ declare const __VLS_export$9: import("vue").DefineComponent<Props$9, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$9> & Readonly<{}>, {
462
+ animation: "pulse" | "shimmer" | "wave" | "none";
463
+ width: number | string;
464
+ height: number | string;
465
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
466
+ declare const _default$4: typeof __VLS_export$9;
467
+ //#endregion
468
+ //#region src/components/variants/ASkeletonInput.vue.d.ts
469
+ interface Props$8 {
470
+ width?: number | string;
471
+ height?: number | string;
472
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
473
+ class?: HTMLAttributes['class'];
474
+ }
475
+ declare const __VLS_export$8: import("vue").DefineComponent<Props$8, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$8> & Readonly<{}>, {
476
+ animation: "pulse" | "shimmer" | "wave" | "none";
477
+ width: number | string;
478
+ height: number | string;
479
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
480
+ declare const _default$13: typeof __VLS_export$8;
481
+ //#endregion
482
+ //#region src/components/variants/ASkeletonListItem.vue.d.ts
483
+ interface Props$7 {
484
+ avatar?: boolean;
485
+ lines?: 1 | 2 | 3;
486
+ trailing?: boolean;
487
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
488
+ class?: HTMLAttributes['class'];
489
+ }
490
+ declare const __VLS_export$7: import("vue").DefineComponent<Props$7, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$7> & Readonly<{}>, {
491
+ lines: 1 | 2 | 3;
492
+ animation: "pulse" | "shimmer" | "wave" | "none";
493
+ avatar: boolean;
494
+ trailing: boolean;
495
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
496
+ declare const _default$15: typeof __VLS_export$7;
497
+ //#endregion
498
+ //#region src/components/variants/ASkeletonCard.vue.d.ts
499
+ interface Props$6 {
500
+ media?: boolean;
501
+ heading?: boolean;
502
+ lines?: number;
503
+ actions?: boolean;
504
+ footerAvatar?: boolean;
505
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
506
+ class?: HTMLAttributes['class'];
507
+ }
508
+ declare const __VLS_export$6: import("vue").DefineComponent<Props$6, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$6> & Readonly<{}>, {
509
+ lines: number;
510
+ animation: "pulse" | "shimmer" | "wave" | "none";
511
+ media: boolean;
512
+ heading: boolean;
513
+ actions: boolean;
514
+ footerAvatar: boolean;
515
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
516
+ declare const _default$5: typeof __VLS_export$6;
517
+ //#endregion
518
+ //#region src/components/variants/ASkeletonTable.vue.d.ts
519
+ interface Props$5 {
520
+ rows?: number;
521
+ columns?: number;
522
+ showHeader?: boolean;
523
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
524
+ class?: HTMLAttributes['class'];
525
+ }
526
+ declare const __VLS_export$5: import("vue").DefineComponent<Props$5, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$5> & Readonly<{}>, {
527
+ animation: "pulse" | "shimmer" | "wave" | "none";
528
+ rows: number;
529
+ columns: number;
530
+ showHeader: boolean;
531
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
532
+ declare const _default$16: typeof __VLS_export$5;
533
+ //#endregion
534
+ //#region src/components/variants/ASkeletonChart.vue.d.ts
535
+ interface Props$4 {
536
+ bars?: number;
537
+ height?: number | string;
538
+ showHeader?: boolean;
539
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
540
+ class?: HTMLAttributes['class'];
541
+ }
542
+ declare const __VLS_export$4: import("vue").DefineComponent<Props$4, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$4> & Readonly<{}>, {
543
+ animation: "pulse" | "shimmer" | "wave" | "none";
544
+ height: number | string;
545
+ showHeader: boolean;
546
+ bars: number;
547
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
548
+ declare const _default$6: typeof __VLS_export$4;
549
+ //#endregion
550
+ //#region src/components/variants/ASkeletonForm.vue.d.ts
551
+ interface Props$3 {
552
+ fields?: number;
553
+ showSubmit?: boolean;
554
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
555
+ class?: HTMLAttributes['class'];
556
+ }
557
+ declare const __VLS_export$3: import("vue").DefineComponent<Props$3, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$3> & Readonly<{}>, {
558
+ animation: "pulse" | "shimmer" | "wave" | "none";
559
+ fields: number;
560
+ showSubmit: boolean;
561
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
562
+ declare const _default$10: typeof __VLS_export$3;
563
+ //#endregion
564
+ //#region src/components/variants/ASkeletonArticle.vue.d.ts
565
+ interface Props$2 {
566
+ media?: boolean;
567
+ paragraphs?: number;
568
+ linesPerParagraph?: number;
569
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
570
+ class?: HTMLAttributes['class'];
571
+ }
572
+ declare const __VLS_export$2: import("vue").DefineComponent<Props$2, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$2> & Readonly<{}>, {
573
+ animation: "pulse" | "shimmer" | "wave" | "none";
574
+ media: boolean;
575
+ paragraphs: number;
576
+ linesPerParagraph: number;
577
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
578
+ declare const _default$1: typeof __VLS_export$2;
579
+ //#endregion
580
+ //#region src/components/variants/ASkeletonDivider.vue.d.ts
581
+ interface Props$1 {
582
+ thickness?: number;
583
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
584
+ class?: HTMLAttributes['class'];
585
+ }
586
+ declare const __VLS_export$1: import("vue").DefineComponent<Props$1, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props$1> & Readonly<{}>, {
587
+ animation: "pulse" | "shimmer" | "wave" | "none";
588
+ thickness: number;
589
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
590
+ declare const _default$9: typeof __VLS_export$1;
591
+ //#endregion
592
+ //#region src/components/variants/ASkeletonChip.vue.d.ts
593
+ interface Props {
594
+ width?: number | string;
595
+ height?: number | string;
596
+ animation?: 'pulse' | 'shimmer' | 'wave' | 'none';
597
+ class?: HTMLAttributes['class'];
598
+ }
599
+ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {
600
+ animation: "pulse" | "shimmer" | "wave" | "none";
601
+ width: number | string;
602
+ height: number | string;
603
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
604
+ declare const _default$7: typeof __VLS_export;
605
+ //#endregion
184
606
  //#region src/composables/useSkeleton.d.ts
185
607
  interface UseSkeletonOptions {
186
608
  /**
@@ -200,31 +622,33 @@ interface UseSkeletonOptions {
200
622
  target?: () => HTMLElement | null;
201
623
  /** Persist to `localStorage` so first-paint after reload skips the cold start. Default false. */
202
624
  persist?: boolean;
203
- /** Forwarded to `walkDom`. Default 6. */
625
+ /** Forwarded to `walkStructural`. Default 12. */
204
626
  maxDepth?: number;
205
- /** Forwarded to `walkDom`. Default 500. */
627
+ /** Forwarded to `walkStructural`. Default 500. */
206
628
  maxNodes?: number;
207
- /** Forwarded to `walkDom`. Default 4. */
629
+ /** Forwarded to `walkStructural`. Default 4. */
208
630
  minSize?: number;
209
631
  /** Forwarded to `useShapeProbe`. Default 150 ms. */
210
632
  resizeDebounceMs?: number;
211
633
  }
212
634
  interface UseSkeletonReturn {
213
- /** Reactive captured shape — `undefined` on cache miss. Replace your skeleton render path. */
214
- shape: Readonly<Ref<CachedShape | undefined>>;
635
+ /** Reactive captured shape — `undefined` on cache miss. Feed it to `<ASkeletonLayer>`. */
636
+ shape: Readonly<Ref<StructuralShape | undefined>>;
215
637
  /**
216
638
  * Synchronously measure the current target and write to cache. Returns the
217
639
  * captured shape, or `undefined` if the target wasn't available / nothing
218
640
  * worth measuring was rendered. Use when you want to force a capture outside
219
641
  * the automatic `ResizeObserver` flow (e.g. after an animation settles).
220
642
  */
221
- captureNow: () => CachedShape | undefined;
643
+ captureNow: () => StructuralShape | undefined;
222
644
  /** Drop the cache entry for this `cacheKey`. The reactive `shape` flips to `undefined`. */
223
645
  clear: () => void;
224
646
  }
225
647
  /**
226
- * High-level building block for custom skeleton UIs. Wires up the probe + cache
227
- * + reactivity so the consumer just renders something using the reactive shape:
648
+ * High-level building block for Recipe 3 wires the structural capture +
649
+ * cache + reactivity around a target element. The reactive `shape` is fed to
650
+ * `<ASkeletonLayer>` which renders it in normal flow inside the consumer's
651
+ * container.
228
652
  *
229
653
  * ```ts
230
654
  * const containerRef = ref<HTMLElement | null>(null);
@@ -241,17 +665,29 @@ interface UseSkeletonReturn {
241
665
  * </div>
242
666
  * ```
243
667
  *
244
- * For more control, drop down to `useShapeProbe` + `getCached`/`setCached` and
245
- * compose your own.
668
+ * For more control, drop down to `useShapeProbe` (pass `walkStructural` as the
669
+ * capture strategy) + `getCachedStructural` / `setCachedStructural` and compose
670
+ * your own orchestration.
246
671
  */
247
672
  declare function useSkeleton(options: UseSkeletonOptions): UseSkeletonReturn;
248
673
  //#endregion
249
674
  //#region src/composables/useShapeProbe.d.ts
250
- interface ShapeProbeOptions {
675
+ type ProbeShape = CachedShape | StructuralShape;
676
+ /**
677
+ * Pluggable capture strategy. `walkDom` is the default (flat, absolute-
678
+ * positioned `CachedShape`). Recipe 3 passes `walkStructural` to produce a
679
+ * tree-shaped `StructuralShape` instead.
680
+ */
681
+ type CaptureStrategy<S extends ProbeShape> = (el: HTMLElement, options: {
682
+ maxDepth: number;
683
+ maxNodes?: number;
684
+ minSize?: number;
685
+ }) => S;
686
+ interface ShapeProbeOptions<S extends ProbeShape = CachedShape> {
251
687
  maxDepth: number;
252
- /** Forwarded to `walkDom`. Default 500. */
688
+ /** Forwarded to the capture strategy. Default 500. */
253
689
  maxNodes?: number;
254
- /** Forwarded to `walkDom`. Default 4. */
690
+ /** Forwarded to the capture strategy. Default 4. */
255
691
  minSize?: number;
256
692
  /**
257
693
  * Debounce window for `ResizeObserver`-triggered re-captures, in ms.
@@ -260,7 +696,12 @@ interface ShapeProbeOptions {
260
696
  * always immediate via `requestAnimationFrame`.
261
697
  */
262
698
  resizeDebounceMs?: number;
263
- onCapture: (shape: CachedShape) => void;
699
+ /**
700
+ * Capture strategy. Default: `walkDom` (flat positioned-block model).
701
+ * Pass `walkStructural` for the tree-shaped Recipe 3 model.
702
+ */
703
+ capture?: CaptureStrategy<S>;
704
+ onCapture: (shape: S) => void;
264
705
  }
265
706
  /**
266
707
  * Observe `getTarget()` and capture its rendered shape whenever the element
@@ -272,10 +713,10 @@ interface ShapeProbeOptions {
272
713
  * render queue.
273
714
  * - Subsequent `ResizeObserver` callbacks are debounced (default 150 ms) so a
274
715
  * drag-resize doesn't trigger a fresh DOM walk per frame.
275
- * - `walkDom` itself enforces `maxNodes` so even a worst-case capture (10k
276
- * descendants) returns in bounded time.
716
+ * - The capture strategy itself enforces `maxNodes` so even a worst-case
717
+ * capture (10k descendants) returns in bounded time.
277
718
  */
278
- declare function useShapeProbe(getTarget: () => HTMLElement | null, options: ShapeProbeOptions): void;
719
+ declare function useShapeProbe<S extends ProbeShape = CachedShape>(getTarget: () => HTMLElement | null, options: ShapeProbeOptions<S>): void;
279
720
  //#endregion
280
721
  //#region src/composables/useSkeletonCache.d.ts
281
722
  /**
@@ -288,8 +729,24 @@ declare function useShapeProbe(getTarget: () => HTMLElement | null, options: Sha
288
729
  declare function getCached(key: string, persist: boolean): CachedShape | undefined;
289
730
  /** Store a captured shape. `persist=true` mirrors to `localStorage`. */
290
731
  declare function setCached(key: string, value: CachedShape, persist: boolean): void;
291
- /** Drop a single entry (or all entries when no key). Exposed for tests + manual invalidation. */
732
+ /**
733
+ * Drop a single entry (or all entries when no key) — wipes both the flat-shape
734
+ * and the structural-shape namespaces. Exposed for tests + manual invalidation.
735
+ */
292
736
  declare function clearCached(key?: string): void;
737
+ /**
738
+ * Drop a single structural-shape entry (or all when no key). Kept separate
739
+ * from `clearCached` so callers that only manage structural shapes don't have
740
+ * to reach across namespaces.
741
+ */
742
+ declare function clearCachedStructural(key?: string): void;
743
+ /**
744
+ * Lookup a structural shape by key. Reads in-memory first, then `localStorage`
745
+ * when `persist` is enabled. Stale schema versions get purged on read.
746
+ */
747
+ declare function getCachedStructural(key: string, persist: boolean): StructuralShape | undefined;
748
+ /** Store a structural shape. `persist=true` mirrors to `localStorage`. */
749
+ declare function setCachedStructural(key: string, value: StructuralShape, persist: boolean): void;
293
750
  //#endregion
294
751
  //#region src/utils/walkDom.d.ts
295
752
  interface WalkOptions {
@@ -316,29 +773,59 @@ interface WalkOptions {
316
773
  */
317
774
  declare function walkDom(root: HTMLElement, options: WalkOptions): CachedShape;
318
775
  //#endregion
776
+ //#region src/utils/walkStructural.d.ts
777
+ interface WalkStructuralOptions {
778
+ /** Max recursion depth. Default 12. */
779
+ maxDepth?: number;
780
+ /** Hard cap on captured nodes. Default 500. */
781
+ maxNodes?: number;
782
+ /** Skip elements smaller than this many CSS pixels (either axis). Default 4. */
783
+ minSize?: number;
784
+ }
785
+ /**
786
+ * Walk `root`'s descendants and produce a frozen tree-shaped capture.
787
+ * Direct children of `root` become top-level `nodes`; recursive children
788
+ * live on their respective `ContainerNode`s.
789
+ */
790
+ declare function walkStructural(root: HTMLElement, options?: WalkStructuralOptions): StructuralShape;
791
+ //#endregion
319
792
  //#region src/utils/buildStructuralSkeleton.d.ts
320
793
  interface BuildOptions {
794
+ /** Animation class applied to every emitted shimmer surface. `null` disables animation. */
321
795
  animationClass: string | null;
322
- /** Max recursion depth — guards runaway templates. Default 8. */
796
+ /** Max recursion depth — guards runaway templates. Default 16. */
323
797
  maxDepth?: number;
324
798
  /**
325
- * Hard cap on emitted skeleton nodes. Default 300. A 200-row table doesn't
326
- * need 200 distinct skeleton rows on first paint; cap and stop early.
799
+ * Hard cap on emitted skeleton nodes. Default 600. The walk stops emitting
800
+ * past this cap; the partial tree is still valid (it just won't include the
801
+ * leaves beyond the budget).
327
802
  */
328
803
  maxNodes?: number;
329
804
  }
330
805
  /**
331
- * Walk a slot's vnode tree and produce a skeleton that mirrors its rendered
332
- * structure: same wrapping tags, same `class` strings (so flex/grid/spacing/
333
- * sizing utilities still apply), but text/atomic leaves replaced with shimmer
334
- * blocks. The result renders correctly on the FIRST paint without any DOM
335
- * measurement, as long as the slot's template renders structure even when its
336
- * data is empty (use `v-if`/`v-else` to swap content, not to gate the wrapper).
806
+ * Walk a slot's vnode tree and produce a **DOM-mirror skeleton**: every element
807
+ * is preserved (same tag, same `class`, same inline `style`), and only the
808
+ * content is replaced. The output is structurally identical to what the real
809
+ * component would render Tailwind utilities for layout, spacing, sizing,
810
+ * backgrounds and shadows all carry through. The CSS does the work of making
811
+ * it *look* like a skeleton.
337
812
  *
338
- * Performance: `maxNodes` caps the work. When the cap is hit we stop emitting
339
- * the caller still gets a valid skeleton, just clipped at the budget. A 1000-
340
- * row list renders ~300 skeleton rows on first paint and then the measured cache
341
- * takes over for subsequent loads.
813
+ * Replacement rules:
814
+ * - **Raw text** (e.g. interpolations, static strings) is wrapped in
815
+ * `<span class="a-skel-text-content">…the real text…</span>`. The text is
816
+ * kept as the span's children so the inline layout reserves its real
817
+ * rendered width — but the glyphs are painted transparent and a skeleton
818
+ * gradient is painted in their place. Multi-line text wraps naturally,
819
+ * producing one shimmer rect per visual line at the exact rendered position.
820
+ * - **Atomic / interactive tags** (`img`, `svg`, `button`, `input`, …) are
821
+ * replaced by a `<div class="a-skel-block">` carrying the original element's
822
+ * `class` and `style`, so dimensions and shapes (size, border-radius, etc.)
823
+ * still drive the layout.
824
+ * - **Container elements** (`div`, `section`, `h1`, `p`, `a`, `span`, …) are
825
+ * preserved as the same tag with the same `class` and `style`; we recurse
826
+ * into their children.
827
+ * - **Component vnodes** can't be introspected at render time, so we emit a
828
+ * single `<div class="a-skel-block">` carrying their outer `class` / `style`.
342
829
  */
343
830
  declare function buildStructuralSkeleton(vnodes: VNodeChild | VNodeArrayChildren | undefined | null, opts: BuildOptions): VNode[];
344
831
  //#endregion
@@ -351,5 +838,5 @@ declare function buildStructuralSkeleton(vnodes: VNodeChild | VNodeArrayChildren
351
838
  */
352
839
  declare function fingerprintSlot(vnodes: VNode[] | undefined): string;
353
840
  //#endregion
354
- 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 };
841
+ export { _default as ASkeleton, _default$1 as ASkeletonArticle, _default$2 as ASkeletonAvatar, _default$3 as ASkeletonBlock, type ASkeletonBlockProps, _default$4 as ASkeletonButton, _default$5 as ASkeletonCard, _default$6 as ASkeletonChart, _default$7 as ASkeletonChip, _default$8 as ASkeletonClone, _default$9 as ASkeletonDivider, _default$10 as ASkeletonForm, _default$11 as ASkeletonHeading, _default$12 as ASkeletonImage, _default$13 as ASkeletonInput, _default$14 as ASkeletonLayer, type ASkeletonLayerProps, _default$15 as ASkeletonListItem, type ASkeletonProps, type ASkeletonSlots, _default$16 as ASkeletonTable, _default$17 as ASkeletonText, _default$18 as ASkeletonVideo, type BuildOptions, type CachedShape, type CaptureOptions, type CaptureSnapshot, type CaptureStrategy, type CapturedNode, type ContainerNode, type LeafKind, type LeafNode, type ProbeShape, type ShapeNode, type ShapeNodeType, type ShapeProbeOptions, type SkeletonAnimation, type SkeletonFallback, type StructuralNode, type StructuralShape, StructuralSkeleton, type StructuralTextLineRect, type TextLineRect, type UseSkeletonOptions, type UseSkeletonReturn, type WalkOptions, type WalkStructuralOptions, buildStructuralSkeleton, captureSnapshot, clearCached, clearCachedStructural, fingerprintSlot, getCached, getCachedStructural, setCached, setCachedStructural, useShapeProbe, useSkeleton, walkDom, walkStructural };
355
842
  //# sourceMappingURL=index.d.ts.map