@auto-skeleton/react 0.0.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/dist/index.cjs +189 -0
- package/dist/index.css +62 -0
- package/dist/index.d.mts +38 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.js +161 -0
- package/dist/styles.css +67 -0
- package/package.json +32 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key2 of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
14
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AutoSkeleton: () => AutoSkeleton,
|
|
24
|
+
useAutoSkeleton: () => useAutoSkeleton
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/SkeletonOverlay.tsx
|
|
29
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
30
|
+
function SkeletonOverlay({
|
|
31
|
+
bones,
|
|
32
|
+
animation,
|
|
33
|
+
debug
|
|
34
|
+
}) {
|
|
35
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "as-overlay", "aria-hidden": "true", children: bones.map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
36
|
+
"span",
|
|
37
|
+
{
|
|
38
|
+
className: `as-bone as-${animation}${debug ? " as-debug" : ""}`,
|
|
39
|
+
style: {
|
|
40
|
+
left: b.x,
|
|
41
|
+
top: b.y,
|
|
42
|
+
width: b.width,
|
|
43
|
+
height: b.height,
|
|
44
|
+
borderRadius: b.kind === "circle" ? "50%" : `${b.radius}px`
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
`${b.x}-${b.y}-${i}`
|
|
48
|
+
)) });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/useAutoSkeleton.ts
|
|
52
|
+
var import_react = require("react");
|
|
53
|
+
var import_core = require("@auto-skeleton/core");
|
|
54
|
+
|
|
55
|
+
// src/cache.ts
|
|
56
|
+
var memory = /* @__PURE__ */ new Map();
|
|
57
|
+
function key(id) {
|
|
58
|
+
const bp = typeof window !== "undefined" ? window.innerWidth : 0;
|
|
59
|
+
return `${id}::${bp}`;
|
|
60
|
+
}
|
|
61
|
+
function getCachedBones(id) {
|
|
62
|
+
const k = key(id);
|
|
63
|
+
if (memory.has(k)) return memory.get(k) ?? null;
|
|
64
|
+
if (typeof window === "undefined") return null;
|
|
65
|
+
const raw = window.sessionStorage.getItem(`auto-skeleton:${k}`);
|
|
66
|
+
if (!raw) return null;
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(raw);
|
|
69
|
+
if (!Array.isArray(parsed)) return null;
|
|
70
|
+
memory.set(k, parsed);
|
|
71
|
+
return parsed;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function setCachedBones(id, bones, enabled = true) {
|
|
77
|
+
if (!enabled) return;
|
|
78
|
+
const k = key(id);
|
|
79
|
+
memory.set(k, bones);
|
|
80
|
+
if (typeof window === "undefined") return;
|
|
81
|
+
window.sessionStorage.setItem(`auto-skeleton:${k}`, JSON.stringify(bones));
|
|
82
|
+
}
|
|
83
|
+
function clearCachedBones(id) {
|
|
84
|
+
const k = key(id);
|
|
85
|
+
memory.delete(k);
|
|
86
|
+
if (typeof window === "undefined") return;
|
|
87
|
+
window.sessionStorage.removeItem(`auto-skeleton:${k}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/useAutoSkeleton.ts
|
|
91
|
+
function useAutoSkeleton({
|
|
92
|
+
id,
|
|
93
|
+
loading,
|
|
94
|
+
options
|
|
95
|
+
}) {
|
|
96
|
+
const rootRef = (0, import_react.useRef)(null);
|
|
97
|
+
const cacheEnabled = options?.cache ?? true;
|
|
98
|
+
const [bones, setBones] = (0, import_react.useState)(() => cacheEnabled ? getCachedBones(id) : null);
|
|
99
|
+
const scanTimerRef = (0, import_react.useRef)(null);
|
|
100
|
+
const runScan = (0, import_react.useCallback)(() => {
|
|
101
|
+
if (!rootRef.current) return;
|
|
102
|
+
const next = (0, import_core.scanBones)(rootRef.current, {
|
|
103
|
+
ignoreSelectors: options?.ignoreSelectors,
|
|
104
|
+
minSize: options?.minSize
|
|
105
|
+
});
|
|
106
|
+
setBones(next);
|
|
107
|
+
setCachedBones(id, next, cacheEnabled);
|
|
108
|
+
}, [id, options?.ignoreSelectors, options?.minSize, cacheEnabled]);
|
|
109
|
+
(0, import_react.useEffect)(() => {
|
|
110
|
+
if (!rootRef.current) return;
|
|
111
|
+
if (loading) return;
|
|
112
|
+
runScan();
|
|
113
|
+
}, [loading, runScan]);
|
|
114
|
+
(0, import_react.useEffect)(() => {
|
|
115
|
+
if (!cacheEnabled) clearCachedBones(id);
|
|
116
|
+
}, [id, cacheEnabled]);
|
|
117
|
+
(0, import_react.useEffect)(() => {
|
|
118
|
+
if (!rootRef.current) return;
|
|
119
|
+
if (loading) return;
|
|
120
|
+
if ((options?.watch ?? true) === false) return;
|
|
121
|
+
const debounceMs = options?.watchDebounceMs ?? 120;
|
|
122
|
+
const rootEl = rootRef.current;
|
|
123
|
+
const schedule = () => {
|
|
124
|
+
if (scanTimerRef.current !== null) window.clearTimeout(scanTimerRef.current);
|
|
125
|
+
scanTimerRef.current = window.setTimeout(() => {
|
|
126
|
+
runScan();
|
|
127
|
+
}, debounceMs);
|
|
128
|
+
};
|
|
129
|
+
if (typeof ResizeObserver === "undefined") {
|
|
130
|
+
window.addEventListener("resize", schedule);
|
|
131
|
+
return () => {
|
|
132
|
+
if (scanTimerRef.current !== null) {
|
|
133
|
+
window.clearTimeout(scanTimerRef.current);
|
|
134
|
+
scanTimerRef.current = null;
|
|
135
|
+
}
|
|
136
|
+
window.removeEventListener("resize", schedule);
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const resizeObserver = new ResizeObserver(() => schedule());
|
|
140
|
+
const mutationObserver = new MutationObserver(() => schedule());
|
|
141
|
+
resizeObserver.observe(rootEl);
|
|
142
|
+
mutationObserver.observe(rootEl, {
|
|
143
|
+
childList: true,
|
|
144
|
+
subtree: true,
|
|
145
|
+
characterData: true,
|
|
146
|
+
attributes: true
|
|
147
|
+
});
|
|
148
|
+
window.addEventListener("resize", schedule);
|
|
149
|
+
return () => {
|
|
150
|
+
if (scanTimerRef.current !== null) {
|
|
151
|
+
window.clearTimeout(scanTimerRef.current);
|
|
152
|
+
scanTimerRef.current = null;
|
|
153
|
+
}
|
|
154
|
+
window.removeEventListener("resize", schedule);
|
|
155
|
+
mutationObserver.disconnect();
|
|
156
|
+
resizeObserver.disconnect();
|
|
157
|
+
};
|
|
158
|
+
}, [loading, runScan, options?.watch, options?.watchDebounceMs]);
|
|
159
|
+
const animation = options?.animation ?? "wave";
|
|
160
|
+
const debug = options?.debug ?? false;
|
|
161
|
+
const shouldShow = loading && !!bones && bones.length > 0;
|
|
162
|
+
return {
|
|
163
|
+
rootRef,
|
|
164
|
+
bones,
|
|
165
|
+
animation,
|
|
166
|
+
debug,
|
|
167
|
+
shouldShow
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/AutoSkeleton.tsx
|
|
172
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
173
|
+
function AutoSkeleton({ id, loading, children, className, options }) {
|
|
174
|
+
const { rootRef, bones, animation, debug, shouldShow } = useAutoSkeleton({
|
|
175
|
+
id,
|
|
176
|
+
loading,
|
|
177
|
+
options
|
|
178
|
+
});
|
|
179
|
+
const classes = ["as-root", className, shouldShow ? "as-loading" : ""].filter(Boolean).join(" ");
|
|
180
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { ref: rootRef, className: classes, children: [
|
|
181
|
+
children,
|
|
182
|
+
shouldShow && bones ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SkeletonOverlay, { bones, animation, debug }) : null
|
|
183
|
+
] });
|
|
184
|
+
}
|
|
185
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
186
|
+
0 && (module.exports = {
|
|
187
|
+
AutoSkeleton,
|
|
188
|
+
useAutoSkeleton
|
|
189
|
+
});
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* src/styles.css */
|
|
2
|
+
:root {
|
|
3
|
+
--as-base: #e4e4e7;
|
|
4
|
+
--as-highlight: rgba(255, 255, 255, 0.9);
|
|
5
|
+
--as-debug: rgba(255, 99, 71, 0.45);
|
|
6
|
+
}
|
|
7
|
+
.as-root {
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
.as-root.as-loading > :not(.as-overlay) {
|
|
11
|
+
visibility: hidden;
|
|
12
|
+
}
|
|
13
|
+
.as-overlay {
|
|
14
|
+
position: absolute;
|
|
15
|
+
inset: 0;
|
|
16
|
+
pointer-events: none;
|
|
17
|
+
z-index: 10;
|
|
18
|
+
}
|
|
19
|
+
.as-bone {
|
|
20
|
+
position: absolute;
|
|
21
|
+
background: var(--as-base);
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
}
|
|
24
|
+
.as-bone.as-wave::after {
|
|
25
|
+
content: "";
|
|
26
|
+
position: absolute;
|
|
27
|
+
inset: 0;
|
|
28
|
+
transform: translateX(-100%);
|
|
29
|
+
background:
|
|
30
|
+
linear-gradient(
|
|
31
|
+
90deg,
|
|
32
|
+
transparent 0%,
|
|
33
|
+
var(--as-highlight) 45%,
|
|
34
|
+
transparent 100%);
|
|
35
|
+
animation: as-wave 1.2s infinite;
|
|
36
|
+
}
|
|
37
|
+
.as-bone.as-pulse {
|
|
38
|
+
animation: as-pulse 1.1s ease-in-out infinite;
|
|
39
|
+
}
|
|
40
|
+
.as-bone.as-debug {
|
|
41
|
+
outline: 1px dashed var(--as-debug);
|
|
42
|
+
outline-offset: -1px;
|
|
43
|
+
}
|
|
44
|
+
@keyframes as-wave {
|
|
45
|
+
100% {
|
|
46
|
+
transform: translateX(100%);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
@keyframes as-pulse {
|
|
50
|
+
0%, 100% {
|
|
51
|
+
opacity: 1;
|
|
52
|
+
}
|
|
53
|
+
50% {
|
|
54
|
+
opacity: 0.55;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
@media (prefers-reduced-motion: reduce) {
|
|
58
|
+
.as-bone.as-wave::after,
|
|
59
|
+
.as-bone.as-pulse {
|
|
60
|
+
animation: none;
|
|
61
|
+
}
|
|
62
|
+
}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { ScanOptions, Bone } from '@auto-skeleton/core';
|
|
5
|
+
|
|
6
|
+
type AutoSkeletonOptions = ScanOptions & {
|
|
7
|
+
animation?: "wave" | "pulse" | "none";
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
/** Re-scan when layout/content mutates while not loading. */
|
|
10
|
+
watch?: boolean;
|
|
11
|
+
/** Debounce for watcher-driven re-scan. */
|
|
12
|
+
watchDebounceMs?: number;
|
|
13
|
+
/** Disable sessionStorage caching if needed. */
|
|
14
|
+
cache?: boolean;
|
|
15
|
+
};
|
|
16
|
+
type AutoSkeletonProps = {
|
|
17
|
+
id: string;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
options?: AutoSkeletonOptions;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare function AutoSkeleton({ id, loading, children, className, options }: AutoSkeletonProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
|
|
26
|
+
declare function useAutoSkeleton({ id, loading, options }: {
|
|
27
|
+
id: string;
|
|
28
|
+
loading: boolean;
|
|
29
|
+
options?: AutoSkeletonOptions;
|
|
30
|
+
}): {
|
|
31
|
+
rootRef: react.RefObject<HTMLDivElement>;
|
|
32
|
+
bones: Bone[] | null;
|
|
33
|
+
animation: "wave" | "pulse" | "none";
|
|
34
|
+
debug: boolean;
|
|
35
|
+
shouldShow: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { AutoSkeleton, type AutoSkeletonOptions, type AutoSkeletonProps, useAutoSkeleton };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as react from 'react';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
4
|
+
import { ScanOptions, Bone } from '@auto-skeleton/core';
|
|
5
|
+
|
|
6
|
+
type AutoSkeletonOptions = ScanOptions & {
|
|
7
|
+
animation?: "wave" | "pulse" | "none";
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
/** Re-scan when layout/content mutates while not loading. */
|
|
10
|
+
watch?: boolean;
|
|
11
|
+
/** Debounce for watcher-driven re-scan. */
|
|
12
|
+
watchDebounceMs?: number;
|
|
13
|
+
/** Disable sessionStorage caching if needed. */
|
|
14
|
+
cache?: boolean;
|
|
15
|
+
};
|
|
16
|
+
type AutoSkeletonProps = {
|
|
17
|
+
id: string;
|
|
18
|
+
loading: boolean;
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
options?: AutoSkeletonOptions;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
declare function AutoSkeleton({ id, loading, children, className, options }: AutoSkeletonProps): react_jsx_runtime.JSX.Element;
|
|
25
|
+
|
|
26
|
+
declare function useAutoSkeleton({ id, loading, options }: {
|
|
27
|
+
id: string;
|
|
28
|
+
loading: boolean;
|
|
29
|
+
options?: AutoSkeletonOptions;
|
|
30
|
+
}): {
|
|
31
|
+
rootRef: react.RefObject<HTMLDivElement>;
|
|
32
|
+
bones: Bone[] | null;
|
|
33
|
+
animation: "wave" | "pulse" | "none";
|
|
34
|
+
debug: boolean;
|
|
35
|
+
shouldShow: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export { AutoSkeleton, type AutoSkeletonOptions, type AutoSkeletonProps, useAutoSkeleton };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/SkeletonOverlay.tsx
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
function SkeletonOverlay({
|
|
4
|
+
bones,
|
|
5
|
+
animation,
|
|
6
|
+
debug
|
|
7
|
+
}) {
|
|
8
|
+
return /* @__PURE__ */ jsx("div", { className: "as-overlay", "aria-hidden": "true", children: bones.map((b, i) => /* @__PURE__ */ jsx(
|
|
9
|
+
"span",
|
|
10
|
+
{
|
|
11
|
+
className: `as-bone as-${animation}${debug ? " as-debug" : ""}`,
|
|
12
|
+
style: {
|
|
13
|
+
left: b.x,
|
|
14
|
+
top: b.y,
|
|
15
|
+
width: b.width,
|
|
16
|
+
height: b.height,
|
|
17
|
+
borderRadius: b.kind === "circle" ? "50%" : `${b.radius}px`
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
`${b.x}-${b.y}-${i}`
|
|
21
|
+
)) });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/useAutoSkeleton.ts
|
|
25
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
26
|
+
import { scanBones } from "@auto-skeleton/core";
|
|
27
|
+
|
|
28
|
+
// src/cache.ts
|
|
29
|
+
var memory = /* @__PURE__ */ new Map();
|
|
30
|
+
function key(id) {
|
|
31
|
+
const bp = typeof window !== "undefined" ? window.innerWidth : 0;
|
|
32
|
+
return `${id}::${bp}`;
|
|
33
|
+
}
|
|
34
|
+
function getCachedBones(id) {
|
|
35
|
+
const k = key(id);
|
|
36
|
+
if (memory.has(k)) return memory.get(k) ?? null;
|
|
37
|
+
if (typeof window === "undefined") return null;
|
|
38
|
+
const raw = window.sessionStorage.getItem(`auto-skeleton:${k}`);
|
|
39
|
+
if (!raw) return null;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(raw);
|
|
42
|
+
if (!Array.isArray(parsed)) return null;
|
|
43
|
+
memory.set(k, parsed);
|
|
44
|
+
return parsed;
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function setCachedBones(id, bones, enabled = true) {
|
|
50
|
+
if (!enabled) return;
|
|
51
|
+
const k = key(id);
|
|
52
|
+
memory.set(k, bones);
|
|
53
|
+
if (typeof window === "undefined") return;
|
|
54
|
+
window.sessionStorage.setItem(`auto-skeleton:${k}`, JSON.stringify(bones));
|
|
55
|
+
}
|
|
56
|
+
function clearCachedBones(id) {
|
|
57
|
+
const k = key(id);
|
|
58
|
+
memory.delete(k);
|
|
59
|
+
if (typeof window === "undefined") return;
|
|
60
|
+
window.sessionStorage.removeItem(`auto-skeleton:${k}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/useAutoSkeleton.ts
|
|
64
|
+
function useAutoSkeleton({
|
|
65
|
+
id,
|
|
66
|
+
loading,
|
|
67
|
+
options
|
|
68
|
+
}) {
|
|
69
|
+
const rootRef = useRef(null);
|
|
70
|
+
const cacheEnabled = options?.cache ?? true;
|
|
71
|
+
const [bones, setBones] = useState(() => cacheEnabled ? getCachedBones(id) : null);
|
|
72
|
+
const scanTimerRef = useRef(null);
|
|
73
|
+
const runScan = useCallback(() => {
|
|
74
|
+
if (!rootRef.current) return;
|
|
75
|
+
const next = scanBones(rootRef.current, {
|
|
76
|
+
ignoreSelectors: options?.ignoreSelectors,
|
|
77
|
+
minSize: options?.minSize
|
|
78
|
+
});
|
|
79
|
+
setBones(next);
|
|
80
|
+
setCachedBones(id, next, cacheEnabled);
|
|
81
|
+
}, [id, options?.ignoreSelectors, options?.minSize, cacheEnabled]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (!rootRef.current) return;
|
|
84
|
+
if (loading) return;
|
|
85
|
+
runScan();
|
|
86
|
+
}, [loading, runScan]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
if (!cacheEnabled) clearCachedBones(id);
|
|
89
|
+
}, [id, cacheEnabled]);
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (!rootRef.current) return;
|
|
92
|
+
if (loading) return;
|
|
93
|
+
if ((options?.watch ?? true) === false) return;
|
|
94
|
+
const debounceMs = options?.watchDebounceMs ?? 120;
|
|
95
|
+
const rootEl = rootRef.current;
|
|
96
|
+
const schedule = () => {
|
|
97
|
+
if (scanTimerRef.current !== null) window.clearTimeout(scanTimerRef.current);
|
|
98
|
+
scanTimerRef.current = window.setTimeout(() => {
|
|
99
|
+
runScan();
|
|
100
|
+
}, debounceMs);
|
|
101
|
+
};
|
|
102
|
+
if (typeof ResizeObserver === "undefined") {
|
|
103
|
+
window.addEventListener("resize", schedule);
|
|
104
|
+
return () => {
|
|
105
|
+
if (scanTimerRef.current !== null) {
|
|
106
|
+
window.clearTimeout(scanTimerRef.current);
|
|
107
|
+
scanTimerRef.current = null;
|
|
108
|
+
}
|
|
109
|
+
window.removeEventListener("resize", schedule);
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const resizeObserver = new ResizeObserver(() => schedule());
|
|
113
|
+
const mutationObserver = new MutationObserver(() => schedule());
|
|
114
|
+
resizeObserver.observe(rootEl);
|
|
115
|
+
mutationObserver.observe(rootEl, {
|
|
116
|
+
childList: true,
|
|
117
|
+
subtree: true,
|
|
118
|
+
characterData: true,
|
|
119
|
+
attributes: true
|
|
120
|
+
});
|
|
121
|
+
window.addEventListener("resize", schedule);
|
|
122
|
+
return () => {
|
|
123
|
+
if (scanTimerRef.current !== null) {
|
|
124
|
+
window.clearTimeout(scanTimerRef.current);
|
|
125
|
+
scanTimerRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
window.removeEventListener("resize", schedule);
|
|
128
|
+
mutationObserver.disconnect();
|
|
129
|
+
resizeObserver.disconnect();
|
|
130
|
+
};
|
|
131
|
+
}, [loading, runScan, options?.watch, options?.watchDebounceMs]);
|
|
132
|
+
const animation = options?.animation ?? "wave";
|
|
133
|
+
const debug = options?.debug ?? false;
|
|
134
|
+
const shouldShow = loading && !!bones && bones.length > 0;
|
|
135
|
+
return {
|
|
136
|
+
rootRef,
|
|
137
|
+
bones,
|
|
138
|
+
animation,
|
|
139
|
+
debug,
|
|
140
|
+
shouldShow
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/AutoSkeleton.tsx
|
|
145
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
146
|
+
function AutoSkeleton({ id, loading, children, className, options }) {
|
|
147
|
+
const { rootRef, bones, animation, debug, shouldShow } = useAutoSkeleton({
|
|
148
|
+
id,
|
|
149
|
+
loading,
|
|
150
|
+
options
|
|
151
|
+
});
|
|
152
|
+
const classes = ["as-root", className, shouldShow ? "as-loading" : ""].filter(Boolean).join(" ");
|
|
153
|
+
return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: classes, children: [
|
|
154
|
+
children,
|
|
155
|
+
shouldShow && bones ? /* @__PURE__ */ jsx2(SkeletonOverlay, { bones, animation, debug }) : null
|
|
156
|
+
] });
|
|
157
|
+
}
|
|
158
|
+
export {
|
|
159
|
+
AutoSkeleton,
|
|
160
|
+
useAutoSkeleton
|
|
161
|
+
};
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--as-base: #e4e4e7;
|
|
3
|
+
--as-highlight: rgba(255, 255, 255, 0.9);
|
|
4
|
+
--as-debug: rgba(255, 99, 71, 0.45);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.as-root {
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.as-root.as-loading > :not(.as-overlay) {
|
|
12
|
+
visibility: hidden;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.as-overlay {
|
|
16
|
+
position: absolute;
|
|
17
|
+
inset: 0;
|
|
18
|
+
pointer-events: none;
|
|
19
|
+
z-index: 10;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.as-bone {
|
|
23
|
+
position: absolute;
|
|
24
|
+
background: var(--as-base);
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.as-bone.as-wave::after {
|
|
29
|
+
content: "";
|
|
30
|
+
position: absolute;
|
|
31
|
+
inset: 0;
|
|
32
|
+
transform: translateX(-100%);
|
|
33
|
+
background: linear-gradient(90deg, transparent 0%, var(--as-highlight) 45%, transparent 100%);
|
|
34
|
+
animation: as-wave 1.2s infinite;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.as-bone.as-pulse {
|
|
38
|
+
animation: as-pulse 1.1s ease-in-out infinite;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.as-bone.as-debug {
|
|
42
|
+
outline: 1px dashed var(--as-debug);
|
|
43
|
+
outline-offset: -1px;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
@keyframes as-wave {
|
|
47
|
+
100% {
|
|
48
|
+
transform: translateX(100%);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@keyframes as-pulse {
|
|
53
|
+
0%,
|
|
54
|
+
100% {
|
|
55
|
+
opacity: 1;
|
|
56
|
+
}
|
|
57
|
+
50% {
|
|
58
|
+
opacity: 0.55;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@media (prefers-reduced-motion: reduce) {
|
|
63
|
+
.as-bone.as-wave::after,
|
|
64
|
+
.as-bone.as-pulse {
|
|
65
|
+
animation: none;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@auto-skeleton/react",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"main": "dist/index.cjs",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.cjs"
|
|
12
|
+
},
|
|
13
|
+
"./styles.css": "./dist/styles.css"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": [
|
|
19
|
+
"**/*.css"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup && cp src/styles.css dist/styles.css",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react": ">=18",
|
|
27
|
+
"react-dom": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@auto-skeleton/core": "0.0.2"
|
|
31
|
+
}
|
|
32
|
+
}
|