@alikhalilll/a-smart-skeleton 1.0.0 → 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.
- package/README.md +248 -186
- package/dist/_chunks/core.js +142 -15
- package/dist/_chunks/core.js.map +1 -1
- package/dist/_chunks/index.d.ts +30 -5
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/styles.css +136 -2
- package/package.json +2 -2
- package/src/core/fade.ts +3 -3
- package/src/core/index.ts +1 -1
- package/src/core/inline.generated.ts +1 -1
- package/src/core/measure.ts +117 -0
- package/src/core/plan.ts +1 -1
- package/src/core/reveals.ts +12 -3
- package/src/core/shimmer.ts +71 -15
- package/src/core/styles.ts +20 -0
- package/web-types.json +1 -1
package/README.md
CHANGED
|
@@ -1,34 +1,35 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@alikhalilll/a-smart-skeleton`
|
|
2
2
|
|
|
3
|
-
>
|
|
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
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@alikhalilll/a-smart-skeleton)
|
|
6
|
-
[](https://github.com/alikhalilll/ali-nuxt-toolkit/blob/main/LICENSE)
|
|
7
|
+
[](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
**[Live demo and full documentation →](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton)**
|
|
9
10
|
|
|
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.
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
---
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
## Table of contents
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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)
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
|
-
##
|
|
30
|
+
## Install
|
|
30
31
|
|
|
31
|
-
```
|
|
32
|
+
```sh
|
|
32
33
|
pnpm add @alikhalilll/a-smart-skeleton
|
|
33
34
|
# or
|
|
34
35
|
npm install @alikhalilll/a-smart-skeleton
|
|
@@ -36,260 +37,321 @@ npm install @alikhalilll/a-smart-skeleton
|
|
|
36
37
|
yarn add @alikhalilll/a-smart-skeleton
|
|
37
38
|
```
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
**Nuxt 3 / 4** — add the module, four components auto-import:
|
|
40
41
|
|
|
41
42
|
```ts
|
|
42
|
-
|
|
43
|
+
// nuxt.config.ts
|
|
44
|
+
export default defineNuxtConfig({
|
|
45
|
+
modules: ['@alikhalilll/a-smart-skeleton/nuxt'],
|
|
46
|
+
});
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Nuxt 3 / 4 Setup
|
|
48
|
-
|
|
49
|
-
Add the module to `nuxt.config.ts`:
|
|
49
|
+
**Vue 3 + Vite** — import the stylesheet and optionally register the auto-import resolver:
|
|
50
50
|
|
|
51
51
|
```ts
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
aSmartSkeleton: {
|
|
55
|
-
prefix: 'A', // Optional prefix
|
|
56
|
-
styles: true, // Automatically injects styles.css
|
|
57
|
-
},
|
|
58
|
-
});
|
|
52
|
+
// main.ts
|
|
53
|
+
import '@alikhalilll/a-smart-skeleton/styles.css';
|
|
59
54
|
```
|
|
60
55
|
|
|
61
|
-
|
|
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
|
+
```
|
|
62
65
|
|
|
63
|
-
-
|
|
64
|
-
- `<ASmartSkeletonBox>`
|
|
65
|
-
- `<ASmartSkeletonSuspense>`
|
|
66
|
-
- `<ASmartSkeletonProvider>`
|
|
66
|
+
Full install guide with troubleshooting → [docs/install](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#install).
|
|
67
67
|
|
|
68
68
|
---
|
|
69
69
|
|
|
70
|
-
##
|
|
71
|
-
|
|
72
|
-
### Basic Data-Driven Skeleton
|
|
70
|
+
## Minimal example
|
|
73
71
|
|
|
74
|
-
|
|
72
|
+
The simplest useful pattern — bind `data`, pass a `sample`, give it an `id`, done:
|
|
75
73
|
|
|
76
74
|
```vue
|
|
77
75
|
<script setup lang="ts">
|
|
78
|
-
import { ref } from 'vue';
|
|
79
|
-
import { ASmartSkeleton } from '@alikhalilll/a-smart-skeleton';
|
|
76
|
+
import { ref, onMounted } from 'vue';
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
name: string;
|
|
83
|
-
role: string;
|
|
84
|
-
avatar: string;
|
|
85
|
-
}
|
|
78
|
+
const user = ref<User | null>(null);
|
|
86
79
|
|
|
87
|
-
const SAMPLE_USER
|
|
88
|
-
name: '
|
|
89
|
-
role: '
|
|
90
|
-
avatar: '',
|
|
80
|
+
const SAMPLE_USER = {
|
|
81
|
+
name: 'Sarah Connor',
|
|
82
|
+
role: 'Chief Mitigation Officer',
|
|
83
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
91
84
|
};
|
|
92
85
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
setTimeout(() => {
|
|
97
|
-
user.value = {
|
|
98
|
-
name: 'Sarah Connor',
|
|
99
|
-
role: 'Cyberdyne Systems Lead',
|
|
100
|
-
avatar: 'https://example.com/avatar.jpg',
|
|
101
|
-
};
|
|
102
|
-
}, 1500);
|
|
86
|
+
onMounted(async () => {
|
|
87
|
+
user.value = await $fetch('/api/me');
|
|
88
|
+
});
|
|
103
89
|
</script>
|
|
104
90
|
|
|
105
91
|
<template>
|
|
106
|
-
<ASmartSkeleton :data="user" :sample="SAMPLE_USER" id="user-
|
|
92
|
+
<ASmartSkeleton :data="user" :sample="SAMPLE_USER" id="user-card">
|
|
107
93
|
<template #default="{ data }">
|
|
108
|
-
<
|
|
109
|
-
<img :src="data.avatar" alt="Avatar" class="avatar" />
|
|
110
|
-
<div class="meta">
|
|
111
|
-
<h3>{{ data.name }}</h3>
|
|
112
|
-
<p>{{ data.role }}</p>
|
|
113
|
-
</div>
|
|
114
|
-
</div>
|
|
94
|
+
<UserCard :user="data" />
|
|
115
95
|
</template>
|
|
116
96
|
</ASmartSkeleton>
|
|
117
97
|
</template>
|
|
118
98
|
```
|
|
119
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
|
+
|
|
120
108
|
---
|
|
121
109
|
|
|
122
|
-
|
|
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.
|
|
123
113
|
|
|
124
|
-
|
|
114
|
+
Interactive version with reveal-mode / animation / theme controls → [Customize demo](https://alikhalilll.github.io/ali-nuxt-toolkit/ui/smart-skeleton#customize).
|
|
125
115
|
|
|
126
116
|
```vue
|
|
127
|
-
<!--
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
}
|
|
131
132
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
+
];
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
}
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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>
|
|
147
152
|
|
|
148
|
-
|
|
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
|
+
```
|
|
149
165
|
|
|
150
166
|
```vue
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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>
|
|
154
206
|
```
|
|
155
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
|
+
|
|
156
216
|
---
|
|
157
217
|
|
|
158
|
-
|
|
218
|
+
## States: `loading`, `content`, `empty`, `error`
|
|
159
219
|
|
|
160
|
-
|
|
220
|
+
One component handles all four states. Resolution order is `loading` → `error` → `empty` → `content`:
|
|
161
221
|
|
|
162
222
|
```vue
|
|
163
|
-
<ASmartSkeleton
|
|
223
|
+
<ASmartSkeleton
|
|
224
|
+
:data="posts"
|
|
225
|
+
:sample="SAMPLE_POSTS"
|
|
226
|
+
:error="fetchError"
|
|
227
|
+
:empty="posts?.length === 0"
|
|
228
|
+
@reveal="onContentRevealed"
|
|
229
|
+
>
|
|
164
230
|
<template #default="{ data }">
|
|
165
|
-
<
|
|
231
|
+
<PostList :posts="data" />
|
|
166
232
|
</template>
|
|
167
|
-
|
|
168
233
|
<template #empty>
|
|
169
|
-
<
|
|
234
|
+
<EmptyIllustration message="No posts yet" />
|
|
170
235
|
</template>
|
|
171
|
-
|
|
172
236
|
<template #error="{ error }">
|
|
173
|
-
<
|
|
237
|
+
<ErrorBanner :error="error" @retry="reload" />
|
|
174
238
|
</template>
|
|
175
239
|
</ASmartSkeleton>
|
|
176
240
|
```
|
|
177
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
|
+
|
|
178
244
|
---
|
|
179
245
|
|
|
180
|
-
|
|
246
|
+
## Hints — `ask-block`, `ask-ignore`, `ask-own`
|
|
181
247
|
|
|
182
|
-
|
|
248
|
+
Three attributes let you override the walker where it matters:
|
|
183
249
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
</template>
|
|
192
|
-
```
|
|
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).
|
|
193
257
|
|
|
194
258
|
---
|
|
195
259
|
|
|
196
|
-
|
|
260
|
+
## Chrome capture
|
|
197
261
|
|
|
198
|
-
|
|
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.
|
|
199
263
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
</template>
|
|
207
|
-
</ASmartSkeletonSuspense>
|
|
208
|
-
</template>
|
|
209
|
-
```
|
|
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).
|
|
210
270
|
|
|
211
271
|
---
|
|
212
272
|
|
|
213
|
-
|
|
273
|
+
## Reveal transitions
|
|
214
274
|
|
|
215
|
-
|
|
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. |
|
|
216
282
|
|
|
217
|
-
|
|
218
|
-
<template>
|
|
219
|
-
<ASmartSkeletonProvider
|
|
220
|
-
mode="system"
|
|
221
|
-
:theme="{
|
|
222
|
-
light: { base: 'rgb(0 0 0 / 0.07)', highlight: 'rgb(255 255 255 / 0.6)' },
|
|
223
|
-
dark: { base: 'rgb(255 255 255 / 0.08)', highlight: 'rgb(255 255 255 / 0.1)' },
|
|
224
|
-
radius: '6px',
|
|
225
|
-
animation: 'shimmer',
|
|
226
|
-
speed: 'slow',
|
|
227
|
-
text: { top: 'ascender', bottom: 'descender' },
|
|
228
|
-
}"
|
|
229
|
-
reveal="smart"
|
|
230
|
-
:debug="false"
|
|
231
|
-
>
|
|
232
|
-
<App />
|
|
233
|
-
</ASmartSkeletonProvider>
|
|
234
|
-
</template>
|
|
235
|
-
```
|
|
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).
|
|
236
284
|
|
|
237
285
|
---
|
|
238
286
|
|
|
239
|
-
##
|
|
287
|
+
## Theming
|
|
240
288
|
|
|
241
|
-
|
|
289
|
+
Every surface is driven by CSS custom properties. Set them on any ancestor:
|
|
242
290
|
|
|
243
|
-
```
|
|
244
|
-
|
|
291
|
+
```css
|
|
292
|
+
.my-feed {
|
|
293
|
+
--ask-base: #d1d4dc;
|
|
294
|
+
--ask-highlight: #ffffff;
|
|
295
|
+
}
|
|
296
|
+
```
|
|
245
297
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
`);
|
|
262
|
-
}, 2000);
|
|
263
|
-
</script>
|
|
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>
|
|
264
313
|
```
|
|
265
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
|
+
|
|
266
327
|
---
|
|
267
328
|
|
|
268
|
-
##
|
|
329
|
+
## API reference
|
|
269
330
|
|
|
270
|
-
|
|
331
|
+
**`<ASmartSkeleton>`** — `data`, `sample`, `loading`, `id`, `label`, `empty`, `error`, `reveal` · emits `@reveal` · slots `default` / `empty` / `error` / `skeleton`.
|
|
271
332
|
|
|
272
|
-
|
|
273
|
-
- `ask-block` / `[ask-block]`: Force an element to be measured as a solid rectangle block rather than text lines.
|
|
274
|
-
- `ask-own` / `[ask-own]`: Subtree provides its own custom skeleton bones; measuring stops at this boundary.
|
|
275
|
-
- `ask-xray` / `[ask-xray]`: Debug mode that exposes the ghost tree through translucent bones.
|
|
333
|
+
**`<ASmartSkeletonProvider>`** — `theme`, `mode`, `reveal`, `debug`, `nonce`.
|
|
276
334
|
|
|
277
|
-
|
|
335
|
+
**`<ASmartSkeletonBox>`** — standalone shimmer bar · `width`, `height`, `radius`.
|
|
278
336
|
|
|
279
|
-
|
|
337
|
+
**`<ASmartSkeletonSuspense>`** — Suspense-boundary wrapper that measures a fallback skeleton.
|
|
280
338
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
| `--ask-highlight` | `rgb(255 255 255 / 0.12)` | Shimmer sweep highlight color |
|
|
285
|
-
| `--ask-radius` | `0.25em` | Default border-radius for text lines and standalone boxes |
|
|
286
|
-
| `--ask-animation` | `shimmer` | Animation: `shimmer`, `group-shimmer`, `pulse`, `none` |
|
|
287
|
-
| `--ask-speed` | `slow` | Animation speed: `slow`, `fast` |
|
|
288
|
-
| `--ask-text-top` | `ascender` | Top typographic boundary (`ascender`, `cap-height`, `x-height`) |
|
|
289
|
-
| `--ask-text-bottom` | `descender` | Bottom typographic boundary (`baseline`, `descender`) |
|
|
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).
|
|
290
342
|
|
|
291
343
|
---
|
|
292
344
|
|
|
293
|
-
##
|
|
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
|
+
---
|
|
294
356
|
|
|
295
357
|
MIT © [alikhalilll](https://github.com/alikhalilll)
|