@0xgf/boneyard 1.0.0 → 1.0.1
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/bin/boneyard.mjs +35 -5
- package/dist/extract.js +22 -13
- package/dist/react.d.ts +18 -68
- package/dist/react.js +27 -76
- package/package.json +1 -1
package/bin/boneyard.mjs
CHANGED
|
@@ -133,6 +133,11 @@ console.log(` Output: ${outDir}\n`)
|
|
|
133
133
|
const browser = await chromium.launch()
|
|
134
134
|
const page = await browser.newPage()
|
|
135
135
|
|
|
136
|
+
// Set build mode flag before any page loads so <Skeleton fixture={...}> renders mock content
|
|
137
|
+
await page.addInitScript(() => {
|
|
138
|
+
window.__BONEYARD_BUILD = true
|
|
139
|
+
})
|
|
140
|
+
|
|
136
141
|
// { [skeletonName]: { breakpoints: { [width]: SkeletonResult } } }
|
|
137
142
|
const collected = {}
|
|
138
143
|
|
|
@@ -148,19 +153,44 @@ for (const url of urls) {
|
|
|
148
153
|
// networkidle can timeout on heavy pages — still try to capture
|
|
149
154
|
}
|
|
150
155
|
|
|
151
|
-
// Wait for React to render
|
|
156
|
+
// Wait for React to render
|
|
152
157
|
if (waitMs > 0) await page.waitForTimeout(waitMs)
|
|
153
158
|
|
|
154
|
-
//
|
|
159
|
+
// Find [data-boneyard] elements and extract bones using the real snapshotBones function
|
|
155
160
|
const bones = await page.evaluate(() => {
|
|
156
|
-
|
|
161
|
+
const fn = window.__BONEYARD_SNAPSHOT
|
|
162
|
+
if (!fn) return {}
|
|
163
|
+
|
|
164
|
+
const elements = document.querySelectorAll('[data-boneyard]')
|
|
165
|
+
const results = {}
|
|
166
|
+
|
|
167
|
+
for (const el of elements) {
|
|
168
|
+
const name = el.getAttribute('data-boneyard')
|
|
169
|
+
if (!name) continue
|
|
170
|
+
|
|
171
|
+
// Read snapshotConfig from data attribute
|
|
172
|
+
const configStr = el.getAttribute('data-boneyard-config')
|
|
173
|
+
const config = configStr ? JSON.parse(configStr) : undefined
|
|
174
|
+
|
|
175
|
+
// Target the first rendered child (fixture content)
|
|
176
|
+
const target = el.firstElementChild?.firstElementChild
|
|
177
|
+
if (!target) continue
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
results[name] = fn(target, name, config)
|
|
181
|
+
} catch {
|
|
182
|
+
// skip on error
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return results
|
|
157
187
|
})
|
|
158
188
|
|
|
159
189
|
const names = Object.keys(bones)
|
|
160
190
|
|
|
161
191
|
if (names.length === 0) {
|
|
162
|
-
console.warn(` ⚠ No
|
|
163
|
-
console.warn(` Make sure your <Skeleton> has a name prop and
|
|
192
|
+
console.warn(` ⚠ No <Skeleton name="..."> found at ${width}px`)
|
|
193
|
+
console.warn(` Make sure your <Skeleton> has a name prop and a fixture prop`)
|
|
164
194
|
continue
|
|
165
195
|
}
|
|
166
196
|
|
package/dist/extract.js
CHANGED
|
@@ -10,7 +10,7 @@ const DEFAULT_LEAF_TAGS = new Set(['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li'
|
|
|
10
10
|
export function snapshotBones(el, name = 'component', config) {
|
|
11
11
|
const rootRect = el.getBoundingClientRect();
|
|
12
12
|
const bones = [];
|
|
13
|
-
const leafTags = config?.leafTags ? new Set(config.leafTags) : DEFAULT_LEAF_TAGS;
|
|
13
|
+
const leafTags = config?.leafTags ? new Set([...DEFAULT_LEAF_TAGS, ...config.leafTags]) : DEFAULT_LEAF_TAGS;
|
|
14
14
|
const captureRoundedBorders = config?.captureRoundedBorders ?? true;
|
|
15
15
|
const excludeTags = config?.excludeTags ? new Set(config.excludeTags) : null;
|
|
16
16
|
const excludeSelectors = config?.excludeSelectors ?? null;
|
|
@@ -41,17 +41,18 @@ export function snapshotBones(el, name = 'component', config) {
|
|
|
41
41
|
const hasBorder = captureRoundedBorders && borderTopWidth > 0 && style.borderTopColor !== 'rgba(0, 0, 0, 0)' && style.borderTopColor !== 'transparent';
|
|
42
42
|
const hasBorderRadius = (parseFloat(style.borderTopLeftRadius) || 0) > 0;
|
|
43
43
|
const hasVisualSurface = hasBg || hasBgImage || (hasBorder && hasBorderRadius);
|
|
44
|
+
const isTableNode = tag === 'tr' || tag === 'td' || tag === 'th' || tag === 'thead' || tag === 'tbody' || tag === 'table';
|
|
44
45
|
if (isLeaf) {
|
|
45
46
|
const rect = node.getBoundingClientRect();
|
|
46
47
|
if (rect.width < 1 || rect.height < 1)
|
|
47
48
|
return;
|
|
48
|
-
const br = parseBorderRadius(style, node);
|
|
49
|
+
const br = isTableNode ? 0 : (parseBorderRadius(style, node) ?? 8);
|
|
49
50
|
bones.push({
|
|
50
51
|
x: Math.round(rect.left - rootRect.left),
|
|
51
52
|
y: Math.round(rect.top - rootRect.top),
|
|
52
53
|
w: Math.round(rect.width),
|
|
53
54
|
h: Math.round(rect.height),
|
|
54
|
-
r: br
|
|
55
|
+
r: br,
|
|
55
56
|
});
|
|
56
57
|
return;
|
|
57
58
|
}
|
|
@@ -60,13 +61,13 @@ export function snapshotBones(el, name = 'component', config) {
|
|
|
60
61
|
if (hasVisualSurface) {
|
|
61
62
|
const rect = node.getBoundingClientRect();
|
|
62
63
|
if (rect.width >= 1 && rect.height >= 1) {
|
|
63
|
-
const br = parseBorderRadius(style, node);
|
|
64
|
+
const br = isTableNode ? 0 : (parseBorderRadius(style, node) ?? 8);
|
|
64
65
|
bones.push({
|
|
65
66
|
x: Math.round(rect.left - rootRect.left),
|
|
66
67
|
y: Math.round(rect.top - rootRect.top),
|
|
67
68
|
w: Math.round(rect.width),
|
|
68
69
|
h: Math.round(rect.height),
|
|
69
|
-
r: br
|
|
70
|
+
r: br,
|
|
70
71
|
c: true, // container bone — rendered at reduced opacity
|
|
71
72
|
});
|
|
72
73
|
}
|
|
@@ -133,10 +134,14 @@ function extractNode(el) {
|
|
|
133
134
|
const mar = extractSides(style, 'margin');
|
|
134
135
|
if (mar)
|
|
135
136
|
desc.margin = mar;
|
|
136
|
-
// Border radius
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
// Border radius (skip table elements — they inherit from overflow:hidden parents)
|
|
138
|
+
const elTag = el.tagName.toLowerCase();
|
|
139
|
+
const isTableEl = elTag === 'tr' || elTag === 'td' || elTag === 'th' || elTag === 'thead' || elTag === 'tbody' || elTag === 'table';
|
|
140
|
+
if (!isTableEl) {
|
|
141
|
+
const br = parseBorderRadius(style, el);
|
|
142
|
+
if (br !== undefined)
|
|
143
|
+
desc.borderRadius = br;
|
|
144
|
+
}
|
|
140
145
|
// Max width
|
|
141
146
|
const maxW = parseFloat(style.maxWidth);
|
|
142
147
|
if (maxW > 0 && isFinite(maxW))
|
|
@@ -345,10 +350,14 @@ function snapshotAsLeaf(el, style, desc) {
|
|
|
345
350
|
const rect = el.getBoundingClientRect();
|
|
346
351
|
desc.width = Math.round(rect.width);
|
|
347
352
|
desc.height = Math.round(rect.height);
|
|
348
|
-
// Border radius
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
|
|
353
|
+
// Border radius (skip table elements)
|
|
354
|
+
const leafTag = el.tagName.toLowerCase();
|
|
355
|
+
const isTableLeaf = leafTag === 'tr' || leafTag === 'td' || leafTag === 'th' || leafTag === 'thead' || leafTag === 'tbody' || leafTag === 'table';
|
|
356
|
+
if (!isTableLeaf) {
|
|
357
|
+
const br = parseBorderRadius(style, el);
|
|
358
|
+
if (br !== undefined)
|
|
359
|
+
desc.borderRadius = br;
|
|
360
|
+
}
|
|
352
361
|
// If it has children, try to extract them — we just fix this node's own dimensions
|
|
353
362
|
// so the layout engine treats it as a fixed-size container
|
|
354
363
|
const children = [];
|
package/dist/react.d.ts
CHANGED
|
@@ -9,44 +9,20 @@ import type { SkeletonResult, ResponsiveBones, SnapshotConfig } from './types.js
|
|
|
9
9
|
* ```ts
|
|
10
10
|
* import './bones/registry'
|
|
11
11
|
* ```
|
|
12
|
-
*
|
|
13
|
-
* Then every `<Skeleton name="blog-card">` automatically gets its bones — no
|
|
14
|
-
* `initialBones` prop needed.
|
|
15
12
|
*/
|
|
16
13
|
export declare function registerBones(map: Record<string, SkeletonResult | ResponsiveBones>): void;
|
|
17
14
|
export interface SkeletonProps {
|
|
18
|
-
/** When true, shows the skeleton. When false, shows children
|
|
15
|
+
/** When true, shows the skeleton. When false, shows children. */
|
|
19
16
|
loading: boolean;
|
|
20
|
-
/** Your component — rendered when not loading.
|
|
17
|
+
/** Your component — rendered when not loading. */
|
|
21
18
|
children: ReactNode;
|
|
22
19
|
/**
|
|
23
|
-
* Name this skeleton.
|
|
24
|
-
*
|
|
25
|
-
* - The `boneyard build` CLI reads this registry to generate bones JSON files
|
|
26
|
-
*
|
|
27
|
-
* @example
|
|
28
|
-
* <Skeleton name="blog-card" loading={isLoading}>
|
|
29
|
-
* <BlogCard />
|
|
30
|
-
* </Skeleton>
|
|
31
|
-
*
|
|
32
|
-
* Then run: npx boneyard capture http://localhost:3000 --out ./src/bones
|
|
33
|
-
* Which writes: ./src/bones/blog-card.bones.json
|
|
20
|
+
* Name this skeleton. Used by `npx boneyard build` to identify and capture bones.
|
|
21
|
+
* Also used to auto-resolve pre-generated bones from the registry.
|
|
34
22
|
*/
|
|
35
23
|
name?: string;
|
|
36
24
|
/**
|
|
37
|
-
* Pre-generated bones
|
|
38
|
-
* Accepts a single `SkeletonResult` or a `ResponsiveBones` map (from `boneyard build`).
|
|
39
|
-
*
|
|
40
|
-
* - Single: used regardless of viewport width
|
|
41
|
-
* - Responsive: boneyard picks the nearest breakpoint match for the current container width
|
|
42
|
-
*
|
|
43
|
-
* After the first real render, live `snapshotBones` measurements replace the initial bones.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* import blogBones from './src/bones/blog-card.bones.json'
|
|
47
|
-
* <Skeleton loading={isLoading} initialBones={blogBones}>
|
|
48
|
-
* <BlogCard />
|
|
49
|
-
* </Skeleton>
|
|
25
|
+
* Pre-generated bones. Accepts a single `SkeletonResult` or a `ResponsiveBones` map.
|
|
50
26
|
*/
|
|
51
27
|
initialBones?: SkeletonResult | ResponsiveBones;
|
|
52
28
|
/** Bone color (default: '#e0e0e0') */
|
|
@@ -56,52 +32,26 @@ export interface SkeletonProps {
|
|
|
56
32
|
/** Additional className for the container */
|
|
57
33
|
className?: string;
|
|
58
34
|
/**
|
|
59
|
-
* Shown
|
|
60
|
-
* Unnecessary when initialBones is provided.
|
|
35
|
+
* Shown when loading is true and no bones are available.
|
|
61
36
|
*/
|
|
62
37
|
fallback?: ReactNode;
|
|
63
38
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* }}
|
|
39
|
+
* Mock content rendered during `npx boneyard build` so the CLI can capture
|
|
40
|
+
* bone positions even when real data isn't available.
|
|
41
|
+
* Only rendered when the CLI sets `window.__BONEYARD_BUILD = true`.
|
|
42
|
+
*/
|
|
43
|
+
fixture?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Controls how `npx boneyard build` extracts bones from the fixture.
|
|
46
|
+
* Stored as a data attribute — the CLI reads it during capture.
|
|
73
47
|
*/
|
|
74
48
|
snapshotConfig?: SnapshotConfig;
|
|
75
49
|
}
|
|
76
50
|
/**
|
|
77
51
|
* Wrap any component to get automatic skeleton loading screens.
|
|
78
52
|
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* 2. When loading=true, the cached bones are replayed as a skeleton overlay.
|
|
83
|
-
* 3. On the very first load (no cache yet):
|
|
84
|
-
* - With `initialBones`: shows pre-generated bones immediately, no flash
|
|
85
|
-
* - Without: shows the `fallback` prop
|
|
86
|
-
*
|
|
87
|
-
* For zero first-load flash, run `npx boneyard capture` to generate initialBones.
|
|
88
|
-
*
|
|
89
|
-
* @example Basic
|
|
90
|
-
* ```tsx
|
|
91
|
-
* import { Skeleton } from '@0xgf/boneyard/react'
|
|
92
|
-
*
|
|
93
|
-
* <Skeleton name="blog-card" loading={isLoading}>
|
|
94
|
-
* <BlogCard data={data} />
|
|
95
|
-
* </Skeleton>
|
|
96
|
-
* ```
|
|
97
|
-
*
|
|
98
|
-
* @example With pre-generated responsive bones (zero flash)
|
|
99
|
-
* ```tsx
|
|
100
|
-
* import blogBones from './src/bones/blog-card.bones.json'
|
|
101
|
-
*
|
|
102
|
-
* <Skeleton name="blog-card" loading={isLoading} initialBones={blogBones}>
|
|
103
|
-
* <BlogCard data={data} />
|
|
104
|
-
* </Skeleton>
|
|
105
|
-
* ```
|
|
53
|
+
* 1. Run `npx boneyard build` — captures bone positions from your rendered UI
|
|
54
|
+
* 2. Import the generated registry in your app entry
|
|
55
|
+
* 3. `<Skeleton name="..." loading={isLoading}>` auto-resolves bones by name
|
|
106
56
|
*/
|
|
107
|
-
export declare function Skeleton({ loading, children, name, initialBones, color, animate, className, fallback, snapshotConfig, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export declare function Skeleton({ loading, children, name, initialBones, color, animate, className, fallback, fixture, snapshotConfig, }: SkeletonProps): import("react/jsx-runtime").JSX.Element;
|
package/dist/react.js
CHANGED
|
@@ -2,8 +2,6 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useRef, useState, useEffect } from 'react';
|
|
3
3
|
import { snapshotBones } from './extract.js';
|
|
4
4
|
// ── Bones registry ──────────────────────────────────────────────────────────
|
|
5
|
-
// Module-level registry populated by registerBones() from the generated registry file.
|
|
6
|
-
// This lets <Skeleton name="x"> auto-resolve bones without an explicit initialBones prop.
|
|
7
5
|
const bonesRegistry = new Map();
|
|
8
6
|
/**
|
|
9
7
|
* Register pre-generated bones so `<Skeleton name="...">` can auto-resolve them.
|
|
@@ -14,15 +12,16 @@ const bonesRegistry = new Map();
|
|
|
14
12
|
* ```ts
|
|
15
13
|
* import './bones/registry'
|
|
16
14
|
* ```
|
|
17
|
-
*
|
|
18
|
-
* Then every `<Skeleton name="blog-card">` automatically gets its bones — no
|
|
19
|
-
* `initialBones` prop needed.
|
|
20
15
|
*/
|
|
21
16
|
export function registerBones(map) {
|
|
22
17
|
for (const [name, bones] of Object.entries(map)) {
|
|
23
18
|
bonesRegistry.set(name, bones);
|
|
24
19
|
}
|
|
25
20
|
}
|
|
21
|
+
// ── Expose snapshotBones for CLI build mode (module-level, no useEffect) ────
|
|
22
|
+
if (typeof window !== 'undefined' && window.__BONEYARD_BUILD) {
|
|
23
|
+
window.__BONEYARD_SNAPSHOT = snapshotBones;
|
|
24
|
+
}
|
|
26
25
|
/** Pick the right SkeletonResult from a responsive set for the current width */
|
|
27
26
|
function resolveResponsive(bones, width) {
|
|
28
27
|
if (!('breakpoints' in bones))
|
|
@@ -30,7 +29,6 @@ function resolveResponsive(bones, width) {
|
|
|
30
29
|
const bps = Object.keys(bones.breakpoints).map(Number).sort((a, b) => a - b);
|
|
31
30
|
if (bps.length === 0)
|
|
32
31
|
return null;
|
|
33
|
-
// Largest breakpoint that fits (same logic as CSS min-width media queries)
|
|
34
32
|
const match = [...bps].reverse().find(bp => width >= bp) ?? bps[0];
|
|
35
33
|
return bones.breakpoints[match] ?? null;
|
|
36
34
|
}
|
|
@@ -47,39 +45,15 @@ function lightenHex(hex, amount) {
|
|
|
47
45
|
/**
|
|
48
46
|
* Wrap any component to get automatic skeleton loading screens.
|
|
49
47
|
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* 2. When loading=true, the cached bones are replayed as a skeleton overlay.
|
|
54
|
-
* 3. On the very first load (no cache yet):
|
|
55
|
-
* - With `initialBones`: shows pre-generated bones immediately, no flash
|
|
56
|
-
* - Without: shows the `fallback` prop
|
|
57
|
-
*
|
|
58
|
-
* For zero first-load flash, run `npx boneyard capture` to generate initialBones.
|
|
59
|
-
*
|
|
60
|
-
* @example Basic
|
|
61
|
-
* ```tsx
|
|
62
|
-
* import { Skeleton } from '@0xgf/boneyard/react'
|
|
63
|
-
*
|
|
64
|
-
* <Skeleton name="blog-card" loading={isLoading}>
|
|
65
|
-
* <BlogCard data={data} />
|
|
66
|
-
* </Skeleton>
|
|
67
|
-
* ```
|
|
68
|
-
*
|
|
69
|
-
* @example With pre-generated responsive bones (zero flash)
|
|
70
|
-
* ```tsx
|
|
71
|
-
* import blogBones from './src/bones/blog-card.bones.json'
|
|
72
|
-
*
|
|
73
|
-
* <Skeleton name="blog-card" loading={isLoading} initialBones={blogBones}>
|
|
74
|
-
* <BlogCard data={data} />
|
|
75
|
-
* </Skeleton>
|
|
76
|
-
* ```
|
|
48
|
+
* 1. Run `npx boneyard build` — captures bone positions from your rendered UI
|
|
49
|
+
* 2. Import the generated registry in your app entry
|
|
50
|
+
* 3. `<Skeleton name="..." loading={isLoading}>` auto-resolves bones by name
|
|
77
51
|
*/
|
|
78
|
-
export function Skeleton({ loading, children, name, initialBones, color = '#e0e0e0', animate = true, className, fallback, snapshotConfig, }) {
|
|
52
|
+
export function Skeleton({ loading, children, name, initialBones, color = '#e0e0e0', animate = true, className, fallback, fixture, snapshotConfig, }) {
|
|
79
53
|
const containerRef = useRef(null);
|
|
80
|
-
const [cachedBones, setCachedBones] = useState(null);
|
|
81
54
|
const [containerWidth, setContainerWidth] = useState(0);
|
|
82
|
-
|
|
55
|
+
const isBuildMode = typeof window !== 'undefined' && window.__BONEYARD_BUILD === true;
|
|
56
|
+
// Track container width for responsive breakpoint selection
|
|
83
57
|
useEffect(() => {
|
|
84
58
|
const el = containerRef.current;
|
|
85
59
|
if (!el)
|
|
@@ -91,55 +65,32 @@ export function Skeleton({ loading, children, name, initialBones, color = '#e0e0
|
|
|
91
65
|
setContainerWidth(Math.round(el.getBoundingClientRect().width));
|
|
92
66
|
return () => ro.disconnect();
|
|
93
67
|
}, []);
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Register to global so `boneyard build` CLI can read it
|
|
111
|
-
if (name && typeof window !== 'undefined') {
|
|
112
|
-
const w = window;
|
|
113
|
-
w.__BONEYARD_BONES = w.__BONEYARD_BONES ?? {};
|
|
114
|
-
w.__BONEYARD_BONES[name] = result;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
// keep existing cache on error
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
return () => {
|
|
123
|
-
cancelAnimationFrame(raf1);
|
|
124
|
-
cancelAnimationFrame(raf2);
|
|
125
|
-
};
|
|
126
|
-
}, [loading, name]);
|
|
127
|
-
// Active bones: live cache > explicit initialBones > registry lookup
|
|
128
|
-
const effectiveInitialBones = initialBones ?? (name ? bonesRegistry.get(name) : undefined);
|
|
129
|
-
const resolved = !cachedBones && effectiveInitialBones && containerWidth > 0
|
|
130
|
-
? resolveResponsive(effectiveInitialBones, containerWidth)
|
|
68
|
+
// Data attributes for CLI discovery
|
|
69
|
+
const dataAttrs = {};
|
|
70
|
+
if (name) {
|
|
71
|
+
dataAttrs['data-boneyard'] = name;
|
|
72
|
+
if (snapshotConfig) {
|
|
73
|
+
dataAttrs['data-boneyard-config'] = JSON.stringify(snapshotConfig);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Build mode: render fixture so CLI can capture bones from it
|
|
77
|
+
if (isBuildMode && fixture) {
|
|
78
|
+
return (_jsx("div", { ref: containerRef, className: className, style: { position: 'relative' }, ...dataAttrs, children: _jsx("div", { children: fixture }) }));
|
|
79
|
+
}
|
|
80
|
+
// Resolve bones: explicit initialBones > registry lookup
|
|
81
|
+
const effectiveBones = initialBones ?? (name ? bonesRegistry.get(name) : undefined);
|
|
82
|
+
const activeBones = effectiveBones && containerWidth > 0
|
|
83
|
+
? resolveResponsive(effectiveBones, containerWidth)
|
|
131
84
|
: null;
|
|
132
|
-
const activeBones = cachedBones ?? resolved;
|
|
133
85
|
const showSkeleton = loading && activeBones;
|
|
134
86
|
const showFallback = loading && !activeBones;
|
|
135
|
-
return (_jsxs("div", { ref: containerRef, className: className, style: { position: 'relative' }, children: [_jsx("div", { style: showSkeleton ? { visibility: 'hidden' } : undefined, children: showFallback ? fallback : children }), showSkeleton && (_jsx("div", { style: { position: 'absolute', inset: 0 }, children: _jsxs("div", { style: { position: 'relative', width: '100%', height: activeBones.height }, children: [activeBones.bones.map((b, i) => (_jsx("div", { style: {
|
|
87
|
+
return (_jsxs("div", { ref: containerRef, className: className, style: { position: 'relative' }, ...dataAttrs, children: [_jsx("div", { style: showSkeleton ? { visibility: 'hidden' } : undefined, children: showFallback ? fallback : children }), showSkeleton && (_jsx("div", { style: { position: 'absolute', inset: 0 }, children: _jsxs("div", { style: { position: 'relative', width: '100%', height: activeBones.height }, children: [activeBones.bones.map((b, i) => (_jsx("div", { style: {
|
|
136
88
|
position: 'absolute',
|
|
137
89
|
left: b.x,
|
|
138
90
|
top: b.y,
|
|
139
91
|
width: b.w,
|
|
140
92
|
height: b.h,
|
|
141
93
|
borderRadius: typeof b.r === 'string' ? b.r : `${b.r}px`,
|
|
142
|
-
// Container bones are rendered lighter so children stand out on top
|
|
143
94
|
backgroundColor: b.c ? lightenHex(color, 0.45) : color,
|
|
144
95
|
animation: animate ? 'boneyard-pulse 1.8s ease-in-out infinite' : undefined,
|
|
145
96
|
} }, i))), animate && (_jsx("style", { children: `@keyframes boneyard-pulse{0%,100%{background-color:${color}}50%{background-color:${lightenHex(color, 0.3)}}}` }))] }) }))] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@0xgf/boneyard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Pixel-perfect skeleton loading screens. Wrap your component in <Skeleton> and boneyard snapshots the real DOM layout — no manual descriptors, no configuration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|