smart-skeleton-loader 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -134,11 +134,14 @@ Customize the appearance via the `config` prop:
134
134
 
135
135
  ### Config Object
136
136
 
137
- | Key | Type | Default | Description |
138
- | ---------------- | -------- | --------- | ---------------------------------------------------- |
139
- | `color` | `string` | `#eee` | Background color of primitives. |
140
- | `highlightColor` | `string` | `#f5f5f5` | Animation highlight color. |
141
- | `borderRadius` | `string` | `4px` | Fallback radius (Auto-detection is attempted first). |
137
+ | Key | Type | Default | Description |
138
+ | ------------------------------------ | ----------------------------- | --------- | ------------------------------------------------------------------------------------------------- |
139
+ | `color` | `string` | `#eee` | Background color of primitives. |
140
+ | `highlightColor` | `string` | `#f5f5f5` | Animation highlight color. |
141
+ | `borderRadius` | `string` | `4px` | Fallback radius (Auto-detection is attempted first). |
142
+ | `theme` | `'light' \| 'dark' \| 'auto'` | `'auto'` | Force a theme, or follow the OS color scheme. |
143
+ | `lightColor` / `lightHighlightColor` | `string` | - | Colors used when the (resolved) theme is light. |
144
+ | `darkColor` / `darkHighlightColor` | `string` | - | Colors used when the (resolved) theme is dark. With `'auto'` they switch live with the OS scheme. |
142
145
 
143
146
  ## 🛠 How It Works
144
147
 
