@theengineeringmind/auto-skeleton 0.1.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/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/cache-BEybr_z-.d.cts +154 -0
- package/dist/cache-BEybr_z-.d.ts +154 -0
- package/dist/chunk-VI2M6DZS.js +439 -0
- package/dist/chunk-VI2M6DZS.js.map +1 -0
- package/dist/chunk-VTA53XYO.cjs +470 -0
- package/dist/chunk-VTA53XYO.cjs.map +1 -0
- package/dist/index.cjs +128 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +95 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +244 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +86 -0
- package/dist/react.d.ts +86 -0
- package/dist/react.js +228 -0
- package/dist/react.js.map +1 -0
- package/package.json +142 -0
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, ElementType, CSSProperties } from 'react';
|
|
3
|
+
import { M as MeasureOptions, b as SkeletonTheme, L as LayoutCache, a as SkeletonLayout } from './cache-BEybr_z-.js';
|
|
4
|
+
export { B as BlockKind, C as Classification, S as SkeletonBlock, e as autoskeletonCSS, m as measure } from './cache-BEybr_z-.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Defaults shared by every `<AutoSkeleton>` below the provider. Any prop set
|
|
8
|
+
* on an individual component wins over these.
|
|
9
|
+
*/
|
|
10
|
+
interface AutoSkeletonConfig extends MeasureOptions, SkeletonTheme {
|
|
11
|
+
/** Run the shimmer animation. */
|
|
12
|
+
animate?: boolean;
|
|
13
|
+
/** Cross-fade duration in ms when content arrives. */
|
|
14
|
+
fadeDuration?: number;
|
|
15
|
+
/** Accessible label for loading regions. */
|
|
16
|
+
label?: string;
|
|
17
|
+
/** Inject the default stylesheet into the document. */
|
|
18
|
+
injectStyles?: boolean;
|
|
19
|
+
/** CSP nonce for the injected stylesheet. */
|
|
20
|
+
nonce?: string;
|
|
21
|
+
/** Cache instance for `cacheKey` lookups. */
|
|
22
|
+
cache?: LayoutCache;
|
|
23
|
+
/**
|
|
24
|
+
* Precomputed layouts keyed by `cacheKey`. Used on the very first render,
|
|
25
|
+
* including on the server, so skeletons stream down as real blocks with no
|
|
26
|
+
* layout shift and no client-side measurement. Generate them once with
|
|
27
|
+
* `measure()` in a browser (for example a Playwright script in CI) and
|
|
28
|
+
* ship the JSON.
|
|
29
|
+
*/
|
|
30
|
+
layouts?: Readonly<Record<string, SkeletonLayout>>;
|
|
31
|
+
/** Called when measurement throws. The component falls back to `minHeight`. */
|
|
32
|
+
onError?: (error: unknown) => void;
|
|
33
|
+
}
|
|
34
|
+
interface AutoSkeletonProviderProps extends AutoSkeletonConfig {
|
|
35
|
+
children?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Provide app-wide defaults (theme, cache, precomputed layouts, measure
|
|
39
|
+
* options). Providers nest: inner values override outer ones per key.
|
|
40
|
+
*/
|
|
41
|
+
declare function AutoSkeletonProvider({ children, ...config }: AutoSkeletonProviderProps): ReactNode;
|
|
42
|
+
declare function useAutoSkeletonConfig(): AutoSkeletonConfig;
|
|
43
|
+
/** Merge two configs, ignoring `undefined` so an explicit prop never erases a default. */
|
|
44
|
+
declare function mergeConfig(base: AutoSkeletonConfig, override: AutoSkeletonConfig): AutoSkeletonConfig;
|
|
45
|
+
|
|
46
|
+
interface AutoSkeletonProps extends AutoSkeletonConfig {
|
|
47
|
+
/** Show the skeleton instead of `children`. */
|
|
48
|
+
loading: boolean;
|
|
49
|
+
/** The real content. Also used as the measurement source unless `placeholder` is set. */
|
|
50
|
+
children?: ReactNode;
|
|
51
|
+
/**
|
|
52
|
+
* What to measure while loading. Defaults to `children`. Pass a version
|
|
53
|
+
* rendered with sample data when `children` cannot render without data,
|
|
54
|
+
* or `null` to rely purely on `layout`, `cacheKey` and `minHeight`.
|
|
55
|
+
*/
|
|
56
|
+
placeholder?: ReactNode;
|
|
57
|
+
/**
|
|
58
|
+
* A precomputed layout. Rendered as-is on the server and the client with
|
|
59
|
+
* no measurement at all. Takes precedence over `cacheKey` and `layouts`.
|
|
60
|
+
*/
|
|
61
|
+
layout?: SkeletonLayout;
|
|
62
|
+
/**
|
|
63
|
+
* Reuse a layout across mounts and across `loading` cycles. Layouts are
|
|
64
|
+
* keyed by this name and the container width. A cached or provided layout
|
|
65
|
+
* for the name is painted on the first frame; the source is only mounted
|
|
66
|
+
* and measured when no layout exists for the current width.
|
|
67
|
+
*/
|
|
68
|
+
cacheKey?: string;
|
|
69
|
+
/** Height used before the first measurement and during SSR without a layout. */
|
|
70
|
+
minHeight?: number | string;
|
|
71
|
+
/** Wrapper element type. Default `'div'`. */
|
|
72
|
+
as?: ElementType;
|
|
73
|
+
className?: string;
|
|
74
|
+
style?: CSSProperties;
|
|
75
|
+
/** Extra class names for the skeleton container. */
|
|
76
|
+
skeletonClassName?: string;
|
|
77
|
+
/** Called whenever a layout is measured, provided, or read from the cache. */
|
|
78
|
+
onLayout?: (layout: SkeletonLayout) => void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Wrap any content and get a pixel-accurate skeleton for it while `loading`
|
|
82
|
+
* is true, with a cross-fade to the real content when it flips to false.
|
|
83
|
+
*/
|
|
84
|
+
declare const AutoSkeleton: react.ForwardRefExoticComponent<AutoSkeletonProps & react.RefAttributes<HTMLElement>>;
|
|
85
|
+
|
|
86
|
+
export { AutoSkeleton, type AutoSkeletonConfig, type AutoSkeletonProps, AutoSkeletonProvider, type AutoSkeletonProviderProps, LayoutCache, MeasureOptions, SkeletonLayout, SkeletonTheme, mergeConfig, useAutoSkeletonConfig };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { defaultLayoutCache, layoutCacheKey, measure, ensureStyles, containerStyle, blockStyle, blockClassName, FADING_CLASS, CONTAINER_CLASS } from './chunk-VI2M6DZS.js';
|
|
3
|
+
export { LayoutCache, autoskeletonCSS, measure } from './chunk-VI2M6DZS.js';
|
|
4
|
+
import { createContext, forwardRef, useRef, useImperativeHandle, useState, useEffect, useMemo, useCallback, useContext, useLayoutEffect } from 'react';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
|
|
7
|
+
var AutoSkeletonContext = createContext({});
|
|
8
|
+
AutoSkeletonContext.displayName = "AutoSkeletonContext";
|
|
9
|
+
function AutoSkeletonProvider({ children, ...config }) {
|
|
10
|
+
const parent = useContext(AutoSkeletonContext);
|
|
11
|
+
const value = useMemo(() => mergeConfig(parent, config), [parent, config]);
|
|
12
|
+
return /* @__PURE__ */ jsx(AutoSkeletonContext.Provider, { value, children });
|
|
13
|
+
}
|
|
14
|
+
function useAutoSkeletonConfig() {
|
|
15
|
+
return useContext(AutoSkeletonContext);
|
|
16
|
+
}
|
|
17
|
+
function mergeConfig(base, override) {
|
|
18
|
+
const out = { ...base };
|
|
19
|
+
for (const key of Object.keys(override)) {
|
|
20
|
+
const value = override[key];
|
|
21
|
+
if (value !== void 0) out[key] = value;
|
|
22
|
+
}
|
|
23
|
+
if (base.layouts && override.layouts) out.layouts = { ...base.layouts, ...override.layouts };
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
27
|
+
var MEASURE_STYLE = {
|
|
28
|
+
position: "absolute",
|
|
29
|
+
top: 0,
|
|
30
|
+
left: 0,
|
|
31
|
+
width: "100%",
|
|
32
|
+
clipPath: "inset(50%)",
|
|
33
|
+
overflow: "hidden",
|
|
34
|
+
pointerEvents: "none",
|
|
35
|
+
userSelect: "none"
|
|
36
|
+
};
|
|
37
|
+
var OVERLAY_STYLE = { position: "absolute", top: 0, left: 0, right: 0 };
|
|
38
|
+
function cx(...names) {
|
|
39
|
+
return names.filter(Boolean).join(" ");
|
|
40
|
+
}
|
|
41
|
+
var isDev = typeof process !== "undefined" && process.env?.["NODE_ENV"] !== "production";
|
|
42
|
+
var AutoSkeleton = forwardRef(function AutoSkeleton2(props, ref) {
|
|
43
|
+
const context = useAutoSkeletonConfig();
|
|
44
|
+
const {
|
|
45
|
+
loading,
|
|
46
|
+
children,
|
|
47
|
+
placeholder,
|
|
48
|
+
layout: layoutProp,
|
|
49
|
+
cacheKey,
|
|
50
|
+
minHeight,
|
|
51
|
+
as: Tag = "div",
|
|
52
|
+
className,
|
|
53
|
+
style,
|
|
54
|
+
skeletonClassName,
|
|
55
|
+
onLayout,
|
|
56
|
+
...ownConfig
|
|
57
|
+
} = props;
|
|
58
|
+
const config = mergeConfig(context, ownConfig);
|
|
59
|
+
const {
|
|
60
|
+
fadeDuration = 200,
|
|
61
|
+
animate = true,
|
|
62
|
+
label = "Loading",
|
|
63
|
+
injectStyles = true,
|
|
64
|
+
nonce,
|
|
65
|
+
cache = defaultLayoutCache,
|
|
66
|
+
layouts,
|
|
67
|
+
onError,
|
|
68
|
+
color,
|
|
69
|
+
highlight,
|
|
70
|
+
duration,
|
|
71
|
+
fade,
|
|
72
|
+
textScale,
|
|
73
|
+
minSize,
|
|
74
|
+
maxBackgroundBlock,
|
|
75
|
+
mergeGap,
|
|
76
|
+
textRadius,
|
|
77
|
+
maxBlocks,
|
|
78
|
+
classify
|
|
79
|
+
} = config;
|
|
80
|
+
const wrapperRef = useRef(null);
|
|
81
|
+
useImperativeHandle(ref, () => wrapperRef.current, []);
|
|
82
|
+
const measureRef = useRef(null);
|
|
83
|
+
const [initialLayout] = useState(() => {
|
|
84
|
+
if (layoutProp) return layoutProp;
|
|
85
|
+
if (cacheKey === void 0) return null;
|
|
86
|
+
return layouts?.[cacheKey] ?? cache.latest(cacheKey) ?? null;
|
|
87
|
+
});
|
|
88
|
+
const [measured, setMeasured] = useState(null);
|
|
89
|
+
const layout = layoutProp ?? measured ?? initialLayout;
|
|
90
|
+
const [needsSource, setNeedsSource] = useState(initialLayout === null && layoutProp === void 0);
|
|
91
|
+
const [prevLoading, setPrevLoading] = useState(loading);
|
|
92
|
+
const [fading, setFading] = useState(false);
|
|
93
|
+
if (prevLoading !== loading) {
|
|
94
|
+
setPrevLoading(loading);
|
|
95
|
+
setFading(!loading && layout !== null && fadeDuration > 0);
|
|
96
|
+
}
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!fading) return;
|
|
99
|
+
const timer = setTimeout(() => setFading(false), fadeDuration);
|
|
100
|
+
return () => clearTimeout(timer);
|
|
101
|
+
}, [fading, fadeDuration]);
|
|
102
|
+
const measureOptions = useMemo(
|
|
103
|
+
() => ({ textScale, minSize, maxBackgroundBlock, mergeGap, textRadius, maxBlocks, classify }),
|
|
104
|
+
[textScale, minSize, maxBackgroundBlock, mergeGap, textRadius, maxBlocks, classify]
|
|
105
|
+
);
|
|
106
|
+
const theme = useMemo(
|
|
107
|
+
() => ({ color, highlight, duration, fade }),
|
|
108
|
+
[color, highlight, duration, fade]
|
|
109
|
+
);
|
|
110
|
+
const callbacks = useRef({ onLayout, onError });
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
callbacks.current = { onLayout, onError };
|
|
113
|
+
}, [onLayout, onError]);
|
|
114
|
+
const layoutRef = useRef(null);
|
|
115
|
+
useIsomorphicLayoutEffect(() => {
|
|
116
|
+
layoutRef.current = layout;
|
|
117
|
+
});
|
|
118
|
+
const reported = useRef(null);
|
|
119
|
+
const report = useCallback((next) => {
|
|
120
|
+
if (reported.current === next) return;
|
|
121
|
+
reported.current = next;
|
|
122
|
+
callbacks.current.onLayout?.(next);
|
|
123
|
+
}, []);
|
|
124
|
+
const runMeasure = useCallback(() => {
|
|
125
|
+
const wrapper = wrapperRef.current;
|
|
126
|
+
if (!wrapper || layoutProp) return;
|
|
127
|
+
const width = wrapper.clientWidth;
|
|
128
|
+
const current = layoutRef.current;
|
|
129
|
+
if (current && Math.abs(current.width - width) <= 1) {
|
|
130
|
+
setNeedsSource(false);
|
|
131
|
+
report(current);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const key = cacheKey === void 0 ? void 0 : layoutCacheKey(cacheKey, width);
|
|
135
|
+
let next = key === void 0 ? void 0 : lookupLayout(cache, layouts, cacheKey, key, width);
|
|
136
|
+
if (!next) {
|
|
137
|
+
const source2 = measureRef.current;
|
|
138
|
+
if (!source2) {
|
|
139
|
+
setNeedsSource(true);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
next = measure(source2, measureOptions);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
callbacks.current.onError?.(error);
|
|
146
|
+
if (isDev && !callbacks.current.onError) console.error("auto-skeleton: measurement failed", error);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (key !== void 0) cache.set(key, next);
|
|
150
|
+
}
|
|
151
|
+
setMeasured(next);
|
|
152
|
+
setNeedsSource(false);
|
|
153
|
+
report(next);
|
|
154
|
+
}, [cache, cacheKey, layoutProp, layouts, measureOptions, report]);
|
|
155
|
+
useIsomorphicLayoutEffect(() => {
|
|
156
|
+
if (!loading) return;
|
|
157
|
+
if (injectStyles) ensureStyles(document, nonce);
|
|
158
|
+
if (layoutProp) {
|
|
159
|
+
report(layoutProp);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const source2 = measureRef.current;
|
|
163
|
+
if (source2) source2.inert = true;
|
|
164
|
+
runMeasure();
|
|
165
|
+
const wrapper = wrapperRef.current;
|
|
166
|
+
if (!wrapper || typeof ResizeObserver === "undefined") return;
|
|
167
|
+
let frame = 0;
|
|
168
|
+
let lastWidth = wrapper.clientWidth;
|
|
169
|
+
const observer = new ResizeObserver(() => {
|
|
170
|
+
const width = wrapper.clientWidth;
|
|
171
|
+
if (width === lastWidth) return;
|
|
172
|
+
lastWidth = width;
|
|
173
|
+
cancelAnimationFrame(frame);
|
|
174
|
+
frame = requestAnimationFrame(runMeasure);
|
|
175
|
+
});
|
|
176
|
+
observer.observe(wrapper);
|
|
177
|
+
return () => {
|
|
178
|
+
observer.disconnect();
|
|
179
|
+
cancelAnimationFrame(frame);
|
|
180
|
+
};
|
|
181
|
+
}, [loading, needsSource, runMeasure, injectStyles, nonce, layoutProp, report]);
|
|
182
|
+
const source = placeholder === void 0 ? children : placeholder;
|
|
183
|
+
const mountSource = loading && needsSource && source != null;
|
|
184
|
+
const showSkeleton = loading || fading;
|
|
185
|
+
const state = loading ? "loading" : fading ? "fading" : "idle";
|
|
186
|
+
if (isDev && loading && layout === null && source == null && minHeight === void 0) {
|
|
187
|
+
console.warn(
|
|
188
|
+
"auto-skeleton: nothing to measure and no layout, cacheKey, or minHeight given; the skeleton will be empty."
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
const skeletonStyle = layout ? { ...containerStyle(layout, theme), ...fading ? OVERLAY_STYLE : null } : { height: minHeight ?? 0 };
|
|
192
|
+
return /* @__PURE__ */ jsxs(
|
|
193
|
+
Tag,
|
|
194
|
+
{
|
|
195
|
+
ref: wrapperRef,
|
|
196
|
+
className,
|
|
197
|
+
style: { position: "relative", ...style },
|
|
198
|
+
"data-auto-skeleton-state": state,
|
|
199
|
+
children: [
|
|
200
|
+
mountSource ? /* @__PURE__ */ jsx("div", { ref: measureRef, "aria-hidden": true, style: MEASURE_STYLE, children: source }) : null,
|
|
201
|
+
showSkeleton ? /* @__PURE__ */ jsx(
|
|
202
|
+
"div",
|
|
203
|
+
{
|
|
204
|
+
className: cx(CONTAINER_CLASS, fading && FADING_CLASS, skeletonClassName),
|
|
205
|
+
role: "status",
|
|
206
|
+
"aria-busy": loading,
|
|
207
|
+
"aria-label": label,
|
|
208
|
+
"data-animate": String(animate),
|
|
209
|
+
style: skeletonStyle,
|
|
210
|
+
children: layout?.blocks.map((block, index) => /* @__PURE__ */ jsx("div", { className: blockClassName(block), "aria-hidden": true, style: blockStyle(block) }, index))
|
|
211
|
+
}
|
|
212
|
+
) : null,
|
|
213
|
+
loading ? null : children
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
function lookupLayout(cache, layouts, name, key, width) {
|
|
219
|
+
const cached = cache.get(key);
|
|
220
|
+
if (cached) return cached;
|
|
221
|
+
const provided = name === void 0 ? void 0 : layouts?.[name];
|
|
222
|
+
if (provided && Math.abs(provided.width - width) <= 1) return provided;
|
|
223
|
+
return void 0;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { AutoSkeleton, AutoSkeletonProvider, mergeConfig, useAutoSkeletonConfig };
|
|
227
|
+
//# sourceMappingURL=react.js.map
|
|
228
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/context.tsx","../src/react/AutoSkeleton.tsx"],"names":["AutoSkeleton","useMemo","source","jsx"],"mappings":"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","file":"react.js","sourcesContent":["import { createContext, useContext, useMemo, type ReactNode } from 'react';\nimport type { LayoutCache, MeasureOptions, SkeletonLayout, SkeletonTheme } from '../core';\n\n/**\n * Defaults shared by every `<AutoSkeleton>` below the provider. Any prop set\n * on an individual component wins over these.\n */\nexport interface AutoSkeletonConfig extends MeasureOptions, SkeletonTheme {\n /** Run the shimmer animation. */\n animate?: boolean;\n /** Cross-fade duration in ms when content arrives. */\n fadeDuration?: number;\n /** Accessible label for loading regions. */\n label?: string;\n /** Inject the default stylesheet into the document. */\n injectStyles?: boolean;\n /** CSP nonce for the injected stylesheet. */\n nonce?: string;\n /** Cache instance for `cacheKey` lookups. */\n cache?: LayoutCache;\n /**\n * Precomputed layouts keyed by `cacheKey`. Used on the very first render,\n * including on the server, so skeletons stream down as real blocks with no\n * layout shift and no client-side measurement. Generate them once with\n * `measure()` in a browser (for example a Playwright script in CI) and\n * ship the JSON.\n */\n layouts?: Readonly<Record<string, SkeletonLayout>>;\n /** Called when measurement throws. The component falls back to `minHeight`. */\n onError?: (error: unknown) => void;\n}\n\nconst AutoSkeletonContext = createContext<AutoSkeletonConfig>({});\nAutoSkeletonContext.displayName = 'AutoSkeletonContext';\n\nexport interface AutoSkeletonProviderProps extends AutoSkeletonConfig {\n children?: ReactNode;\n}\n\n/**\n * Provide app-wide defaults (theme, cache, precomputed layouts, measure\n * options). Providers nest: inner values override outer ones per key.\n */\nexport function AutoSkeletonProvider({ children, ...config }: AutoSkeletonProviderProps): ReactNode {\n const parent = useContext(AutoSkeletonContext);\n const value = useMemo(() => mergeConfig(parent, config), [parent, config]);\n return <AutoSkeletonContext.Provider value={value}>{children}</AutoSkeletonContext.Provider>;\n}\n\nexport function useAutoSkeletonConfig(): AutoSkeletonConfig {\n return useContext(AutoSkeletonContext);\n}\n\n/** Merge two configs, ignoring `undefined` so an explicit prop never erases a default. */\nexport function mergeConfig(base: AutoSkeletonConfig, override: AutoSkeletonConfig): AutoSkeletonConfig {\n const out: AutoSkeletonConfig = { ...base };\n for (const key of Object.keys(override) as (keyof AutoSkeletonConfig)[]) {\n const value = override[key];\n if (value !== undefined) (out as Record<string, unknown>)[key] = value;\n }\n if (base.layouts && override.layouts) out.layouts = { ...base.layouts, ...override.layouts };\n return out;\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ElementType,\n type ReactNode,\n} from 'react';\nimport {\n CONTAINER_CLASS,\n FADING_CLASS,\n blockClassName,\n blockStyle,\n containerStyle,\n defaultLayoutCache,\n ensureStyles,\n layoutCacheKey,\n measure,\n type MeasureOptions,\n type SkeletonLayout,\n type SkeletonTheme,\n} from '../core';\nimport { mergeConfig, useAutoSkeletonConfig, type AutoSkeletonConfig } from './context';\n\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect;\n\nexport interface AutoSkeletonProps extends AutoSkeletonConfig {\n /** Show the skeleton instead of `children`. */\n loading: boolean;\n /** The real content. Also used as the measurement source unless `placeholder` is set. */\n children?: ReactNode;\n /**\n * What to measure while loading. Defaults to `children`. Pass a version\n * rendered with sample data when `children` cannot render without data,\n * or `null` to rely purely on `layout`, `cacheKey` and `minHeight`.\n */\n placeholder?: ReactNode;\n /**\n * A precomputed layout. Rendered as-is on the server and the client with\n * no measurement at all. Takes precedence over `cacheKey` and `layouts`.\n */\n layout?: SkeletonLayout;\n /**\n * Reuse a layout across mounts and across `loading` cycles. Layouts are\n * keyed by this name and the container width. A cached or provided layout\n * for the name is painted on the first frame; the source is only mounted\n * and measured when no layout exists for the current width.\n */\n cacheKey?: string;\n /** Height used before the first measurement and during SSR without a layout. */\n minHeight?: number | string;\n /** Wrapper element type. Default `'div'`. */\n as?: ElementType;\n className?: string;\n style?: CSSProperties;\n /** Extra class names for the skeleton container. */\n skeletonClassName?: string;\n /** Called whenever a layout is measured, provided, or read from the cache. */\n onLayout?: (layout: SkeletonLayout) => void;\n}\n\nconst MEASURE_STYLE: CSSProperties = {\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n clipPath: 'inset(50%)',\n overflow: 'hidden',\n pointerEvents: 'none',\n userSelect: 'none',\n};\n\nconst OVERLAY_STYLE: CSSProperties = { position: 'absolute', top: 0, left: 0, right: 0 };\n\nfunction cx(...names: (string | false | null | undefined)[]): string {\n return names.filter(Boolean).join(' ');\n}\n\nconst isDev = typeof process !== 'undefined' && process.env?.['NODE_ENV'] !== 'production';\n\n/**\n * Wrap any content and get a pixel-accurate skeleton for it while `loading`\n * is true, with a cross-fade to the real content when it flips to false.\n */\nexport const AutoSkeleton = forwardRef<HTMLElement, AutoSkeletonProps>(function AutoSkeleton(props, ref) {\n const context = useAutoSkeletonConfig();\n const {\n loading,\n children,\n placeholder,\n layout: layoutProp,\n cacheKey,\n minHeight,\n as: Tag = 'div',\n className,\n style,\n skeletonClassName,\n onLayout,\n ...ownConfig\n } = props;\n const config = mergeConfig(context, ownConfig);\n const {\n fadeDuration = 200,\n animate = true,\n label = 'Loading',\n injectStyles = true,\n nonce,\n cache = defaultLayoutCache,\n layouts,\n onError,\n color,\n highlight,\n duration,\n fade,\n textScale,\n minSize,\n maxBackgroundBlock,\n mergeGap,\n textRadius,\n maxBlocks,\n classify,\n } = config;\n\n const wrapperRef = useRef<HTMLElement | null>(null);\n useImperativeHandle(ref, () => wrapperRef.current as HTMLElement, []);\n const measureRef = useRef<HTMLDivElement | null>(null);\n\n // First-frame layout without touching the DOM: an explicit prop, a\n // provided (server-shipped) layout, or the freshest cache entry for the key.\n const [initialLayout] = useState<SkeletonLayout | null>(() => {\n if (layoutProp) return layoutProp;\n if (cacheKey === undefined) return null;\n return layouts?.[cacheKey] ?? cache.latest(cacheKey) ?? null;\n });\n const [measured, setMeasured] = useState<SkeletonLayout | null>(null);\n const layout = layoutProp ?? measured ?? initialLayout;\n\n // The source is only mounted when there is nothing better to show, which\n // keeps repeat loads free of any measurement or extra rendering.\n const [needsSource, setNeedsSource] = useState(initialLayout === null && layoutProp === undefined);\n\n const [prevLoading, setPrevLoading] = useState(loading);\n const [fading, setFading] = useState(false);\n if (prevLoading !== loading) {\n setPrevLoading(loading);\n setFading(!loading && layout !== null && fadeDuration > 0);\n }\n useEffect(() => {\n if (!fading) return;\n const timer = setTimeout(() => setFading(false), fadeDuration);\n return () => clearTimeout(timer);\n }, [fading, fadeDuration]);\n\n const measureOptions = useMemo<MeasureOptions>(\n () => ({ textScale, minSize, maxBackgroundBlock, mergeGap, textRadius, maxBlocks, classify }),\n [textScale, minSize, maxBackgroundBlock, mergeGap, textRadius, maxBlocks, classify],\n );\n const theme = useMemo<SkeletonTheme>(\n () => ({ color, highlight, duration, fade }),\n [color, highlight, duration, fade],\n );\n\n const callbacks = useRef({ onLayout, onError });\n useEffect(() => {\n callbacks.current = { onLayout, onError };\n }, [onLayout, onError]);\n const layoutRef = useRef<SkeletonLayout | null>(null);\n useIsomorphicLayoutEffect(() => {\n layoutRef.current = layout;\n });\n const reported = useRef<SkeletonLayout | null>(null);\n const report = useCallback((next: SkeletonLayout) => {\n if (reported.current === next) return;\n reported.current = next;\n callbacks.current.onLayout?.(next);\n }, []);\n\n const runMeasure = useCallback(() => {\n const wrapper = wrapperRef.current;\n if (!wrapper || layoutProp) return;\n const width = wrapper.clientWidth;\n\n // Already have a layout for this width: nothing to do, and the source\n // can go (or stay unmounted).\n const current = layoutRef.current;\n if (current && Math.abs(current.width - width) <= 1) {\n setNeedsSource(false);\n report(current);\n return;\n }\n\n const key = cacheKey === undefined ? undefined : layoutCacheKey(cacheKey, width);\n let next = key === undefined ? undefined : lookupLayout(cache, layouts, cacheKey, key, width);\n if (!next) {\n const source = measureRef.current;\n if (!source) {\n // Nothing to measure at this width. Ask for the source and try again\n // once it has rendered.\n setNeedsSource(true);\n return;\n }\n try {\n next = measure(source, measureOptions);\n } catch (error) {\n callbacks.current.onError?.(error);\n if (isDev && !callbacks.current.onError) console.error('auto-skeleton: measurement failed', error);\n return;\n }\n if (key !== undefined) cache.set(key, next);\n }\n setMeasured(next);\n setNeedsSource(false);\n report(next);\n }, [cache, cacheKey, layoutProp, layouts, measureOptions, report]);\n\n useIsomorphicLayoutEffect(() => {\n if (!loading) return;\n if (injectStyles) ensureStyles(document, nonce);\n if (layoutProp) {\n report(layoutProp);\n return;\n }\n const source = measureRef.current;\n if (source) source.inert = true;\n runMeasure();\n\n const wrapper = wrapperRef.current;\n if (!wrapper || typeof ResizeObserver === 'undefined') return;\n let frame = 0;\n let lastWidth = wrapper.clientWidth;\n const observer = new ResizeObserver(() => {\n const width = wrapper.clientWidth;\n if (width === lastWidth) return;\n lastWidth = width;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(runMeasure);\n });\n observer.observe(wrapper);\n return () => {\n observer.disconnect();\n cancelAnimationFrame(frame);\n };\n }, [loading, needsSource, runMeasure, injectStyles, nonce, layoutProp, report]);\n\n const source = placeholder === undefined ? children : placeholder;\n const mountSource = loading && needsSource && source != null;\n const showSkeleton = loading || fading;\n const state = loading ? 'loading' : fading ? 'fading' : 'idle';\n\n if (isDev && loading && layout === null && source == null && minHeight === undefined) {\n console.warn(\n 'auto-skeleton: nothing to measure and no layout, cacheKey, or minHeight given; the skeleton will be empty.',\n );\n }\n\n const skeletonStyle: CSSProperties = layout\n ? { ...(containerStyle(layout, theme) as CSSProperties), ...(fading ? OVERLAY_STYLE : null) }\n : { height: minHeight ?? 0 };\n\n return (\n <Tag\n ref={wrapperRef}\n className={className}\n style={{ position: 'relative', ...style }}\n data-auto-skeleton-state={state}\n >\n {mountSource ? (\n <div ref={measureRef} aria-hidden style={MEASURE_STYLE}>\n {source}\n </div>\n ) : null}\n {showSkeleton ? (\n <div\n className={cx(CONTAINER_CLASS, fading && FADING_CLASS, skeletonClassName)}\n role=\"status\"\n aria-busy={loading}\n aria-label={label}\n data-animate={String(animate)}\n style={skeletonStyle}\n >\n {layout?.blocks.map((block, index) => (\n <div key={index} className={blockClassName(block)} aria-hidden style={blockStyle(block)} />\n ))}\n </div>\n ) : null}\n {loading ? null : children}\n </Tag>\n );\n});\n\n/** Resolve a layout for a key at a width from the cache or provided layouts, without measuring. */\nfunction lookupLayout(\n cache: NonNullable<AutoSkeletonConfig['cache']>,\n layouts: AutoSkeletonConfig['layouts'],\n name: string | undefined,\n key: string,\n width: number,\n): SkeletonLayout | undefined {\n const cached = cache.get(key);\n if (cached) return cached;\n const provided = name === undefined ? undefined : layouts?.[name];\n if (provided && Math.abs(provided.width - width) <= 1) return provided;\n return undefined;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theengineeringmind/auto-skeleton",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Automatic skeleton loading screens measured from your real UI. React skeleton component and vanilla JS core with shimmer, SSR and Next.js support. No hand-written skeleton components.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"skeleton",
|
|
7
|
+
"skeleton-loader",
|
|
8
|
+
"skeleton-screen",
|
|
9
|
+
"skeleton-loading",
|
|
10
|
+
"loading-skeleton",
|
|
11
|
+
"react-skeleton",
|
|
12
|
+
"react-loading-skeleton",
|
|
13
|
+
"content-loader",
|
|
14
|
+
"loading",
|
|
15
|
+
"loading-placeholder",
|
|
16
|
+
"placeholder",
|
|
17
|
+
"shimmer",
|
|
18
|
+
"suspense",
|
|
19
|
+
"fallback",
|
|
20
|
+
"react",
|
|
21
|
+
"react-component",
|
|
22
|
+
"nextjs",
|
|
23
|
+
"ssr",
|
|
24
|
+
"server-components",
|
|
25
|
+
"typescript",
|
|
26
|
+
"ui",
|
|
27
|
+
"accessibility",
|
|
28
|
+
"a11y"
|
|
29
|
+
],
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Midhun",
|
|
33
|
+
"email": "midhun.2k18@gmail.com"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/theengineeringmind/auto-skeleton.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/theengineeringmind/auto-skeleton#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/theengineeringmind/auto-skeleton/issues"
|
|
42
|
+
},
|
|
43
|
+
"type": "module",
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"main": "./dist/index.cjs",
|
|
46
|
+
"module": "./dist/index.js",
|
|
47
|
+
"types": "./dist/index.d.ts",
|
|
48
|
+
"exports": {
|
|
49
|
+
".": {
|
|
50
|
+
"import": {
|
|
51
|
+
"types": "./dist/index.d.ts",
|
|
52
|
+
"default": "./dist/index.js"
|
|
53
|
+
},
|
|
54
|
+
"require": {
|
|
55
|
+
"types": "./dist/index.d.cts",
|
|
56
|
+
"default": "./dist/index.cjs"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"./react": {
|
|
60
|
+
"import": {
|
|
61
|
+
"types": "./dist/react.d.ts",
|
|
62
|
+
"default": "./dist/react.js"
|
|
63
|
+
},
|
|
64
|
+
"require": {
|
|
65
|
+
"types": "./dist/react.d.cts",
|
|
66
|
+
"default": "./dist/react.cjs"
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"./package.json": "./package.json"
|
|
70
|
+
},
|
|
71
|
+
"files": [
|
|
72
|
+
"dist",
|
|
73
|
+
"README.md",
|
|
74
|
+
"LICENSE"
|
|
75
|
+
],
|
|
76
|
+
"peerDependencies": {
|
|
77
|
+
"react": ">=18",
|
|
78
|
+
"react-dom": ">=18"
|
|
79
|
+
},
|
|
80
|
+
"peerDependenciesMeta": {
|
|
81
|
+
"react": {
|
|
82
|
+
"optional": true
|
|
83
|
+
},
|
|
84
|
+
"react-dom": {
|
|
85
|
+
"optional": true
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=18"
|
|
90
|
+
},
|
|
91
|
+
"publishConfig": {
|
|
92
|
+
"access": "public"
|
|
93
|
+
},
|
|
94
|
+
"devDependencies": {
|
|
95
|
+
"@eslint/js": "^10.0.1",
|
|
96
|
+
"@size-limit/preset-small-lib": "^13.1.1",
|
|
97
|
+
"@testing-library/dom": "^10.4.1",
|
|
98
|
+
"@testing-library/react": "^16.3.3",
|
|
99
|
+
"@types/node": "^26.5.1",
|
|
100
|
+
"@types/react": "^19.3.0",
|
|
101
|
+
"@types/react-dom": "^19.3.0",
|
|
102
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
103
|
+
"@vitest/browser": "^5.0.0",
|
|
104
|
+
"@vitest/browser-playwright": "5.0.0",
|
|
105
|
+
"eslint": "^10.10.0",
|
|
106
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
107
|
+
"jsdom": "^30.0.1",
|
|
108
|
+
"playwright": "^1.63.0",
|
|
109
|
+
"prettier": "^3.9.6",
|
|
110
|
+
"react": "^19.3.0",
|
|
111
|
+
"react-dom": "^19.3.0",
|
|
112
|
+
"size-limit": "^13.1.1",
|
|
113
|
+
"tsup": "^8.5.1",
|
|
114
|
+
"typescript": "^5.9.3",
|
|
115
|
+
"typescript-eslint": "^8.70.0",
|
|
116
|
+
"vite": "^8.3.0",
|
|
117
|
+
"vitest": "^5.0.0",
|
|
118
|
+
"vitest-browser-react": "^2.3.0"
|
|
119
|
+
},
|
|
120
|
+
"typesVersions": {
|
|
121
|
+
"*": {
|
|
122
|
+
"react": [
|
|
123
|
+
"./dist/react.d.ts"
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"scripts": {
|
|
128
|
+
"build": "tsup",
|
|
129
|
+
"dev": "tsup --watch",
|
|
130
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
131
|
+
"lint": "eslint .",
|
|
132
|
+
"format": "prettier --write .",
|
|
133
|
+
"format:check": "prettier --check .",
|
|
134
|
+
"test": "vitest run --project unit",
|
|
135
|
+
"test:browser": "vitest run --project browser",
|
|
136
|
+
"test:all": "vitest run",
|
|
137
|
+
"test:watch": "vitest",
|
|
138
|
+
"size": "size-limit",
|
|
139
|
+
"check": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm test:all && pnpm build && pnpm size",
|
|
140
|
+
"playground": "vite playground"
|
|
141
|
+
}
|
|
142
|
+
}
|