@willa-ui/shared 0.0.3-alpha.f3c50c9 → 0.0.4
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 +23 -0
- package/dist/index.cjs +705 -1
- package/dist/index.d.cts +225 -3
- package/dist/index.global.js +50100 -1
- package/dist/index.js +833 -2
- package/dist/index.mjs +651 -3
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1,10 +1,40 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* @willa-ui/shared.js v0.0.
|
|
2
|
+
* @willa-ui/shared.js v0.0.4
|
|
3
3
|
* (c) 2026 chentao.arthur
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
+
//#region \0rolldown/runtime.js
|
|
7
|
+
var __create = Object.create;
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
12
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
15
|
+
key = keys[i];
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
17
|
+
get: ((k) => from[k]).bind(null, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
//#endregion
|
|
6
28
|
let react = require("react");
|
|
29
|
+
let highlight_js = require("highlight.js");
|
|
30
|
+
highlight_js = __toESM(highlight_js);
|
|
7
31
|
let aidly = require("aidly");
|
|
32
|
+
//#region src/css.ts
|
|
33
|
+
function formatCssSize(value) {
|
|
34
|
+
if (typeof value === "number") return Number.isFinite(value) ? `${value}px` : void 0;
|
|
35
|
+
if (typeof value === "string") return value.trim() || void 0;
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
8
38
|
//#region src/clipboard.ts
|
|
9
39
|
async function copyToClipboard(text) {
|
|
10
40
|
if (!text) return false;
|
|
@@ -30,6 +60,390 @@ async function copyToClipboard(text) {
|
|
|
30
60
|
}
|
|
31
61
|
}
|
|
32
62
|
//#endregion
|
|
63
|
+
//#region src/controllableState.ts
|
|
64
|
+
function useControllableState(options) {
|
|
65
|
+
const { value, defaultValue, onChange } = options;
|
|
66
|
+
const [uncontrolledValue, setUncontrolledValue] = (0, react.useState)(defaultValue);
|
|
67
|
+
const controlled = value !== void 0;
|
|
68
|
+
const currentValue = controlled ? value : uncontrolledValue;
|
|
69
|
+
return [
|
|
70
|
+
currentValue,
|
|
71
|
+
(0, react.useCallback)((nextValue) => {
|
|
72
|
+
const resolvedValue = resolveStateAction(nextValue, currentValue);
|
|
73
|
+
if (!controlled) setUncontrolledValue(resolvedValue);
|
|
74
|
+
onChange?.(resolvedValue);
|
|
75
|
+
}, [
|
|
76
|
+
controlled,
|
|
77
|
+
currentValue,
|
|
78
|
+
onChange
|
|
79
|
+
]),
|
|
80
|
+
controlled
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
const resolveStateAction = (value, previousValue) => {
|
|
84
|
+
if (typeof value === "function") return value(previousValue);
|
|
85
|
+
return value;
|
|
86
|
+
};
|
|
87
|
+
//#endregion
|
|
88
|
+
//#region src/viewport.ts
|
|
89
|
+
const MOBILE_BREAKPOINT = 640;
|
|
90
|
+
function isMobileViewport(viewportWidth) {
|
|
91
|
+
return viewportWidth <= 640;
|
|
92
|
+
}
|
|
93
|
+
function isMobile() {
|
|
94
|
+
if (typeof window === "undefined") return false;
|
|
95
|
+
return isMobileViewport(window.innerWidth);
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/copy.ts
|
|
99
|
+
function useCopyToClipboard(options = {}) {
|
|
100
|
+
const { resetDuration = 1200, onCopy } = options;
|
|
101
|
+
const [status, setStatus] = (0, react.useState)("idle");
|
|
102
|
+
const timerRef = (0, react.useRef)(null);
|
|
103
|
+
const clearTimer = (0, react.useCallback)(() => {
|
|
104
|
+
if (timerRef.current === null) return;
|
|
105
|
+
window.clearTimeout(timerRef.current);
|
|
106
|
+
timerRef.current = null;
|
|
107
|
+
}, []);
|
|
108
|
+
const startFeedback = (0, react.useCallback)((nextStatus, duration = resetDuration) => {
|
|
109
|
+
clearTimer();
|
|
110
|
+
setStatus(nextStatus);
|
|
111
|
+
if (nextStatus === "idle" || duration <= 0) return;
|
|
112
|
+
timerRef.current = window.setTimeout(() => {
|
|
113
|
+
setStatus("idle");
|
|
114
|
+
timerRef.current = null;
|
|
115
|
+
}, duration);
|
|
116
|
+
}, [clearTimer, resetDuration]);
|
|
117
|
+
const reset = (0, react.useCallback)(() => {
|
|
118
|
+
startFeedback("idle", 0);
|
|
119
|
+
}, [startFeedback]);
|
|
120
|
+
const copy = (0, react.useCallback)(async (text, actionOptions = {}) => {
|
|
121
|
+
startFeedback("idle", 0);
|
|
122
|
+
const ok = await copyToClipboard(text);
|
|
123
|
+
startFeedback(ok ? "copied" : "failed", actionOptions.resetDuration);
|
|
124
|
+
if (ok) {
|
|
125
|
+
onCopy?.(text);
|
|
126
|
+
actionOptions.onCopy?.(text);
|
|
127
|
+
}
|
|
128
|
+
return ok;
|
|
129
|
+
}, [onCopy, startFeedback]);
|
|
130
|
+
(0, react.useEffect)(() => {
|
|
131
|
+
return () => {
|
|
132
|
+
clearTimer();
|
|
133
|
+
};
|
|
134
|
+
}, [clearTimer]);
|
|
135
|
+
return {
|
|
136
|
+
status,
|
|
137
|
+
copied: status === "copied",
|
|
138
|
+
failed: status === "failed",
|
|
139
|
+
copy,
|
|
140
|
+
reset
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//#endregion
|
|
144
|
+
//#region src/codeHighlight.ts
|
|
145
|
+
const parseCodeMeta = (className) => {
|
|
146
|
+
const [rawLanguage = "text", rawMeta = ""] = (/language-([^\s]+)/.exec(className ?? "")?.[1] ?? "text").split("--meta-");
|
|
147
|
+
const highlightLines = /* @__PURE__ */ new Set();
|
|
148
|
+
const normalizedMeta = rawMeta.replace(/_/g, " ");
|
|
149
|
+
const showLineNumbers = /(?:^|[^A-Za-z0-9-])ln(?:$|[^A-Za-z0-9-])/.test(normalizedMeta);
|
|
150
|
+
for (const match of rawMeta.matchAll(/\{([^}]+)\}/g)) addCodeHighlightRange(highlightLines, match[1]);
|
|
151
|
+
return {
|
|
152
|
+
rawLanguage,
|
|
153
|
+
highlightLines,
|
|
154
|
+
showLineNumbers
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
const normalizeHljsLanguage = (language) => {
|
|
158
|
+
const key = language.toLowerCase();
|
|
159
|
+
return {
|
|
160
|
+
c: "c",
|
|
161
|
+
cc: "cpp",
|
|
162
|
+
"c++": "cpp",
|
|
163
|
+
cpp: "cpp",
|
|
164
|
+
cxx: "cpp",
|
|
165
|
+
css: "css",
|
|
166
|
+
go: "go",
|
|
167
|
+
golang: "go",
|
|
168
|
+
html: "xml",
|
|
169
|
+
js: "javascript",
|
|
170
|
+
jsx: "javascript",
|
|
171
|
+
rs: "rust",
|
|
172
|
+
rust: "rust",
|
|
173
|
+
ts: "typescript",
|
|
174
|
+
tsx: "typescript",
|
|
175
|
+
sh: "bash",
|
|
176
|
+
bash: "bash",
|
|
177
|
+
shell: "bash",
|
|
178
|
+
yml: "yaml",
|
|
179
|
+
md: "markdown"
|
|
180
|
+
}[key] ?? key;
|
|
181
|
+
};
|
|
182
|
+
const highlightCodeToHtml = (code, rawLanguage) => {
|
|
183
|
+
const language = normalizeHljsLanguage(rawLanguage);
|
|
184
|
+
if (highlight_js.default.getLanguage(language)) return {
|
|
185
|
+
html: highlight_js.default.highlight(code, { language }).value,
|
|
186
|
+
display: rawLanguage
|
|
187
|
+
};
|
|
188
|
+
const result = highlight_js.default.highlightAuto(code);
|
|
189
|
+
return {
|
|
190
|
+
html: result.value,
|
|
191
|
+
display: result.language ?? rawLanguage
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
const createCodeHighlightLines = (ranges) => {
|
|
195
|
+
const highlightLines = /* @__PURE__ */ new Set();
|
|
196
|
+
for (const range of ranges ?? []) if (typeof range === "number") {
|
|
197
|
+
if (!Number.isInteger(range) || range <= 0) continue;
|
|
198
|
+
highlightLines.add(range);
|
|
199
|
+
} else addCodeHighlightRange(highlightLines, range.join("-"));
|
|
200
|
+
return highlightLines;
|
|
201
|
+
};
|
|
202
|
+
const addCodeHighlightRange = (highlightLines, value) => {
|
|
203
|
+
for (const part of value.split(",")) {
|
|
204
|
+
const rangeMatch = /^\s*(\d+)(?:-(\d+))?\s*$/.exec(part);
|
|
205
|
+
if (!rangeMatch) continue;
|
|
206
|
+
const start = Number(rangeMatch[1]);
|
|
207
|
+
const end = Number(rangeMatch[2] ?? rangeMatch[1]);
|
|
208
|
+
if (!Number.isInteger(start) || !Number.isInteger(end)) continue;
|
|
209
|
+
for (let line = start; line <= end; line += 1) if (line > 0) highlightLines.add(line);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
//#endregion
|
|
213
|
+
//#region src/dom.ts
|
|
214
|
+
const focusableSelector = [
|
|
215
|
+
"a[href]",
|
|
216
|
+
"button:not([disabled])",
|
|
217
|
+
"textarea:not([disabled])",
|
|
218
|
+
"input:not([disabled])",
|
|
219
|
+
"select:not([disabled])",
|
|
220
|
+
"[tabindex]:not([tabindex='-1'])"
|
|
221
|
+
].join(",");
|
|
222
|
+
function getFocusableElements(container) {
|
|
223
|
+
return Array.from(container.querySelectorAll(focusableSelector));
|
|
224
|
+
}
|
|
225
|
+
//#endregion
|
|
226
|
+
//#region src/number.ts
|
|
227
|
+
function clampNumber(value, min, max) {
|
|
228
|
+
if (max < min) return min;
|
|
229
|
+
return Math.min(Math.max(value, min), max);
|
|
230
|
+
}
|
|
231
|
+
function createNumberRange(start, end) {
|
|
232
|
+
if (end < start) return [];
|
|
233
|
+
return Array.from({ length: end - start + 1 }, (_, index) => start + index);
|
|
234
|
+
}
|
|
235
|
+
//#endregion
|
|
236
|
+
//#region src/floating.ts
|
|
237
|
+
function getFloatingPanelPosition(options) {
|
|
238
|
+
const { anchorRect, anchorPoint, floatingRect, side = "bottom", align = "start", offset = 0, viewportWidth, viewportHeight, viewportPadding = 8, matchAnchorMinWidth = false } = options;
|
|
239
|
+
const resolvedViewportWidth = viewportWidth ?? (typeof window === "undefined" ? 0 : window.innerWidth);
|
|
240
|
+
const resolvedViewportHeight = viewportHeight ?? (typeof window === "undefined" ? 0 : window.innerHeight);
|
|
241
|
+
const floatingWidth = floatingRect.width;
|
|
242
|
+
const floatingHeight = floatingRect.height;
|
|
243
|
+
const basePosition = anchorPoint ? {
|
|
244
|
+
top: anchorPoint.y,
|
|
245
|
+
left: anchorPoint.x
|
|
246
|
+
} : getAnchoredPanelPosition({
|
|
247
|
+
anchorRect: anchorRect ?? createPointRect({
|
|
248
|
+
x: 0,
|
|
249
|
+
y: 0
|
|
250
|
+
}),
|
|
251
|
+
floatingRect,
|
|
252
|
+
side,
|
|
253
|
+
align,
|
|
254
|
+
offset
|
|
255
|
+
});
|
|
256
|
+
const maxLeft = Math.max(viewportPadding, resolvedViewportWidth - floatingWidth - viewportPadding);
|
|
257
|
+
const maxTop = Math.max(viewportPadding, resolvedViewportHeight - floatingHeight - viewportPadding);
|
|
258
|
+
const position = {
|
|
259
|
+
top: clampNumber(basePosition.top, viewportPadding, maxTop),
|
|
260
|
+
left: clampNumber(basePosition.left, viewportPadding, maxLeft)
|
|
261
|
+
};
|
|
262
|
+
if (anchorRect && matchAnchorMinWidth) position.minWidth = Math.min(anchorRect.width, resolvedViewportWidth - viewportPadding * 2);
|
|
263
|
+
return position;
|
|
264
|
+
}
|
|
265
|
+
const getAnchoredPanelPosition = (options) => {
|
|
266
|
+
const { anchorRect, floatingRect, side, align, offset } = options;
|
|
267
|
+
const centerLeft = anchorRect.left + anchorRect.width / 2 - floatingRect.width / 2;
|
|
268
|
+
const centerTop = anchorRect.top + anchorRect.height / 2 - floatingRect.height / 2;
|
|
269
|
+
if (side === "top" || side === "bottom") return {
|
|
270
|
+
top: side === "top" ? anchorRect.top - floatingRect.height - offset : anchorRect.bottom + offset,
|
|
271
|
+
left: getAlignedPosition({
|
|
272
|
+
start: anchorRect.left,
|
|
273
|
+
end: anchorRect.right,
|
|
274
|
+
size: floatingRect.width,
|
|
275
|
+
center: centerLeft,
|
|
276
|
+
align
|
|
277
|
+
})
|
|
278
|
+
};
|
|
279
|
+
return {
|
|
280
|
+
top: getAlignedPosition({
|
|
281
|
+
start: anchorRect.top,
|
|
282
|
+
end: anchorRect.bottom,
|
|
283
|
+
size: floatingRect.height,
|
|
284
|
+
center: centerTop,
|
|
285
|
+
align
|
|
286
|
+
}),
|
|
287
|
+
left: side === "left" ? anchorRect.left - floatingRect.width - offset : anchorRect.right + offset
|
|
288
|
+
};
|
|
289
|
+
};
|
|
290
|
+
const getAlignedPosition = (options) => {
|
|
291
|
+
const { start, end, size, center, align } = options;
|
|
292
|
+
if (align === "start") return start;
|
|
293
|
+
if (align === "end") return end - size;
|
|
294
|
+
return center;
|
|
295
|
+
};
|
|
296
|
+
const createPointRect = (point) => ({
|
|
297
|
+
top: point.y,
|
|
298
|
+
right: point.x,
|
|
299
|
+
bottom: point.y,
|
|
300
|
+
left: point.x,
|
|
301
|
+
width: 0,
|
|
302
|
+
height: 0
|
|
303
|
+
});
|
|
304
|
+
//#endregion
|
|
305
|
+
//#region src/floatingLayer.ts
|
|
306
|
+
function useFloatingLayer(options) {
|
|
307
|
+
const { open, triggerRef, floatingRef, anchorRect, anchorPoint, getAnchorRect, getAnchorPoint, side = "bottom", align = "start", offset = 0, viewportPadding = 8, minWidth, fallbackWidth, fallbackHeight = 0, matchAnchorWidth = false, matchAnchorMinWidth = false, fullWidthBelow, applyResolvedWidth = false, flipToFit = false, outsideRefs, closeOnOutsidePointerDown = true, restoreFocus = false, onClose, onOpenAutoFocus } = options;
|
|
308
|
+
const [position, setPosition] = (0, react.useState)();
|
|
309
|
+
const previousFocusRef = (0, react.useRef)(null);
|
|
310
|
+
const updatePosition = (0, react.useCallback)(() => {
|
|
311
|
+
const triggerElement = triggerRef?.current;
|
|
312
|
+
const floatingElement = floatingRef.current;
|
|
313
|
+
if (!floatingElement || typeof window === "undefined") return;
|
|
314
|
+
const resolvedAnchorRect = getAnchorRect?.() ?? anchorRect ?? triggerElement?.getBoundingClientRect();
|
|
315
|
+
const resolvedAnchorPoint = getAnchorPoint?.() ?? anchorPoint ?? null;
|
|
316
|
+
const viewportWidth = window.innerWidth;
|
|
317
|
+
const maxWidth = Math.max(0, viewportWidth - viewportPadding * 2);
|
|
318
|
+
const rawFloatingWidth = floatingElement.offsetWidth || resolvedAnchorRect?.width || fallbackWidth || 0;
|
|
319
|
+
const floatingHeight = floatingElement.offsetHeight || fallbackHeight;
|
|
320
|
+
const resolvedSide = flipToFit && resolvedAnchorRect && !resolvedAnchorPoint ? getFloatingLayerFitSide({
|
|
321
|
+
anchorRect: resolvedAnchorRect,
|
|
322
|
+
floatingHeight,
|
|
323
|
+
side,
|
|
324
|
+
viewportHeight: window.innerHeight,
|
|
325
|
+
viewportPadding
|
|
326
|
+
}) : side;
|
|
327
|
+
const floatingWidth = resolveFloatingLayerWidth({
|
|
328
|
+
anchorWidth: resolvedAnchorRect?.width,
|
|
329
|
+
floatingWidth: rawFloatingWidth,
|
|
330
|
+
fullWidthBelow,
|
|
331
|
+
matchAnchorWidth,
|
|
332
|
+
maxWidth,
|
|
333
|
+
minWidth,
|
|
334
|
+
viewportWidth
|
|
335
|
+
});
|
|
336
|
+
setPosition({
|
|
337
|
+
...getFloatingPanelPosition({
|
|
338
|
+
anchorPoint: resolvedAnchorPoint ?? void 0,
|
|
339
|
+
anchorRect: resolvedAnchorPoint ? void 0 : resolvedAnchorRect,
|
|
340
|
+
floatingRect: {
|
|
341
|
+
width: floatingWidth,
|
|
342
|
+
height: floatingHeight
|
|
343
|
+
},
|
|
344
|
+
side: resolvedSide,
|
|
345
|
+
align,
|
|
346
|
+
offset,
|
|
347
|
+
viewportPadding,
|
|
348
|
+
matchAnchorMinWidth: matchAnchorMinWidth && !matchAnchorWidth && !resolvedAnchorPoint
|
|
349
|
+
}),
|
|
350
|
+
anchorRect: resolvedAnchorRect ?? void 0,
|
|
351
|
+
width: applyResolvedWidth || matchAnchorWidth || minWidth !== void 0 ? floatingWidth : void 0
|
|
352
|
+
});
|
|
353
|
+
}, [
|
|
354
|
+
align,
|
|
355
|
+
anchorRect,
|
|
356
|
+
anchorPoint,
|
|
357
|
+
fallbackHeight,
|
|
358
|
+
fallbackWidth,
|
|
359
|
+
flipToFit,
|
|
360
|
+
floatingRef,
|
|
361
|
+
fullWidthBelow,
|
|
362
|
+
getAnchorRect,
|
|
363
|
+
getAnchorPoint,
|
|
364
|
+
matchAnchorMinWidth,
|
|
365
|
+
matchAnchorWidth,
|
|
366
|
+
minWidth,
|
|
367
|
+
offset,
|
|
368
|
+
applyResolvedWidth,
|
|
369
|
+
side,
|
|
370
|
+
triggerRef,
|
|
371
|
+
viewportPadding
|
|
372
|
+
]);
|
|
373
|
+
(0, react.useEffect)(() => {
|
|
374
|
+
if (!open || typeof window === "undefined") return;
|
|
375
|
+
const ownerDocument = floatingRef.current?.ownerDocument ?? triggerRef?.current?.ownerDocument ?? document;
|
|
376
|
+
if (restoreFocus) previousFocusRef.current = ownerDocument.activeElement instanceof HTMLElement ? ownerDocument.activeElement : null;
|
|
377
|
+
updatePosition();
|
|
378
|
+
const frame = window.requestAnimationFrame(updatePosition);
|
|
379
|
+
const focusTimer = onOpenAutoFocus === void 0 ? void 0 : window.setTimeout(onOpenAutoFocus, 0);
|
|
380
|
+
const handlePointerDown = (event) => {
|
|
381
|
+
if (!closeOnOutsidePointerDown) return;
|
|
382
|
+
const target = event.target;
|
|
383
|
+
if (!(target instanceof Node)) return;
|
|
384
|
+
if (containsPointerTarget(target, [
|
|
385
|
+
triggerRef,
|
|
386
|
+
floatingRef,
|
|
387
|
+
...outsideRefs ?? []
|
|
388
|
+
])) return;
|
|
389
|
+
onClose?.();
|
|
390
|
+
};
|
|
391
|
+
const handleWindowUpdate = () => {
|
|
392
|
+
updatePosition();
|
|
393
|
+
};
|
|
394
|
+
ownerDocument.addEventListener("pointerdown", handlePointerDown);
|
|
395
|
+
window.addEventListener("resize", handleWindowUpdate);
|
|
396
|
+
window.addEventListener("scroll", handleWindowUpdate, true);
|
|
397
|
+
return () => {
|
|
398
|
+
window.cancelAnimationFrame(frame);
|
|
399
|
+
if (focusTimer !== void 0) window.clearTimeout(focusTimer);
|
|
400
|
+
ownerDocument.removeEventListener("pointerdown", handlePointerDown);
|
|
401
|
+
window.removeEventListener("resize", handleWindowUpdate);
|
|
402
|
+
window.removeEventListener("scroll", handleWindowUpdate, true);
|
|
403
|
+
if (restoreFocus) {
|
|
404
|
+
previousFocusRef.current?.focus();
|
|
405
|
+
previousFocusRef.current = null;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}, [
|
|
409
|
+
closeOnOutsidePointerDown,
|
|
410
|
+
floatingRef,
|
|
411
|
+
onClose,
|
|
412
|
+
onOpenAutoFocus,
|
|
413
|
+
open,
|
|
414
|
+
outsideRefs,
|
|
415
|
+
restoreFocus,
|
|
416
|
+
triggerRef,
|
|
417
|
+
updatePosition
|
|
418
|
+
]);
|
|
419
|
+
(0, react.useEffect)(() => {
|
|
420
|
+
if (open) return;
|
|
421
|
+
setPosition(void 0);
|
|
422
|
+
}, [open]);
|
|
423
|
+
return {
|
|
424
|
+
position,
|
|
425
|
+
updatePosition
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
const getFloatingLayerFitSide = (options) => {
|
|
429
|
+
const { anchorRect, floatingHeight, side, viewportHeight, viewportPadding } = options;
|
|
430
|
+
if (side !== "top" && side !== "bottom") return side;
|
|
431
|
+
const topSpace = anchorRect.top - viewportPadding;
|
|
432
|
+
const bottomSpace = viewportHeight - anchorRect.bottom - viewportPadding;
|
|
433
|
+
if (side === "top") return topSpace >= floatingHeight || topSpace >= bottomSpace ? "top" : "bottom";
|
|
434
|
+
return bottomSpace >= floatingHeight || bottomSpace >= topSpace ? "bottom" : "top";
|
|
435
|
+
};
|
|
436
|
+
const resolveFloatingLayerWidth = (options) => {
|
|
437
|
+
const { anchorWidth, floatingWidth, fullWidthBelow, matchAnchorWidth, maxWidth, minWidth, viewportWidth } = options;
|
|
438
|
+
if (fullWidthBelow !== void 0 && viewportWidth <= fullWidthBelow) return maxWidth;
|
|
439
|
+
if (matchAnchorWidth && anchorWidth !== void 0) return Math.min(Math.max(anchorWidth, minWidth ?? anchorWidth), maxWidth);
|
|
440
|
+
if (minWidth !== void 0) return Math.min(Math.max(floatingWidth, minWidth), maxWidth);
|
|
441
|
+
return floatingWidth;
|
|
442
|
+
};
|
|
443
|
+
const containsPointerTarget = (target, refs) => {
|
|
444
|
+
return refs.some((ref) => ref?.current?.contains(target));
|
|
445
|
+
};
|
|
446
|
+
//#endregion
|
|
33
447
|
//#region src/nodes.ts
|
|
34
448
|
const toNodeArray = (children) => {
|
|
35
449
|
return ((0, aidly.isArray)(children) ? children : [children]).filter((item) => {
|
|
@@ -47,6 +461,184 @@ function isMediaOnlyParagraph(children) {
|
|
|
47
461
|
return Boolean(node.type.__willaMediaElement);
|
|
48
462
|
}
|
|
49
463
|
//#endregion
|
|
464
|
+
//#region src/refs.ts
|
|
465
|
+
function assignRef(ref, value) {
|
|
466
|
+
if (!ref) return;
|
|
467
|
+
if (typeof ref === "function") {
|
|
468
|
+
ref(value);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
ref.current = value;
|
|
472
|
+
}
|
|
473
|
+
function composeRefs(...refs) {
|
|
474
|
+
return (value) => {
|
|
475
|
+
refs.forEach((ref) => assignRef(ref, value));
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
//#region src/media.ts
|
|
480
|
+
function resolveMediaAsset(props, assetPath) {
|
|
481
|
+
if (!assetPath) return void 0;
|
|
482
|
+
return props.resolveAssetUrl ? props.resolveAssetUrl(props.articleSourcePath ?? "", assetPath) : assetPath;
|
|
483
|
+
}
|
|
484
|
+
function resolveMediaVolume(volume) {
|
|
485
|
+
if (volume === void 0) return void 0;
|
|
486
|
+
if (!Number.isFinite(volume)) return void 0;
|
|
487
|
+
return clampNumber(volume, 0, 1);
|
|
488
|
+
}
|
|
489
|
+
//#endregion
|
|
490
|
+
//#region src/file.ts
|
|
491
|
+
const createObjectFileItem = (file) => {
|
|
492
|
+
return {
|
|
493
|
+
id: `${file.name}-${file.size}-${file.lastModified}-${Math.random().toString(36).slice(2)}`,
|
|
494
|
+
file,
|
|
495
|
+
url: URL.createObjectURL(file),
|
|
496
|
+
kind: resolveFileKind(file)
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
const resolveFileKind = (file) => {
|
|
500
|
+
if (file.type.startsWith("image/")) return "image";
|
|
501
|
+
if (file.type.startsWith("audio/")) return "audio";
|
|
502
|
+
if (file.type.startsWith("video/")) return "video";
|
|
503
|
+
return "file";
|
|
504
|
+
};
|
|
505
|
+
const resolveFilePreviewType = (options) => {
|
|
506
|
+
if (options.type !== "auto") return options.type;
|
|
507
|
+
const mimeType = options.mimeType?.toLowerCase() ?? "";
|
|
508
|
+
const extension = getFileExtension(options.name);
|
|
509
|
+
if (mimeType.startsWith("image/") || imageExtensions.has(extension)) return "image";
|
|
510
|
+
if (mimeType.startsWith("video/") || videoExtensions.has(extension)) return "video";
|
|
511
|
+
if (mimeType.startsWith("audio/") || audioExtensions.has(extension)) return "audio";
|
|
512
|
+
if (mimeType === "application/pdf" || extension === "pdf") return "pdf";
|
|
513
|
+
if (mimeType === "text/csv" || extension === "csv") return "csv";
|
|
514
|
+
if (codeExtensions.has(extension)) return "code";
|
|
515
|
+
if (mimeType.startsWith("text/") || textExtensions.has(extension)) return "text";
|
|
516
|
+
return "download";
|
|
517
|
+
};
|
|
518
|
+
const getFileExtension = (name) => {
|
|
519
|
+
return name.trim().toLowerCase().split(".").filter(Boolean).pop() ?? "";
|
|
520
|
+
};
|
|
521
|
+
const getFileCodeLanguage = (name) => {
|
|
522
|
+
const extension = getFileExtension(name);
|
|
523
|
+
return codeLanguageByExtension[extension] ?? extension;
|
|
524
|
+
};
|
|
525
|
+
const resolveFileKindLabel = (kind) => {
|
|
526
|
+
if (kind === "image") return "图片";
|
|
527
|
+
if (kind === "audio") return "音频";
|
|
528
|
+
if (kind === "video") return "视频";
|
|
529
|
+
return "文件";
|
|
530
|
+
};
|
|
531
|
+
const formatFileSize = (size) => {
|
|
532
|
+
if (size <= 0) return "0 B";
|
|
533
|
+
const units = [
|
|
534
|
+
"B",
|
|
535
|
+
"KB",
|
|
536
|
+
"MB",
|
|
537
|
+
"GB"
|
|
538
|
+
];
|
|
539
|
+
const unitIndex = Math.min(Math.floor(Math.log(size) / Math.log(1024)), units.length - 1);
|
|
540
|
+
const value = size / 1024 ** unitIndex;
|
|
541
|
+
const fractionDigits = value >= 10 || unitIndex === 0 ? 0 : 1;
|
|
542
|
+
return `${value.toFixed(fractionDigits)} ${units[unitIndex]}`;
|
|
543
|
+
};
|
|
544
|
+
const normalizeFileProgress = (progress) => {
|
|
545
|
+
if (typeof progress !== "number" || Number.isNaN(progress)) return;
|
|
546
|
+
return Math.min(100, Math.max(0, progress));
|
|
547
|
+
};
|
|
548
|
+
const resolveFilePreviewMode = (itemMode, fallbackMode) => itemMode ?? fallbackMode;
|
|
549
|
+
const canOpenFilePreviewDialog = (options) => {
|
|
550
|
+
const { disabled, href, previewMode, status = "ready" } = options;
|
|
551
|
+
return previewMode === "dialog" && Boolean(href) && status === "ready" && !disabled;
|
|
552
|
+
};
|
|
553
|
+
const imageExtensions = new Set([
|
|
554
|
+
"avif",
|
|
555
|
+
"gif",
|
|
556
|
+
"jpeg",
|
|
557
|
+
"jpg",
|
|
558
|
+
"png",
|
|
559
|
+
"webp"
|
|
560
|
+
]);
|
|
561
|
+
const videoExtensions = new Set([
|
|
562
|
+
"mov",
|
|
563
|
+
"mp4",
|
|
564
|
+
"webm"
|
|
565
|
+
]);
|
|
566
|
+
const audioExtensions = new Set([
|
|
567
|
+
"aac",
|
|
568
|
+
"flac",
|
|
569
|
+
"m4a",
|
|
570
|
+
"mp3",
|
|
571
|
+
"ogg",
|
|
572
|
+
"wav"
|
|
573
|
+
]);
|
|
574
|
+
const codeExtensions = new Set([
|
|
575
|
+
"bash",
|
|
576
|
+
"c",
|
|
577
|
+
"cpp",
|
|
578
|
+
"css",
|
|
579
|
+
"diff",
|
|
580
|
+
"go",
|
|
581
|
+
"html",
|
|
582
|
+
"java",
|
|
583
|
+
"js",
|
|
584
|
+
"json",
|
|
585
|
+
"jsx",
|
|
586
|
+
"md",
|
|
587
|
+
"py",
|
|
588
|
+
"rs",
|
|
589
|
+
"sh",
|
|
590
|
+
"sql",
|
|
591
|
+
"ts",
|
|
592
|
+
"tsx",
|
|
593
|
+
"xml",
|
|
594
|
+
"yaml",
|
|
595
|
+
"yml"
|
|
596
|
+
]);
|
|
597
|
+
const textExtensions = new Set(["log", "txt"]);
|
|
598
|
+
const codeLanguageByExtension = {
|
|
599
|
+
bash: "bash",
|
|
600
|
+
c: "c",
|
|
601
|
+
cpp: "cpp",
|
|
602
|
+
css: "css",
|
|
603
|
+
diff: "diff",
|
|
604
|
+
go: "go",
|
|
605
|
+
html: "html",
|
|
606
|
+
java: "java",
|
|
607
|
+
js: "javascript",
|
|
608
|
+
json: "json",
|
|
609
|
+
jsx: "jsx",
|
|
610
|
+
md: "markdown",
|
|
611
|
+
py: "python",
|
|
612
|
+
rs: "rust",
|
|
613
|
+
sh: "bash",
|
|
614
|
+
sql: "sql",
|
|
615
|
+
ts: "typescript",
|
|
616
|
+
tsx: "tsx",
|
|
617
|
+
xml: "xml",
|
|
618
|
+
yaml: "yaml",
|
|
619
|
+
yml: "yaml"
|
|
620
|
+
};
|
|
621
|
+
//#endregion
|
|
622
|
+
//#region src/request.ts
|
|
623
|
+
const pendingJsonRequests = /* @__PURE__ */ new Map();
|
|
624
|
+
function isAbortError(error) {
|
|
625
|
+
return error !== null && typeof error === "object" && "name" in error && error.name === "AbortError";
|
|
626
|
+
}
|
|
627
|
+
async function requestJson(input, options = {}) {
|
|
628
|
+
const { createError, dedupeKey, ...requestInit } = options;
|
|
629
|
+
const cachedRequest = dedupeKey ? pendingJsonRequests.get(dedupeKey) : null;
|
|
630
|
+
if (cachedRequest) return cachedRequest;
|
|
631
|
+
const request = fetch(input, requestInit).then(async (response) => {
|
|
632
|
+
if (!response.ok) throw createError ? await createError(response) : /* @__PURE__ */ new Error(`Request failed with status ${response.status}.`);
|
|
633
|
+
return response.json();
|
|
634
|
+
});
|
|
635
|
+
if (dedupeKey) {
|
|
636
|
+
pendingJsonRequests.set(dedupeKey, request);
|
|
637
|
+
request.then(() => pendingJsonRequests.delete(dedupeKey), () => pendingJsonRequests.delete(dedupeKey));
|
|
638
|
+
}
|
|
639
|
+
return request;
|
|
640
|
+
}
|
|
641
|
+
//#endregion
|
|
50
642
|
//#region src/heading.ts
|
|
51
643
|
const toHeadingSlug = (value) => {
|
|
52
644
|
return value.trim().replace(/\[[^\]]*\]\([^)]*\)/g, (m) => {
|
|
@@ -94,8 +686,120 @@ function extractHeadings(source) {
|
|
|
94
686
|
return headings;
|
|
95
687
|
}
|
|
96
688
|
//#endregion
|
|
689
|
+
//#region src/virtualScroll.ts
|
|
690
|
+
const getVirtualScrollWindow = (options) => {
|
|
691
|
+
const { itemCount, itemHeight, overscan, scrollTop, viewportHeight } = options;
|
|
692
|
+
if (itemCount <= 0) return {
|
|
693
|
+
startIndex: 0,
|
|
694
|
+
endIndex: 0,
|
|
695
|
+
paddingTop: 0,
|
|
696
|
+
paddingBottom: 0,
|
|
697
|
+
totalHeight: 0
|
|
698
|
+
};
|
|
699
|
+
const safeItemHeight = Math.max(1, itemHeight);
|
|
700
|
+
const visibleCount = Math.ceil(Math.max(0, viewportHeight) / safeItemHeight);
|
|
701
|
+
const startIndex = clampNumber(Math.floor(scrollTop / safeItemHeight) - overscan, 0, Math.max(itemCount - 1, 0));
|
|
702
|
+
const endIndex = clampNumber(startIndex + visibleCount + overscan * 2, 0, itemCount);
|
|
703
|
+
return {
|
|
704
|
+
startIndex,
|
|
705
|
+
endIndex,
|
|
706
|
+
paddingTop: startIndex * safeItemHeight,
|
|
707
|
+
paddingBottom: Math.max(0, itemCount - endIndex) * safeItemHeight,
|
|
708
|
+
totalHeight: itemCount * safeItemHeight
|
|
709
|
+
};
|
|
710
|
+
};
|
|
711
|
+
const useVirtualScrollWindow = (options) => {
|
|
712
|
+
const { enabled = false, itemCount, itemHeight, overscan = 4, container } = options;
|
|
713
|
+
const [metrics, setMetrics] = (0, react.useState)({
|
|
714
|
+
scrollTop: 0,
|
|
715
|
+
viewportHeight: 0
|
|
716
|
+
});
|
|
717
|
+
(0, react.useEffect)(() => {
|
|
718
|
+
if (!enabled || !container) return;
|
|
719
|
+
let frame = 0;
|
|
720
|
+
const update = () => {
|
|
721
|
+
frame = 0;
|
|
722
|
+
setMetrics({
|
|
723
|
+
scrollTop: container.scrollTop,
|
|
724
|
+
viewportHeight: container.clientHeight
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
const schedule = () => {
|
|
728
|
+
if (frame) return;
|
|
729
|
+
frame = window.requestAnimationFrame(update);
|
|
730
|
+
};
|
|
731
|
+
update();
|
|
732
|
+
container.addEventListener("scroll", schedule, { passive: true });
|
|
733
|
+
const resizeObserver = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(schedule);
|
|
734
|
+
resizeObserver?.observe(container);
|
|
735
|
+
window.addEventListener("resize", schedule);
|
|
736
|
+
return () => {
|
|
737
|
+
if (frame) window.cancelAnimationFrame(frame);
|
|
738
|
+
container.removeEventListener("scroll", schedule);
|
|
739
|
+
resizeObserver?.disconnect();
|
|
740
|
+
window.removeEventListener("resize", schedule);
|
|
741
|
+
};
|
|
742
|
+
}, [container, enabled]);
|
|
743
|
+
return (0, react.useMemo)(() => {
|
|
744
|
+
if (!enabled) return {
|
|
745
|
+
startIndex: 0,
|
|
746
|
+
endIndex: itemCount,
|
|
747
|
+
paddingTop: 0,
|
|
748
|
+
paddingBottom: 0,
|
|
749
|
+
totalHeight: itemCount * itemHeight
|
|
750
|
+
};
|
|
751
|
+
return getVirtualScrollWindow({
|
|
752
|
+
itemCount,
|
|
753
|
+
itemHeight,
|
|
754
|
+
overscan,
|
|
755
|
+
scrollTop: metrics.scrollTop,
|
|
756
|
+
viewportHeight: metrics.viewportHeight
|
|
757
|
+
});
|
|
758
|
+
}, [
|
|
759
|
+
enabled,
|
|
760
|
+
itemCount,
|
|
761
|
+
itemHeight,
|
|
762
|
+
metrics.scrollTop,
|
|
763
|
+
metrics.viewportHeight,
|
|
764
|
+
overscan
|
|
765
|
+
]);
|
|
766
|
+
};
|
|
767
|
+
//#endregion
|
|
768
|
+
exports.MOBILE_BREAKPOINT = MOBILE_BREAKPOINT;
|
|
769
|
+
exports.assignRef = assignRef;
|
|
770
|
+
exports.canOpenFilePreviewDialog = canOpenFilePreviewDialog;
|
|
771
|
+
exports.clampNumber = clampNumber;
|
|
772
|
+
exports.composeRefs = composeRefs;
|
|
97
773
|
exports.copyToClipboard = copyToClipboard;
|
|
774
|
+
exports.createCodeHighlightLines = createCodeHighlightLines;
|
|
98
775
|
exports.createHeadingIdFactory = createHeadingIdFactory;
|
|
776
|
+
exports.createNumberRange = createNumberRange;
|
|
777
|
+
exports.createObjectFileItem = createObjectFileItem;
|
|
99
778
|
exports.extractHeadings = extractHeadings;
|
|
100
779
|
exports.flattenText = flattenText;
|
|
780
|
+
exports.formatCssSize = formatCssSize;
|
|
781
|
+
exports.formatFileSize = formatFileSize;
|
|
782
|
+
exports.getFileCodeLanguage = getFileCodeLanguage;
|
|
783
|
+
exports.getFileExtension = getFileExtension;
|
|
784
|
+
exports.getFloatingPanelPosition = getFloatingPanelPosition;
|
|
785
|
+
exports.getFocusableElements = getFocusableElements;
|
|
786
|
+
exports.getVirtualScrollWindow = getVirtualScrollWindow;
|
|
787
|
+
exports.highlightCodeToHtml = highlightCodeToHtml;
|
|
788
|
+
exports.isAbortError = isAbortError;
|
|
101
789
|
exports.isMediaOnlyParagraph = isMediaOnlyParagraph;
|
|
790
|
+
exports.isMobile = isMobile;
|
|
791
|
+
exports.isMobileViewport = isMobileViewport;
|
|
792
|
+
exports.normalizeFileProgress = normalizeFileProgress;
|
|
793
|
+
exports.normalizeHljsLanguage = normalizeHljsLanguage;
|
|
794
|
+
exports.parseCodeMeta = parseCodeMeta;
|
|
795
|
+
exports.requestJson = requestJson;
|
|
796
|
+
exports.resolveFileKind = resolveFileKind;
|
|
797
|
+
exports.resolveFileKindLabel = resolveFileKindLabel;
|
|
798
|
+
exports.resolveFilePreviewMode = resolveFilePreviewMode;
|
|
799
|
+
exports.resolveFilePreviewType = resolveFilePreviewType;
|
|
800
|
+
exports.resolveMediaAsset = resolveMediaAsset;
|
|
801
|
+
exports.resolveMediaVolume = resolveMediaVolume;
|
|
802
|
+
exports.useControllableState = useControllableState;
|
|
803
|
+
exports.useCopyToClipboard = useCopyToClipboard;
|
|
804
|
+
exports.useFloatingLayer = useFloatingLayer;
|
|
805
|
+
exports.useVirtualScrollWindow = useVirtualScrollWindow;
|