shimmer-trace 1.1.3 → 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.
- package/AGENTS.md +78 -19
- package/README.md +178 -57
- package/dist/{index.js → index.cjs} +26 -10
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +25 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/dist/index.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
## What This Library Does (One Paragraph)
|
|
10
10
|
|
|
11
|
-
`shimmer-trace` is a React skeleton loading library. It renders your real component
|
|
11
|
+
`shimmer-trace` is a React skeleton loading library. It renders your real component, walks the live DOM to find every visible leaf element (headings, paragraphs, images, inputs, buttons, etc.), measures each one's exact position and size via `getBoundingClientRect()`, then paints an absolutely-positioned shimmer overlay on top of those measured rects. The skeleton is therefore pixel-perfect and auto-updates via `ResizeObserver`. You never write a manual skeleton. You wrap your component and pass `loading={true}`.
|
|
12
|
+
|
|
13
|
+
The library is **client-only** by nature — measurement requires a real DOM — but its bundled output is marked with `'use client'`, so it can be **imported directly from React Server Components / Next.js App Router pages** without manual wrapping.
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
@@ -20,19 +22,29 @@ npm install shimmer-trace
|
|
|
20
22
|
|
|
21
23
|
**Peer deps required:** `react >= 18.0.0`, `react-dom >= 18.0.0`
|
|
22
24
|
|
|
25
|
+
### Server vs Client Components (Next.js App Router)
|
|
26
|
+
|
|
27
|
+
- The package's bundled `dist/index.mjs` and `dist/index.js` both start with `'use client';`. Every exported component (`Shimmer`, `ShimmerSuspense`, `createShimmer`) and hook (`useIsShimmering`, `useShimmerContext`) crosses the client boundary automatically.
|
|
28
|
+
- **You can import the package from a Server Component file.** Next.js will treat the imported subgraph as client code. You do NOT need to add `'use client'` at the top of a page just because it imports `Shimmer`.
|
|
29
|
+
- **Children passed to `<Shimmer>` follow normal RSC rules.** A Server Component child that produces DOM synchronously will render and be measured fine. A Server Component child that suspends needs `<ShimmerSuspense>` instead — otherwise it produces no DOM during the fallback and the skeleton is empty.
|
|
30
|
+
- All measurement uses `window`, `document`, `getBoundingClientRect`, and `ResizeObserver`. There is no SSR measurement pass; the skeleton paints after hydration.
|
|
31
|
+
|
|
23
32
|
---
|
|
24
33
|
|
|
25
34
|
## Public API — Full Export List
|
|
26
35
|
|
|
27
36
|
```ts
|
|
28
37
|
// Components
|
|
29
|
-
import { Shimmer }
|
|
30
|
-
import { ShimmerSuspense }
|
|
31
|
-
import { createShimmer }
|
|
38
|
+
import { Shimmer } from 'shimmer-trace'; // main component
|
|
39
|
+
import { ShimmerSuspense } from 'shimmer-trace'; // Suspense boundary with auto-skeleton
|
|
40
|
+
import { createShimmer } from 'shimmer-trace'; // factory — bakes config into component
|
|
32
41
|
|
|
33
42
|
// Hooks
|
|
34
|
-
import { useIsShimmering }
|
|
35
|
-
import { useShimmerContext } from 'shimmer-trace'; // raw context — advanced use only
|
|
43
|
+
import { useIsShimmering } from 'shimmer-trace'; // true when inside ShimmerSuspense fallback
|
|
44
|
+
import { useShimmerContext } from 'shimmer-trace'; // raw context value — advanced use only
|
|
45
|
+
|
|
46
|
+
// Raw context (rarely needed — only if you build a custom Master/Reporter outside <Shimmer>)
|
|
47
|
+
import { ShimmerContext } from 'shimmer-trace';
|
|
36
48
|
|
|
37
49
|
// Types
|
|
38
50
|
import type {
|
|
@@ -285,7 +297,7 @@ Use when: multiple sub-sections exist but you want one synchronized shimmer acro
|
|
|
285
297
|
</Shimmer>
|
|
286
298
|
```
|
|
287
299
|
|
|
288
|
-
**Key rule:**
|
|
300
|
+
**Key rule:** Any nested `<Shimmer>` inside an outer `<Shimmer>` auto-becomes a Reporter, regardless of whether it has its own `loading` prop. The Reporter measures its own subtree, sends rects to the Master, and ignores its own `loading` prop — Master's `loading` controls everything. One overlay covers all. To break out and create an independent Master, use `stopPropagation={true}` (see Pattern 8).
|
|
289
301
|
|
|
290
302
|
---
|
|
291
303
|
|
|
@@ -323,26 +335,29 @@ Use when: component uses `use()`, `useSuspenseQuery`, or similar — throws a Pr
|
|
|
323
335
|
|
|
324
336
|
### Option A — `template` prop (component has zero shimmer awareness)
|
|
325
337
|
|
|
338
|
+
Reuse the same component as its own template — pass template props so it renders DOM without suspending. No duplicate skeleton component, no ` ` width padding.
|
|
339
|
+
|
|
326
340
|
```tsx
|
|
327
341
|
import { ShimmerSuspense } from 'shimmer-trace';
|
|
328
342
|
|
|
329
|
-
//
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<p> </p>
|
|
337
|
-
</div>
|
|
338
|
-
</div>
|
|
339
|
-
);
|
|
343
|
+
// Template data: same shape the real component expects, no fetch
|
|
344
|
+
const userTemplate = {
|
|
345
|
+
name: 'xxxxxxxxxxxxxx',
|
|
346
|
+
role: 'xxxxxxxxxx',
|
|
347
|
+
bio: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
|
|
348
|
+
avatar: '',
|
|
349
|
+
};
|
|
340
350
|
|
|
341
|
-
<ShimmerSuspense template={<
|
|
351
|
+
<ShimmerSuspense template={<UserCard user={userTemplate} />} animation="wave">
|
|
342
352
|
<UserCard /> {/* throws Promise while fetching — shimmer shows automatically */}
|
|
343
353
|
</ShimmerSuspense>
|
|
344
354
|
```
|
|
345
355
|
|
|
356
|
+
**Why a `template` prop at all (not just `dummyData` like `<Shimmer>`):** the real `<UserCard />` throws a Promise during render — it never produces DOM until data resolves. `cloneElement` + props merge doesn't help because the cloned element still suspends. `template` renders a separate, non-suspending instance (same component, template data) so Shimmer gets real DOM to trace.
|
|
357
|
+
|
|
358
|
+
**Rule:** template should render synchronously. If you pass `<UserCard resource={realResource} />` as template, it'll suspend inside the fallback and skeleton goes empty. Either pass template data props (above) or use Option B.
|
|
359
|
+
|
|
360
|
+
|
|
346
361
|
### Option B — `useIsShimmering` hook (component skips fetch in shimmer mode)
|
|
347
362
|
|
|
348
363
|
```tsx
|
|
@@ -405,8 +420,25 @@ Use when: nested Shimmer should NOT bubble rects to parent — it manages its ow
|
|
|
405
420
|
<span data-shimmer-ignore>
|
|
406
421
|
<DecorativeIcon />
|
|
407
422
|
</span>
|
|
423
|
+
|
|
424
|
+
// Force trace a position:fixed / position:sticky element (overrides
|
|
425
|
+
// the auto-skip described in "Known Limitations"). The block will
|
|
426
|
+
// drift on scroll — only use this on non-scrolling pages or when the
|
|
427
|
+
// fixed element is short-lived.
|
|
428
|
+
<div data-shimmer style={{ position: 'fixed', top: 0 }}>
|
|
429
|
+
<Banner />
|
|
430
|
+
</div>
|
|
408
431
|
```
|
|
409
432
|
|
|
433
|
+
**Precedence** (matches source order in `useTrace.ts`):
|
|
434
|
+
|
|
435
|
+
1. `data-shimmer-ignore` → never traced, subtree skipped.
|
|
436
|
+
2. `data-shimmer-reporter` → internal, skipped by walk.
|
|
437
|
+
3. `data-shimmer` on a `position: fixed` / `sticky` element → trace it anyway (override).
|
|
438
|
+
4. No override + `fixed` / `sticky` position → skip subtree, warn once per Master.
|
|
439
|
+
5. `data-shimmer` on a normal element → force-trace.
|
|
440
|
+
6. Default: tag-whitelist or visible-leaf heuristic.
|
|
441
|
+
|
|
410
442
|
---
|
|
411
443
|
|
|
412
444
|
## Config Inheritance Chain
|
|
@@ -563,6 +595,33 @@ function UserCard() {
|
|
|
563
595
|
|
|
564
596
|
Library injects one `<style>` tag with id `shimmer-trace-styles` into `document.head` on first render. Contains only `@keyframes` definitions and `preserveBackground` CSS attribute selectors. Safe to SSR — guard is `typeof document === 'undefined'`.
|
|
565
597
|
|
|
598
|
+
Injection runs inside `useInsertionEffect`, not `useEffect`. This means the keyframes are present in the document **before the first paint** of any `<Shimmer>` subtree, so there is no first-frame FOUC (no momentary static gray blocks before the wave starts). Agents should not "fix" this back to `useEffect` — the timing is intentional.
|
|
599
|
+
|
|
600
|
+
## Clone Keys & Hydration
|
|
601
|
+
|
|
602
|
+
When `dummyLength` or `dummyData` causes Shimmer to clone children, each clone receives a key of the form `${parentShimmerId}-${slot}-${index}`, where `parentShimmerId` comes from React's `useId()` (stable across SSR and hydration).
|
|
603
|
+
|
|
604
|
+
Keys are deterministic per render slot. Agents should:
|
|
605
|
+
|
|
606
|
+
- **Not** assume keys change between renders.
|
|
607
|
+
- **Not** introduce a module-scope counter into `utils.ts` or anywhere else — the previous implementation did this and it caused (a) hydration mismatch warnings when SSR and client incremented the counter differently and (b) full remount of every cloned child on every parent re-render.
|
|
608
|
+
- Rely on `useId()` already being called inside `<Shimmer>` for any new keying logic.
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Known Limitations (Agents: do not invent fixes that aren't in source)
|
|
613
|
+
|
|
614
|
+
These are real gaps in the current implementation. If a user asks why their setup misbehaves, match the symptom to one of these before debugging deeper.
|
|
615
|
+
|
|
616
|
+
- **Portals are silently skipped.** `collectTraceableElements` walks the descendants of the Master container ref. Anything mounted via `ReactDOM.createPortal` lives elsewhere in the DOM and never enters the walk. No console warning. Workaround: put a separate `<Shimmer>` *inside* the portal target.
|
|
617
|
+
- **`position: fixed` / `position: sticky` children are detected and skipped.** The rect math is `el.getBoundingClientRect() - container.getBoundingClientRect()`. For fixed/sticky elements that coordinate space diverges from the container as soon as the page scrolls, so any overlay block we generated would drift. `collectTraceableElements` calls `getComputedStyle(el).position` during the walk; if it's `fixed` or `sticky`, the entire subtree is skipped and one `console.warn` is emitted per Master container (deduped via a `WeakSet`, so resize-driven re-traces never re-warn). The warning fires in production too — it's intentional. Two opt-ins exist:
|
|
618
|
+
- Add `data-shimmer` to the fixed element to force-trace it (user accepts the scroll drift). When `data-shimmer` is present, the position check is bypassed.
|
|
619
|
+
- Recommended: render a separate `<Shimmer>` *inside* the fixed element. Master/Reporter detection handles the nesting, and the inner overlay sits inside the fixed coordinate space, so alignment works.
|
|
620
|
+
- **Transformed ancestors.** The overlay sits inside the Master container, so it inherits the same transform stack — this usually renders correctly. A transform applied *between* the Master container and a traced descendant can desynchronize the overlay block from its source element.
|
|
621
|
+
- **Suspending Server Components inside plain `<Shimmer>`.** Without `<ShimmerSuspense>`, a suspending child produces no DOM during the fallback, so the trace returns zero rects and the skeleton is empty. Always recommend `ShimmerSuspense` for suspending children.
|
|
622
|
+
- **Server-side measurement is not supported.** There is no DOM on the server. The first paint after hydration is when measurements happen. Agents should not promise SSR-rendered skeletons — only that hydration is clean.
|
|
623
|
+
- **`getBoundingClientRect` is called twice per leaf** (once in `isTraceable` for the dimension check, once in `measureElement`). This is a known perf bottleneck on very large trees and is on the roadmap; do not claim it's already optimized.
|
|
624
|
+
|
|
566
625
|
---
|
|
567
626
|
|
|
568
627
|
## TypeScript Usage
|
package/README.md
CHANGED
|
@@ -43,9 +43,14 @@ It renders your real component invisibly, traces every element's exact position
|
|
|
43
43
|
|
|
44
44
|
- **Auto-tracing** — Measures real DOM layout. No manual skeleton code.
|
|
45
45
|
- **Zero CLS** — Container layout preserved. Default `preserveBackground` keeps card backgrounds, borders, and padding visible underneath the shimmer.
|
|
46
|
+
- **Next.js App Router / RSC ready** — Ships with `'use client'` directive baked into the bundled output. Import directly from a Server Component without a wrapper.
|
|
47
|
+
- **SSR-safe** — Deterministic clone keys across server and client. No hydration mismatch warnings, no remount of cloned children on every render.
|
|
48
|
+
- **No first-frame FOUC** — Animation styles are injected via `useInsertionEffect` so keyframes land before the first paint.
|
|
46
49
|
- **Synchronized animation** — One overlay, one wave. All skeletons animate in perfect sync.
|
|
47
50
|
- **5 animation styles** — `wave`, `pulse`, `shine`, `glow`, `gradient`.
|
|
48
|
-
- **
|
|
51
|
+
- **Dummy data injection** — `dummyData` clones children with template props so skeletons render with realistic shape, no `data || fallback` ternaries in JSX.
|
|
52
|
+
- **List mode** — `dummyLength` clones the first child N times for skeleton lists, even when your array is empty.
|
|
53
|
+
- **Component templates** — `as={MovieCard}` generates skeletons from a component + `dummyData`, no children required.
|
|
49
54
|
- **Suspense-native** — `ShimmerSuspense` wraps any suspended component with no `loading` prop.
|
|
50
55
|
- **Factory pattern** — `createShimmer` pre-bakes your config. Use it like a component everywhere.
|
|
51
56
|
- **Composable** — Nested `Shimmer` components bubble their rects up to a single master overlay.
|
|
@@ -69,6 +74,31 @@ pnpm add shimmer-trace
|
|
|
69
74
|
|
|
70
75
|
---
|
|
71
76
|
|
|
77
|
+
## Next.js App Router (RSC) usage
|
|
78
|
+
|
|
79
|
+
`shimmer-trace` ships its bundled output with a top-of-file `'use client'` directive, so you can import `Shimmer` (or `ShimmerSuspense`, `createShimmer`) directly from a Server Component without crashing the build.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
// app/profile/page.tsx — Server Component, no 'use client' at the top
|
|
83
|
+
import { Shimmer } from "shimmer-trace";
|
|
84
|
+
|
|
85
|
+
export default function ProfilePage() {
|
|
86
|
+
return (
|
|
87
|
+
<Shimmer loading>
|
|
88
|
+
<UserCard />
|
|
89
|
+
</Shimmer>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Notes:
|
|
95
|
+
|
|
96
|
+
- The `<Shimmer>` boundary itself is a Client Component. Children you pass through it must also be renderable as Client Components if they use state, effects, or browser APIs.
|
|
97
|
+
- The library uses `getBoundingClientRect`, `ResizeObserver`, and `useInsertionEffect`, all of which only run in the browser. There is no server-side measurement step — the skeleton appears after hydration.
|
|
98
|
+
- Server Component children that suspend should be wrapped with `ShimmerSuspense` (see [Suspense](#shimmersuspense--suspense-native-loading) below).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
72
102
|
## Quick Start
|
|
73
103
|
|
|
74
104
|
```tsx
|
|
@@ -87,6 +117,19 @@ function ProfilePage() {
|
|
|
87
117
|
|
|
88
118
|
That's it. `shimmer-trace` walks the DOM inside `<UserCard />`, finds every text node, image, input, and button, and draws a shimmer skeleton that matches it exactly.
|
|
89
119
|
|
|
120
|
+
Need realistic shape before real data arrives? Pass a template via `dummyData`:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<Shimmer
|
|
124
|
+
loading={loading}
|
|
125
|
+
dummyData={{ user: { name: "dummy_user", role: "dummy_role", avatar: "" } }}
|
|
126
|
+
>
|
|
127
|
+
<UserCard user={user} />
|
|
128
|
+
</Shimmer>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
See [Examples](#examples) for `dummyLength` (list mode) and `as` (component template) patterns.
|
|
132
|
+
|
|
90
133
|
---
|
|
91
134
|
|
|
92
135
|
## API Reference
|
|
@@ -103,7 +146,10 @@ The core component. Wrap anything with it.
|
|
|
103
146
|
highlightColor="#f5f5f5" // shimmer highlight color
|
|
104
147
|
speed={1.5} // animation duration in seconds
|
|
105
148
|
borderRadius="4px" // override auto-detected border-radius
|
|
149
|
+
preserveBackground={true} // keep card bg/borders visible under shimmer
|
|
150
|
+
dummyData={{ user: tpl }} // inject template props into children
|
|
106
151
|
dummyLength={10} // list mode: number of skeleton items
|
|
152
|
+
as={UserCard} // component template — generate skeletons from a component
|
|
107
153
|
stopPropagation={false} // force this Shimmer to be a master
|
|
108
154
|
className="my-class" // applied to the container div
|
|
109
155
|
style={{ display: "flex" }} // merged into container styles
|
|
@@ -112,19 +158,21 @@ The core component. Wrap anything with it.
|
|
|
112
158
|
</Shimmer>
|
|
113
159
|
```
|
|
114
160
|
|
|
115
|
-
| Prop | Type | Default | Description
|
|
116
|
-
| -------------------- | ------------------------------------------------------ | ----------- |
|
|
117
|
-
| `loading` | `boolean` | `false` | Enables the shimmer skeleton
|
|
118
|
-
| `animation` | `'wave' \| 'pulse' \| 'shine' \| 'glow' \| 'gradient'` | `'wave'` | Animation style
|
|
119
|
-
| `preserveBackground` | `boolean` | `true` | Keep card backgrounds/borders visible while loading
|
|
120
|
-
| `baseColor` | `string` | `'#e0e0e0'` | Base skeleton color
|
|
121
|
-
| `highlightColor` | `string` | `'#f5f5f5'` | Shimmer highlight color
|
|
122
|
-
| `speed` | `number` | `1.5` | Animation speed in seconds
|
|
123
|
-
| `borderRadius` | `string` | auto | Override border-radius on all blocks
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
126
|
-
| `
|
|
127
|
-
| `
|
|
161
|
+
| Prop | Type | Default | Description |
|
|
162
|
+
| -------------------- | ------------------------------------------------------ | ----------- | ------------------------------------------------------------------------ |
|
|
163
|
+
| `loading` | `boolean` | `false` | Enables the shimmer skeleton |
|
|
164
|
+
| `animation` | `'wave' \| 'pulse' \| 'shine' \| 'glow' \| 'gradient'` | `'wave'` | Animation style |
|
|
165
|
+
| `preserveBackground` | `boolean` | `true` | Keep card backgrounds/borders visible while loading |
|
|
166
|
+
| `baseColor` | `string` | `'#e0e0e0'` | Base skeleton color |
|
|
167
|
+
| `highlightColor` | `string` | `'#f5f5f5'` | Shimmer highlight color |
|
|
168
|
+
| `speed` | `number` | `1.5` | Animation speed in seconds |
|
|
169
|
+
| `borderRadius` | `string` | auto | Override border-radius on all blocks |
|
|
170
|
+
| `dummyData` | `Record<string, any>` | — | Props merged into each child while loading (template data, no real API) |
|
|
171
|
+
| `dummyLength` | `number` | — | List mode — clones first child N times (see below) |
|
|
172
|
+
| `as` | `ComponentType<any>` | — | Component template — renders `dummyLength` × `<as {...dummyData} />` |
|
|
173
|
+
| `stopPropagation` | `boolean` | `false` | Force master renderer, ignore parent context |
|
|
174
|
+
| `className` | `string` | — | Class on the container `<div>` |
|
|
175
|
+
| `style` | `CSSProperties` | — | Inline styles on the container `<div>` |
|
|
128
176
|
|
|
129
177
|
---
|
|
130
178
|
|
|
@@ -173,32 +221,74 @@ Works out of the box with inputs, labels, and buttons.
|
|
|
173
221
|
</Shimmer>
|
|
174
222
|
```
|
|
175
223
|
|
|
176
|
-
### 3.
|
|
224
|
+
### 3. Skeleton Shape with `dummyData`
|
|
225
|
+
|
|
226
|
+
No more `data?.name ?? 'Loading...'` ternaries scattered through your component. Pass a template object via `dummyData` and Shimmer clones each child with those props merged on top of its own.
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
const userTemplate = {
|
|
230
|
+
name: "",
|
|
231
|
+
role: "",
|
|
232
|
+
avatar: "",
|
|
233
|
+
bio: "",
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
<Shimmer loading={loading} dummyData={{ user: userTemplate }}>
|
|
237
|
+
<UserCard user={user} />
|
|
238
|
+
</Shimmer>;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
While `loading=true`, `<UserCard>` is cloned with `user={userTemplate}` — giving the shimmer realistic shape even before any data arrives. Once `loading=false`, real props pass through untouched.
|
|
242
|
+
|
|
243
|
+
### 4. List Skeleton with `dummyLength` + `dummyData`
|
|
177
244
|
|
|
178
|
-
Loading a list from an API? `dummyLength` clones
|
|
245
|
+
Loading a list from an API? `dummyLength` clones a template N times so the skeleton shows the right number of rows — even when your array is empty during the first fetch.
|
|
179
246
|
|
|
180
247
|
```tsx
|
|
181
|
-
|
|
248
|
+
const postTemplate = {
|
|
249
|
+
title: "xxxxxxxxxxxxxxxxxxxx",
|
|
250
|
+
author: "xxxxxxxx",
|
|
251
|
+
category: "xxxxx",
|
|
252
|
+
thumbnail: "",
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
<Shimmer
|
|
256
|
+
loading={loading}
|
|
257
|
+
dummyLength={10}
|
|
258
|
+
dummyData={{ post: postTemplate }}
|
|
259
|
+
>
|
|
182
260
|
{posts.map((post) => (
|
|
183
|
-
<
|
|
184
|
-
<img src={post.thumbnail} alt="" />
|
|
185
|
-
<div>
|
|
186
|
-
<h4>{post.title}</h4>
|
|
187
|
-
<span>{post.author}</span>
|
|
188
|
-
</div>
|
|
189
|
-
<span className="badge">{post.category}</span>
|
|
190
|
-
</div>
|
|
261
|
+
<PostRow post={post} key={post.id} />
|
|
191
262
|
))}
|
|
192
|
-
</Shimmer
|
|
263
|
+
</Shimmer>;
|
|
193
264
|
```
|
|
194
265
|
|
|
195
266
|
**How it works:**
|
|
196
267
|
|
|
197
|
-
- `loading=false` → renders your `.map()` output normally
|
|
198
|
-
- `loading=true` → grabs the first
|
|
199
|
-
-
|
|
268
|
+
- `loading=false` → renders your `.map()` output normally.
|
|
269
|
+
- `loading=true` with children → grabs the first child, merges `dummyData` into its props, clones it `dummyLength` times.
|
|
270
|
+
- `loading=true` with empty array → use `as` (next example) so there's a component to clone even with no children.
|
|
271
|
+
|
|
272
|
+
### 5. Component Template with `as`
|
|
273
|
+
|
|
274
|
+
When your array is empty on first render (e.g. `posts = []` before fetch), there's no child to clone. Use `as` to point Shimmer at the component directly — it renders `dummyLength` instances of `<as {...dummyData} />`.
|
|
275
|
+
|
|
276
|
+
```tsx
|
|
277
|
+
<Shimmer
|
|
278
|
+
loading={loading}
|
|
279
|
+
as={PostRow}
|
|
280
|
+
dummyData={{ post: postTemplate }}
|
|
281
|
+
dummyLength={10}
|
|
282
|
+
>
|
|
283
|
+
{posts.map((post) => (
|
|
284
|
+
<PostRow post={post} key={post.id} />
|
|
285
|
+
))}
|
|
286
|
+
</Shimmer>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Cold-start safe — no children needed during loading. Children render normally once `loading=false`.
|
|
200
290
|
|
|
201
|
-
###
|
|
291
|
+
### 6. Synchronized Flex Layout
|
|
202
292
|
|
|
203
293
|
One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wave.
|
|
204
294
|
|
|
@@ -212,7 +302,7 @@ One `<Shimmer>` wraps multiple cards. One overlay. One perfectly synchronized wa
|
|
|
212
302
|
|
|
213
303
|
No separate shimmers per card. One master overlay covers them all — the wave sweeps the entire row in sync.
|
|
214
304
|
|
|
215
|
-
###
|
|
305
|
+
### 7. Custom Colors (Dark Mode)
|
|
216
306
|
|
|
217
307
|
```tsx
|
|
218
308
|
<Shimmer loading={loading} baseColor="#1e1e3a" highlightColor="#2d2d52">
|
|
@@ -261,7 +351,7 @@ import { ShimmerSuspense } from "shimmer-trace";
|
|
|
261
351
|
|
|
262
352
|
### Option A: Explicit template (recommended)
|
|
263
353
|
|
|
264
|
-
|
|
354
|
+
Reuse the same component as its own skeleton — pass it through `template` with template props. No duplicate skeleton component, no ` ` width hacks.
|
|
265
355
|
|
|
266
356
|
```tsx
|
|
267
357
|
function UserCard({ user }) {
|
|
@@ -274,20 +364,20 @@ function UserCard({ user }) {
|
|
|
274
364
|
);
|
|
275
365
|
}
|
|
276
366
|
|
|
277
|
-
//
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
</div>
|
|
284
|
-
);
|
|
367
|
+
// Template data — same shape as real user, no fetch
|
|
368
|
+
const userTemplate = {
|
|
369
|
+
name: "xxxxxxxxxxxxxx",
|
|
370
|
+
bio: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
371
|
+
avatar: "",
|
|
372
|
+
};
|
|
285
373
|
|
|
286
|
-
<ShimmerSuspense template={<
|
|
374
|
+
<ShimmerSuspense template={<UserCard user={userTemplate} />}>
|
|
287
375
|
<UserCard resource={resource} />
|
|
288
376
|
</ShimmerSuspense>;
|
|
289
377
|
```
|
|
290
378
|
|
|
379
|
+
Why a template prop at all (not just `dummyData` like `<Shimmer>`)? Because the real `<UserCard resource={resource} />` throws a Promise during render — it never produces DOM until data resolves. The library can't merge props into a component that's mid-suspend. Rendering a separate, non-suspending instance (same component, template data) gives Shimmer real DOM to trace.
|
|
380
|
+
|
|
291
381
|
### Option B: `useIsShimmering` hook
|
|
292
382
|
|
|
293
383
|
No template needed. The component detects shimmer mode and renders an empty shape.
|
|
@@ -373,19 +463,21 @@ Fine-tune what gets traced with data attributes:
|
|
|
373
463
|
<div data-shimmer-ignore>Never shimmer this</div>
|
|
374
464
|
```
|
|
375
465
|
|
|
466
|
+
`data-shimmer` is also the explicit opt-in for tracing `position: fixed` / `position: sticky` elements — the library skips those by default to avoid scroll drift. See [Known Limitations](#known-limitations) for the trade-off.
|
|
467
|
+
|
|
376
468
|
---
|
|
377
469
|
|
|
378
470
|
## How It Works
|
|
379
471
|
|
|
380
|
-
1. **
|
|
472
|
+
1. **Inject keyframes pre-paint** — On first mount, the library inserts its `@keyframes` and `preserveBackground` CSS rules into `document.head` via `useInsertionEffect`. This runs before any layout effect or paint, so the wave animation is live on the very first frame — no flash of unstyled (static) shimmer blocks.
|
|
381
473
|
|
|
382
|
-
2. **
|
|
474
|
+
2. **Render real DOM** — `Shimmer` renders children normally. With `preserveBackground` (default), CSS rules hide text (`color: transparent`) and media (`opacity: 0`) on leaf elements while keeping container backgrounds, borders, and padding fully visible. Layout stays identical — zero CLS.
|
|
383
475
|
|
|
384
|
-
3. **
|
|
476
|
+
3. **Walk the DOM** — `useTrace` recursively traverses the container, collecting every traceable element: headings, paragraphs, images, inputs, buttons, and leaf nodes with visible dimensions.
|
|
385
477
|
|
|
386
|
-
4. **
|
|
478
|
+
4. **Measure everything** — Each element is measured with `getBoundingClientRect()` relative to the master container, capturing position, size, and computed `border-radius`.
|
|
387
479
|
|
|
388
|
-
5. **
|
|
480
|
+
5. **Build the overlay** — One absolutely-positioned `<div>` is rendered per traced rect, sized and positioned to match exactly. For sweep animations (`wave`, `shine`), each block also gets a gradient layer that spans the full container width — the highlight sweeps across all blocks in perfect sync.
|
|
389
481
|
|
|
390
482
|
6. **Re-trace on resize** — `ResizeObserver` watches the container and re-measures on every resize, keeping skeletons accurate at any screen size.
|
|
391
483
|
|
|
@@ -400,24 +492,53 @@ import type {
|
|
|
400
492
|
ShimmerProps, // Props for <Shimmer>
|
|
401
493
|
ShimmerConfig, // Config options (colors, speed, animation)
|
|
402
494
|
ShimmerRect, // Measured element rectangle
|
|
403
|
-
AnimationType, // 'wave' | 'pulse' | '
|
|
404
|
-
ShimmerSuspenseProps,
|
|
495
|
+
AnimationType, // 'wave' | 'pulse' | 'shine' | 'glow' | 'gradient'
|
|
496
|
+
ShimmerSuspenseProps, // Props for <ShimmerSuspense>
|
|
405
497
|
} from "shimmer-trace";
|
|
406
498
|
```
|
|
407
499
|
|
|
500
|
+
Runtime exports:
|
|
501
|
+
|
|
502
|
+
```ts
|
|
503
|
+
import {
|
|
504
|
+
Shimmer,
|
|
505
|
+
createShimmer,
|
|
506
|
+
ShimmerSuspense,
|
|
507
|
+
ShimmerContext,
|
|
508
|
+
useShimmerContext,
|
|
509
|
+
useIsShimmering,
|
|
510
|
+
} from "shimmer-trace";
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
---
|
|
514
|
+
|
|
515
|
+
## Known Limitations
|
|
516
|
+
|
|
517
|
+
These are real edge cases the library does **not** currently handle gracefully. Worth knowing before you wrap something complex.
|
|
518
|
+
|
|
519
|
+
- **React Portals are skipped silently.** `useTrace` walks the subtree of the Master container; anything rendered into a portal (e.g. `createPortal(<Modal/>, document.body)`) is mounted outside that subtree, so it never gets measured and never gets a shimmer block. Use `<Shimmer>` *inside* the portal target if you need it shimmered there.
|
|
520
|
+
- **`position: fixed` / `position: sticky` children are auto-skipped.** Their coordinate space cannot follow the Master container during scroll, so the overlay block would drift. The library detects them during the trace walk and silently skips the entire subtree, emitting one `console.warn` per Master container (deduped via a `WeakSet`, so resize-driven re-traces never re-warn). The warning fires in production too — it's an actionable signal, not log noise. Two workarounds:
|
|
521
|
+
- **Recommended:** put a nested `<Shimmer>` *inside* the fixed element. The inner Master sits in the fixed coordinate space and aligns correctly.
|
|
522
|
+
- **Override:** add `data-shimmer` to the fixed element to force-trace it. The block will drift on scroll — you accept the trade-off.
|
|
523
|
+
- **Heavily-transformed ancestors.** If an ancestor of the Master container has a CSS `transform`, the overlay sits inside the same transformed space and usually renders correctly. If a *descendant* has a transform that the rect math doesn't expect, the overlay block may not line up.
|
|
524
|
+
- **Suspending Server Component children.** A Server Component that suspends inside `<Shimmer>` (without `ShimmerSuspense`) won't produce DOM for the library to measure, so the skeleton appears empty. Use `ShimmerSuspense` for the suspending boundary, or pass template data via `dummyData`.
|
|
525
|
+
|
|
526
|
+
If you hit one of these in a real app, please open an issue with a reproduction.
|
|
527
|
+
|
|
408
528
|
---
|
|
409
529
|
|
|
410
530
|
## Comparison
|
|
411
531
|
|
|
412
|
-
| | shimmer-trace
|
|
413
|
-
| ---------------------- |
|
|
414
|
-
| Manual skeleton code | ❌ None
|
|
415
|
-
| Matches real layout | ✅ Automatically
|
|
416
|
-
|
|
|
417
|
-
|
|
|
418
|
-
|
|
|
419
|
-
|
|
|
420
|
-
|
|
|
532
|
+
| | shimmer-trace | react-loading-skeleton | MUI Skeleton |
|
|
533
|
+
| ---------------------- | ------------------------- | ---------------------- | -------------- |
|
|
534
|
+
| Manual skeleton code | ❌ None | ✅ Required | ✅ Required |
|
|
535
|
+
| Matches real layout | ✅ Automatically | ⚠️ Manual | ⚠️ Manual |
|
|
536
|
+
| Template data | ✅ `dummyData` | ❌ | ❌ |
|
|
537
|
+
| List mode | ✅ `dummyLength` / `as` | ❌ | ❌ |
|
|
538
|
+
| Suspense support | ✅ Native | ❌ | ❌ |
|
|
539
|
+
| Synchronized animation | ✅ One overlay | ⚠️ Per-element | ⚠️ Per-element |
|
|
540
|
+
| Zero layout shift | ✅ | ⚠️ | ⚠️ |
|
|
541
|
+
| Bundle size | ~3kb | ~5kb | ~12kb |
|
|
421
542
|
|
|
422
543
|
---
|
|
423
544
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
'use client';
|
|
1
2
|
'use strict';
|
|
2
3
|
|
|
3
4
|
var React2 = require('react');
|
|
@@ -133,9 +134,8 @@ var ShimmerOverlay = ({
|
|
|
133
134
|
};
|
|
134
135
|
|
|
135
136
|
// src/utils.ts
|
|
136
|
-
var counter = 0;
|
|
137
137
|
function generateShimmerKey(prefix = "shimmer") {
|
|
138
|
-
return
|
|
138
|
+
return prefix;
|
|
139
139
|
}
|
|
140
140
|
var FALLBACK_DIMENSIONS = {
|
|
141
141
|
INPUT: { width: 200, height: 36 },
|
|
@@ -196,13 +196,23 @@ function isTraceable(el) {
|
|
|
196
196
|
}
|
|
197
197
|
return false;
|
|
198
198
|
}
|
|
199
|
+
function isViewportLocked(el) {
|
|
200
|
+
const pos = window.getComputedStyle(el).position;
|
|
201
|
+
return pos === "fixed" || pos === "sticky";
|
|
202
|
+
}
|
|
203
|
+
var warnedContainers = /* @__PURE__ */ new WeakSet();
|
|
199
204
|
function collectTraceableElements(root) {
|
|
200
|
-
const
|
|
205
|
+
const traced = [];
|
|
206
|
+
let skippedFixed = 0;
|
|
201
207
|
function walk(el) {
|
|
202
208
|
if (el.hasAttribute("data-shimmer-ignore")) return;
|
|
203
209
|
if (el.hasAttribute("data-shimmer-reporter")) return;
|
|
210
|
+
if (!el.hasAttribute("data-shimmer") && isViewportLocked(el)) {
|
|
211
|
+
skippedFixed += 1;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
204
214
|
if (isTraceable(el)) {
|
|
205
|
-
|
|
215
|
+
traced.push(el);
|
|
206
216
|
return;
|
|
207
217
|
}
|
|
208
218
|
for (let i = 0; i < el.children.length; i++) {
|
|
@@ -212,7 +222,7 @@ function collectTraceableElements(root) {
|
|
|
212
222
|
for (let i = 0; i < root.children.length; i++) {
|
|
213
223
|
walk(root.children[i]);
|
|
214
224
|
}
|
|
215
|
-
return
|
|
225
|
+
return { traced, skippedFixed };
|
|
216
226
|
}
|
|
217
227
|
function measureElement(el, containerRect, globalBorderRadius) {
|
|
218
228
|
const elRect = el.getBoundingClientRect();
|
|
@@ -245,8 +255,14 @@ function measureElement(el, containerRect, globalBorderRadius) {
|
|
|
245
255
|
function performTrace(container, globalBorderRadius, anchorRef) {
|
|
246
256
|
const anchor = anchorRef?.current ?? container;
|
|
247
257
|
const anchorRect = anchor.getBoundingClientRect();
|
|
248
|
-
const
|
|
249
|
-
|
|
258
|
+
const { traced, skippedFixed } = collectTraceableElements(container);
|
|
259
|
+
if (skippedFixed > 0 && !warnedContainers.has(container)) {
|
|
260
|
+
warnedContainers.add(container);
|
|
261
|
+
console.warn(
|
|
262
|
+
`[shimmer-trace] Skipped ${skippedFixed} element(s) with position:fixed or position:sticky. Their coordinate space cannot follow the Master container during scroll, so the overlay block would drift. Render a nested <Shimmer> inside the fixed/sticky element if you need a skeleton there, or add data-shimmer to opt in (positioning may still drift on scroll).`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
return traced.map((el) => measureElement(el, anchorRect, globalBorderRadius)).filter((r) => r !== null && r.width > 0 && r.height > 0);
|
|
250
266
|
}
|
|
251
267
|
function useTrace(containerRef, loading, globalBorderRadius, anchorRef) {
|
|
252
268
|
const [rects, setRects] = React2.useState([]);
|
|
@@ -414,7 +430,7 @@ function MasterShimmer({
|
|
|
414
430
|
return next;
|
|
415
431
|
});
|
|
416
432
|
}, []);
|
|
417
|
-
|
|
433
|
+
React2.useInsertionEffect(() => {
|
|
418
434
|
injectStyles();
|
|
419
435
|
}, []);
|
|
420
436
|
const tracedRects = useTrace(
|
|
@@ -597,5 +613,5 @@ exports.ShimmerSuspense = ShimmerSuspense;
|
|
|
597
613
|
exports.createShimmer = createShimmer;
|
|
598
614
|
exports.useIsShimmering = useIsShimmering;
|
|
599
615
|
exports.useShimmerContext = useShimmerContext;
|
|
600
|
-
//# sourceMappingURL=index.
|
|
601
|
-
//# sourceMappingURL=index.
|
|
616
|
+
//# sourceMappingURL=index.cjs.map
|
|
617
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/ShimmerContext.tsx","../src/ShimmerOverlay.tsx","../src/utils.ts","../src/useTrace.ts","../src/styles.ts","../src/Shimmer.tsx","../src/createShimmer.tsx","../src/ShimmerSuspense.tsx"],"names":["createContext","useContext","jsx","React","useState","useCallback","useLayoutEffect","useId","useMemo","useRef","useInsertionEffect","jsxs","createElement"],"mappings":"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","file":"index.cjs","sourcesContent":["import React, { ReactNode } from 'react';\r\n\r\n/**\r\n * Represents a measured rectangle of a traced DOM element,\r\n * positioned relative to the Master Shimmer container.\r\n */\r\nexport interface ShimmerRect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n borderRadius: string;\r\n}\r\n\r\n/** Available animation types for the shimmer effect. */\r\nexport type AnimationType =\r\n | 'wave'\r\n | 'pulse'\r\n | 'shine'\r\n | 'glow'\r\n | 'gradient';\r\n\r\n/** Configuration options for the shimmer effect (all optional). */\r\nexport interface ShimmerConfig {\r\n /** Animation style. Defaults to 'wave'. */\r\n animation?: AnimationType;\r\n /** Base color of the shimmer blocks. Defaults to '#e0e0e0'. */\r\n baseColor?: string;\r\n /** Highlight color of the shimmer animation. Defaults to '#f5f5f5'. */\r\n highlightColor?: string;\r\n /** Animation duration in seconds. Defaults to 1.5. */\r\n speed?: number;\r\n /** Global border-radius override. If omitted, auto-detected from each element (defaults to 4px if detection is 0px). */\r\n borderRadius?: string;\r\n /**\r\n * Keep container backgrounds, borders, and padding visible while loading.\r\n * When `true` (default), only text and media leaves are hidden via\r\n * `color:transparent` / `opacity:0` so card backgrounds, borders, and\r\n * spacing remain visible underneath the shimmer overlay.\r\n *\r\n * Set `false` for legacy behavior (`visibility:hidden` on whole tree).\r\n */\r\n preserveBackground?: boolean;\r\n}\r\n\r\n/** Props for the Shimmer component. */\r\nexport interface ShimmerProps extends ShimmerConfig {\r\n /** Whether the loading state is active. */\r\n loading?: boolean;\r\n /** The children to trace and render shimmer over. */\r\n children: ReactNode;\r\n /**\r\n * Number of placeholder clones to generate for list-like loading states.\r\n *\r\n * When `loading=true` and `dummyLength` is set, Shimmer grabs the first\r\n * available child (or a cached template from the last loaded render) and\r\n * clones it `dummyLength` times to produce skeleton placeholders.\r\n *\r\n * When `loading=false`, children are rendered as-is.\r\n */\r\n dummyLength?: number;\r\n /**\r\n * Props injected into each child element while `loading=true` so the\r\n * skeleton renders with realistic shape without requiring real data.\r\n *\r\n * Example:\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyData={{ user: { name: 'Loading...', role: '...', avatar: '' } }}\r\n * >\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * While loading, each direct child is cloned with these props merged on top\r\n * of its own props. Ignored when `loading=false`.\r\n */\r\n dummyData?: Record<string, any>;\r\n /**\r\n * Component used to auto-generate skeleton elements while `loading=true`.\r\n *\r\n * When set, Shimmer ignores `children` during loading and renders\r\n * `dummyLength` (defaults to 1) instances of `<as {...dummyData} />`\r\n * to derive shape. Real children render once `loading=false`.\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * as={MovieCard}\r\n * dummyData={{ movie: movieTemplate }}\r\n * dummyLength={10}\r\n * >\r\n * {movies.map((m) => <MovieCard movie={m} key={m.id} />)}\r\n * </Shimmer>\r\n * ```\r\n */\r\n as?: React.ComponentType<any>;\r\n /** Force this Shimmer to be a Master renderer even if nested inside another Shimmer. */\r\n stopPropagation?: boolean;\r\n /**\r\n * className applied to the Master container div.\r\n * Use to control layout (e.g. display:flex) without losing position:relative.\r\n */\r\n className?: string;\r\n /**\r\n * Inline styles merged into the Master container div.\r\n * position:relative is always applied; everything else is overridable.\r\n */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULTS: Required<ShimmerConfig> = {\r\n animation: 'wave',\r\n baseColor: '#e0e0e0',\r\n highlightColor: '#f5f5f5',\r\n speed: 1.5,\r\n borderRadius: '',\r\n preserveBackground: true,\r\n};\r\n","\"use client\";\r\n\r\nimport { createContext, useContext, RefObject } from \"react\";\r\nimport { ShimmerRect, ShimmerConfig } from \"./types\";\r\n\r\nexport interface ShimmerContextValue {\r\n\tregister: (id: string, rects: ShimmerRect[]) => void;\r\n\tunregister: (id: string) => void;\r\n\t/** Ref object (not .current) so Reporters always read a fresh value. */\r\n\tmasterRef: RefObject<HTMLElement | null>;\r\n\tloading: boolean;\r\n\tconfig: Required<ShimmerConfig>;\r\n}\r\n\r\nexport const ShimmerContext = createContext<ShimmerContextValue | null>(null);\r\n\r\nexport function useShimmerContext(): ShimmerContextValue | null {\r\n\treturn useContext(ShimmerContext);\r\n}\r\n\r\n/**\r\n * True when rendered inside a ShimmerSuspense fallback (Option B).\r\n * Components use this to skip data fetching and return an empty shape.\r\n */\r\nexport const IsShimmeringContext = createContext<boolean>(false);\r\n\r\nexport function useIsShimmering(): boolean {\r\n\treturn useContext(IsShimmeringContext);\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { ShimmerRect, AnimationType } from './types';\r\n\r\ninterface ShimmerOverlayProps {\r\n rects: ShimmerRect[];\r\n animation: AnimationType;\r\n baseColor: string;\r\n highlightColor: string;\r\n speed: number;\r\n}\r\n\r\n/**\r\n * Returns animation-specific inline styles for each shimmer block.\r\n * For sweep-style animations (wave, shine), the colored gradient is rendered\r\n * as a child layer so the sweep can extend across the container in sync.\r\n */\r\nfunction getBlockStyles(\r\n animation: AnimationType,\r\n baseColor: string,\r\n highlightColor: string,\r\n speed: number,\r\n rect: ShimmerRect,\r\n): React.CSSProperties {\r\n const base: React.CSSProperties = {\r\n position: 'absolute',\r\n top: rect.y,\r\n left: rect.x,\r\n width: rect.width,\r\n height: rect.height,\r\n borderRadius: rect.borderRadius,\r\n overflow: 'hidden',\r\n };\r\n\r\n switch (animation) {\r\n case 'wave':\r\n case 'shine':\r\n return { ...base, background: baseColor };\r\n case 'pulse':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-pulse ${speed}s ease-in-out infinite`,\r\n };\r\n case 'glow':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-glow ${speed}s ease-in-out infinite`,\r\n };\r\n case 'gradient':\r\n return {\r\n ...base,\r\n backgroundImage: `linear-gradient(90deg, ${baseColor}, ${highlightColor}, ${baseColor})`,\r\n backgroundSize: '200% 100%',\r\n animation: `shimmer-gradient ${speed * 1.5}s ease-in-out infinite`,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Sweeping shine layer used by `wave` and `shine` animations.\r\n * Spans the full container width so the highlight sweeps in sync across all blocks.\r\n */\r\nconst SweepLayer: React.FC<{\r\n rect: ShimmerRect;\r\n highlightColor: string;\r\n speed: number;\r\n containerWidth: number;\r\n variant: 'wave' | 'shine';\r\n}> = ({ rect, highlightColor, speed, containerWidth, variant }) => {\r\n const isShine = variant === 'shine';\r\n return (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: -rect.x,\r\n width: containerWidth > 0 ? containerWidth : '100vw',\r\n height: '100%',\r\n background: isShine\r\n ? `linear-gradient(115deg, transparent 30%, ${highlightColor} 50%, transparent 70%)`\r\n : `linear-gradient(90deg, transparent 0%, ${highlightColor} 50%, transparent 100%)`,\r\n animation: `${isShine ? 'shimmer-shine' : 'shimmer-wave'} ${speed}s ease-in-out infinite`,\r\n }}\r\n />\r\n );\r\n};\r\n\r\n/**\r\n * The overlay component rendered by the Master Shimmer.\r\n *\r\n * Renders one absolutely-positioned div per traced rect. Sweep-style\r\n * animations (`wave`, `shine`) get an additional gradient layer that spans\r\n * the container so the highlight passes across all blocks in sync.\r\n */\r\nexport const ShimmerOverlay: React.FC<ShimmerOverlayProps> = ({\r\n rects,\r\n animation,\r\n baseColor,\r\n highlightColor,\r\n speed,\r\n}) => {\r\n const overlayRef = React.useRef<HTMLDivElement>(null);\r\n const [containerWidth, setContainerWidth] = React.useState(0);\r\n\r\n React.useLayoutEffect(() => {\r\n if (!overlayRef.current?.parentElement) return;\r\n setContainerWidth(overlayRef.current.parentElement.offsetWidth);\r\n }, [rects]);\r\n\r\n if (rects.length === 0) return null;\r\n\r\n const isSweep = animation === 'wave' || animation === 'shine';\r\n\r\n return (\r\n <div\r\n ref={overlayRef}\r\n role=\"status\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading content\"\r\n data-shimmer-ignore=\"true\"\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n zIndex: 1,\r\n pointerEvents: 'none',\r\n visibility: 'visible',\r\n }}\r\n >\r\n {rects.map((rect, i) => (\r\n <div\r\n key={i}\r\n style={getBlockStyles(animation, baseColor, highlightColor, speed, rect)}\r\n >\r\n {isSweep && (\r\n <SweepLayer\r\n rect={rect}\r\n highlightColor={highlightColor}\r\n speed={speed}\r\n containerWidth={containerWidth}\r\n variant={animation as 'wave' | 'shine'}\r\n />\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * Generates a deterministic key for cloned elements.\r\n *\r\n * Prefix already includes the parent Shimmer's `useId()` plus a positional\r\n * index from the caller, so it is unique per render slot and stable across\r\n * SSR + client hydration. No module-scope counter — that caused hydration\r\n * mismatches and forced React to remount cloned children every render.\r\n */\r\nexport function generateShimmerKey(prefix: string = 'shimmer'): string {\r\n return prefix;\r\n}\r\n\r\n/**\r\n * Default fallback dimensions for common elements when their\r\n * measured dimensions are 0px (e.g., empty inputs, images not yet loaded).\r\n */\r\nexport const FALLBACK_DIMENSIONS: Record<string, { width: number; height: number }> = {\r\n INPUT: { width: 200, height: 36 },\r\n BUTTON: { width: 120, height: 36 },\r\n TEXTAREA: { width: 300, height: 80 },\r\n SELECT: { width: 200, height: 36 },\r\n IMG: { width: 100, height: 100 },\r\n H1: { width: 300, height: 36 },\r\n H2: { width: 260, height: 30 },\r\n H3: { width: 220, height: 26 },\r\n H4: { width: 200, height: 22 },\r\n H5: { width: 180, height: 20 },\r\n H6: { width: 160, height: 18 },\r\n P: { width: 250, height: 16 },\r\n SPAN: { width: 100, height: 16 },\r\n};\r\n","'use client';\r\n\r\nimport { useLayoutEffect, useState, RefObject, useCallback } from 'react';\r\nimport { ShimmerRect } from './types';\r\nimport { FALLBACK_DIMENSIONS } from './utils';\r\n\r\n/**\r\n * Tags that are always considered \"traceable\" leaf elements\r\n * whose dimensions should be captured for the shimmer overlay.\r\n */\r\nconst TRACEABLE_TAGS = new Set([\r\n // Text\r\n 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LI',\r\n 'LABEL', 'TD', 'TH', 'BLOCKQUOTE', 'CODE', 'PRE',\r\n // Media\r\n 'IMG', 'VIDEO', 'SVG', 'CANVAS', 'PICTURE',\r\n // Form\r\n 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON',\r\n // Misc\r\n 'HR',\r\n]);\r\n\r\n/**\r\n * Determines if an element should be traced.\r\n * Explicit data attributes override automatic detection.\r\n */\r\nfunction isTraceable(el: Element): boolean {\r\n if (el.hasAttribute('data-shimmer-ignore')) return false;\r\n if (el.hasAttribute('data-shimmer')) return true;\r\n if (TRACEABLE_TAGS.has(el.tagName)) return true;\r\n\r\n // Leaf element with visible dimensions → trace it\r\n if (el.children.length === 0) {\r\n const rect = el.getBoundingClientRect();\r\n return rect.width > 0 && rect.height > 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Detects elements whose coordinate space cannot follow the Master\r\n * container during scroll. `position: fixed` and `position: sticky`\r\n * both live in a different scroll context (viewport / nearest scroll\r\n * container) than the Master, so any container-relative rect we compute\r\n * for them becomes stale on the first scroll.\r\n *\r\n * Returning `true` here causes the entire subtree under `el` to be\r\n * skipped, since descendants of a fixed/sticky element inherit the same\r\n * broken coordinate space.\r\n */\r\nfunction isViewportLocked(el: Element): boolean {\r\n const pos = window.getComputedStyle(el).position;\r\n return pos === 'fixed' || pos === 'sticky';\r\n}\r\n\r\n/**\r\n * Tracks Master containers we've already warned about, so the\r\n * console.warn fires once per container instead of on every resize-driven\r\n * re-trace. WeakSet keeps the dedupe noise-free across container churn —\r\n * a fresh Master gets one warning, period.\r\n */\r\nconst warnedContainers = new WeakSet<Element>();\r\n\r\ninterface CollectResult {\r\n traced: Element[];\r\n skippedFixed: number;\r\n}\r\n\r\n/**\r\n * Recursively walks the DOM tree and collects all traceable elements.\r\n *\r\n * Also reports how many elements were silently skipped because they used\r\n * `position: fixed` / `position: sticky`. Users can override this skip\r\n * by adding `data-shimmer` to the element, but the resulting overlay\r\n * block will drift on scroll — that's their choice to make.\r\n */\r\nfunction collectTraceableElements(root: Element): CollectResult {\r\n const traced: Element[] = [];\r\n let skippedFixed = 0;\r\n\r\n function walk(el: Element) {\r\n if (el.hasAttribute('data-shimmer-ignore')) return;\r\n if (el.hasAttribute('data-shimmer-reporter')) return; // Ignore nested reporters, they report their own rects\r\n\r\n // `data-shimmer` is an explicit opt-in — trust the user and trace.\r\n // Without it, fixed/sticky elements are skipped entirely (including\r\n // their descendants, which share the broken coordinate space).\r\n if (!el.hasAttribute('data-shimmer') && isViewportLocked(el)) {\r\n skippedFixed += 1;\r\n return;\r\n }\r\n\r\n if (isTraceable(el)) {\r\n traced.push(el);\r\n return; // Don't recurse into traced elements\r\n }\r\n\r\n for (let i = 0; i < el.children.length; i++) {\r\n walk(el.children[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < root.children.length; i++) {\r\n walk(root.children[i]);\r\n }\r\n\r\n return { traced, skippedFixed };\r\n}\r\n\r\n/**\r\n * Measures an element and returns its ShimmerRect relative to the container.\r\n */\r\nfunction measureElement(\r\n el: Element,\r\n containerRect: DOMRect,\r\n globalBorderRadius?: string,\r\n): ShimmerRect | null {\r\n const elRect = el.getBoundingClientRect();\r\n\r\n // If the element is display: none or detached, getBoundingClientRect returns all zeros.\r\n // We must not trace it, otherwise it ends up at the left edge of the screen.\r\n if (elRect.width === 0 && elRect.height === 0 && elRect.left === 0 && elRect.top === 0) {\r\n return null;\r\n }\r\n\r\n const computedStyle = window.getComputedStyle(el);\r\n let borderRadius = globalBorderRadius || computedStyle.borderRadius;\r\n\r\n // If the element has no border radius (common for text tags),\r\n // apply a small default to avoid sharp edges in the shimmer.\r\n const isZero = !borderRadius || borderRadius === 'none' || borderRadius.split(' ').every(v => v === '0' || v === '0px' || v === '0%');\r\n if (isZero) {\r\n borderRadius = '4px';\r\n }\r\n\r\n let width = elRect.width;\r\n let height = elRect.height;\r\n\r\n // Apply fallback dimensions if element has 0 size but is actually visible (e.g. empty inline element)\r\n if (width === 0 || height === 0) {\r\n const fallback = FALLBACK_DIMENSIONS[el.tagName];\r\n if (fallback) {\r\n width = width || fallback.width;\r\n height = height || fallback.height;\r\n }\r\n }\r\n\r\n return {\r\n x: elRect.left - containerRect.left,\r\n y: elRect.top - containerRect.top,\r\n width,\r\n height,\r\n borderRadius,\r\n };\r\n}\r\n\r\n/**\r\n * Performs a full trace of all traceable elements within the container.\r\n *\r\n * @param anchorRef - When provided (Reporter mode), elements are measured\r\n * relative to this element instead of the container. Allows Reporter's\r\n * wrapper to use display:contents without breaking measurement.\r\n */\r\nfunction performTrace(\r\n container: HTMLElement,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const anchor = anchorRef?.current ?? container;\r\n const anchorRect = anchor.getBoundingClientRect();\r\n const { traced, skippedFixed } = collectTraceableElements(container);\r\n\r\n if (skippedFixed > 0 && !warnedContainers.has(container)) {\r\n warnedContainers.add(container);\r\n // eslint-disable-next-line no-console\r\n console.warn(\r\n `[shimmer-trace] Skipped ${skippedFixed} element(s) with ` +\r\n `position:fixed or position:sticky. Their coordinate space ` +\r\n `cannot follow the Master container during scroll, so the overlay ` +\r\n `block would drift. Render a nested <Shimmer> inside the fixed/sticky ` +\r\n `element if you need a skeleton there, or add data-shimmer to opt in ` +\r\n `(positioning may still drift on scroll).`,\r\n );\r\n }\r\n\r\n return traced\r\n .map((el) => measureElement(el, anchorRect, globalBorderRadius))\r\n .filter((r): r is ShimmerRect => r !== null && r.width > 0 && r.height > 0);\r\n}\r\n\r\n/**\r\n * Hook that traces all visible leaf DOM elements within a container\r\n * and returns their measured ShimmerRects.\r\n *\r\n * Uses ResizeObserver to re-trace on container resize.\r\n *\r\n * @param anchorRef - When set, rects are relative to this element (Master).\r\n * Used by Reporter so its display:contents wrapper doesn't break measurement.\r\n */\r\nexport function useTrace(\r\n containerRef: RefObject<HTMLElement | null>,\r\n loading: boolean,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const [rects, setRects] = useState<ShimmerRect[]>([]);\r\n\r\n const trace = useCallback(() => {\r\n if (!containerRef.current) return;\r\n const traced = performTrace(containerRef.current, globalBorderRadius, anchorRef);\r\n setRects(traced);\r\n }, [containerRef, globalBorderRadius, anchorRef]);\r\n\r\n useLayoutEffect(() => {\r\n if (!loading || !containerRef.current) {\r\n setRects([]);\r\n return;\r\n }\r\n\r\n // Initial trace\r\n trace();\r\n\r\n // Re-trace on resize\r\n const observer = new ResizeObserver(() => {\r\n trace();\r\n });\r\n observer.observe(containerRef.current);\r\n\r\n return () => observer.disconnect();\r\n }, [loading, trace]);\r\n\r\n return rects;\r\n}\r\n","const SHIMMER_STYLES_ID = 'shimmer-trace-styles';\r\n\r\nconst CSS = `\r\n@keyframes shimmer-wave {\r\n 0% { transform: translateX(-100%); }\r\n 100% { transform: translateX(100%); }\r\n}\r\n\r\n@keyframes shimmer-pulse {\r\n 0%, 100% { opacity: 0.4; }\r\n 50% { opacity: 1; }\r\n}\r\n\r\n@keyframes shimmer-shine {\r\n 0% { transform: translateX(-150%) skewX(-20deg); }\r\n 100% { transform: translateX(150%) skewX(-20deg); }\r\n}\r\n\r\n@keyframes shimmer-glow {\r\n 0%, 100% { filter: brightness(1); }\r\n 50% { filter: brightness(1.35); }\r\n}\r\n\r\n@keyframes shimmer-gradient {\r\n 0% { background-position: 0% 50%; }\r\n 50% { background-position: 100% 50%; }\r\n 100% { background-position: 0% 50%; }\r\n}\r\n\r\n/* preserveBackground mode: hide text + media but keep container styles */\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(h1,h2,h3,h4,h5,h6,p,span,a,li,label,td,th,blockquote,code,pre,strong,em,small) {\r\n color: transparent !important;\r\n text-shadow: none !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(img,video,svg,canvas,picture) {\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(input,textarea,select,button) {\r\n color: transparent !important;\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] {\r\n pointer-events: none !important;\r\n user-select: none !important;\r\n}\r\n`;\r\n\r\n/**\r\n * Injects the shimmer keyframe animations into the document head.\r\n * Safe to call multiple times — only injects once.\r\n */\r\nexport function injectStyles(): void {\r\n if (typeof document === 'undefined') return;\r\n if (document.getElementById(SHIMMER_STYLES_ID)) return;\r\n\r\n const style = document.createElement('style');\r\n style.id = SHIMMER_STYLES_ID;\r\n style.textContent = CSS;\r\n document.head.appendChild(style);\r\n}\r\n","'use client';\r\n\r\nimport React, { useRef, useCallback, useState, useId, useMemo, useInsertionEffect } from \"react\";\r\nimport { ShimmerProps, ShimmerRect, DEFAULTS } from \"./types\";\r\nimport { ShimmerContext, useShimmerContext } from \"./ShimmerContext\";\r\nimport { ShimmerOverlay } from \"./ShimmerOverlay\";\r\nimport { useTrace } from \"./useTrace\";\r\nimport { injectStyles } from \"./styles\";\r\nimport { generateShimmerKey } from \"./utils\";\r\n\r\n/**\r\n * The main Shimmer component.\r\n *\r\n * Auto-detects **Master** (no parent Shimmer) vs **Reporter** (nested).\r\n * - Master: renders children hidden, traces DOM, paints overlay.\r\n * - Reporter: measures own rects, reports to parent Master.\r\n *\r\n * ### Skeleton shape via `dummyData`\r\n *\r\n * Pass `dummyData` so children render with realistic data while loading.\r\n * No render-prop, no manual `data || fallback` in JSX.\r\n *\r\n * ```tsx\r\n * const userTemplate = { name: 'Loading...', role: '...', avatar: '' };\r\n *\r\n * <Shimmer loading={loading} dummyData={{ user: userTemplate }}>\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * ### List mode (`dummyLength`)\r\n *\r\n * Combined with `dummyData`, clones the first child N times with\r\n * template props merged in:\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyLength={5}\r\n * dummyData={{ fruit: { name: 'xxxxx', price: '$0.00' } }}\r\n * >\r\n * <FruitCard fruit={undefined as any} />\r\n * </Shimmer>\r\n * ```\r\n */\r\nexport function Shimmer({\r\n\tloading = false,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tstopPropagation = false,\r\n\tanimation,\r\n\tbaseColor,\r\n\thighlightColor,\r\n\tspeed,\r\n\tborderRadius,\r\n\tpreserveBackground,\r\n\tclassName,\r\n\tstyle,\r\n}: ShimmerProps) {\r\n\tconst parentContext = useShimmerContext();\r\n\tconst isMaster = !parentContext || stopPropagation;\r\n\tconst id = useId();\r\n\r\n\tconst config = useMemo(\r\n\t\t() => ({\r\n\t\t\tanimation:\r\n\t\t\t\tanimation ?? parentContext?.config.animation ?? DEFAULTS.animation,\r\n\t\t\tbaseColor:\r\n\t\t\t\tbaseColor ?? parentContext?.config.baseColor ?? DEFAULTS.baseColor,\r\n\t\t\thighlightColor:\r\n\t\t\t\thighlightColor ??\r\n\t\t\t\tparentContext?.config.highlightColor ??\r\n\t\t\t\tDEFAULTS.highlightColor,\r\n\t\t\tspeed: speed ?? parentContext?.config.speed ?? DEFAULTS.speed,\r\n\t\t\tborderRadius:\r\n\t\t\t\tborderRadius ??\r\n\t\t\t\tparentContext?.config.borderRadius ??\r\n\t\t\t\tDEFAULTS.borderRadius,\r\n\t\t\tpreserveBackground:\r\n\t\t\t\tpreserveBackground ??\r\n\t\t\t\tparentContext?.config.preserveBackground ??\r\n\t\t\t\tDEFAULTS.preserveBackground,\r\n\t\t}),\r\n\t\t[\r\n\t\t\tanimation,\r\n\t\t\tbaseColor,\r\n\t\t\thighlightColor,\r\n\t\t\tspeed,\r\n\t\t\tborderRadius,\r\n\t\t\tpreserveBackground,\r\n\t\t\tparentContext?.config,\r\n\t\t],\r\n\t);\r\n\r\n\tif (isMaster) {\r\n\t\treturn (\r\n\t\t\t<MasterShimmer\r\n\t\t\t\tid={id}\r\n\t\t\t\tloading={loading}\r\n\t\t\t\tconfig={config}\r\n\t\t\t\tdummyLength={dummyLength}\r\n\t\t\t\tdummyData={dummyData}\r\n\t\t\t\tas={as}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={style}\r\n\t\t\t>\r\n\t\t\t\t{children}\r\n\t\t\t</MasterShimmer>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<ReporterShimmer\r\n\t\t\tid={id}\r\n\t\t\tparentContext={parentContext!}\r\n\t\t\tconfig={config}\r\n\t\t\tdummyLength={dummyLength}\r\n\t\t\tdummyData={dummyData}\r\n\t\t\tas={as}\r\n\t\t>\r\n\t\t\t{children}\r\n\t\t</ReporterShimmer>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Master ─────────────────── */\r\n\r\ninterface MasterShimmerProps {\r\n\tid: string;\r\n\tloading: boolean;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tclassName?: string;\r\n\tstyle?: React.CSSProperties;\r\n}\r\n\r\nfunction MasterShimmer({\r\n\tid,\r\n\tloading,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tclassName,\r\n\tstyle,\r\n}: MasterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst [reporterRects, setReporterRects] = useState<\r\n\t\tRecord<string, ShimmerRect[]>\r\n\t>({});\r\n\r\n\tconst register = useCallback((rid: string, rects: ShimmerRect[]) => {\r\n\t\tsetReporterRects((prev) => ({ ...prev, [rid]: rects }));\r\n\t}, []);\r\n\r\n\tconst unregister = useCallback((rid: string) => {\r\n\t\tsetReporterRects((prev) => {\r\n\t\t\tconst next = { ...prev };\r\n\t\t\tdelete next[rid];\r\n\t\t\treturn next;\r\n\t\t});\r\n\t}, []);\r\n\r\n\tuseInsertionEffect(() => {\r\n\t\tinjectStyles();\r\n\t}, []);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tloading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t);\r\n\r\n\tconst allRects = useMemo(() => {\r\n\t\tconst reported = Object.values(reporterRects).flat();\r\n\t\treturn [...tracedRects, ...reported];\r\n\t}, [tracedRects, reporterRects]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\tconst contextValue = useMemo(\r\n\t\t() => ({\r\n\t\t\tregister,\r\n\t\t\tunregister,\r\n\t\t\tmasterRef: containerRef,\r\n\t\t\tloading,\r\n\t\t\tconfig,\r\n\t\t}),\r\n\t\t[register, unregister, loading, config],\r\n\t);\r\n\r\n\treturn (\r\n\t\t<ShimmerContext.Provider value={contextValue}>\r\n\t\t\t<div\r\n\t\t\t\tref={containerRef}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={{\r\n\t\t\t\t\tposition: \"relative\",\r\n\t\t\t\t\tvisibility:\r\n\t\t\t\t\t\tloading && !config.preserveBackground ? \"hidden\" : undefined,\r\n\t\t\t\t\t...style,\r\n\t\t\t\t}}\r\n\t\t\t\taria-hidden={loading || undefined}\r\n\t\t\t\tdata-shimmer-master\r\n\t\t\t\tdata-shimmer-preserve-bg={\r\n\t\t\t\t\tloading && config.preserveBackground ? \"true\" : undefined\r\n\t\t\t\t}\r\n\t\t\t>\r\n\t\t\t\t{renderedChildren}\r\n\r\n\t\t\t\t{loading && (\r\n\t\t\t\t\t<ShimmerOverlay\r\n\t\t\t\t\t\trects={allRects}\r\n\t\t\t\t\t\tanimation={config.animation}\r\n\t\t\t\t\t\tbaseColor={config.baseColor}\r\n\t\t\t\t\t\thighlightColor={config.highlightColor}\r\n\t\t\t\t\t\tspeed={config.speed}\r\n\t\t\t\t\t/>\r\n\t\t\t\t)}\r\n\t\t\t</div>\r\n\t\t</ShimmerContext.Provider>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Reporter ─────────────────── */\r\n\r\ninterface ReporterShimmerProps {\r\n\tid: string;\r\n\tparentContext: NonNullable<ReturnType<typeof useShimmerContext>>;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n}\r\n\r\nfunction ReporterShimmer({\r\n\tid,\r\n\tparentContext,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n}: ReporterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tparentContext.loading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t\tparentContext.masterRef,\r\n\t);\r\n\r\n\tReact.useLayoutEffect(() => {\r\n\t\tif (!parentContext.loading || tracedRects.length === 0) {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tparentContext.register(id, tracedRects);\r\n\t\treturn () => {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t};\r\n\t}, [tracedRects, parentContext, id]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading: parentContext.loading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\treturn (\r\n\t\t<div\r\n\t\t\tref={containerRef}\r\n\t\t\tdata-shimmer-reporter\r\n\t\t\tstyle={{ display: \"contents\" }}\r\n\t\t>\r\n\t\t\t{renderedChildren}\r\n\t\t</div>\r\n\t);\r\n}\r\n\r\n/* ─────────────── Skeleton Children ─────────────── */\r\n\r\ninterface UseSkeletonChildrenParams {\r\n\tloading: boolean;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tid: string;\r\n}\r\n\r\n/**\r\n * Build the rendered children tree.\r\n *\r\n * Priority during `loading=true`:\r\n * 1. `as` set → render `dummyLength` (or 1) instances of `<as {...dummyData} />`.\r\n * Children ignored. Cold-start safe.\r\n * 2. `dummyData` set + children present → clone each child merging\r\n * dummyData over its props. If `dummyLength` set, clone first\r\n * templated child N times.\r\n * 3. None → pass children through (e.g. `useIsShimmering` flow).\r\n *\r\n * `loading=false` → children untouched.\r\n */\r\nfunction useSkeletonChildren({\r\n\tloading,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tid,\r\n}: UseSkeletonChildrenParams): React.ReactNode {\r\n\tif (!loading) return children;\r\n\r\n\tif (as) {\r\n\t\tconst count = dummyLength && dummyLength > 0 ? dummyLength : 1;\r\n\t\tconst Component = as;\r\n\t\treturn Array.from({ length: count }, (_, i) => (\r\n\t\t\t<Component\r\n\t\t\t\t{...(dummyData || {})}\r\n\t\t\t\tkey={generateShimmerKey(`${id}-as-${i}`)}\r\n\t\t\t/>\r\n\t\t));\r\n\t}\r\n\r\n\tconst childArray = React.Children.toArray(children);\r\n\r\n\tconst templated = childArray.map((c, i) => {\r\n\t\tif (!React.isValidElement(c)) return c;\r\n\t\tconst key = generateShimmerKey(`${id}-tpl-${i}`);\r\n\t\tconst props = dummyData ? { ...dummyData, key } : { key };\r\n\t\treturn React.cloneElement(c as React.ReactElement, props as any);\r\n\t});\r\n\r\n\tif (dummyLength && dummyLength > 0) {\r\n\t\tconst first = templated.find((c) => React.isValidElement(c)) as\r\n\t\t\t| React.ReactElement\r\n\t\t\t| undefined;\r\n\t\tif (!first) return null;\r\n\t\treturn Array.from({ length: dummyLength }, (_, i) =>\r\n\t\t\tReact.cloneElement(first, {\r\n\t\t\t\tkey: generateShimmerKey(`${id}-clone-${i}`),\r\n\t\t\t} as any),\r\n\t\t);\r\n\t}\r\n\r\n\treturn templated;\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { ShimmerConfig, ShimmerProps, DEFAULTS } from './types';\r\n\r\n/**\r\n * Factory function to create a pre-configured Shimmer component.\r\n * Avoids \"Provider Hell\" by baking config into the returned component.\r\n *\r\n * All config properties are optional — defaults are used for anything\r\n * not specified.\r\n *\r\n * @example\r\n * ```tsx\r\n * const AppShimmer = createShimmer({\r\n * animation: 'pulse',\r\n * baseColor: '#1a1a2e',\r\n * highlightColor: '#16213e',\r\n * speed: 2,\r\n * });\r\n *\r\n * <AppShimmer loading={isLoading}>\r\n * <MyComponent />\r\n * </AppShimmer>\r\n * ```\r\n */\r\nexport function createShimmer(config: ShimmerConfig = {}) {\r\n const mergedConfig: Required<ShimmerConfig> = {\r\n animation: config.animation ?? DEFAULTS.animation,\r\n baseColor: config.baseColor ?? DEFAULTS.baseColor,\r\n highlightColor: config.highlightColor ?? DEFAULTS.highlightColor,\r\n speed: config.speed ?? DEFAULTS.speed,\r\n borderRadius: config.borderRadius ?? DEFAULTS.borderRadius,\r\n preserveBackground: config.preserveBackground ?? DEFAULTS.preserveBackground,\r\n };\r\n\r\n function ConfiguredShimmer(props: Omit<ShimmerProps, keyof ShimmerConfig> & Partial<ShimmerConfig>) {\r\n return (\r\n <Shimmer\r\n {...mergedConfig}\r\n {...props}\r\n />\r\n );\r\n }\r\n\r\n // Removed ConfiguredShimmer.displayName\r\n return ConfiguredShimmer;\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { IsShimmeringContext } from './ShimmerContext';\r\nimport { ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerSuspenseProps extends ShimmerConfig {\r\n children: React.ReactNode;\r\n /**\r\n * Explicit skeleton template. Rendered hidden and traced for shimmer shape.\r\n *\r\n * Preferred — pass the same component with no data props:\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * If omitted, falls back to Option B: children are re-rendered with\r\n * `useIsShimmering()=true` so they can return an empty shape themselves.\r\n */\r\n template?: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Suspense boundary that automatically shows a shimmer skeleton while\r\n * children are suspended (e.g. useSuspenseQuery, use(promise), etc).\r\n *\r\n * **Option A — explicit template (preferred):**\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * **Option B — useIsShimmering hook (no template):**\r\n * ```tsx\r\n * function UserCard() {\r\n * const isShimmering = useIsShimmering();\r\n * const data = isShimmering ? null : useSuspenseQuery(...);\r\n * return <div><h3>{data?.name}</h3></div>;\r\n * }\r\n *\r\n * <ShimmerSuspense>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n * Components must use `useIsShimmering()` to skip data fetching in shimmer mode,\r\n * otherwise they will also suspend inside the fallback (causing an empty skeleton).\r\n */\r\nexport function ShimmerSuspense({\r\n children,\r\n template,\r\n ...shimmerConfig\r\n}: ShimmerSuspenseProps) {\r\n const skeletonContent =\r\n template !== undefined ? (\r\n template\r\n ) : (\r\n <IsShimmeringContext.Provider value={true}>\r\n {children}\r\n </IsShimmeringContext.Provider>\r\n );\r\n\r\n return (\r\n <React.Suspense\r\n fallback={\r\n <Shimmer loading={true} {...shimmerConfig}>\r\n {skeletonContent}\r\n </Shimmer>\r\n }\r\n >\r\n {children}\r\n </React.Suspense>\r\n );\r\n}\r\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
'use client';
|
|
2
|
+
import React2, { createContext, useContext, useId, useMemo, useRef, useState, useCallback, useInsertionEffect, createElement, useLayoutEffect } from 'react';
|
|
2
3
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
3
4
|
|
|
4
5
|
// src/Shimmer.tsx
|
|
@@ -127,9 +128,8 @@ var ShimmerOverlay = ({
|
|
|
127
128
|
};
|
|
128
129
|
|
|
129
130
|
// src/utils.ts
|
|
130
|
-
var counter = 0;
|
|
131
131
|
function generateShimmerKey(prefix = "shimmer") {
|
|
132
|
-
return
|
|
132
|
+
return prefix;
|
|
133
133
|
}
|
|
134
134
|
var FALLBACK_DIMENSIONS = {
|
|
135
135
|
INPUT: { width: 200, height: 36 },
|
|
@@ -190,13 +190,23 @@ function isTraceable(el) {
|
|
|
190
190
|
}
|
|
191
191
|
return false;
|
|
192
192
|
}
|
|
193
|
+
function isViewportLocked(el) {
|
|
194
|
+
const pos = window.getComputedStyle(el).position;
|
|
195
|
+
return pos === "fixed" || pos === "sticky";
|
|
196
|
+
}
|
|
197
|
+
var warnedContainers = /* @__PURE__ */ new WeakSet();
|
|
193
198
|
function collectTraceableElements(root) {
|
|
194
|
-
const
|
|
199
|
+
const traced = [];
|
|
200
|
+
let skippedFixed = 0;
|
|
195
201
|
function walk(el) {
|
|
196
202
|
if (el.hasAttribute("data-shimmer-ignore")) return;
|
|
197
203
|
if (el.hasAttribute("data-shimmer-reporter")) return;
|
|
204
|
+
if (!el.hasAttribute("data-shimmer") && isViewportLocked(el)) {
|
|
205
|
+
skippedFixed += 1;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
198
208
|
if (isTraceable(el)) {
|
|
199
|
-
|
|
209
|
+
traced.push(el);
|
|
200
210
|
return;
|
|
201
211
|
}
|
|
202
212
|
for (let i = 0; i < el.children.length; i++) {
|
|
@@ -206,7 +216,7 @@ function collectTraceableElements(root) {
|
|
|
206
216
|
for (let i = 0; i < root.children.length; i++) {
|
|
207
217
|
walk(root.children[i]);
|
|
208
218
|
}
|
|
209
|
-
return
|
|
219
|
+
return { traced, skippedFixed };
|
|
210
220
|
}
|
|
211
221
|
function measureElement(el, containerRect, globalBorderRadius) {
|
|
212
222
|
const elRect = el.getBoundingClientRect();
|
|
@@ -239,8 +249,14 @@ function measureElement(el, containerRect, globalBorderRadius) {
|
|
|
239
249
|
function performTrace(container, globalBorderRadius, anchorRef) {
|
|
240
250
|
const anchor = anchorRef?.current ?? container;
|
|
241
251
|
const anchorRect = anchor.getBoundingClientRect();
|
|
242
|
-
const
|
|
243
|
-
|
|
252
|
+
const { traced, skippedFixed } = collectTraceableElements(container);
|
|
253
|
+
if (skippedFixed > 0 && !warnedContainers.has(container)) {
|
|
254
|
+
warnedContainers.add(container);
|
|
255
|
+
console.warn(
|
|
256
|
+
`[shimmer-trace] Skipped ${skippedFixed} element(s) with position:fixed or position:sticky. Their coordinate space cannot follow the Master container during scroll, so the overlay block would drift. Render a nested <Shimmer> inside the fixed/sticky element if you need a skeleton there, or add data-shimmer to opt in (positioning may still drift on scroll).`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
return traced.map((el) => measureElement(el, anchorRect, globalBorderRadius)).filter((r) => r !== null && r.width > 0 && r.height > 0);
|
|
244
260
|
}
|
|
245
261
|
function useTrace(containerRef, loading, globalBorderRadius, anchorRef) {
|
|
246
262
|
const [rects, setRects] = useState([]);
|
|
@@ -408,7 +424,7 @@ function MasterShimmer({
|
|
|
408
424
|
return next;
|
|
409
425
|
});
|
|
410
426
|
}, []);
|
|
411
|
-
|
|
427
|
+
useInsertionEffect(() => {
|
|
412
428
|
injectStyles();
|
|
413
429
|
}, []);
|
|
414
430
|
const tracedRects = useTrace(
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/ShimmerContext.tsx","../src/ShimmerOverlay.tsx","../src/utils.ts","../src/useTrace.ts","../src/styles.ts","../src/Shimmer.tsx","../src/createShimmer.tsx","../src/ShimmerSuspense.tsx"],"names":["React","jsx","useState","useCallback"],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { ReactNode } from 'react';\r\n\r\n/**\r\n * Represents a measured rectangle of a traced DOM element,\r\n * positioned relative to the Master Shimmer container.\r\n */\r\nexport interface ShimmerRect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n borderRadius: string;\r\n}\r\n\r\n/** Available animation types for the shimmer effect. */\r\nexport type AnimationType =\r\n | 'wave'\r\n | 'pulse'\r\n | 'shine'\r\n | 'glow'\r\n | 'gradient';\r\n\r\n/** Configuration options for the shimmer effect (all optional). */\r\nexport interface ShimmerConfig {\r\n /** Animation style. Defaults to 'wave'. */\r\n animation?: AnimationType;\r\n /** Base color of the shimmer blocks. Defaults to '#e0e0e0'. */\r\n baseColor?: string;\r\n /** Highlight color of the shimmer animation. Defaults to '#f5f5f5'. */\r\n highlightColor?: string;\r\n /** Animation duration in seconds. Defaults to 1.5. */\r\n speed?: number;\r\n /** Global border-radius override. If omitted, auto-detected from each element (defaults to 4px if detection is 0px). */\r\n borderRadius?: string;\r\n /**\r\n * Keep container backgrounds, borders, and padding visible while loading.\r\n * When `true` (default), only text and media leaves are hidden via\r\n * `color:transparent` / `opacity:0` so card backgrounds, borders, and\r\n * spacing remain visible underneath the shimmer overlay.\r\n *\r\n * Set `false` for legacy behavior (`visibility:hidden` on whole tree).\r\n */\r\n preserveBackground?: boolean;\r\n}\r\n\r\n/** Props for the Shimmer component. */\r\nexport interface ShimmerProps extends ShimmerConfig {\r\n /** Whether the loading state is active. */\r\n loading?: boolean;\r\n /** The children to trace and render shimmer over. */\r\n children: ReactNode;\r\n /**\r\n * Number of placeholder clones to generate for list-like loading states.\r\n *\r\n * When `loading=true` and `dummyLength` is set, Shimmer grabs the first\r\n * available child (or a cached template from the last loaded render) and\r\n * clones it `dummyLength` times to produce skeleton placeholders.\r\n *\r\n * When `loading=false`, children are rendered as-is.\r\n */\r\n dummyLength?: number;\r\n /**\r\n * Props injected into each child element while `loading=true` so the\r\n * skeleton renders with realistic shape without requiring real data.\r\n *\r\n * Example:\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyData={{ user: { name: 'Loading...', role: '...', avatar: '' } }}\r\n * >\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * While loading, each direct child is cloned with these props merged on top\r\n * of its own props. Ignored when `loading=false`.\r\n */\r\n dummyData?: Record<string, any>;\r\n /**\r\n * Component used to auto-generate skeleton elements while `loading=true`.\r\n *\r\n * When set, Shimmer ignores `children` during loading and renders\r\n * `dummyLength` (defaults to 1) instances of `<as {...dummyData} />`\r\n * to derive shape. Real children render once `loading=false`.\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * as={MovieCard}\r\n * dummyData={{ movie: movieTemplate }}\r\n * dummyLength={10}\r\n * >\r\n * {movies.map((m) => <MovieCard movie={m} key={m.id} />)}\r\n * </Shimmer>\r\n * ```\r\n */\r\n as?: React.ComponentType<any>;\r\n /** Force this Shimmer to be a Master renderer even if nested inside another Shimmer. */\r\n stopPropagation?: boolean;\r\n /**\r\n * className applied to the Master container div.\r\n * Use to control layout (e.g. display:flex) without losing position:relative.\r\n */\r\n className?: string;\r\n /**\r\n * Inline styles merged into the Master container div.\r\n * position:relative is always applied; everything else is overridable.\r\n */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULTS: Required<ShimmerConfig> = {\r\n animation: 'wave',\r\n baseColor: '#e0e0e0',\r\n highlightColor: '#f5f5f5',\r\n speed: 1.5,\r\n borderRadius: '',\r\n preserveBackground: true,\r\n};\r\n","import { createContext, useContext, RefObject } from 'react';\r\nimport { ShimmerRect, ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerContextValue {\r\n register: (id: string, rects: ShimmerRect[]) => void;\r\n unregister: (id: string) => void;\r\n /** Ref object (not .current) so Reporters always read a fresh value. */\r\n masterRef: RefObject<HTMLElement | null>;\r\n loading: boolean;\r\n config: Required<ShimmerConfig>;\r\n}\r\n\r\nexport const ShimmerContext = createContext<ShimmerContextValue | null>(null);\r\n\r\nexport function useShimmerContext(): ShimmerContextValue | null {\r\n return useContext(ShimmerContext);\r\n}\r\n\r\n/**\r\n * True when rendered inside a ShimmerSuspense fallback (Option B).\r\n * Components use this to skip data fetching and return an empty shape.\r\n */\r\nexport const IsShimmeringContext = createContext<boolean>(false);\r\n\r\nexport function useIsShimmering(): boolean {\r\n return useContext(IsShimmeringContext);\r\n}\r\n","import React from 'react';\r\nimport { ShimmerRect, AnimationType } from './types';\r\n\r\ninterface ShimmerOverlayProps {\r\n rects: ShimmerRect[];\r\n animation: AnimationType;\r\n baseColor: string;\r\n highlightColor: string;\r\n speed: number;\r\n}\r\n\r\n/**\r\n * Returns animation-specific inline styles for each shimmer block.\r\n * For sweep-style animations (wave, shine), the colored gradient is rendered\r\n * as a child layer so the sweep can extend across the container in sync.\r\n */\r\nfunction getBlockStyles(\r\n animation: AnimationType,\r\n baseColor: string,\r\n highlightColor: string,\r\n speed: number,\r\n rect: ShimmerRect,\r\n): React.CSSProperties {\r\n const base: React.CSSProperties = {\r\n position: 'absolute',\r\n top: rect.y,\r\n left: rect.x,\r\n width: rect.width,\r\n height: rect.height,\r\n borderRadius: rect.borderRadius,\r\n overflow: 'hidden',\r\n };\r\n\r\n switch (animation) {\r\n case 'wave':\r\n case 'shine':\r\n return { ...base, background: baseColor };\r\n case 'pulse':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-pulse ${speed}s ease-in-out infinite`,\r\n };\r\n case 'glow':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-glow ${speed}s ease-in-out infinite`,\r\n };\r\n case 'gradient':\r\n return {\r\n ...base,\r\n backgroundImage: `linear-gradient(90deg, ${baseColor}, ${highlightColor}, ${baseColor})`,\r\n backgroundSize: '200% 100%',\r\n animation: `shimmer-gradient ${speed * 1.5}s ease-in-out infinite`,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Sweeping shine layer used by `wave` and `shine` animations.\r\n * Spans the full container width so the highlight sweeps in sync across all blocks.\r\n */\r\nconst SweepLayer: React.FC<{\r\n rect: ShimmerRect;\r\n highlightColor: string;\r\n speed: number;\r\n containerWidth: number;\r\n variant: 'wave' | 'shine';\r\n}> = ({ rect, highlightColor, speed, containerWidth, variant }) => {\r\n const isShine = variant === 'shine';\r\n return (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: -rect.x,\r\n width: containerWidth > 0 ? containerWidth : '100vw',\r\n height: '100%',\r\n background: isShine\r\n ? `linear-gradient(115deg, transparent 30%, ${highlightColor} 50%, transparent 70%)`\r\n : `linear-gradient(90deg, transparent 0%, ${highlightColor} 50%, transparent 100%)`,\r\n animation: `${isShine ? 'shimmer-shine' : 'shimmer-wave'} ${speed}s ease-in-out infinite`,\r\n }}\r\n />\r\n );\r\n};\r\n\r\n/**\r\n * The overlay component rendered by the Master Shimmer.\r\n *\r\n * Renders one absolutely-positioned div per traced rect. Sweep-style\r\n * animations (`wave`, `shine`) get an additional gradient layer that spans\r\n * the container so the highlight passes across all blocks in sync.\r\n */\r\nexport const ShimmerOverlay: React.FC<ShimmerOverlayProps> = ({\r\n rects,\r\n animation,\r\n baseColor,\r\n highlightColor,\r\n speed,\r\n}) => {\r\n const overlayRef = React.useRef<HTMLDivElement>(null);\r\n const [containerWidth, setContainerWidth] = React.useState(0);\r\n\r\n React.useLayoutEffect(() => {\r\n if (!overlayRef.current?.parentElement) return;\r\n setContainerWidth(overlayRef.current.parentElement.offsetWidth);\r\n }, [rects]);\r\n\r\n if (rects.length === 0) return null;\r\n\r\n const isSweep = animation === 'wave' || animation === 'shine';\r\n\r\n return (\r\n <div\r\n ref={overlayRef}\r\n role=\"status\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading content\"\r\n data-shimmer-ignore=\"true\"\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n zIndex: 1,\r\n pointerEvents: 'none',\r\n visibility: 'visible',\r\n }}\r\n >\r\n {rects.map((rect, i) => (\r\n <div\r\n key={i}\r\n style={getBlockStyles(animation, baseColor, highlightColor, speed, rect)}\r\n >\r\n {isSweep && (\r\n <SweepLayer\r\n rect={rect}\r\n highlightColor={highlightColor}\r\n speed={speed}\r\n containerWidth={containerWidth}\r\n variant={animation as 'wave' | 'shine'}\r\n />\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * Generates a unique key for cloned elements to prevent\r\n * React \"missing key\" warnings during loading state.\r\n */\r\nlet counter = 0;\r\nexport function generateShimmerKey(prefix: string = 'shimmer'): string {\r\n return `${prefix}-clone-${++counter}`;\r\n}\r\n\r\n/**\r\n * Default fallback dimensions for common elements when their\r\n * measured dimensions are 0px (e.g., empty inputs, images not yet loaded).\r\n */\r\nexport const FALLBACK_DIMENSIONS: Record<string, { width: number; height: number }> = {\r\n INPUT: { width: 200, height: 36 },\r\n BUTTON: { width: 120, height: 36 },\r\n TEXTAREA: { width: 300, height: 80 },\r\n SELECT: { width: 200, height: 36 },\r\n IMG: { width: 100, height: 100 },\r\n H1: { width: 300, height: 36 },\r\n H2: { width: 260, height: 30 },\r\n H3: { width: 220, height: 26 },\r\n H4: { width: 200, height: 22 },\r\n H5: { width: 180, height: 20 },\r\n H6: { width: 160, height: 18 },\r\n P: { width: 250, height: 16 },\r\n SPAN: { width: 100, height: 16 },\r\n};\r\n","import { useLayoutEffect, useState, RefObject, useCallback } from 'react';\r\nimport { ShimmerRect } from './types';\r\nimport { FALLBACK_DIMENSIONS } from './utils';\r\n\r\n/**\r\n * Tags that are always considered \"traceable\" leaf elements\r\n * whose dimensions should be captured for the shimmer overlay.\r\n */\r\nconst TRACEABLE_TAGS = new Set([\r\n // Text\r\n 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LI',\r\n 'LABEL', 'TD', 'TH', 'BLOCKQUOTE', 'CODE', 'PRE',\r\n // Media\r\n 'IMG', 'VIDEO', 'SVG', 'CANVAS', 'PICTURE',\r\n // Form\r\n 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON',\r\n // Misc\r\n 'HR',\r\n]);\r\n\r\n/**\r\n * Determines if an element should be traced.\r\n * Explicit data attributes override automatic detection.\r\n */\r\nfunction isTraceable(el: Element): boolean {\r\n if (el.hasAttribute('data-shimmer-ignore')) return false;\r\n if (el.hasAttribute('data-shimmer')) return true;\r\n if (TRACEABLE_TAGS.has(el.tagName)) return true;\r\n\r\n // Leaf element with visible dimensions → trace it\r\n if (el.children.length === 0) {\r\n const rect = el.getBoundingClientRect();\r\n return rect.width > 0 && rect.height > 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Recursively walks the DOM tree and collects all traceable elements.\r\n */\r\nfunction collectTraceableElements(root: Element): Element[] {\r\n const result: Element[] = [];\r\n\r\n function walk(el: Element) {\r\n if (el.hasAttribute('data-shimmer-ignore')) return;\r\n if (el.hasAttribute('data-shimmer-reporter')) return; // Ignore nested reporters, they report their own rects\r\n\r\n if (isTraceable(el)) {\r\n result.push(el);\r\n return; // Don't recurse into traced elements\r\n }\r\n\r\n for (let i = 0; i < el.children.length; i++) {\r\n walk(el.children[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < root.children.length; i++) {\r\n walk(root.children[i]);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Measures an element and returns its ShimmerRect relative to the container.\r\n */\r\nfunction measureElement(\r\n el: Element,\r\n containerRect: DOMRect,\r\n globalBorderRadius?: string,\r\n): ShimmerRect | null {\r\n const elRect = el.getBoundingClientRect();\r\n\r\n // If the element is display: none or detached, getBoundingClientRect returns all zeros.\r\n // We must not trace it, otherwise it ends up at the left edge of the screen.\r\n if (elRect.width === 0 && elRect.height === 0 && elRect.left === 0 && elRect.top === 0) {\r\n return null;\r\n }\r\n\r\n const computedStyle = window.getComputedStyle(el);\r\n let borderRadius = globalBorderRadius || computedStyle.borderRadius;\r\n\r\n // If the element has no border radius (common for text tags),\r\n // apply a small default to avoid sharp edges in the shimmer.\r\n const isZero = !borderRadius || borderRadius === 'none' || borderRadius.split(' ').every(v => v === '0' || v === '0px' || v === '0%');\r\n if (isZero) {\r\n borderRadius = '4px';\r\n }\r\n\r\n let width = elRect.width;\r\n let height = elRect.height;\r\n\r\n // Apply fallback dimensions if element has 0 size but is actually visible (e.g. empty inline element)\r\n if (width === 0 || height === 0) {\r\n const fallback = FALLBACK_DIMENSIONS[el.tagName];\r\n if (fallback) {\r\n width = width || fallback.width;\r\n height = height || fallback.height;\r\n }\r\n }\r\n\r\n return {\r\n x: elRect.left - containerRect.left,\r\n y: elRect.top - containerRect.top,\r\n width,\r\n height,\r\n borderRadius,\r\n };\r\n}\r\n\r\n/**\r\n * Performs a full trace of all traceable elements within the container.\r\n *\r\n * @param anchorRef - When provided (Reporter mode), elements are measured\r\n * relative to this element instead of the container. Allows Reporter's\r\n * wrapper to use display:contents without breaking measurement.\r\n */\r\nfunction performTrace(\r\n container: HTMLElement,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const anchor = anchorRef?.current ?? container;\r\n const anchorRect = anchor.getBoundingClientRect();\r\n const elements = collectTraceableElements(container);\r\n\r\n return elements\r\n .map((el) => measureElement(el, anchorRect, globalBorderRadius))\r\n .filter((r): r is ShimmerRect => r !== null && r.width > 0 && r.height > 0);\r\n}\r\n\r\n/**\r\n * Hook that traces all visible leaf DOM elements within a container\r\n * and returns their measured ShimmerRects.\r\n *\r\n * Uses ResizeObserver to re-trace on container resize.\r\n *\r\n * @param anchorRef - When set, rects are relative to this element (Master).\r\n * Used by Reporter so its display:contents wrapper doesn't break measurement.\r\n */\r\nexport function useTrace(\r\n containerRef: RefObject<HTMLElement | null>,\r\n loading: boolean,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const [rects, setRects] = useState<ShimmerRect[]>([]);\r\n\r\n const trace = useCallback(() => {\r\n if (!containerRef.current) return;\r\n const traced = performTrace(containerRef.current, globalBorderRadius, anchorRef);\r\n setRects(traced);\r\n }, [containerRef, globalBorderRadius, anchorRef]);\r\n\r\n useLayoutEffect(() => {\r\n if (!loading || !containerRef.current) {\r\n setRects([]);\r\n return;\r\n }\r\n\r\n // Initial trace\r\n trace();\r\n\r\n // Re-trace on resize\r\n const observer = new ResizeObserver(() => {\r\n trace();\r\n });\r\n observer.observe(containerRef.current);\r\n\r\n return () => observer.disconnect();\r\n }, [loading, trace]);\r\n\r\n return rects;\r\n}\r\n","const SHIMMER_STYLES_ID = 'shimmer-trace-styles';\r\n\r\nconst CSS = `\r\n@keyframes shimmer-wave {\r\n 0% { transform: translateX(-100%); }\r\n 100% { transform: translateX(100%); }\r\n}\r\n\r\n@keyframes shimmer-pulse {\r\n 0%, 100% { opacity: 0.4; }\r\n 50% { opacity: 1; }\r\n}\r\n\r\n@keyframes shimmer-shine {\r\n 0% { transform: translateX(-150%) skewX(-20deg); }\r\n 100% { transform: translateX(150%) skewX(-20deg); }\r\n}\r\n\r\n@keyframes shimmer-glow {\r\n 0%, 100% { filter: brightness(1); }\r\n 50% { filter: brightness(1.35); }\r\n}\r\n\r\n@keyframes shimmer-gradient {\r\n 0% { background-position: 0% 50%; }\r\n 50% { background-position: 100% 50%; }\r\n 100% { background-position: 0% 50%; }\r\n}\r\n\r\n/* preserveBackground mode: hide text + media but keep container styles */\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(h1,h2,h3,h4,h5,h6,p,span,a,li,label,td,th,blockquote,code,pre,strong,em,small) {\r\n color: transparent !important;\r\n text-shadow: none !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(img,video,svg,canvas,picture) {\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(input,textarea,select,button) {\r\n color: transparent !important;\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] {\r\n pointer-events: none !important;\r\n user-select: none !important;\r\n}\r\n`;\r\n\r\n/**\r\n * Injects the shimmer keyframe animations into the document head.\r\n * Safe to call multiple times — only injects once.\r\n */\r\nexport function injectStyles(): void {\r\n if (typeof document === 'undefined') return;\r\n if (document.getElementById(SHIMMER_STYLES_ID)) return;\r\n\r\n const style = document.createElement('style');\r\n style.id = SHIMMER_STYLES_ID;\r\n style.textContent = CSS;\r\n document.head.appendChild(style);\r\n}\r\n","import React, { useRef, useCallback, useState, useId, useMemo } from \"react\";\r\nimport { ShimmerProps, ShimmerRect, DEFAULTS } from \"./types\";\r\nimport { ShimmerContext, useShimmerContext } from \"./ShimmerContext\";\r\nimport { ShimmerOverlay } from \"./ShimmerOverlay\";\r\nimport { useTrace } from \"./useTrace\";\r\nimport { injectStyles } from \"./styles\";\r\nimport { generateShimmerKey } from \"./utils\";\r\n\r\n/**\r\n * The main Shimmer component.\r\n *\r\n * Auto-detects **Master** (no parent Shimmer) vs **Reporter** (nested).\r\n * - Master: renders children hidden, traces DOM, paints overlay.\r\n * - Reporter: measures own rects, reports to parent Master.\r\n *\r\n * ### Skeleton shape via `dummyData`\r\n *\r\n * Pass `dummyData` so children render with realistic data while loading.\r\n * No render-prop, no manual `data || fallback` in JSX.\r\n *\r\n * ```tsx\r\n * const userTemplate = { name: 'Loading...', role: '...', avatar: '' };\r\n *\r\n * <Shimmer loading={loading} dummyData={{ user: userTemplate }}>\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * ### List mode (`dummyLength`)\r\n *\r\n * Combined with `dummyData`, clones the first child N times with\r\n * template props merged in:\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyLength={5}\r\n * dummyData={{ fruit: { name: 'xxxxx', price: '$0.00' } }}\r\n * >\r\n * <FruitCard fruit={undefined as any} />\r\n * </Shimmer>\r\n * ```\r\n */\r\nexport function Shimmer({\r\n\tloading = false,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tstopPropagation = false,\r\n\tanimation,\r\n\tbaseColor,\r\n\thighlightColor,\r\n\tspeed,\r\n\tborderRadius,\r\n\tpreserveBackground,\r\n\tclassName,\r\n\tstyle,\r\n}: ShimmerProps) {\r\n\tconst parentContext = useShimmerContext();\r\n\tconst isMaster = !parentContext || stopPropagation;\r\n\tconst id = useId();\r\n\r\n\tconst config = useMemo(\r\n\t\t() => ({\r\n\t\t\tanimation:\r\n\t\t\t\tanimation ?? parentContext?.config.animation ?? DEFAULTS.animation,\r\n\t\t\tbaseColor:\r\n\t\t\t\tbaseColor ?? parentContext?.config.baseColor ?? DEFAULTS.baseColor,\r\n\t\t\thighlightColor:\r\n\t\t\t\thighlightColor ??\r\n\t\t\t\tparentContext?.config.highlightColor ??\r\n\t\t\t\tDEFAULTS.highlightColor,\r\n\t\t\tspeed: speed ?? parentContext?.config.speed ?? DEFAULTS.speed,\r\n\t\t\tborderRadius:\r\n\t\t\t\tborderRadius ??\r\n\t\t\t\tparentContext?.config.borderRadius ??\r\n\t\t\t\tDEFAULTS.borderRadius,\r\n\t\t\tpreserveBackground:\r\n\t\t\t\tpreserveBackground ??\r\n\t\t\t\tparentContext?.config.preserveBackground ??\r\n\t\t\t\tDEFAULTS.preserveBackground,\r\n\t\t}),\r\n\t\t[\r\n\t\t\tanimation,\r\n\t\t\tbaseColor,\r\n\t\t\thighlightColor,\r\n\t\t\tspeed,\r\n\t\t\tborderRadius,\r\n\t\t\tpreserveBackground,\r\n\t\t\tparentContext?.config,\r\n\t\t],\r\n\t);\r\n\r\n\tif (isMaster) {\r\n\t\treturn (\r\n\t\t\t<MasterShimmer\r\n\t\t\t\tid={id}\r\n\t\t\t\tloading={loading}\r\n\t\t\t\tconfig={config}\r\n\t\t\t\tdummyLength={dummyLength}\r\n\t\t\t\tdummyData={dummyData}\r\n\t\t\t\tas={as}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={style}\r\n\t\t\t>\r\n\t\t\t\t{children}\r\n\t\t\t</MasterShimmer>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<ReporterShimmer\r\n\t\t\tid={id}\r\n\t\t\tparentContext={parentContext!}\r\n\t\t\tconfig={config}\r\n\t\t\tdummyLength={dummyLength}\r\n\t\t\tdummyData={dummyData}\r\n\t\t\tas={as}\r\n\t\t>\r\n\t\t\t{children}\r\n\t\t</ReporterShimmer>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Master ─────────────────── */\r\n\r\ninterface MasterShimmerProps {\r\n\tid: string;\r\n\tloading: boolean;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tclassName?: string;\r\n\tstyle?: React.CSSProperties;\r\n}\r\n\r\nfunction MasterShimmer({\r\n\tid,\r\n\tloading,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tclassName,\r\n\tstyle,\r\n}: MasterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst [reporterRects, setReporterRects] = useState<\r\n\t\tRecord<string, ShimmerRect[]>\r\n\t>({});\r\n\r\n\tconst register = useCallback((rid: string, rects: ShimmerRect[]) => {\r\n\t\tsetReporterRects((prev) => ({ ...prev, [rid]: rects }));\r\n\t}, []);\r\n\r\n\tconst unregister = useCallback((rid: string) => {\r\n\t\tsetReporterRects((prev) => {\r\n\t\t\tconst next = { ...prev };\r\n\t\t\tdelete next[rid];\r\n\t\t\treturn next;\r\n\t\t});\r\n\t}, []);\r\n\r\n\tReact.useEffect(() => {\r\n\t\tinjectStyles();\r\n\t}, []);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tloading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t);\r\n\r\n\tconst allRects = useMemo(() => {\r\n\t\tconst reported = Object.values(reporterRects).flat();\r\n\t\treturn [...tracedRects, ...reported];\r\n\t}, [tracedRects, reporterRects]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\tconst contextValue = useMemo(\r\n\t\t() => ({\r\n\t\t\tregister,\r\n\t\t\tunregister,\r\n\t\t\tmasterRef: containerRef,\r\n\t\t\tloading,\r\n\t\t\tconfig,\r\n\t\t}),\r\n\t\t[register, unregister, loading, config],\r\n\t);\r\n\r\n\treturn (\r\n\t\t<ShimmerContext.Provider value={contextValue}>\r\n\t\t\t<div\r\n\t\t\t\tref={containerRef}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={{\r\n\t\t\t\t\tposition: \"relative\",\r\n\t\t\t\t\tvisibility:\r\n\t\t\t\t\t\tloading && !config.preserveBackground ? \"hidden\" : undefined,\r\n\t\t\t\t\t...style,\r\n\t\t\t\t}}\r\n\t\t\t\taria-hidden={loading || undefined}\r\n\t\t\t\tdata-shimmer-master\r\n\t\t\t\tdata-shimmer-preserve-bg={\r\n\t\t\t\t\tloading && config.preserveBackground ? \"true\" : undefined\r\n\t\t\t\t}\r\n\t\t\t>\r\n\t\t\t\t{renderedChildren}\r\n\r\n\t\t\t\t{loading && (\r\n\t\t\t\t\t<ShimmerOverlay\r\n\t\t\t\t\t\trects={allRects}\r\n\t\t\t\t\t\tanimation={config.animation}\r\n\t\t\t\t\t\tbaseColor={config.baseColor}\r\n\t\t\t\t\t\thighlightColor={config.highlightColor}\r\n\t\t\t\t\t\tspeed={config.speed}\r\n\t\t\t\t\t/>\r\n\t\t\t\t)}\r\n\t\t\t</div>\r\n\t\t</ShimmerContext.Provider>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Reporter ─────────────────── */\r\n\r\ninterface ReporterShimmerProps {\r\n\tid: string;\r\n\tparentContext: NonNullable<ReturnType<typeof useShimmerContext>>;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n}\r\n\r\nfunction ReporterShimmer({\r\n\tid,\r\n\tparentContext,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n}: ReporterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tparentContext.loading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t\tparentContext.masterRef,\r\n\t);\r\n\r\n\tReact.useLayoutEffect(() => {\r\n\t\tif (!parentContext.loading || tracedRects.length === 0) {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tparentContext.register(id, tracedRects);\r\n\t\treturn () => {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t};\r\n\t}, [tracedRects, parentContext, id]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading: parentContext.loading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\treturn (\r\n\t\t<div\r\n\t\t\tref={containerRef}\r\n\t\t\tdata-shimmer-reporter\r\n\t\t\tstyle={{ display: \"contents\" }}\r\n\t\t>\r\n\t\t\t{renderedChildren}\r\n\t\t</div>\r\n\t);\r\n}\r\n\r\n/* ─────────────── Skeleton Children ─────────────── */\r\n\r\ninterface UseSkeletonChildrenParams {\r\n\tloading: boolean;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tid: string;\r\n}\r\n\r\n/**\r\n * Build the rendered children tree.\r\n *\r\n * Priority during `loading=true`:\r\n * 1. `as` set → render `dummyLength` (or 1) instances of `<as {...dummyData} />`.\r\n * Children ignored. Cold-start safe.\r\n * 2. `dummyData` set + children present → clone each child merging\r\n * dummyData over its props. If `dummyLength` set, clone first\r\n * templated child N times.\r\n * 3. None → pass children through (e.g. `useIsShimmering` flow).\r\n *\r\n * `loading=false` → children untouched.\r\n */\r\nfunction useSkeletonChildren({\r\n\tloading,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tid,\r\n}: UseSkeletonChildrenParams): React.ReactNode {\r\n\tif (!loading) return children;\r\n\r\n\tif (as) {\r\n\t\tconst count = dummyLength && dummyLength > 0 ? dummyLength : 1;\r\n\t\tconst Component = as;\r\n\t\treturn Array.from({ length: count }, (_, i) => (\r\n\t\t\t<Component\r\n\t\t\t\t{...(dummyData || {})}\r\n\t\t\t\tkey={generateShimmerKey(`${id}-as-${i}`)}\r\n\t\t\t/>\r\n\t\t));\r\n\t}\r\n\r\n\tconst childArray = React.Children.toArray(children);\r\n\r\n\tconst templated = childArray.map((c, i) => {\r\n\t\tif (!React.isValidElement(c)) return c;\r\n\t\tconst key = generateShimmerKey(`${id}-tpl-${i}`);\r\n\t\tconst props = dummyData ? { ...dummyData, key } : { key };\r\n\t\treturn React.cloneElement(c as React.ReactElement, props as any);\r\n\t});\r\n\r\n\tif (dummyLength && dummyLength > 0) {\r\n\t\tconst first = templated.find((c) => React.isValidElement(c)) as\r\n\t\t\t| React.ReactElement\r\n\t\t\t| undefined;\r\n\t\tif (!first) return null;\r\n\t\treturn Array.from({ length: dummyLength }, (_, i) =>\r\n\t\t\tReact.cloneElement(first, {\r\n\t\t\t\tkey: generateShimmerKey(`${id}-clone-${i}`),\r\n\t\t\t} as any),\r\n\t\t);\r\n\t}\r\n\r\n\treturn templated;\r\n}\r\n","import React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { ShimmerConfig, ShimmerProps, DEFAULTS } from './types';\r\n\r\n/**\r\n * Factory function to create a pre-configured Shimmer component.\r\n * Avoids \"Provider Hell\" by baking config into the returned component.\r\n *\r\n * All config properties are optional — defaults are used for anything\r\n * not specified.\r\n *\r\n * @example\r\n * ```tsx\r\n * const AppShimmer = createShimmer({\r\n * animation: 'pulse',\r\n * baseColor: '#1a1a2e',\r\n * highlightColor: '#16213e',\r\n * speed: 2,\r\n * });\r\n *\r\n * <AppShimmer loading={isLoading}>\r\n * <MyComponent />\r\n * </AppShimmer>\r\n * ```\r\n */\r\nexport function createShimmer(config: ShimmerConfig = {}) {\r\n const mergedConfig: Required<ShimmerConfig> = {\r\n animation: config.animation ?? DEFAULTS.animation,\r\n baseColor: config.baseColor ?? DEFAULTS.baseColor,\r\n highlightColor: config.highlightColor ?? DEFAULTS.highlightColor,\r\n speed: config.speed ?? DEFAULTS.speed,\r\n borderRadius: config.borderRadius ?? DEFAULTS.borderRadius,\r\n preserveBackground: config.preserveBackground ?? DEFAULTS.preserveBackground,\r\n };\r\n\r\n function ConfiguredShimmer(props: Omit<ShimmerProps, keyof ShimmerConfig> & Partial<ShimmerConfig>) {\r\n return (\r\n <Shimmer\r\n {...mergedConfig}\r\n {...props}\r\n />\r\n );\r\n }\r\n\r\n // Removed ConfiguredShimmer.displayName\r\n return ConfiguredShimmer;\r\n}\r\n","import React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { IsShimmeringContext } from './ShimmerContext';\r\nimport { ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerSuspenseProps extends ShimmerConfig {\r\n children: React.ReactNode;\r\n /**\r\n * Explicit skeleton template. Rendered hidden and traced for shimmer shape.\r\n *\r\n * Preferred — pass the same component with no data props:\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * If omitted, falls back to Option B: children are re-rendered with\r\n * `useIsShimmering()=true` so they can return an empty shape themselves.\r\n */\r\n template?: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Suspense boundary that automatically shows a shimmer skeleton while\r\n * children are suspended (e.g. useSuspenseQuery, use(promise), etc).\r\n *\r\n * **Option A — explicit template (preferred):**\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * **Option B — useIsShimmering hook (no template):**\r\n * ```tsx\r\n * function UserCard() {\r\n * const isShimmering = useIsShimmering();\r\n * const data = isShimmering ? null : useSuspenseQuery(...);\r\n * return <div><h3>{data?.name}</h3></div>;\r\n * }\r\n *\r\n * <ShimmerSuspense>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n * Components must use `useIsShimmering()` to skip data fetching in shimmer mode,\r\n * otherwise they will also suspend inside the fallback (causing an empty skeleton).\r\n */\r\nexport function ShimmerSuspense({\r\n children,\r\n template,\r\n ...shimmerConfig\r\n}: ShimmerSuspenseProps) {\r\n const skeletonContent =\r\n template !== undefined ? (\r\n template\r\n ) : (\r\n <IsShimmeringContext.Provider value={true}>\r\n {children}\r\n </IsShimmeringContext.Provider>\r\n );\r\n\r\n return (\r\n <React.Suspense\r\n fallback={\r\n <Shimmer loading={true} {...shimmerConfig}>\r\n {skeletonContent}\r\n </Shimmer>\r\n }\r\n >\r\n {children}\r\n </React.Suspense>\r\n );\r\n}\r\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/ShimmerContext.tsx","../src/ShimmerOverlay.tsx","../src/utils.ts","../src/useTrace.ts","../src/styles.ts","../src/Shimmer.tsx","../src/createShimmer.tsx","../src/ShimmerSuspense.tsx"],"names":["React","jsx","useState","useCallback"],"mappings":"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","file":"index.mjs","sourcesContent":["import React, { ReactNode } from 'react';\r\n\r\n/**\r\n * Represents a measured rectangle of a traced DOM element,\r\n * positioned relative to the Master Shimmer container.\r\n */\r\nexport interface ShimmerRect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n borderRadius: string;\r\n}\r\n\r\n/** Available animation types for the shimmer effect. */\r\nexport type AnimationType =\r\n | 'wave'\r\n | 'pulse'\r\n | 'shine'\r\n | 'glow'\r\n | 'gradient';\r\n\r\n/** Configuration options for the shimmer effect (all optional). */\r\nexport interface ShimmerConfig {\r\n /** Animation style. Defaults to 'wave'. */\r\n animation?: AnimationType;\r\n /** Base color of the shimmer blocks. Defaults to '#e0e0e0'. */\r\n baseColor?: string;\r\n /** Highlight color of the shimmer animation. Defaults to '#f5f5f5'. */\r\n highlightColor?: string;\r\n /** Animation duration in seconds. Defaults to 1.5. */\r\n speed?: number;\r\n /** Global border-radius override. If omitted, auto-detected from each element (defaults to 4px if detection is 0px). */\r\n borderRadius?: string;\r\n /**\r\n * Keep container backgrounds, borders, and padding visible while loading.\r\n * When `true` (default), only text and media leaves are hidden via\r\n * `color:transparent` / `opacity:0` so card backgrounds, borders, and\r\n * spacing remain visible underneath the shimmer overlay.\r\n *\r\n * Set `false` for legacy behavior (`visibility:hidden` on whole tree).\r\n */\r\n preserveBackground?: boolean;\r\n}\r\n\r\n/** Props for the Shimmer component. */\r\nexport interface ShimmerProps extends ShimmerConfig {\r\n /** Whether the loading state is active. */\r\n loading?: boolean;\r\n /** The children to trace and render shimmer over. */\r\n children: ReactNode;\r\n /**\r\n * Number of placeholder clones to generate for list-like loading states.\r\n *\r\n * When `loading=true` and `dummyLength` is set, Shimmer grabs the first\r\n * available child (or a cached template from the last loaded render) and\r\n * clones it `dummyLength` times to produce skeleton placeholders.\r\n *\r\n * When `loading=false`, children are rendered as-is.\r\n */\r\n dummyLength?: number;\r\n /**\r\n * Props injected into each child element while `loading=true` so the\r\n * skeleton renders with realistic shape without requiring real data.\r\n *\r\n * Example:\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyData={{ user: { name: 'Loading...', role: '...', avatar: '' } }}\r\n * >\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * While loading, each direct child is cloned with these props merged on top\r\n * of its own props. Ignored when `loading=false`.\r\n */\r\n dummyData?: Record<string, any>;\r\n /**\r\n * Component used to auto-generate skeleton elements while `loading=true`.\r\n *\r\n * When set, Shimmer ignores `children` during loading and renders\r\n * `dummyLength` (defaults to 1) instances of `<as {...dummyData} />`\r\n * to derive shape. Real children render once `loading=false`.\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * as={MovieCard}\r\n * dummyData={{ movie: movieTemplate }}\r\n * dummyLength={10}\r\n * >\r\n * {movies.map((m) => <MovieCard movie={m} key={m.id} />)}\r\n * </Shimmer>\r\n * ```\r\n */\r\n as?: React.ComponentType<any>;\r\n /** Force this Shimmer to be a Master renderer even if nested inside another Shimmer. */\r\n stopPropagation?: boolean;\r\n /**\r\n * className applied to the Master container div.\r\n * Use to control layout (e.g. display:flex) without losing position:relative.\r\n */\r\n className?: string;\r\n /**\r\n * Inline styles merged into the Master container div.\r\n * position:relative is always applied; everything else is overridable.\r\n */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULTS: Required<ShimmerConfig> = {\r\n animation: 'wave',\r\n baseColor: '#e0e0e0',\r\n highlightColor: '#f5f5f5',\r\n speed: 1.5,\r\n borderRadius: '',\r\n preserveBackground: true,\r\n};\r\n","\"use client\";\r\n\r\nimport { createContext, useContext, RefObject } from \"react\";\r\nimport { ShimmerRect, ShimmerConfig } from \"./types\";\r\n\r\nexport interface ShimmerContextValue {\r\n\tregister: (id: string, rects: ShimmerRect[]) => void;\r\n\tunregister: (id: string) => void;\r\n\t/** Ref object (not .current) so Reporters always read a fresh value. */\r\n\tmasterRef: RefObject<HTMLElement | null>;\r\n\tloading: boolean;\r\n\tconfig: Required<ShimmerConfig>;\r\n}\r\n\r\nexport const ShimmerContext = createContext<ShimmerContextValue | null>(null);\r\n\r\nexport function useShimmerContext(): ShimmerContextValue | null {\r\n\treturn useContext(ShimmerContext);\r\n}\r\n\r\n/**\r\n * True when rendered inside a ShimmerSuspense fallback (Option B).\r\n * Components use this to skip data fetching and return an empty shape.\r\n */\r\nexport const IsShimmeringContext = createContext<boolean>(false);\r\n\r\nexport function useIsShimmering(): boolean {\r\n\treturn useContext(IsShimmeringContext);\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { ShimmerRect, AnimationType } from './types';\r\n\r\ninterface ShimmerOverlayProps {\r\n rects: ShimmerRect[];\r\n animation: AnimationType;\r\n baseColor: string;\r\n highlightColor: string;\r\n speed: number;\r\n}\r\n\r\n/**\r\n * Returns animation-specific inline styles for each shimmer block.\r\n * For sweep-style animations (wave, shine), the colored gradient is rendered\r\n * as a child layer so the sweep can extend across the container in sync.\r\n */\r\nfunction getBlockStyles(\r\n animation: AnimationType,\r\n baseColor: string,\r\n highlightColor: string,\r\n speed: number,\r\n rect: ShimmerRect,\r\n): React.CSSProperties {\r\n const base: React.CSSProperties = {\r\n position: 'absolute',\r\n top: rect.y,\r\n left: rect.x,\r\n width: rect.width,\r\n height: rect.height,\r\n borderRadius: rect.borderRadius,\r\n overflow: 'hidden',\r\n };\r\n\r\n switch (animation) {\r\n case 'wave':\r\n case 'shine':\r\n return { ...base, background: baseColor };\r\n case 'pulse':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-pulse ${speed}s ease-in-out infinite`,\r\n };\r\n case 'glow':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-glow ${speed}s ease-in-out infinite`,\r\n };\r\n case 'gradient':\r\n return {\r\n ...base,\r\n backgroundImage: `linear-gradient(90deg, ${baseColor}, ${highlightColor}, ${baseColor})`,\r\n backgroundSize: '200% 100%',\r\n animation: `shimmer-gradient ${speed * 1.5}s ease-in-out infinite`,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Sweeping shine layer used by `wave` and `shine` animations.\r\n * Spans the full container width so the highlight sweeps in sync across all blocks.\r\n */\r\nconst SweepLayer: React.FC<{\r\n rect: ShimmerRect;\r\n highlightColor: string;\r\n speed: number;\r\n containerWidth: number;\r\n variant: 'wave' | 'shine';\r\n}> = ({ rect, highlightColor, speed, containerWidth, variant }) => {\r\n const isShine = variant === 'shine';\r\n return (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: -rect.x,\r\n width: containerWidth > 0 ? containerWidth : '100vw',\r\n height: '100%',\r\n background: isShine\r\n ? `linear-gradient(115deg, transparent 30%, ${highlightColor} 50%, transparent 70%)`\r\n : `linear-gradient(90deg, transparent 0%, ${highlightColor} 50%, transparent 100%)`,\r\n animation: `${isShine ? 'shimmer-shine' : 'shimmer-wave'} ${speed}s ease-in-out infinite`,\r\n }}\r\n />\r\n );\r\n};\r\n\r\n/**\r\n * The overlay component rendered by the Master Shimmer.\r\n *\r\n * Renders one absolutely-positioned div per traced rect. Sweep-style\r\n * animations (`wave`, `shine`) get an additional gradient layer that spans\r\n * the container so the highlight passes across all blocks in sync.\r\n */\r\nexport const ShimmerOverlay: React.FC<ShimmerOverlayProps> = ({\r\n rects,\r\n animation,\r\n baseColor,\r\n highlightColor,\r\n speed,\r\n}) => {\r\n const overlayRef = React.useRef<HTMLDivElement>(null);\r\n const [containerWidth, setContainerWidth] = React.useState(0);\r\n\r\n React.useLayoutEffect(() => {\r\n if (!overlayRef.current?.parentElement) return;\r\n setContainerWidth(overlayRef.current.parentElement.offsetWidth);\r\n }, [rects]);\r\n\r\n if (rects.length === 0) return null;\r\n\r\n const isSweep = animation === 'wave' || animation === 'shine';\r\n\r\n return (\r\n <div\r\n ref={overlayRef}\r\n role=\"status\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading content\"\r\n data-shimmer-ignore=\"true\"\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n zIndex: 1,\r\n pointerEvents: 'none',\r\n visibility: 'visible',\r\n }}\r\n >\r\n {rects.map((rect, i) => (\r\n <div\r\n key={i}\r\n style={getBlockStyles(animation, baseColor, highlightColor, speed, rect)}\r\n >\r\n {isSweep && (\r\n <SweepLayer\r\n rect={rect}\r\n highlightColor={highlightColor}\r\n speed={speed}\r\n containerWidth={containerWidth}\r\n variant={animation as 'wave' | 'shine'}\r\n />\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * Generates a deterministic key for cloned elements.\r\n *\r\n * Prefix already includes the parent Shimmer's `useId()` plus a positional\r\n * index from the caller, so it is unique per render slot and stable across\r\n * SSR + client hydration. No module-scope counter — that caused hydration\r\n * mismatches and forced React to remount cloned children every render.\r\n */\r\nexport function generateShimmerKey(prefix: string = 'shimmer'): string {\r\n return prefix;\r\n}\r\n\r\n/**\r\n * Default fallback dimensions for common elements when their\r\n * measured dimensions are 0px (e.g., empty inputs, images not yet loaded).\r\n */\r\nexport const FALLBACK_DIMENSIONS: Record<string, { width: number; height: number }> = {\r\n INPUT: { width: 200, height: 36 },\r\n BUTTON: { width: 120, height: 36 },\r\n TEXTAREA: { width: 300, height: 80 },\r\n SELECT: { width: 200, height: 36 },\r\n IMG: { width: 100, height: 100 },\r\n H1: { width: 300, height: 36 },\r\n H2: { width: 260, height: 30 },\r\n H3: { width: 220, height: 26 },\r\n H4: { width: 200, height: 22 },\r\n H5: { width: 180, height: 20 },\r\n H6: { width: 160, height: 18 },\r\n P: { width: 250, height: 16 },\r\n SPAN: { width: 100, height: 16 },\r\n};\r\n","'use client';\r\n\r\nimport { useLayoutEffect, useState, RefObject, useCallback } from 'react';\r\nimport { ShimmerRect } from './types';\r\nimport { FALLBACK_DIMENSIONS } from './utils';\r\n\r\n/**\r\n * Tags that are always considered \"traceable\" leaf elements\r\n * whose dimensions should be captured for the shimmer overlay.\r\n */\r\nconst TRACEABLE_TAGS = new Set([\r\n // Text\r\n 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LI',\r\n 'LABEL', 'TD', 'TH', 'BLOCKQUOTE', 'CODE', 'PRE',\r\n // Media\r\n 'IMG', 'VIDEO', 'SVG', 'CANVAS', 'PICTURE',\r\n // Form\r\n 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON',\r\n // Misc\r\n 'HR',\r\n]);\r\n\r\n/**\r\n * Determines if an element should be traced.\r\n * Explicit data attributes override automatic detection.\r\n */\r\nfunction isTraceable(el: Element): boolean {\r\n if (el.hasAttribute('data-shimmer-ignore')) return false;\r\n if (el.hasAttribute('data-shimmer')) return true;\r\n if (TRACEABLE_TAGS.has(el.tagName)) return true;\r\n\r\n // Leaf element with visible dimensions → trace it\r\n if (el.children.length === 0) {\r\n const rect = el.getBoundingClientRect();\r\n return rect.width > 0 && rect.height > 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Detects elements whose coordinate space cannot follow the Master\r\n * container during scroll. `position: fixed` and `position: sticky`\r\n * both live in a different scroll context (viewport / nearest scroll\r\n * container) than the Master, so any container-relative rect we compute\r\n * for them becomes stale on the first scroll.\r\n *\r\n * Returning `true` here causes the entire subtree under `el` to be\r\n * skipped, since descendants of a fixed/sticky element inherit the same\r\n * broken coordinate space.\r\n */\r\nfunction isViewportLocked(el: Element): boolean {\r\n const pos = window.getComputedStyle(el).position;\r\n return pos === 'fixed' || pos === 'sticky';\r\n}\r\n\r\n/**\r\n * Tracks Master containers we've already warned about, so the\r\n * console.warn fires once per container instead of on every resize-driven\r\n * re-trace. WeakSet keeps the dedupe noise-free across container churn —\r\n * a fresh Master gets one warning, period.\r\n */\r\nconst warnedContainers = new WeakSet<Element>();\r\n\r\ninterface CollectResult {\r\n traced: Element[];\r\n skippedFixed: number;\r\n}\r\n\r\n/**\r\n * Recursively walks the DOM tree and collects all traceable elements.\r\n *\r\n * Also reports how many elements were silently skipped because they used\r\n * `position: fixed` / `position: sticky`. Users can override this skip\r\n * by adding `data-shimmer` to the element, but the resulting overlay\r\n * block will drift on scroll — that's their choice to make.\r\n */\r\nfunction collectTraceableElements(root: Element): CollectResult {\r\n const traced: Element[] = [];\r\n let skippedFixed = 0;\r\n\r\n function walk(el: Element) {\r\n if (el.hasAttribute('data-shimmer-ignore')) return;\r\n if (el.hasAttribute('data-shimmer-reporter')) return; // Ignore nested reporters, they report their own rects\r\n\r\n // `data-shimmer` is an explicit opt-in — trust the user and trace.\r\n // Without it, fixed/sticky elements are skipped entirely (including\r\n // their descendants, which share the broken coordinate space).\r\n if (!el.hasAttribute('data-shimmer') && isViewportLocked(el)) {\r\n skippedFixed += 1;\r\n return;\r\n }\r\n\r\n if (isTraceable(el)) {\r\n traced.push(el);\r\n return; // Don't recurse into traced elements\r\n }\r\n\r\n for (let i = 0; i < el.children.length; i++) {\r\n walk(el.children[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < root.children.length; i++) {\r\n walk(root.children[i]);\r\n }\r\n\r\n return { traced, skippedFixed };\r\n}\r\n\r\n/**\r\n * Measures an element and returns its ShimmerRect relative to the container.\r\n */\r\nfunction measureElement(\r\n el: Element,\r\n containerRect: DOMRect,\r\n globalBorderRadius?: string,\r\n): ShimmerRect | null {\r\n const elRect = el.getBoundingClientRect();\r\n\r\n // If the element is display: none or detached, getBoundingClientRect returns all zeros.\r\n // We must not trace it, otherwise it ends up at the left edge of the screen.\r\n if (elRect.width === 0 && elRect.height === 0 && elRect.left === 0 && elRect.top === 0) {\r\n return null;\r\n }\r\n\r\n const computedStyle = window.getComputedStyle(el);\r\n let borderRadius = globalBorderRadius || computedStyle.borderRadius;\r\n\r\n // If the element has no border radius (common for text tags),\r\n // apply a small default to avoid sharp edges in the shimmer.\r\n const isZero = !borderRadius || borderRadius === 'none' || borderRadius.split(' ').every(v => v === '0' || v === '0px' || v === '0%');\r\n if (isZero) {\r\n borderRadius = '4px';\r\n }\r\n\r\n let width = elRect.width;\r\n let height = elRect.height;\r\n\r\n // Apply fallback dimensions if element has 0 size but is actually visible (e.g. empty inline element)\r\n if (width === 0 || height === 0) {\r\n const fallback = FALLBACK_DIMENSIONS[el.tagName];\r\n if (fallback) {\r\n width = width || fallback.width;\r\n height = height || fallback.height;\r\n }\r\n }\r\n\r\n return {\r\n x: elRect.left - containerRect.left,\r\n y: elRect.top - containerRect.top,\r\n width,\r\n height,\r\n borderRadius,\r\n };\r\n}\r\n\r\n/**\r\n * Performs a full trace of all traceable elements within the container.\r\n *\r\n * @param anchorRef - When provided (Reporter mode), elements are measured\r\n * relative to this element instead of the container. Allows Reporter's\r\n * wrapper to use display:contents without breaking measurement.\r\n */\r\nfunction performTrace(\r\n container: HTMLElement,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const anchor = anchorRef?.current ?? container;\r\n const anchorRect = anchor.getBoundingClientRect();\r\n const { traced, skippedFixed } = collectTraceableElements(container);\r\n\r\n if (skippedFixed > 0 && !warnedContainers.has(container)) {\r\n warnedContainers.add(container);\r\n // eslint-disable-next-line no-console\r\n console.warn(\r\n `[shimmer-trace] Skipped ${skippedFixed} element(s) with ` +\r\n `position:fixed or position:sticky. Their coordinate space ` +\r\n `cannot follow the Master container during scroll, so the overlay ` +\r\n `block would drift. Render a nested <Shimmer> inside the fixed/sticky ` +\r\n `element if you need a skeleton there, or add data-shimmer to opt in ` +\r\n `(positioning may still drift on scroll).`,\r\n );\r\n }\r\n\r\n return traced\r\n .map((el) => measureElement(el, anchorRect, globalBorderRadius))\r\n .filter((r): r is ShimmerRect => r !== null && r.width > 0 && r.height > 0);\r\n}\r\n\r\n/**\r\n * Hook that traces all visible leaf DOM elements within a container\r\n * and returns their measured ShimmerRects.\r\n *\r\n * Uses ResizeObserver to re-trace on container resize.\r\n *\r\n * @param anchorRef - When set, rects are relative to this element (Master).\r\n * Used by Reporter so its display:contents wrapper doesn't break measurement.\r\n */\r\nexport function useTrace(\r\n containerRef: RefObject<HTMLElement | null>,\r\n loading: boolean,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const [rects, setRects] = useState<ShimmerRect[]>([]);\r\n\r\n const trace = useCallback(() => {\r\n if (!containerRef.current) return;\r\n const traced = performTrace(containerRef.current, globalBorderRadius, anchorRef);\r\n setRects(traced);\r\n }, [containerRef, globalBorderRadius, anchorRef]);\r\n\r\n useLayoutEffect(() => {\r\n if (!loading || !containerRef.current) {\r\n setRects([]);\r\n return;\r\n }\r\n\r\n // Initial trace\r\n trace();\r\n\r\n // Re-trace on resize\r\n const observer = new ResizeObserver(() => {\r\n trace();\r\n });\r\n observer.observe(containerRef.current);\r\n\r\n return () => observer.disconnect();\r\n }, [loading, trace]);\r\n\r\n return rects;\r\n}\r\n","const SHIMMER_STYLES_ID = 'shimmer-trace-styles';\r\n\r\nconst CSS = `\r\n@keyframes shimmer-wave {\r\n 0% { transform: translateX(-100%); }\r\n 100% { transform: translateX(100%); }\r\n}\r\n\r\n@keyframes shimmer-pulse {\r\n 0%, 100% { opacity: 0.4; }\r\n 50% { opacity: 1; }\r\n}\r\n\r\n@keyframes shimmer-shine {\r\n 0% { transform: translateX(-150%) skewX(-20deg); }\r\n 100% { transform: translateX(150%) skewX(-20deg); }\r\n}\r\n\r\n@keyframes shimmer-glow {\r\n 0%, 100% { filter: brightness(1); }\r\n 50% { filter: brightness(1.35); }\r\n}\r\n\r\n@keyframes shimmer-gradient {\r\n 0% { background-position: 0% 50%; }\r\n 50% { background-position: 100% 50%; }\r\n 100% { background-position: 0% 50%; }\r\n}\r\n\r\n/* preserveBackground mode: hide text + media but keep container styles */\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(h1,h2,h3,h4,h5,h6,p,span,a,li,label,td,th,blockquote,code,pre,strong,em,small) {\r\n color: transparent !important;\r\n text-shadow: none !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(img,video,svg,canvas,picture) {\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(input,textarea,select,button) {\r\n color: transparent !important;\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] {\r\n pointer-events: none !important;\r\n user-select: none !important;\r\n}\r\n`;\r\n\r\n/**\r\n * Injects the shimmer keyframe animations into the document head.\r\n * Safe to call multiple times — only injects once.\r\n */\r\nexport function injectStyles(): void {\r\n if (typeof document === 'undefined') return;\r\n if (document.getElementById(SHIMMER_STYLES_ID)) return;\r\n\r\n const style = document.createElement('style');\r\n style.id = SHIMMER_STYLES_ID;\r\n style.textContent = CSS;\r\n document.head.appendChild(style);\r\n}\r\n","'use client';\r\n\r\nimport React, { useRef, useCallback, useState, useId, useMemo, useInsertionEffect } from \"react\";\r\nimport { ShimmerProps, ShimmerRect, DEFAULTS } from \"./types\";\r\nimport { ShimmerContext, useShimmerContext } from \"./ShimmerContext\";\r\nimport { ShimmerOverlay } from \"./ShimmerOverlay\";\r\nimport { useTrace } from \"./useTrace\";\r\nimport { injectStyles } from \"./styles\";\r\nimport { generateShimmerKey } from \"./utils\";\r\n\r\n/**\r\n * The main Shimmer component.\r\n *\r\n * Auto-detects **Master** (no parent Shimmer) vs **Reporter** (nested).\r\n * - Master: renders children hidden, traces DOM, paints overlay.\r\n * - Reporter: measures own rects, reports to parent Master.\r\n *\r\n * ### Skeleton shape via `dummyData`\r\n *\r\n * Pass `dummyData` so children render with realistic data while loading.\r\n * No render-prop, no manual `data || fallback` in JSX.\r\n *\r\n * ```tsx\r\n * const userTemplate = { name: 'Loading...', role: '...', avatar: '' };\r\n *\r\n * <Shimmer loading={loading} dummyData={{ user: userTemplate }}>\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * ### List mode (`dummyLength`)\r\n *\r\n * Combined with `dummyData`, clones the first child N times with\r\n * template props merged in:\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyLength={5}\r\n * dummyData={{ fruit: { name: 'xxxxx', price: '$0.00' } }}\r\n * >\r\n * <FruitCard fruit={undefined as any} />\r\n * </Shimmer>\r\n * ```\r\n */\r\nexport function Shimmer({\r\n\tloading = false,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tstopPropagation = false,\r\n\tanimation,\r\n\tbaseColor,\r\n\thighlightColor,\r\n\tspeed,\r\n\tborderRadius,\r\n\tpreserveBackground,\r\n\tclassName,\r\n\tstyle,\r\n}: ShimmerProps) {\r\n\tconst parentContext = useShimmerContext();\r\n\tconst isMaster = !parentContext || stopPropagation;\r\n\tconst id = useId();\r\n\r\n\tconst config = useMemo(\r\n\t\t() => ({\r\n\t\t\tanimation:\r\n\t\t\t\tanimation ?? parentContext?.config.animation ?? DEFAULTS.animation,\r\n\t\t\tbaseColor:\r\n\t\t\t\tbaseColor ?? parentContext?.config.baseColor ?? DEFAULTS.baseColor,\r\n\t\t\thighlightColor:\r\n\t\t\t\thighlightColor ??\r\n\t\t\t\tparentContext?.config.highlightColor ??\r\n\t\t\t\tDEFAULTS.highlightColor,\r\n\t\t\tspeed: speed ?? parentContext?.config.speed ?? DEFAULTS.speed,\r\n\t\t\tborderRadius:\r\n\t\t\t\tborderRadius ??\r\n\t\t\t\tparentContext?.config.borderRadius ??\r\n\t\t\t\tDEFAULTS.borderRadius,\r\n\t\t\tpreserveBackground:\r\n\t\t\t\tpreserveBackground ??\r\n\t\t\t\tparentContext?.config.preserveBackground ??\r\n\t\t\t\tDEFAULTS.preserveBackground,\r\n\t\t}),\r\n\t\t[\r\n\t\t\tanimation,\r\n\t\t\tbaseColor,\r\n\t\t\thighlightColor,\r\n\t\t\tspeed,\r\n\t\t\tborderRadius,\r\n\t\t\tpreserveBackground,\r\n\t\t\tparentContext?.config,\r\n\t\t],\r\n\t);\r\n\r\n\tif (isMaster) {\r\n\t\treturn (\r\n\t\t\t<MasterShimmer\r\n\t\t\t\tid={id}\r\n\t\t\t\tloading={loading}\r\n\t\t\t\tconfig={config}\r\n\t\t\t\tdummyLength={dummyLength}\r\n\t\t\t\tdummyData={dummyData}\r\n\t\t\t\tas={as}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={style}\r\n\t\t\t>\r\n\t\t\t\t{children}\r\n\t\t\t</MasterShimmer>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<ReporterShimmer\r\n\t\t\tid={id}\r\n\t\t\tparentContext={parentContext!}\r\n\t\t\tconfig={config}\r\n\t\t\tdummyLength={dummyLength}\r\n\t\t\tdummyData={dummyData}\r\n\t\t\tas={as}\r\n\t\t>\r\n\t\t\t{children}\r\n\t\t</ReporterShimmer>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Master ─────────────────── */\r\n\r\ninterface MasterShimmerProps {\r\n\tid: string;\r\n\tloading: boolean;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tclassName?: string;\r\n\tstyle?: React.CSSProperties;\r\n}\r\n\r\nfunction MasterShimmer({\r\n\tid,\r\n\tloading,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tclassName,\r\n\tstyle,\r\n}: MasterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst [reporterRects, setReporterRects] = useState<\r\n\t\tRecord<string, ShimmerRect[]>\r\n\t>({});\r\n\r\n\tconst register = useCallback((rid: string, rects: ShimmerRect[]) => {\r\n\t\tsetReporterRects((prev) => ({ ...prev, [rid]: rects }));\r\n\t}, []);\r\n\r\n\tconst unregister = useCallback((rid: string) => {\r\n\t\tsetReporterRects((prev) => {\r\n\t\t\tconst next = { ...prev };\r\n\t\t\tdelete next[rid];\r\n\t\t\treturn next;\r\n\t\t});\r\n\t}, []);\r\n\r\n\tuseInsertionEffect(() => {\r\n\t\tinjectStyles();\r\n\t}, []);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tloading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t);\r\n\r\n\tconst allRects = useMemo(() => {\r\n\t\tconst reported = Object.values(reporterRects).flat();\r\n\t\treturn [...tracedRects, ...reported];\r\n\t}, [tracedRects, reporterRects]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\tconst contextValue = useMemo(\r\n\t\t() => ({\r\n\t\t\tregister,\r\n\t\t\tunregister,\r\n\t\t\tmasterRef: containerRef,\r\n\t\t\tloading,\r\n\t\t\tconfig,\r\n\t\t}),\r\n\t\t[register, unregister, loading, config],\r\n\t);\r\n\r\n\treturn (\r\n\t\t<ShimmerContext.Provider value={contextValue}>\r\n\t\t\t<div\r\n\t\t\t\tref={containerRef}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={{\r\n\t\t\t\t\tposition: \"relative\",\r\n\t\t\t\t\tvisibility:\r\n\t\t\t\t\t\tloading && !config.preserveBackground ? \"hidden\" : undefined,\r\n\t\t\t\t\t...style,\r\n\t\t\t\t}}\r\n\t\t\t\taria-hidden={loading || undefined}\r\n\t\t\t\tdata-shimmer-master\r\n\t\t\t\tdata-shimmer-preserve-bg={\r\n\t\t\t\t\tloading && config.preserveBackground ? \"true\" : undefined\r\n\t\t\t\t}\r\n\t\t\t>\r\n\t\t\t\t{renderedChildren}\r\n\r\n\t\t\t\t{loading && (\r\n\t\t\t\t\t<ShimmerOverlay\r\n\t\t\t\t\t\trects={allRects}\r\n\t\t\t\t\t\tanimation={config.animation}\r\n\t\t\t\t\t\tbaseColor={config.baseColor}\r\n\t\t\t\t\t\thighlightColor={config.highlightColor}\r\n\t\t\t\t\t\tspeed={config.speed}\r\n\t\t\t\t\t/>\r\n\t\t\t\t)}\r\n\t\t\t</div>\r\n\t\t</ShimmerContext.Provider>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Reporter ─────────────────── */\r\n\r\ninterface ReporterShimmerProps {\r\n\tid: string;\r\n\tparentContext: NonNullable<ReturnType<typeof useShimmerContext>>;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n}\r\n\r\nfunction ReporterShimmer({\r\n\tid,\r\n\tparentContext,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n}: ReporterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tparentContext.loading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t\tparentContext.masterRef,\r\n\t);\r\n\r\n\tReact.useLayoutEffect(() => {\r\n\t\tif (!parentContext.loading || tracedRects.length === 0) {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tparentContext.register(id, tracedRects);\r\n\t\treturn () => {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t};\r\n\t}, [tracedRects, parentContext, id]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading: parentContext.loading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\treturn (\r\n\t\t<div\r\n\t\t\tref={containerRef}\r\n\t\t\tdata-shimmer-reporter\r\n\t\t\tstyle={{ display: \"contents\" }}\r\n\t\t>\r\n\t\t\t{renderedChildren}\r\n\t\t</div>\r\n\t);\r\n}\r\n\r\n/* ─────────────── Skeleton Children ─────────────── */\r\n\r\ninterface UseSkeletonChildrenParams {\r\n\tloading: boolean;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tid: string;\r\n}\r\n\r\n/**\r\n * Build the rendered children tree.\r\n *\r\n * Priority during `loading=true`:\r\n * 1. `as` set → render `dummyLength` (or 1) instances of `<as {...dummyData} />`.\r\n * Children ignored. Cold-start safe.\r\n * 2. `dummyData` set + children present → clone each child merging\r\n * dummyData over its props. If `dummyLength` set, clone first\r\n * templated child N times.\r\n * 3. None → pass children through (e.g. `useIsShimmering` flow).\r\n *\r\n * `loading=false` → children untouched.\r\n */\r\nfunction useSkeletonChildren({\r\n\tloading,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tid,\r\n}: UseSkeletonChildrenParams): React.ReactNode {\r\n\tif (!loading) return children;\r\n\r\n\tif (as) {\r\n\t\tconst count = dummyLength && dummyLength > 0 ? dummyLength : 1;\r\n\t\tconst Component = as;\r\n\t\treturn Array.from({ length: count }, (_, i) => (\r\n\t\t\t<Component\r\n\t\t\t\t{...(dummyData || {})}\r\n\t\t\t\tkey={generateShimmerKey(`${id}-as-${i}`)}\r\n\t\t\t/>\r\n\t\t));\r\n\t}\r\n\r\n\tconst childArray = React.Children.toArray(children);\r\n\r\n\tconst templated = childArray.map((c, i) => {\r\n\t\tif (!React.isValidElement(c)) return c;\r\n\t\tconst key = generateShimmerKey(`${id}-tpl-${i}`);\r\n\t\tconst props = dummyData ? { ...dummyData, key } : { key };\r\n\t\treturn React.cloneElement(c as React.ReactElement, props as any);\r\n\t});\r\n\r\n\tif (dummyLength && dummyLength > 0) {\r\n\t\tconst first = templated.find((c) => React.isValidElement(c)) as\r\n\t\t\t| React.ReactElement\r\n\t\t\t| undefined;\r\n\t\tif (!first) return null;\r\n\t\treturn Array.from({ length: dummyLength }, (_, i) =>\r\n\t\t\tReact.cloneElement(first, {\r\n\t\t\t\tkey: generateShimmerKey(`${id}-clone-${i}`),\r\n\t\t\t} as any),\r\n\t\t);\r\n\t}\r\n\r\n\treturn templated;\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { ShimmerConfig, ShimmerProps, DEFAULTS } from './types';\r\n\r\n/**\r\n * Factory function to create a pre-configured Shimmer component.\r\n * Avoids \"Provider Hell\" by baking config into the returned component.\r\n *\r\n * All config properties are optional — defaults are used for anything\r\n * not specified.\r\n *\r\n * @example\r\n * ```tsx\r\n * const AppShimmer = createShimmer({\r\n * animation: 'pulse',\r\n * baseColor: '#1a1a2e',\r\n * highlightColor: '#16213e',\r\n * speed: 2,\r\n * });\r\n *\r\n * <AppShimmer loading={isLoading}>\r\n * <MyComponent />\r\n * </AppShimmer>\r\n * ```\r\n */\r\nexport function createShimmer(config: ShimmerConfig = {}) {\r\n const mergedConfig: Required<ShimmerConfig> = {\r\n animation: config.animation ?? DEFAULTS.animation,\r\n baseColor: config.baseColor ?? DEFAULTS.baseColor,\r\n highlightColor: config.highlightColor ?? DEFAULTS.highlightColor,\r\n speed: config.speed ?? DEFAULTS.speed,\r\n borderRadius: config.borderRadius ?? DEFAULTS.borderRadius,\r\n preserveBackground: config.preserveBackground ?? DEFAULTS.preserveBackground,\r\n };\r\n\r\n function ConfiguredShimmer(props: Omit<ShimmerProps, keyof ShimmerConfig> & Partial<ShimmerConfig>) {\r\n return (\r\n <Shimmer\r\n {...mergedConfig}\r\n {...props}\r\n />\r\n );\r\n }\r\n\r\n // Removed ConfiguredShimmer.displayName\r\n return ConfiguredShimmer;\r\n}\r\n","'use client';\r\n\r\nimport React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { IsShimmeringContext } from './ShimmerContext';\r\nimport { ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerSuspenseProps extends ShimmerConfig {\r\n children: React.ReactNode;\r\n /**\r\n * Explicit skeleton template. Rendered hidden and traced for shimmer shape.\r\n *\r\n * Preferred — pass the same component with no data props:\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * If omitted, falls back to Option B: children are re-rendered with\r\n * `useIsShimmering()=true` so they can return an empty shape themselves.\r\n */\r\n template?: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Suspense boundary that automatically shows a shimmer skeleton while\r\n * children are suspended (e.g. useSuspenseQuery, use(promise), etc).\r\n *\r\n * **Option A — explicit template (preferred):**\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * **Option B — useIsShimmering hook (no template):**\r\n * ```tsx\r\n * function UserCard() {\r\n * const isShimmering = useIsShimmering();\r\n * const data = isShimmering ? null : useSuspenseQuery(...);\r\n * return <div><h3>{data?.name}</h3></div>;\r\n * }\r\n *\r\n * <ShimmerSuspense>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n * Components must use `useIsShimmering()` to skip data fetching in shimmer mode,\r\n * otherwise they will also suspend inside the fallback (causing an empty skeleton).\r\n */\r\nexport function ShimmerSuspense({\r\n children,\r\n template,\r\n ...shimmerConfig\r\n}: ShimmerSuspenseProps) {\r\n const skeletonContent =\r\n template !== undefined ? (\r\n template\r\n ) : (\r\n <IsShimmeringContext.Provider value={true}>\r\n {children}\r\n </IsShimmeringContext.Provider>\r\n );\r\n\r\n return (\r\n <React.Suspense\r\n fallback={\r\n <Shimmer loading={true} {...shimmerConfig}>\r\n {skeletonContent}\r\n </Shimmer>\r\n }\r\n >\r\n {children}\r\n </React.Suspense>\r\n );\r\n}\r\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shimmer-trace",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "High-performance React skeleton loaders that automatically trace your UI dimensions. Synchronized animations, zero CLS, and one-line implementation.",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -53,11 +53,12 @@
|
|
|
53
53
|
"react-dom": ">=18.0.0"
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
|
+
"@types/node": "^25.9.1",
|
|
56
57
|
"@types/react": "^19.1.3",
|
|
57
58
|
"@types/react-dom": "^19.1.3",
|
|
59
|
+
"esbuild": "^0.25.0",
|
|
58
60
|
"react": "^19.1.0",
|
|
59
61
|
"react-dom": "^19.1.0",
|
|
60
|
-
"esbuild": "^0.25.0",
|
|
61
62
|
"tsup": "^8.5.0",
|
|
62
63
|
"typescript": "^6.0.0"
|
|
63
64
|
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/types.ts","../src/ShimmerContext.tsx","../src/ShimmerOverlay.tsx","../src/utils.ts","../src/useTrace.ts","../src/styles.ts","../src/Shimmer.tsx","../src/createShimmer.tsx","../src/ShimmerSuspense.tsx"],"names":["createContext","useContext","jsx","React","useState","useCallback","useLayoutEffect","useId","useMemo","useRef","jsxs","createElement"],"mappings":"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","file":"index.js","sourcesContent":["import React, { ReactNode } from 'react';\r\n\r\n/**\r\n * Represents a measured rectangle of a traced DOM element,\r\n * positioned relative to the Master Shimmer container.\r\n */\r\nexport interface ShimmerRect {\r\n x: number;\r\n y: number;\r\n width: number;\r\n height: number;\r\n borderRadius: string;\r\n}\r\n\r\n/** Available animation types for the shimmer effect. */\r\nexport type AnimationType =\r\n | 'wave'\r\n | 'pulse'\r\n | 'shine'\r\n | 'glow'\r\n | 'gradient';\r\n\r\n/** Configuration options for the shimmer effect (all optional). */\r\nexport interface ShimmerConfig {\r\n /** Animation style. Defaults to 'wave'. */\r\n animation?: AnimationType;\r\n /** Base color of the shimmer blocks. Defaults to '#e0e0e0'. */\r\n baseColor?: string;\r\n /** Highlight color of the shimmer animation. Defaults to '#f5f5f5'. */\r\n highlightColor?: string;\r\n /** Animation duration in seconds. Defaults to 1.5. */\r\n speed?: number;\r\n /** Global border-radius override. If omitted, auto-detected from each element (defaults to 4px if detection is 0px). */\r\n borderRadius?: string;\r\n /**\r\n * Keep container backgrounds, borders, and padding visible while loading.\r\n * When `true` (default), only text and media leaves are hidden via\r\n * `color:transparent` / `opacity:0` so card backgrounds, borders, and\r\n * spacing remain visible underneath the shimmer overlay.\r\n *\r\n * Set `false` for legacy behavior (`visibility:hidden` on whole tree).\r\n */\r\n preserveBackground?: boolean;\r\n}\r\n\r\n/** Props for the Shimmer component. */\r\nexport interface ShimmerProps extends ShimmerConfig {\r\n /** Whether the loading state is active. */\r\n loading?: boolean;\r\n /** The children to trace and render shimmer over. */\r\n children: ReactNode;\r\n /**\r\n * Number of placeholder clones to generate for list-like loading states.\r\n *\r\n * When `loading=true` and `dummyLength` is set, Shimmer grabs the first\r\n * available child (or a cached template from the last loaded render) and\r\n * clones it `dummyLength` times to produce skeleton placeholders.\r\n *\r\n * When `loading=false`, children are rendered as-is.\r\n */\r\n dummyLength?: number;\r\n /**\r\n * Props injected into each child element while `loading=true` so the\r\n * skeleton renders with realistic shape without requiring real data.\r\n *\r\n * Example:\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyData={{ user: { name: 'Loading...', role: '...', avatar: '' } }}\r\n * >\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * While loading, each direct child is cloned with these props merged on top\r\n * of its own props. Ignored when `loading=false`.\r\n */\r\n dummyData?: Record<string, any>;\r\n /**\r\n * Component used to auto-generate skeleton elements while `loading=true`.\r\n *\r\n * When set, Shimmer ignores `children` during loading and renders\r\n * `dummyLength` (defaults to 1) instances of `<as {...dummyData} />`\r\n * to derive shape. Real children render once `loading=false`.\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * as={MovieCard}\r\n * dummyData={{ movie: movieTemplate }}\r\n * dummyLength={10}\r\n * >\r\n * {movies.map((m) => <MovieCard movie={m} key={m.id} />)}\r\n * </Shimmer>\r\n * ```\r\n */\r\n as?: React.ComponentType<any>;\r\n /** Force this Shimmer to be a Master renderer even if nested inside another Shimmer. */\r\n stopPropagation?: boolean;\r\n /**\r\n * className applied to the Master container div.\r\n * Use to control layout (e.g. display:flex) without losing position:relative.\r\n */\r\n className?: string;\r\n /**\r\n * Inline styles merged into the Master container div.\r\n * position:relative is always applied; everything else is overridable.\r\n */\r\n style?: React.CSSProperties;\r\n}\r\n\r\n/** Default configuration values. */\r\nexport const DEFAULTS: Required<ShimmerConfig> = {\r\n animation: 'wave',\r\n baseColor: '#e0e0e0',\r\n highlightColor: '#f5f5f5',\r\n speed: 1.5,\r\n borderRadius: '',\r\n preserveBackground: true,\r\n};\r\n","import { createContext, useContext, RefObject } from 'react';\r\nimport { ShimmerRect, ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerContextValue {\r\n register: (id: string, rects: ShimmerRect[]) => void;\r\n unregister: (id: string) => void;\r\n /** Ref object (not .current) so Reporters always read a fresh value. */\r\n masterRef: RefObject<HTMLElement | null>;\r\n loading: boolean;\r\n config: Required<ShimmerConfig>;\r\n}\r\n\r\nexport const ShimmerContext = createContext<ShimmerContextValue | null>(null);\r\n\r\nexport function useShimmerContext(): ShimmerContextValue | null {\r\n return useContext(ShimmerContext);\r\n}\r\n\r\n/**\r\n * True when rendered inside a ShimmerSuspense fallback (Option B).\r\n * Components use this to skip data fetching and return an empty shape.\r\n */\r\nexport const IsShimmeringContext = createContext<boolean>(false);\r\n\r\nexport function useIsShimmering(): boolean {\r\n return useContext(IsShimmeringContext);\r\n}\r\n","import React from 'react';\r\nimport { ShimmerRect, AnimationType } from './types';\r\n\r\ninterface ShimmerOverlayProps {\r\n rects: ShimmerRect[];\r\n animation: AnimationType;\r\n baseColor: string;\r\n highlightColor: string;\r\n speed: number;\r\n}\r\n\r\n/**\r\n * Returns animation-specific inline styles for each shimmer block.\r\n * For sweep-style animations (wave, shine), the colored gradient is rendered\r\n * as a child layer so the sweep can extend across the container in sync.\r\n */\r\nfunction getBlockStyles(\r\n animation: AnimationType,\r\n baseColor: string,\r\n highlightColor: string,\r\n speed: number,\r\n rect: ShimmerRect,\r\n): React.CSSProperties {\r\n const base: React.CSSProperties = {\r\n position: 'absolute',\r\n top: rect.y,\r\n left: rect.x,\r\n width: rect.width,\r\n height: rect.height,\r\n borderRadius: rect.borderRadius,\r\n overflow: 'hidden',\r\n };\r\n\r\n switch (animation) {\r\n case 'wave':\r\n case 'shine':\r\n return { ...base, background: baseColor };\r\n case 'pulse':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-pulse ${speed}s ease-in-out infinite`,\r\n };\r\n case 'glow':\r\n return {\r\n ...base,\r\n background: baseColor,\r\n animation: `shimmer-glow ${speed}s ease-in-out infinite`,\r\n };\r\n case 'gradient':\r\n return {\r\n ...base,\r\n backgroundImage: `linear-gradient(90deg, ${baseColor}, ${highlightColor}, ${baseColor})`,\r\n backgroundSize: '200% 100%',\r\n animation: `shimmer-gradient ${speed * 1.5}s ease-in-out infinite`,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Sweeping shine layer used by `wave` and `shine` animations.\r\n * Spans the full container width so the highlight sweeps in sync across all blocks.\r\n */\r\nconst SweepLayer: React.FC<{\r\n rect: ShimmerRect;\r\n highlightColor: string;\r\n speed: number;\r\n containerWidth: number;\r\n variant: 'wave' | 'shine';\r\n}> = ({ rect, highlightColor, speed, containerWidth, variant }) => {\r\n const isShine = variant === 'shine';\r\n return (\r\n <div\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: -rect.x,\r\n width: containerWidth > 0 ? containerWidth : '100vw',\r\n height: '100%',\r\n background: isShine\r\n ? `linear-gradient(115deg, transparent 30%, ${highlightColor} 50%, transparent 70%)`\r\n : `linear-gradient(90deg, transparent 0%, ${highlightColor} 50%, transparent 100%)`,\r\n animation: `${isShine ? 'shimmer-shine' : 'shimmer-wave'} ${speed}s ease-in-out infinite`,\r\n }}\r\n />\r\n );\r\n};\r\n\r\n/**\r\n * The overlay component rendered by the Master Shimmer.\r\n *\r\n * Renders one absolutely-positioned div per traced rect. Sweep-style\r\n * animations (`wave`, `shine`) get an additional gradient layer that spans\r\n * the container so the highlight passes across all blocks in sync.\r\n */\r\nexport const ShimmerOverlay: React.FC<ShimmerOverlayProps> = ({\r\n rects,\r\n animation,\r\n baseColor,\r\n highlightColor,\r\n speed,\r\n}) => {\r\n const overlayRef = React.useRef<HTMLDivElement>(null);\r\n const [containerWidth, setContainerWidth] = React.useState(0);\r\n\r\n React.useLayoutEffect(() => {\r\n if (!overlayRef.current?.parentElement) return;\r\n setContainerWidth(overlayRef.current.parentElement.offsetWidth);\r\n }, [rects]);\r\n\r\n if (rects.length === 0) return null;\r\n\r\n const isSweep = animation === 'wave' || animation === 'shine';\r\n\r\n return (\r\n <div\r\n ref={overlayRef}\r\n role=\"status\"\r\n aria-busy=\"true\"\r\n aria-label=\"Loading content\"\r\n data-shimmer-ignore=\"true\"\r\n style={{\r\n position: 'absolute',\r\n top: 0,\r\n left: 0,\r\n width: '100%',\r\n height: '100%',\r\n zIndex: 1,\r\n pointerEvents: 'none',\r\n visibility: 'visible',\r\n }}\r\n >\r\n {rects.map((rect, i) => (\r\n <div\r\n key={i}\r\n style={getBlockStyles(animation, baseColor, highlightColor, speed, rect)}\r\n >\r\n {isSweep && (\r\n <SweepLayer\r\n rect={rect}\r\n highlightColor={highlightColor}\r\n speed={speed}\r\n containerWidth={containerWidth}\r\n variant={animation as 'wave' | 'shine'}\r\n />\r\n )}\r\n </div>\r\n ))}\r\n </div>\r\n );\r\n};\r\n","/**\r\n * Generates a unique key for cloned elements to prevent\r\n * React \"missing key\" warnings during loading state.\r\n */\r\nlet counter = 0;\r\nexport function generateShimmerKey(prefix: string = 'shimmer'): string {\r\n return `${prefix}-clone-${++counter}`;\r\n}\r\n\r\n/**\r\n * Default fallback dimensions for common elements when their\r\n * measured dimensions are 0px (e.g., empty inputs, images not yet loaded).\r\n */\r\nexport const FALLBACK_DIMENSIONS: Record<string, { width: number; height: number }> = {\r\n INPUT: { width: 200, height: 36 },\r\n BUTTON: { width: 120, height: 36 },\r\n TEXTAREA: { width: 300, height: 80 },\r\n SELECT: { width: 200, height: 36 },\r\n IMG: { width: 100, height: 100 },\r\n H1: { width: 300, height: 36 },\r\n H2: { width: 260, height: 30 },\r\n H3: { width: 220, height: 26 },\r\n H4: { width: 200, height: 22 },\r\n H5: { width: 180, height: 20 },\r\n H6: { width: 160, height: 18 },\r\n P: { width: 250, height: 16 },\r\n SPAN: { width: 100, height: 16 },\r\n};\r\n","import { useLayoutEffect, useState, RefObject, useCallback } from 'react';\r\nimport { ShimmerRect } from './types';\r\nimport { FALLBACK_DIMENSIONS } from './utils';\r\n\r\n/**\r\n * Tags that are always considered \"traceable\" leaf elements\r\n * whose dimensions should be captured for the shimmer overlay.\r\n */\r\nconst TRACEABLE_TAGS = new Set([\r\n // Text\r\n 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'SPAN', 'A', 'LI',\r\n 'LABEL', 'TD', 'TH', 'BLOCKQUOTE', 'CODE', 'PRE',\r\n // Media\r\n 'IMG', 'VIDEO', 'SVG', 'CANVAS', 'PICTURE',\r\n // Form\r\n 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON',\r\n // Misc\r\n 'HR',\r\n]);\r\n\r\n/**\r\n * Determines if an element should be traced.\r\n * Explicit data attributes override automatic detection.\r\n */\r\nfunction isTraceable(el: Element): boolean {\r\n if (el.hasAttribute('data-shimmer-ignore')) return false;\r\n if (el.hasAttribute('data-shimmer')) return true;\r\n if (TRACEABLE_TAGS.has(el.tagName)) return true;\r\n\r\n // Leaf element with visible dimensions → trace it\r\n if (el.children.length === 0) {\r\n const rect = el.getBoundingClientRect();\r\n return rect.width > 0 && rect.height > 0;\r\n }\r\n\r\n return false;\r\n}\r\n\r\n/**\r\n * Recursively walks the DOM tree and collects all traceable elements.\r\n */\r\nfunction collectTraceableElements(root: Element): Element[] {\r\n const result: Element[] = [];\r\n\r\n function walk(el: Element) {\r\n if (el.hasAttribute('data-shimmer-ignore')) return;\r\n if (el.hasAttribute('data-shimmer-reporter')) return; // Ignore nested reporters, they report their own rects\r\n\r\n if (isTraceable(el)) {\r\n result.push(el);\r\n return; // Don't recurse into traced elements\r\n }\r\n\r\n for (let i = 0; i < el.children.length; i++) {\r\n walk(el.children[i]);\r\n }\r\n }\r\n\r\n for (let i = 0; i < root.children.length; i++) {\r\n walk(root.children[i]);\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/**\r\n * Measures an element and returns its ShimmerRect relative to the container.\r\n */\r\nfunction measureElement(\r\n el: Element,\r\n containerRect: DOMRect,\r\n globalBorderRadius?: string,\r\n): ShimmerRect | null {\r\n const elRect = el.getBoundingClientRect();\r\n\r\n // If the element is display: none or detached, getBoundingClientRect returns all zeros.\r\n // We must not trace it, otherwise it ends up at the left edge of the screen.\r\n if (elRect.width === 0 && elRect.height === 0 && elRect.left === 0 && elRect.top === 0) {\r\n return null;\r\n }\r\n\r\n const computedStyle = window.getComputedStyle(el);\r\n let borderRadius = globalBorderRadius || computedStyle.borderRadius;\r\n\r\n // If the element has no border radius (common for text tags),\r\n // apply a small default to avoid sharp edges in the shimmer.\r\n const isZero = !borderRadius || borderRadius === 'none' || borderRadius.split(' ').every(v => v === '0' || v === '0px' || v === '0%');\r\n if (isZero) {\r\n borderRadius = '4px';\r\n }\r\n\r\n let width = elRect.width;\r\n let height = elRect.height;\r\n\r\n // Apply fallback dimensions if element has 0 size but is actually visible (e.g. empty inline element)\r\n if (width === 0 || height === 0) {\r\n const fallback = FALLBACK_DIMENSIONS[el.tagName];\r\n if (fallback) {\r\n width = width || fallback.width;\r\n height = height || fallback.height;\r\n }\r\n }\r\n\r\n return {\r\n x: elRect.left - containerRect.left,\r\n y: elRect.top - containerRect.top,\r\n width,\r\n height,\r\n borderRadius,\r\n };\r\n}\r\n\r\n/**\r\n * Performs a full trace of all traceable elements within the container.\r\n *\r\n * @param anchorRef - When provided (Reporter mode), elements are measured\r\n * relative to this element instead of the container. Allows Reporter's\r\n * wrapper to use display:contents without breaking measurement.\r\n */\r\nfunction performTrace(\r\n container: HTMLElement,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const anchor = anchorRef?.current ?? container;\r\n const anchorRect = anchor.getBoundingClientRect();\r\n const elements = collectTraceableElements(container);\r\n\r\n return elements\r\n .map((el) => measureElement(el, anchorRect, globalBorderRadius))\r\n .filter((r): r is ShimmerRect => r !== null && r.width > 0 && r.height > 0);\r\n}\r\n\r\n/**\r\n * Hook that traces all visible leaf DOM elements within a container\r\n * and returns their measured ShimmerRects.\r\n *\r\n * Uses ResizeObserver to re-trace on container resize.\r\n *\r\n * @param anchorRef - When set, rects are relative to this element (Master).\r\n * Used by Reporter so its display:contents wrapper doesn't break measurement.\r\n */\r\nexport function useTrace(\r\n containerRef: RefObject<HTMLElement | null>,\r\n loading: boolean,\r\n globalBorderRadius?: string,\r\n anchorRef?: RefObject<HTMLElement | null>,\r\n): ShimmerRect[] {\r\n const [rects, setRects] = useState<ShimmerRect[]>([]);\r\n\r\n const trace = useCallback(() => {\r\n if (!containerRef.current) return;\r\n const traced = performTrace(containerRef.current, globalBorderRadius, anchorRef);\r\n setRects(traced);\r\n }, [containerRef, globalBorderRadius, anchorRef]);\r\n\r\n useLayoutEffect(() => {\r\n if (!loading || !containerRef.current) {\r\n setRects([]);\r\n return;\r\n }\r\n\r\n // Initial trace\r\n trace();\r\n\r\n // Re-trace on resize\r\n const observer = new ResizeObserver(() => {\r\n trace();\r\n });\r\n observer.observe(containerRef.current);\r\n\r\n return () => observer.disconnect();\r\n }, [loading, trace]);\r\n\r\n return rects;\r\n}\r\n","const SHIMMER_STYLES_ID = 'shimmer-trace-styles';\r\n\r\nconst CSS = `\r\n@keyframes shimmer-wave {\r\n 0% { transform: translateX(-100%); }\r\n 100% { transform: translateX(100%); }\r\n}\r\n\r\n@keyframes shimmer-pulse {\r\n 0%, 100% { opacity: 0.4; }\r\n 50% { opacity: 1; }\r\n}\r\n\r\n@keyframes shimmer-shine {\r\n 0% { transform: translateX(-150%) skewX(-20deg); }\r\n 100% { transform: translateX(150%) skewX(-20deg); }\r\n}\r\n\r\n@keyframes shimmer-glow {\r\n 0%, 100% { filter: brightness(1); }\r\n 50% { filter: brightness(1.35); }\r\n}\r\n\r\n@keyframes shimmer-gradient {\r\n 0% { background-position: 0% 50%; }\r\n 50% { background-position: 100% 50%; }\r\n 100% { background-position: 0% 50%; }\r\n}\r\n\r\n/* preserveBackground mode: hide text + media but keep container styles */\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(h1,h2,h3,h4,h5,h6,p,span,a,li,label,td,th,blockquote,code,pre,strong,em,small) {\r\n color: transparent !important;\r\n text-shadow: none !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(img,video,svg,canvas,picture) {\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] :is(input,textarea,select,button) {\r\n color: transparent !important;\r\n opacity: 0 !important;\r\n}\r\n[data-shimmer-master][data-shimmer-preserve-bg=\"true\"] {\r\n pointer-events: none !important;\r\n user-select: none !important;\r\n}\r\n`;\r\n\r\n/**\r\n * Injects the shimmer keyframe animations into the document head.\r\n * Safe to call multiple times — only injects once.\r\n */\r\nexport function injectStyles(): void {\r\n if (typeof document === 'undefined') return;\r\n if (document.getElementById(SHIMMER_STYLES_ID)) return;\r\n\r\n const style = document.createElement('style');\r\n style.id = SHIMMER_STYLES_ID;\r\n style.textContent = CSS;\r\n document.head.appendChild(style);\r\n}\r\n","import React, { useRef, useCallback, useState, useId, useMemo } from \"react\";\r\nimport { ShimmerProps, ShimmerRect, DEFAULTS } from \"./types\";\r\nimport { ShimmerContext, useShimmerContext } from \"./ShimmerContext\";\r\nimport { ShimmerOverlay } from \"./ShimmerOverlay\";\r\nimport { useTrace } from \"./useTrace\";\r\nimport { injectStyles } from \"./styles\";\r\nimport { generateShimmerKey } from \"./utils\";\r\n\r\n/**\r\n * The main Shimmer component.\r\n *\r\n * Auto-detects **Master** (no parent Shimmer) vs **Reporter** (nested).\r\n * - Master: renders children hidden, traces DOM, paints overlay.\r\n * - Reporter: measures own rects, reports to parent Master.\r\n *\r\n * ### Skeleton shape via `dummyData`\r\n *\r\n * Pass `dummyData` so children render with realistic data while loading.\r\n * No render-prop, no manual `data || fallback` in JSX.\r\n *\r\n * ```tsx\r\n * const userTemplate = { name: 'Loading...', role: '...', avatar: '' };\r\n *\r\n * <Shimmer loading={loading} dummyData={{ user: userTemplate }}>\r\n * <UserCard user={user} />\r\n * </Shimmer>\r\n * ```\r\n *\r\n * ### List mode (`dummyLength`)\r\n *\r\n * Combined with `dummyData`, clones the first child N times with\r\n * template props merged in:\r\n *\r\n * ```tsx\r\n * <Shimmer\r\n * loading={loading}\r\n * dummyLength={5}\r\n * dummyData={{ fruit: { name: 'xxxxx', price: '$0.00' } }}\r\n * >\r\n * <FruitCard fruit={undefined as any} />\r\n * </Shimmer>\r\n * ```\r\n */\r\nexport function Shimmer({\r\n\tloading = false,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tstopPropagation = false,\r\n\tanimation,\r\n\tbaseColor,\r\n\thighlightColor,\r\n\tspeed,\r\n\tborderRadius,\r\n\tpreserveBackground,\r\n\tclassName,\r\n\tstyle,\r\n}: ShimmerProps) {\r\n\tconst parentContext = useShimmerContext();\r\n\tconst isMaster = !parentContext || stopPropagation;\r\n\tconst id = useId();\r\n\r\n\tconst config = useMemo(\r\n\t\t() => ({\r\n\t\t\tanimation:\r\n\t\t\t\tanimation ?? parentContext?.config.animation ?? DEFAULTS.animation,\r\n\t\t\tbaseColor:\r\n\t\t\t\tbaseColor ?? parentContext?.config.baseColor ?? DEFAULTS.baseColor,\r\n\t\t\thighlightColor:\r\n\t\t\t\thighlightColor ??\r\n\t\t\t\tparentContext?.config.highlightColor ??\r\n\t\t\t\tDEFAULTS.highlightColor,\r\n\t\t\tspeed: speed ?? parentContext?.config.speed ?? DEFAULTS.speed,\r\n\t\t\tborderRadius:\r\n\t\t\t\tborderRadius ??\r\n\t\t\t\tparentContext?.config.borderRadius ??\r\n\t\t\t\tDEFAULTS.borderRadius,\r\n\t\t\tpreserveBackground:\r\n\t\t\t\tpreserveBackground ??\r\n\t\t\t\tparentContext?.config.preserveBackground ??\r\n\t\t\t\tDEFAULTS.preserveBackground,\r\n\t\t}),\r\n\t\t[\r\n\t\t\tanimation,\r\n\t\t\tbaseColor,\r\n\t\t\thighlightColor,\r\n\t\t\tspeed,\r\n\t\t\tborderRadius,\r\n\t\t\tpreserveBackground,\r\n\t\t\tparentContext?.config,\r\n\t\t],\r\n\t);\r\n\r\n\tif (isMaster) {\r\n\t\treturn (\r\n\t\t\t<MasterShimmer\r\n\t\t\t\tid={id}\r\n\t\t\t\tloading={loading}\r\n\t\t\t\tconfig={config}\r\n\t\t\t\tdummyLength={dummyLength}\r\n\t\t\t\tdummyData={dummyData}\r\n\t\t\t\tas={as}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={style}\r\n\t\t\t>\r\n\t\t\t\t{children}\r\n\t\t\t</MasterShimmer>\r\n\t\t);\r\n\t}\r\n\r\n\treturn (\r\n\t\t<ReporterShimmer\r\n\t\t\tid={id}\r\n\t\t\tparentContext={parentContext!}\r\n\t\t\tconfig={config}\r\n\t\t\tdummyLength={dummyLength}\r\n\t\t\tdummyData={dummyData}\r\n\t\t\tas={as}\r\n\t\t>\r\n\t\t\t{children}\r\n\t\t</ReporterShimmer>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Master ─────────────────── */\r\n\r\ninterface MasterShimmerProps {\r\n\tid: string;\r\n\tloading: boolean;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tclassName?: string;\r\n\tstyle?: React.CSSProperties;\r\n}\r\n\r\nfunction MasterShimmer({\r\n\tid,\r\n\tloading,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tclassName,\r\n\tstyle,\r\n}: MasterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst [reporterRects, setReporterRects] = useState<\r\n\t\tRecord<string, ShimmerRect[]>\r\n\t>({});\r\n\r\n\tconst register = useCallback((rid: string, rects: ShimmerRect[]) => {\r\n\t\tsetReporterRects((prev) => ({ ...prev, [rid]: rects }));\r\n\t}, []);\r\n\r\n\tconst unregister = useCallback((rid: string) => {\r\n\t\tsetReporterRects((prev) => {\r\n\t\t\tconst next = { ...prev };\r\n\t\t\tdelete next[rid];\r\n\t\t\treturn next;\r\n\t\t});\r\n\t}, []);\r\n\r\n\tReact.useEffect(() => {\r\n\t\tinjectStyles();\r\n\t}, []);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tloading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t);\r\n\r\n\tconst allRects = useMemo(() => {\r\n\t\tconst reported = Object.values(reporterRects).flat();\r\n\t\treturn [...tracedRects, ...reported];\r\n\t}, [tracedRects, reporterRects]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\tconst contextValue = useMemo(\r\n\t\t() => ({\r\n\t\t\tregister,\r\n\t\t\tunregister,\r\n\t\t\tmasterRef: containerRef,\r\n\t\t\tloading,\r\n\t\t\tconfig,\r\n\t\t}),\r\n\t\t[register, unregister, loading, config],\r\n\t);\r\n\r\n\treturn (\r\n\t\t<ShimmerContext.Provider value={contextValue}>\r\n\t\t\t<div\r\n\t\t\t\tref={containerRef}\r\n\t\t\t\tclassName={className}\r\n\t\t\t\tstyle={{\r\n\t\t\t\t\tposition: \"relative\",\r\n\t\t\t\t\tvisibility:\r\n\t\t\t\t\t\tloading && !config.preserveBackground ? \"hidden\" : undefined,\r\n\t\t\t\t\t...style,\r\n\t\t\t\t}}\r\n\t\t\t\taria-hidden={loading || undefined}\r\n\t\t\t\tdata-shimmer-master\r\n\t\t\t\tdata-shimmer-preserve-bg={\r\n\t\t\t\t\tloading && config.preserveBackground ? \"true\" : undefined\r\n\t\t\t\t}\r\n\t\t\t>\r\n\t\t\t\t{renderedChildren}\r\n\r\n\t\t\t\t{loading && (\r\n\t\t\t\t\t<ShimmerOverlay\r\n\t\t\t\t\t\trects={allRects}\r\n\t\t\t\t\t\tanimation={config.animation}\r\n\t\t\t\t\t\tbaseColor={config.baseColor}\r\n\t\t\t\t\t\thighlightColor={config.highlightColor}\r\n\t\t\t\t\t\tspeed={config.speed}\r\n\t\t\t\t\t/>\r\n\t\t\t\t)}\r\n\t\t\t</div>\r\n\t\t</ShimmerContext.Provider>\r\n\t);\r\n}\r\n\r\n/* ─────────────────── Reporter ─────────────────── */\r\n\r\ninterface ReporterShimmerProps {\r\n\tid: string;\r\n\tparentContext: NonNullable<ReturnType<typeof useShimmerContext>>;\r\n\tconfig: Required<typeof DEFAULTS>;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n}\r\n\r\nfunction ReporterShimmer({\r\n\tid,\r\n\tparentContext,\r\n\tconfig,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n}: ReporterShimmerProps) {\r\n\tconst containerRef = useRef<HTMLDivElement>(null);\r\n\r\n\tconst tracedRects = useTrace(\r\n\t\tcontainerRef,\r\n\t\tparentContext.loading,\r\n\t\tconfig.borderRadius || undefined,\r\n\t\tparentContext.masterRef,\r\n\t);\r\n\r\n\tReact.useLayoutEffect(() => {\r\n\t\tif (!parentContext.loading || tracedRects.length === 0) {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tparentContext.register(id, tracedRects);\r\n\t\treturn () => {\r\n\t\t\tparentContext.unregister(id);\r\n\t\t};\r\n\t}, [tracedRects, parentContext, id]);\r\n\r\n\tconst renderedChildren = useSkeletonChildren({\r\n\t\tloading: parentContext.loading,\r\n\t\tchildren,\r\n\t\tdummyLength,\r\n\t\tdummyData,\r\n\t\tas,\r\n\t\tid,\r\n\t});\r\n\r\n\treturn (\r\n\t\t<div\r\n\t\t\tref={containerRef}\r\n\t\t\tdata-shimmer-reporter\r\n\t\t\tstyle={{ display: \"contents\" }}\r\n\t\t>\r\n\t\t\t{renderedChildren}\r\n\t\t</div>\r\n\t);\r\n}\r\n\r\n/* ─────────────── Skeleton Children ─────────────── */\r\n\r\ninterface UseSkeletonChildrenParams {\r\n\tloading: boolean;\r\n\tchildren: React.ReactNode;\r\n\tdummyLength?: number;\r\n\tdummyData?: Record<string, any>;\r\n\tas?: React.ComponentType<any>;\r\n\tid: string;\r\n}\r\n\r\n/**\r\n * Build the rendered children tree.\r\n *\r\n * Priority during `loading=true`:\r\n * 1. `as` set → render `dummyLength` (or 1) instances of `<as {...dummyData} />`.\r\n * Children ignored. Cold-start safe.\r\n * 2. `dummyData` set + children present → clone each child merging\r\n * dummyData over its props. If `dummyLength` set, clone first\r\n * templated child N times.\r\n * 3. None → pass children through (e.g. `useIsShimmering` flow).\r\n *\r\n * `loading=false` → children untouched.\r\n */\r\nfunction useSkeletonChildren({\r\n\tloading,\r\n\tchildren,\r\n\tdummyLength,\r\n\tdummyData,\r\n\tas,\r\n\tid,\r\n}: UseSkeletonChildrenParams): React.ReactNode {\r\n\tif (!loading) return children;\r\n\r\n\tif (as) {\r\n\t\tconst count = dummyLength && dummyLength > 0 ? dummyLength : 1;\r\n\t\tconst Component = as;\r\n\t\treturn Array.from({ length: count }, (_, i) => (\r\n\t\t\t<Component\r\n\t\t\t\t{...(dummyData || {})}\r\n\t\t\t\tkey={generateShimmerKey(`${id}-as-${i}`)}\r\n\t\t\t/>\r\n\t\t));\r\n\t}\r\n\r\n\tconst childArray = React.Children.toArray(children);\r\n\r\n\tconst templated = childArray.map((c, i) => {\r\n\t\tif (!React.isValidElement(c)) return c;\r\n\t\tconst key = generateShimmerKey(`${id}-tpl-${i}`);\r\n\t\tconst props = dummyData ? { ...dummyData, key } : { key };\r\n\t\treturn React.cloneElement(c as React.ReactElement, props as any);\r\n\t});\r\n\r\n\tif (dummyLength && dummyLength > 0) {\r\n\t\tconst first = templated.find((c) => React.isValidElement(c)) as\r\n\t\t\t| React.ReactElement\r\n\t\t\t| undefined;\r\n\t\tif (!first) return null;\r\n\t\treturn Array.from({ length: dummyLength }, (_, i) =>\r\n\t\t\tReact.cloneElement(first, {\r\n\t\t\t\tkey: generateShimmerKey(`${id}-clone-${i}`),\r\n\t\t\t} as any),\r\n\t\t);\r\n\t}\r\n\r\n\treturn templated;\r\n}\r\n","import React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { ShimmerConfig, ShimmerProps, DEFAULTS } from './types';\r\n\r\n/**\r\n * Factory function to create a pre-configured Shimmer component.\r\n * Avoids \"Provider Hell\" by baking config into the returned component.\r\n *\r\n * All config properties are optional — defaults are used for anything\r\n * not specified.\r\n *\r\n * @example\r\n * ```tsx\r\n * const AppShimmer = createShimmer({\r\n * animation: 'pulse',\r\n * baseColor: '#1a1a2e',\r\n * highlightColor: '#16213e',\r\n * speed: 2,\r\n * });\r\n *\r\n * <AppShimmer loading={isLoading}>\r\n * <MyComponent />\r\n * </AppShimmer>\r\n * ```\r\n */\r\nexport function createShimmer(config: ShimmerConfig = {}) {\r\n const mergedConfig: Required<ShimmerConfig> = {\r\n animation: config.animation ?? DEFAULTS.animation,\r\n baseColor: config.baseColor ?? DEFAULTS.baseColor,\r\n highlightColor: config.highlightColor ?? DEFAULTS.highlightColor,\r\n speed: config.speed ?? DEFAULTS.speed,\r\n borderRadius: config.borderRadius ?? DEFAULTS.borderRadius,\r\n preserveBackground: config.preserveBackground ?? DEFAULTS.preserveBackground,\r\n };\r\n\r\n function ConfiguredShimmer(props: Omit<ShimmerProps, keyof ShimmerConfig> & Partial<ShimmerConfig>) {\r\n return (\r\n <Shimmer\r\n {...mergedConfig}\r\n {...props}\r\n />\r\n );\r\n }\r\n\r\n // Removed ConfiguredShimmer.displayName\r\n return ConfiguredShimmer;\r\n}\r\n","import React from 'react';\r\nimport { Shimmer } from './Shimmer';\r\nimport { IsShimmeringContext } from './ShimmerContext';\r\nimport { ShimmerConfig } from './types';\r\n\r\nexport interface ShimmerSuspenseProps extends ShimmerConfig {\r\n children: React.ReactNode;\r\n /**\r\n * Explicit skeleton template. Rendered hidden and traced for shimmer shape.\r\n *\r\n * Preferred — pass the same component with no data props:\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * If omitted, falls back to Option B: children are re-rendered with\r\n * `useIsShimmering()=true` so they can return an empty shape themselves.\r\n */\r\n template?: React.ReactNode;\r\n}\r\n\r\n/**\r\n * Suspense boundary that automatically shows a shimmer skeleton while\r\n * children are suspended (e.g. useSuspenseQuery, use(promise), etc).\r\n *\r\n * **Option A — explicit template (preferred):**\r\n * ```tsx\r\n * <ShimmerSuspense template={<UserCard />}>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n *\r\n * **Option B — useIsShimmering hook (no template):**\r\n * ```tsx\r\n * function UserCard() {\r\n * const isShimmering = useIsShimmering();\r\n * const data = isShimmering ? null : useSuspenseQuery(...);\r\n * return <div><h3>{data?.name}</h3></div>;\r\n * }\r\n *\r\n * <ShimmerSuspense>\r\n * <UserCard />\r\n * </ShimmerSuspense>\r\n * ```\r\n * Components must use `useIsShimmering()` to skip data fetching in shimmer mode,\r\n * otherwise they will also suspend inside the fallback (causing an empty skeleton).\r\n */\r\nexport function ShimmerSuspense({\r\n children,\r\n template,\r\n ...shimmerConfig\r\n}: ShimmerSuspenseProps) {\r\n const skeletonContent =\r\n template !== undefined ? (\r\n template\r\n ) : (\r\n <IsShimmeringContext.Provider value={true}>\r\n {children}\r\n </IsShimmeringContext.Provider>\r\n );\r\n\r\n return (\r\n <React.Suspense\r\n fallback={\r\n <Shimmer loading={true} {...shimmerConfig}>\r\n {skeletonContent}\r\n </Shimmer>\r\n }\r\n >\r\n {children}\r\n </React.Suspense>\r\n );\r\n}\r\n"]}
|