package/dist/index.cjs CHANGED
@@ -27,7 +27,7 @@ __export(index_exports, {
27
27
  module.exports = __toCommonJS(index_exports);
28
28
 
29
29
  // src/AutoSkeleton.tsx
30
- var import_react2 = require("react");
30
+ var import_react3 = require("react");
31
31
 
32
32
  // src/measure.ts
33
33
  function groupRectsIntoLines(rects, tolerance = 2) {
@@ -133,6 +133,23 @@ function useDisplayedLoading(loading, { delay = 0, minDuration = 0 } = {}) {
133
133
  return shown;
134
134
  }
135
135
 
136
+ // src/usePrefersDark.ts
137
+ var import_react2 = require("react");
138
+ var QUERY = "(prefers-color-scheme: dark)";
139
+ var getMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(QUERY) : null;
140
+ var subscribe = (onChange) => {
141
+ const media = getMedia();
142
+ media?.addEventListener("change", onChange);
143
+ return () => media?.removeEventListener("change", onChange);
144
+ };
145
+ function usePrefersDark() {
146
+ return (0, import_react2.useSyncExternalStore)(
147
+ subscribe,
148
+ () => getMedia()?.matches ?? false,
149
+ () => false
150
+ );
151
+ }
152
+
136
153
  // src/AutoSkeleton.tsx
137
154
  var import_jsx_runtime2 = require("react/jsx-runtime");
138
155
  var TEXT_BAR_RATIO = 0.7;
@@ -159,25 +176,27 @@ var AutoSkeleton = ({
159
176
  config
160
177
  }) => {
161
178
  const theme = config?.theme || "auto";
179
+ const prefersDark = usePrefersDark();
180
+ const resolvedTheme = theme === "auto" ? prefersDark ? "dark" : "light" : theme;
162
181
  const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });
163
182
  const busy = loading || showSkeleton;
164
- const [revealed, setRevealed] = (0, import_react2.useState)(!busy);
183
+ const [revealed, setRevealed] = (0, import_react3.useState)(!busy);
165
184
  if (!busy && !revealed) setRevealed(true);
166
185
  const hidden = showSkeleton || loading && !revealed;
167
- const [fadingOut, setFadingOut] = (0, import_react2.useState)(false);
168
- const [prevShowSkeleton, setPrevShowSkeleton] = (0, import_react2.useState)(showSkeleton);
186
+ const [fadingOut, setFadingOut] = (0, import_react3.useState)(false);
187
+ const [prevShowSkeleton, setPrevShowSkeleton] = (0, import_react3.useState)(showSkeleton);
169
188
  if (prevShowSkeleton !== showSkeleton) {
170
189
  setPrevShowSkeleton(showSkeleton);
171
190
  setFadingOut(!showSkeleton);
172
191
  }
173
- (0, import_react2.useEffect)(() => {
192
+ (0, import_react3.useEffect)(() => {
174
193
  if (!fadingOut) return;
175
194
  const timer = setTimeout(() => setFadingOut(false), FADE_MS);
176
195
  return () => clearTimeout(timer);
177
196
  }, [fadingOut]);
178
- const containerRef = (0, import_react2.useRef)(null);
179
- const [skeletons, setSkeletons] = (0, import_react2.useState)([]);
180
- const [measured, setMeasured] = (0, import_react2.useState)(false);
197
+ const containerRef = (0, import_react3.useRef)(null);
198
+ const [skeletons, setSkeletons] = (0, import_react3.useState)([]);
199
+ const [measured, setMeasured] = (0, import_react3.useState)(false);
181
200
  const updateSkeletons = () => {
182
201
  if (!containerRef.current) return;
183
202
  const containerRect = containerRef.current.getBoundingClientRect();
@@ -279,7 +298,7 @@ var AutoSkeleton = ({
279
298
  setSkeletons(newSkeletons);
280
299
  setMeasured(true);
281
300
  };
282
- (0, import_react2.useLayoutEffect)(() => {
301
+ (0, import_react3.useLayoutEffect)(() => {
283
302
  if (!containerRef.current) return;
284
303
  updateSkeletons();
285
304
  const observer = new ResizeObserver(() => {
@@ -304,13 +323,13 @@ var AutoSkeleton = ({
304
323
  highlightColor: config.highlightColor
305
324
  };
306
325
  }
307
- if (theme === "dark" && (config?.darkColor || config?.darkHighlightColor)) {
326
+ if (resolvedTheme === "dark" && (config?.darkColor || config?.darkHighlightColor)) {
308
327
  return {
309
328
  color: config.darkColor,
310
329
  highlightColor: config.darkHighlightColor
311
330
  };
312
331
  }
313
- if (theme === "light" && (config?.lightColor || config?.lightHighlightColor)) {
332
+ if (resolvedTheme === "light" && (config?.lightColor || config?.lightHighlightColor)) {
314
333
  return {
315
334
  color: config.lightColor,
316
335
  highlightColor: config.lightHighlightColor
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/AutoSkeleton.tsx","../src/measure.ts","../src/SkeletonPrimitive.tsx","../src/useDisplayedLoading.ts"],"sourcesContent":["export * from './AutoSkeleton';\nexport * from './SkeletonPrimitive';\n","import React, {\n ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { getTextLineBoxes } from './measure';\nimport { SkeletonPrimitive } from './SkeletonPrimitive';\nimport { useDisplayedLoading } from './useDisplayedLoading';\n\n/** Text bars are drawn at this fraction of the line box height, vertically centered. */\nconst TEXT_BAR_RATIO = 0.7;\n\n/** Crossfade duration (ms) between the skeleton and the real content. */\nconst FADE_MS = 200;\n\nexport interface AutoSkeletonProps {\n loading: boolean;\n children: ReactNode;\n /** Text announced to screen readers while loading. */\n loadingLabel?: string;\n /** Class name for the outer wrapper. */\n className?: string;\n /** Style for the outer wrapper; merged over the defaults (`position: relative; display: block`). */\n style?: React.CSSProperties;\n /** Wait this long (ms) before showing the skeleton, so fast loads never flash it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms). */\n minDuration?: number;\n config?: {\n color?: string;\n highlightColor?: string;\n borderRadius?: string | number;\n theme?: 'light' | 'dark' | 'auto';\n // Theme-specific colors (override CSS variables)\n lightColor?: string;\n lightHighlightColor?: string;\n darkColor?: string;\n darkHighlightColor?: string;\n };\n}\n\nconst VISUALLY_HIDDEN: React.CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\nexport const AutoSkeleton: React.FC<AutoSkeletonProps> = ({\n loading,\n children,\n loadingLabel = 'Loading…',\n className,\n style,\n delay = 0,\n minDuration = 0,\n config,\n}) => {\n const theme = config?.theme || 'auto';\n const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });\n // Content stays hidden while data is missing or the skeleton is still held on screen\n const busy = loading || showSkeleton;\n\n // Once real content has been shown, a refetch keeps it on screen during `delay`\n // (so fast refetches change nothing visually). Before that, there is only\n // placeholder content, which must stay hidden.\n const [revealed, setRevealed] = useState(!busy);\n if (!busy && !revealed) setRevealed(true);\n const hidden = showSkeleton || (loading && !revealed);\n\n // Keep the overlay mounted while it fades out (set-state-during-render avoids a blank frame)\n const [fadingOut, setFadingOut] = useState(false);\n const [prevShowSkeleton, setPrevShowSkeleton] = useState(showSkeleton);\n if (prevShowSkeleton !== showSkeleton) {\n setPrevShowSkeleton(showSkeleton);\n setFadingOut(!showSkeleton);\n }\n useEffect(() => {\n if (!fadingOut) return;\n const timer = setTimeout(() => setFadingOut(false), FADE_MS);\n return () => clearTimeout(timer);\n }, [fadingOut]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [skeletons, setSkeletons] = useState<\n Array<{\n key: string;\n type: 'primitive' | 'container';\n width: number;\n height: number;\n top: number;\n left: number;\n borderRadius: string;\n style?: React.CSSProperties; // For container styles\n }>\n >([]);\n // False until the first client-side measurement (always false on the server)\n const [measured, setMeasured] = useState(false);\n\n const updateSkeletons = () => {\n if (!containerRef.current) return;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const newSkeletons: typeof skeletons = [];\n\n // Helper to categorize node\n const getSkeletonType = (\n el: Element,\n ): {\n type: 'primitive' | 'container';\n text?: boolean;\n styles?: React.CSSProperties;\n } | null => {\n const tagName = el.tagName.toLowerCase();\n const style = window.getComputedStyle(el);\n\n // 1. Atomic Content (always pulse)\n if (['img', 'input', 'button', 'textarea', 'select'].includes(tagName)) {\n return { type: 'primitive' };\n }\n\n // 2. Text Content (always pulse)\n const hasText = Array.from(el.childNodes).some(\n (n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim(),\n );\n if (hasText) return { type: 'primitive', text: true };\n\n // 3. Structural Container (static clone)\n // If it has visible styles but no direct text => it's a container\n const hasBackground =\n style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&\n style.backgroundColor !== 'transparent';\n const hasBorder =\n style.borderWidth !== '0px' &&\n style.borderStyle !== 'none' &&\n style.borderColor !== 'rgba(0, 0, 0, 0)' &&\n style.borderColor !== 'transparent';\n const hasShadow = style.boxShadow !== 'none';\n\n if (hasBackground || hasBorder || hasShadow) {\n return {\n type: 'container',\n styles: {\n backgroundColor: style.backgroundColor,\n border: style.border,\n borderRadius: style.borderRadius,\n boxShadow: style.boxShadow,\n },\n };\n }\n\n return null;\n };\n\n // Traverse\n const walker = document.createTreeWalker(\n containerRef.current,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n const el = node as Element;\n const style = window.getComputedStyle(el);\n if (style.display === 'none') return NodeFilter.FILTER_REJECT;\n\n // If it matches a type, accept it.\n // Note: We want to accept containers AND traverse their children.\n // FILTER_ACCEPT does both.\n if (getSkeletonType(el)) return NodeFilter.FILTER_ACCEPT;\n\n return NodeFilter.FILTER_SKIP;\n },\n },\n );\n\n let node = walker.nextNode();\n let idx = 0;\n while (node) {\n const el = node as Element;\n const rect = el.getBoundingClientRect();\n const typeData = getSkeletonType(el);\n\n // Text: one bar per rendered line instead of one block per element\n const lines = typeData?.text ? getTextLineBoxes(el) : [];\n if (lines.length > 0) {\n for (const line of lines) {\n const height = Math.max(4, line.height * TEXT_BAR_RATIO);\n newSkeletons.push({\n key: `${idx++}`,\n type: 'primitive',\n width: line.width,\n height,\n top: line.top + (line.height - height) / 2 - containerRect.top,\n left: line.left - containerRect.left,\n borderRadius:\n typeof config?.borderRadius === 'number'\n ? `${config.borderRadius}px`\n : (config?.borderRadius ?? '4px'),\n });\n }\n } else if (typeData) {\n // Determine border radius logic\n let borderRadius =\n config?.borderRadius ||\n typeData.styles?.borderRadius ||\n window.getComputedStyle(el).borderRadius;\n\n if (typeData.type === 'primitive') {\n // Auto-detect circle for rounded primitives\n if (borderRadius === '0px' || !borderRadius) {\n if (\n window.getComputedStyle(el).borderRadius === '50%' ||\n (rect.width === rect.height && rect.width < 50)\n ) {\n // Keep heuristic or default\n }\n borderRadius = '4px';\n if (el.tagName.toLowerCase() === 'button') borderRadius = '6px';\n if (el.tagName.toLowerCase() === 'img') borderRadius = '4px';\n }\n }\n\n newSkeletons.push({\n key: `${idx++}`,\n type: typeData.type,\n width: rect.width,\n height: rect.height,\n top: rect.top - containerRect.top,\n left: rect.left - containerRect.left,\n borderRadius: borderRadius as string,\n style: typeData.styles,\n });\n }\n\n node = walker.nextNode();\n }\n\n // If no content nodes found, but container has size (maybe a canvas or simple div w/ background),\n // Fallback? Or maybe user wants the single block behavior.\n // Let's add that fallback if newSkeletons is empty but container has dimension.\n if (\n newSkeletons.length === 0 &&\n (containerRect.width > 0 || containerRect.height > 0)\n ) {\n newSkeletons.push({\n key: 'fallback',\n type: 'primitive',\n width: containerRect.width,\n height: containerRect.height,\n top: 0,\n left: 0,\n borderRadius: (config?.borderRadius as string) || '4px',\n });\n }\n\n setSkeletons(newSkeletons);\n setMeasured(true);\n };\n\n // Layout effect: measure before paint so the overlay never shows a frame without bars\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n\n // Initial measure\n updateSkeletons();\n\n const observer = new ResizeObserver(() => {\n updateSkeletons();\n });\n\n observer.observe(containerRef.current);\n // Also observe children mutations if possible, but ResizeObserver on container catches layout changes often.\n // For robust child changes:\n const mutationObserver = new MutationObserver(() => updateSkeletons());\n mutationObserver.observe(containerRef.current, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n mutationObserver.disconnect();\n };\n }, [showSkeleton, children]); // Re-scan when children change\n\n // Determine effective colors based on theme\n const getEffectiveColors = () => {\n // If user passed explicit color/highlightColor, use those\n if (config?.color || config?.highlightColor) {\n return {\n color: config.color,\n highlightColor: config.highlightColor,\n };\n }\n // If theme-specific colors are provided, use them\n if (theme === 'dark' && (config?.darkColor || config?.darkHighlightColor)) {\n return {\n color: config.darkColor,\n highlightColor: config.darkHighlightColor,\n };\n }\n if (\n theme === 'light' &&\n (config?.lightColor || config?.lightHighlightColor)\n ) {\n return {\n color: config.lightColor,\n highlightColor: config.lightHighlightColor,\n };\n }\n // Otherwise, let CSS variables handle it (undefined = use CSS var defaults)\n return { color: undefined, highlightColor: undefined };\n };\n\n const effectiveColors = getEffectiveColors();\n\n return (\n <div\n className={className}\n style={{ position: 'relative', display: 'block', ...style }}\n {...(theme !== 'auto' ? { 'data-skeleton-theme': theme } : {})}\n aria-busy={busy}\n >\n {/* <output> is not reliably announced as a live region; role=\"status\" is. */}\n {/* oxlint-disable-next-line jsx-a11y/prefer-tag-over-role */}\n <span role=\"status\" style={VISUALLY_HIDDEN}>\n {busy ? loadingLabel : ''}\n </span>\n <div\n ref={containerRef}\n style={{\n opacity: hidden ? 0 : 1,\n // Hide instantly (no ghost content under the bars); fade only on reveal\n transition: hidden ? 'none' : `opacity ${FADE_MS}ms ease`,\n }}\n aria-hidden={hidden}\n inert={busy}\n >\n {children}\n </div>\n\n {(showSkeleton || fadingOut) && (\n <div\n data-testid=\"skeleton-overlay\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n opacity: showSkeleton ? 1 : 0,\n transition: `opacity ${FADE_MS}ms ease`,\n }}\n >\n {!measured && (\n // Server render / before hydration: nothing can be measured yet, so\n // cover the reserved space with one block instead of showing nothing\n <SkeletonPrimitive\n width=\"100%\"\n height=\"100%\"\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n )}\n {skeletons.map((sk) => (\n <div\n key={sk.key}\n style={{ position: 'absolute', top: sk.top, left: sk.left }}\n >\n {sk.type === 'primitive' ? (\n <SkeletonPrimitive\n width={sk.width}\n height={sk.height}\n borderRadius={sk.borderRadius}\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n ) : (\n /* Container Clone: Renders static style */\n <div\n style={{\n width: sk.width,\n height: sk.height,\n borderRadius: sk.borderRadius,\n ...sk.style, // Apply captured container styles\n // Ensure it doesn't hide children if z-index issues arise,\n // but since we render in DOM order, children come later and sit on top.\n boxSizing: 'border-box',\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n","export interface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/**\n * Merges rects that sit on the same visual line (tops within `tolerance` px)\n * into one box per line, ordered top to bottom.\n */\nexport function groupRectsIntoLines(\n rects: ArrayLike<Box>,\n tolerance = 2,\n): Box[] {\n const sorted = Array.from(rects)\n .filter((r) => r.width > 0 && r.height > 0)\n .sort((a, b) => a.top - b.top || a.left - b.left);\n\n const lines: Array<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n }> = [];\n for (const r of sorted) {\n const line = lines[lines.length - 1];\n if (line && Math.abs(r.top - line.top) <= tolerance) {\n line.left = Math.min(line.left, r.left);\n line.right = Math.max(line.right, r.left + r.width);\n line.bottom = Math.max(line.bottom, r.top + r.height);\n } else {\n lines.push({\n top: r.top,\n left: r.left,\n right: r.left + r.width,\n bottom: r.top + r.height,\n });\n }\n }\n\n return lines.map((l) => ({\n top: l.top,\n left: l.left,\n width: l.right - l.left,\n height: l.bottom - l.top,\n }));\n}\n\n/**\n * Line boxes of an element's own (direct) text nodes, in viewport coordinates.\n * Returns [] when the environment can't measure text ranges (e.g. jsdom).\n */\nexport function getTextLineBoxes(el: Element): Box[] {\n if (typeof document.createRange !== 'function') return [];\n\n const rects: Box[] = [];\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim()) continue;\n const range = document.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects !== 'function') return [];\n rects.push(...Array.from(range.getClientRects()));\n }\n return groupRectsIntoLines(rects);\n}\n","import React from 'react';\n\nexport interface SkeletonPrimitiveProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string | number;\n height?: string | number;\n borderRadius?: string | number;\n color?: string;\n highlightColor?: string;\n}\n\nconst PULSE_CSS = `\n@keyframes auto-skeleton-pulse {\n 0%, 100% { background-color: var(--auto-skeleton-c); }\n 50% { background-color: var(--auto-skeleton-h); }\n}\n.auto-skeleton { animation: auto-skeleton-pulse 1.5s ease-in-out infinite; }\n@media (prefers-reduced-motion: reduce) {\n .auto-skeleton { animation: none; }\n}\n`;\n\nexport const SkeletonPrimitive: React.FC<SkeletonPrimitiveProps> = ({\n width,\n height,\n borderRadius = '4px',\n color = 'var(--auto-skeleton-color, #eee)',\n highlightColor = 'var(--auto-skeleton-highlight, #f5f5f5)',\n className,\n style,\n ...props\n}) => {\n return (\n <>\n {/* React 19 hoists this into <head> and dedupes it by href */}\n <style href=\"auto-skeleton-pulse\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n <div\n className={className ? `auto-skeleton ${className}` : 'auto-skeleton'}\n style={\n {\n width,\n height,\n borderRadius,\n backgroundColor: color,\n '--auto-skeleton-c': color,\n '--auto-skeleton-h': highlightColor,\n ...style,\n } as React.CSSProperties\n }\n data-testid=\"skeleton\"\n {...props}\n />\n </>\n );\n};\n","import { useLayoutEffect, useRef, useState } from 'react';\n\nexport interface DisplayedLoadingOptions {\n /** Wait this long (ms) before showing the skeleton; fast loads never show it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms) to avoid a flash. */\n minDuration?: number;\n}\n\n/**\n * Turns a raw `loading` flag into \"should the skeleton be on screen\",\n * applying a show delay and a minimum display time.\n */\nexport function useDisplayedLoading(\n loading: boolean,\n { delay = 0, minDuration = 0 }: DisplayedLoadingOptions = {},\n): boolean {\n const [shown, setShown] = useState(() => loading && delay <= 0);\n const shownAt = useRef(0);\n\n useLayoutEffect(() => {\n if (shown) shownAt.current = Date.now();\n }, [shown]);\n\n useLayoutEffect(() => {\n if (loading === shown) return;\n\n const wait = loading\n ? delay\n : Math.max(0, minDuration - (Date.now() - shownAt.current));\n\n if (wait <= 0) {\n setShown(loading);\n return;\n }\n const timer = setTimeout(() => setShown(loading), wait);\n return () => clearTimeout(timer);\n }, [loading, shown, delay, minDuration]);\n\n return shown;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime","style","node"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/AutoSkeleton.tsx","../src/measure.ts","../src/SkeletonPrimitive.tsx","../src/useDisplayedLoading.ts","../src/usePrefersDark.ts"],"sourcesContent":["export * from './AutoSkeleton';\nexport * from './SkeletonPrimitive';\n","import React, {\n ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { getTextLineBoxes } from './measure';\nimport { SkeletonPrimitive } from './SkeletonPrimitive';\nimport { useDisplayedLoading } from './useDisplayedLoading';\nimport { usePrefersDark } from './usePrefersDark';\n\n/** Text bars are drawn at this fraction of the line box height, vertically centered. */\nconst TEXT_BAR_RATIO = 0.7;\n\n/** Crossfade duration (ms) between the skeleton and the real content. */\nconst FADE_MS = 200;\n\nexport interface AutoSkeletonProps {\n loading: boolean;\n children: ReactNode;\n /** Text announced to screen readers while loading. */\n loadingLabel?: string;\n /** Class name for the outer wrapper. */\n className?: string;\n /** Style for the outer wrapper; merged over the defaults (`position: relative; display: block`). */\n style?: React.CSSProperties;\n /** Wait this long (ms) before showing the skeleton, so fast loads never flash it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms). */\n minDuration?: number;\n config?: {\n color?: string;\n highlightColor?: string;\n borderRadius?: string | number;\n theme?: 'light' | 'dark' | 'auto';\n // Theme-specific colors (override CSS variables)\n lightColor?: string;\n lightHighlightColor?: string;\n darkColor?: string;\n darkHighlightColor?: string;\n };\n}\n\nconst VISUALLY_HIDDEN: React.CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\nexport const AutoSkeleton: React.FC<AutoSkeletonProps> = ({\n loading,\n children,\n loadingLabel = 'Loading…',\n className,\n style,\n delay = 0,\n minDuration = 0,\n config,\n}) => {\n const theme = config?.theme || 'auto';\n const prefersDark = usePrefersDark();\n // 'auto' follows the OS color scheme so light*/dark* colors apply there too\n const resolvedTheme =\n theme === 'auto' ? (prefersDark ? 'dark' : 'light') : theme;\n const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });\n // Content stays hidden while data is missing or the skeleton is still held on screen\n const busy = loading || showSkeleton;\n\n // Once real content has been shown, a refetch keeps it on screen during `delay`\n // (so fast refetches change nothing visually). Before that, there is only\n // placeholder content, which must stay hidden.\n const [revealed, setRevealed] = useState(!busy);\n if (!busy && !revealed) setRevealed(true);\n const hidden = showSkeleton || (loading && !revealed);\n\n // Keep the overlay mounted while it fades out (set-state-during-render avoids a blank frame)\n const [fadingOut, setFadingOut] = useState(false);\n const [prevShowSkeleton, setPrevShowSkeleton] = useState(showSkeleton);\n if (prevShowSkeleton !== showSkeleton) {\n setPrevShowSkeleton(showSkeleton);\n setFadingOut(!showSkeleton);\n }\n useEffect(() => {\n if (!fadingOut) return;\n const timer = setTimeout(() => setFadingOut(false), FADE_MS);\n return () => clearTimeout(timer);\n }, [fadingOut]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [skeletons, setSkeletons] = useState<\n Array<{\n key: string;\n type: 'primitive' | 'container';\n width: number;\n height: number;\n top: number;\n left: number;\n borderRadius: string;\n style?: React.CSSProperties; // For container styles\n }>\n >([]);\n // False until the first client-side measurement (always false on the server)\n const [measured, setMeasured] = useState(false);\n\n const updateSkeletons = () => {\n if (!containerRef.current) return;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const newSkeletons: typeof skeletons = [];\n\n // Helper to categorize node\n const getSkeletonType = (\n el: Element,\n ): {\n type: 'primitive' | 'container';\n text?: boolean;\n styles?: React.CSSProperties;\n } | null => {\n const tagName = el.tagName.toLowerCase();\n const style = window.getComputedStyle(el);\n\n // 1. Atomic Content (always pulse)\n if (['img', 'input', 'button', 'textarea', 'select'].includes(tagName)) {\n return { type: 'primitive' };\n }\n\n // 2. Text Content (always pulse)\n const hasText = Array.from(el.childNodes).some(\n (n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim(),\n );\n if (hasText) return { type: 'primitive', text: true };\n\n // 3. Structural Container (static clone)\n // If it has visible styles but no direct text => it's a container\n const hasBackground =\n style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&\n style.backgroundColor !== 'transparent';\n const hasBorder =\n style.borderWidth !== '0px' &&\n style.borderStyle !== 'none' &&\n style.borderColor !== 'rgba(0, 0, 0, 0)' &&\n style.borderColor !== 'transparent';\n const hasShadow = style.boxShadow !== 'none';\n\n if (hasBackground || hasBorder || hasShadow) {\n return {\n type: 'container',\n styles: {\n backgroundColor: style.backgroundColor,\n border: style.border,\n borderRadius: style.borderRadius,\n boxShadow: style.boxShadow,\n },\n };\n }\n\n return null;\n };\n\n // Traverse\n const walker = document.createTreeWalker(\n containerRef.current,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n const el = node as Element;\n const style = window.getComputedStyle(el);\n if (style.display === 'none') return NodeFilter.FILTER_REJECT;\n\n // If it matches a type, accept it.\n // Note: We want to accept containers AND traverse their children.\n // FILTER_ACCEPT does both.\n if (getSkeletonType(el)) return NodeFilter.FILTER_ACCEPT;\n\n return NodeFilter.FILTER_SKIP;\n },\n },\n );\n\n let node = walker.nextNode();\n let idx = 0;\n while (node) {\n const el = node as Element;\n const rect = el.getBoundingClientRect();\n const typeData = getSkeletonType(el);\n\n // Text: one bar per rendered line instead of one block per element\n const lines = typeData?.text ? getTextLineBoxes(el) : [];\n if (lines.length > 0) {\n for (const line of lines) {\n const height = Math.max(4, line.height * TEXT_BAR_RATIO);\n newSkeletons.push({\n key: `${idx++}`,\n type: 'primitive',\n width: line.width,\n height,\n top: line.top + (line.height - height) / 2 - containerRect.top,\n left: line.left - containerRect.left,\n borderRadius:\n typeof config?.borderRadius === 'number'\n ? `${config.borderRadius}px`\n : (config?.borderRadius ?? '4px'),\n });\n }\n } else if (typeData) {\n // Determine border radius logic\n let borderRadius =\n config?.borderRadius ||\n typeData.styles?.borderRadius ||\n window.getComputedStyle(el).borderRadius;\n\n if (typeData.type === 'primitive') {\n // Auto-detect circle for rounded primitives\n if (borderRadius === '0px' || !borderRadius) {\n if (\n window.getComputedStyle(el).borderRadius === '50%' ||\n (rect.width === rect.height && rect.width < 50)\n ) {\n // Keep heuristic or default\n }\n borderRadius = '4px';\n if (el.tagName.toLowerCase() === 'button') borderRadius = '6px';\n if (el.tagName.toLowerCase() === 'img') borderRadius = '4px';\n }\n }\n\n newSkeletons.push({\n key: `${idx++}`,\n type: typeData.type,\n width: rect.width,\n height: rect.height,\n top: rect.top - containerRect.top,\n left: rect.left - containerRect.left,\n borderRadius: borderRadius as string,\n style: typeData.styles,\n });\n }\n\n node = walker.nextNode();\n }\n\n // If no content nodes found, but container has size (maybe a canvas or simple div w/ background),\n // Fallback? Or maybe user wants the single block behavior.\n // Let's add that fallback if newSkeletons is empty but container has dimension.\n if (\n newSkeletons.length === 0 &&\n (containerRect.width > 0 || containerRect.height > 0)\n ) {\n newSkeletons.push({\n key: 'fallback',\n type: 'primitive',\n width: containerRect.width,\n height: containerRect.height,\n top: 0,\n left: 0,\n borderRadius: (config?.borderRadius as string) || '4px',\n });\n }\n\n setSkeletons(newSkeletons);\n setMeasured(true);\n };\n\n // Layout effect: measure before paint so the overlay never shows a frame without bars\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n\n // Initial measure\n updateSkeletons();\n\n const observer = new ResizeObserver(() => {\n updateSkeletons();\n });\n\n observer.observe(containerRef.current);\n // Also observe children mutations if possible, but ResizeObserver on container catches layout changes often.\n // For robust child changes:\n const mutationObserver = new MutationObserver(() => updateSkeletons());\n mutationObserver.observe(containerRef.current, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n mutationObserver.disconnect();\n };\n }, [showSkeleton, children]); // Re-scan when children change\n\n // Determine effective colors based on theme\n const getEffectiveColors = () => {\n // If user passed explicit color/highlightColor, use those\n if (config?.color || config?.highlightColor) {\n return {\n color: config.color,\n highlightColor: config.highlightColor,\n };\n }\n // If theme-specific colors are provided, use them\n if (\n resolvedTheme === 'dark' &&\n (config?.darkColor || config?.darkHighlightColor)\n ) {\n return {\n color: config.darkColor,\n highlightColor: config.darkHighlightColor,\n };\n }\n if (\n resolvedTheme === 'light' &&\n (config?.lightColor || config?.lightHighlightColor)\n ) {\n return {\n color: config.lightColor,\n highlightColor: config.lightHighlightColor,\n };\n }\n // Otherwise, let CSS variables handle it (undefined = use CSS var defaults)\n return { color: undefined, highlightColor: undefined };\n };\n\n const effectiveColors = getEffectiveColors();\n\n return (\n <div\n className={className}\n style={{ position: 'relative', display: 'block', ...style }}\n {...(theme !== 'auto' ? { 'data-skeleton-theme': theme } : {})}\n aria-busy={busy}\n >\n {/* <output> is not reliably announced as a live region; role=\"status\" is. */}\n {/* oxlint-disable-next-line jsx-a11y/prefer-tag-over-role */}\n <span role=\"status\" style={VISUALLY_HIDDEN}>\n {busy ? loadingLabel : ''}\n </span>\n <div\n ref={containerRef}\n style={{\n opacity: hidden ? 0 : 1,\n // Hide instantly (no ghost content under the bars); fade only on reveal\n transition: hidden ? 'none' : `opacity ${FADE_MS}ms ease`,\n }}\n aria-hidden={hidden}\n inert={busy}\n >\n {children}\n </div>\n\n {(showSkeleton || fadingOut) && (\n <div\n data-testid=\"skeleton-overlay\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n opacity: showSkeleton ? 1 : 0,\n transition: `opacity ${FADE_MS}ms ease`,\n }}\n >\n {!measured && (\n // Server render / before hydration: nothing can be measured yet, so\n // cover the reserved space with one block instead of showing nothing\n <SkeletonPrimitive\n width=\"100%\"\n height=\"100%\"\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n )}\n {skeletons.map((sk) => (\n <div\n key={sk.key}\n style={{ position: 'absolute', top: sk.top, left: sk.left }}\n >\n {sk.type === 'primitive' ? (\n <SkeletonPrimitive\n width={sk.width}\n height={sk.height}\n borderRadius={sk.borderRadius}\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n ) : (\n /* Container Clone: Renders static style */\n <div\n style={{\n width: sk.width,\n height: sk.height,\n borderRadius: sk.borderRadius,\n ...sk.style, // Apply captured container styles\n // Ensure it doesn't hide children if z-index issues arise,\n // but since we render in DOM order, children come later and sit on top.\n boxSizing: 'border-box',\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n","export interface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/**\n * Merges rects that sit on the same visual line (tops within `tolerance` px)\n * into one box per line, ordered top to bottom.\n */\nexport function groupRectsIntoLines(\n rects: ArrayLike<Box>,\n tolerance = 2,\n): Box[] {\n const sorted = Array.from(rects)\n .filter((r) => r.width > 0 && r.height > 0)\n .sort((a, b) => a.top - b.top || a.left - b.left);\n\n const lines: Array<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n }> = [];\n for (const r of sorted) {\n const line = lines[lines.length - 1];\n if (line && Math.abs(r.top - line.top) <= tolerance) {\n line.left = Math.min(line.left, r.left);\n line.right = Math.max(line.right, r.left + r.width);\n line.bottom = Math.max(line.bottom, r.top + r.height);\n } else {\n lines.push({\n top: r.top,\n left: r.left,\n right: r.left + r.width,\n bottom: r.top + r.height,\n });\n }\n }\n\n return lines.map((l) => ({\n top: l.top,\n left: l.left,\n width: l.right - l.left,\n height: l.bottom - l.top,\n }));\n}\n\n/**\n * Line boxes of an element's own (direct) text nodes, in viewport coordinates.\n * Returns [] when the environment can't measure text ranges (e.g. jsdom).\n */\nexport function getTextLineBoxes(el: Element): Box[] {\n if (typeof document.createRange !== 'function') return [];\n\n const rects: Box[] = [];\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim()) continue;\n const range = document.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects !== 'function') return [];\n rects.push(...Array.from(range.getClientRects()));\n }\n return groupRectsIntoLines(rects);\n}\n","import React from 'react';\n\nexport interface SkeletonPrimitiveProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string | number;\n height?: string | number;\n borderRadius?: string | number;\n color?: string;\n highlightColor?: string;\n}\n\nconst PULSE_CSS = `\n@keyframes auto-skeleton-pulse {\n 0%, 100% { background-color: var(--auto-skeleton-c); }\n 50% { background-color: var(--auto-skeleton-h); }\n}\n.auto-skeleton { animation: auto-skeleton-pulse 1.5s ease-in-out infinite; }\n@media (prefers-reduced-motion: reduce) {\n .auto-skeleton { animation: none; }\n}\n`;\n\nexport const SkeletonPrimitive: React.FC<SkeletonPrimitiveProps> = ({\n width,\n height,\n borderRadius = '4px',\n color = 'var(--auto-skeleton-color, #eee)',\n highlightColor = 'var(--auto-skeleton-highlight, #f5f5f5)',\n className,\n style,\n ...props\n}) => {\n return (\n <>\n {/* React 19 hoists this into <head> and dedupes it by href */}\n <style href=\"auto-skeleton-pulse\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n <div\n className={className ? `auto-skeleton ${className}` : 'auto-skeleton'}\n style={\n {\n width,\n height,\n borderRadius,\n backgroundColor: color,\n '--auto-skeleton-c': color,\n '--auto-skeleton-h': highlightColor,\n ...style,\n } as React.CSSProperties\n }\n data-testid=\"skeleton\"\n {...props}\n />\n </>\n );\n};\n","import { useLayoutEffect, useRef, useState } from 'react';\n\nexport interface DisplayedLoadingOptions {\n /** Wait this long (ms) before showing the skeleton; fast loads never show it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms) to avoid a flash. */\n minDuration?: number;\n}\n\n/**\n * Turns a raw `loading` flag into \"should the skeleton be on screen\",\n * applying a show delay and a minimum display time.\n */\nexport function useDisplayedLoading(\n loading: boolean,\n { delay = 0, minDuration = 0 }: DisplayedLoadingOptions = {},\n): boolean {\n const [shown, setShown] = useState(() => loading && delay <= 0);\n const shownAt = useRef(0);\n\n useLayoutEffect(() => {\n if (shown) shownAt.current = Date.now();\n }, [shown]);\n\n useLayoutEffect(() => {\n if (loading === shown) return;\n\n const wait = loading\n ? delay\n : Math.max(0, minDuration - (Date.now() - shownAt.current));\n\n if (wait <= 0) {\n setShown(loading);\n return;\n }\n const timer = setTimeout(() => setShown(loading), wait);\n return () => clearTimeout(timer);\n }, [loading, shown, delay, minDuration]);\n\n return shown;\n}\n","import { useSyncExternalStore } from 'react';\n\nconst QUERY = '(prefers-color-scheme: dark)';\n\nconst getMedia = () =>\n typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia(QUERY)\n : null;\n\nconst subscribe = (onChange: () => void) => {\n const media = getMedia();\n media?.addEventListener('change', onChange);\n return () => media?.removeEventListener('change', onChange);\n};\n\n/** Live `prefers-color-scheme: dark` match; `false` on the server. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(\n subscribe,\n () => getMedia()?.matches ?? false,\n () => false,\n );\n}\n"],"mappings":"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","names":["import_react","import_react","import_jsx_runtime","style","node"]}
package/dist/index.js CHANGED
@@ -112,6 +112,23 @@ function useDisplayedLoading(loading, { delay = 0, minDuration = 0 } = {}) {
112
112
  return shown;
113
113
  }
114
114
 
115
+ // src/usePrefersDark.ts
116
+ import { useSyncExternalStore } from "react";
117
+ var QUERY = "(prefers-color-scheme: dark)";
118
+ var getMedia = () => typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia(QUERY) : null;
119
+ var subscribe = (onChange) => {
120
+ const media = getMedia();
121
+ media?.addEventListener("change", onChange);
122
+ return () => media?.removeEventListener("change", onChange);
123
+ };
124
+ function usePrefersDark() {
125
+ return useSyncExternalStore(
126
+ subscribe,
127
+ () => getMedia()?.matches ?? false,
128
+ () => false
129
+ );
130
+ }
131
+
115
132
  // src/AutoSkeleton.tsx
116
133
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
117
134
  var TEXT_BAR_RATIO = 0.7;
@@ -138,6 +155,8 @@ var AutoSkeleton = ({
138
155
  config
139
156
  }) => {
140
157
  const theme = config?.theme || "auto";
158
+ const prefersDark = usePrefersDark();
159
+ const resolvedTheme = theme === "auto" ? prefersDark ? "dark" : "light" : theme;
141
160
  const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });
142
161
  const busy = loading || showSkeleton;
143
162
  const [revealed, setRevealed] = useState2(!busy);
@@ -283,13 +302,13 @@ var AutoSkeleton = ({
283
302
  highlightColor: config.highlightColor
284
303
  };
285
304
  }
286
- if (theme === "dark" && (config?.darkColor || config?.darkHighlightColor)) {
305
+ if (resolvedTheme === "dark" && (config?.darkColor || config?.darkHighlightColor)) {
287
306
  return {
288
307
  color: config.darkColor,
289
308
  highlightColor: config.darkHighlightColor
290
309
  };
291
310
  }
292
- if (theme === "light" && (config?.lightColor || config?.lightHighlightColor)) {
311
+ if (resolvedTheme === "light" && (config?.lightColor || config?.lightHighlightColor)) {
293
312
  return {
294
313
  color: config.lightColor,
295
314
  highlightColor: config.lightHighlightColor
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/AutoSkeleton.tsx","../src/measure.ts","../src/SkeletonPrimitive.tsx","../src/useDisplayedLoading.ts"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { getTextLineBoxes } from './measure';\nimport { SkeletonPrimitive } from './SkeletonPrimitive';\nimport { useDisplayedLoading } from './useDisplayedLoading';\n\n/** Text bars are drawn at this fraction of the line box height, vertically centered. */\nconst TEXT_BAR_RATIO = 0.7;\n\n/** Crossfade duration (ms) between the skeleton and the real content. */\nconst FADE_MS = 200;\n\nexport interface AutoSkeletonProps {\n loading: boolean;\n children: ReactNode;\n /** Text announced to screen readers while loading. */\n loadingLabel?: string;\n /** Class name for the outer wrapper. */\n className?: string;\n /** Style for the outer wrapper; merged over the defaults (`position: relative; display: block`). */\n style?: React.CSSProperties;\n /** Wait this long (ms) before showing the skeleton, so fast loads never flash it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms). */\n minDuration?: number;\n config?: {\n color?: string;\n highlightColor?: string;\n borderRadius?: string | number;\n theme?: 'light' | 'dark' | 'auto';\n // Theme-specific colors (override CSS variables)\n lightColor?: string;\n lightHighlightColor?: string;\n darkColor?: string;\n darkHighlightColor?: string;\n };\n}\n\nconst VISUALLY_HIDDEN: React.CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\nexport const AutoSkeleton: React.FC<AutoSkeletonProps> = ({\n loading,\n children,\n loadingLabel = 'Loading…',\n className,\n style,\n delay = 0,\n minDuration = 0,\n config,\n}) => {\n const theme = config?.theme || 'auto';\n const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });\n // Content stays hidden while data is missing or the skeleton is still held on screen\n const busy = loading || showSkeleton;\n\n // Once real content has been shown, a refetch keeps it on screen during `delay`\n // (so fast refetches change nothing visually). Before that, there is only\n // placeholder content, which must stay hidden.\n const [revealed, setRevealed] = useState(!busy);\n if (!busy && !revealed) setRevealed(true);\n const hidden = showSkeleton || (loading && !revealed);\n\n // Keep the overlay mounted while it fades out (set-state-during-render avoids a blank frame)\n const [fadingOut, setFadingOut] = useState(false);\n const [prevShowSkeleton, setPrevShowSkeleton] = useState(showSkeleton);\n if (prevShowSkeleton !== showSkeleton) {\n setPrevShowSkeleton(showSkeleton);\n setFadingOut(!showSkeleton);\n }\n useEffect(() => {\n if (!fadingOut) return;\n const timer = setTimeout(() => setFadingOut(false), FADE_MS);\n return () => clearTimeout(timer);\n }, [fadingOut]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [skeletons, setSkeletons] = useState<\n Array<{\n key: string;\n type: 'primitive' | 'container';\n width: number;\n height: number;\n top: number;\n left: number;\n borderRadius: string;\n style?: React.CSSProperties; // For container styles\n }>\n >([]);\n // False until the first client-side measurement (always false on the server)\n const [measured, setMeasured] = useState(false);\n\n const updateSkeletons = () => {\n if (!containerRef.current) return;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const newSkeletons: typeof skeletons = [];\n\n // Helper to categorize node\n const getSkeletonType = (\n el: Element,\n ): {\n type: 'primitive' | 'container';\n text?: boolean;\n styles?: React.CSSProperties;\n } | null => {\n const tagName = el.tagName.toLowerCase();\n const style = window.getComputedStyle(el);\n\n // 1. Atomic Content (always pulse)\n if (['img', 'input', 'button', 'textarea', 'select'].includes(tagName)) {\n return { type: 'primitive' };\n }\n\n // 2. Text Content (always pulse)\n const hasText = Array.from(el.childNodes).some(\n (n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim(),\n );\n if (hasText) return { type: 'primitive', text: true };\n\n // 3. Structural Container (static clone)\n // If it has visible styles but no direct text => it's a container\n const hasBackground =\n style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&\n style.backgroundColor !== 'transparent';\n const hasBorder =\n style.borderWidth !== '0px' &&\n style.borderStyle !== 'none' &&\n style.borderColor !== 'rgba(0, 0, 0, 0)' &&\n style.borderColor !== 'transparent';\n const hasShadow = style.boxShadow !== 'none';\n\n if (hasBackground || hasBorder || hasShadow) {\n return {\n type: 'container',\n styles: {\n backgroundColor: style.backgroundColor,\n border: style.border,\n borderRadius: style.borderRadius,\n boxShadow: style.boxShadow,\n },\n };\n }\n\n return null;\n };\n\n // Traverse\n const walker = document.createTreeWalker(\n containerRef.current,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n const el = node as Element;\n const style = window.getComputedStyle(el);\n if (style.display === 'none') return NodeFilter.FILTER_REJECT;\n\n // If it matches a type, accept it.\n // Note: We want to accept containers AND traverse their children.\n // FILTER_ACCEPT does both.\n if (getSkeletonType(el)) return NodeFilter.FILTER_ACCEPT;\n\n return NodeFilter.FILTER_SKIP;\n },\n },\n );\n\n let node = walker.nextNode();\n let idx = 0;\n while (node) {\n const el = node as Element;\n const rect = el.getBoundingClientRect();\n const typeData = getSkeletonType(el);\n\n // Text: one bar per rendered line instead of one block per element\n const lines = typeData?.text ? getTextLineBoxes(el) : [];\n if (lines.length > 0) {\n for (const line of lines) {\n const height = Math.max(4, line.height * TEXT_BAR_RATIO);\n newSkeletons.push({\n key: `${idx++}`,\n type: 'primitive',\n width: line.width,\n height,\n top: line.top + (line.height - height) / 2 - containerRect.top,\n left: line.left - containerRect.left,\n borderRadius:\n typeof config?.borderRadius === 'number'\n ? `${config.borderRadius}px`\n : (config?.borderRadius ?? '4px'),\n });\n }\n } else if (typeData) {\n // Determine border radius logic\n let borderRadius =\n config?.borderRadius ||\n typeData.styles?.borderRadius ||\n window.getComputedStyle(el).borderRadius;\n\n if (typeData.type === 'primitive') {\n // Auto-detect circle for rounded primitives\n if (borderRadius === '0px' || !borderRadius) {\n if (\n window.getComputedStyle(el).borderRadius === '50%' ||\n (rect.width === rect.height && rect.width < 50)\n ) {\n // Keep heuristic or default\n }\n borderRadius = '4px';\n if (el.tagName.toLowerCase() === 'button') borderRadius = '6px';\n if (el.tagName.toLowerCase() === 'img') borderRadius = '4px';\n }\n }\n\n newSkeletons.push({\n key: `${idx++}`,\n type: typeData.type,\n width: rect.width,\n height: rect.height,\n top: rect.top - containerRect.top,\n left: rect.left - containerRect.left,\n borderRadius: borderRadius as string,\n style: typeData.styles,\n });\n }\n\n node = walker.nextNode();\n }\n\n // If no content nodes found, but container has size (maybe a canvas or simple div w/ background),\n // Fallback? Or maybe user wants the single block behavior.\n // Let's add that fallback if newSkeletons is empty but container has dimension.\n if (\n newSkeletons.length === 0 &&\n (containerRect.width > 0 || containerRect.height > 0)\n ) {\n newSkeletons.push({\n key: 'fallback',\n type: 'primitive',\n width: containerRect.width,\n height: containerRect.height,\n top: 0,\n left: 0,\n borderRadius: (config?.borderRadius as string) || '4px',\n });\n }\n\n setSkeletons(newSkeletons);\n setMeasured(true);\n };\n\n // Layout effect: measure before paint so the overlay never shows a frame without bars\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n\n // Initial measure\n updateSkeletons();\n\n const observer = new ResizeObserver(() => {\n updateSkeletons();\n });\n\n observer.observe(containerRef.current);\n // Also observe children mutations if possible, but ResizeObserver on container catches layout changes often.\n // For robust child changes:\n const mutationObserver = new MutationObserver(() => updateSkeletons());\n mutationObserver.observe(containerRef.current, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n mutationObserver.disconnect();\n };\n }, [showSkeleton, children]); // Re-scan when children change\n\n // Determine effective colors based on theme\n const getEffectiveColors = () => {\n // If user passed explicit color/highlightColor, use those\n if (config?.color || config?.highlightColor) {\n return {\n color: config.color,\n highlightColor: config.highlightColor,\n };\n }\n // If theme-specific colors are provided, use them\n if (theme === 'dark' && (config?.darkColor || config?.darkHighlightColor)) {\n return {\n color: config.darkColor,\n highlightColor: config.darkHighlightColor,\n };\n }\n if (\n theme === 'light' &&\n (config?.lightColor || config?.lightHighlightColor)\n ) {\n return {\n color: config.lightColor,\n highlightColor: config.lightHighlightColor,\n };\n }\n // Otherwise, let CSS variables handle it (undefined = use CSS var defaults)\n return { color: undefined, highlightColor: undefined };\n };\n\n const effectiveColors = getEffectiveColors();\n\n return (\n <div\n className={className}\n style={{ position: 'relative', display: 'block', ...style }}\n {...(theme !== 'auto' ? { 'data-skeleton-theme': theme } : {})}\n aria-busy={busy}\n >\n {/* <output> is not reliably announced as a live region; role=\"status\" is. */}\n {/* oxlint-disable-next-line jsx-a11y/prefer-tag-over-role */}\n <span role=\"status\" style={VISUALLY_HIDDEN}>\n {busy ? loadingLabel : ''}\n </span>\n <div\n ref={containerRef}\n style={{\n opacity: hidden ? 0 : 1,\n // Hide instantly (no ghost content under the bars); fade only on reveal\n transition: hidden ? 'none' : `opacity ${FADE_MS}ms ease`,\n }}\n aria-hidden={hidden}\n inert={busy}\n >\n {children}\n </div>\n\n {(showSkeleton || fadingOut) && (\n <div\n data-testid=\"skeleton-overlay\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n opacity: showSkeleton ? 1 : 0,\n transition: `opacity ${FADE_MS}ms ease`,\n }}\n >\n {!measured && (\n // Server render / before hydration: nothing can be measured yet, so\n // cover the reserved space with one block instead of showing nothing\n <SkeletonPrimitive\n width=\"100%\"\n height=\"100%\"\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n )}\n {skeletons.map((sk) => (\n <div\n key={sk.key}\n style={{ position: 'absolute', top: sk.top, left: sk.left }}\n >\n {sk.type === 'primitive' ? (\n <SkeletonPrimitive\n width={sk.width}\n height={sk.height}\n borderRadius={sk.borderRadius}\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n ) : (\n /* Container Clone: Renders static style */\n <div\n style={{\n width: sk.width,\n height: sk.height,\n borderRadius: sk.borderRadius,\n ...sk.style, // Apply captured container styles\n // Ensure it doesn't hide children if z-index issues arise,\n // but since we render in DOM order, children come later and sit on top.\n boxSizing: 'border-box',\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n","export interface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/**\n * Merges rects that sit on the same visual line (tops within `tolerance` px)\n * into one box per line, ordered top to bottom.\n */\nexport function groupRectsIntoLines(\n rects: ArrayLike<Box>,\n tolerance = 2,\n): Box[] {\n const sorted = Array.from(rects)\n .filter((r) => r.width > 0 && r.height > 0)\n .sort((a, b) => a.top - b.top || a.left - b.left);\n\n const lines: Array<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n }> = [];\n for (const r of sorted) {\n const line = lines[lines.length - 1];\n if (line && Math.abs(r.top - line.top) <= tolerance) {\n line.left = Math.min(line.left, r.left);\n line.right = Math.max(line.right, r.left + r.width);\n line.bottom = Math.max(line.bottom, r.top + r.height);\n } else {\n lines.push({\n top: r.top,\n left: r.left,\n right: r.left + r.width,\n bottom: r.top + r.height,\n });\n }\n }\n\n return lines.map((l) => ({\n top: l.top,\n left: l.left,\n width: l.right - l.left,\n height: l.bottom - l.top,\n }));\n}\n\n/**\n * Line boxes of an element's own (direct) text nodes, in viewport coordinates.\n * Returns [] when the environment can't measure text ranges (e.g. jsdom).\n */\nexport function getTextLineBoxes(el: Element): Box[] {\n if (typeof document.createRange !== 'function') return [];\n\n const rects: Box[] = [];\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim()) continue;\n const range = document.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects !== 'function') return [];\n rects.push(...Array.from(range.getClientRects()));\n }\n return groupRectsIntoLines(rects);\n}\n","import React from 'react';\n\nexport interface SkeletonPrimitiveProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string | number;\n height?: string | number;\n borderRadius?: string | number;\n color?: string;\n highlightColor?: string;\n}\n\nconst PULSE_CSS = `\n@keyframes auto-skeleton-pulse {\n 0%, 100% { background-color: var(--auto-skeleton-c); }\n 50% { background-color: var(--auto-skeleton-h); }\n}\n.auto-skeleton { animation: auto-skeleton-pulse 1.5s ease-in-out infinite; }\n@media (prefers-reduced-motion: reduce) {\n .auto-skeleton { animation: none; }\n}\n`;\n\nexport const SkeletonPrimitive: React.FC<SkeletonPrimitiveProps> = ({\n width,\n height,\n borderRadius = '4px',\n color = 'var(--auto-skeleton-color, #eee)',\n highlightColor = 'var(--auto-skeleton-highlight, #f5f5f5)',\n className,\n style,\n ...props\n}) => {\n return (\n <>\n {/* React 19 hoists this into <head> and dedupes it by href */}\n <style href=\"auto-skeleton-pulse\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n <div\n className={className ? `auto-skeleton ${className}` : 'auto-skeleton'}\n style={\n {\n width,\n height,\n borderRadius,\n backgroundColor: color,\n '--auto-skeleton-c': color,\n '--auto-skeleton-h': highlightColor,\n ...style,\n } as React.CSSProperties\n }\n data-testid=\"skeleton\"\n {...props}\n />\n </>\n );\n};\n","import { useLayoutEffect, useRef, useState } from 'react';\n\nexport interface DisplayedLoadingOptions {\n /** Wait this long (ms) before showing the skeleton; fast loads never show it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms) to avoid a flash. */\n minDuration?: number;\n}\n\n/**\n * Turns a raw `loading` flag into \"should the skeleton be on screen\",\n * applying a show delay and a minimum display time.\n */\nexport function useDisplayedLoading(\n loading: boolean,\n { delay = 0, minDuration = 0 }: DisplayedLoadingOptions = {},\n): boolean {\n const [shown, setShown] = useState(() => loading && delay <= 0);\n const shownAt = useRef(0);\n\n useLayoutEffect(() => {\n if (shown) shownAt.current = Date.now();\n }, [shown]);\n\n useLayoutEffect(() => {\n if (loading === shown) return;\n\n const wait = loading\n ? delay\n : Math.max(0, minDuration - (Date.now() - shownAt.current));\n\n if (wait <= 0) {\n setShown(loading);\n return;\n }\n const timer = setTimeout(() => setShown(loading), wait);\n return () => clearTimeout(timer);\n }, [loading, shown, delay, minDuration]);\n\n return shown;\n}\n"],"mappings":"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","names":["useLayoutEffect","useRef","useState","jsx","jsxs","useState","useRef","style","node","useLayoutEffect"]}
1
+ {"version":3,"sources":["../src/AutoSkeleton.tsx","../src/measure.ts","../src/SkeletonPrimitive.tsx","../src/useDisplayedLoading.ts","../src/usePrefersDark.ts"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { getTextLineBoxes } from './measure';\nimport { SkeletonPrimitive } from './SkeletonPrimitive';\nimport { useDisplayedLoading } from './useDisplayedLoading';\nimport { usePrefersDark } from './usePrefersDark';\n\n/** Text bars are drawn at this fraction of the line box height, vertically centered. */\nconst TEXT_BAR_RATIO = 0.7;\n\n/** Crossfade duration (ms) between the skeleton and the real content. */\nconst FADE_MS = 200;\n\nexport interface AutoSkeletonProps {\n loading: boolean;\n children: ReactNode;\n /** Text announced to screen readers while loading. */\n loadingLabel?: string;\n /** Class name for the outer wrapper. */\n className?: string;\n /** Style for the outer wrapper; merged over the defaults (`position: relative; display: block`). */\n style?: React.CSSProperties;\n /** Wait this long (ms) before showing the skeleton, so fast loads never flash it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms). */\n minDuration?: number;\n config?: {\n color?: string;\n highlightColor?: string;\n borderRadius?: string | number;\n theme?: 'light' | 'dark' | 'auto';\n // Theme-specific colors (override CSS variables)\n lightColor?: string;\n lightHighlightColor?: string;\n darkColor?: string;\n darkHighlightColor?: string;\n };\n}\n\nconst VISUALLY_HIDDEN: React.CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\nexport const AutoSkeleton: React.FC<AutoSkeletonProps> = ({\n loading,\n children,\n loadingLabel = 'Loading…',\n className,\n style,\n delay = 0,\n minDuration = 0,\n config,\n}) => {\n const theme = config?.theme || 'auto';\n const prefersDark = usePrefersDark();\n // 'auto' follows the OS color scheme so light*/dark* colors apply there too\n const resolvedTheme =\n theme === 'auto' ? (prefersDark ? 'dark' : 'light') : theme;\n const showSkeleton = useDisplayedLoading(loading, { delay, minDuration });\n // Content stays hidden while data is missing or the skeleton is still held on screen\n const busy = loading || showSkeleton;\n\n // Once real content has been shown, a refetch keeps it on screen during `delay`\n // (so fast refetches change nothing visually). Before that, there is only\n // placeholder content, which must stay hidden.\n const [revealed, setRevealed] = useState(!busy);\n if (!busy && !revealed) setRevealed(true);\n const hidden = showSkeleton || (loading && !revealed);\n\n // Keep the overlay mounted while it fades out (set-state-during-render avoids a blank frame)\n const [fadingOut, setFadingOut] = useState(false);\n const [prevShowSkeleton, setPrevShowSkeleton] = useState(showSkeleton);\n if (prevShowSkeleton !== showSkeleton) {\n setPrevShowSkeleton(showSkeleton);\n setFadingOut(!showSkeleton);\n }\n useEffect(() => {\n if (!fadingOut) return;\n const timer = setTimeout(() => setFadingOut(false), FADE_MS);\n return () => clearTimeout(timer);\n }, [fadingOut]);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const [skeletons, setSkeletons] = useState<\n Array<{\n key: string;\n type: 'primitive' | 'container';\n width: number;\n height: number;\n top: number;\n left: number;\n borderRadius: string;\n style?: React.CSSProperties; // For container styles\n }>\n >([]);\n // False until the first client-side measurement (always false on the server)\n const [measured, setMeasured] = useState(false);\n\n const updateSkeletons = () => {\n if (!containerRef.current) return;\n\n const containerRect = containerRef.current.getBoundingClientRect();\n const newSkeletons: typeof skeletons = [];\n\n // Helper to categorize node\n const getSkeletonType = (\n el: Element,\n ): {\n type: 'primitive' | 'container';\n text?: boolean;\n styles?: React.CSSProperties;\n } | null => {\n const tagName = el.tagName.toLowerCase();\n const style = window.getComputedStyle(el);\n\n // 1. Atomic Content (always pulse)\n if (['img', 'input', 'button', 'textarea', 'select'].includes(tagName)) {\n return { type: 'primitive' };\n }\n\n // 2. Text Content (always pulse)\n const hasText = Array.from(el.childNodes).some(\n (n) => n.nodeType === Node.TEXT_NODE && n.textContent?.trim(),\n );\n if (hasText) return { type: 'primitive', text: true };\n\n // 3. Structural Container (static clone)\n // If it has visible styles but no direct text => it's a container\n const hasBackground =\n style.backgroundColor !== 'rgba(0, 0, 0, 0)' &&\n style.backgroundColor !== 'transparent';\n const hasBorder =\n style.borderWidth !== '0px' &&\n style.borderStyle !== 'none' &&\n style.borderColor !== 'rgba(0, 0, 0, 0)' &&\n style.borderColor !== 'transparent';\n const hasShadow = style.boxShadow !== 'none';\n\n if (hasBackground || hasBorder || hasShadow) {\n return {\n type: 'container',\n styles: {\n backgroundColor: style.backgroundColor,\n border: style.border,\n borderRadius: style.borderRadius,\n boxShadow: style.boxShadow,\n },\n };\n }\n\n return null;\n };\n\n // Traverse\n const walker = document.createTreeWalker(\n containerRef.current,\n NodeFilter.SHOW_ELEMENT,\n {\n acceptNode: (node) => {\n const el = node as Element;\n const style = window.getComputedStyle(el);\n if (style.display === 'none') return NodeFilter.FILTER_REJECT;\n\n // If it matches a type, accept it.\n // Note: We want to accept containers AND traverse their children.\n // FILTER_ACCEPT does both.\n if (getSkeletonType(el)) return NodeFilter.FILTER_ACCEPT;\n\n return NodeFilter.FILTER_SKIP;\n },\n },\n );\n\n let node = walker.nextNode();\n let idx = 0;\n while (node) {\n const el = node as Element;\n const rect = el.getBoundingClientRect();\n const typeData = getSkeletonType(el);\n\n // Text: one bar per rendered line instead of one block per element\n const lines = typeData?.text ? getTextLineBoxes(el) : [];\n if (lines.length > 0) {\n for (const line of lines) {\n const height = Math.max(4, line.height * TEXT_BAR_RATIO);\n newSkeletons.push({\n key: `${idx++}`,\n type: 'primitive',\n width: line.width,\n height,\n top: line.top + (line.height - height) / 2 - containerRect.top,\n left: line.left - containerRect.left,\n borderRadius:\n typeof config?.borderRadius === 'number'\n ? `${config.borderRadius}px`\n : (config?.borderRadius ?? '4px'),\n });\n }\n } else if (typeData) {\n // Determine border radius logic\n let borderRadius =\n config?.borderRadius ||\n typeData.styles?.borderRadius ||\n window.getComputedStyle(el).borderRadius;\n\n if (typeData.type === 'primitive') {\n // Auto-detect circle for rounded primitives\n if (borderRadius === '0px' || !borderRadius) {\n if (\n window.getComputedStyle(el).borderRadius === '50%' ||\n (rect.width === rect.height && rect.width < 50)\n ) {\n // Keep heuristic or default\n }\n borderRadius = '4px';\n if (el.tagName.toLowerCase() === 'button') borderRadius = '6px';\n if (el.tagName.toLowerCase() === 'img') borderRadius = '4px';\n }\n }\n\n newSkeletons.push({\n key: `${idx++}`,\n type: typeData.type,\n width: rect.width,\n height: rect.height,\n top: rect.top - containerRect.top,\n left: rect.left - containerRect.left,\n borderRadius: borderRadius as string,\n style: typeData.styles,\n });\n }\n\n node = walker.nextNode();\n }\n\n // If no content nodes found, but container has size (maybe a canvas or simple div w/ background),\n // Fallback? Or maybe user wants the single block behavior.\n // Let's add that fallback if newSkeletons is empty but container has dimension.\n if (\n newSkeletons.length === 0 &&\n (containerRect.width > 0 || containerRect.height > 0)\n ) {\n newSkeletons.push({\n key: 'fallback',\n type: 'primitive',\n width: containerRect.width,\n height: containerRect.height,\n top: 0,\n left: 0,\n borderRadius: (config?.borderRadius as string) || '4px',\n });\n }\n\n setSkeletons(newSkeletons);\n setMeasured(true);\n };\n\n // Layout effect: measure before paint so the overlay never shows a frame without bars\n useLayoutEffect(() => {\n if (!containerRef.current) return;\n\n // Initial measure\n updateSkeletons();\n\n const observer = new ResizeObserver(() => {\n updateSkeletons();\n });\n\n observer.observe(containerRef.current);\n // Also observe children mutations if possible, but ResizeObserver on container catches layout changes often.\n // For robust child changes:\n const mutationObserver = new MutationObserver(() => updateSkeletons());\n mutationObserver.observe(containerRef.current, {\n childList: true,\n subtree: true,\n characterData: true,\n });\n\n return () => {\n observer.disconnect();\n mutationObserver.disconnect();\n };\n }, [showSkeleton, children]); // Re-scan when children change\n\n // Determine effective colors based on theme\n const getEffectiveColors = () => {\n // If user passed explicit color/highlightColor, use those\n if (config?.color || config?.highlightColor) {\n return {\n color: config.color,\n highlightColor: config.highlightColor,\n };\n }\n // If theme-specific colors are provided, use them\n if (\n resolvedTheme === 'dark' &&\n (config?.darkColor || config?.darkHighlightColor)\n ) {\n return {\n color: config.darkColor,\n highlightColor: config.darkHighlightColor,\n };\n }\n if (\n resolvedTheme === 'light' &&\n (config?.lightColor || config?.lightHighlightColor)\n ) {\n return {\n color: config.lightColor,\n highlightColor: config.lightHighlightColor,\n };\n }\n // Otherwise, let CSS variables handle it (undefined = use CSS var defaults)\n return { color: undefined, highlightColor: undefined };\n };\n\n const effectiveColors = getEffectiveColors();\n\n return (\n <div\n className={className}\n style={{ position: 'relative', display: 'block', ...style }}\n {...(theme !== 'auto' ? { 'data-skeleton-theme': theme } : {})}\n aria-busy={busy}\n >\n {/* <output> is not reliably announced as a live region; role=\"status\" is. */}\n {/* oxlint-disable-next-line jsx-a11y/prefer-tag-over-role */}\n <span role=\"status\" style={VISUALLY_HIDDEN}>\n {busy ? loadingLabel : ''}\n </span>\n <div\n ref={containerRef}\n style={{\n opacity: hidden ? 0 : 1,\n // Hide instantly (no ghost content under the bars); fade only on reveal\n transition: hidden ? 'none' : `opacity ${FADE_MS}ms ease`,\n }}\n aria-hidden={hidden}\n inert={busy}\n >\n {children}\n </div>\n\n {(showSkeleton || fadingOut) && (\n <div\n data-testid=\"skeleton-overlay\"\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n width: '100%',\n height: '100%',\n pointerEvents: 'none',\n opacity: showSkeleton ? 1 : 0,\n transition: `opacity ${FADE_MS}ms ease`,\n }}\n >\n {!measured && (\n // Server render / before hydration: nothing can be measured yet, so\n // cover the reserved space with one block instead of showing nothing\n <SkeletonPrimitive\n width=\"100%\"\n height=\"100%\"\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n )}\n {skeletons.map((sk) => (\n <div\n key={sk.key}\n style={{ position: 'absolute', top: sk.top, left: sk.left }}\n >\n {sk.type === 'primitive' ? (\n <SkeletonPrimitive\n width={sk.width}\n height={sk.height}\n borderRadius={sk.borderRadius}\n color={effectiveColors.color}\n highlightColor={effectiveColors.highlightColor}\n />\n ) : (\n /* Container Clone: Renders static style */\n <div\n style={{\n width: sk.width,\n height: sk.height,\n borderRadius: sk.borderRadius,\n ...sk.style, // Apply captured container styles\n // Ensure it doesn't hide children if z-index issues arise,\n // but since we render in DOM order, children come later and sit on top.\n boxSizing: 'border-box',\n }}\n />\n )}\n </div>\n ))}\n </div>\n )}\n </div>\n );\n};\n","export interface Box {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\n/**\n * Merges rects that sit on the same visual line (tops within `tolerance` px)\n * into one box per line, ordered top to bottom.\n */\nexport function groupRectsIntoLines(\n rects: ArrayLike<Box>,\n tolerance = 2,\n): Box[] {\n const sorted = Array.from(rects)\n .filter((r) => r.width > 0 && r.height > 0)\n .sort((a, b) => a.top - b.top || a.left - b.left);\n\n const lines: Array<{\n top: number;\n left: number;\n right: number;\n bottom: number;\n }> = [];\n for (const r of sorted) {\n const line = lines[lines.length - 1];\n if (line && Math.abs(r.top - line.top) <= tolerance) {\n line.left = Math.min(line.left, r.left);\n line.right = Math.max(line.right, r.left + r.width);\n line.bottom = Math.max(line.bottom, r.top + r.height);\n } else {\n lines.push({\n top: r.top,\n left: r.left,\n right: r.left + r.width,\n bottom: r.top + r.height,\n });\n }\n }\n\n return lines.map((l) => ({\n top: l.top,\n left: l.left,\n width: l.right - l.left,\n height: l.bottom - l.top,\n }));\n}\n\n/**\n * Line boxes of an element's own (direct) text nodes, in viewport coordinates.\n * Returns [] when the environment can't measure text ranges (e.g. jsdom).\n */\nexport function getTextLineBoxes(el: Element): Box[] {\n if (typeof document.createRange !== 'function') return [];\n\n const rects: Box[] = [];\n for (const node of Array.from(el.childNodes)) {\n if (node.nodeType !== Node.TEXT_NODE || !node.textContent?.trim()) continue;\n const range = document.createRange();\n range.selectNodeContents(node);\n if (typeof range.getClientRects !== 'function') return [];\n rects.push(...Array.from(range.getClientRects()));\n }\n return groupRectsIntoLines(rects);\n}\n","import React from 'react';\n\nexport interface SkeletonPrimitiveProps extends React.HTMLAttributes<HTMLDivElement> {\n width?: string | number;\n height?: string | number;\n borderRadius?: string | number;\n color?: string;\n highlightColor?: string;\n}\n\nconst PULSE_CSS = `\n@keyframes auto-skeleton-pulse {\n 0%, 100% { background-color: var(--auto-skeleton-c); }\n 50% { background-color: var(--auto-skeleton-h); }\n}\n.auto-skeleton { animation: auto-skeleton-pulse 1.5s ease-in-out infinite; }\n@media (prefers-reduced-motion: reduce) {\n .auto-skeleton { animation: none; }\n}\n`;\n\nexport const SkeletonPrimitive: React.FC<SkeletonPrimitiveProps> = ({\n width,\n height,\n borderRadius = '4px',\n color = 'var(--auto-skeleton-color, #eee)',\n highlightColor = 'var(--auto-skeleton-highlight, #f5f5f5)',\n className,\n style,\n ...props\n}) => {\n return (\n <>\n {/* React 19 hoists this into <head> and dedupes it by href */}\n <style href=\"auto-skeleton-pulse\" precedence=\"default\">\n {PULSE_CSS}\n </style>\n <div\n className={className ? `auto-skeleton ${className}` : 'auto-skeleton'}\n style={\n {\n width,\n height,\n borderRadius,\n backgroundColor: color,\n '--auto-skeleton-c': color,\n '--auto-skeleton-h': highlightColor,\n ...style,\n } as React.CSSProperties\n }\n data-testid=\"skeleton\"\n {...props}\n />\n </>\n );\n};\n","import { useLayoutEffect, useRef, useState } from 'react';\n\nexport interface DisplayedLoadingOptions {\n /** Wait this long (ms) before showing the skeleton; fast loads never show it. */\n delay?: number;\n /** Once shown, keep the skeleton at least this long (ms) to avoid a flash. */\n minDuration?: number;\n}\n\n/**\n * Turns a raw `loading` flag into \"should the skeleton be on screen\",\n * applying a show delay and a minimum display time.\n */\nexport function useDisplayedLoading(\n loading: boolean,\n { delay = 0, minDuration = 0 }: DisplayedLoadingOptions = {},\n): boolean {\n const [shown, setShown] = useState(() => loading && delay <= 0);\n const shownAt = useRef(0);\n\n useLayoutEffect(() => {\n if (shown) shownAt.current = Date.now();\n }, [shown]);\n\n useLayoutEffect(() => {\n if (loading === shown) return;\n\n const wait = loading\n ? delay\n : Math.max(0, minDuration - (Date.now() - shownAt.current));\n\n if (wait <= 0) {\n setShown(loading);\n return;\n }\n const timer = setTimeout(() => setShown(loading), wait);\n return () => clearTimeout(timer);\n }, [loading, shown, delay, minDuration]);\n\n return shown;\n}\n","import { useSyncExternalStore } from 'react';\n\nconst QUERY = '(prefers-color-scheme: dark)';\n\nconst getMedia = () =>\n typeof window !== 'undefined' && typeof window.matchMedia === 'function'\n ? window.matchMedia(QUERY)\n : null;\n\nconst subscribe = (onChange: () => void) => {\n const media = getMedia();\n media?.addEventListener('change', onChange);\n return () => media?.removeEventListener('change', onChange);\n};\n\n/** Live `prefers-color-scheme: dark` match; `false` on the server. */\nexport function usePrefersDark(): boolean {\n return useSyncExternalStore(\n subscribe,\n () => getMedia()?.matches ?? false,\n () => false,\n );\n}\n"],"mappings":"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","names":["useLayoutEffect","useRef","useState","jsx","jsxs","useState","useRef","style","node","useLayoutEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-skeleton-loader",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",