@theengineeringmind/auto-skeleton 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/cache-BEybr_z-.d.cts +154 -0
- package/dist/cache-BEybr_z-.d.ts +154 -0
- package/dist/chunk-VI2M6DZS.js +439 -0
- package/dist/chunk-VI2M6DZS.js.map +1 -0
- package/dist/chunk-VTA53XYO.cjs +470 -0
- package/dist/chunk-VTA53XYO.cjs.map +1 -0
- package/dist/index.cjs +128 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +95 -0
- package/dist/index.d.ts +95 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +244 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +86 -0
- package/dist/react.d.ts +86 -0
- package/dist/react.js +228 -0
- package/dist/react.js.map +1 -0
- package/package.json +142 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/core/classify.ts
|
|
6
|
+
var MEDIA = /* @__PURE__ */ new Set(["img", "svg", "video", "canvas", "picture", "iframe", "object", "embed", "audio"]);
|
|
7
|
+
var CONTROL = /* @__PURE__ */ new Set(["input", "textarea", "select", "button", "progress", "meter"]);
|
|
8
|
+
var IGNORED = /* @__PURE__ */ new Set([
|
|
9
|
+
"script",
|
|
10
|
+
"style",
|
|
11
|
+
"template",
|
|
12
|
+
"noscript",
|
|
13
|
+
"br",
|
|
14
|
+
"wbr",
|
|
15
|
+
"head",
|
|
16
|
+
"meta",
|
|
17
|
+
"link",
|
|
18
|
+
"title"
|
|
19
|
+
]);
|
|
20
|
+
var BLOCK_ROLES = /* @__PURE__ */ new Set(["img", "progressbar", "slider", "switch", "checkbox", "radio"]);
|
|
21
|
+
var DATA_ATTRIBUTE = "data-auto-skeleton";
|
|
22
|
+
function isTransparent(color) {
|
|
23
|
+
if (!color || color === "transparent") return true;
|
|
24
|
+
const match = /^rgba?\(([^)]+)\)$/.exec(color);
|
|
25
|
+
if (!match) return false;
|
|
26
|
+
const parts = match[1].split(/[\s,/]+/).filter(Boolean);
|
|
27
|
+
return parts.length === 4 && Number.parseFloat(parts[3]) === 0;
|
|
28
|
+
}
|
|
29
|
+
function hasVisibleBorder(style) {
|
|
30
|
+
const widths = [
|
|
31
|
+
style.borderTopWidth,
|
|
32
|
+
style.borderRightWidth,
|
|
33
|
+
style.borderBottomWidth,
|
|
34
|
+
style.borderLeftWidth
|
|
35
|
+
];
|
|
36
|
+
return widths.some((w) => Number.parseFloat(w) > 0) && style.borderTopStyle !== "none";
|
|
37
|
+
}
|
|
38
|
+
function isEmptyLeaf(element) {
|
|
39
|
+
if (element.childElementCount > 0) return false;
|
|
40
|
+
return !(element.textContent ?? "").trim();
|
|
41
|
+
}
|
|
42
|
+
function kindOf(element) {
|
|
43
|
+
const tag = element.localName;
|
|
44
|
+
if (MEDIA.has(tag) || element.getAttribute("role") === "img") return "media";
|
|
45
|
+
if (CONTROL.has(tag)) return "control";
|
|
46
|
+
return "box";
|
|
47
|
+
}
|
|
48
|
+
function classify(element, window, options, context = {}) {
|
|
49
|
+
const custom = options.classify?.(element);
|
|
50
|
+
if (custom) return custom;
|
|
51
|
+
const attr = element.getAttribute(DATA_ATTRIBUTE);
|
|
52
|
+
if (attr === "skip" || attr === "block" || attr === "descend") return attr;
|
|
53
|
+
const tag = element.localName;
|
|
54
|
+
if (IGNORED.has(tag)) return "skip";
|
|
55
|
+
const style = window.getComputedStyle(element);
|
|
56
|
+
if (style.display === "none") return "skip";
|
|
57
|
+
if (!context.root) {
|
|
58
|
+
const hidden = style.visibility === "hidden" || style.visibility === "collapse";
|
|
59
|
+
if (hidden && style.visibility !== context.parentVisibility) return "skip";
|
|
60
|
+
if (Number.parseFloat(style.opacity) === 0) return "skip";
|
|
61
|
+
}
|
|
62
|
+
if (MEDIA.has(tag) || CONTROL.has(tag)) return "block";
|
|
63
|
+
const role = element.getAttribute("role");
|
|
64
|
+
if (role && BLOCK_ROLES.has(role)) return "block";
|
|
65
|
+
const decorated = style.backgroundImage !== "none" || !isTransparent(style.backgroundColor) || hasVisibleBorder(style);
|
|
66
|
+
if (decorated) {
|
|
67
|
+
if (isEmptyLeaf(element)) return "block";
|
|
68
|
+
const rect = element.getBoundingClientRect();
|
|
69
|
+
if (rect.height <= options.maxBackgroundBlock) {
|
|
70
|
+
if (rect.width <= options.maxBackgroundBlock) return "block";
|
|
71
|
+
if (rect.width <= options.maxBackgroundBlock * 4 && element.childElementCount === 0) return "block";
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return "descend";
|
|
75
|
+
}
|
|
76
|
+
function radiusOf(style) {
|
|
77
|
+
const norm = (value) => !value || value === "0" ? "0px" : value;
|
|
78
|
+
const tl = norm(style.borderTopLeftRadius);
|
|
79
|
+
const tr = norm(style.borderTopRightRadius);
|
|
80
|
+
const br = norm(style.borderBottomRightRadius);
|
|
81
|
+
const bl = norm(style.borderBottomLeftRadius);
|
|
82
|
+
if (tl === tr && tr === br && br === bl) return tl;
|
|
83
|
+
return `${tl} ${tr} ${br} ${bl}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/core/geometry.ts
|
|
87
|
+
function round(n) {
|
|
88
|
+
return Math.round(n * 100) / 100;
|
|
89
|
+
}
|
|
90
|
+
function toRelative(rect, origin) {
|
|
91
|
+
return {
|
|
92
|
+
x: round(rect.left - origin.left),
|
|
93
|
+
y: round(rect.top - origin.top),
|
|
94
|
+
width: round(rect.width),
|
|
95
|
+
height: round(rect.height)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function scaleHeight(rect, scale) {
|
|
99
|
+
const height = round(rect.height * scale);
|
|
100
|
+
return { ...rect, y: round(rect.y + (rect.height - height) / 2), height };
|
|
101
|
+
}
|
|
102
|
+
function sameLine(a, b) {
|
|
103
|
+
const top = Math.max(a.y, b.y);
|
|
104
|
+
const bottom = Math.min(a.y + a.height, b.y + b.height);
|
|
105
|
+
const overlap = bottom - top;
|
|
106
|
+
if (overlap <= 0) return false;
|
|
107
|
+
return overlap >= Math.min(a.height, b.height) * 0.5;
|
|
108
|
+
}
|
|
109
|
+
function union(a, b) {
|
|
110
|
+
const x = Math.min(a.x, b.x);
|
|
111
|
+
const y = Math.min(a.y, b.y);
|
|
112
|
+
const right = Math.max(a.x + a.width, b.x + b.width);
|
|
113
|
+
const bottom = Math.max(a.y + a.height, b.y + b.height);
|
|
114
|
+
return { x, y, width: round(right - x), height: round(bottom - y) };
|
|
115
|
+
}
|
|
116
|
+
function mergeLineRects(rects, gap) {
|
|
117
|
+
if (rects.length === 0) return [];
|
|
118
|
+
const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);
|
|
119
|
+
const lines = [];
|
|
120
|
+
let current = [];
|
|
121
|
+
let currentBox = null;
|
|
122
|
+
for (const r of sorted) {
|
|
123
|
+
if (currentBox && sameLine(currentBox, r)) {
|
|
124
|
+
current.push(r);
|
|
125
|
+
currentBox = union(currentBox, r);
|
|
126
|
+
} else {
|
|
127
|
+
if (current.length) lines.push(current);
|
|
128
|
+
current = [r];
|
|
129
|
+
currentBox = r;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (current.length) lines.push(current);
|
|
133
|
+
const out = [];
|
|
134
|
+
for (const line of lines) {
|
|
135
|
+
line.sort((a, b) => a.x - b.x);
|
|
136
|
+
let acc = line[0];
|
|
137
|
+
for (let i = 1; i < line.length; i++) {
|
|
138
|
+
const next = line[i];
|
|
139
|
+
const distance = next.x - (acc.x + acc.width);
|
|
140
|
+
if (distance <= gap) {
|
|
141
|
+
acc = union(acc, next);
|
|
142
|
+
} else {
|
|
143
|
+
out.push(acc);
|
|
144
|
+
acc = next;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
out.push(acc);
|
|
148
|
+
}
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
function isVisible(rect, bounds, minSize) {
|
|
152
|
+
if (rect.width < minSize || rect.height < minSize) return false;
|
|
153
|
+
if (rect.x + rect.width <= 0 || rect.y + rect.height <= 0) return false;
|
|
154
|
+
if (rect.x >= bounds.width || rect.y >= bounds.height) return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/core/measure.ts
|
|
159
|
+
var DEFAULT_MEASURE_OPTIONS = {
|
|
160
|
+
textScale: 0.7,
|
|
161
|
+
minSize: 2,
|
|
162
|
+
maxBackgroundBlock: 64,
|
|
163
|
+
mergeGap: 4,
|
|
164
|
+
textRadius: "4px",
|
|
165
|
+
maxBlocks: 400,
|
|
166
|
+
classify: void 0
|
|
167
|
+
};
|
|
168
|
+
function resolveMeasureOptions(options = {}) {
|
|
169
|
+
const resolved = { ...DEFAULT_MEASURE_OPTIONS };
|
|
170
|
+
for (const key of Object.keys(options)) {
|
|
171
|
+
const value = options[key];
|
|
172
|
+
if (value !== void 0) resolved[key] = value;
|
|
173
|
+
}
|
|
174
|
+
if (!(resolved.textScale > 0 && resolved.textScale <= 1)) {
|
|
175
|
+
throw new RangeError(`auto-skeleton: textScale must be in (0, 1], got ${String(options.textScale)}`);
|
|
176
|
+
}
|
|
177
|
+
if (!(Number.isInteger(resolved.maxBlocks) && resolved.maxBlocks > 0)) {
|
|
178
|
+
throw new RangeError(
|
|
179
|
+
`auto-skeleton: maxBlocks must be a positive integer, got ${String(options.maxBlocks)}`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return resolved;
|
|
183
|
+
}
|
|
184
|
+
function measure(root, options) {
|
|
185
|
+
const opts = resolveMeasureOptions(options);
|
|
186
|
+
const win = root.ownerDocument.defaultView;
|
|
187
|
+
if (!win) throw new Error("auto-skeleton: root element is not attached to a window");
|
|
188
|
+
const rootRect = root.getBoundingClientRect();
|
|
189
|
+
const origin = { left: rootRect.left, top: rootRect.top };
|
|
190
|
+
const bounds = { width: rootRect.width, height: rootRect.height };
|
|
191
|
+
const blocks = [];
|
|
192
|
+
const textRects = [];
|
|
193
|
+
const full = () => blocks.length + textRects.length >= opts.maxBlocks;
|
|
194
|
+
const pushBlock = (element) => {
|
|
195
|
+
const rect = toRelative(element.getBoundingClientRect(), origin);
|
|
196
|
+
if (!isVisible(rect, bounds, opts.minSize)) return;
|
|
197
|
+
blocks.push({ ...rect, radius: radiusOf(win.getComputedStyle(element)), kind: kindOf(element) });
|
|
198
|
+
};
|
|
199
|
+
const collectText = (node) => {
|
|
200
|
+
if (node.data.length === 0) return;
|
|
201
|
+
const range = root.ownerDocument.createRange();
|
|
202
|
+
range.selectNodeContents(node);
|
|
203
|
+
for (const r of Array.from(range.getClientRects())) {
|
|
204
|
+
const rect = toRelative(r, origin);
|
|
205
|
+
if (isVisible(rect, bounds, opts.minSize)) textRects.push(rect);
|
|
206
|
+
}
|
|
207
|
+
range.detach();
|
|
208
|
+
};
|
|
209
|
+
const walk = (element) => {
|
|
210
|
+
const parentVisibility = win.getComputedStyle(element).visibility;
|
|
211
|
+
for (const child of Array.from(element.childNodes)) {
|
|
212
|
+
if (full()) return;
|
|
213
|
+
if (child.nodeType === Node.TEXT_NODE) {
|
|
214
|
+
collectText(child);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
if (child.nodeType !== Node.ELEMENT_NODE) continue;
|
|
218
|
+
const el = child;
|
|
219
|
+
const decision = classify(el, win, opts, { parentVisibility });
|
|
220
|
+
if (decision === "skip") continue;
|
|
221
|
+
if (decision === "block") {
|
|
222
|
+
pushBlock(el);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
walk(el);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
const rootDecision = classify(root, win, opts, { root: true });
|
|
229
|
+
if (rootDecision === "block") {
|
|
230
|
+
pushBlock(root);
|
|
231
|
+
} else if (rootDecision === "descend") {
|
|
232
|
+
walk(root);
|
|
233
|
+
}
|
|
234
|
+
for (const line of mergeLineRects(textRects, opts.mergeGap)) {
|
|
235
|
+
const scaled = scaleHeight(line, opts.textScale);
|
|
236
|
+
blocks.push({ ...scaled, radius: opts.textRadius, kind: "text" });
|
|
237
|
+
}
|
|
238
|
+
blocks.sort((a, b) => a.y - b.y || a.x - b.x);
|
|
239
|
+
return {
|
|
240
|
+
width: Math.round(bounds.width * 100) / 100,
|
|
241
|
+
height: Math.round(bounds.height * 100) / 100,
|
|
242
|
+
blocks: blocks.length > opts.maxBlocks ? blocks.slice(0, opts.maxBlocks) : blocks
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// src/core/styles.ts
|
|
247
|
+
var STYLE_ID = "auto-skeleton-styles";
|
|
248
|
+
var autoskeletonCSS = `
|
|
249
|
+
.auto-skeleton {
|
|
250
|
+
position: relative;
|
|
251
|
+
display: block;
|
|
252
|
+
width: 100%;
|
|
253
|
+
overflow: hidden;
|
|
254
|
+
contain: layout style paint;
|
|
255
|
+
--_c: var(--auto-skeleton-color, rgba(128, 128, 128, 0.18));
|
|
256
|
+
--_h: var(--auto-skeleton-highlight, rgba(128, 128, 128, 0.34));
|
|
257
|
+
}
|
|
258
|
+
.auto-skeleton__block {
|
|
259
|
+
position: absolute;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
pointer-events: none;
|
|
262
|
+
background-color: var(--_c);
|
|
263
|
+
}
|
|
264
|
+
.auto-skeleton[data-animate="true"] .auto-skeleton__block::after {
|
|
265
|
+
content: "";
|
|
266
|
+
position: absolute;
|
|
267
|
+
top: 0;
|
|
268
|
+
bottom: 0;
|
|
269
|
+
left: calc(var(--_x) * -1);
|
|
270
|
+
width: var(--auto-skeleton-w);
|
|
271
|
+
background: linear-gradient(90deg, transparent 0%, var(--_h) 50%, transparent 100%);
|
|
272
|
+
transform: translateX(-100%);
|
|
273
|
+
animation: auto-skeleton-sweep var(--auto-skeleton-duration, 1.6s) linear infinite;
|
|
274
|
+
}
|
|
275
|
+
@keyframes auto-skeleton-sweep {
|
|
276
|
+
to { transform: translateX(100%); }
|
|
277
|
+
}
|
|
278
|
+
.auto-skeleton--fading {
|
|
279
|
+
opacity: 0;
|
|
280
|
+
transition: opacity var(--auto-skeleton-fade, 200ms) ease-out;
|
|
281
|
+
}
|
|
282
|
+
@media (prefers-reduced-motion: reduce) {
|
|
283
|
+
.auto-skeleton[data-animate="true"] .auto-skeleton__block::after { animation: none; content: none; }
|
|
284
|
+
}
|
|
285
|
+
`.trim();
|
|
286
|
+
function ensureStyles(doc = document, nonce) {
|
|
287
|
+
const existing = doc.getElementById(STYLE_ID);
|
|
288
|
+
if (existing instanceof HTMLStyleElement) return existing;
|
|
289
|
+
const style = doc.createElement("style");
|
|
290
|
+
style.id = STYLE_ID;
|
|
291
|
+
if (nonce) style.setAttribute("nonce", nonce);
|
|
292
|
+
style.textContent = autoskeletonCSS;
|
|
293
|
+
(doc.head ?? doc.documentElement).appendChild(style);
|
|
294
|
+
return style;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/core/render.ts
|
|
298
|
+
var CONTAINER_CLASS = "auto-skeleton";
|
|
299
|
+
var BLOCK_CLASS = "auto-skeleton__block";
|
|
300
|
+
var FADING_CLASS = "auto-skeleton--fading";
|
|
301
|
+
function cssTime(value) {
|
|
302
|
+
return typeof value === "number" ? `${value}ms` : value;
|
|
303
|
+
}
|
|
304
|
+
function themeStyle(theme = {}) {
|
|
305
|
+
const style = {};
|
|
306
|
+
if (theme.color !== void 0) style["--auto-skeleton-color"] = theme.color;
|
|
307
|
+
if (theme.highlight !== void 0) style["--auto-skeleton-highlight"] = theme.highlight;
|
|
308
|
+
if (theme.duration !== void 0) style["--auto-skeleton-duration"] = cssTime(theme.duration);
|
|
309
|
+
if (theme.fade !== void 0) style["--auto-skeleton-fade"] = cssTime(theme.fade);
|
|
310
|
+
return style;
|
|
311
|
+
}
|
|
312
|
+
function containerStyle(layout, theme) {
|
|
313
|
+
return {
|
|
314
|
+
...themeStyle(theme),
|
|
315
|
+
height: `${layout.height}px`,
|
|
316
|
+
"--auto-skeleton-w": `${layout.width}px`
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
function blockStyle(block) {
|
|
320
|
+
return {
|
|
321
|
+
left: `${block.x}px`,
|
|
322
|
+
top: `${block.y}px`,
|
|
323
|
+
width: `${block.width}px`,
|
|
324
|
+
height: `${block.height}px`,
|
|
325
|
+
borderRadius: block.radius,
|
|
326
|
+
"--_x": `${block.x}px`
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function blockClassName(block) {
|
|
330
|
+
return `${BLOCK_CLASS} ${BLOCK_CLASS}--${block.kind}`;
|
|
331
|
+
}
|
|
332
|
+
function applyStyle(el, style) {
|
|
333
|
+
for (const [key, value] of Object.entries(style)) {
|
|
334
|
+
if (key.startsWith("--")) el.style.setProperty(key, value);
|
|
335
|
+
else el.style[key] = value;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function updateSkeleton(container, layout, theme) {
|
|
339
|
+
applyStyle(container, containerStyle(layout, theme));
|
|
340
|
+
const doc = container.ownerDocument;
|
|
341
|
+
const fragment = doc.createDocumentFragment();
|
|
342
|
+
for (const block of layout.blocks) {
|
|
343
|
+
const el = doc.createElement("div");
|
|
344
|
+
el.className = blockClassName(block);
|
|
345
|
+
el.setAttribute("aria-hidden", "true");
|
|
346
|
+
applyStyle(el, blockStyle(block));
|
|
347
|
+
fragment.appendChild(el);
|
|
348
|
+
}
|
|
349
|
+
container.replaceChildren(fragment);
|
|
350
|
+
}
|
|
351
|
+
function renderSkeleton(layout, options = {}) {
|
|
352
|
+
const doc = options.document ?? document;
|
|
353
|
+
if (options.injectStyles !== false) ensureStyles(doc, options.nonce);
|
|
354
|
+
const container = doc.createElement("div");
|
|
355
|
+
container.className = options.className ? `${CONTAINER_CLASS} ${options.className}` : CONTAINER_CLASS;
|
|
356
|
+
container.setAttribute("role", "status");
|
|
357
|
+
container.setAttribute("aria-busy", "true");
|
|
358
|
+
container.setAttribute("aria-label", options.label ?? "Loading");
|
|
359
|
+
container.dataset["animate"] = String(options.animate !== false);
|
|
360
|
+
updateSkeleton(container, layout, options);
|
|
361
|
+
return container;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// src/core/skeleton.ts
|
|
365
|
+
function createSkeleton(source, options = {}) {
|
|
366
|
+
let layout = measure(source, options);
|
|
367
|
+
const element = renderSkeleton(layout, options);
|
|
368
|
+
return {
|
|
369
|
+
element,
|
|
370
|
+
get layout() {
|
|
371
|
+
return layout;
|
|
372
|
+
},
|
|
373
|
+
update() {
|
|
374
|
+
layout = measure(source, options);
|
|
375
|
+
updateSkeleton(element, layout, options);
|
|
376
|
+
return layout;
|
|
377
|
+
},
|
|
378
|
+
destroy() {
|
|
379
|
+
element.remove();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// src/core/cache.ts
|
|
385
|
+
var LayoutCache = class {
|
|
386
|
+
constructor(max = 100) {
|
|
387
|
+
__publicField(this, "max", max);
|
|
388
|
+
__publicField(this, "map", /* @__PURE__ */ new Map());
|
|
389
|
+
if (!(Number.isInteger(max) && max > 0))
|
|
390
|
+
throw new RangeError("auto-skeleton: cache size must be a positive integer");
|
|
391
|
+
}
|
|
392
|
+
get size() {
|
|
393
|
+
return this.map.size;
|
|
394
|
+
}
|
|
395
|
+
has(key) {
|
|
396
|
+
return this.map.has(key);
|
|
397
|
+
}
|
|
398
|
+
get(key) {
|
|
399
|
+
const value = this.map.get(key);
|
|
400
|
+
if (value === void 0) return void 0;
|
|
401
|
+
this.map.delete(key);
|
|
402
|
+
this.map.set(key, value);
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
set(key, layout) {
|
|
406
|
+
if (this.map.has(key)) this.map.delete(key);
|
|
407
|
+
this.map.set(key, layout);
|
|
408
|
+
if (this.map.size > this.max) {
|
|
409
|
+
const oldest = this.map.keys().next().value;
|
|
410
|
+
if (oldest !== void 0) this.map.delete(oldest);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* The most recently stored layout whose key was built with
|
|
415
|
+
* `layoutCacheKey(name, …)`, regardless of width. Lets a wrapper paint
|
|
416
|
+
* something plausible on the very first frame and only re-measure when
|
|
417
|
+
* the width turns out to differ.
|
|
418
|
+
*/
|
|
419
|
+
latest(name) {
|
|
420
|
+
const prefix = `${name}@`;
|
|
421
|
+
let found;
|
|
422
|
+
for (const [key, value] of this.map) if (key.startsWith(prefix)) found = value;
|
|
423
|
+
return found;
|
|
424
|
+
}
|
|
425
|
+
delete(key) {
|
|
426
|
+
return this.map.delete(key);
|
|
427
|
+
}
|
|
428
|
+
clear() {
|
|
429
|
+
this.map.clear();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
function layoutCacheKey(name, width) {
|
|
433
|
+
return `${name}@${Math.round(width)}`;
|
|
434
|
+
}
|
|
435
|
+
var defaultLayoutCache = new LayoutCache();
|
|
436
|
+
|
|
437
|
+
export { BLOCK_CLASS, CONTAINER_CLASS, DATA_ATTRIBUTE, DEFAULT_MEASURE_OPTIONS, FADING_CLASS, LayoutCache, STYLE_ID, autoskeletonCSS, blockClassName, blockStyle, classify, containerStyle, createSkeleton, defaultLayoutCache, ensureStyles, isVisible, kindOf, layoutCacheKey, measure, mergeLineRects, radiusOf, renderSkeleton, resolveMeasureOptions, round, sameLine, scaleHeight, themeStyle, toRelative, union, updateSkeleton };
|
|
438
|
+
//# sourceMappingURL=chunk-VI2M6DZS.js.map
|
|
439
|
+
//# sourceMappingURL=chunk-VI2M6DZS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/classify.ts","../src/core/geometry.ts","../src/core/measure.ts","../src/core/styles.ts","../src/core/render.ts","../src/core/skeleton.ts","../src/core/cache.ts"],"names":[],"mappings":"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","file":"chunk-VI2M6DZS.js","sourcesContent":["import type { BlockKind, Classification, ResolvedMeasureOptions } from './types';\n\nconst MEDIA = new Set(['img', 'svg', 'video', 'canvas', 'picture', 'iframe', 'object', 'embed', 'audio']);\nconst CONTROL = new Set(['input', 'textarea', 'select', 'button', 'progress', 'meter']);\nconst IGNORED = new Set([\n 'script',\n 'style',\n 'template',\n 'noscript',\n 'br',\n 'wbr',\n 'head',\n 'meta',\n 'link',\n 'title',\n]);\nconst BLOCK_ROLES = new Set(['img', 'progressbar', 'slider', 'switch', 'checkbox', 'radio']);\n\nexport const DATA_ATTRIBUTE = 'data-auto-skeleton';\n\nfunction isTransparent(color: string): boolean {\n if (!color || color === 'transparent') return true;\n const match = /^rgba?\\(([^)]+)\\)$/.exec(color);\n if (!match) return false;\n const parts = match[1]!.split(/[\\s,/]+/).filter(Boolean);\n return parts.length === 4 && Number.parseFloat(parts[3]!) === 0;\n}\n\nfunction hasVisibleBorder(style: CSSStyleDeclaration): boolean {\n const widths = [\n style.borderTopWidth,\n style.borderRightWidth,\n style.borderBottomWidth,\n style.borderLeftWidth,\n ];\n return widths.some((w) => Number.parseFloat(w) > 0) && style.borderTopStyle !== 'none';\n}\n\nfunction isEmptyLeaf(element: Element): boolean {\n if (element.childElementCount > 0) return false;\n return !(element.textContent ?? '').trim();\n}\n\nexport function kindOf(element: Element): BlockKind {\n const tag = element.localName;\n if (MEDIA.has(tag) || element.getAttribute('role') === 'img') return 'media';\n if (CONTROL.has(tag)) return 'control';\n return 'box';\n}\n\n/**\n * Decide how to treat an element. Order of precedence:\n * 1. `options.classify` callback\n * 2. `data-auto-skeleton=\"skip|block|descend\"` attribute\n * 3. Built-in rules (hidden, replaced/media, form controls, small decorated boxes)\n */\nexport interface ClassifyContext {\n /**\n * The root passed to `measure`. Roots are often deliberately hidden (a\n * clone used only for measurement), so hidden-ness is ignored for them.\n */\n root?: boolean;\n /**\n * Computed `visibility` of the parent. A child that merely inherits\n * `hidden` from a hidden root is still measured; one that sets it itself\n * is skipped.\n */\n parentVisibility?: string;\n}\n\nexport function classify(\n element: Element,\n window: Window,\n options: ResolvedMeasureOptions,\n context: ClassifyContext = {},\n): Classification {\n const custom = options.classify?.(element);\n if (custom) return custom;\n\n const attr = element.getAttribute(DATA_ATTRIBUTE);\n if (attr === 'skip' || attr === 'block' || attr === 'descend') return attr;\n\n const tag = element.localName;\n if (IGNORED.has(tag)) return 'skip';\n\n const style = window.getComputedStyle(element);\n if (style.display === 'none') return 'skip';\n if (!context.root) {\n const hidden = style.visibility === 'hidden' || style.visibility === 'collapse';\n if (hidden && style.visibility !== context.parentVisibility) return 'skip';\n if (Number.parseFloat(style.opacity) === 0) return 'skip';\n }\n\n if (MEDIA.has(tag) || CONTROL.has(tag)) return 'block';\n const role = element.getAttribute('role');\n if (role && BLOCK_ROLES.has(role)) return 'block';\n\n const decorated =\n style.backgroundImage !== 'none' || !isTransparent(style.backgroundColor) || hasVisibleBorder(style);\n if (decorated) {\n // A decorated element with nothing inside is pure visual (a CSS-only\n // image, gradient, divider or placeholder) and is painted whole.\n if (isEmptyLeaf(element)) return 'block';\n // A decorated element no taller than a line or two with only text inside\n // is a chip, badge, tag or pill: one shape, not a text bar.\n const rect = element.getBoundingClientRect();\n if (rect.height <= options.maxBackgroundBlock) {\n if (rect.width <= options.maxBackgroundBlock) return 'block';\n if (rect.width <= options.maxBackgroundBlock * 4 && element.childElementCount === 0) return 'block';\n }\n }\n return 'descend';\n}\n\n/** Read the element's border radius as a single CSS shorthand value. */\nexport function radiusOf(style: CSSStyleDeclaration): string {\n const norm = (value: string | undefined): string => (!value || value === '0' ? '0px' : value);\n const tl = norm(style.borderTopLeftRadius);\n const tr = norm(style.borderTopRightRadius);\n const br = norm(style.borderBottomRightRadius);\n const bl = norm(style.borderBottomLeftRadius);\n if (tl === tr && tr === br && br === bl) return tl;\n return `${tl} ${tr} ${br} ${bl}`;\n}\n","export interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\n/** Round to two decimals so layouts serialize cleanly and compare stably. */\nexport function round(n: number): number {\n return Math.round(n * 100) / 100;\n}\n\n/** Convert a viewport-relative DOMRect into coordinates relative to `origin`. */\nexport function toRelative(rect: DOMRectReadOnly, origin: { left: number; top: number }): Rect {\n return {\n x: round(rect.left - origin.left),\n y: round(rect.top - origin.top),\n width: round(rect.width),\n height: round(rect.height),\n };\n}\n\n/** Shrink a rect vertically around its centre. */\nexport function scaleHeight(rect: Rect, scale: number): Rect {\n const height = round(rect.height * scale);\n return { ...rect, y: round(rect.y + (rect.height - height) / 2), height };\n}\n\n/** True when the two rects share at least half of the smaller one's vertical span. */\nexport function sameLine(a: Rect, b: Rect): boolean {\n const top = Math.max(a.y, b.y);\n const bottom = Math.min(a.y + a.height, b.y + b.height);\n const overlap = bottom - top;\n if (overlap <= 0) return false;\n return overlap >= Math.min(a.height, b.height) * 0.5;\n}\n\n/** Bounding box of two rects. */\nexport function union(a: Rect, b: Rect): Rect {\n const x = Math.min(a.x, b.x);\n const y = Math.min(a.y, b.y);\n const right = Math.max(a.x + a.width, b.x + b.width);\n const bottom = Math.max(a.y + a.height, b.y + b.height);\n return { x, y, width: round(right - x), height: round(bottom - y) };\n}\n\n/**\n * Merge text fragments that sit on the same line and are horizontally close\n * into a single bar. Fragments are first grouped by vertical overlap, then\n * merged left to right when the gap between them is at most `gap`.\n */\nexport function mergeLineRects(rects: readonly Rect[], gap: number): Rect[] {\n if (rects.length === 0) return [];\n const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);\n\n // Group into lines by vertical overlap with the running union of the line.\n const lines: Rect[][] = [];\n let current: Rect[] = [];\n let currentBox: Rect | null = null;\n for (const r of sorted) {\n if (currentBox && sameLine(currentBox, r)) {\n current.push(r);\n currentBox = union(currentBox, r);\n } else {\n if (current.length) lines.push(current);\n current = [r];\n currentBox = r;\n }\n }\n if (current.length) lines.push(current);\n\n const out: Rect[] = [];\n for (const line of lines) {\n line.sort((a, b) => a.x - b.x);\n let acc = line[0]!;\n for (let i = 1; i < line.length; i++) {\n const next = line[i]!;\n const distance = next.x - (acc.x + acc.width);\n if (distance <= gap) {\n acc = union(acc, next);\n } else {\n out.push(acc);\n acc = next;\n }\n }\n out.push(acc);\n }\n return out;\n}\n\n/** Drop rects that are too small to matter or that fall entirely outside the bounds. */\nexport function isVisible(rect: Rect, bounds: { width: number; height: number }, minSize: number): boolean {\n if (rect.width < minSize || rect.height < minSize) return false;\n if (rect.x + rect.width <= 0 || rect.y + rect.height <= 0) return false;\n if (rect.x >= bounds.width || rect.y >= bounds.height) return false;\n return true;\n}\n","import { classify, kindOf, radiusOf } from './classify';\nimport { isVisible, mergeLineRects, scaleHeight, toRelative, type Rect } from './geometry';\nimport type { MeasureOptions, ResolvedMeasureOptions, SkeletonBlock, SkeletonLayout } from './types';\n\nexport const DEFAULT_MEASURE_OPTIONS: ResolvedMeasureOptions = {\n textScale: 0.7,\n minSize: 2,\n maxBackgroundBlock: 64,\n mergeGap: 4,\n textRadius: '4px',\n maxBlocks: 400,\n classify: undefined,\n};\n\nexport function resolveMeasureOptions(options: MeasureOptions = {}): ResolvedMeasureOptions {\n const resolved: ResolvedMeasureOptions = { ...DEFAULT_MEASURE_OPTIONS };\n for (const key of Object.keys(options) as (keyof MeasureOptions)[]) {\n const value = options[key];\n if (value !== undefined) (resolved as Record<string, unknown>)[key] = value;\n }\n if (!(resolved.textScale > 0 && resolved.textScale <= 1)) {\n throw new RangeError(`auto-skeleton: textScale must be in (0, 1], got ${String(options.textScale)}`);\n }\n if (!(Number.isInteger(resolved.maxBlocks) && resolved.maxBlocks > 0)) {\n throw new RangeError(\n `auto-skeleton: maxBlocks must be a positive integer, got ${String(options.maxBlocks)}`,\n );\n }\n return resolved;\n}\n\n/**\n * Measure a rendered DOM subtree and describe it as a list of skeleton blocks.\n *\n * The root must be attached to a document and laid out (it may be\n * `visibility: hidden`, but not `display: none`). Coordinates in the result\n * are relative to the root's border box.\n */\nexport function measure(root: Element, options?: MeasureOptions): SkeletonLayout {\n const opts = resolveMeasureOptions(options);\n const win = root.ownerDocument.defaultView;\n if (!win) throw new Error('auto-skeleton: root element is not attached to a window');\n\n const rootRect = root.getBoundingClientRect();\n const origin = { left: rootRect.left, top: rootRect.top };\n const bounds = { width: rootRect.width, height: rootRect.height };\n\n const blocks: SkeletonBlock[] = [];\n const textRects: Rect[] = [];\n // Text rects are an upper bound on text bars (merging only reduces them),\n // so this is a conservative early-exit budget.\n const full = () => blocks.length + textRects.length >= opts.maxBlocks;\n\n const pushBlock = (element: Element) => {\n const rect = toRelative(element.getBoundingClientRect(), origin);\n if (!isVisible(rect, bounds, opts.minSize)) return;\n blocks.push({ ...rect, radius: radiusOf(win.getComputedStyle(element)), kind: kindOf(element) });\n };\n\n // Whitespace-only nodes are measured too: a space between two inline\n // elements has a real width and lets \"Hello <b>world</b>\" become one bar.\n // Collapsed whitespace (indentation between blocks) yields zero-size rects\n // that the size filter drops.\n const collectText = (node: Text) => {\n if (node.data.length === 0) return;\n const range = root.ownerDocument.createRange();\n range.selectNodeContents(node);\n for (const r of Array.from(range.getClientRects())) {\n const rect = toRelative(r, origin);\n if (isVisible(rect, bounds, opts.minSize)) textRects.push(rect);\n }\n range.detach();\n };\n\n const walk = (element: Element) => {\n const parentVisibility = win.getComputedStyle(element).visibility;\n for (const child of Array.from(element.childNodes)) {\n if (full()) return;\n if (child.nodeType === Node.TEXT_NODE) {\n collectText(child as Text);\n continue;\n }\n if (child.nodeType !== Node.ELEMENT_NODE) continue;\n const el = child as Element;\n const decision = classify(el, win, opts, { parentVisibility });\n if (decision === 'skip') continue;\n if (decision === 'block') {\n pushBlock(el);\n continue;\n }\n walk(el);\n }\n };\n\n // The root itself may be a single block (for example an <img>). Otherwise\n // its own box is never painted; only its contents are.\n const rootDecision = classify(root, win, opts, { root: true });\n if (rootDecision === 'block') {\n pushBlock(root);\n } else if (rootDecision === 'descend') {\n walk(root);\n }\n\n for (const line of mergeLineRects(textRects, opts.mergeGap)) {\n const scaled = scaleHeight(line, opts.textScale);\n blocks.push({ ...scaled, radius: opts.textRadius, kind: 'text' });\n }\n\n blocks.sort((a, b) => a.y - b.y || a.x - b.x);\n\n return {\n width: Math.round(bounds.width * 100) / 100,\n height: Math.round(bounds.height * 100) / 100,\n blocks: blocks.length > opts.maxBlocks ? blocks.slice(0, opts.maxBlocks) : blocks,\n };\n}\n","export const STYLE_ID = 'auto-skeleton-styles';\n\n/**\n * The default stylesheet. Exported so apps with a strict CSP can ship it in\n * their own CSS and pass `injectStyles: false`.\n *\n * Customise with CSS variables on any ancestor, or per instance through\n * the `color`, `highlight`, `duration` and `fade` options:\n * - `--auto-skeleton-color` base block colour\n * - `--auto-skeleton-highlight` shimmer highlight colour\n * - `--auto-skeleton-duration` one shimmer sweep\n * - `--auto-skeleton-fade` fade-out duration when content arrives\n *\n * The shimmer is a single `transform` animation per block pseudo-element,\n * offset so every block shows the same sweep. Transforms run on the\n * compositor, so an animating skeleton causes no style or layout work on\n * the main thread.\n */\nexport const autoskeletonCSS = `\n.auto-skeleton {\n position: relative;\n display: block;\n width: 100%;\n overflow: hidden;\n contain: layout style paint;\n --_c: var(--auto-skeleton-color, rgba(128, 128, 128, 0.18));\n --_h: var(--auto-skeleton-highlight, rgba(128, 128, 128, 0.34));\n}\n.auto-skeleton__block {\n position: absolute;\n overflow: hidden;\n pointer-events: none;\n background-color: var(--_c);\n}\n.auto-skeleton[data-animate=\"true\"] .auto-skeleton__block::after {\n content: \"\";\n position: absolute;\n top: 0;\n bottom: 0;\n left: calc(var(--_x) * -1);\n width: var(--auto-skeleton-w);\n background: linear-gradient(90deg, transparent 0%, var(--_h) 50%, transparent 100%);\n transform: translateX(-100%);\n animation: auto-skeleton-sweep var(--auto-skeleton-duration, 1.6s) linear infinite;\n}\n@keyframes auto-skeleton-sweep {\n to { transform: translateX(100%); }\n}\n.auto-skeleton--fading {\n opacity: 0;\n transition: opacity var(--auto-skeleton-fade, 200ms) ease-out;\n}\n@media (prefers-reduced-motion: reduce) {\n .auto-skeleton[data-animate=\"true\"] .auto-skeleton__block::after { animation: none; content: none; }\n}\n`.trim();\n\n/**\n * Insert the stylesheet into `doc` once. Safe to call repeatedly and from\n * multiple bundles: the tag is looked up by id. Returns the style element.\n */\nexport function ensureStyles(doc: Document = document, nonce?: string): HTMLStyleElement {\n const existing = doc.getElementById(STYLE_ID);\n if (existing instanceof HTMLStyleElement) return existing;\n const style = doc.createElement('style');\n style.id = STYLE_ID;\n if (nonce) style.setAttribute('nonce', nonce);\n style.textContent = autoskeletonCSS;\n (doc.head ?? doc.documentElement).appendChild(style);\n return style;\n}\n","import { ensureStyles } from './styles';\nimport type { RenderOptions, SkeletonBlock, SkeletonLayout, SkeletonTheme } from './types';\n\nexport const CONTAINER_CLASS = 'auto-skeleton';\nexport const BLOCK_CLASS = 'auto-skeleton__block';\nexport const FADING_CLASS = 'auto-skeleton--fading';\n\nfunction cssTime(value: number | string): string {\n return typeof value === 'number' ? `${value}ms` : value;\n}\n\n/** Inline custom properties for a theme. Empty when nothing is set. */\nexport function themeStyle(theme: SkeletonTheme = {}): Record<string, string> {\n const style: Record<string, string> = {};\n if (theme.color !== undefined) style['--auto-skeleton-color'] = theme.color;\n if (theme.highlight !== undefined) style['--auto-skeleton-highlight'] = theme.highlight;\n if (theme.duration !== undefined) style['--auto-skeleton-duration'] = cssTime(theme.duration);\n if (theme.fade !== undefined) style['--auto-skeleton-fade'] = cssTime(theme.fade);\n return style;\n}\n\n/** Inline style for the container. Shared by the DOM renderer and framework wrappers. */\nexport function containerStyle(layout: SkeletonLayout, theme?: SkeletonTheme): Record<string, string> {\n return {\n ...themeStyle(theme),\n height: `${layout.height}px`,\n '--auto-skeleton-w': `${layout.width}px`,\n };\n}\n\n/** Inline style for one block. Shared by the DOM renderer and framework wrappers. */\nexport function blockStyle(block: SkeletonBlock): Record<string, string> {\n return {\n left: `${block.x}px`,\n top: `${block.y}px`,\n width: `${block.width}px`,\n height: `${block.height}px`,\n borderRadius: block.radius,\n '--_x': `${block.x}px`,\n };\n}\n\nexport function blockClassName(block: SkeletonBlock): string {\n return `${BLOCK_CLASS} ${BLOCK_CLASS}--${block.kind}`;\n}\n\nfunction applyStyle(el: HTMLElement, style: Record<string, string>): void {\n for (const [key, value] of Object.entries(style)) {\n if (key.startsWith('--')) el.style.setProperty(key, value);\n else (el.style as unknown as Record<string, string>)[key] = value;\n }\n}\n\n/** Replace the container's blocks with those from a new layout. */\nexport function updateSkeleton(container: HTMLElement, layout: SkeletonLayout, theme?: SkeletonTheme): void {\n applyStyle(container, containerStyle(layout, theme));\n const doc = container.ownerDocument;\n const fragment = doc.createDocumentFragment();\n for (const block of layout.blocks) {\n const el = doc.createElement('div');\n el.className = blockClassName(block);\n el.setAttribute('aria-hidden', 'true');\n applyStyle(el, blockStyle(block));\n fragment.appendChild(el);\n }\n container.replaceChildren(fragment);\n}\n\n/**\n * Build a skeleton DOM element from a layout. The result is a `div` with\n * `role=\"status\"` and `aria-busy=\"true\"` that you can insert anywhere.\n */\nexport function renderSkeleton(layout: SkeletonLayout, options: RenderOptions = {}): HTMLElement {\n const doc = options.document ?? document;\n if (options.injectStyles !== false) ensureStyles(doc, options.nonce);\n\n const container = doc.createElement('div');\n container.className = options.className ? `${CONTAINER_CLASS} ${options.className}` : CONTAINER_CLASS;\n container.setAttribute('role', 'status');\n container.setAttribute('aria-busy', 'true');\n container.setAttribute('aria-label', options.label ?? 'Loading');\n container.dataset['animate'] = String(options.animate !== false);\n updateSkeleton(container, layout, options);\n return container;\n}\n","import { measure } from './measure';\nimport { renderSkeleton, updateSkeleton } from './render';\nimport type { MeasureOptions, RenderOptions, SkeletonLayout } from './types';\n\nexport interface SkeletonHandle {\n /** The skeleton element. Insert it wherever the content will appear. */\n readonly element: HTMLElement;\n /** The layout currently painted. */\n readonly layout: SkeletonLayout;\n /** Re-measure the source and repaint. Call after the container resizes. */\n update(): SkeletonLayout;\n /** Remove the element from the DOM. */\n destroy(): void;\n}\n\n/**\n * Measure `source` and return a live skeleton element for it. This is the\n * one-call vanilla API; frameworks wrappers compose `measure` and\n * `renderSkeleton` directly.\n */\nexport function createSkeleton(\n source: Element,\n options: MeasureOptions & RenderOptions = {},\n): SkeletonHandle {\n let layout = measure(source, options);\n const element = renderSkeleton(layout, options);\n return {\n element,\n get layout() {\n return layout;\n },\n update() {\n layout = measure(source, options);\n updateSkeleton(element, layout, options);\n return layout;\n },\n destroy() {\n element.remove();\n },\n };\n}\n","import type { SkeletonLayout } from './types';\n\n/**\n * A small LRU cache for layouts. Keyed by whatever the caller chooses;\n * `layoutCacheKey` builds a key from a name and a container width, which is\n * what determines a layout in practice.\n */\nexport class LayoutCache {\n private readonly map = new Map<string, SkeletonLayout>();\n\n constructor(private readonly max = 100) {\n if (!(Number.isInteger(max) && max > 0))\n throw new RangeError('auto-skeleton: cache size must be a positive integer');\n }\n\n get size(): number {\n return this.map.size;\n }\n\n has(key: string): boolean {\n return this.map.has(key);\n }\n\n get(key: string): SkeletonLayout | undefined {\n const value = this.map.get(key);\n if (value === undefined) return undefined;\n // Refresh recency.\n this.map.delete(key);\n this.map.set(key, value);\n return value;\n }\n\n set(key: string, layout: SkeletonLayout): void {\n if (this.map.has(key)) this.map.delete(key);\n this.map.set(key, layout);\n if (this.map.size > this.max) {\n const oldest = this.map.keys().next().value;\n if (oldest !== undefined) this.map.delete(oldest);\n }\n }\n\n /**\n * The most recently stored layout whose key was built with\n * `layoutCacheKey(name, …)`, regardless of width. Lets a wrapper paint\n * something plausible on the very first frame and only re-measure when\n * the width turns out to differ.\n */\n latest(name: string): SkeletonLayout | undefined {\n const prefix = `${name}@`;\n let found: SkeletonLayout | undefined;\n for (const [key, value] of this.map) if (key.startsWith(prefix)) found = value;\n return found;\n }\n\n delete(key: string): boolean {\n return this.map.delete(key);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n\nexport function layoutCacheKey(name: string, width: number): string {\n return `${name}@${Math.round(width)}`;\n}\n\n/** Shared default cache used by framework wrappers when no cache is supplied. */\nexport const defaultLayoutCache = new LayoutCache();\n"]}
|