@alikhalilll/a-smart-skeleton 0.0.0-stage → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +356 -2
  3. package/dist/_chunks/core.js +2314 -0
  4. package/dist/_chunks/core.js.map +1 -0
  5. package/dist/_chunks/index.d.ts +431 -0
  6. package/dist/core/index.cjs +67 -0
  7. package/dist/core/index.d.cts +2 -0
  8. package/dist/core/index.d.ts +2 -0
  9. package/dist/core/index.js +2 -0
  10. package/dist/element/index.cjs +47 -0
  11. package/dist/element/index.cjs.map +1 -0
  12. package/dist/element/index.d.cts +24 -0
  13. package/dist/element/index.d.ts +24 -0
  14. package/dist/element/index.js +46 -0
  15. package/dist/element/index.js.map +1 -0
  16. package/dist/index.cjs +512 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +204 -0
  19. package/dist/index.d.ts +204 -0
  20. package/dist/index.js +441 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/nuxt/index.cjs +36 -0
  23. package/dist/nuxt/index.cjs.map +1 -0
  24. package/dist/nuxt/index.d.cts +30 -0
  25. package/dist/nuxt/index.d.ts +30 -0
  26. package/dist/nuxt/index.js +36 -0
  27. package/dist/nuxt/index.js.map +1 -0
  28. package/dist/resolver/index.cjs +26 -0
  29. package/dist/resolver/index.cjs.map +1 -0
  30. package/dist/resolver/index.d.cts +19 -0
  31. package/dist/resolver/index.d.ts +19 -0
  32. package/dist/resolver/index.js +26 -0
  33. package/dist/resolver/index.js.map +1 -0
  34. package/dist/styles.css +136 -0
  35. package/package.json +144 -3
  36. package/src/components/ASmartSkeleton.vue +228 -0
  37. package/src/components/ASmartSkeletonBox.vue +23 -0
  38. package/src/components/ASmartSkeletonProvider.vue +34 -0
  39. package/src/components/ASmartSkeletonSuspense.vue +142 -0
  40. package/src/composables/useSmartSkeleton.ts +20 -0
  41. package/src/composables/useStyles.ts +8 -0
  42. package/src/core/boot.ts +63 -0
  43. package/src/core/canvas.ts +7 -0
  44. package/src/core/constants.ts +76 -0
  45. package/src/core/debug.ts +154 -0
  46. package/src/core/dev.ts +53 -0
  47. package/src/core/fade.ts +490 -0
  48. package/src/core/ghost.ts +236 -0
  49. package/src/core/images.ts +28 -0
  50. package/src/core/index.ts +71 -0
  51. package/src/core/inline.generated.ts +9 -0
  52. package/src/core/measure.ts +571 -0
  53. package/src/core/memory.ts +146 -0
  54. package/src/core/motion.ts +12 -0
  55. package/src/core/plan.ts +368 -0
  56. package/src/core/reveal.ts +89 -0
  57. package/src/core/reveals.ts +39 -0
  58. package/src/core/shapes.ts +84 -0
  59. package/src/core/shimmer.ts +229 -0
  60. package/src/core/skeleton.ts +43 -0
  61. package/src/core/snapshot.ts +159 -0
  62. package/src/core/styles.ts +187 -0
  63. package/src/core/sweep.ts +203 -0
  64. package/src/core/theme.ts +162 -0
  65. package/src/element/index.ts +59 -0
  66. package/src/index.ts +27 -0
  67. package/src/nuxt/index.ts +59 -0
  68. package/src/resolver/index.ts +35 -0
  69. package/src/types.ts +100 -0
  70. package/src/vue/Skeleton.ts +275 -0
  71. package/src/vue/Suspense.ts +157 -0
  72. package/src/vue/config.ts +78 -0
  73. package/src/vue/index.ts +7 -0
  74. package/web-types.json +256 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 alikhalilll
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,357 @@
1
- # Temporary Holding Version
1
+ # `@alikhalilll/a-smart-skeleton`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > Measured skeleton loaders for Vue 3 and Nuxt 3+. Captures your real component's layout — background, border, radius, box-shadow, text lines, and media boxes — and replays it as a pixel-faithful placeholder. One template to maintain. Zero cumulative layout shift.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@alikhalilll/a-smart-skeleton.svg?color=444)](https://www.npmjs.com/package/@alikhalilll/a-smart-skeleton)
6
+ [![license](https://img.shields.io/npm/l/@alikhalilll/a-smart-skeleton.svg?color=444)](https://github.com/alikhalilll/ali-nuxt-toolkit/blob/main/LICENSE)
7
+ [![docs](https://img.shields.io/badge/docs-live%20demo-5c6ac4)](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)
8
+
9
+ **[Live demo and full documentation →](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)**
10
+
11
+ Hand-authored skeletons are the second template you have to maintain. Padding drifts, font sizes bump, images start loading later — the skeleton guesses wrong and the reveal jumps. `ASmartSkeleton` lays your real content out invisibly, measures the live geometry, and reconstructs a skeleton from what it saw. One template — the real one — stays the source of truth.
12
+
13
+ ---
14
+
15
+ ## Table of contents
16
+
17
+ - [Install](#install)
18
+ - [Minimal example](#minimal-example)
19
+ - [Full example — social feed card](#full-example--social-feed-card)
20
+ - [States: `loading`, `content`, `empty`, `error`](#states-loading-content-empty-error)
21
+ - [Hints — `ask-block`, `ask-ignore`, `ask-own`](#hints--ask-block-ask-ignore-ask-own)
22
+ - [Chrome capture](#chrome-capture)
23
+ - [Reveal transitions](#reveal-transitions)
24
+ - [Theming](#theming)
25
+ - [API reference](#api-reference)
26
+ - [Changelog highlights](#changelog-highlights)
27
+
28
+ ---
29
+
30
+ ## Install
31
+
32
+ ```sh
33
+ pnpm add @alikhalilll/a-smart-skeleton
34
+ # or
35
+ npm install @alikhalilll/a-smart-skeleton
36
+ # or
37
+ yarn add @alikhalilll/a-smart-skeleton
38
+ ```
39
+
40
+ **Nuxt 3 / 4** — add the module, four components auto-import:
41
+
42
+ ```ts
43
+ // nuxt.config.ts
44
+ export default defineNuxtConfig({
45
+ modules: ['@alikhalilll/a-smart-skeleton/nuxt'],
46
+ });
47
+ ```
48
+
49
+ **Vue 3 + Vite** — import the stylesheet and optionally register the auto-import resolver:
50
+
51
+ ```ts
52
+ // main.ts
53
+ import '@alikhalilll/a-smart-skeleton/styles.css';
54
+ ```
55
+
56
+ ```ts
57
+ // vite.config.ts (optional, for unplugin-vue-components)
58
+ import Components from 'unplugin-vue-components/vite';
59
+ import ASmartSkeletonResolver from '@alikhalilll/a-smart-skeleton/resolver';
60
+
61
+ export default {
62
+ plugins: [Components({ resolvers: [ASmartSkeletonResolver()] })],
63
+ };
64
+ ```
65
+
66
+ Full install guide with troubleshooting → [docs/install](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#install).
67
+
68
+ ---
69
+
70
+ ## Minimal example
71
+
72
+ The simplest useful pattern — bind `data`, pass a `sample`, give it an `id`, done:
73
+
74
+ ```vue
75
+ <script setup lang="ts">
76
+ import { ref, onMounted } from 'vue';
77
+
78
+ const user = ref<User | null>(null);
79
+
80
+ const SAMPLE_USER = {
81
+ name: 'Sarah Connor',
82
+ role: 'Chief Mitigation Officer',
83
+ avatar: 'https://example.com/avatar.jpg',
84
+ };
85
+
86
+ onMounted(async () => {
87
+ user.value = await $fetch('/api/me');
88
+ });
89
+ </script>
90
+
91
+ <template>
92
+ <ASmartSkeleton :data="user" :sample="SAMPLE_USER" id="user-card">
93
+ <template #default="{ data }">
94
+ <UserCard :user="data" />
95
+ </template>
96
+ </ASmartSkeleton>
97
+ </template>
98
+ ```
99
+
100
+ **Three things to know:**
101
+
102
+ 1. **Always pass a `sample`.** It's what the skeleton measures before real data arrives.
103
+ 2. **Give the skeleton a stable `id`.** Shape memory is keyed by `id` across visits.
104
+ 3. **Give images explicit `width` / `height`** (or `aspect-ratio`) so the skeleton can reserve their space.
105
+
106
+ Live on the site → [Quick start demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#quick-start).
107
+
108
+ ---
109
+
110
+ ## Full example — social feed card
111
+
112
+ A real social feed: `<article>` wrapper with `border` + `bg-white` + `rounded-[12px]`, split sub-components for `Header` (avatar + author + actions menu), body text, `MediaGrid`, and a `footer` divider holding the reactions and comment buttons.
113
+
114
+ Interactive version with reveal-mode / animation / theme controls → [Customize demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#customize).
115
+
116
+ ```vue
117
+ <!-- PostList.vue (parent) -->
118
+ <script setup lang="ts">
119
+ import { ref, computed } from 'vue';
120
+ import PostCard from './PostCard.vue';
121
+
122
+ interface Post {
123
+ id: string;
124
+ author: { id: string; name: string; avatar?: string | null; verified?: boolean };
125
+ created_at_relative: string;
126
+ body: string;
127
+ media?: { type: 'image' | 'video'; url: string }[];
128
+ reactions_count: number;
129
+ comments_count: number;
130
+ is_reacted?: boolean;
131
+ }
132
+
133
+ // Sample bodies match the real-feed replacements 1:1 in length so the
134
+ // reveal doesn't trigger the plugin's dev-only "layout jumped" warning.
135
+ const SAMPLE_POSTS: Post[] = [
136
+ /* ... three sample shapes ... */
137
+ ];
138
+
139
+ const feed = ref<Post[] | null>(null);
140
+ async function loadFeed() {
141
+ feed.value = null;
142
+ feed.value = await $fetch<Post[]>('/api/community/posts');
143
+ }
144
+
145
+ const theme = computed(() => ({
146
+ light: { base: '#d1d4dc', highlight: '#ffffff' },
147
+ dark: { base: '#31363f', highlight: 'rgba(255,255,255,0.08)' },
148
+ animation: 'shimmer' as const,
149
+ speed: 'slow' as const,
150
+ }));
151
+ </script>
152
+
153
+ <template>
154
+ <ASmartSkeletonProvider :theme="theme" mode="system" reveal="smart">
155
+ <ASmartSkeleton id="community-feed" :data="feed" :sample="SAMPLE_POSTS">
156
+ <template #default="{ data }">
157
+ <div class="flex flex-col gap-4">
158
+ <PostCard v-for="post in data as Post[]" :key="post.id" :post="post" />
159
+ </div>
160
+ </template>
161
+ </ASmartSkeleton>
162
+ </ASmartSkeletonProvider>
163
+ </template>
164
+ ```
165
+
166
+ ```vue
167
+ <!-- PostCard.vue (child) -->
168
+ <script setup lang="ts">
169
+ import Header from './Header.vue';
170
+ import MediaGrid from './MediaGrid.vue';
171
+ import Reactions from './Reactions.vue';
172
+
173
+ defineProps<{ post: Post }>();
174
+ </script>
175
+
176
+ <template>
177
+ <article
178
+ :id="`post-${post.id}`"
179
+ class="border border-[#EDF0F3] rounded-[12px] bg-white flex flex-col w-full"
180
+ >
181
+ <Header :post="post" class="px-5 pb-4 pt-4" />
182
+
183
+ <div class="px-5 pb-4 flex flex-col gap-3">
184
+ <p class="text-sm leading-relaxed whitespace-pre-wrap break-words">{{ post.body }}</p>
185
+ <MediaGrid :media="post.media" />
186
+ </div>
187
+
188
+ <footer class="px-5 py-4 border-t border-[#EDF0F3] flex gap-4 items-center">
189
+ <Reactions :count="post.reactions_count" :is-reacted="post.is_reacted" />
190
+ <!--
191
+ `data-ask-block` collapses the icon + count into a single pill bone
192
+ during loading — otherwise the walker recurses and emits a tiny
193
+ media bone for the icon and a separate text bar for the number.
194
+ -->
195
+ <button
196
+ type="button"
197
+ data-ask-block
198
+ class="text-sm text-gray-600 rounded-lg flex gap-1.5 items-center hover:text-primary"
199
+ >
200
+ <svg class="size-4" aria-hidden="true"><!-- comment icon --></svg>
201
+ <span>{{ post.comments_count }}</span>
202
+ </button>
203
+ </footer>
204
+ </article>
205
+ </template>
206
+ ```
207
+
208
+ **What this example exercises** (and what the demo on the docs site shows live):
209
+
210
+ - **Chrome capture** on the painted `<article>` — the skeleton reproduces the background, border, and radius verbatim from the real element's computed style. See [Chrome capture section](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#chrome-capture).
211
+ - **Divider capture** on the `<footer>`'s `border-t` — one-sided borders survive as hairlines in the skeleton.
212
+ - **`data-ask-block`** on action buttons for one-bone pills instead of SVG + text fragments. See [Hints](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#hints--ask-block-ask-ignore-ask-own).
213
+ - **Split sub-component walker** — the plugin follows DOM nodes, not Vue VNodes, so SFC boundaries are transparent.
214
+ - **Provider-driven theming** — switch `mode`, `theme.light`, `theme.dark`, `animation`, `speed`, `reveal` in one place. See [Theming](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#theming).
215
+
216
+ ---
217
+
218
+ ## States: `loading`, `content`, `empty`, `error`
219
+
220
+ One component handles all four states. Resolution order is `loading` → `error` → `empty` → `content`:
221
+
222
+ ```vue
223
+ <ASmartSkeleton
224
+ :data="posts"
225
+ :sample="SAMPLE_POSTS"
226
+ :error="fetchError"
227
+ :empty="posts?.length === 0"
228
+ @reveal="onContentRevealed"
229
+ >
230
+ <template #default="{ data }">
231
+ <PostList :posts="data" />
232
+ </template>
233
+ <template #empty>
234
+ <EmptyIllustration message="No posts yet" />
235
+ </template>
236
+ <template #error="{ error }">
237
+ <ErrorBanner :error="error" @retry="reload" />
238
+ </template>
239
+ </ASmartSkeleton>
240
+ ```
241
+
242
+ `@reveal` fires once per `skeleton → content` transition. Full behaviour → [States demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#states-loading-content-empty-error).
243
+
244
+ ---
245
+
246
+ ## Hints — `ask-block`, `ask-ignore`, `ask-own`
247
+
248
+ Three attributes let you override the walker where it matters:
249
+
250
+ | Hint | Alias | What it does |
251
+ | ------------ | ---------------------- | -------------------------------------------------------------------------------------------- |
252
+ | `ask-block` | `data-skeleton-block` | Draws the element as **one** bone sized to its box + border-radius. Walker does not recurse. |
253
+ | `ask-ignore` | `data-skeleton-ignore` | Element keeps its layout footprint but emits **zero** bones. |
254
+ | `ask-own` | `data-skeleton-own` | Subtree opts out — you're drawing its skeleton yourself. |
255
+
256
+ See it in action → [Hints demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#hints--ask-block-ask-ignore-ask-own).
257
+
258
+ ---
259
+
260
+ ## Chrome capture
261
+
262
+ When the walker crosses a **painted container** (a background, a border on any side, or a box-shadow) it emits an extra `type: 'chrome'` bone that carries the captured `background`, per-side `border-*`, `border-radius`, and `box-shadow` inline. The bone skips the shimmer animation so it reads as an outline, not a placeholder fill.
263
+
264
+ - `backgroundColor` and gradient `backgroundImage` layers are kept. `url(…)` layers are stripped — hero bitmaps don't re-download in the skeleton.
265
+ - Each border side is captured separately, so a `border-t` footer divider survives as a hairline.
266
+ - `border-radius` is captured per-corner where the parent's overflow clips it.
267
+ - Chrome bones ignore `--ask-base` by design — they're reproductions, not placeholders.
268
+
269
+ Full details and before/after shots → [Chrome capture section](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#chrome-capture).
270
+
271
+ ---
272
+
273
+ ## Reveal transitions
274
+
275
+ | Mode | Behaviour |
276
+ | ---------- | ----------------------------------------------------------------------------- |
277
+ | `smart` | Instant if the content lands under ~150 ms, otherwise crossfade. The default. |
278
+ | `together` | Simple crossfade. |
279
+ | `sequence` | Staggered top-to-bottom cascade. |
280
+ | `sweep` | A highlight beam sweeps across the transition edge. |
281
+ | `instant` | Snap, no transition. |
282
+
283
+ `prefers-reduced-motion: reduce` forces `instant` automatically. Compare them side-by-side → [Reveal transitions](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#reveal-transitions).
284
+
285
+ ---
286
+
287
+ ## Theming
288
+
289
+ Every surface is driven by CSS custom properties. Set them on any ancestor:
290
+
291
+ ```css
292
+ .my-feed {
293
+ --ask-base: #d1d4dc;
294
+ --ask-highlight: #ffffff;
295
+ }
296
+ ```
297
+
298
+ For app-wide defaults, wrap your app in `<ASmartSkeletonProvider>`:
299
+
300
+ ```vue
301
+ <ASmartSkeletonProvider
302
+ mode="system"
303
+ :theme="{
304
+ light: { base: '#d1d4dc', highlight: '#ffffff' },
305
+ dark: { base: '#31363f', highlight: 'rgba(255,255,255,0.08)' },
306
+ animation: 'shimmer',
307
+ speed: 'slow',
308
+ }"
309
+ reveal="smart"
310
+ >
311
+ <App />
312
+ </ASmartSkeletonProvider>
313
+ ```
314
+
315
+ Bones ship as `<div [ask-rect="text|media|box|chrome"]>` in the **light DOM** (no shadow root), so your stylesheet reaches them without `:deep()`. A 4-px minimum radius is applied to every non-chrome bone so nothing reads as a sharp glyph.
316
+
317
+ ```css
318
+ /* Handcrafted ASkeletonBlock-style look on placeholder bones. */
319
+ [ask-rect='text'],
320
+ [ask-rect='box'] {
321
+ box-shadow: inset 0 0 0 1px rgb(169 175 188 / 0.35);
322
+ }
323
+ ```
324
+
325
+ Full CSS variable table + per-type targeting → [Theming](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#theming).
326
+
327
+ ---
328
+
329
+ ## API reference
330
+
331
+ **`<ASmartSkeleton>`** — `data`, `sample`, `loading`, `id`, `label`, `empty`, `error`, `reveal` · emits `@reveal` · slots `default` / `empty` / `error` / `skeleton`.
332
+
333
+ **`<ASmartSkeletonProvider>`** — `theme`, `mode`, `reveal`, `debug`, `nonce`.
334
+
335
+ **`<ASmartSkeletonBox>`** — standalone shimmer bar · `width`, `height`, `radius`.
336
+
337
+ **`<ASmartSkeletonSuspense>`** — Suspense-boundary wrapper that measures a fallback skeleton.
338
+
339
+ **Author hints (HTML attrs)** — `ask-block`, `ask-ignore`, `ask-own` (each with a `data-skeleton-*` alias).
340
+
341
+ Full props / slots / events reference → [API reference](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#api-reference).
342
+
343
+ ---
344
+
345
+ ## Changelog highlights
346
+
347
+ - **Chrome capture** (`type: 'chrome'`) — reproduces card outlines pixel-for-pixel from the consumer's own paint. Partial borders (`border-t` dividers) are preserved. `url(…)` background layers are filtered out so hero bitmaps don't re-download.
348
+ - **Light-DOM bones** — placeholders now live in the light DOM with a `[ask-rect="text|media|box|chrome"]` attribute, so consumer stylesheets can style them per type without `:deep()` on a shadow root.
349
+ - **4-px minimum radius** — every non-chrome bone is floored at 4 px so sharp-cornered placeholders don't read as glyphs of the real content.
350
+ - **Fixed `reveal="instant"`** — previously threw `Cannot read properties of undefined (reading 'hold')`. Unknown reveal strings now fall through to `smart` instead of crashing.
351
+ - **Cleaned stylesheet** — the published `dist/styles.css` is now a hand-authored ~4 KB file. No Tailwind utilities, `@property` globals, or theme tokens leak into the consumer's `:root`.
352
+
353
+ See the full version history → [CHANGELOG](https://github.com/alikhalilll/ali-nuxt-toolkit/blob/main/packages/ui-components/ASmartSkeleton/CHANGELOG.md) · browse source → [GitHub](https://github.com/alikhalilll/ali-nuxt-toolkit/tree/main/packages/ui-components/ASmartSkeleton) · open an issue → [Issues](https://github.com/alikhalilll/ali-nuxt-toolkit/issues).
354
+
355
+ ---
356
+
357
+ MIT © [alikhalilll](https://github.com/alikhalilll)