@alikhalilll/a-skeleton 1.0.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 +459 -170
  4. package/dist/index.cjs +3696 -828
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +534 -43
  7. package/dist/index.d.ts +534 -43
  8. package/dist/index.js +3677 -830
  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 +216 -98
  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 +282 -24
  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 +124 -5
  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 +10 -4
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Comment, Fragment, Text, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, h, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderList, renderSlot, shallowRef, useSlots, watch } from "vue";
1
+ import { Comment, Fragment, Text, cloneVNode, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, h, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderList, renderSlot, shallowRef, useId, useSlots, watch } from "vue";
2
2
  //#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
3
3
  function r(e) {
4
4
  var t, f, n = "";
@@ -3398,689 +3398,1118 @@ function cn(...inputs) {
3398
3398
  return twMerge(clsx(inputs));
3399
3399
  }
3400
3400
  //#endregion
3401
- //#region src/utils/walkDom.ts
3402
- const DEFAULT_MAX_NODES$1 = 500;
3403
- const DEFAULT_MIN_SIZE = 4;
3404
- const LEAF_TAGS = new Set([
3405
- "IMG",
3406
- "SVG",
3407
- "CANVAS",
3408
- "VIDEO",
3409
- "INPUT",
3410
- "TEXTAREA",
3411
- "SELECT",
3412
- "BUTTON",
3413
- "PROGRESS",
3414
- "METER",
3415
- "HR"
3401
+ //#region src/utils/buildStructuralSkeleton.ts
3402
+ /**
3403
+ * Atomic tags — rendered as a single shimmer block. Their internal rendering
3404
+ * is opaque to vnode walking (no meaningful child structure for us to mirror).
3405
+ * Their own `class` + `style` are preserved so Tailwind utilities like
3406
+ * `size-16` and `rounded-full` still drive the dimensions.
3407
+ *
3408
+ * Note: `button`, `a`, `label` are deliberately NOT atomic — they're treated
3409
+ * as containers so their real `background-color` / `border` / shadow survive
3410
+ * (we add `.a-skel-block` to atomics, which paints a skeleton gradient that
3411
+ * would override Tailwind's `bg-emerald-600` and friends). Their text content
3412
+ * is recursed and replaced with a shimmer span inside the real button shape.
3413
+ */
3414
+ const ATOMIC_TAGS$2 = new Set([
3415
+ "img",
3416
+ "svg",
3417
+ "canvas",
3418
+ "video",
3419
+ "audio",
3420
+ "input",
3421
+ "textarea",
3422
+ "select",
3423
+ "progress",
3424
+ "meter",
3425
+ "hr",
3426
+ "iframe",
3427
+ "object",
3428
+ "embed",
3429
+ "picture"
3416
3430
  ]);
3417
3431
  /**
3418
- * Walk `root`'s descendants and produce a list of shimmer blocks that mirror its
3419
- * rendered layout. Coordinates are relative to `root`'s top-left so the result can
3420
- * be replayed in any container of the same size.
3432
+ * SVG child tags that should never be walked by the structural skeleton
3433
+ * SVG interior elements use a different coordinate space (`x`/`y` are
3434
+ * attributes, not CSS), so emitting `.a-skel-block` divs at their tag would
3435
+ * yield non-rendering elements. When we see one of these, the parent `<svg>`
3436
+ * has already been treated as an atomic block — we just return null/skip.
3437
+ */
3438
+ const SVG_INTERIOR_TAGS = new Set([
3439
+ "circle",
3440
+ "rect",
3441
+ "path",
3442
+ "line",
3443
+ "polyline",
3444
+ "polygon",
3445
+ "ellipse",
3446
+ "g",
3447
+ "defs",
3448
+ "clippath",
3449
+ "mask",
3450
+ "pattern",
3451
+ "lineargradient",
3452
+ "radialgradient",
3453
+ "stop",
3454
+ "use",
3455
+ "symbol",
3456
+ "foreignobject",
3457
+ "text",
3458
+ "tspan",
3459
+ "textpath",
3460
+ "marker",
3461
+ "filter",
3462
+ "feblend",
3463
+ "fecolormatrix",
3464
+ "fegaussianblur",
3465
+ "feoffset",
3466
+ "fedropshadow",
3467
+ "femerge",
3468
+ "femergenode"
3469
+ ]);
3470
+ /**
3471
+ * Table-structural tags — preserve them as-is. Their semantics (`display:
3472
+ * table-*`) are critical for layout, and replacing them with `<div>` would
3473
+ * break the grid. Children are recursed normally.
3474
+ */
3475
+ const TABLE_TAGS = new Set([
3476
+ "table",
3477
+ "thead",
3478
+ "tbody",
3479
+ "tfoot",
3480
+ "tr",
3481
+ "td",
3482
+ "th",
3483
+ "caption",
3484
+ "colgroup",
3485
+ "col"
3486
+ ]);
3487
+ /**
3488
+ * List-structural tags — `<ul>`, `<ol>`, `<li>`, `<dl>`, `<dt>`, `<dd>`.
3489
+ * Preserved as containers; `<li>` recurses into its text/children so each
3490
+ * list item gets the right shimmer treatment.
3491
+ */
3492
+ const LIST_TAGS = new Set([
3493
+ "ul",
3494
+ "ol",
3495
+ "li",
3496
+ "dl",
3497
+ "dt",
3498
+ "dd"
3499
+ ]);
3500
+ /**
3501
+ * Tags whose content is conventionally text. When the author writes
3502
+ * `<h3>{{ data?.name }}</h3>` and `data` is null during loading, the
3503
+ * interpolation produces no children — the walker would otherwise emit an
3504
+ * empty `<h3></h3>` and no skeleton bar shows. For these tags we emit a
3505
+ * synthetic placeholder text-content span so the bar renders at the tag's
3506
+ * natural rendered width (Tailwind sizing on the tag still drives height).
3507
+ */
3508
+ const TEXT_OWNER_TAGS = new Set([
3509
+ "h1",
3510
+ "h2",
3511
+ "h3",
3512
+ "h4",
3513
+ "h5",
3514
+ "h6",
3515
+ "p",
3516
+ "span",
3517
+ "a",
3518
+ "em",
3519
+ "strong",
3520
+ "small",
3521
+ "code",
3522
+ "b",
3523
+ "i",
3524
+ "mark",
3525
+ "label",
3526
+ "caption",
3527
+ "time",
3528
+ "dt",
3529
+ "dd",
3530
+ "li",
3531
+ "th",
3532
+ "td",
3533
+ "figcaption",
3534
+ "blockquote",
3535
+ "cite",
3536
+ "q"
3537
+ ]);
3538
+ /**
3539
+ * Non-breaking-space placeholder for empty text-owners. ~24 chars wide is
3540
+ * enough to read as "a line of text" in most fonts while still being short
3541
+ * enough that the rendered bar doesn't wrap in narrow containers.
3542
+ */
3543
+ const TEXT_PLACEHOLDER = "\xA0".repeat(24);
3544
+ const DEFAULT_MAX_DEPTH$3 = 16;
3545
+ const DEFAULT_MAX_NODES$3 = 600;
3546
+ /**
3547
+ * Walk a slot's vnode tree and produce a **DOM-mirror skeleton**: every element
3548
+ * is preserved (same tag, same `class`, same inline `style`), and only the
3549
+ * content is replaced. The output is structurally identical to what the real
3550
+ * component would render — Tailwind utilities for layout, spacing, sizing,
3551
+ * backgrounds and shadows all carry through. The CSS does the work of making
3552
+ * it *look* like a skeleton.
3421
3553
  *
3422
- * Performance:
3423
- * - `maxNodes` caps the walk so a 5000-row table doesn't lock up the main thread.
3424
- * - `minSize` filters out hairlines (1-2 px paddings, decorative dots) that
3425
- * inflate node count without adding visual signal.
3426
- * - All `getBoundingClientRect` / `getComputedStyle` reads happen in a single
3427
- * top-down pass with no intervening writes, so the browser does one layout
3428
- * up front and serves cached values from then on (no layout thrashing).
3429
- * - Each emitted `ShapeNode` has a frozen pre-computed `style` (and `lineStyles`
3430
- * for multi-line text) so the render path is allocation-free.
3554
+ * Replacement rules:
3555
+ * - **Raw text** (e.g. interpolations, static strings) is wrapped in
3556
+ * `<span class="a-skel-text-content">…the real text…</span>`. The text is
3557
+ * kept as the span's children so the inline layout reserves its real
3558
+ * rendered width but the glyphs are painted transparent and a skeleton
3559
+ * gradient is painted in their place. Multi-line text wraps naturally,
3560
+ * producing one shimmer rect per visual line at the exact rendered position.
3561
+ * - **Atomic / interactive tags** (`img`, `svg`, `button`, `input`, …) are
3562
+ * replaced by a `<div class="a-skel-block">` carrying the original element's
3563
+ * `class` and `style`, so dimensions and shapes (size, border-radius, etc.)
3564
+ * still drive the layout.
3565
+ * - **Container elements** (`div`, `section`, `h1`, `p`, `a`, `span`, …) are
3566
+ * preserved as the same tag with the same `class` and `style`; we recurse
3567
+ * into their children.
3568
+ * - **Component vnodes** can't be introspected at render time, so we emit a
3569
+ * single `<div class="a-skel-block">` carrying their outer `class` / `style`.
3431
3570
  */
3432
- function walkDom(root, options) {
3433
- const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES$1;
3434
- const minSize = options.minSize ?? DEFAULT_MIN_SIZE;
3435
- const nodes = [];
3436
- const rootRect = root.getBoundingClientRect();
3437
- let truncated = false;
3438
- function visit(el, depth) {
3439
- if (nodes.length >= maxNodes) {
3440
- truncated = true;
3571
+ function buildStructuralSkeleton(vnodes, opts) {
3572
+ const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH$3;
3573
+ const state = {
3574
+ emitted: 0,
3575
+ cap: opts.maxNodes ?? DEFAULT_MAX_NODES$3
3576
+ };
3577
+ const out = [];
3578
+ walk(vnodes, opts, 0, maxDepth, state, out);
3579
+ return out;
3580
+ }
3581
+ function walk(input, opts, depth, max, state, out) {
3582
+ if (state.emitted >= state.cap) return;
3583
+ if (input == null || typeof input === "boolean") return;
3584
+ if (Array.isArray(input)) {
3585
+ for (let i = 0; i < input.length; i++) {
3586
+ if (state.emitted >= state.cap) return;
3587
+ walk(input[i], opts, depth, max, state, out);
3588
+ }
3589
+ return;
3590
+ }
3591
+ if (typeof input === "string" || typeof input === "number") {
3592
+ const str = String(input);
3593
+ if (str.trim()) push(out, textContentSpan(str, opts.animationClass), state);
3594
+ return;
3595
+ }
3596
+ const v = input;
3597
+ const type = v.type;
3598
+ if (type === Comment) return;
3599
+ if (type === Text) {
3600
+ const text = typeof v.children === "string" ? v.children : "";
3601
+ if (text.trim()) push(out, textContentSpan(text, opts.animationClass), state);
3602
+ return;
3603
+ }
3604
+ if (type === Fragment) {
3605
+ walk(v.children, opts, depth, max, state, out);
3606
+ return;
3607
+ }
3608
+ if (typeof type === "string") {
3609
+ const tag = type.toLowerCase();
3610
+ if (SVG_INTERIOR_TAGS.has(tag)) return;
3611
+ const props = v.props ?? {};
3612
+ if (props["data-skeleton-ignore"] !== void 0) {
3613
+ push(out, cloneVNode(v), state);
3441
3614
  return;
3442
3615
  }
3443
- const html = el;
3444
- if (html.dataset?.skeletonIgnore !== void 0) return;
3445
- const cs = window.getComputedStyle(el);
3446
- if (cs.display === "none" || cs.visibility === "hidden" || cs.opacity === "0") return;
3447
- const rect = el.getBoundingClientRect();
3448
- if (rect.width < minSize || rect.height < minSize) return;
3449
- const tag = el.tagName.toUpperCase();
3450
- const isLeafTag = LEAF_TAGS.has(tag);
3451
- const hasStop = html.dataset?.skeletonStop !== void 0;
3452
- const childElements = [];
3453
- for (let i = 0; i < el.children.length; i++) {
3454
- const c = el.children[i];
3455
- if (c.dataset?.skeletonIgnore === void 0) childElements.push(c);
3616
+ if (props["data-skeleton-stop"] !== void 0) {
3617
+ push(out, h("div", {
3618
+ class: [
3619
+ "a-skel-block",
3620
+ v.props?.class,
3621
+ opts.animationClass
3622
+ ],
3623
+ style: v.props?.style,
3624
+ "aria-hidden": "true"
3625
+ }), state);
3626
+ return;
3456
3627
  }
3457
- const hasOwnText = hasDirectTextContent(el);
3458
- const reachedDepth = depth >= options.maxDepth;
3459
- if (isLeafTag || hasStop || reachedDepth || childElements.length === 0) {
3460
- const node = elementToShape(tag, cs, rect, rootRect, hasOwnText);
3461
- if (node) nodes.push(node);
3628
+ if (props["data-skeleton-text"] !== void 0) {
3629
+ push(out, textContentSpan(" ", opts.animationClass), state);
3462
3630
  return;
3463
3631
  }
3464
- for (let i = 0; i < childElements.length; i++) {
3465
- if (nodes.length >= maxNodes) {
3466
- truncated = true;
3467
- return;
3468
- }
3469
- visit(childElements[i], depth + 1);
3632
+ if (props["data-skeleton-block"] !== void 0) {
3633
+ push(out, h("div", {
3634
+ class: [
3635
+ "a-skel-block",
3636
+ v.props?.class,
3637
+ opts.animationClass
3638
+ ],
3639
+ style: v.props?.style,
3640
+ "aria-hidden": "true"
3641
+ }), state);
3642
+ return;
3470
3643
  }
3644
+ push(out, transformElement(v, tag, opts, depth, max, state), state);
3645
+ return;
3471
3646
  }
3472
- for (let i = 0; i < root.children.length; i++) {
3473
- if (nodes.length >= maxNodes) {
3474
- truncated = true;
3475
- break;
3476
- }
3477
- visit(root.children[i], 1);
3647
+ if (typeof type === "object" || typeof type === "function") push(out, h("div", {
3648
+ class: [
3649
+ "a-skel-block",
3650
+ v.props?.class,
3651
+ opts.animationClass
3652
+ ],
3653
+ style: v.props?.style,
3654
+ "aria-hidden": "true"
3655
+ }), state);
3656
+ }
3657
+ function push(out, vn, state) {
3658
+ if (state.emitted >= state.cap) return;
3659
+ out.push(vn);
3660
+ state.emitted++;
3661
+ }
3662
+ /**
3663
+ * Surface-bearing tags — when these are encountered without their own explicit
3664
+ * background, the skeleton paints the default fill so they still read as a
3665
+ * solid element (a button without `bg-*` shouldn't look invisible).
3666
+ *
3667
+ * Pure layout containers (`div`, `section`, `main`, `article`, `header`,
3668
+ * `footer`, `nav`, `aside`) deliberately don't get the fallback — they're
3669
+ * transparent in the real DOM and should stay that way in the skeleton.
3670
+ */
3671
+ const SURFACE_TAGS = new Set([
3672
+ "button",
3673
+ "a",
3674
+ "label",
3675
+ "summary",
3676
+ "fieldset",
3677
+ "legend"
3678
+ ]);
3679
+ function transformElement(v, tag, opts, depth, max, state) {
3680
+ const cls = v.props?.class;
3681
+ const styl = v.props?.style;
3682
+ if (ATOMIC_TAGS$2.has(tag)) {
3683
+ const dimStyle = atomicDimensionStyle(v.props);
3684
+ return h("div", {
3685
+ class: [
3686
+ "a-skel-block",
3687
+ cls,
3688
+ opts.animationClass
3689
+ ],
3690
+ style: dimStyle ? mergeStyle(dimStyle, styl) : styl,
3691
+ "aria-hidden": "true"
3692
+ });
3478
3693
  }
3479
- return Object.freeze({
3480
- nodes: Object.freeze(nodes),
3481
- width: Math.round(rootRect.width),
3482
- height: Math.round(rootRect.height),
3483
- truncated
3694
+ if (TABLE_TAGS.has(tag)) {
3695
+ if (depth >= max) return h(tag, {
3696
+ class: cls,
3697
+ style: styl
3698
+ });
3699
+ const recursed = [];
3700
+ walk(v.children, opts, depth + 1, max, state, recursed);
3701
+ if (recursed.length === 0 && TEXT_OWNER_TAGS.has(tag)) return h(tag, {
3702
+ class: cls,
3703
+ style: styl
3704
+ }, [textContentSpan(TEXT_PLACEHOLDER, opts.animationClass)]);
3705
+ return h(tag, {
3706
+ class: cls,
3707
+ style: styl
3708
+ }, recursed.length > 0 ? recursed : void 0);
3709
+ }
3710
+ if (LIST_TAGS.has(tag)) {
3711
+ if (depth >= max) return h(tag, {
3712
+ class: cls,
3713
+ style: styl
3714
+ });
3715
+ const recursed = [];
3716
+ walk(v.children, opts, depth + 1, max, state, recursed);
3717
+ if (recursed.length === 0 && TEXT_OWNER_TAGS.has(tag)) return h(tag, {
3718
+ class: cls,
3719
+ style: styl
3720
+ }, [textContentSpan(TEXT_PLACEHOLDER, opts.animationClass)]);
3721
+ return h(tag, {
3722
+ class: cls,
3723
+ style: styl
3724
+ }, recursed.length > 0 ? recursed : void 0);
3725
+ }
3726
+ if (depth >= max) return h("div", {
3727
+ class: [
3728
+ "a-skel-block",
3729
+ cls,
3730
+ opts.animationClass
3731
+ ],
3732
+ style: styl,
3733
+ "aria-hidden": "true"
3734
+ });
3735
+ const recursed = [];
3736
+ walk(v.children, opts, depth + 1, max, state, recursed);
3737
+ if (recursed.length === 0 && TEXT_OWNER_TAGS.has(tag)) return h(tag, {
3738
+ class: cls,
3739
+ style: styl
3740
+ }, [textContentSpan(TEXT_PLACEHOLDER, opts.animationClass)]);
3741
+ if (recursed.length === 0) return h(tag, {
3742
+ class: cls,
3743
+ style: styl
3484
3744
  });
3745
+ if (SURFACE_TAGS.has(tag) && !hasExplicitBackground(cls, styl)) return h(tag, {
3746
+ class: [
3747
+ "a-skel-block",
3748
+ cls,
3749
+ opts.animationClass
3750
+ ],
3751
+ style: styl
3752
+ }, recursed);
3753
+ return cloneTag(tag, cls, styl, recursed);
3485
3754
  }
3486
- function hasDirectTextContent(el) {
3487
- for (let i = 0; i < el.childNodes.length; i++) {
3488
- const node = el.childNodes[i];
3489
- if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim().length > 0) return true;
3755
+ /**
3756
+ * Extract width/height from HTML attributes that affect layout (`<svg
3757
+ * width="408">`, `<img width="…" height="…">`) and project them into inline
3758
+ * style. Without this, replacing an attribute-sized atomic element with a
3759
+ * `<div>` would drop the size — divs don't honour those attributes.
3760
+ */
3761
+ function atomicDimensionStyle(props) {
3762
+ if (!props) return null;
3763
+ const out = {};
3764
+ const w = props.width;
3765
+ const h = props.height;
3766
+ if (w !== void 0 && w !== null && w !== "") out.width = typeof w === "number" ? `${w}px` : /^\d+$/.test(String(w)) ? `${w}px` : String(w);
3767
+ if (h !== void 0 && h !== null && h !== "") out.height = typeof h === "number" ? `${h}px` : /^\d+$/.test(String(h)) ? `${h}px` : String(h);
3768
+ return Object.keys(out).length > 0 ? out : null;
3769
+ }
3770
+ /**
3771
+ * Detect whether the element has an explicit background — either via a
3772
+ * Tailwind / DaisyUI / CSS-modules `bg-*` class or via an inline
3773
+ * `background` / `background-color` / `background-image` style. When false,
3774
+ * surface-bearing elements (button, a, label, …) fall back to the default
3775
+ * skeleton fill so they don't render as a transparent gap during loading.
3776
+ */
3777
+ function hasExplicitBackground(cls, styl) {
3778
+ if (hasBackgroundInStyle(styl)) return true;
3779
+ if (hasBackgroundInClass(cls)) return true;
3780
+ return false;
3781
+ }
3782
+ const BG_CLASS_RE = /(?:^|\s|:)bg-(?!skel)(?:\[|[a-z])/i;
3783
+ function hasBackgroundInClass(cls) {
3784
+ if (cls == null || cls === false) return false;
3785
+ if (typeof cls === "string") return BG_CLASS_RE.test(cls);
3786
+ if (Array.isArray(cls)) {
3787
+ for (const item of cls) if (hasBackgroundInClass(item)) return true;
3788
+ return false;
3789
+ }
3790
+ if (typeof cls === "object") {
3791
+ for (const k of Object.keys(cls)) if (cls[k] && BG_CLASS_RE.test(k)) return true;
3490
3792
  }
3491
3793
  return false;
3492
3794
  }
3493
- function elementToShape(tag, cs, rect, origin, hasText) {
3494
- const x = Math.round(rect.left - origin.left);
3495
- const y = Math.round(rect.top - origin.top);
3496
- const w = Math.round(rect.width);
3497
- const h = Math.round(rect.height);
3498
- const radius = parseFloat(cs.borderRadius) || 0;
3499
- const minDim = Math.min(w, h);
3500
- const isCircle = radius >= minDim / 2 - 1 && Math.abs(w - h) <= 2 && minDim > 0;
3501
- let type;
3502
- let resolvedRadius = radius;
3503
- let lines;
3504
- let lineHeight;
3505
- if (tag === "IMG" || tag === "SVG" || tag === "VIDEO" || tag === "CANVAS") type = "image";
3506
- else if (isCircle) {
3507
- type = "circle";
3508
- resolvedRadius = Math.floor(minDim / 2);
3509
- } else if (hasText) {
3510
- type = "text";
3511
- lineHeight = Math.round(parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.4 || 16);
3512
- lines = Math.max(1, Math.round(h / lineHeight));
3513
- resolvedRadius = Math.min(radius, 4);
3514
- } else type = "block";
3515
- return freezeShape({
3516
- type,
3517
- x,
3518
- y,
3519
- w,
3520
- h,
3521
- radius: resolvedRadius,
3522
- lines,
3523
- lineHeight
3524
- });
3795
+ function hasBackgroundInStyle(styl) {
3796
+ if (!styl) return false;
3797
+ if (typeof styl === "string") return /background(?:-color|-image)?\s*:/i.test(styl);
3798
+ if (typeof styl === "object") {
3799
+ const s = styl;
3800
+ return "background" in s || "backgroundColor" in s || "backgroundImage" in s || "background-color" in s || "background-image" in s;
3801
+ }
3802
+ return false;
3525
3803
  }
3526
- /**
3527
- * Pre-compute (and freeze) the inline styles used at render time. Doing it once
3528
- * here means rendering 500 blocks doesn't allocate 500 style objects per frame.
3804
+ function mergeStyle(a, b) {
3805
+ if (!b) return a;
3806
+ if (typeof b === "string") return `${Object.entries(a).map(([k, v]) => `${k}: ${v}`).join("; ")}; ${b}`;
3807
+ return {
3808
+ ...a,
3809
+ ...b
3810
+ };
3811
+ }
3812
+ function cloneTag(tag, cls, styl, children) {
3813
+ return h(tag, {
3814
+ class: cls,
3815
+ style: styl
3816
+ }, children);
3817
+ }
3818
+ function textContentSpan(text, animationClass) {
3819
+ return h("span", {
3820
+ class: ["a-skel-text-content", animationClass],
3821
+ "aria-hidden": "true"
3822
+ }, text);
3823
+ }
3824
+ //#endregion
3825
+ //#region src/components/StructuralSkeleton.ts
3826
+ /**
3827
+ * Renders a structural skeleton derived from a slot's vnode tree. Pure render
3828
+ * function — no template, no scoped styles — so the parent's class strings
3829
+ * pass through unchanged and Tailwind utilities continue to drive layout.
3830
+ *
3831
+ * `maxNodes` is forwarded to the walker; cap defaults to 300 (see
3832
+ * `buildStructuralSkeleton`). Beyond that the walk stops emitting and the cap
3833
+ * propagates back as a clipped tree, keeping first-paint bounded.
3529
3834
  */
3530
- function freezeShape(node) {
3531
- const style = Object.freeze({
3532
- left: `${node.x}px`,
3533
- top: `${node.y}px`,
3534
- width: `${node.w}px`,
3535
- height: `${node.h}px`,
3536
- borderRadius: `${node.radius}px`
3537
- });
3538
- let lineStyles;
3539
- if (node.type === "text" && node.lines && node.lines > 1) {
3540
- const lh = node.lineHeight ?? Math.round(node.h / node.lines);
3541
- const barHeight = Math.max(8, Math.round(lh * .7));
3542
- const widthFull = `${node.w}px`;
3543
- const widthLast = `${Math.max(40, Math.round(node.w * .7))}px`;
3544
- const heightStr = `${barHeight}px`;
3545
- const radiusStr = `${node.radius}px`;
3546
- const arr = [];
3547
- for (let i = 1; i <= node.lines; i++) {
3548
- const isLast = i === node.lines;
3549
- arr.push(Object.freeze({
3550
- left: `${node.x}px`,
3551
- top: `${node.y + (i - 1) * lh}px`,
3552
- width: isLast ? widthLast : widthFull,
3553
- height: heightStr,
3554
- borderRadius: radiusStr
3555
- }));
3835
+ const StructuralSkeleton = defineComponent({
3836
+ name: "StructuralSkeleton",
3837
+ props: {
3838
+ vnodes: {
3839
+ type: Array,
3840
+ required: true
3841
+ },
3842
+ animation: {
3843
+ type: String,
3844
+ default: null
3845
+ },
3846
+ maxDepth: {
3847
+ type: Number,
3848
+ default: 8
3849
+ },
3850
+ maxNodes: {
3851
+ type: Number,
3852
+ default: 300
3556
3853
  }
3557
- lineStyles = Object.freeze(arr);
3854
+ },
3855
+ setup(props) {
3856
+ return () => buildStructuralSkeleton(props.vnodes, {
3857
+ animationClass: props.animation,
3858
+ maxDepth: props.maxDepth,
3859
+ maxNodes: props.maxNodes
3860
+ });
3558
3861
  }
3559
- return Object.freeze({
3560
- type: node.type,
3862
+ });
3863
+ //#endregion
3864
+ //#region src/components/icons.ts
3865
+ /**
3866
+ * Shared placeholder icon renderers for leaf nodes (image / video). Lives at
3867
+ * one site so both `CloneNode.ts` (clone mode) and `StructuralLayerNode.ts`
3868
+ * (Recipe 3 structural mode) render identical glyphs.
3869
+ */
3870
+ function renderImageIcon() {
3871
+ return h("svg", {
3872
+ class: "a-skel-clone-icon",
3873
+ viewBox: "0 0 24 24",
3874
+ "aria-hidden": "true"
3875
+ }, [h("path", {
3876
+ d: "M19 5H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-3.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM19 17H5l3.5-4.5 2.5 3 3.5-4.5L19 17Z",
3877
+ fill: "currentColor"
3878
+ })]);
3879
+ }
3880
+ function renderPlayIcon() {
3881
+ return h("svg", {
3882
+ class: "a-skel-clone-icon",
3883
+ viewBox: "0 0 24 24",
3884
+ "aria-hidden": "true"
3885
+ }, [h("circle", {
3886
+ cx: 12,
3887
+ cy: 12,
3888
+ r: 10,
3889
+ stroke: "currentColor",
3890
+ "stroke-width": 1.5,
3891
+ fill: "none"
3892
+ }), h("path", {
3893
+ d: "M10 8l6 4-6 4V8Z",
3894
+ fill: "currentColor"
3895
+ })]);
3896
+ }
3897
+ //#endregion
3898
+ //#region src/components/CloneNode.ts
3899
+ /**
3900
+ * `CloneNode` — recursive renderer for one node of a `CaptureSnapshot`.
3901
+ *
3902
+ * Design:
3903
+ * - **Strategy table by `CapturedNode.kind`**. Each kind ('container' |
3904
+ * 'text' | 'image' | 'video' | 'media' | 'block') has its own renderer
3905
+ * function. Adding a new kind is one entry in `RENDERERS` — no edits
3906
+ * elsewhere (Open/Closed).
3907
+ * - **Pure render function**. No reactive state of its own; everything
3908
+ * flows from props.
3909
+ * - **Composition**: per-line text bars share the same shape as block
3910
+ * leaves so there's no behaviour duplication.
3911
+ */
3912
+ const RENDERERS$1 = {
3913
+ container: renderContainer$1,
3914
+ text: renderText,
3915
+ image: (ctx) => renderLeafWithIcon$1(ctx, "image"),
3916
+ video: (ctx) => renderLeafWithIcon$1(ctx, "video"),
3917
+ media: (ctx) => renderLeaf(ctx),
3918
+ block: (ctx) => renderLeaf(ctx)
3919
+ };
3920
+ /**
3921
+ * Container — positioned wrapper with captured background / border / shadow.
3922
+ * No `.a-skel` class so the surface shows through exactly as in the real DOM.
3923
+ * Recurse into children via the same `CloneNode`.
3924
+ *
3925
+ * `CapturedNode.{x,y}` is always root-relative, but children render inside
3926
+ * this container — itself `position: absolute`. If we hand children the
3927
+ * root-relative coords as-is, their `left` / `top` resolve against the
3928
+ * container (their new offset parent), doubling the offset. Pass a
3929
+ * `blockStyle` closure that subtracts this container's offset so each
3930
+ * descendant lands at its captured root-relative position regardless of
3931
+ * how deeply it's nested.
3932
+ */
3933
+ function renderContainer$1(ctx) {
3934
+ const { node } = ctx;
3935
+ const childBlockStyle = (n) => ({
3936
+ position: "absolute",
3937
+ left: `${n.x - node.x}px`,
3938
+ top: `${n.y - node.y}px`,
3939
+ width: `${n.w}px`,
3940
+ height: `${n.h}px`,
3941
+ ...n.style
3942
+ });
3943
+ return h("div", {
3944
+ class: "a-skel-clone-container",
3945
+ style: ctx.blockStyle(node),
3946
+ "aria-hidden": "true"
3947
+ }, (node.children ?? []).map((child) => h(CloneNode, {
3948
+ node: child,
3949
+ animClass: ctx.animClass,
3950
+ blockStyle: childBlockStyle,
3951
+ lineStyle: ctx.lineStyle
3952
+ })));
3953
+ }
3954
+ /**
3955
+ * Text — container carrying captured background/border, with per-line
3956
+ * shimmer bars positioned absolutely inside. Each text line becomes one bar
3957
+ * at the exact rendered text rect (multi-line / wrapped / RTL all replay 1:1).
3958
+ */
3959
+ function renderText(ctx) {
3960
+ const { node, animClass } = ctx;
3961
+ const lines = node.textLines ?? [{
3561
3962
  x: node.x,
3562
3963
  y: node.y,
3563
3964
  w: node.w,
3564
- h: node.h,
3565
- radius: node.radius,
3566
- lines: node.lines,
3567
- lineHeight: node.lineHeight,
3568
- style,
3569
- lineStyles
3965
+ h: node.h
3966
+ }];
3967
+ return h("div", {
3968
+ class: "a-skel-clone-container a-skel-clone-text",
3969
+ style: ctx.blockStyle(node),
3970
+ "aria-hidden": "true"
3971
+ }, lines.map((line) => h("div", {
3972
+ class: ["a-skel-clone-textbar", animClass],
3973
+ style: ctx.lineStyle({
3974
+ x: line.x - node.x,
3975
+ y: line.y - node.y,
3976
+ w: line.w,
3977
+ h: line.h
3978
+ }, node.style)
3979
+ })));
3980
+ }
3981
+ /** Plain shimmer leaf (block / media). */
3982
+ function renderLeaf(ctx) {
3983
+ return h("div", {
3984
+ class: ["a-skel a-skel-clone-leaf", ctx.animClass],
3985
+ style: ctx.blockStyle(ctx.node),
3986
+ "aria-hidden": "true"
3570
3987
  });
3571
3988
  }
3989
+ /** Shimmer leaf with a centered placeholder icon (image / video). */
3990
+ function renderLeafWithIcon$1(ctx, kind) {
3991
+ return h("div", {
3992
+ class: ["a-skel a-skel-clone-leaf a-skel-clone-leaf--with-icon", ctx.animClass],
3993
+ style: ctx.blockStyle(ctx.node),
3994
+ "aria-hidden": "true"
3995
+ }, [kind === "image" ? renderImageIcon() : renderPlayIcon()]);
3996
+ }
3997
+ const CloneNode = defineComponent({
3998
+ name: "CloneNode",
3999
+ props: {
4000
+ node: {
4001
+ type: Object,
4002
+ required: true
4003
+ },
4004
+ animClass: {
4005
+ type: [String, null],
4006
+ default: null
4007
+ },
4008
+ blockStyle: {
4009
+ type: Function,
4010
+ required: true
4011
+ },
4012
+ lineStyle: {
4013
+ type: Function,
4014
+ required: true
4015
+ }
4016
+ },
4017
+ setup(props) {
4018
+ return () => {
4019
+ const renderer = RENDERERS$1[props.node.kind];
4020
+ return renderer({
4021
+ node: props.node,
4022
+ animClass: props.animClass,
4023
+ blockStyle: props.blockStyle,
4024
+ lineStyle: props.lineStyle
4025
+ });
4026
+ };
4027
+ }
4028
+ });
3572
4029
  //#endregion
3573
- //#region src/composables/useShapeProbe.ts
3574
- const DEFAULT_RESIZE_DEBOUNCE_MS = 150;
3575
- /**
3576
- * Observe `getTarget()` and capture its rendered shape whenever the element
3577
- * appears or resizes.
3578
- *
3579
- * Performance:
3580
- * - Initial capture runs via `requestAnimationFrame` so it sneaks into the
3581
- * first idle window after mount no synchronous layout from inside the
3582
- * render queue.
3583
- * - Subsequent `ResizeObserver` callbacks are debounced (default 150 ms) so a
3584
- * drag-resize doesn't trigger a fresh DOM walk per frame.
3585
- * - `walkDom` itself enforces `maxNodes` so even a worst-case capture (10k
3586
- * descendants) returns in bounded time.
3587
- */
3588
- function useShapeProbe(getTarget, options) {
3589
- let observer;
3590
- let frame;
3591
- let timer;
3592
- let hasCaptured = false;
3593
- const debounceMs = options.resizeDebounceMs ?? DEFAULT_RESIZE_DEBOUNCE_MS;
3594
- function cleanup() {
3595
- if (observer) {
3596
- observer.disconnect();
3597
- observer = void 0;
4030
+ //#region \0/plugin-vue/export-helper
4031
+ var export_helper_default = (sfc, props) => {
4032
+ const target = sfc.__vccOpts || sfc;
4033
+ for (const [key, val] of props) target[key] = val;
4034
+ return target;
4035
+ };
4036
+ //#endregion
4037
+ //#region src/components/ASkeletonClone.vue
4038
+ const _sfc_main$18 = /* @__PURE__ */ defineComponent({
4039
+ __name: "ASkeletonClone",
4040
+ props: {
4041
+ shape: {
4042
+ type: Object,
4043
+ required: true
4044
+ },
4045
+ animation: {
4046
+ type: String,
4047
+ required: false,
4048
+ default: "pulse"
4049
+ },
4050
+ class: {
4051
+ type: [
4052
+ String,
4053
+ Array,
4054
+ Object
4055
+ ],
4056
+ required: false
3598
4057
  }
3599
- if (frame !== void 0) {
3600
- cancelAnimationFrame(frame);
3601
- frame = void 0;
4058
+ },
4059
+ setup(__props, { expose: __expose }) {
4060
+ __expose();
4061
+ const props = __props;
4062
+ const animClass = computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`);
4063
+ const layerStyle = computed(() => ({
4064
+ position: "relative",
4065
+ width: `${props.shape.width}px`,
4066
+ height: `${props.shape.height}px`
4067
+ }));
4068
+ function blockStyle(n) {
4069
+ return {
4070
+ position: "absolute",
4071
+ left: `${n.x}px`,
4072
+ top: `${n.y}px`,
4073
+ width: `${n.w}px`,
4074
+ height: `${n.h}px`,
4075
+ ...n.style
4076
+ };
3602
4077
  }
3603
- if (timer !== void 0) {
3604
- clearTimeout(timer);
3605
- timer = void 0;
4078
+ function lineStyle(line, parentStyle) {
4079
+ const radius = parentStyle.borderTopLeftRadius ?? "var(--ak-skel-radius-sm)";
4080
+ return {
4081
+ position: "absolute",
4082
+ left: `${line.x}px`,
4083
+ top: `${line.y}px`,
4084
+ width: `${line.w}px`,
4085
+ height: `${line.h}px`,
4086
+ backgroundColor: "var(--ak-skel-base)",
4087
+ borderRadius: radius
4088
+ };
3606
4089
  }
3607
- }
3608
- function capture(el) {
3609
- const result = walkDom(el, {
3610
- maxDepth: options.maxDepth,
3611
- maxNodes: options.maxNodes,
3612
- minSize: options.minSize
4090
+ const __returned__ = {
4091
+ props,
4092
+ animClass,
4093
+ layerStyle,
4094
+ blockStyle,
4095
+ lineStyle,
4096
+ get cn() {
4097
+ return cn;
4098
+ },
4099
+ get CloneNode() {
4100
+ return CloneNode;
4101
+ }
4102
+ };
4103
+ Object.defineProperty(__returned__, "__isScriptSetup", {
4104
+ enumerable: false,
4105
+ value: true
3613
4106
  });
3614
- if (result.width > 0 && result.height > 0 && result.nodes.length > 0) {
3615
- hasCaptured = true;
3616
- options.onCapture(result);
3617
- }
4107
+ return __returned__;
3618
4108
  }
3619
- function scheduleImmediate(el) {
3620
- if (frame !== void 0) cancelAnimationFrame(frame);
3621
- frame = requestAnimationFrame(() => {
3622
- frame = void 0;
3623
- capture(el);
3624
- });
4109
+ });
4110
+ function _sfc_render$18(_ctx, _cache, $props, $setup, $data, $options) {
4111
+ return openBlock(), createElementBlock("div", {
4112
+ class: normalizeClass($setup.cn("a-skeleton__clone", $setup.props.class)),
4113
+ style: normalizeStyle($setup.layerStyle),
4114
+ role: "status",
4115
+ "aria-busy": "true",
4116
+ "aria-live": "polite"
4117
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.shape.nodes, (node, idx) => {
4118
+ return openBlock(), createBlock($setup["CloneNode"], {
4119
+ key: idx,
4120
+ node,
4121
+ "anim-class": $setup.animClass,
4122
+ "block-style": $setup.blockStyle,
4123
+ "line-style": $setup.lineStyle
4124
+ }, null, 8, ["node", "anim-class"]);
4125
+ }), 128)), _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading…", -1))], 6);
4126
+ }
4127
+ var ASkeletonClone_default = /* @__PURE__ */ export_helper_default(_sfc_main$18, [
4128
+ ["render", _sfc_render$18],
4129
+ ["__scopeId", "data-v-1554ff36"],
4130
+ ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeletonClone.vue"]
4131
+ ]);
4132
+ //#endregion
4133
+ //#region src/utils/domRead.ts
4134
+ /**
4135
+ * Computed values that count as "default" and shouldn't be persisted. Stripped
4136
+ * before a captured `style` object lands on a node so a plain unstyled element
4137
+ * produces an empty style — keeps snapshots small and the rendered DOM clean.
4138
+ */
4139
+ const SKIP_VALUES = new Set([
4140
+ "none",
4141
+ "normal",
4142
+ "auto",
4143
+ "0px",
4144
+ "0",
4145
+ "0 0",
4146
+ "0% 0%",
4147
+ "0px 0px",
4148
+ "0deg",
4149
+ "0s",
4150
+ "visible",
4151
+ "static",
4152
+ "transparent",
4153
+ "rgba(0, 0, 0, 0)",
4154
+ "rgb(0, 0, 0, 0)",
4155
+ "initial",
4156
+ "inherit",
4157
+ "unset",
4158
+ "currentcolor"
4159
+ ]);
4160
+ /**
4161
+ * Read the subset of `props` from `cs` and return a frozen camelCased style
4162
+ * map with the SKIP_VALUES entries omitted. `opacity: 1` is treated as default
4163
+ * (matches CSS' initial value).
4164
+ */
4165
+ function readComputedStyles(cs, props) {
4166
+ const out = {};
4167
+ for (const prop of props) {
4168
+ const val = cs.getPropertyValue(prop).trim();
4169
+ if (!val) continue;
4170
+ if (SKIP_VALUES.has(val.toLowerCase())) continue;
4171
+ if (prop === "opacity" && (val === "1" || parseFloat(val) === 1)) continue;
4172
+ out[camelCase(prop)] = val;
3625
4173
  }
3626
- function scheduleDebounced(el) {
3627
- if (timer !== void 0) clearTimeout(timer);
3628
- timer = setTimeout(() => {
3629
- timer = void 0;
3630
- capture(el);
3631
- }, debounceMs);
4174
+ return Object.freeze(out);
4175
+ }
4176
+ function camelCase(prop) {
4177
+ return prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
4178
+ }
4179
+ /** True when the element has at least one non-whitespace direct text-node child. */
4180
+ function hasDirectText(el) {
4181
+ for (let i = 0; i < el.childNodes.length; i++) {
4182
+ const node = el.childNodes[i];
4183
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim().length > 0) return true;
3632
4184
  }
3633
- watch(getTarget, (el) => {
3634
- cleanup();
3635
- hasCaptured = false;
3636
- if (!el || typeof window === "undefined") return;
3637
- scheduleImmediate(el);
3638
- if (typeof ResizeObserver !== "undefined") {
3639
- observer = new ResizeObserver(() => {
3640
- if (hasCaptured) scheduleDebounced(el);
3641
- });
3642
- observer.observe(el);
3643
- }
3644
- }, {
3645
- immediate: true,
3646
- flush: "post"
3647
- });
3648
- onBeforeUnmount(cleanup);
4185
+ return false;
4186
+ }
4187
+ /** Element children, with `data-skeleton-ignore` descendants filtered out. */
4188
+ function collectVisibleChildren(el) {
4189
+ const out = [];
4190
+ for (let i = 0; i < el.children.length; i++) {
4191
+ const c = el.children[i];
4192
+ if (c.dataset?.skeletonIgnore !== void 0) continue;
4193
+ out.push(c);
4194
+ }
4195
+ return out;
3649
4196
  }
3650
- //#endregion
3651
- //#region src/composables/useSkeletonCache.ts
3652
- const memory = /* @__PURE__ */ new Map();
3653
- const STORAGE_PREFIX = "a-skeleton:";
3654
4197
  /**
3655
- * Lookup a captured shape by key. Reads in-memory first, then `localStorage` if
3656
- * `persist` is enabled. SSR-safe bypasses `window` access on the server.
3657
- * Rehydrates pre-computed styles for shapes deserialized from older sessions
3658
- * (Object.freeze + style/lineStyles don't survive `JSON.stringify` round-trip).
4198
+ * Per-line text rects via `Range.getClientRects()`. Returns one rect per visual
4199
+ * line exact left/width for each line so wrapped paragraphs, RTL last-line
4200
+ * positions, centered headings replay 1:1 without heuristics. Returns
4201
+ * `undefined` if the element has no direct text or the Range API isn't usable.
4202
+ *
4203
+ * Two rects on the same baseline AND horizontally touching (gap ≤ 2 px) are
4204
+ * merged so inline spans on the same line collapse to one bar. Same-baseline
4205
+ * rects on different visual lines (rare; float-into-paragraph layouts) won't
4206
+ * touch horizontally and stay separate.
3659
4207
  */
3660
- function getCached(key, persist) {
3661
- const hit = memory.get(key);
3662
- if (hit) return hit;
3663
- if (!persist || typeof window === "undefined") return void 0;
4208
+ function captureTextLines(el, origin) {
4209
+ if (typeof document === "undefined" || typeof document.createRange !== "function") return void 0;
4210
+ let range;
3664
4211
  try {
3665
- const raw = window.localStorage.getItem(STORAGE_PREFIX + key);
3666
- if (!raw) return void 0;
3667
- const rehydrated = rehydrateShape(JSON.parse(raw));
3668
- memory.set(key, rehydrated);
3669
- return rehydrated;
4212
+ range = document.createRange();
4213
+ range.selectNodeContents(el);
3670
4214
  } catch {
3671
4215
  return;
3672
4216
  }
3673
- }
3674
- /** Store a captured shape. `persist=true` mirrors to `localStorage`. */
3675
- function setCached(key, value, persist) {
3676
- memory.set(key, value);
3677
- if (!persist || typeof window === "undefined") return;
3678
- try {
3679
- const lean = {
3680
- width: value.width,
3681
- height: value.height,
3682
- nodes: leanNodes(value.nodes)
3683
- };
3684
- window.localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(lean));
3685
- } catch {}
3686
- }
3687
- /** Drop a single entry (or all entries when no key). Exposed for tests + manual invalidation. */
3688
- function clearCached(key) {
3689
- if (!key) {
3690
- memory.clear();
3691
- if (typeof window === "undefined") return;
3692
- try {
3693
- for (const k of Object.keys(window.localStorage)) if (k.startsWith(STORAGE_PREFIX)) window.localStorage.removeItem(k);
3694
- } catch {}
3695
- return;
3696
- }
3697
- memory.delete(key);
3698
- if (typeof window === "undefined") return;
3699
- try {
3700
- window.localStorage.removeItem(STORAGE_PREFIX + key);
3701
- } catch {}
3702
- }
3703
- /**
3704
- * Rebuild `style` + `lineStyles` for nodes that lost them via serialization.
3705
- * Walks the array in-place where possible and freezes the result so the render
3706
- * path stays allocation-free.
3707
- */
3708
- function rehydrateShape(shape) {
3709
- const nodes = shape.nodes.map((n) => n.style ? n : freezeNodeStyles(n));
3710
- return Object.freeze({
3711
- nodes: Object.freeze(nodes),
3712
- width: shape.width,
3713
- height: shape.height,
3714
- truncated: shape.truncated
3715
- });
3716
- }
3717
- function leanNodes(nodes) {
3718
- return nodes.map((n) => ({
3719
- type: n.type,
3720
- x: n.x,
3721
- y: n.y,
3722
- w: n.w,
3723
- h: n.h,
3724
- radius: n.radius,
3725
- lines: n.lines,
3726
- lineHeight: n.lineHeight
3727
- }));
3728
- }
3729
- function freezeNodeStyles(node) {
3730
- const style = Object.freeze({
3731
- left: `${node.x}px`,
3732
- top: `${node.y}px`,
3733
- width: `${node.w}px`,
3734
- height: `${node.h}px`,
3735
- borderRadius: `${node.radius}px`
3736
- });
3737
- let lineStyles;
3738
- if (node.type === "text" && node.lines && node.lines > 1) {
3739
- const lh = node.lineHeight ?? Math.round(node.h / node.lines);
3740
- const barHeight = Math.max(8, Math.round(lh * .7));
3741
- const widthFull = `${node.w}px`;
3742
- const widthLast = `${Math.max(40, Math.round(node.w * .7))}px`;
3743
- const heightStr = `${barHeight}px`;
3744
- const radiusStr = `${node.radius}px`;
3745
- const arr = [];
3746
- for (let i = 1; i <= node.lines; i++) {
3747
- const isLast = i === node.lines;
3748
- arr.push(Object.freeze({
3749
- left: `${node.x}px`,
3750
- top: `${node.y + (i - 1) * lh}px`,
3751
- width: isLast ? widthLast : widthFull,
3752
- height: heightStr,
3753
- borderRadius: radiusStr
3754
- }));
3755
- }
3756
- lineStyles = Object.freeze(arr);
3757
- }
3758
- return Object.freeze({
3759
- type: node.type,
3760
- x: node.x,
3761
- y: node.y,
3762
- w: node.w,
3763
- h: node.h,
3764
- radius: node.radius,
3765
- lines: node.lines,
3766
- lineHeight: node.lineHeight,
3767
- style,
3768
- lineStyles
3769
- });
3770
- }
3771
- //#endregion
3772
- //#region src/utils/fingerprint.ts
3773
- /**
3774
- * Derive a default cache key from a slot's vnode tree. Returns the first
3775
- * non-comment child's component name (or HTML tag). When the slot only contains
3776
- * text / comments / unknown content, returns `'anonymous'` so the caller can
3777
- * still cache, but with no useful identity — encourage an explicit `cacheKey`.
3778
- */
3779
- function fingerprintSlot(vnodes) {
3780
- if (!vnodes) return "anonymous";
3781
- for (const vnode of vnodes) {
3782
- const tag = describeVNode(vnode);
3783
- if (tag) return tag;
3784
- }
3785
- return "anonymous";
3786
- }
3787
- function describeVNode(vnode) {
3788
- const t = vnode.type;
3789
- if (t === Comment || t === Text) return void 0;
3790
- if (t === Fragment) {
3791
- const children = vnode.children;
3792
- if (Array.isArray(children)) {
3793
- for (const child of children) if (child && typeof child === "object" && "type" in child) {
3794
- const found = describeVNode(child);
3795
- if (found) return found;
3796
- }
3797
- }
3798
- return;
3799
- }
3800
- if (typeof t === "string") return t;
3801
- if (typeof t === "object" && t !== null) {
3802
- const named = t.name ?? t.__name ?? t.displayName;
3803
- if (named) return named;
3804
- }
4217
+ const rects = range.getClientRects();
4218
+ if (!rects || rects.length === 0) return void 0;
4219
+ const merged = [];
4220
+ for (let i = 0; i < rects.length; i++) {
4221
+ const r = rects[i];
4222
+ if (r.width <= 0 || r.height <= 0) continue;
4223
+ const lr = {
4224
+ x: Math.round(r.left - origin.left),
4225
+ y: Math.round(r.top - origin.top),
4226
+ w: Math.round(r.width),
4227
+ h: Math.round(r.height)
4228
+ };
4229
+ const last = merged[merged.length - 1];
4230
+ if (last && Math.abs(last.y - lr.y) <= 1 && Math.abs(last.h - lr.h) <= 1 && Math.max(last.x, lr.x) - Math.min(last.x + last.w, lr.x + lr.w) <= 2) {
4231
+ const leftEdge = Math.min(last.x, lr.x);
4232
+ const rightEdge = Math.max(last.x + last.w, lr.x + lr.w);
4233
+ last.x = leftEdge;
4234
+ last.w = rightEdge - leftEdge;
4235
+ } else merged.push(lr);
4236
+ }
4237
+ return merged.length > 0 ? merged : void 0;
3805
4238
  }
3806
4239
  //#endregion
3807
- //#region src/utils/buildStructuralSkeleton.ts
4240
+ //#region src/utils/captureStyles.ts
3808
4241
  /**
3809
- * Atomic HTML tags — rendered as a single skeleton block. Their own class/style
3810
- * is preserved so Tailwind utilities (`size-16`, `rounded-full`, …) carry the
3811
- * dimensions across without us needing to measure.
4242
+ * Comprehensive style-capture engine.
4243
+ *
4244
+ * Walks the slot's rendered DOM after mount and captures **every visible CSS
4245
+ * property** of every element it encounters, plus geometry, into a frozen
4246
+ * snapshot tree. The snapshot is replayed by `<ASkeletonClone>` as a tree
4247
+ * of positioned divs each carrying its captured inline style.
4248
+ *
4249
+ * Why this exists (vs the DOM-mirror walker in `buildStructuralSkeleton.ts`):
4250
+ *
4251
+ * - DOM-mirror preserves the slot's vnode tree and inherits styling from the
4252
+ * consumer's `class` / inline `style` attributes. That works for *static*
4253
+ * styles, but it can't see styles applied via:
4254
+ * - JavaScript-set inline style (refs, watchers, animation libs)
4255
+ * - CSS-in-JS runtimes that hash class names per instance
4256
+ * - DaisyUI / shadcn / custom CSS where the computed result differs
4257
+ * from what the class string implies
4258
+ * - Scoped styles compiled with content-hash data attributes
4259
+ *
4260
+ * - `captureSnapshot` reads `getComputedStyle()` for each element — the
4261
+ * **final** computed style after the cascade has resolved. Replaying that
4262
+ * produces a pixel-identical surface no matter what styling system the
4263
+ * consumer uses.
4264
+ *
4265
+ * Cost is bounded by the same `maxNodes` / `maxDepth` / `minSize` filters
4266
+ * as the legacy `walkDom` capture. The whole pass is a single top-down read
4267
+ * with no DOM writes between, so the browser does one layout up front.
3812
4268
  */
3813
- const ATOMIC_TAGS = new Set([
4269
+ const DEFAULT_MAX_DEPTH$2 = 12;
4270
+ const DEFAULT_MAX_NODES$2 = 800;
4271
+ const DEFAULT_MIN_SIZE$2 = 4;
4272
+ /** Tags treated as atomic leaves — no recursion into their contents. */
4273
+ const ATOMIC_TAGS$1 = new Set([
3814
4274
  "img",
3815
4275
  "svg",
3816
4276
  "canvas",
3817
4277
  "video",
4278
+ "audio",
3818
4279
  "input",
3819
4280
  "textarea",
3820
4281
  "select",
3821
- "button",
3822
4282
  "progress",
3823
4283
  "meter",
3824
- "hr"
4284
+ "hr",
4285
+ "iframe",
4286
+ "object",
4287
+ "embed",
4288
+ "picture",
4289
+ "br"
3825
4290
  ]);
3826
- /** Single-line text containers produce one bar. */
3827
- const HEADING_TAGS = new Set([
4291
+ /** Tags whose content is text — captured as text bars (per-line via Range). */
4292
+ const TEXT_OWNERS$1 = new Set([
4293
+ "p",
3828
4294
  "h1",
3829
4295
  "h2",
3830
4296
  "h3",
3831
4297
  "h4",
3832
4298
  "h5",
3833
- "h6"
3834
- ]);
3835
- /** Multi-line text containers — produce N bars with a shortened last line. */
3836
- const PARAGRAPH_TAGS = new Set(["p", "blockquote"]);
3837
- /** Inline text — single bar, but inherits parent font sizing. */
3838
- const INLINE_TEXT_TAGS = new Set([
4299
+ "h6",
3839
4300
  "span",
3840
4301
  "a",
3841
- "small",
3842
- "strong",
3843
4302
  "em",
4303
+ "strong",
4304
+ "small",
3844
4305
  "code",
3845
- "time",
3846
- "label",
3847
4306
  "b",
3848
4307
  "i",
3849
- "mark"
4308
+ "mark",
4309
+ "label",
4310
+ "caption",
4311
+ "time",
4312
+ "dt",
4313
+ "dd",
4314
+ "li",
4315
+ "th",
4316
+ "td",
4317
+ "figcaption",
4318
+ "blockquote",
4319
+ "cite",
4320
+ "q"
3850
4321
  ]);
3851
- const DEFAULT_MAX_DEPTH = 8;
3852
- const DEFAULT_MAX_NODES = 300;
3853
4322
  /**
3854
- * Walk a slot's vnode tree and produce a skeleton that mirrors its rendered
3855
- * structure: same wrapping tags, same `class` strings (so flex/grid/spacing/
3856
- * sizing utilities still apply), but text/atomic leaves replaced with shimmer
3857
- * blocks. The result renders correctly on the FIRST paint without any DOM
3858
- * measurement, as long as the slot's template renders structure even when its
3859
- * data is empty (use `v-if`/`v-else` to swap content, not to gate the wrapper).
4323
+ * Visual CSS properties read from `getComputedStyle()`. Property listed here
4324
+ * is included in the captured snapshot **only when its value differs from the
4325
+ * skip set** so a plain unstyled `<div>` produces an empty style object.
3860
4326
  *
3861
- * Performance: `maxNodes` caps the work. When the cap is hit we stop emitting
3862
- * the caller still gets a valid skeleton, just clipped at the budget. A 1000-
3863
- * row list renders ~300 skeleton rows on first paint and then the measured cache
3864
- * takes over for subsequent loads.
4327
+ * Listed in the order they're written into the snapshot's `style` object so
4328
+ * the replay is deterministic across captures.
3865
4329
  */
3866
- function buildStructuralSkeleton(vnodes, opts) {
3867
- const maxDepth = opts.maxDepth ?? DEFAULT_MAX_DEPTH;
4330
+ const VISUAL_PROPS$1 = [
4331
+ "padding-top",
4332
+ "padding-right",
4333
+ "padding-bottom",
4334
+ "padding-left",
4335
+ "border-top-width",
4336
+ "border-right-width",
4337
+ "border-bottom-width",
4338
+ "border-left-width",
4339
+ "border-top-style",
4340
+ "border-right-style",
4341
+ "border-bottom-style",
4342
+ "border-left-style",
4343
+ "border-top-color",
4344
+ "border-right-color",
4345
+ "border-bottom-color",
4346
+ "border-left-color",
4347
+ "border-top-left-radius",
4348
+ "border-top-right-radius",
4349
+ "border-bottom-right-radius",
4350
+ "border-bottom-left-radius",
4351
+ "background-color",
4352
+ "background-image",
4353
+ "background-position",
4354
+ "background-size",
4355
+ "background-repeat",
4356
+ "background-origin",
4357
+ "background-clip",
4358
+ "box-shadow",
4359
+ "opacity",
4360
+ "filter",
4361
+ "backdrop-filter",
4362
+ "transform",
4363
+ "transform-origin",
4364
+ "mix-blend-mode",
4365
+ "font-family",
4366
+ "font-size",
4367
+ "font-weight",
4368
+ "font-style",
4369
+ "line-height",
4370
+ "letter-spacing",
4371
+ "text-align",
4372
+ "text-transform",
4373
+ "text-decoration-line",
4374
+ "text-decoration-color"
4375
+ ];
4376
+ /**
4377
+ * Snapshot the rendered DOM under `root`, returning a frozen tree of every
4378
+ * visible element + its computed visual styles. Replaying the snapshot
4379
+ * produces a surface visually identical to `root`.
4380
+ */
4381
+ function captureSnapshot(root, options = {}) {
4382
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH$2;
4383
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES$2;
4384
+ const minSize = options.minSize ?? DEFAULT_MIN_SIZE$2;
4385
+ const walkAtomic = options.walkAtomic ?? false;
4386
+ const rootRect = root.getBoundingClientRect();
3868
4387
  const state = {
3869
- emitted: 0,
3870
- cap: opts.maxNodes ?? DEFAULT_MAX_NODES
4388
+ count: 0,
4389
+ truncated: false
3871
4390
  };
3872
- const out = [];
3873
- walk(vnodes, opts, 0, maxDepth, state, out);
3874
- return out;
3875
- }
3876
- function walk(input, opts, depth, max, state, out) {
3877
- if (state.emitted >= state.cap) return;
3878
- if (input == null || typeof input === "boolean") return;
3879
- if (Array.isArray(input)) {
3880
- for (let i = 0; i < input.length; i++) {
3881
- if (state.emitted >= state.cap) return;
3882
- walk(input[i], opts, depth, max, state, out);
4391
+ const nodes = [];
4392
+ for (let i = 0; i < root.children.length; i++) {
4393
+ if (state.count >= maxNodes) {
4394
+ state.truncated = true;
4395
+ break;
3883
4396
  }
3884
- return;
3885
- }
3886
- if (typeof input === "string" || typeof input === "number") {
3887
- if (String(input).trim()) push(out, textBar(opts.animationClass), state);
3888
- return;
3889
- }
3890
- const v = input;
3891
- const type = v.type;
3892
- if (type === Comment) return;
3893
- if (type === Text) {
3894
- if (typeof v.children === "string" ? v.children.trim() : "") push(out, textBar(opts.animationClass), state);
3895
- return;
3896
- }
3897
- if (type === Fragment) {
3898
- walk(v.children, opts, depth, max, state, out);
3899
- return;
3900
- }
3901
- if (typeof type === "string") {
3902
- push(out, transformElement(v, type.toLowerCase(), opts, depth, max, state), state);
3903
- return;
4397
+ const node = capture$1(root.children[i], rootRect, 1, {
4398
+ maxDepth,
4399
+ maxNodes,
4400
+ minSize,
4401
+ walkAtomic,
4402
+ state
4403
+ });
4404
+ if (node) nodes.push(node);
3904
4405
  }
3905
- if (typeof type === "object" || typeof type === "function") push(out, h("div", {
3906
- class: [
3907
- "a-skel-block",
3908
- v.props?.class,
3909
- opts.animationClass
3910
- ],
3911
- style: v.props?.style
3912
- }), state);
3913
- }
3914
- function push(out, vn, state) {
3915
- if (state.emitted >= state.cap) return;
3916
- out.push(vn);
3917
- state.emitted++;
3918
- }
3919
- function transformElement(v, tag, opts, depth, max, state) {
3920
- const cls = v.props?.class;
3921
- const styl = v.props?.style;
3922
- if (ATOMIC_TAGS.has(tag)) return h("div", {
3923
- class: [
3924
- "a-skel-block",
3925
- cls,
3926
- opts.animationClass
3927
- ],
3928
- style: styl
4406
+ return Object.freeze({
4407
+ width: Math.round(rootRect.width),
4408
+ height: Math.round(rootRect.height),
4409
+ nodes: Object.freeze(nodes),
4410
+ truncated: state.truncated,
4411
+ capturedAt: Date.now()
3929
4412
  });
3930
- if (HEADING_TAGS.has(tag)) return h(tag, {
3931
- class: cls,
3932
- style: styl
3933
- }, [textBar(opts.animationClass)]);
3934
- if (PARAGRAPH_TAGS.has(tag)) {
3935
- const children = v.children;
3936
- const recursedChildren = [];
3937
- walk(children, opts, depth + 1, max, state, recursedChildren);
3938
- if (recursedChildren.length > 0) return h(tag, {
3939
- class: cls,
3940
- style: styl
3941
- }, recursedChildren);
3942
- const lines = estimateLines(children, 3);
3943
- return h(tag, {
3944
- class: cls,
3945
- style: styl
3946
- }, multiLineBars(lines, opts.animationClass));
4413
+ }
4414
+ function capture$1(el, origin, depth, ctx) {
4415
+ if (ctx.state.count >= ctx.maxNodes) {
4416
+ ctx.state.truncated = true;
4417
+ return null;
3947
4418
  }
3948
- if (INLINE_TEXT_TAGS.has(tag)) {
3949
- const children = v.children;
3950
- const recursedChildren = [];
3951
- walk(children, opts, depth + 1, max, state, recursedChildren);
3952
- if (recursedChildren.length > 0) return h(tag, {
3953
- class: cls,
3954
- style: styl
3955
- }, recursedChildren);
3956
- return h(tag, {
3957
- class: cls,
3958
- style: styl
3959
- }, [textBar(opts.animationClass)]);
4419
+ if (el.dataset?.skeletonIgnore !== void 0) return null;
4420
+ const cs = window.getComputedStyle(el);
4421
+ if (cs.display === "none" || cs.visibility === "hidden") return null;
4422
+ const o = parseFloat(cs.opacity);
4423
+ if (Number.isFinite(o) && o === 0) return null;
4424
+ const rect = el.getBoundingClientRect();
4425
+ const tag = el.tagName.toLowerCase();
4426
+ const isTextOwnerTag = TEXT_OWNERS$1.has(tag);
4427
+ let effectiveHeight = rect.height;
4428
+ if (isTextOwnerTag && effectiveHeight < ctx.minSize) {
4429
+ const lh = parseFloat(cs.lineHeight);
4430
+ const fs = parseFloat(cs.fontSize);
4431
+ if (Number.isFinite(lh) && lh > 0) effectiveHeight = lh;
4432
+ else if (Number.isFinite(fs) && fs > 0) effectiveHeight = fs * 1.4;
3960
4433
  }
3961
- if (depth >= max) return h("div", {
3962
- class: [
3963
- "a-skel-block",
3964
- cls,
3965
- opts.animationClass
3966
- ],
3967
- style: styl
3968
- });
3969
- const recursed = [];
3970
- walk(v.children, opts, depth + 1, max, state, recursed);
3971
- if (recursed.length === 0) return h("div", {
3972
- class: [
3973
- "a-skel-block",
3974
- cls,
3975
- opts.animationClass
3976
- ],
3977
- style: styl
3978
- });
3979
- return h(tag, {
3980
- class: cls,
3981
- style: styl
3982
- }, recursed);
3983
- }
3984
- function estimateLines(children, max) {
3985
- if (typeof children !== "string") return 1;
3986
- const len = children.trim().length;
3987
- if (len === 0) return 2;
3988
- if (len < 40) return 1;
3989
- if (len < 100) return 2;
3990
- return Math.min(max, 3);
3991
- }
3992
- function multiLineBars(lines, animationClass) {
3993
- const out = [];
3994
- for (let i = 0; i < lines; i++) out.push(textBar(animationClass, i === lines - 1 && lines > 1 ? .65 : 1));
3995
- return out;
3996
- }
3997
- const BAR_STYLE_FULL = Object.freeze({
3998
- display: "inline-block",
3999
- width: "100%",
4000
- height: "0.75em",
4001
- verticalAlign: "middle",
4002
- borderRadius: "4px"
4003
- });
4004
- const PARTIAL_BAR_CACHE = /* @__PURE__ */ new Map();
4005
- function partialBarStyle(widthFraction) {
4006
- const key = Math.round(widthFraction * 10) / 10;
4007
- const hit = PARTIAL_BAR_CACHE.get(key);
4008
- if (hit) return hit;
4009
- const made = Object.freeze({
4010
- display: "inline-block",
4011
- width: `${Math.round(key * 100)}%`,
4012
- height: "0.75em",
4013
- verticalAlign: "middle",
4014
- borderRadius: "4px"
4015
- });
4016
- PARTIAL_BAR_CACHE.set(key, made);
4017
- return made;
4018
- }
4019
- function textBar(animationClass, widthFraction = 1) {
4020
- return h("span", {
4021
- class: [
4022
- "a-skel-block",
4023
- "a-skel-block--text",
4024
- animationClass
4025
- ],
4026
- style: widthFraction === 1 ? BAR_STYLE_FULL : partialBarStyle(widthFraction)
4434
+ if (rect.width < ctx.minSize || effectiveHeight < ctx.minSize) return null;
4435
+ const x = Math.round(rect.left - origin.left);
4436
+ const y = Math.round(rect.top - origin.top);
4437
+ const w = Math.round(rect.width);
4438
+ const h = Math.round(effectiveHeight);
4439
+ const style = readComputedStyles(cs, VISUAL_PROPS$1);
4440
+ const isAtomic = ATOMIC_TAGS$1.has(tag);
4441
+ const hasOwnText = hasDirectText(el);
4442
+ const childrenEls = collectVisibleChildren(el);
4443
+ let kind;
4444
+ if (tag === "img" || tag === "picture") kind = "image";
4445
+ else if (tag === "video") kind = "video";
4446
+ else if (isAtomic) kind = "media";
4447
+ else if (childrenEls.length === 0 && (hasOwnText || isTextOwnerTag)) kind = "text";
4448
+ else if (childrenEls.length === 0) kind = "block";
4449
+ else kind = "container";
4450
+ let textLines;
4451
+ if (kind === "text") textLines = captureTextLines(el, origin);
4452
+ let children;
4453
+ if (kind === "container" && depth < ctx.maxDepth) {
4454
+ children = [];
4455
+ for (const c of childrenEls) {
4456
+ if (ctx.state.count >= ctx.maxNodes) {
4457
+ ctx.state.truncated = true;
4458
+ break;
4459
+ }
4460
+ const childNode = capture$1(c, origin, depth + 1, ctx);
4461
+ if (childNode) children.push(childNode);
4462
+ }
4463
+ if (children.length === 0) {
4464
+ children = void 0;
4465
+ kind = style && Object.keys(style).length > 0 ? "block" : "container";
4466
+ }
4467
+ } else if (kind === "container" && depth >= ctx.maxDepth || isAtomic && ctx.walkAtomic) kind = "block";
4468
+ ctx.state.count++;
4469
+ return Object.freeze({
4470
+ tag,
4471
+ x,
4472
+ y,
4473
+ w,
4474
+ h,
4475
+ style,
4476
+ kind,
4477
+ textLines: textLines ? Object.freeze(textLines) : void 0,
4478
+ children: children ? Object.freeze(children) : void 0
4027
4479
  });
4028
4480
  }
4029
4481
  //#endregion
4030
- //#region src/components/StructuralSkeleton.ts
4482
+ //#region src/components/ASkeleton.vue
4031
4483
  /**
4032
- * Renders a structural skeleton derived from a slot's vnode tree. Pure render
4033
- * function — no template, no scoped styles — so the parent's class strings
4034
- * pass through unchanged and Tailwind utilities continue to drive layout.
4484
+ * `<ASkeleton>` the package's headline wrapper.
4035
4485
  *
4036
- * `maxNodes` is forwarded to the walker; cap defaults to 300 (see
4037
- * `buildStructuralSkeleton`). Beyond that the walk stops emitting and the cap
4038
- * propagates back as a clipped tree, keeping first-paint bounded.
4486
+ * Two rendering strategies, selected via `mode`:
4487
+ * - `mirror` (default): walks the slot's vnode tree (`buildStructuralSkeleton`)
4488
+ * and preserves every element with its real `class` / inline `style`. Pure
4489
+ * Vue, SSR-safe, no DOM read.
4490
+ * - `clone`: mounts the slot off-screen once, snapshots every leaf's
4491
+ * `getComputedStyle()` (`captureSnapshot`), then replays the snapshot via
4492
+ * `<ASkeletonClone>` as positioned divs carrying every captured CSS
4493
+ * property. Pixel-identical regardless of styling system.
4494
+ *
4495
+ * The strategies are exclusive — picking one decides the entire loading-state
4496
+ * render path. The wrapper itself only orchestrates: it doesn't decide
4497
+ * per-element treatment (that's the strategies' job). Single Responsibility:
4498
+ * orchestration + a11y + containment.
4039
4499
  */
4040
- const StructuralSkeleton = defineComponent({
4041
- name: "StructuralSkeleton",
4042
- props: {
4043
- vnodes: {
4044
- type: Array,
4045
- required: true
4046
- },
4047
- animation: {
4048
- type: String,
4049
- default: null
4050
- },
4051
- maxDepth: {
4052
- type: Number,
4053
- default: 8
4054
- },
4055
- maxNodes: {
4056
- type: Number,
4057
- default: 300
4058
- }
4059
- },
4060
- setup(props) {
4061
- return () => buildStructuralSkeleton(props.vnodes, {
4062
- animationClass: props.animation,
4063
- maxDepth: props.maxDepth,
4064
- maxNodes: props.maxNodes
4065
- });
4066
- }
4067
- });
4068
- //#endregion
4069
- //#region \0/plugin-vue/export-helper
4070
- var export_helper_default = (sfc, props) => {
4071
- const target = sfc.__vccOpts || sfc;
4072
- for (const [key, val] of props) target[key] = val;
4073
- return target;
4074
- };
4075
- //#endregion
4076
- //#region src/components/ASkeleton.vue
4077
- const _sfc_main$2 = /* @__PURE__ */ defineComponent({
4500
+ const MAX_RETRY_ATTEMPTS = 5;
4501
+ const _sfc_main$17 = /* @__PURE__ */ defineComponent({
4078
4502
  __name: "ASkeleton",
4079
4503
  props: {
4080
4504
  loading: {
4081
4505
  type: Boolean,
4082
4506
  required: true
4083
4507
  },
4508
+ mode: {
4509
+ type: String,
4510
+ required: false,
4511
+ default: "clone"
4512
+ },
4084
4513
  cacheKey: {
4085
4514
  type: String,
4086
4515
  required: false
@@ -4088,12 +4517,12 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
4088
4517
  maxDepth: {
4089
4518
  type: Number,
4090
4519
  required: false,
4091
- default: 6
4520
+ default: 16
4092
4521
  },
4093
4522
  maxNodes: {
4094
4523
  type: Number,
4095
4524
  required: false,
4096
- default: 500
4525
+ default: 600
4097
4526
  },
4098
4527
  minNodeSize: {
4099
4528
  type: Number,
@@ -4131,54 +4560,91 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
4131
4560
  __expose();
4132
4561
  const props = __props;
4133
4562
  const slots = useSlots();
4134
- const resolvedKey = computed(() => props.cacheKey ?? fingerprintSlot(slots.default?.()));
4135
- const cached = shallowRef(getCached(resolvedKey.value, props.persist));
4136
- watch(resolvedKey, (key) => {
4137
- cached.value = getCached(key, props.persist);
4138
- });
4139
- const wrapperRef = ref(null);
4140
- useShapeProbe(() => props.loading ? null : wrapperRef.value, {
4141
- maxDepth: props.maxDepth,
4142
- maxNodes: props.maxNodes,
4143
- minSize: props.minNodeSize,
4144
- onCapture: (shape) => {
4145
- setCached(resolvedKey.value, shape, props.persist);
4146
- cached.value = shape;
4147
- }
4148
- });
4563
+ const instanceId = useId();
4149
4564
  const animationClass = computed(() => props.animation === "none" ? null : `a-skel-block--anim-${props.animation}`);
4150
- const layerStyle = computed(() => cached.value ? {
4151
- width: `${cached.value.width}px`,
4152
- height: `${cached.value.height}px`
4153
- } : {});
4154
- const blockClassByType = computed(() => {
4155
- const anim = animationClass.value;
4156
- const suffix = anim ? ` ${anim}` : "";
4157
- return Object.freeze({
4158
- block: `a-skel-block a-skel-block--block${suffix}`,
4159
- text: `a-skel-block a-skel-block--text${suffix}`,
4160
- image: `a-skel-block a-skel-block--image${suffix}`,
4161
- circle: `a-skel-block a-skel-block--circle${suffix}`
4565
+ const cloneAnimation = computed(() => props.animation);
4566
+ const mirrorVNodes = computed(() => props.loading ? slots.default?.() ?? [] : []);
4567
+ const hasContent = computed(() => mirrorVNodes.value.length > 0);
4568
+ const captureRef = ref(null);
4569
+ const snapshot = shallowRef(void 0);
4570
+ const snapshotValid = computed(() => !!snapshot.value && snapshot.value.width > 0 && snapshot.value.height > 0);
4571
+ const snapshotRenderable = computed(() => snapshotValid.value && snapshot.value.nodes.length > 0);
4572
+ let captureFrame;
4573
+ let retryAttempts = 0;
4574
+ function takeSnapshot() {
4575
+ if (captureFrame !== void 0) cancelAnimationFrame(captureFrame);
4576
+ retryAttempts = 0;
4577
+ scheduleCapture();
4578
+ }
4579
+ function scheduleCapture() {
4580
+ captureFrame = requestAnimationFrame(() => {
4581
+ captureFrame = requestAnimationFrame(() => {
4582
+ captureFrame = void 0;
4583
+ if (!captureRef.value) return;
4584
+ const result = captureSnapshot(captureRef.value, {
4585
+ maxDepth: props.maxDepth,
4586
+ maxNodes: props.maxNodes,
4587
+ minSize: props.minNodeSize
4588
+ });
4589
+ if (result.width > 0 && result.height > 0) {
4590
+ snapshot.value = result;
4591
+ return;
4592
+ }
4593
+ if (retryAttempts < MAX_RETRY_ATTEMPTS) {
4594
+ retryAttempts++;
4595
+ scheduleCapture();
4596
+ }
4597
+ });
4162
4598
  });
4599
+ }
4600
+ watch(captureRef, (el) => {
4601
+ if (props.mode !== "clone") return;
4602
+ if (el) takeSnapshot();
4603
+ }, { flush: "post" });
4604
+ watch(() => props.mode, (mode) => {
4605
+ if (mode === "clone" && captureRef.value) takeSnapshot();
4606
+ });
4607
+ watch(() => props.loading, (next, prev) => {
4608
+ if (props.mode !== "clone") return;
4609
+ if (next && !prev && captureRef.value) takeSnapshot();
4610
+ });
4611
+ onBeforeUnmount(() => {
4612
+ if (captureFrame !== void 0) cancelAnimationFrame(captureFrame);
4163
4613
  });
4164
- const structuralVNodes = computed(() => props.loading ? slots.default?.() ?? [] : []);
4165
4614
  const __returned__ = {
4166
4615
  props,
4167
4616
  slots,
4168
- resolvedKey,
4169
- cached,
4170
- wrapperRef,
4617
+ instanceId,
4171
4618
  animationClass,
4172
- layerStyle,
4173
- blockClassByType,
4174
- structuralVNodes,
4175
- hasStructure: computed(() => structuralVNodes.value.length > 0),
4619
+ cloneAnimation,
4620
+ mirrorVNodes,
4621
+ hasContent,
4622
+ captureRef,
4623
+ snapshot,
4624
+ snapshotValid,
4625
+ snapshotRenderable,
4626
+ get captureFrame() {
4627
+ return captureFrame;
4628
+ },
4629
+ set captureFrame(v) {
4630
+ captureFrame = v;
4631
+ },
4632
+ get retryAttempts() {
4633
+ return retryAttempts;
4634
+ },
4635
+ set retryAttempts(v) {
4636
+ retryAttempts = v;
4637
+ },
4638
+ MAX_RETRY_ATTEMPTS,
4639
+ takeSnapshot,
4640
+ scheduleCapture,
4176
4641
  get cn() {
4177
4642
  return cn;
4178
4643
  },
4179
4644
  get StructuralSkeleton() {
4180
4645
  return StructuralSkeleton;
4181
- }
4646
+ },
4647
+ ASkeletonClone: ASkeletonClone_default
4182
4648
  };
4183
4649
  Object.defineProperty(__returned__, "__isScriptSetup", {
4184
4650
  enumerable: false,
@@ -4187,44 +4653,61 @@ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
4187
4653
  return __returned__;
4188
4654
  }
4189
4655
  });
4190
- const _hoisted_1 = ["data-loading"];
4191
- const _hoisted_2 = {
4192
- class: "a-skeleton__structural",
4193
- role: "status",
4194
- "aria-live": "polite",
4195
- "aria-busy": "true"
4656
+ const _hoisted_1$5 = [
4657
+ "data-loading",
4658
+ "aria-busy",
4659
+ "aria-live"
4660
+ ];
4661
+ const _hoisted_2$1 = ["aria-hidden"];
4662
+ const _hoisted_3$1 = {
4663
+ key: 0,
4664
+ class: "a-skeleton__mirror a-skeleton__mirror--fallback"
4196
4665
  };
4197
- const _hoisted_3 = {
4198
- class: "a-skeleton__fallback",
4199
- role: "status",
4200
- "aria-busy": "true"
4666
+ const _hoisted_4 = {
4667
+ key: 1,
4668
+ class: "a-skeleton__replay"
4201
4669
  };
4202
- function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
4670
+ const _hoisted_5 = {
4671
+ key: 0,
4672
+ class: "a-skeleton__mirror"
4673
+ };
4674
+ const _hoisted_6 = {
4675
+ key: 1,
4676
+ class: "a-skeleton__fallback"
4677
+ };
4678
+ function _sfc_render$17(_ctx, _cache, $props, $setup, $data, $options) {
4203
4679
  return openBlock(), createElementBlock("div", {
4204
- ref: "wrapperRef",
4205
- class: normalizeClass($setup.cn("a-skeleton", $setup.props.class)),
4206
- "data-loading": $setup.props.loading ? "" : void 0
4207
- }, [$setup.props.loading ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createCommentVNode(" Cache hit: pixel-aligned positioned blocks from a previous measurement.\n Styles are pre-computed during capture so the loop below never calls\n a function or allocates a style object per node. "), $setup.cached ? (openBlock(), createElementBlock("div", {
4208
- key: 0,
4209
- class: "a-skeleton__layer",
4210
- style: normalizeStyle($setup.layerStyle),
4680
+ class: normalizeClass($setup.cn("a-skeleton", `a-skeleton--mode-${$setup.props.mode}`, $setup.props.class)),
4681
+ "data-loading": $setup.props.loading ? "" : void 0,
4211
4682
  role: "status",
4212
- "aria-live": "polite",
4213
- "aria-busy": "true"
4214
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.cached.nodes, (node, idx) => {
4215
- return openBlock(), createElementBlock(Fragment, { key: idx }, [node.lineStyles ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(node.lineStyles, (lineStyle, i) => {
4216
- return openBlock(), createElementBlock("div", {
4217
- key: `${idx}-${i}`,
4218
- class: normalizeClass($setup.blockClassByType.text),
4219
- style: normalizeStyle(lineStyle)
4220
- }, null, 6);
4221
- }), 128)) : (openBlock(), createElementBlock("div", {
4222
- key: 1,
4223
- class: normalizeClass($setup.blockClassByType[node.type]),
4224
- style: normalizeStyle(node.style)
4225
- }, null, 6))], 64);
4226
- }), 128))], 4)) : $setup.hasStructure ? (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" Cache miss + slot has structure: render a structural skeleton derived\n from the slot's vnode tree. First paint already looks right. "), createElementVNode("div", _hoisted_2, [createVNode($setup["StructuralSkeleton"], {
4227
- vnodes: $setup.structuralVNodes,
4683
+ "aria-busy": $setup.props.loading ? "true" : void 0,
4684
+ "aria-live": $setup.props.loading ? "polite" : void 0
4685
+ }, [$setup.props.mode === "clone" ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
4686
+ createCommentVNode(" Off-screen capture mount: the slot is always rendered (so we have a\n live target to snapshot from) but visually suppressed while the\n skeleton is showing. `aria-hidden` keeps it out of AT trees too. "),
4687
+ createElementVNode("div", {
4688
+ ref: "captureRef",
4689
+ class: normalizeClass(["a-skeleton__capture", $setup.props.loading ? "a-skeleton__capture--hidden" : null]),
4690
+ "aria-hidden": $setup.props.loading ? "true" : void 0
4691
+ }, [renderSlot(_ctx.$slots, "default", {}, void 0, true)], 10, _hoisted_2$1),
4692
+ createCommentVNode(" Mirror fallback — **always rendered as a backdrop** while loading,\n regardless of snapshot state. The replay layer below sits on top of\n it (higher z-index). If the snapshot turns out to be empty / zero-\n sized / otherwise unrenderable, the mirror underneath still gives the\n user a structural skeleton instead of a blank wrapper. "),
4693
+ $setup.props.loading && $setup.hasContent ? (openBlock(), createElementBlock("div", _hoisted_3$1, [createVNode($setup["StructuralSkeleton"], {
4694
+ vnodes: $setup.mirrorVNodes,
4695
+ animation: $setup.animationClass,
4696
+ "max-depth": $props.maxDepth,
4697
+ "max-nodes": $props.maxNodes
4698
+ }, null, 8, [
4699
+ "vnodes",
4700
+ "animation",
4701
+ "max-depth",
4702
+ "max-nodes"
4703
+ ])])) : createCommentVNode("v-if", true),
4704
+ createCommentVNode(" Replay layer — wrapped in a positioning div whose scoped styles\n are GUARANTEED to apply (a child-component root only inherits the\n parent's scope when no inner div is present; placing the absolute-\n positioning on a regular div in the parent template removes that\n ambiguity). Gated on `snapshotValid` (width AND height > 0) AND\n snapshot.nodes.length > 0 so an empty capture doesn't render a\n transparent overlay that masks the mirror underneath. "),
4705
+ $setup.props.loading && $setup.snapshotRenderable ? (openBlock(), createElementBlock("div", _hoisted_4, [createVNode($setup["ASkeletonClone"], {
4706
+ shape: $setup.snapshot,
4707
+ animation: $setup.cloneAnimation
4708
+ }, null, 8, ["shape", "animation"])])) : createCommentVNode("v-if", true)
4709
+ ], 64)) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [$setup.props.loading ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [$setup.hasContent ? (openBlock(), createElementBlock("div", _hoisted_5, [createVNode($setup["StructuralSkeleton"], {
4710
+ vnodes: $setup.mirrorVNodes,
4228
4711
  animation: $setup.animationClass,
4229
4712
  "max-depth": $props.maxDepth,
4230
4713
  "max-nodes": $props.maxNodes
@@ -4233,16 +4716,153 @@ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
4233
4716
  "animation",
4234
4717
  "max-depth",
4235
4718
  "max-nodes"
4236
- ])])], 2112)) : (openBlock(), createElementBlock(Fragment, { key: 2 }, [createCommentVNode(" Cache miss + nothing to walk: generic shimmer. "), createElementVNode("div", _hoisted_3, [renderSlot(_ctx.$slots, "fallback", {}, () => [createElementVNode("div", { class: normalizeClass(["a-skel-block a-skel-block--block a-skel-fallback-default", $setup.animationClass]) }, null, 2)], true)])], 2112))], 64)) : renderSlot(_ctx.$slots, "default", { key: 1 }, void 0, true)], 10, _hoisted_1);
4719
+ ])])) : (openBlock(), createElementBlock("div", _hoisted_6, [renderSlot(_ctx.$slots, "fallback", {}, () => [createElementVNode("div", { class: normalizeClass(["a-skel-block a-skel-block--block a-skel-fallback-default", $setup.animationClass]) }, null, 2)], true)]))], 64)) : renderSlot(_ctx.$slots, "default", { key: 1 }, void 0, true)], 64))], 10, _hoisted_1$5);
4237
4720
  }
4238
- var ASkeleton_default = /* @__PURE__ */ export_helper_default(_sfc_main$2, [
4239
- ["render", _sfc_render$2],
4721
+ var ASkeleton_default = /* @__PURE__ */ export_helper_default(_sfc_main$17, [
4722
+ ["render", _sfc_render$17],
4240
4723
  ["__scopeId", "data-v-16717541"],
4241
4724
  ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeleton.vue"]
4242
4725
  ]);
4243
4726
  //#endregion
4727
+ //#region src/components/StructuralLayerNode.ts
4728
+ /**
4729
+ * `StructuralLayerNode` — recursive renderer for a `StructuralNode` from a
4730
+ * `StructuralShape` captured by `walkStructural`. Used internally by
4731
+ * `<ASkeletonLayer>`.
4732
+ *
4733
+ * Design:
4734
+ * - **Strategy table** keyed by `node.kind` + `node.leafKind`. Each kind has
4735
+ * its own renderer function. Adding a new kind is one entry in `RENDERERS`
4736
+ * (Open/Closed) — no edits elsewhere.
4737
+ * - **Pure render function**: no reactive state of its own; everything flows
4738
+ * from props.
4739
+ * - **Container preservation**: container nodes emit `<{node.tag}
4740
+ * :class="node.className" :style="node.style">…children…</…>`. The
4741
+ * captured `style` carries comprehensive layout + visual CSS so the
4742
+ * skeleton reads correctly even when the consumer's stylesheet isn't
4743
+ * loaded at the mount point.
4744
+ * - **Leaves**: render `<div class="a-skel + originalClass" :style="style">`.
4745
+ * The original `class` is preserved so utility-first CSS (`mt-4`,
4746
+ * `flex-1`, `inline-block`, …) survives; the captured style carries the
4747
+ * same layout + visual properties used on containers plus `width` +
4748
+ * `height` so the placeholder claims the right space.
4749
+ */
4750
+ const LEAF_RENDERERS = {
4751
+ block: renderLeafBlock,
4752
+ media: renderLeafBlock,
4753
+ text: renderLeafText,
4754
+ image: (node, animClass) => renderLeafWithIcon(node, animClass, "image")
4755
+ };
4756
+ function renderContainer(node, animClass) {
4757
+ return h(node.tag, {
4758
+ class: node.className || void 0,
4759
+ style: node.style,
4760
+ "aria-hidden": "true"
4761
+ }, node.children.map((child) => h(StructuralLayerNode, {
4762
+ node: child,
4763
+ animClass
4764
+ })));
4765
+ }
4766
+ /**
4767
+ * Build the leaf's `class` payload. Always includes `a-skel` (skeleton
4768
+ * surface) + the animation class; the original consumer class trails so it
4769
+ * survives the cascade alongside utility-first frameworks.
4770
+ */
4771
+ function leafClass(node, animClass, extra) {
4772
+ const classes = ["a-skel"];
4773
+ if (extra) classes.push(extra);
4774
+ if (animClass) classes.push(animClass);
4775
+ if (node.className) classes.push(node.className);
4776
+ return classes;
4777
+ }
4778
+ function renderLeafBlock(node, animClass) {
4779
+ return h("div", {
4780
+ class: leafClass(node, animClass),
4781
+ style: node.style,
4782
+ "aria-hidden": "true"
4783
+ });
4784
+ }
4785
+ /**
4786
+ * Text leaf — host carries the captured layout + visual surface (including
4787
+ * the leaf's `width` / `height` so it claims the right space in its parent's
4788
+ * layout). Each captured per-line rect renders as one bar inside the host,
4789
+ * absolutely positioned at the captured rect (rects are leaf-relative; the
4790
+ * host's captured style includes `position: relative` via the `.a-skel-text-host`
4791
+ * default — see styles.src.css).
4792
+ *
4793
+ * When `textLines` is absent (Range API unusable), the leaf renders as a
4794
+ * single shimmer bar at the host's bounds — same as a leaf-block.
4795
+ */
4796
+ function renderLeafText(node, animClass) {
4797
+ const lines = node.textLines;
4798
+ if (!lines || lines.length === 0) return h("div", {
4799
+ class: leafClass(node, animClass, "a-skel--text"),
4800
+ style: node.style,
4801
+ "aria-hidden": "true"
4802
+ });
4803
+ return h("div", {
4804
+ class: ["a-skel-text-host", node.className || void 0],
4805
+ style: node.style,
4806
+ "aria-hidden": "true"
4807
+ }, lines.map((line) => h("div", {
4808
+ class: [
4809
+ "a-skel",
4810
+ "a-skel--text-line",
4811
+ animClass
4812
+ ],
4813
+ style: {
4814
+ position: "absolute",
4815
+ left: `${line.x}px`,
4816
+ top: `${line.y}px`,
4817
+ width: `${line.w}px`,
4818
+ height: `${line.h}px`
4819
+ }
4820
+ })));
4821
+ }
4822
+ function renderLeafWithIcon(node, animClass, kind) {
4823
+ return h("div", {
4824
+ class: leafClass(node, animClass, "a-skel--with-icon"),
4825
+ style: node.style,
4826
+ "aria-hidden": "true"
4827
+ }, [kind === "image" ? renderImageIcon() : renderPlayIcon()]);
4828
+ }
4829
+ const RENDERERS = {
4830
+ container: (ctx) => renderContainer(ctx.node, ctx.animClass),
4831
+ leaf: (ctx) => {
4832
+ const leaf = ctx.node;
4833
+ return LEAF_RENDERERS[leaf.leafKind](leaf, ctx.animClass);
4834
+ }
4835
+ };
4836
+ const StructuralLayerNode = defineComponent({
4837
+ name: "StructuralLayerNode",
4838
+ props: {
4839
+ node: {
4840
+ type: Object,
4841
+ required: true
4842
+ },
4843
+ animClass: {
4844
+ type: [String, null],
4845
+ default: null
4846
+ }
4847
+ },
4848
+ setup(props) {
4849
+ return () => RENDERERS[props.node.kind]({
4850
+ node: props.node,
4851
+ animClass: props.animClass
4852
+ });
4853
+ }
4854
+ });
4855
+ //#endregion
4244
4856
  //#region src/components/ASkeletonLayer.vue
4245
- const _sfc_main$1 = /* @__PURE__ */ defineComponent({
4857
+ /**
4858
+ * `<ASkeletonLayer>` — replays a `StructuralShape` produced by
4859
+ * `walkStructural` / `useSkeleton()` as a tree of preserved containers +
4860
+ * a-skel leaf placeholders. The layer is a transparent shell with no
4861
+ * width / height / position constraints, so it drops into the consumer's
4862
+ * own container and lets the captured tree's flex/grid/spacing dictate
4863
+ * how the skeleton lays itself out.
4864
+ */
4865
+ const _sfc_main$16 = /* @__PURE__ */ defineComponent({
4246
4866
  __name: "ASkeletonLayer",
4247
4867
  props: {
4248
4868
  shape: {
@@ -4266,176 +4886,2366 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
4266
4886
  skipCheck: true
4267
4887
  }
4268
4888
  },
4889
+ setup(__props, { expose: __expose }) {
4890
+ __expose();
4891
+ const props = __props;
4892
+ const __returned__ = {
4893
+ props,
4894
+ animationClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
4895
+ get cn() {
4896
+ return cn;
4897
+ },
4898
+ get StructuralLayerNode() {
4899
+ return StructuralLayerNode;
4900
+ }
4901
+ };
4902
+ Object.defineProperty(__returned__, "__isScriptSetup", {
4903
+ enumerable: false,
4904
+ value: true
4905
+ });
4906
+ return __returned__;
4907
+ }
4908
+ });
4909
+ function _sfc_render$16(_ctx, _cache, $props, $setup, $data, $options) {
4910
+ return $props.shape ? (openBlock(), createElementBlock("div", {
4911
+ key: 0,
4912
+ class: normalizeClass($setup.cn("a-skeleton__layer", $setup.props.class)),
4913
+ role: "status",
4914
+ "aria-live": "polite",
4915
+ "aria-busy": "true"
4916
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($props.shape.nodes, (node, idx) => {
4917
+ return openBlock(), createBlock($setup["StructuralLayerNode"], {
4918
+ key: idx,
4919
+ node,
4920
+ "anim-class": $setup.animationClass
4921
+ }, null, 8, ["node", "anim-class"]);
4922
+ }), 128)), _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading…", -1))], 2)) : createCommentVNode("v-if", true);
4923
+ }
4924
+ var ASkeletonLayer_default = /* @__PURE__ */ export_helper_default(_sfc_main$16, [["render", _sfc_render$16], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeletonLayer.vue"]]);
4925
+ //#endregion
4926
+ //#region src/components/ASkeletonBlock.vue
4927
+ const _sfc_main$15 = /* @__PURE__ */ defineComponent({
4928
+ __name: "ASkeletonBlock",
4929
+ props: {
4930
+ type: {
4931
+ type: String,
4932
+ required: false,
4933
+ default: "block"
4934
+ },
4935
+ w: {
4936
+ type: [Number, String],
4937
+ required: false
4938
+ },
4939
+ h: {
4940
+ type: [Number, String],
4941
+ required: false
4942
+ },
4943
+ radius: {
4944
+ type: [Number, String],
4945
+ required: false
4946
+ },
4947
+ lines: {
4948
+ type: Number,
4949
+ required: false,
4950
+ default: 1
4951
+ },
4952
+ animation: {
4953
+ type: String,
4954
+ required: false,
4955
+ default: "shimmer"
4956
+ },
4957
+ class: {
4958
+ type: [
4959
+ Boolean,
4960
+ null,
4961
+ String,
4962
+ Object,
4963
+ Array
4964
+ ],
4965
+ required: false,
4966
+ skipCheck: true
4967
+ }
4968
+ },
4269
4969
  setup(__props, { expose: __expose }) {
4270
4970
  __expose();
4271
4971
  const props = __props;
4272
4972
  const animationClass = computed(() => props.animation === "none" ? null : `a-skel-block--anim-${props.animation}`);
4973
+ const blockClass = computed(() => [
4974
+ "a-skel-block",
4975
+ `a-skel-block--${props.type}`,
4976
+ animationClass.value
4977
+ ]);
4978
+ function toLength(v) {
4979
+ if (v === void 0) return void 0;
4980
+ return typeof v === "number" ? `${v}px` : v;
4981
+ }
4982
+ const radiusValue = computed(() => props.type === "circle" && props.radius === void 0 ? "50%" : toLength(props.radius));
4273
4983
  const __returned__ = {
4274
4984
  props,
4275
4985
  animationClass,
4276
- layerStyle: computed(() => props.shape ? {
4277
- width: `${props.shape.width}px`,
4278
- height: `${props.shape.height}px`
4279
- } : {}),
4280
- blockClassByType: computed(() => {
4281
- const anim = animationClass.value;
4282
- const suffix = anim ? ` ${anim}` : "";
4283
- return Object.freeze({
4284
- block: `a-skel-block a-skel-block--block${suffix}`,
4285
- text: `a-skel-block a-skel-block--text${suffix}`,
4286
- image: `a-skel-block a-skel-block--image${suffix}`,
4287
- circle: `a-skel-block a-skel-block--circle${suffix}`
4288
- });
4289
- }),
4986
+ blockClass,
4987
+ toLength,
4988
+ radiusValue,
4989
+ blockStyle: computed(() => ({
4990
+ width: toLength(props.w),
4991
+ height: toLength(props.h),
4992
+ borderRadius: radiusValue.value
4993
+ })),
4994
+ isMultiLineText: computed(() => props.type === "text" && props.lines > 1),
4290
4995
  get cn() {
4291
4996
  return cn;
4292
4997
  }
4293
- };
4294
- Object.defineProperty(__returned__, "__isScriptSetup", {
4295
- enumerable: false,
4296
- value: true
4297
- });
4298
- return __returned__;
4998
+ };
4999
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5000
+ enumerable: false,
5001
+ value: true
5002
+ });
5003
+ return __returned__;
5004
+ }
5005
+ });
5006
+ function _sfc_render$15(_ctx, _cache, $props, $setup, $data, $options) {
5007
+ return $setup.isMultiLineText ? (openBlock(), createElementBlock("div", {
5008
+ key: 0,
5009
+ class: normalizeClass($setup.cn("a-skel-block-stack", $setup.props.class)),
5010
+ role: "status",
5011
+ "aria-busy": "true"
5012
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.lines, (i) => {
5013
+ return openBlock(), createElementBlock("div", {
5014
+ key: i,
5015
+ class: normalizeClass($setup.blockClass),
5016
+ style: normalizeStyle({
5017
+ height: $setup.blockStyle.height ?? "0.75em",
5018
+ width: i === $setup.props.lines ? "70%" : "100%",
5019
+ borderRadius: $setup.blockStyle.borderRadius ?? "4px"
5020
+ })
5021
+ }, null, 6);
5022
+ }), 128))], 2)) : (openBlock(), createElementBlock("div", {
5023
+ key: 1,
5024
+ class: normalizeClass($setup.cn($setup.blockClass, $setup.props.class)),
5025
+ style: normalizeStyle($setup.blockStyle),
5026
+ role: "status",
5027
+ "aria-busy": "true"
5028
+ }, null, 6));
5029
+ }
5030
+ var ASkeletonBlock_default = /* @__PURE__ */ export_helper_default(_sfc_main$15, [
5031
+ ["render", _sfc_render$15],
5032
+ ["__scopeId", "data-v-bdfba69a"],
5033
+ ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeletonBlock.vue"]
5034
+ ]);
5035
+ //#endregion
5036
+ //#region src/components/variants/ASkeletonText.vue
5037
+ const _sfc_main$14 = /* @__PURE__ */ defineComponent({
5038
+ __name: "ASkeletonText",
5039
+ props: {
5040
+ lines: {
5041
+ type: Number,
5042
+ required: false,
5043
+ default: 1
5044
+ },
5045
+ width: {
5046
+ type: [Number, String],
5047
+ required: false
5048
+ },
5049
+ animation: {
5050
+ type: String,
5051
+ required: false,
5052
+ default: "pulse"
5053
+ },
5054
+ class: {
5055
+ type: [
5056
+ Boolean,
5057
+ null,
5058
+ String,
5059
+ Object,
5060
+ Array
5061
+ ],
5062
+ required: false,
5063
+ skipCheck: true
5064
+ }
5065
+ },
5066
+ setup(__props, { expose: __expose }) {
5067
+ __expose();
5068
+ const props = __props;
5069
+ const animClass = computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`);
5070
+ const rootStyle = computed(() => props.width !== void 0 ? { width: typeof props.width === "number" ? `${props.width}px` : String(props.width) } : void 0);
5071
+ function widthForLine(i) {
5072
+ if (i === props.lines - 1 && props.lines > 1) return "65%";
5073
+ const palette = [
5074
+ "100%",
5075
+ "92%",
5076
+ "88%",
5077
+ "95%"
5078
+ ];
5079
+ return palette[i % palette.length];
5080
+ }
5081
+ const __returned__ = {
5082
+ props,
5083
+ animClass,
5084
+ rootStyle,
5085
+ widthForLine,
5086
+ get cn() {
5087
+ return cn;
5088
+ }
5089
+ };
5090
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5091
+ enumerable: false,
5092
+ value: true
5093
+ });
5094
+ return __returned__;
5095
+ }
5096
+ });
5097
+ function _sfc_render$14(_ctx, _cache, $props, $setup, $data, $options) {
5098
+ return openBlock(), createElementBlock("div", {
5099
+ class: normalizeClass($setup.cn("a-skel-variants-text", $setup.props.class)),
5100
+ style: normalizeStyle($setup.rootStyle),
5101
+ role: "status",
5102
+ "aria-live": "polite",
5103
+ "aria-busy": "true"
5104
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.lines, (i) => {
5105
+ return openBlock(), createElementBlock("div", {
5106
+ key: i,
5107
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-text", $setup.animClass)),
5108
+ style: normalizeStyle({
5109
+ width: $setup.widthForLine(i - 1),
5110
+ marginTop: i > 1 ? "0.5em" : void 0
5111
+ })
5112
+ }, null, 6);
5113
+ }), 128)), _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading…", -1))], 6);
5114
+ }
5115
+ var ASkeletonText_default = /* @__PURE__ */ export_helper_default(_sfc_main$14, [["render", _sfc_render$14], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonText.vue"]]);
5116
+ //#endregion
5117
+ //#region src/components/variants/ASkeletonHeading.vue
5118
+ const _sfc_main$13 = /* @__PURE__ */ defineComponent({
5119
+ __name: "ASkeletonHeading",
5120
+ props: {
5121
+ level: {
5122
+ type: Number,
5123
+ required: false,
5124
+ default: 2
5125
+ },
5126
+ width: {
5127
+ type: [Number, String],
5128
+ required: false
5129
+ },
5130
+ animation: {
5131
+ type: String,
5132
+ required: false,
5133
+ default: "pulse"
5134
+ },
5135
+ class: {
5136
+ type: [
5137
+ Boolean,
5138
+ null,
5139
+ String,
5140
+ Object,
5141
+ Array
5142
+ ],
5143
+ required: false,
5144
+ skipCheck: true
5145
+ }
5146
+ },
5147
+ setup(__props, { expose: __expose }) {
5148
+ __expose();
5149
+ const props = __props;
5150
+ const animClass = computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`);
5151
+ const HEIGHT_BY_LEVEL = {
5152
+ 1: "2.25rem",
5153
+ 2: "1.75rem",
5154
+ 3: "1.5rem",
5155
+ 4: "1.25rem",
5156
+ 5: "1.125rem",
5157
+ 6: "1rem"
5158
+ };
5159
+ const __returned__ = {
5160
+ props,
5161
+ animClass,
5162
+ HEIGHT_BY_LEVEL,
5163
+ rootStyle: computed(() => ({
5164
+ width: props.width !== void 0 ? typeof props.width === "number" ? `${props.width}px` : String(props.width) : "60%",
5165
+ height: HEIGHT_BY_LEVEL[props.level]
5166
+ })),
5167
+ get cn() {
5168
+ return cn;
5169
+ }
5170
+ };
5171
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5172
+ enumerable: false,
5173
+ value: true
5174
+ });
5175
+ return __returned__;
5176
+ }
5177
+ });
5178
+ function _sfc_render$13(_ctx, _cache, $props, $setup, $data, $options) {
5179
+ return openBlock(), createElementBlock("div", {
5180
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-heading", $setup.animClass, $setup.props.class)),
5181
+ style: normalizeStyle($setup.rootStyle),
5182
+ role: "status",
5183
+ "aria-busy": "true"
5184
+ }, [..._cache[0] || (_cache[0] = [createElementVNode("span", { class: "a-skel-sr-only" }, "Loading heading…", -1)])], 6);
5185
+ }
5186
+ var ASkeletonHeading_default = /* @__PURE__ */ export_helper_default(_sfc_main$13, [["render", _sfc_render$13], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonHeading.vue"]]);
5187
+ //#endregion
5188
+ //#region src/components/variants/ASkeletonAvatar.vue
5189
+ const _sfc_main$12 = /* @__PURE__ */ defineComponent({
5190
+ __name: "ASkeletonAvatar",
5191
+ props: {
5192
+ size: {
5193
+ type: [Number, String],
5194
+ required: false,
5195
+ default: 48
5196
+ },
5197
+ shape: {
5198
+ type: String,
5199
+ required: false,
5200
+ default: "circle"
5201
+ },
5202
+ animation: {
5203
+ type: String,
5204
+ required: false,
5205
+ default: "pulse"
5206
+ },
5207
+ class: {
5208
+ type: [
5209
+ Boolean,
5210
+ null,
5211
+ String,
5212
+ Object,
5213
+ Array
5214
+ ],
5215
+ required: false,
5216
+ skipCheck: true
5217
+ }
5218
+ },
5219
+ setup(__props, { expose: __expose }) {
5220
+ __expose();
5221
+ const props = __props;
5222
+ const __returned__ = {
5223
+ props,
5224
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5225
+ sizeStyle: computed(() => {
5226
+ const s = typeof props.size === "number" ? `${props.size}px` : String(props.size);
5227
+ return {
5228
+ width: s,
5229
+ height: s,
5230
+ borderRadius: props.shape === "circle" ? "9999px" : props.shape === "rounded" ? "var(--ak-skel-radius)" : "0"
5231
+ };
5232
+ }),
5233
+ get cn() {
5234
+ return cn;
5235
+ }
5236
+ };
5237
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5238
+ enumerable: false,
5239
+ value: true
5240
+ });
5241
+ return __returned__;
5242
+ }
5243
+ });
5244
+ function _sfc_render$12(_ctx, _cache, $props, $setup, $data, $options) {
5245
+ return openBlock(), createElementBlock("div", {
5246
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-avatar", $setup.animClass, $setup.props.class)),
5247
+ style: normalizeStyle($setup.sizeStyle),
5248
+ role: "status",
5249
+ "aria-busy": "true"
5250
+ }, [..._cache[0] || (_cache[0] = [createElementVNode("span", { class: "a-skel-sr-only" }, "Loading avatar…", -1)])], 6);
5251
+ }
5252
+ var ASkeletonAvatar_default = /* @__PURE__ */ export_helper_default(_sfc_main$12, [["render", _sfc_render$12], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonAvatar.vue"]]);
5253
+ //#endregion
5254
+ //#region src/components/variants/ASkeletonImage.vue
5255
+ const _sfc_main$11 = /* @__PURE__ */ defineComponent({
5256
+ __name: "ASkeletonImage",
5257
+ props: {
5258
+ ratio: {
5259
+ type: String,
5260
+ required: false,
5261
+ default: "16 / 9"
5262
+ },
5263
+ width: {
5264
+ type: [Number, String],
5265
+ required: false
5266
+ },
5267
+ height: {
5268
+ type: [Number, String],
5269
+ required: false
5270
+ },
5271
+ showIcon: {
5272
+ type: Boolean,
5273
+ required: false,
5274
+ default: true
5275
+ },
5276
+ animation: {
5277
+ type: String,
5278
+ required: false,
5279
+ default: "pulse"
5280
+ },
5281
+ class: {
5282
+ type: [
5283
+ Boolean,
5284
+ null,
5285
+ String,
5286
+ Object,
5287
+ Array
5288
+ ],
5289
+ required: false,
5290
+ skipCheck: true
5291
+ }
5292
+ },
5293
+ setup(__props, { expose: __expose }) {
5294
+ __expose();
5295
+ const props = __props;
5296
+ const __returned__ = {
5297
+ props,
5298
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5299
+ rootStyle: computed(() => {
5300
+ const s = {};
5301
+ if (props.ratio) s.aspectRatio = props.ratio;
5302
+ if (props.width !== void 0) s.width = typeof props.width === "number" ? `${props.width}px` : String(props.width);
5303
+ if (props.height !== void 0) s.height = typeof props.height === "number" ? `${props.height}px` : String(props.height);
5304
+ return s;
5305
+ }),
5306
+ get cn() {
5307
+ return cn;
5308
+ }
5309
+ };
5310
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5311
+ enumerable: false,
5312
+ value: true
5313
+ });
5314
+ return __returned__;
5315
+ }
5316
+ });
5317
+ const _hoisted_1$4 = {
5318
+ key: 0,
5319
+ class: "size-10",
5320
+ viewBox: "0 0 24 24",
5321
+ fill: "none",
5322
+ xmlns: "http://www.w3.org/2000/svg",
5323
+ "aria-hidden": "true"
5324
+ };
5325
+ function _sfc_render$11(_ctx, _cache, $props, $setup, $data, $options) {
5326
+ return openBlock(), createElementBlock("div", {
5327
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-image", $setup.animClass, $setup.props.class)),
5328
+ style: normalizeStyle($setup.rootStyle),
5329
+ role: "status",
5330
+ "aria-busy": "true"
5331
+ }, [$setup.props.showIcon ? (openBlock(), createElementBlock("svg", _hoisted_1$4, [..._cache[0] || (_cache[0] = [createElementVNode("path", {
5332
+ d: "M19 5H5a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2Zm-3.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM19 17H5l3.5-4.5 2.5 3 3.5-4.5L19 17Z",
5333
+ fill: "currentColor"
5334
+ }, null, -1)])])) : createCommentVNode("v-if", true), _cache[1] || (_cache[1] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading image…", -1))], 6);
5335
+ }
5336
+ var ASkeletonImage_default = /* @__PURE__ */ export_helper_default(_sfc_main$11, [["render", _sfc_render$11], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonImage.vue"]]);
5337
+ //#endregion
5338
+ //#region src/components/variants/ASkeletonVideo.vue
5339
+ const _sfc_main$10 = /* @__PURE__ */ defineComponent({
5340
+ __name: "ASkeletonVideo",
5341
+ props: {
5342
+ ratio: {
5343
+ type: String,
5344
+ required: false,
5345
+ default: "16 / 9"
5346
+ },
5347
+ width: {
5348
+ type: [Number, String],
5349
+ required: false
5350
+ },
5351
+ height: {
5352
+ type: [Number, String],
5353
+ required: false
5354
+ },
5355
+ showIcon: {
5356
+ type: Boolean,
5357
+ required: false,
5358
+ default: true
5359
+ },
5360
+ animation: {
5361
+ type: String,
5362
+ required: false,
5363
+ default: "pulse"
5364
+ },
5365
+ class: {
5366
+ type: [
5367
+ Boolean,
5368
+ null,
5369
+ String,
5370
+ Object,
5371
+ Array
5372
+ ],
5373
+ required: false,
5374
+ skipCheck: true
5375
+ }
5376
+ },
5377
+ setup(__props, { expose: __expose }) {
5378
+ __expose();
5379
+ const props = __props;
5380
+ const __returned__ = {
5381
+ props,
5382
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5383
+ rootStyle: computed(() => {
5384
+ const s = {};
5385
+ if (props.ratio) s.aspectRatio = props.ratio;
5386
+ if (props.width !== void 0) s.width = typeof props.width === "number" ? `${props.width}px` : String(props.width);
5387
+ if (props.height !== void 0) s.height = typeof props.height === "number" ? `${props.height}px` : String(props.height);
5388
+ return s;
5389
+ }),
5390
+ get cn() {
5391
+ return cn;
5392
+ }
5393
+ };
5394
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5395
+ enumerable: false,
5396
+ value: true
5397
+ });
5398
+ return __returned__;
5399
+ }
5400
+ });
5401
+ const _hoisted_1$3 = {
5402
+ key: 0,
5403
+ class: "size-12",
5404
+ viewBox: "0 0 24 24",
5405
+ fill: "none",
5406
+ xmlns: "http://www.w3.org/2000/svg",
5407
+ "aria-hidden": "true"
5408
+ };
5409
+ function _sfc_render$10(_ctx, _cache, $props, $setup, $data, $options) {
5410
+ return openBlock(), createElementBlock("div", {
5411
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-video", $setup.animClass, $setup.props.class)),
5412
+ style: normalizeStyle($setup.rootStyle),
5413
+ role: "status",
5414
+ "aria-busy": "true"
5415
+ }, [$setup.props.showIcon ? (openBlock(), createElementBlock("svg", _hoisted_1$3, [..._cache[0] || (_cache[0] = [createElementVNode("circle", {
5416
+ cx: "12",
5417
+ cy: "12",
5418
+ r: "10",
5419
+ stroke: "currentColor",
5420
+ "stroke-width": "1.5"
5421
+ }, null, -1), createElementVNode("path", {
5422
+ d: "M10 8l6 4-6 4V8Z",
5423
+ fill: "currentColor"
5424
+ }, null, -1)])])) : createCommentVNode("v-if", true), _cache[1] || (_cache[1] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading video…", -1))], 6);
5425
+ }
5426
+ var ASkeletonVideo_default = /* @__PURE__ */ export_helper_default(_sfc_main$10, [["render", _sfc_render$10], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonVideo.vue"]]);
5427
+ //#endregion
5428
+ //#region src/components/variants/ASkeletonButton.vue
5429
+ const _sfc_main$9 = /* @__PURE__ */ defineComponent({
5430
+ __name: "ASkeletonButton",
5431
+ props: {
5432
+ width: {
5433
+ type: [Number, String],
5434
+ required: false,
5435
+ default: 120
5436
+ },
5437
+ height: {
5438
+ type: [Number, String],
5439
+ required: false,
5440
+ default: 40
5441
+ },
5442
+ outlined: {
5443
+ type: Boolean,
5444
+ required: false
5445
+ },
5446
+ animation: {
5447
+ type: String,
5448
+ required: false,
5449
+ default: "pulse"
5450
+ },
5451
+ class: {
5452
+ type: [
5453
+ Boolean,
5454
+ null,
5455
+ String,
5456
+ Object,
5457
+ Array
5458
+ ],
5459
+ required: false,
5460
+ skipCheck: true
5461
+ }
5462
+ },
5463
+ setup(__props, { expose: __expose }) {
5464
+ __expose();
5465
+ const props = __props;
5466
+ const __returned__ = {
5467
+ props,
5468
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5469
+ rootStyle: computed(() => ({
5470
+ width: typeof props.width === "number" ? `${props.width}px` : String(props.width),
5471
+ height: typeof props.height === "number" ? `${props.height}px` : String(props.height),
5472
+ ...props.outlined ? {
5473
+ backgroundColor: "transparent",
5474
+ border: "1px solid var(--ak-skel-ring)"
5475
+ } : {}
5476
+ })),
5477
+ get cn() {
5478
+ return cn;
5479
+ }
5480
+ };
5481
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5482
+ enumerable: false,
5483
+ value: true
5484
+ });
5485
+ return __returned__;
5486
+ }
5487
+ });
5488
+ function _sfc_render$9(_ctx, _cache, $props, $setup, $data, $options) {
5489
+ return openBlock(), createElementBlock("div", {
5490
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-button", $setup.animClass, $setup.props.class)),
5491
+ style: normalizeStyle($setup.rootStyle),
5492
+ role: "status",
5493
+ "aria-busy": "true"
5494
+ }, [..._cache[0] || (_cache[0] = [createElementVNode("span", { class: "a-skel-sr-only" }, "Loading button…", -1)])], 6);
5495
+ }
5496
+ var ASkeletonButton_default = /* @__PURE__ */ export_helper_default(_sfc_main$9, [["render", _sfc_render$9], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonButton.vue"]]);
5497
+ //#endregion
5498
+ //#region src/components/variants/ASkeletonInput.vue
5499
+ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
5500
+ __name: "ASkeletonInput",
5501
+ props: {
5502
+ width: {
5503
+ type: [Number, String],
5504
+ required: false,
5505
+ default: "100%"
5506
+ },
5507
+ height: {
5508
+ type: [Number, String],
5509
+ required: false,
5510
+ default: 40
5511
+ },
5512
+ animation: {
5513
+ type: String,
5514
+ required: false,
5515
+ default: "pulse"
5516
+ },
5517
+ class: {
5518
+ type: [
5519
+ Boolean,
5520
+ null,
5521
+ String,
5522
+ Object,
5523
+ Array
5524
+ ],
5525
+ required: false,
5526
+ skipCheck: true
5527
+ }
5528
+ },
5529
+ setup(__props, { expose: __expose }) {
5530
+ __expose();
5531
+ const props = __props;
5532
+ const __returned__ = {
5533
+ props,
5534
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5535
+ rootStyle: computed(() => ({
5536
+ width: typeof props.width === "number" ? `${props.width}px` : String(props.width),
5537
+ height: typeof props.height === "number" ? `${props.height}px` : String(props.height)
5538
+ })),
5539
+ get cn() {
5540
+ return cn;
5541
+ }
5542
+ };
5543
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5544
+ enumerable: false,
5545
+ value: true
5546
+ });
5547
+ return __returned__;
5548
+ }
5549
+ });
5550
+ function _sfc_render$8(_ctx, _cache, $props, $setup, $data, $options) {
5551
+ return openBlock(), createElementBlock("div", {
5552
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-input", $setup.animClass, $setup.props.class)),
5553
+ style: normalizeStyle($setup.rootStyle),
5554
+ role: "status",
5555
+ "aria-busy": "true"
5556
+ }, [..._cache[0] || (_cache[0] = [createElementVNode("span", { class: "a-skel-sr-only" }, "Loading input…", -1)])], 6);
5557
+ }
5558
+ var ASkeletonInput_default = /* @__PURE__ */ export_helper_default(_sfc_main$8, [["render", _sfc_render$8], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonInput.vue"]]);
5559
+ //#endregion
5560
+ //#region src/components/variants/ASkeletonListItem.vue
5561
+ const _sfc_main$7 = /* @__PURE__ */ defineComponent({
5562
+ __name: "ASkeletonListItem",
5563
+ props: {
5564
+ avatar: {
5565
+ type: Boolean,
5566
+ required: false,
5567
+ default: true
5568
+ },
5569
+ lines: {
5570
+ type: Number,
5571
+ required: false,
5572
+ default: 2
5573
+ },
5574
+ trailing: {
5575
+ type: Boolean,
5576
+ required: false,
5577
+ default: false
5578
+ },
5579
+ animation: {
5580
+ type: String,
5581
+ required: false,
5582
+ default: "pulse"
5583
+ },
5584
+ class: {
5585
+ type: [
5586
+ Boolean,
5587
+ null,
5588
+ String,
5589
+ Object,
5590
+ Array
5591
+ ],
5592
+ required: false,
5593
+ skipCheck: true
5594
+ }
5595
+ },
5596
+ setup(__props, { expose: __expose }) {
5597
+ __expose();
5598
+ const props = __props;
5599
+ const __returned__ = {
5600
+ props,
5601
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5602
+ get cn() {
5603
+ return cn;
5604
+ },
5605
+ ASkeletonAvatar: ASkeletonAvatar_default,
5606
+ ASkeletonText: ASkeletonText_default
5607
+ };
5608
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5609
+ enumerable: false,
5610
+ value: true
5611
+ });
5612
+ return __returned__;
5613
+ }
5614
+ });
5615
+ const _hoisted_1$2 = { class: "a-skel-variant-list-item__body" };
5616
+ function _sfc_render$7(_ctx, _cache, $props, $setup, $data, $options) {
5617
+ return openBlock(), createElementBlock("div", {
5618
+ class: normalizeClass($setup.cn("a-skel-variant-list-item", $setup.props.class)),
5619
+ role: "status",
5620
+ "aria-busy": "true"
5621
+ }, [
5622
+ $setup.props.avatar ? (openBlock(), createBlock($setup["ASkeletonAvatar"], {
5623
+ key: 0,
5624
+ size: 40,
5625
+ animation: $setup.props.animation
5626
+ }, null, 8, ["animation"])) : createCommentVNode("v-if", true),
5627
+ createElementVNode("div", _hoisted_1$2, [createVNode($setup["ASkeletonText"], {
5628
+ lines: $setup.props.lines,
5629
+ animation: $setup.props.animation
5630
+ }, null, 8, ["lines", "animation"])]),
5631
+ $setup.props.trailing ? (openBlock(), createElementBlock("div", {
5632
+ key: 1,
5633
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-button", $setup.animClass)),
5634
+ style: {
5635
+ width: "40px",
5636
+ height: "24px"
5637
+ }
5638
+ }, null, 2)) : createCommentVNode("v-if", true),
5639
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading list item…", -1))
5640
+ ], 2);
5641
+ }
5642
+ var ASkeletonListItem_default = /* @__PURE__ */ export_helper_default(_sfc_main$7, [["render", _sfc_render$7], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonListItem.vue"]]);
5643
+ //#endregion
5644
+ //#region src/components/variants/ASkeletonCard.vue
5645
+ const _sfc_main$6 = /* @__PURE__ */ defineComponent({
5646
+ __name: "ASkeletonCard",
5647
+ props: {
5648
+ media: {
5649
+ type: Boolean,
5650
+ required: false,
5651
+ default: true
5652
+ },
5653
+ heading: {
5654
+ type: Boolean,
5655
+ required: false,
5656
+ default: true
5657
+ },
5658
+ lines: {
5659
+ type: Number,
5660
+ required: false,
5661
+ default: 3
5662
+ },
5663
+ actions: {
5664
+ type: Boolean,
5665
+ required: false,
5666
+ default: true
5667
+ },
5668
+ footerAvatar: {
5669
+ type: Boolean,
5670
+ required: false,
5671
+ default: false
5672
+ },
5673
+ animation: {
5674
+ type: String,
5675
+ required: false,
5676
+ default: "pulse"
5677
+ },
5678
+ class: {
5679
+ type: [
5680
+ Boolean,
5681
+ null,
5682
+ String,
5683
+ Object,
5684
+ Array
5685
+ ],
5686
+ required: false,
5687
+ skipCheck: true
5688
+ }
5689
+ },
5690
+ setup(__props, { expose: __expose }) {
5691
+ __expose();
5692
+ const __returned__ = {
5693
+ get cn() {
5694
+ return cn;
5695
+ },
5696
+ ASkeletonImage: ASkeletonImage_default,
5697
+ ASkeletonHeading: ASkeletonHeading_default,
5698
+ ASkeletonText: ASkeletonText_default,
5699
+ ASkeletonButton: ASkeletonButton_default,
5700
+ ASkeletonAvatar: ASkeletonAvatar_default
5701
+ };
5702
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5703
+ enumerable: false,
5704
+ value: true
5705
+ });
5706
+ return __returned__;
5707
+ }
5708
+ });
5709
+ const _hoisted_1$1 = {
5710
+ key: 2,
5711
+ class: "mt-2 flex gap-2"
5712
+ };
5713
+ const _hoisted_2 = {
5714
+ key: 3,
5715
+ class: "mt-3 flex items-center gap-3"
5716
+ };
5717
+ const _hoisted_3 = { style: { "flex": "1" } };
5718
+ function _sfc_render$6(_ctx, _cache, $props, $setup, $data, $options) {
5719
+ return openBlock(), createElementBlock("div", {
5720
+ class: normalizeClass($setup.cn("a-skel-variant-card", _ctx.$props.class)),
5721
+ role: "status",
5722
+ "aria-busy": "true"
5723
+ }, [
5724
+ _ctx.$props.media ? (openBlock(), createBlock($setup["ASkeletonImage"], {
5725
+ key: 0,
5726
+ animation: _ctx.$props.animation
5727
+ }, null, 8, ["animation"])) : createCommentVNode("v-if", true),
5728
+ _ctx.$props.heading ? (openBlock(), createBlock($setup["ASkeletonHeading"], {
5729
+ key: 1,
5730
+ level: 3,
5731
+ animation: _ctx.$props.animation
5732
+ }, null, 8, ["animation"])) : createCommentVNode("v-if", true),
5733
+ createVNode($setup["ASkeletonText"], {
5734
+ lines: _ctx.$props.lines,
5735
+ animation: _ctx.$props.animation
5736
+ }, null, 8, ["lines", "animation"]),
5737
+ _ctx.$props.actions ? (openBlock(), createElementBlock("div", _hoisted_1$1, [createVNode($setup["ASkeletonButton"], {
5738
+ width: 96,
5739
+ height: 36,
5740
+ animation: _ctx.$props.animation
5741
+ }, null, 8, ["animation"]), createVNode($setup["ASkeletonButton"], {
5742
+ width: 96,
5743
+ height: 36,
5744
+ outlined: "",
5745
+ animation: _ctx.$props.animation
5746
+ }, null, 8, ["animation"])])) : createCommentVNode("v-if", true),
5747
+ _ctx.$props.footerAvatar ? (openBlock(), createElementBlock("div", _hoisted_2, [createVNode($setup["ASkeletonAvatar"], {
5748
+ size: 36,
5749
+ animation: _ctx.$props.animation
5750
+ }, null, 8, ["animation"]), createElementVNode("div", _hoisted_3, [createVNode($setup["ASkeletonText"], {
5751
+ lines: 2,
5752
+ animation: _ctx.$props.animation
5753
+ }, null, 8, ["animation"])])])) : createCommentVNode("v-if", true),
5754
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading card…", -1))
5755
+ ], 2);
5756
+ }
5757
+ var ASkeletonCard_default = /* @__PURE__ */ export_helper_default(_sfc_main$6, [["render", _sfc_render$6], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonCard.vue"]]);
5758
+ //#endregion
5759
+ //#region src/components/variants/ASkeletonTable.vue
5760
+ const _sfc_main$5 = /* @__PURE__ */ defineComponent({
5761
+ __name: "ASkeletonTable",
5762
+ props: {
5763
+ rows: {
5764
+ type: Number,
5765
+ required: false,
5766
+ default: 5
5767
+ },
5768
+ columns: {
5769
+ type: Number,
5770
+ required: false,
5771
+ default: 4
5772
+ },
5773
+ showHeader: {
5774
+ type: Boolean,
5775
+ required: false,
5776
+ default: true
5777
+ },
5778
+ animation: {
5779
+ type: String,
5780
+ required: false,
5781
+ default: "pulse"
5782
+ },
5783
+ class: {
5784
+ type: [
5785
+ Boolean,
5786
+ null,
5787
+ String,
5788
+ Object,
5789
+ Array
5790
+ ],
5791
+ required: false,
5792
+ skipCheck: true
5793
+ }
5794
+ },
5795
+ setup(__props, { expose: __expose }) {
5796
+ __expose();
5797
+ const props = __props;
5798
+ const __returned__ = {
5799
+ props,
5800
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
5801
+ rowStyle: computed(() => ({ gridTemplateColumns: `repeat(${props.columns}, minmax(0, 1fr))` })),
5802
+ get cn() {
5803
+ return cn;
5804
+ }
5805
+ };
5806
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5807
+ enumerable: false,
5808
+ value: true
5809
+ });
5810
+ return __returned__;
5811
+ }
5812
+ });
5813
+ function _sfc_render$5(_ctx, _cache, $props, $setup, $data, $options) {
5814
+ return openBlock(), createElementBlock("div", {
5815
+ class: normalizeClass($setup.cn("a-skel-variant-table", $setup.props.class)),
5816
+ role: "status",
5817
+ "aria-busy": "true"
5818
+ }, [
5819
+ $setup.props.showHeader ? (openBlock(), createElementBlock("div", {
5820
+ key: 0,
5821
+ class: "a-skel-variant-table__row",
5822
+ style: normalizeStyle($setup.rowStyle)
5823
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.columns, (c) => {
5824
+ return openBlock(), createElementBlock("div", {
5825
+ key: `h-${c}`,
5826
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-heading", $setup.animClass)),
5827
+ style: {
5828
+ height: "1.5rem",
5829
+ width: "70%"
5830
+ }
5831
+ }, null, 2);
5832
+ }), 128))], 4)) : createCommentVNode("v-if", true),
5833
+ (openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.rows, (r) => {
5834
+ return openBlock(), createElementBlock("div", {
5835
+ key: r,
5836
+ class: "a-skel-variant-table__row",
5837
+ style: normalizeStyle($setup.rowStyle)
5838
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.columns, (c) => {
5839
+ return openBlock(), createElementBlock("div", {
5840
+ key: `${r}-${c}`,
5841
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-text", $setup.animClass)),
5842
+ style: normalizeStyle({ width: c === 1 ? "85%" : c === $setup.props.columns ? "50%" : "70%" })
5843
+ }, null, 6);
5844
+ }), 128))], 4);
5845
+ }), 128)),
5846
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading table…", -1))
5847
+ ], 2);
5848
+ }
5849
+ var ASkeletonTable_default = /* @__PURE__ */ export_helper_default(_sfc_main$5, [["render", _sfc_render$5], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonTable.vue"]]);
5850
+ //#endregion
5851
+ //#region src/components/variants/ASkeletonChart.vue
5852
+ const _sfc_main$4 = /* @__PURE__ */ defineComponent({
5853
+ __name: "ASkeletonChart",
5854
+ props: {
5855
+ bars: {
5856
+ type: Number,
5857
+ required: false,
5858
+ default: 7
5859
+ },
5860
+ height: {
5861
+ type: [Number, String],
5862
+ required: false,
5863
+ default: "8rem"
5864
+ },
5865
+ showHeader: {
5866
+ type: Boolean,
5867
+ required: false,
5868
+ default: true
5869
+ },
5870
+ animation: {
5871
+ type: String,
5872
+ required: false,
5873
+ default: "pulse"
5874
+ },
5875
+ class: {
5876
+ type: [
5877
+ Boolean,
5878
+ null,
5879
+ String,
5880
+ Object,
5881
+ Array
5882
+ ],
5883
+ required: false,
5884
+ skipCheck: true
5885
+ }
5886
+ },
5887
+ setup(__props, { expose: __expose }) {
5888
+ __expose();
5889
+ const props = __props;
5890
+ const animClass = computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`);
5891
+ const chartStyle = computed(() => ({ height: typeof props.height === "number" ? `${props.height}px` : String(props.height) }));
5892
+ const HEIGHTS = [
5893
+ 62,
5894
+ 78,
5895
+ 45,
5896
+ 91,
5897
+ 68,
5898
+ 82,
5899
+ 55,
5900
+ 73,
5901
+ 39,
5902
+ 88,
5903
+ 60,
5904
+ 74
5905
+ ];
5906
+ function barHeight(i) {
5907
+ return `${HEIGHTS[i % HEIGHTS.length]}%`;
5908
+ }
5909
+ const __returned__ = {
5910
+ props,
5911
+ animClass,
5912
+ chartStyle,
5913
+ HEIGHTS,
5914
+ barHeight,
5915
+ get cn() {
5916
+ return cn;
5917
+ },
5918
+ ASkeletonHeading: ASkeletonHeading_default,
5919
+ ASkeletonText: ASkeletonText_default
5920
+ };
5921
+ Object.defineProperty(__returned__, "__isScriptSetup", {
5922
+ enumerable: false,
5923
+ value: true
5924
+ });
5925
+ return __returned__;
5926
+ }
5927
+ });
5928
+ const _hoisted_1 = { style: { "margin-top": "0.4rem" } };
5929
+ function _sfc_render$4(_ctx, _cache, $props, $setup, $data, $options) {
5930
+ return openBlock(), createElementBlock("div", {
5931
+ class: normalizeClass($setup.cn($setup.props.class)),
5932
+ role: "status",
5933
+ "aria-busy": "true"
5934
+ }, [
5935
+ $setup.props.showHeader ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createVNode($setup["ASkeletonHeading"], {
5936
+ level: 4,
5937
+ animation: $setup.props.animation,
5938
+ width: "45%"
5939
+ }, null, 8, ["animation"]), createElementVNode("div", _hoisted_1, [createVNode($setup["ASkeletonText"], {
5940
+ lines: 1,
5941
+ animation: $setup.props.animation,
5942
+ width: "60%"
5943
+ }, null, 8, ["animation"])])], 64)) : createCommentVNode("v-if", true),
5944
+ createElementVNode("div", {
5945
+ class: "a-skel-variant-chart",
5946
+ style: normalizeStyle([$setup.chartStyle, { "margin-top": "1rem" }])
5947
+ }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.bars, (i) => {
5948
+ return openBlock(), createElementBlock("div", {
5949
+ key: i,
5950
+ class: normalizeClass($setup.cn("a-skel a-skel-variant-chart__bar", $setup.animClass)),
5951
+ style: normalizeStyle({ height: $setup.barHeight(i - 1) })
5952
+ }, null, 6);
5953
+ }), 128))], 4),
5954
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading chart…", -1))
5955
+ ], 2);
5956
+ }
5957
+ var ASkeletonChart_default = /* @__PURE__ */ export_helper_default(_sfc_main$4, [["render", _sfc_render$4], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonChart.vue"]]);
5958
+ //#endregion
5959
+ //#region src/components/variants/ASkeletonForm.vue
5960
+ const _sfc_main$3 = /* @__PURE__ */ defineComponent({
5961
+ __name: "ASkeletonForm",
5962
+ props: {
5963
+ fields: {
5964
+ type: Number,
5965
+ required: false,
5966
+ default: 3
5967
+ },
5968
+ showSubmit: {
5969
+ type: Boolean,
5970
+ required: false,
5971
+ default: true
5972
+ },
5973
+ animation: {
5974
+ type: String,
5975
+ required: false,
5976
+ default: "pulse"
5977
+ },
5978
+ class: {
5979
+ type: [
5980
+ Boolean,
5981
+ null,
5982
+ String,
5983
+ Object,
5984
+ Array
5985
+ ],
5986
+ required: false,
5987
+ skipCheck: true
5988
+ }
5989
+ },
5990
+ setup(__props, { expose: __expose }) {
5991
+ __expose();
5992
+ const __returned__ = {
5993
+ get cn() {
5994
+ return cn;
5995
+ },
5996
+ ASkeletonText: ASkeletonText_default,
5997
+ ASkeletonInput: ASkeletonInput_default,
5998
+ ASkeletonButton: ASkeletonButton_default
5999
+ };
6000
+ Object.defineProperty(__returned__, "__isScriptSetup", {
6001
+ enumerable: false,
6002
+ value: true
6003
+ });
6004
+ return __returned__;
6005
+ }
6006
+ });
6007
+ function _sfc_render$3(_ctx, _cache, $props, $setup, $data, $options) {
6008
+ return openBlock(), createElementBlock("div", {
6009
+ class: normalizeClass($setup.cn("flex flex-col gap-4", _ctx.$props.class)),
6010
+ role: "status",
6011
+ "aria-busy": "true"
6012
+ }, [
6013
+ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.$props.fields, (i) => {
6014
+ return openBlock(), createElementBlock("div", {
6015
+ key: i,
6016
+ class: "flex flex-col gap-2"
6017
+ }, [createVNode($setup["ASkeletonText"], {
6018
+ lines: 1,
6019
+ width: "30%",
6020
+ animation: _ctx.$props.animation
6021
+ }, null, 8, ["animation"]), createVNode($setup["ASkeletonInput"], { animation: _ctx.$props.animation }, null, 8, ["animation"])]);
6022
+ }), 128)),
6023
+ _ctx.$props.showSubmit ? (openBlock(), createBlock($setup["ASkeletonButton"], {
6024
+ key: 0,
6025
+ width: 120,
6026
+ height: 40,
6027
+ animation: _ctx.$props.animation
6028
+ }, null, 8, ["animation"])) : createCommentVNode("v-if", true),
6029
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading form…", -1))
6030
+ ], 2);
6031
+ }
6032
+ var ASkeletonForm_default = /* @__PURE__ */ export_helper_default(_sfc_main$3, [["render", _sfc_render$3], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonForm.vue"]]);
6033
+ //#endregion
6034
+ //#region src/components/variants/ASkeletonArticle.vue
6035
+ const _sfc_main$2 = /* @__PURE__ */ defineComponent({
6036
+ __name: "ASkeletonArticle",
6037
+ props: {
6038
+ media: {
6039
+ type: Boolean,
6040
+ required: false,
6041
+ default: true
6042
+ },
6043
+ paragraphs: {
6044
+ type: Number,
6045
+ required: false,
6046
+ default: 3
6047
+ },
6048
+ linesPerParagraph: {
6049
+ type: Number,
6050
+ required: false,
6051
+ default: 4
6052
+ },
6053
+ animation: {
6054
+ type: String,
6055
+ required: false,
6056
+ default: "pulse"
6057
+ },
6058
+ class: {
6059
+ type: [
6060
+ Boolean,
6061
+ null,
6062
+ String,
6063
+ Object,
6064
+ Array
6065
+ ],
6066
+ required: false,
6067
+ skipCheck: true
6068
+ }
6069
+ },
6070
+ setup(__props, { expose: __expose }) {
6071
+ __expose();
6072
+ const __returned__ = {
6073
+ get cn() {
6074
+ return cn;
6075
+ },
6076
+ ASkeletonHeading: ASkeletonHeading_default,
6077
+ ASkeletonText: ASkeletonText_default,
6078
+ ASkeletonImage: ASkeletonImage_default
6079
+ };
6080
+ Object.defineProperty(__returned__, "__isScriptSetup", {
6081
+ enumerable: false,
6082
+ value: true
6083
+ });
6084
+ return __returned__;
6085
+ }
6086
+ });
6087
+ function _sfc_render$2(_ctx, _cache, $props, $setup, $data, $options) {
6088
+ return openBlock(), createElementBlock("article", {
6089
+ class: normalizeClass($setup.cn("flex flex-col gap-4", _ctx.$props.class)),
6090
+ role: "status",
6091
+ "aria-busy": "true"
6092
+ }, [
6093
+ createVNode($setup["ASkeletonHeading"], {
6094
+ level: 1,
6095
+ animation: _ctx.$props.animation
6096
+ }, null, 8, ["animation"]),
6097
+ _ctx.$props.media ? (openBlock(), createBlock($setup["ASkeletonImage"], {
6098
+ key: 0,
6099
+ animation: _ctx.$props.animation
6100
+ }, null, 8, ["animation"])) : createCommentVNode("v-if", true),
6101
+ (openBlock(true), createElementBlock(Fragment, null, renderList(_ctx.$props.paragraphs, (i) => {
6102
+ return openBlock(), createElementBlock("div", { key: i }, [createVNode($setup["ASkeletonText"], {
6103
+ lines: _ctx.$props.linesPerParagraph,
6104
+ animation: _ctx.$props.animation
6105
+ }, null, 8, ["lines", "animation"])]);
6106
+ }), 128)),
6107
+ _cache[0] || (_cache[0] = createElementVNode("span", { class: "a-skel-sr-only" }, "Loading article…", -1))
6108
+ ], 2);
6109
+ }
6110
+ var ASkeletonArticle_default = /* @__PURE__ */ export_helper_default(_sfc_main$2, [["render", _sfc_render$2], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonArticle.vue"]]);
6111
+ //#endregion
6112
+ //#region src/components/variants/ASkeletonDivider.vue
6113
+ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
6114
+ __name: "ASkeletonDivider",
6115
+ props: {
6116
+ thickness: {
6117
+ type: Number,
6118
+ required: false,
6119
+ default: 1
6120
+ },
6121
+ animation: {
6122
+ type: String,
6123
+ required: false,
6124
+ default: "pulse"
6125
+ },
6126
+ class: {
6127
+ type: [
6128
+ Boolean,
6129
+ null,
6130
+ String,
6131
+ Object,
6132
+ Array
6133
+ ],
6134
+ required: false,
6135
+ skipCheck: true
6136
+ }
6137
+ },
6138
+ setup(__props, { expose: __expose }) {
6139
+ __expose();
6140
+ const props = __props;
6141
+ const __returned__ = {
6142
+ props,
6143
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
6144
+ rootStyle: computed(() => ({
6145
+ height: `${props.thickness}px`,
6146
+ width: "100%"
6147
+ })),
6148
+ get cn() {
6149
+ return cn;
6150
+ }
6151
+ };
6152
+ Object.defineProperty(__returned__, "__isScriptSetup", {
6153
+ enumerable: false,
6154
+ value: true
6155
+ });
6156
+ return __returned__;
6157
+ }
6158
+ });
6159
+ function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
6160
+ return openBlock(), createElementBlock("div", {
6161
+ class: normalizeClass($setup.cn("a-skel", $setup.animClass, $setup.props.class)),
6162
+ style: normalizeStyle($setup.rootStyle),
6163
+ role: "separator",
6164
+ "aria-hidden": "true"
6165
+ }, null, 6);
6166
+ }
6167
+ var ASkeletonDivider_default = /* @__PURE__ */ export_helper_default(_sfc_main$1, [["render", _sfc_render$1], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonDivider.vue"]]);
6168
+ //#endregion
6169
+ //#region src/components/variants/ASkeletonChip.vue
6170
+ const _sfc_main = /* @__PURE__ */ defineComponent({
6171
+ __name: "ASkeletonChip",
6172
+ props: {
6173
+ width: {
6174
+ type: [Number, String],
6175
+ required: false,
6176
+ default: 80
6177
+ },
6178
+ height: {
6179
+ type: [Number, String],
6180
+ required: false,
6181
+ default: 24
6182
+ },
6183
+ animation: {
6184
+ type: String,
6185
+ required: false,
6186
+ default: "pulse"
6187
+ },
6188
+ class: {
6189
+ type: [
6190
+ Boolean,
6191
+ null,
6192
+ String,
6193
+ Object,
6194
+ Array
6195
+ ],
6196
+ required: false,
6197
+ skipCheck: true
6198
+ }
6199
+ },
6200
+ setup(__props, { expose: __expose }) {
6201
+ __expose();
6202
+ const props = __props;
6203
+ const __returned__ = {
6204
+ props,
6205
+ animClass: computed(() => props.animation === "none" ? null : `a-skel-anim-${props.animation}`),
6206
+ rootStyle: computed(() => ({
6207
+ width: typeof props.width === "number" ? `${props.width}px` : String(props.width),
6208
+ height: typeof props.height === "number" ? `${props.height}px` : String(props.height),
6209
+ borderRadius: "9999px",
6210
+ display: "inline-block"
6211
+ })),
6212
+ get cn() {
6213
+ return cn;
6214
+ }
6215
+ };
6216
+ Object.defineProperty(__returned__, "__isScriptSetup", {
6217
+ enumerable: false,
6218
+ value: true
6219
+ });
6220
+ return __returned__;
6221
+ }
6222
+ });
6223
+ function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
6224
+ return openBlock(), createElementBlock("span", {
6225
+ class: normalizeClass($setup.cn("a-skel", $setup.animClass, $setup.props.class)),
6226
+ style: normalizeStyle($setup.rootStyle),
6227
+ role: "status",
6228
+ "aria-busy": "true"
6229
+ }, null, 6);
6230
+ }
6231
+ var ASkeletonChip_default = /* @__PURE__ */ export_helper_default(_sfc_main, [["render", _sfc_render], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/variants/ASkeletonChip.vue"]]);
6232
+ //#endregion
6233
+ //#region src/utils/walkDom.ts
6234
+ const DEFAULT_MAX_NODES$1 = 500;
6235
+ const DEFAULT_MIN_SIZE$1 = 4;
6236
+ const LEAF_TAGS = new Set([
6237
+ "IMG",
6238
+ "SVG",
6239
+ "CANVAS",
6240
+ "VIDEO",
6241
+ "INPUT",
6242
+ "TEXTAREA",
6243
+ "SELECT",
6244
+ "BUTTON",
6245
+ "PROGRESS",
6246
+ "METER",
6247
+ "HR"
6248
+ ]);
6249
+ /**
6250
+ * Walk `root`'s descendants and produce a list of shimmer blocks that mirror its
6251
+ * rendered layout. Coordinates are relative to `root`'s top-left so the result can
6252
+ * be replayed in any container of the same size.
6253
+ *
6254
+ * Performance:
6255
+ * - `maxNodes` caps the walk so a 5000-row table doesn't lock up the main thread.
6256
+ * - `minSize` filters out hairlines (1-2 px paddings, decorative dots) that
6257
+ * inflate node count without adding visual signal.
6258
+ * - All `getBoundingClientRect` / `getComputedStyle` reads happen in a single
6259
+ * top-down pass with no intervening writes, so the browser does one layout
6260
+ * up front and serves cached values from then on (no layout thrashing).
6261
+ * - Each emitted `ShapeNode` has a frozen pre-computed `style` (and `lineStyles`
6262
+ * for multi-line text) so the render path is allocation-free.
6263
+ */
6264
+ function walkDom(root, options) {
6265
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES$1;
6266
+ const minSize = options.minSize ?? DEFAULT_MIN_SIZE$1;
6267
+ const nodes = [];
6268
+ const rootRect = root.getBoundingClientRect();
6269
+ let truncated = false;
6270
+ function visit(el, depth) {
6271
+ if (nodes.length >= maxNodes) {
6272
+ truncated = true;
6273
+ return;
6274
+ }
6275
+ const html = el;
6276
+ if (html.dataset?.skeletonIgnore !== void 0) return;
6277
+ const cs = window.getComputedStyle(el);
6278
+ if (cs.display === "none" || cs.visibility === "hidden" || cs.opacity === "0") return;
6279
+ const rect = el.getBoundingClientRect();
6280
+ if (rect.width < minSize || rect.height < minSize) return;
6281
+ const tag = el.tagName.toUpperCase();
6282
+ const isLeafTag = LEAF_TAGS.has(tag);
6283
+ const hasStop = html.dataset?.skeletonStop !== void 0;
6284
+ const childElements = [];
6285
+ for (let i = 0; i < el.children.length; i++) {
6286
+ const c = el.children[i];
6287
+ if (c.dataset?.skeletonIgnore === void 0) childElements.push(c);
6288
+ }
6289
+ const hasOwnText = hasDirectTextContent(el);
6290
+ const reachedDepth = depth >= options.maxDepth;
6291
+ if (isLeafTag || hasStop || reachedDepth || childElements.length === 0) {
6292
+ const node = elementToShape(el, tag, cs, rect, rootRect, hasOwnText);
6293
+ if (node) nodes.push(node);
6294
+ return;
6295
+ }
6296
+ const containerNode = containerSurfaceToShape(cs, rect, rootRect);
6297
+ if (containerNode) nodes.push(containerNode);
6298
+ for (let i = 0; i < childElements.length; i++) {
6299
+ if (nodes.length >= maxNodes) {
6300
+ truncated = true;
6301
+ return;
6302
+ }
6303
+ visit(childElements[i], depth + 1);
6304
+ }
6305
+ }
6306
+ for (let i = 0; i < root.children.length; i++) {
6307
+ if (nodes.length >= maxNodes) {
6308
+ truncated = true;
6309
+ break;
6310
+ }
6311
+ visit(root.children[i], 1);
6312
+ }
6313
+ return Object.freeze({
6314
+ nodes: Object.freeze(nodes),
6315
+ width: Math.round(rootRect.width),
6316
+ height: Math.round(rootRect.height),
6317
+ truncated
6318
+ });
6319
+ }
6320
+ function hasDirectTextContent(el) {
6321
+ for (let i = 0; i < el.childNodes.length; i++) {
6322
+ const node = el.childNodes[i];
6323
+ if (node.nodeType === Node.TEXT_NODE && (node.textContent ?? "").trim().length > 0) return true;
6324
+ }
6325
+ return false;
6326
+ }
6327
+ /**
6328
+ * Emit a backing block for a container with its own visible surface — real
6329
+ * background, border, box-shadow, or non-full opacity. Geometry is the
6330
+ * container's exact bounding rect so the width / height / radius match the
6331
+ * real DOM 1:1. Returns `null` when the container has no visible surface
6332
+ * (a plain unstyled `<div>` is layout-only and shouldn't add a block).
6333
+ */
6334
+ function containerSurfaceToShape(cs, rect, origin) {
6335
+ const bg = readBackgroundColor(cs);
6336
+ const border = readBorder(cs);
6337
+ const boxShadow = readBoxShadow(cs);
6338
+ const opacity = readOpacity(cs);
6339
+ if (!bg && !border && !boxShadow && opacity === void 0) return null;
6340
+ return freezeShape({
6341
+ type: "block",
6342
+ x: Math.round(rect.left - origin.left),
6343
+ y: Math.round(rect.top - origin.top),
6344
+ w: Math.round(rect.width),
6345
+ h: Math.round(rect.height),
6346
+ radius: parseFloat(cs.borderRadius) || 0,
6347
+ bg,
6348
+ border,
6349
+ boxShadow,
6350
+ opacity
6351
+ });
6352
+ }
6353
+ function elementToShape(el, tag, cs, rect, origin, hasText) {
6354
+ const x = Math.round(rect.left - origin.left);
6355
+ const y = Math.round(rect.top - origin.top);
6356
+ const w = Math.round(rect.width);
6357
+ const h = Math.round(rect.height);
6358
+ const radius = parseFloat(cs.borderRadius) || 0;
6359
+ const minDim = Math.min(w, h);
6360
+ const isCircle = radius >= minDim / 2 - 1 && Math.abs(w - h) <= 2 && minDim > 0;
6361
+ let type;
6362
+ let resolvedRadius = radius;
6363
+ let lines;
6364
+ let lineHeight;
6365
+ let textRects;
6366
+ let textAlign;
6367
+ if (tag === "IMG" || tag === "SVG" || tag === "VIDEO" || tag === "CANVAS") type = "image";
6368
+ else if (isCircle) {
6369
+ type = "circle";
6370
+ resolvedRadius = Math.floor(minDim / 2);
6371
+ } else if (hasText) {
6372
+ type = "text";
6373
+ lineHeight = Math.round(parseFloat(cs.lineHeight) || parseFloat(cs.fontSize) * 1.4 || 16);
6374
+ lines = Math.max(1, Math.round(h / lineHeight));
6375
+ resolvedRadius = Math.min(radius, 4);
6376
+ textAlign = readTextAlign(cs);
6377
+ textRects = measureTextRects(el, origin);
6378
+ } else type = "block";
6379
+ const bg = readBackgroundColor(cs);
6380
+ const border = readBorder(cs);
6381
+ const boxShadow = readBoxShadow(cs);
6382
+ const opacity = readOpacity(cs);
6383
+ return freezeShape({
6384
+ type,
6385
+ x,
6386
+ y,
6387
+ w,
6388
+ h,
6389
+ radius: resolvedRadius,
6390
+ lines,
6391
+ lineHeight,
6392
+ textRects,
6393
+ bg,
6394
+ border,
6395
+ boxShadow,
6396
+ opacity,
6397
+ textAlign
6398
+ });
6399
+ }
6400
+ /**
6401
+ * Per-line text rects via `Range.getClientRects()`. Returns one rect per visual
6402
+ * line of rendered text — exact left/width for each line so wrapped paragraphs,
6403
+ * RTL last-line position, centered headings all replay 1:1 without heuristics.
6404
+ * Returns `undefined` if the element has no direct text content or the Range
6405
+ * API isn't usable in this environment.
6406
+ */
6407
+ function measureTextRects(el, origin) {
6408
+ if (typeof document === "undefined" || typeof document.createRange !== "function") return void 0;
6409
+ let range;
6410
+ try {
6411
+ range = document.createRange();
6412
+ range.selectNodeContents(el);
6413
+ } catch {
6414
+ return;
6415
+ }
6416
+ const rects = range.getClientRects();
6417
+ if (!rects || rects.length === 0) return void 0;
6418
+ const merged = [];
6419
+ for (let i = 0; i < rects.length; i++) {
6420
+ const r = rects[i];
6421
+ if (r.width <= 0 || r.height <= 0) continue;
6422
+ const lr = {
6423
+ x: Math.round(r.left - origin.left),
6424
+ y: Math.round(r.top - origin.top),
6425
+ w: Math.round(r.width),
6426
+ h: Math.round(r.height)
6427
+ };
6428
+ const last = merged[merged.length - 1];
6429
+ if (last && Math.abs(last.y - lr.y) <= 1 && Math.abs(last.h - lr.h) <= 1 && Math.max(last.x, lr.x) - Math.min(last.x + last.w, lr.x + lr.w) <= 2) {
6430
+ const leftEdge = Math.min(last.x, lr.x);
6431
+ const rightEdge = Math.max(last.x + last.w, lr.x + lr.w);
6432
+ last.x = leftEdge;
6433
+ last.w = rightEdge - leftEdge;
6434
+ } else merged.push(lr);
6435
+ }
6436
+ return merged.length > 0 ? merged : void 0;
6437
+ }
6438
+ const TRANSPARENT_RE = /^(transparent|rgba?\([^)]*,\s*0(\.0+)?\s*\)|hsla?\([^)]*,\s*0%?\s*\))$/i;
6439
+ function readBackgroundColor(cs) {
6440
+ const bg = cs.backgroundColor;
6441
+ if (!bg || TRANSPARENT_RE.test(bg)) return void 0;
6442
+ return bg;
6443
+ }
6444
+ function readBorder(cs) {
6445
+ const width = parseFloat(cs.borderTopWidth) || 0;
6446
+ if (width < .5) return void 0;
6447
+ const style = cs.borderTopStyle;
6448
+ if (!style || style === "none" || style === "hidden") return void 0;
6449
+ const color = cs.borderTopColor;
6450
+ if (!color || TRANSPARENT_RE.test(color)) return void 0;
6451
+ return `${Math.round(width)}px ${style} ${color}`;
6452
+ }
6453
+ function readBoxShadow(cs) {
6454
+ const sh = cs.boxShadow;
6455
+ if (!sh || sh === "none") return void 0;
6456
+ return sh;
6457
+ }
6458
+ function readOpacity(cs) {
6459
+ const o = parseFloat(cs.opacity);
6460
+ if (!Number.isFinite(o) || o >= 1) return void 0;
6461
+ if (o <= 0) return void 0;
6462
+ return Math.round(o * 100) / 100;
6463
+ }
6464
+ function readTextAlign(cs) {
6465
+ const ta = cs.textAlign;
6466
+ if (!ta) return void 0;
6467
+ if (ta === "left" || ta === "right" || ta === "center" || ta === "justify" || ta === "start" || ta === "end") return ta;
6468
+ }
6469
+ /**
6470
+ * Pre-compute (and freeze) the inline styles used at render time. Doing it once
6471
+ * here means rendering 500 blocks doesn't allocate 500 style objects per frame.
6472
+ *
6473
+ * Captured visual signals (bg, border, shadow, opacity) are merged into the
6474
+ * frozen style so the replay carries the real DOM's surface — a white button
6475
+ * stays white, a ring-bordered button keeps its ring, a shadowed card keeps
6476
+ * its elevation.
6477
+ */
6478
+ function freezeShape(node) {
6479
+ const baseStyle = {
6480
+ left: `${node.x}px`,
6481
+ top: `${node.y}px`,
6482
+ width: `${node.w}px`,
6483
+ height: `${node.h}px`,
6484
+ borderRadius: `${node.radius}px`
6485
+ };
6486
+ applyVisualSignals$1(baseStyle, node);
6487
+ const style = Object.freeze(baseStyle);
6488
+ let lineStyles;
6489
+ if (node.type === "text" && node.textRects && node.textRects.length > 0) {
6490
+ const radiusStr = `${node.radius}px`;
6491
+ const arr = [];
6492
+ for (const r of node.textRects) {
6493
+ const lineStyle = {
6494
+ left: `${r.x}px`,
6495
+ top: `${r.y}px`,
6496
+ width: `${r.w}px`,
6497
+ height: `${r.h}px`,
6498
+ borderRadius: radiusStr
6499
+ };
6500
+ applyVisualSignals$1(lineStyle, node);
6501
+ arr.push(Object.freeze(lineStyle));
6502
+ }
6503
+ lineStyles = Object.freeze(arr);
6504
+ } else if (node.type === "text" && node.lines && node.lines > 1) {
6505
+ const lh = node.lineHeight ?? Math.round(node.h / node.lines);
6506
+ const barHeight = Math.max(8, Math.round(lh * .7));
6507
+ const widthFull = node.w;
6508
+ const widthLast = Math.max(40, Math.round(node.w * .7));
6509
+ const heightStr = `${barHeight}px`;
6510
+ const radiusStr = `${node.radius}px`;
6511
+ const arr = [];
6512
+ for (let i = 1; i <= node.lines; i++) {
6513
+ const isLast = i === node.lines;
6514
+ const lineWidth = isLast ? widthLast : widthFull;
6515
+ let leftX = node.x;
6516
+ if (isLast && node.textAlign) {
6517
+ const slack = widthFull - lineWidth;
6518
+ if (node.textAlign === "center") leftX = node.x + Math.round(slack / 2);
6519
+ else if (node.textAlign === "right" || node.textAlign === "end") leftX = node.x + slack;
6520
+ }
6521
+ const lineStyle = {
6522
+ left: `${leftX}px`,
6523
+ top: `${node.y + (i - 1) * lh}px`,
6524
+ width: `${lineWidth}px`,
6525
+ height: heightStr,
6526
+ borderRadius: radiusStr
6527
+ };
6528
+ applyVisualSignals$1(lineStyle, node);
6529
+ arr.push(Object.freeze(lineStyle));
6530
+ }
6531
+ lineStyles = Object.freeze(arr);
6532
+ }
6533
+ return Object.freeze({
6534
+ type: node.type,
6535
+ x: node.x,
6536
+ y: node.y,
6537
+ w: node.w,
6538
+ h: node.h,
6539
+ radius: node.radius,
6540
+ lines: node.lines,
6541
+ lineHeight: node.lineHeight,
6542
+ textRects: node.textRects ? Object.freeze(node.textRects) : void 0,
6543
+ bg: node.bg,
6544
+ border: node.border,
6545
+ boxShadow: node.boxShadow,
6546
+ opacity: node.opacity,
6547
+ textAlign: node.textAlign,
6548
+ style,
6549
+ lineStyles
6550
+ });
6551
+ }
6552
+ /**
6553
+ * Merge captured surface signals into a style object in place. Each signal is
6554
+ * additive; `bg` uses `background` shorthand so it wipes the default linear
6555
+ * gradient on `.a-skel-block` cleanly.
6556
+ */
6557
+ function applyVisualSignals$1(out, node) {
6558
+ if (node.bg) out.background = node.bg;
6559
+ if (node.border) out.border = node.border;
6560
+ if (node.boxShadow) out.boxShadow = node.boxShadow;
6561
+ if (node.opacity !== void 0) out.opacity = node.opacity;
6562
+ }
6563
+ //#endregion
6564
+ //#region src/composables/useShapeProbe.ts
6565
+ const DEFAULT_RESIZE_DEBOUNCE_MS = 150;
6566
+ /**
6567
+ * Observe `getTarget()` and capture its rendered shape whenever the element
6568
+ * appears or resizes.
6569
+ *
6570
+ * Performance:
6571
+ * - Initial capture runs via `requestAnimationFrame` so it sneaks into the
6572
+ * first idle window after mount — no synchronous layout from inside the
6573
+ * render queue.
6574
+ * - Subsequent `ResizeObserver` callbacks are debounced (default 150 ms) so a
6575
+ * drag-resize doesn't trigger a fresh DOM walk per frame.
6576
+ * - The capture strategy itself enforces `maxNodes` so even a worst-case
6577
+ * capture (10k descendants) returns in bounded time.
6578
+ */
6579
+ function useShapeProbe(getTarget, options) {
6580
+ let observer;
6581
+ let frame;
6582
+ let timer;
6583
+ let hasCaptured = false;
6584
+ const debounceMs = options.resizeDebounceMs ?? DEFAULT_RESIZE_DEBOUNCE_MS;
6585
+ const captureFn = options.capture ?? walkDom;
6586
+ function cleanup() {
6587
+ if (observer) {
6588
+ observer.disconnect();
6589
+ observer = void 0;
6590
+ }
6591
+ if (frame !== void 0) {
6592
+ cancelAnimationFrame(frame);
6593
+ frame = void 0;
6594
+ }
6595
+ if (timer !== void 0) {
6596
+ clearTimeout(timer);
6597
+ timer = void 0;
6598
+ }
6599
+ }
6600
+ function capture(el) {
6601
+ const result = captureFn(el, {
6602
+ maxDepth: options.maxDepth,
6603
+ maxNodes: options.maxNodes,
6604
+ minSize: options.minSize
6605
+ });
6606
+ if (result.width > 0 && result.height > 0 && result.nodes.length > 0) {
6607
+ hasCaptured = true;
6608
+ options.onCapture(result);
6609
+ }
6610
+ }
6611
+ function scheduleImmediate(el) {
6612
+ if (frame !== void 0) cancelAnimationFrame(frame);
6613
+ frame = requestAnimationFrame(() => {
6614
+ frame = void 0;
6615
+ capture(el);
6616
+ });
6617
+ }
6618
+ function scheduleDebounced(el) {
6619
+ if (timer !== void 0) clearTimeout(timer);
6620
+ timer = setTimeout(() => {
6621
+ timer = void 0;
6622
+ capture(el);
6623
+ }, debounceMs);
6624
+ }
6625
+ watch(getTarget, (el) => {
6626
+ cleanup();
6627
+ hasCaptured = false;
6628
+ if (!el || typeof window === "undefined") return;
6629
+ scheduleImmediate(el);
6630
+ if (typeof ResizeObserver !== "undefined") {
6631
+ observer = new ResizeObserver(() => {
6632
+ if (hasCaptured) scheduleDebounced(el);
6633
+ });
6634
+ observer.observe(el);
6635
+ }
6636
+ }, {
6637
+ immediate: true,
6638
+ flush: "post"
6639
+ });
6640
+ onBeforeUnmount(cleanup);
6641
+ }
6642
+ //#endregion
6643
+ //#region src/composables/useSkeletonCache.ts
6644
+ const memory = /* @__PURE__ */ new Map();
6645
+ const STORAGE_PREFIX = "a-skeleton:";
6646
+ /**
6647
+ * Schema version for persisted flat `CachedShape` entries. Bump whenever the
6648
+ * `ShapeNode` / `CachedShape` field set changes so stale localStorage payloads
6649
+ * from older releases get dropped on read instead of rehydrating into a wrong
6650
+ * layout.
6651
+ *
6652
+ * v2 — added advanced surface signals (textRects, bg, border, boxShadow,
6653
+ * opacity, textAlign).
6654
+ */
6655
+ const SCHEMA_VERSION = 2;
6656
+ /**
6657
+ * Separate in-memory + localStorage namespace for the Recipe 3 structural
6658
+ * shape (`StructuralShape`, `v: 3`). Kept apart from the flat-shape cache so
6659
+ * the two pipelines can't collide on the same `cacheKey` — `<ASkeleton>`'s
6660
+ * legacy cache-replay path and Recipe 3 may both run on the same page with
6661
+ * the same key.
6662
+ */
6663
+ const structuralMemory = /* @__PURE__ */ new Map();
6664
+ const STRUCTURAL_PREFIX = "a-skeleton:s:";
6665
+ const STRUCTURAL_SCHEMA_VERSION = 3;
6666
+ /**
6667
+ * Lookup a captured shape by key. Reads in-memory first, then `localStorage` if
6668
+ * `persist` is enabled. SSR-safe — bypasses `window` access on the server.
6669
+ * Rehydrates pre-computed styles for shapes deserialized from older sessions
6670
+ * (Object.freeze + style/lineStyles don't survive `JSON.stringify` round-trip).
6671
+ * Drops the entry if it was written by a different schema version.
6672
+ */
6673
+ function getCached(key, persist) {
6674
+ const hit = memory.get(key);
6675
+ if (hit) return hit;
6676
+ if (!persist || typeof window === "undefined") return void 0;
6677
+ try {
6678
+ const storageKey = STORAGE_PREFIX + key;
6679
+ const raw = window.localStorage.getItem(storageKey);
6680
+ if (!raw) return void 0;
6681
+ const parsed = JSON.parse(raw);
6682
+ if (parsed.v !== SCHEMA_VERSION) {
6683
+ window.localStorage.removeItem(storageKey);
6684
+ return;
6685
+ }
6686
+ const rehydrated = rehydrateShape(parsed);
6687
+ memory.set(key, rehydrated);
6688
+ return rehydrated;
6689
+ } catch {
6690
+ return;
6691
+ }
6692
+ }
6693
+ /** Store a captured shape. `persist=true` mirrors to `localStorage`. */
6694
+ function setCached(key, value, persist) {
6695
+ memory.set(key, value);
6696
+ if (!persist || typeof window === "undefined") return;
6697
+ try {
6698
+ const lean = {
6699
+ v: SCHEMA_VERSION,
6700
+ width: value.width,
6701
+ height: value.height,
6702
+ nodes: leanNodes(value.nodes),
6703
+ truncated: value.truncated
6704
+ };
6705
+ window.localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(lean));
6706
+ } catch {}
6707
+ }
6708
+ /**
6709
+ * Drop a single entry (or all entries when no key) — wipes both the flat-shape
6710
+ * and the structural-shape namespaces. Exposed for tests + manual invalidation.
6711
+ */
6712
+ function clearCached(key) {
6713
+ if (!key) {
6714
+ memory.clear();
6715
+ structuralMemory.clear();
6716
+ if (typeof window === "undefined") return;
6717
+ try {
6718
+ const ls = window.localStorage;
6719
+ const stale = [];
6720
+ for (let i = 0; i < ls.length; i++) {
6721
+ const k = ls.key(i);
6722
+ if (!k) continue;
6723
+ if (k.startsWith(STORAGE_PREFIX) || k.startsWith(STRUCTURAL_PREFIX)) stale.push(k);
6724
+ }
6725
+ for (const k of stale) ls.removeItem(k);
6726
+ } catch {}
6727
+ return;
6728
+ }
6729
+ memory.delete(key);
6730
+ structuralMemory.delete(key);
6731
+ if (typeof window === "undefined") return;
6732
+ try {
6733
+ window.localStorage.removeItem(STORAGE_PREFIX + key);
6734
+ window.localStorage.removeItem(STRUCTURAL_PREFIX + key);
6735
+ } catch {}
6736
+ }
6737
+ /**
6738
+ * Drop a single structural-shape entry (or all when no key). Kept separate
6739
+ * from `clearCached` so callers that only manage structural shapes don't have
6740
+ * to reach across namespaces.
6741
+ */
6742
+ function clearCachedStructural(key) {
6743
+ if (!key) {
6744
+ structuralMemory.clear();
6745
+ if (typeof window === "undefined") return;
6746
+ try {
6747
+ const ls = window.localStorage;
6748
+ const stale = [];
6749
+ for (let i = 0; i < ls.length; i++) {
6750
+ const k = ls.key(i);
6751
+ if (k && k.startsWith(STRUCTURAL_PREFIX)) stale.push(k);
6752
+ }
6753
+ for (const k of stale) ls.removeItem(k);
6754
+ } catch {}
6755
+ return;
6756
+ }
6757
+ structuralMemory.delete(key);
6758
+ if (typeof window === "undefined") return;
6759
+ try {
6760
+ window.localStorage.removeItem(STRUCTURAL_PREFIX + key);
6761
+ } catch {}
6762
+ }
6763
+ /**
6764
+ * Lookup a structural shape by key. Reads in-memory first, then `localStorage`
6765
+ * when `persist` is enabled. Stale schema versions get purged on read.
6766
+ */
6767
+ function getCachedStructural(key, persist) {
6768
+ const hit = structuralMemory.get(key);
6769
+ if (hit) return hit;
6770
+ if (!persist || typeof window === "undefined") return void 0;
6771
+ try {
6772
+ const storageKey = STRUCTURAL_PREFIX + key;
6773
+ const raw = window.localStorage.getItem(storageKey);
6774
+ if (!raw) return void 0;
6775
+ const parsed = JSON.parse(raw);
6776
+ if (parsed.v !== STRUCTURAL_SCHEMA_VERSION) {
6777
+ window.localStorage.removeItem(storageKey);
6778
+ return;
6779
+ }
6780
+ const rehydrated = rehydrateStructuralShape(parsed);
6781
+ structuralMemory.set(key, rehydrated);
6782
+ return rehydrated;
6783
+ } catch {
6784
+ return;
6785
+ }
6786
+ }
6787
+ /** Store a structural shape. `persist=true` mirrors to `localStorage`. */
6788
+ function setCachedStructural(key, value, persist) {
6789
+ structuralMemory.set(key, value);
6790
+ if (!persist || typeof window === "undefined") return;
6791
+ try {
6792
+ const payload = {
6793
+ v: STRUCTURAL_SCHEMA_VERSION,
6794
+ width: value.width,
6795
+ height: value.height,
6796
+ nodes: value.nodes.map(leanStructuralNode),
6797
+ truncated: value.truncated,
6798
+ capturedAt: value.capturedAt
6799
+ };
6800
+ window.localStorage.setItem(STRUCTURAL_PREFIX + key, JSON.stringify(payload));
6801
+ } catch {}
6802
+ }
6803
+ function leanStructuralNode(node) {
6804
+ if (node.kind === "container") return {
6805
+ kind: "container",
6806
+ tag: node.tag,
6807
+ className: node.className,
6808
+ style: node.style,
6809
+ children: node.children.map(leanStructuralNode)
6810
+ };
6811
+ return {
6812
+ kind: "leaf",
6813
+ leafKind: node.leafKind,
6814
+ className: node.className,
6815
+ style: node.style,
6816
+ textLines: node.textLines
6817
+ };
6818
+ }
6819
+ function rehydrateStructuralShape(persisted) {
6820
+ return Object.freeze({
6821
+ v: STRUCTURAL_SCHEMA_VERSION,
6822
+ width: persisted.width,
6823
+ height: persisted.height,
6824
+ nodes: Object.freeze(persisted.nodes.map(rehydrateStructuralNode)),
6825
+ truncated: persisted.truncated,
6826
+ capturedAt: persisted.capturedAt
6827
+ });
6828
+ }
6829
+ function rehydrateStructuralNode(node) {
6830
+ if (node.kind === "container") return Object.freeze({
6831
+ kind: "container",
6832
+ tag: node.tag,
6833
+ className: node.className,
6834
+ style: Object.freeze({ ...node.style }),
6835
+ children: Object.freeze(node.children.map(rehydrateStructuralNode))
6836
+ });
6837
+ return Object.freeze({
6838
+ kind: "leaf",
6839
+ leafKind: node.leafKind,
6840
+ className: node.className ?? "",
6841
+ style: Object.freeze({ ...node.style }),
6842
+ textLines: node.textLines ? Object.freeze([...node.textLines]) : void 0
6843
+ });
6844
+ }
6845
+ /**
6846
+ * Rebuild `style` + `lineStyles` for nodes that lost them via serialization.
6847
+ * Walks the array in-place where possible and freezes the result so the render
6848
+ * path stays allocation-free.
6849
+ */
6850
+ function rehydrateShape(shape) {
6851
+ const nodes = shape.nodes.map((n) => freezeNodeStyles(n));
6852
+ return Object.freeze({
6853
+ nodes: Object.freeze(nodes),
6854
+ width: shape.width,
6855
+ height: shape.height,
6856
+ truncated: shape.truncated
6857
+ });
6858
+ }
6859
+ function leanNodes(nodes) {
6860
+ return nodes.map((n) => ({
6861
+ type: n.type,
6862
+ x: n.x,
6863
+ y: n.y,
6864
+ w: n.w,
6865
+ h: n.h,
6866
+ radius: n.radius,
6867
+ lines: n.lines,
6868
+ lineHeight: n.lineHeight,
6869
+ textRects: n.textRects ? n.textRects.map((r) => ({
6870
+ x: r.x,
6871
+ y: r.y,
6872
+ w: r.w,
6873
+ h: r.h
6874
+ })) : void 0,
6875
+ bg: n.bg,
6876
+ border: n.border,
6877
+ boxShadow: n.boxShadow,
6878
+ opacity: n.opacity,
6879
+ textAlign: n.textAlign
6880
+ }));
6881
+ }
6882
+ function freezeNodeStyles(node) {
6883
+ const baseStyle = {
6884
+ left: `${node.x}px`,
6885
+ top: `${node.y}px`,
6886
+ width: `${node.w}px`,
6887
+ height: `${node.h}px`,
6888
+ borderRadius: `${node.radius}px`
6889
+ };
6890
+ applyVisualSignals(baseStyle, node);
6891
+ const style = Object.freeze(baseStyle);
6892
+ let lineStyles;
6893
+ if (node.type === "text" && node.textRects && node.textRects.length > 0) {
6894
+ const radiusStr = `${node.radius}px`;
6895
+ const arr = [];
6896
+ for (const r of node.textRects) {
6897
+ const lineStyle = {
6898
+ left: `${r.x}px`,
6899
+ top: `${r.y}px`,
6900
+ width: `${r.w}px`,
6901
+ height: `${r.h}px`,
6902
+ borderRadius: radiusStr
6903
+ };
6904
+ applyVisualSignals(lineStyle, node);
6905
+ arr.push(Object.freeze(lineStyle));
6906
+ }
6907
+ lineStyles = Object.freeze(arr);
6908
+ } else if (node.type === "text" && node.lines && node.lines > 1) {
6909
+ const lh = node.lineHeight ?? Math.round(node.h / node.lines);
6910
+ const barHeight = Math.max(8, Math.round(lh * .7));
6911
+ const widthFull = node.w;
6912
+ const widthLast = Math.max(40, Math.round(node.w * .7));
6913
+ const heightStr = `${barHeight}px`;
6914
+ const radiusStr = `${node.radius}px`;
6915
+ const arr = [];
6916
+ for (let i = 1; i <= node.lines; i++) {
6917
+ const isLast = i === node.lines;
6918
+ const lineWidth = isLast ? widthLast : widthFull;
6919
+ let leftX = node.x;
6920
+ if (isLast && node.textAlign) {
6921
+ const slack = widthFull - lineWidth;
6922
+ if (node.textAlign === "center") leftX = node.x + Math.round(slack / 2);
6923
+ else if (node.textAlign === "right" || node.textAlign === "end") leftX = node.x + slack;
6924
+ }
6925
+ const lineStyle = {
6926
+ left: `${leftX}px`,
6927
+ top: `${node.y + (i - 1) * lh}px`,
6928
+ width: `${lineWidth}px`,
6929
+ height: heightStr,
6930
+ borderRadius: radiusStr
6931
+ };
6932
+ applyVisualSignals(lineStyle, node);
6933
+ arr.push(Object.freeze(lineStyle));
6934
+ }
6935
+ lineStyles = Object.freeze(arr);
4299
6936
  }
4300
- });
4301
- function _sfc_render$1(_ctx, _cache, $props, $setup, $data, $options) {
4302
- return $props.shape ? (openBlock(), createElementBlock("div", {
4303
- key: 0,
4304
- class: normalizeClass($setup.cn("a-skeleton__layer", $setup.props.class)),
4305
- style: normalizeStyle($setup.layerStyle),
4306
- role: "status",
4307
- "aria-live": "polite",
4308
- "aria-busy": "true"
4309
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList($props.shape.nodes, (node, idx) => {
4310
- return openBlock(), createElementBlock(Fragment, { key: idx }, [node.lineStyles ? (openBlock(true), createElementBlock(Fragment, { key: 0 }, renderList(node.lineStyles, (lineStyle, i) => {
4311
- return openBlock(), createElementBlock("div", {
4312
- key: `${idx}-${i}`,
4313
- class: normalizeClass($setup.blockClassByType.text),
4314
- style: normalizeStyle(lineStyle)
4315
- }, null, 6);
4316
- }), 128)) : (openBlock(), createElementBlock("div", {
4317
- key: 1,
4318
- class: normalizeClass($setup.blockClassByType[node.type]),
4319
- style: normalizeStyle(node.style)
4320
- }, null, 6))], 64);
4321
- }), 128))], 6)) : createCommentVNode("v-if", true);
6937
+ return Object.freeze({
6938
+ type: node.type,
6939
+ x: node.x,
6940
+ y: node.y,
6941
+ w: node.w,
6942
+ h: node.h,
6943
+ radius: node.radius,
6944
+ lines: node.lines,
6945
+ lineHeight: node.lineHeight,
6946
+ textRects: node.textRects ? Object.freeze(node.textRects) : void 0,
6947
+ bg: node.bg,
6948
+ border: node.border,
6949
+ boxShadow: node.boxShadow,
6950
+ opacity: node.opacity,
6951
+ textAlign: node.textAlign,
6952
+ style,
6953
+ lineStyles
6954
+ });
6955
+ }
6956
+ function applyVisualSignals(out, node) {
6957
+ if (node.bg) out.background = node.bg;
6958
+ if (node.border) out.border = node.border;
6959
+ if (node.boxShadow) out.boxShadow = node.boxShadow;
6960
+ if (node.opacity !== void 0) out.opacity = node.opacity;
4322
6961
  }
4323
- var ASkeletonLayer_default = /* @__PURE__ */ export_helper_default(_sfc_main$1, [["render", _sfc_render$1], ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeletonLayer.vue"]]);
4324
6962
  //#endregion
4325
- //#region src/components/ASkeletonBlock.vue
4326
- const _sfc_main = /* @__PURE__ */ defineComponent({
4327
- __name: "ASkeletonBlock",
4328
- props: {
4329
- type: {
4330
- type: String,
4331
- required: false,
4332
- default: "block"
4333
- },
4334
- w: {
4335
- type: [Number, String],
4336
- required: false
4337
- },
4338
- h: {
4339
- type: [Number, String],
4340
- required: false
4341
- },
4342
- radius: {
4343
- type: [Number, String],
4344
- required: false
4345
- },
4346
- lines: {
4347
- type: Number,
4348
- required: false,
4349
- default: 1
4350
- },
4351
- animation: {
4352
- type: String,
4353
- required: false,
4354
- default: "shimmer"
4355
- },
4356
- class: {
4357
- type: [
4358
- Boolean,
4359
- null,
4360
- String,
4361
- Object,
4362
- Array
4363
- ],
4364
- required: false,
4365
- skipCheck: true
6963
+ //#region src/utils/walkStructural.ts
6964
+ const DEFAULT_MAX_DEPTH$1 = 12;
6965
+ const DEFAULT_MAX_NODES = 500;
6966
+ const DEFAULT_MIN_SIZE = 4;
6967
+ /** Tags treated as atomic leaves — never recursed into, rendered as one block. */
6968
+ const ATOMIC_TAGS = new Set([
6969
+ "img",
6970
+ "picture",
6971
+ "svg",
6972
+ "canvas",
6973
+ "video",
6974
+ "audio",
6975
+ "input",
6976
+ "textarea",
6977
+ "select",
6978
+ "button",
6979
+ "progress",
6980
+ "meter",
6981
+ "hr",
6982
+ "iframe",
6983
+ "object",
6984
+ "embed",
6985
+ "br"
6986
+ ]);
6987
+ /**
6988
+ * Tags whose content is conventionally text. When the author writes
6989
+ * `<h3>{{ data?.name }}</h3>` and `data` is null during loading, the
6990
+ * interpolation is empty and `hasDirectText` returns false — without this
6991
+ * fallback the element classifies as `leaf-block` (a generic shimmer rect)
6992
+ * instead of `leaf-text` (a shimmer text bar at the element's natural
6993
+ * rendered dimensions).
6994
+ */
6995
+ const TEXT_OWNERS = new Set([
6996
+ "h1",
6997
+ "h2",
6998
+ "h3",
6999
+ "h4",
7000
+ "h5",
7001
+ "h6",
7002
+ "p",
7003
+ "span",
7004
+ "a",
7005
+ "em",
7006
+ "strong",
7007
+ "small",
7008
+ "code",
7009
+ "b",
7010
+ "i",
7011
+ "mark",
7012
+ "label",
7013
+ "caption",
7014
+ "time",
7015
+ "dt",
7016
+ "dd",
7017
+ "li",
7018
+ "th",
7019
+ "td",
7020
+ "figcaption",
7021
+ "blockquote",
7022
+ "cite",
7023
+ "q"
7024
+ ]);
7025
+ /**
7026
+ * Layout-affecting CSS captured on **every** node. Includes display + box
7027
+ * model + flex + grid + positioning so any element type renders correctly
7028
+ * regardless of its role in the parent's layout (flex item, inline-block,
7029
+ * absolutely-positioned overlay, sticky bar, grid cell).
7030
+ *
7031
+ * Width / height are intentionally NOT here — containers get sized by their
7032
+ * children; leaves get explicit width / height from their `getBoundingClientRect`
7033
+ * in `emitLeaf` below.
7034
+ */
7035
+ const LAYOUT_PROPS = [
7036
+ "display",
7037
+ "position",
7038
+ "top",
7039
+ "right",
7040
+ "bottom",
7041
+ "left",
7042
+ "z-index",
7043
+ "vertical-align",
7044
+ "float",
7045
+ "clear",
7046
+ "box-sizing",
7047
+ "margin-top",
7048
+ "margin-right",
7049
+ "margin-bottom",
7050
+ "margin-left",
7051
+ "padding-top",
7052
+ "padding-right",
7053
+ "padding-bottom",
7054
+ "padding-left",
7055
+ "flex",
7056
+ "flex-direction",
7057
+ "flex-wrap",
7058
+ "flex-grow",
7059
+ "flex-shrink",
7060
+ "flex-basis",
7061
+ "justify-content",
7062
+ "align-items",
7063
+ "align-content",
7064
+ "align-self",
7065
+ "justify-self",
7066
+ "gap",
7067
+ "row-gap",
7068
+ "column-gap",
7069
+ "order",
7070
+ "grid-template-columns",
7071
+ "grid-template-rows",
7072
+ "grid-template-areas",
7073
+ "grid-auto-flow",
7074
+ "grid-auto-columns",
7075
+ "grid-auto-rows",
7076
+ "grid-column",
7077
+ "grid-row",
7078
+ "grid-area"
7079
+ ];
7080
+ /**
7081
+ * Visual signals captured on **every** node. These give each rendered
7082
+ * placeholder the same surface identity the real element had — fill,
7083
+ * outline, elevation, transparency, transforms.
7084
+ */
7085
+ const VISUAL_PROPS = [
7086
+ "border-top-width",
7087
+ "border-right-width",
7088
+ "border-bottom-width",
7089
+ "border-left-width",
7090
+ "border-top-style",
7091
+ "border-right-style",
7092
+ "border-bottom-style",
7093
+ "border-left-style",
7094
+ "border-top-color",
7095
+ "border-right-color",
7096
+ "border-bottom-color",
7097
+ "border-left-color",
7098
+ "border-top-left-radius",
7099
+ "border-top-right-radius",
7100
+ "border-bottom-right-radius",
7101
+ "border-bottom-left-radius",
7102
+ "background-color",
7103
+ "background-image",
7104
+ "background-position",
7105
+ "background-size",
7106
+ "background-repeat",
7107
+ "background-origin",
7108
+ "background-clip",
7109
+ "box-shadow",
7110
+ "opacity",
7111
+ "filter",
7112
+ "backdrop-filter",
7113
+ "transform",
7114
+ "transform-origin",
7115
+ "mix-blend-mode"
7116
+ ];
7117
+ const ALL_PROPS = [...LAYOUT_PROPS, ...VISUAL_PROPS];
7118
+ /**
7119
+ * Walk `root`'s descendants and produce a frozen tree-shaped capture.
7120
+ * Direct children of `root` become top-level `nodes`; recursive children
7121
+ * live on their respective `ContainerNode`s.
7122
+ */
7123
+ function walkStructural(root, options = {}) {
7124
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH$1;
7125
+ const maxNodes = options.maxNodes ?? DEFAULT_MAX_NODES;
7126
+ const minSize = options.minSize ?? DEFAULT_MIN_SIZE;
7127
+ const rootRect = root.getBoundingClientRect();
7128
+ const state = {
7129
+ count: 0,
7130
+ truncated: false
7131
+ };
7132
+ const ctx = {
7133
+ maxDepth,
7134
+ maxNodes,
7135
+ minSize,
7136
+ state
7137
+ };
7138
+ const nodes = [];
7139
+ for (let i = 0; i < root.children.length; i++) {
7140
+ if (state.count >= maxNodes) {
7141
+ state.truncated = true;
7142
+ break;
4366
7143
  }
4367
- },
4368
- setup(__props, { expose: __expose }) {
4369
- __expose();
4370
- const props = __props;
4371
- const animationClass = computed(() => props.animation === "none" ? null : `a-skel-block--anim-${props.animation}`);
4372
- const blockClass = computed(() => [
4373
- "a-skel-block",
4374
- `a-skel-block--${props.type}`,
4375
- animationClass.value
4376
- ]);
4377
- function toLength(v) {
4378
- if (v === void 0) return void 0;
4379
- return typeof v === "number" ? `${v}px` : v;
7144
+ const node = capture(root.children[i], rootRect, 1, ctx);
7145
+ if (node) nodes.push(node);
7146
+ }
7147
+ return Object.freeze({
7148
+ width: Math.round(rootRect.width),
7149
+ height: Math.round(rootRect.height),
7150
+ nodes: Object.freeze(nodes),
7151
+ truncated: state.truncated,
7152
+ capturedAt: Date.now(),
7153
+ v: 3
7154
+ });
7155
+ }
7156
+ function capture(el, origin, depth, ctx) {
7157
+ if (ctx.state.count >= ctx.maxNodes) {
7158
+ ctx.state.truncated = true;
7159
+ return null;
7160
+ }
7161
+ if (el.dataset?.skeletonIgnore !== void 0) return null;
7162
+ const cs = window.getComputedStyle(el);
7163
+ if (cs.display === "none" || cs.visibility === "hidden") return null;
7164
+ const opacity = parseFloat(cs.opacity);
7165
+ if (Number.isFinite(opacity) && opacity === 0) return null;
7166
+ const rect = el.getBoundingClientRect();
7167
+ const tag = el.tagName.toLowerCase();
7168
+ const isTextOwner = TEXT_OWNERS.has(tag);
7169
+ let effectiveHeight = rect.height;
7170
+ if (isTextOwner && effectiveHeight < ctx.minSize) {
7171
+ const lh = parseFloat(cs.lineHeight);
7172
+ const fs = parseFloat(cs.fontSize);
7173
+ if (Number.isFinite(lh) && lh > 0) effectiveHeight = lh;
7174
+ else if (Number.isFinite(fs) && fs > 0) effectiveHeight = fs * 1.4;
7175
+ }
7176
+ if (rect.width < ctx.minSize || effectiveHeight < ctx.minSize) return null;
7177
+ const hasStop = el.dataset?.skeletonStop !== void 0;
7178
+ const isAtomic = ATOMIC_TAGS.has(tag);
7179
+ const reachedDepth = depth >= ctx.maxDepth;
7180
+ const childrenEls = collectVisibleChildren(el);
7181
+ if (hasStop || isAtomic || reachedDepth || childrenEls.length === 0) return emitLeaf(el, tag, cs, rect, effectiveHeight, isAtomic, ctx);
7182
+ const children = [];
7183
+ for (const child of childrenEls) {
7184
+ if (ctx.state.count >= ctx.maxNodes) {
7185
+ ctx.state.truncated = true;
7186
+ break;
4380
7187
  }
4381
- const radiusValue = computed(() => props.type === "circle" && props.radius === void 0 ? "50%" : toLength(props.radius));
4382
- const __returned__ = {
4383
- props,
4384
- animationClass,
4385
- blockClass,
4386
- toLength,
4387
- radiusValue,
4388
- blockStyle: computed(() => ({
4389
- width: toLength(props.w),
4390
- height: toLength(props.h),
4391
- borderRadius: radiusValue.value
4392
- })),
4393
- isMultiLineText: computed(() => props.type === "text" && props.lines > 1),
4394
- get cn() {
4395
- return cn;
4396
- }
4397
- };
4398
- Object.defineProperty(__returned__, "__isScriptSetup", {
4399
- enumerable: false,
4400
- value: true
4401
- });
4402
- return __returned__;
7188
+ const node = capture(child, origin, depth + 1, ctx);
7189
+ if (node) children.push(node);
4403
7190
  }
4404
- });
4405
- function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
4406
- return $setup.isMultiLineText ? (openBlock(), createElementBlock("div", {
4407
- key: 0,
4408
- class: normalizeClass($setup.cn("a-skel-block-stack", $setup.props.class)),
4409
- role: "status",
4410
- "aria-busy": "true"
4411
- }, [(openBlock(true), createElementBlock(Fragment, null, renderList($setup.props.lines, (i) => {
4412
- return openBlock(), createElementBlock("div", {
4413
- key: i,
4414
- class: normalizeClass($setup.blockClass),
4415
- style: normalizeStyle({
4416
- height: $setup.blockStyle.height ?? "0.75em",
4417
- width: i === $setup.props.lines ? "70%" : "100%",
4418
- borderRadius: $setup.blockStyle.borderRadius ?? "4px"
4419
- })
4420
- }, null, 6);
4421
- }), 128))], 2)) : (openBlock(), createElementBlock("div", {
4422
- key: 1,
4423
- class: normalizeClass($setup.cn($setup.blockClass, $setup.props.class)),
4424
- style: normalizeStyle($setup.blockStyle),
4425
- role: "status",
4426
- "aria-busy": "true"
4427
- }, null, 6));
7191
+ if (children.length === 0) return emitLeaf(el, tag, cs, rect, effectiveHeight, false, ctx);
7192
+ ctx.state.count++;
7193
+ const node = {
7194
+ kind: "container",
7195
+ tag,
7196
+ className: el.getAttribute("class") ?? "",
7197
+ style: readComputedStyles(cs, ALL_PROPS),
7198
+ children: Object.freeze(children)
7199
+ };
7200
+ return Object.freeze(node);
7201
+ }
7202
+ function emitLeaf(el, tag, cs, rect, effectiveHeight, isAtomic, ctx) {
7203
+ const w = Math.round(rect.width);
7204
+ const h = Math.round(effectiveHeight);
7205
+ const leafKind = classifyLeaf(tag, el, isAtomic);
7206
+ const captured = { ...readComputedStyles(cs, ALL_PROPS) };
7207
+ if (captured.display === "inline") captured.display = "inline-block";
7208
+ const style = {
7209
+ ...captured,
7210
+ width: `${w}px`,
7211
+ height: `${h}px`
7212
+ };
7213
+ let textLines;
7214
+ if (leafKind === "text") {
7215
+ const rawLines = captureTextLines(el, rect);
7216
+ if (rawLines && rawLines.length > 0) textLines = Object.freeze(rawLines.map((r) => ({
7217
+ x: Math.max(0, r.x),
7218
+ y: Math.max(0, r.y),
7219
+ w: r.w,
7220
+ h: r.h
7221
+ })));
7222
+ }
7223
+ ctx.state.count++;
7224
+ const node = {
7225
+ kind: "leaf",
7226
+ leafKind,
7227
+ className: el.getAttribute("class") ?? "",
7228
+ style: Object.freeze(style),
7229
+ textLines
7230
+ };
7231
+ return Object.freeze(node);
7232
+ }
7233
+ function classifyLeaf(tag, el, isAtomic) {
7234
+ if (tag === "img" || tag === "picture") return "image";
7235
+ if (tag === "video") return "image";
7236
+ if (isAtomic) return "media";
7237
+ if (hasDirectText(el)) return "text";
7238
+ if (TEXT_OWNERS.has(tag)) return "text";
7239
+ return "block";
4428
7240
  }
4429
- var ASkeletonBlock_default = /* @__PURE__ */ export_helper_default(_sfc_main, [
4430
- ["render", _sfc_render],
4431
- ["__scopeId", "data-v-bdfba69a"],
4432
- ["__file", "/Users/alikhalill/Desktop/my-projects/ali-nuxt-toolkit/packages/ui-components/ASkeleton/src/components/ASkeletonBlock.vue"]
4433
- ]);
4434
7241
  //#endregion
4435
7242
  //#region src/composables/useSkeleton.ts
7243
+ const DEFAULT_MAX_DEPTH = 12;
4436
7244
  /**
4437
- * High-level building block for custom skeleton UIs. Wires up the probe + cache
4438
- * + reactivity so the consumer just renders something using the reactive shape:
7245
+ * High-level building block for Recipe 3 wires the structural capture +
7246
+ * cache + reactivity around a target element. The reactive `shape` is fed to
7247
+ * `<ASkeletonLayer>` which renders it in normal flow inside the consumer's
7248
+ * container.
4439
7249
  *
4440
7250
  * ```ts
4441
7251
  * const containerRef = ref<HTMLElement | null>(null);
@@ -4452,13 +7262,14 @@ var ASkeletonBlock_default = /* @__PURE__ */ export_helper_default(_sfc_main, [
4452
7262
  * </div>
4453
7263
  * ```
4454
7264
  *
4455
- * For more control, drop down to `useShapeProbe` + `getCached`/`setCached` and
4456
- * compose your own.
7265
+ * For more control, drop down to `useShapeProbe` (pass `walkStructural` as the
7266
+ * capture strategy) + `getCachedStructural` / `setCachedStructural` and compose
7267
+ * your own orchestration.
4457
7268
  */
4458
7269
  function useSkeleton(options) {
4459
7270
  const persist = options.persist ?? false;
4460
- const maxDepth = options.maxDepth ?? 6;
4461
- const shape = shallowRef(getCached(options.cacheKey, persist));
7271
+ const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
7272
+ const shape = shallowRef(getCachedStructural(options.cacheKey, persist));
4462
7273
  if (options.target) {
4463
7274
  const getTarget = options.target;
4464
7275
  useShapeProbe(getTarget, {
@@ -4466,8 +7277,9 @@ function useSkeleton(options) {
4466
7277
  maxNodes: options.maxNodes,
4467
7278
  minSize: options.minSize,
4468
7279
  resizeDebounceMs: options.resizeDebounceMs,
7280
+ capture: walkStructural,
4469
7281
  onCapture: (captured) => {
4470
- setCached(options.cacheKey, captured, persist);
7282
+ setCachedStructural(options.cacheKey, captured, persist);
4471
7283
  shape.value = captured;
4472
7284
  }
4473
7285
  });
@@ -4475,18 +7287,18 @@ function useSkeleton(options) {
4475
7287
  function captureNow() {
4476
7288
  const el = options.target?.();
4477
7289
  if (!el || typeof window === "undefined") return void 0;
4478
- const captured = walkDom(el, {
7290
+ const captured = walkStructural(el, {
4479
7291
  maxDepth,
4480
7292
  maxNodes: options.maxNodes,
4481
7293
  minSize: options.minSize
4482
7294
  });
4483
7295
  if (captured.width <= 0 || captured.height <= 0 || captured.nodes.length === 0) return void 0;
4484
- setCached(options.cacheKey, captured, persist);
7296
+ setCachedStructural(options.cacheKey, captured, persist);
4485
7297
  shape.value = captured;
4486
7298
  return captured;
4487
7299
  }
4488
7300
  function clear() {
4489
- clearCached(options.cacheKey);
7301
+ clearCachedStructural(options.cacheKey);
4490
7302
  shape.value = void 0;
4491
7303
  }
4492
7304
  return {
@@ -4496,6 +7308,41 @@ function useSkeleton(options) {
4496
7308
  };
4497
7309
  }
4498
7310
  //#endregion
4499
- export { ASkeleton_default as ASkeleton, ASkeletonBlock_default as ASkeletonBlock, ASkeletonLayer_default as ASkeletonLayer, StructuralSkeleton, buildStructuralSkeleton, clearCached, fingerprintSlot, getCached, setCached, useShapeProbe, useSkeleton, walkDom };
7311
+ //#region src/utils/fingerprint.ts
7312
+ /**
7313
+ * Derive a default cache key from a slot's vnode tree. Returns the first
7314
+ * non-comment child's component name (or HTML tag). When the slot only contains
7315
+ * text / comments / unknown content, returns `'anonymous'` so the caller can
7316
+ * still cache, but with no useful identity — encourage an explicit `cacheKey`.
7317
+ */
7318
+ function fingerprintSlot(vnodes) {
7319
+ if (!vnodes) return "anonymous";
7320
+ for (const vnode of vnodes) {
7321
+ const tag = describeVNode(vnode);
7322
+ if (tag) return tag;
7323
+ }
7324
+ return "anonymous";
7325
+ }
7326
+ function describeVNode(vnode) {
7327
+ const t = vnode.type;
7328
+ if (t === Comment || t === Text) return void 0;
7329
+ if (t === Fragment) {
7330
+ const children = vnode.children;
7331
+ if (Array.isArray(children)) {
7332
+ for (const child of children) if (child && typeof child === "object" && "type" in child) {
7333
+ const found = describeVNode(child);
7334
+ if (found) return found;
7335
+ }
7336
+ }
7337
+ return;
7338
+ }
7339
+ if (typeof t === "string") return t;
7340
+ if (typeof t === "object" && t !== null) {
7341
+ const named = t.name ?? t.__name ?? t.displayName;
7342
+ if (named) return named;
7343
+ }
7344
+ }
7345
+ //#endregion
7346
+ export { ASkeleton_default as ASkeleton, ASkeletonArticle_default as ASkeletonArticle, ASkeletonAvatar_default as ASkeletonAvatar, ASkeletonBlock_default as ASkeletonBlock, ASkeletonButton_default as ASkeletonButton, ASkeletonCard_default as ASkeletonCard, ASkeletonChart_default as ASkeletonChart, ASkeletonChip_default as ASkeletonChip, ASkeletonClone_default as ASkeletonClone, ASkeletonDivider_default as ASkeletonDivider, ASkeletonForm_default as ASkeletonForm, ASkeletonHeading_default as ASkeletonHeading, ASkeletonImage_default as ASkeletonImage, ASkeletonInput_default as ASkeletonInput, ASkeletonLayer_default as ASkeletonLayer, ASkeletonListItem_default as ASkeletonListItem, ASkeletonTable_default as ASkeletonTable, ASkeletonText_default as ASkeletonText, ASkeletonVideo_default as ASkeletonVideo, StructuralSkeleton, buildStructuralSkeleton, captureSnapshot, clearCached, clearCachedStructural, fingerprintSlot, getCached, getCachedStructural, setCached, setCachedStructural, useShapeProbe, useSkeleton, walkDom, walkStructural };
4500
7347
 
4501
7348
  //# sourceMappingURL=index.js.map