@icen.ai/ui 0.3.0 → 0.5.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/dist/behaviors/back-top.mjs +84 -0
- package/dist/behaviors/charts.mjs +188 -0
- package/dist/behaviors/command-palette.mjs +177 -0
- package/dist/behaviors/datatable.mjs +244 -24
- package/dist/behaviors/date-picker.mjs +228 -0
- package/dist/behaviors/input.mjs +170 -1
- package/dist/behaviors/notification.mjs +754 -0
- package/dist/behaviors/select.mjs +151 -22
- package/dist/behaviors/split-pane.mjs +102 -0
- package/dist/behaviors/upload.mjs +165 -14
- package/dist/components/back-top.css +88 -0
- package/dist/components/back-top.mjs +3 -0
- package/dist/components/badge.css +131 -0
- package/dist/components/badge.mjs +2 -0
- package/dist/components/breadcrumb.css +57 -0
- package/dist/components/card.css +68 -0
- package/dist/components/chart-area.css +15 -0
- package/dist/components/chart-area.mjs +4 -0
- package/dist/components/chart-bar.css +79 -0
- package/dist/components/chart-bar.mjs +4 -0
- package/dist/components/chart-gauge.css +38 -0
- package/dist/components/chart-gauge.mjs +4 -0
- package/dist/components/chart-heatmap.css +52 -0
- package/dist/components/chart-heatmap.mjs +4 -0
- package/dist/components/chart-line.css +15 -0
- package/dist/components/chart-line.mjs +4 -0
- package/dist/components/chart-pie.css +26 -0
- package/dist/components/chart-pie.mjs +4 -0
- package/dist/components/chart-radar.css +19 -0
- package/dist/components/chart-radar.mjs +4 -0
- package/dist/components/chart-sparkline.css +14 -0
- package/dist/components/chart-sparkline.mjs +4 -0
- package/dist/components/chart-stack.css +30 -0
- package/dist/components/chart-stack.mjs +4 -0
- package/dist/components/charts.css +7 -207
- package/dist/components/charts.mjs +9 -0
- package/dist/components/command-palette.css +246 -0
- package/dist/components/command-palette.mjs +3 -0
- package/dist/components/content.css +214 -0
- package/dist/components/copy.css +93 -0
- package/dist/components/copy.mjs +3 -0
- package/dist/components/datatable.css +86 -0
- package/dist/components/date-picker.css +244 -0
- package/dist/components/date-picker.mjs +3 -0
- package/dist/components/empty.css +73 -3
- package/dist/components/feedback.css +239 -1
- package/dist/components/form.css +119 -0
- package/dist/components/form.mjs +2 -0
- package/dist/components/input.css +24 -1
- package/dist/components/media.css +144 -1
- package/dist/components/nav.css +99 -0
- package/dist/components/notification.css +387 -0
- package/dist/components/notification.mjs +3 -0
- package/dist/components/panel.css +100 -5
- package/dist/components/pill.css +70 -0
- package/dist/components/scroll-area.css +112 -0
- package/dist/components/scroll-area.mjs +2 -0
- package/dist/components/segmented.css +75 -0
- package/dist/components/select.css +97 -2
- package/dist/components/sidebar.css +118 -0
- package/dist/components/slider.css +113 -0
- package/dist/components/split-pane.css +112 -0
- package/dist/components/split-pane.mjs +3 -0
- package/dist/components/stat.css +129 -6
- package/dist/components/steps.css +97 -0
- package/dist/components/switch.css +142 -0
- package/dist/components/table.css +86 -3
- package/dist/components/tabs.css +122 -1
- package/dist/components/tag.css +164 -0
- package/dist/components/tag.mjs +2 -0
- package/dist/components/toast.css +114 -0
- package/dist/components/toolbar.css +101 -0
- package/dist/components/toolbar.mjs +2 -0
- package/dist/components/upload.css +61 -4
- package/dist/index.mjs +2039 -62
- package/dist/registry.json +200 -24
- package/dist/ui.css +4865 -622
- package/package.json +1 -1
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/behaviors/back-top.ts
|
|
2
|
+
var AUTO_BTN_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m18 15-6-6-6 6"/></svg>';
|
|
3
|
+
function prefersReducedMotion() {
|
|
4
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
5
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
6
|
+
}
|
|
7
|
+
function getScrollTarget(target) {
|
|
8
|
+
if (!target) return { el: null, isWindow: true };
|
|
9
|
+
const el = document.querySelector(target);
|
|
10
|
+
return { el, isWindow: false };
|
|
11
|
+
}
|
|
12
|
+
function getScrollY(target) {
|
|
13
|
+
if (!target) return window.scrollY;
|
|
14
|
+
const el = document.querySelector(target);
|
|
15
|
+
return el ? el.scrollTop : window.scrollY;
|
|
16
|
+
}
|
|
17
|
+
function scrollToTop(target, offset = 0) {
|
|
18
|
+
const reduce = prefersReducedMotion();
|
|
19
|
+
const behavior = reduce ? "auto" : "smooth";
|
|
20
|
+
if (!target) {
|
|
21
|
+
window.scrollTo({ top: offset, behavior });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const el = document.querySelector(target);
|
|
25
|
+
if (el) el.scrollTo({ top: offset, behavior });
|
|
26
|
+
}
|
|
27
|
+
function setup(btn, opts = {}) {
|
|
28
|
+
const el = btn;
|
|
29
|
+
if (el.__icenBackTopInit) return;
|
|
30
|
+
el.__icenBackTopInit = true;
|
|
31
|
+
const threshold = Number(btn.dataset.backTopThreshold ?? opts.threshold ?? 320) || 320;
|
|
32
|
+
const target = btn.dataset.backTopTarget ?? opts.target;
|
|
33
|
+
const offset = Number(btn.dataset.backTopOffset ?? opts.offset ?? 0) || 0;
|
|
34
|
+
let ticking = false;
|
|
35
|
+
const apply = () => {
|
|
36
|
+
ticking = false;
|
|
37
|
+
const y = getScrollY(target);
|
|
38
|
+
btn.classList.toggle("is-shown", y > threshold);
|
|
39
|
+
};
|
|
40
|
+
const onScroll = () => {
|
|
41
|
+
if (ticking) return;
|
|
42
|
+
ticking = true;
|
|
43
|
+
window.requestAnimationFrame(apply);
|
|
44
|
+
};
|
|
45
|
+
const { el: targetEl, isWindow } = getScrollTarget(target);
|
|
46
|
+
if (isWindow) {
|
|
47
|
+
window.addEventListener("scroll", onScroll, { passive: true });
|
|
48
|
+
} else if (targetEl) {
|
|
49
|
+
targetEl.addEventListener("scroll", onScroll, { passive: true });
|
|
50
|
+
}
|
|
51
|
+
window.addEventListener("resize", onScroll, { passive: true });
|
|
52
|
+
btn.addEventListener("click", () => scrollToTop(target, offset));
|
|
53
|
+
apply();
|
|
54
|
+
}
|
|
55
|
+
function initBackTop(opts = {}) {
|
|
56
|
+
if (typeof document === "undefined") return;
|
|
57
|
+
if (typeof window === "undefined") return;
|
|
58
|
+
const root = opts.root ?? document;
|
|
59
|
+
if (opts.autoCreate) {
|
|
60
|
+
let btn = document.body.querySelector(".back-top[data-back-top-auto]");
|
|
61
|
+
if (!btn) {
|
|
62
|
+
btn = document.createElement("button");
|
|
63
|
+
btn.type = "button";
|
|
64
|
+
btn.className = "back-top";
|
|
65
|
+
btn.setAttribute("data-back-top", "");
|
|
66
|
+
btn.setAttribute("data-back-top-auto", "");
|
|
67
|
+
btn.setAttribute("aria-label", "\u56DE\u5230\u9876\u90E8");
|
|
68
|
+
btn.innerHTML = AUTO_BTN_SVG;
|
|
69
|
+
document.body.appendChild(btn);
|
|
70
|
+
}
|
|
71
|
+
setup(btn, opts);
|
|
72
|
+
}
|
|
73
|
+
const candidates = [];
|
|
74
|
+
if (root instanceof Element && root.matches(".back-top[data-back-top]")) candidates.push(root);
|
|
75
|
+
candidates.push(...Array.from(root.querySelectorAll(".back-top[data-back-top]")));
|
|
76
|
+
for (const c of candidates) {
|
|
77
|
+
const el = c;
|
|
78
|
+
if (el.hasAttribute("data-back-top-auto") && opts.autoCreate) continue;
|
|
79
|
+
setup(el, opts);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
initBackTop
|
|
84
|
+
};
|
|
@@ -258,6 +258,192 @@ function renderLine(el, opts) {
|
|
|
258
258
|
wrap.append(svg, labelsRow(labels.slice(-7)));
|
|
259
259
|
el.appendChild(wrap);
|
|
260
260
|
}
|
|
261
|
+
function renderArea(el, opts) {
|
|
262
|
+
if (typeof document === "undefined") return;
|
|
263
|
+
el.textContent = "";
|
|
264
|
+
const n = Math.min(opts.labels.length, opts.values.length);
|
|
265
|
+
if (n === 0) {
|
|
266
|
+
renderEmpty(el, opts.emptyLabel);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const labels = opts.labels.slice(0, n);
|
|
270
|
+
const values = opts.values.slice(0, n);
|
|
271
|
+
const width = 360;
|
|
272
|
+
const height = 150;
|
|
273
|
+
const pad = 14;
|
|
274
|
+
const max = Math.max(...values, 1);
|
|
275
|
+
const tone = toneVar(opts.tone);
|
|
276
|
+
const points = values.map((value, i) => ({
|
|
277
|
+
x: values.length <= 1 ? width / 2 : pad + i / (values.length - 1) * (width - pad * 2),
|
|
278
|
+
y: height - pad - value / max * (height - pad * 2),
|
|
279
|
+
value,
|
|
280
|
+
label: labels[i] ?? ""
|
|
281
|
+
}));
|
|
282
|
+
const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(" ");
|
|
283
|
+
const last = points[points.length - 1];
|
|
284
|
+
const first = points[0];
|
|
285
|
+
const area = `${line} L${last.x.toFixed(1)},${height - pad} L${first.x.toFixed(1)},${height - pad} Z`;
|
|
286
|
+
const gradientId = `icen-chart-area-${++lineGradientSeq}`;
|
|
287
|
+
const wrap = document.createElement("div");
|
|
288
|
+
wrap.className = "chart-area";
|
|
289
|
+
const svg = svgEl("svg", { viewBox: `0 0 ${width} ${height}`, role: "img" });
|
|
290
|
+
const defs = svgEl("defs", {});
|
|
291
|
+
const gradient = svgEl("linearGradient", { id: gradientId, x1: "0", x2: "0", y1: "0", y2: "1" });
|
|
292
|
+
gradient.appendChild(svgEl("stop", { offset: "0%", "stop-color": tone, "stop-opacity": "0.5" }));
|
|
293
|
+
gradient.appendChild(svgEl("stop", { offset: "100%", "stop-color": tone, "stop-opacity": "0.05" }));
|
|
294
|
+
defs.appendChild(gradient);
|
|
295
|
+
svg.appendChild(defs);
|
|
296
|
+
for (let i = 0; i < 4; i++) {
|
|
297
|
+
const y = pad + i * (height - pad * 2) / 3;
|
|
298
|
+
svg.appendChild(svgEl("line", {
|
|
299
|
+
x1: String(pad),
|
|
300
|
+
x2: String(width - pad),
|
|
301
|
+
y1: String(y),
|
|
302
|
+
y2: String(y),
|
|
303
|
+
stroke: "var(--token-line-soft)",
|
|
304
|
+
"stroke-width": "1"
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
svg.appendChild(svgEl("path", { d: area, fill: `url(#${gradientId})` }));
|
|
308
|
+
svg.appendChild(svgEl("path", {
|
|
309
|
+
d: line,
|
|
310
|
+
fill: "none",
|
|
311
|
+
stroke: tone,
|
|
312
|
+
"stroke-width": "1.5",
|
|
313
|
+
"stroke-linecap": "round",
|
|
314
|
+
"stroke-linejoin": "round"
|
|
315
|
+
}));
|
|
316
|
+
for (const p of points) {
|
|
317
|
+
const title = svgEl("title", {});
|
|
318
|
+
title.textContent = `${p.label}: ${fmt(opts, p.value)}`;
|
|
319
|
+
const inv = svgEl("circle", {
|
|
320
|
+
cx: p.x.toFixed(1),
|
|
321
|
+
cy: p.y.toFixed(1),
|
|
322
|
+
r: "2",
|
|
323
|
+
fill: "transparent"
|
|
324
|
+
});
|
|
325
|
+
inv.appendChild(title);
|
|
326
|
+
svg.appendChild(inv);
|
|
327
|
+
}
|
|
328
|
+
wrap.append(svg, labelsRow(labels.slice(-7)));
|
|
329
|
+
el.appendChild(wrap);
|
|
330
|
+
}
|
|
331
|
+
function renderRadar(el, opts) {
|
|
332
|
+
if (typeof document === "undefined") return;
|
|
333
|
+
el.textContent = "";
|
|
334
|
+
const axes = opts.axes;
|
|
335
|
+
const n = axes.length;
|
|
336
|
+
if (n < 3 || opts.series.length === 0) {
|
|
337
|
+
renderEmpty(el, opts.emptyLabel);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
let maxVal = opts.max ?? 0;
|
|
341
|
+
if (opts.max == null) {
|
|
342
|
+
for (const s of opts.series) {
|
|
343
|
+
for (let i = 0; i < n; i++) maxVal = Math.max(maxVal, s.values[i] ?? 0);
|
|
344
|
+
}
|
|
345
|
+
maxVal = niceCeil(maxVal);
|
|
346
|
+
}
|
|
347
|
+
if (maxVal <= 0) maxVal = 1;
|
|
348
|
+
const size = 200;
|
|
349
|
+
const cx = size / 2;
|
|
350
|
+
const cy = size / 2;
|
|
351
|
+
const radius = 78;
|
|
352
|
+
const levels = Math.max(2, opts.levels ?? 4);
|
|
353
|
+
const angleOf = (i) => -Math.PI / 2 + i * 2 * Math.PI / n;
|
|
354
|
+
const pointAt = (i, r) => ({
|
|
355
|
+
x: cx + r * Math.cos(angleOf(i)),
|
|
356
|
+
y: cy + r * Math.sin(angleOf(i))
|
|
357
|
+
});
|
|
358
|
+
const wrap = document.createElement("div");
|
|
359
|
+
wrap.className = "chart-radar";
|
|
360
|
+
const svg = svgEl("svg", { viewBox: `0 0 ${size} ${size}`, role: "img" });
|
|
361
|
+
for (let lv = 1; lv <= levels; lv++) {
|
|
362
|
+
const r = radius * lv / levels;
|
|
363
|
+
const pts = Array.from({ length: n }, (_, i) => {
|
|
364
|
+
const p = pointAt(i, r);
|
|
365
|
+
return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
|
|
366
|
+
}).join(" ");
|
|
367
|
+
svg.appendChild(svgEl("polygon", {
|
|
368
|
+
points: pts,
|
|
369
|
+
fill: "none",
|
|
370
|
+
stroke: "var(--token-line-soft)",
|
|
371
|
+
"stroke-width": "1",
|
|
372
|
+
"stroke-dasharray": lv === levels ? "0" : "3 2"
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
for (let i = 0; i < n; i++) {
|
|
376
|
+
const edge = pointAt(i, radius);
|
|
377
|
+
svg.appendChild(svgEl("line", {
|
|
378
|
+
x1: String(cx),
|
|
379
|
+
y1: String(cy),
|
|
380
|
+
x2: edge.x.toFixed(1),
|
|
381
|
+
y2: edge.y.toFixed(1),
|
|
382
|
+
stroke: "var(--token-line-soft)",
|
|
383
|
+
"stroke-width": "1"
|
|
384
|
+
}));
|
|
385
|
+
const labelP = pointAt(i, radius + 14);
|
|
386
|
+
const txt = svgEl("text", {
|
|
387
|
+
x: labelP.x.toFixed(1),
|
|
388
|
+
y: labelP.y.toFixed(1),
|
|
389
|
+
"text-anchor": "middle",
|
|
390
|
+
"dominant-baseline": "middle",
|
|
391
|
+
"font-size": "9",
|
|
392
|
+
fill: "var(--token-text-muted)"
|
|
393
|
+
});
|
|
394
|
+
txt.textContent = axes[i] ?? "";
|
|
395
|
+
svg.appendChild(txt);
|
|
396
|
+
}
|
|
397
|
+
const legend = document.createElement("div");
|
|
398
|
+
legend.className = "chart-legend";
|
|
399
|
+
for (const s of opts.series) {
|
|
400
|
+
const tone = toneVar(s.tone);
|
|
401
|
+
const pts = Array.from({ length: n }, (_, i) => {
|
|
402
|
+
const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
|
|
403
|
+
const p = pointAt(i, v / maxVal * radius);
|
|
404
|
+
return `${p.x.toFixed(1)},${p.y.toFixed(1)}`;
|
|
405
|
+
}).join(" ");
|
|
406
|
+
const polyFill = svgEl("polygon", {
|
|
407
|
+
points: pts,
|
|
408
|
+
fill: tone,
|
|
409
|
+
"fill-opacity": "0.16",
|
|
410
|
+
stroke: tone,
|
|
411
|
+
"stroke-width": "2",
|
|
412
|
+
"stroke-linejoin": "round"
|
|
413
|
+
});
|
|
414
|
+
const title = svgEl("title", {});
|
|
415
|
+
title.textContent = `${s.name}: ${s.values.map((v) => fmt(opts, v)).join(" / ")}`;
|
|
416
|
+
polyFill.appendChild(title);
|
|
417
|
+
svg.appendChild(polyFill);
|
|
418
|
+
for (let i = 0; i < n; i++) {
|
|
419
|
+
const v = Math.max(0, Math.min(maxVal, s.values[i] ?? 0));
|
|
420
|
+
const p = pointAt(i, v / maxVal * radius);
|
|
421
|
+
svg.appendChild(svgEl("circle", {
|
|
422
|
+
cx: p.x.toFixed(1),
|
|
423
|
+
cy: p.y.toFixed(1),
|
|
424
|
+
r: "2.5",
|
|
425
|
+
fill: "var(--token-card)",
|
|
426
|
+
stroke: tone,
|
|
427
|
+
"stroke-width": "1.5"
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
legend.appendChild(legendItem(s.name, "", s.tone));
|
|
431
|
+
}
|
|
432
|
+
wrap.append(svg, legend);
|
|
433
|
+
el.appendChild(wrap);
|
|
434
|
+
}
|
|
435
|
+
function niceCeil(v) {
|
|
436
|
+
if (v <= 0) return 1;
|
|
437
|
+
const exp = Math.floor(Math.log10(v));
|
|
438
|
+
const base = Math.pow(10, exp);
|
|
439
|
+
const frac = v / base;
|
|
440
|
+
let nice;
|
|
441
|
+
if (frac <= 1) nice = 1;
|
|
442
|
+
else if (frac <= 2) nice = 2;
|
|
443
|
+
else if (frac <= 5) nice = 5;
|
|
444
|
+
else nice = 10;
|
|
445
|
+
return nice * base;
|
|
446
|
+
}
|
|
261
447
|
function isoDate(d) {
|
|
262
448
|
const m = String(d.getMonth() + 1).padStart(2, "0");
|
|
263
449
|
const day = String(d.getDate()).padStart(2, "0");
|
|
@@ -424,11 +610,13 @@ function renderGauge(el, opts) {
|
|
|
424
610
|
el.appendChild(wrap);
|
|
425
611
|
}
|
|
426
612
|
export {
|
|
613
|
+
renderArea,
|
|
427
614
|
renderDonut,
|
|
428
615
|
renderGauge,
|
|
429
616
|
renderHBar,
|
|
430
617
|
renderHeatmap,
|
|
431
618
|
renderLine,
|
|
619
|
+
renderRadar,
|
|
432
620
|
renderSparkline,
|
|
433
621
|
renderStack,
|
|
434
622
|
renderVBar
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// src/behaviors/command-palette.ts
|
|
2
|
+
var FOCUSABLE = 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
|
3
|
+
var openId = null;
|
|
4
|
+
var restoreFocusTo = null;
|
|
5
|
+
var prevBodyOverflow = "";
|
|
6
|
+
var keyHandler = null;
|
|
7
|
+
var globalKeyHandler = null;
|
|
8
|
+
var initialized = false;
|
|
9
|
+
function findBackdrop(id) {
|
|
10
|
+
return document.querySelector(
|
|
11
|
+
`.command-palette-backdrop[data-command-palette="${CSS.escape(id)}"]`
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
function getVisibleItems(panel) {
|
|
15
|
+
return Array.from(
|
|
16
|
+
panel.querySelectorAll(".command-palette-item:not([hidden]):not([disabled])")
|
|
17
|
+
).filter((el) => el.offsetParent !== null || el.getClientRects().length > 0);
|
|
18
|
+
}
|
|
19
|
+
function isDisabledItem(el) {
|
|
20
|
+
return el.hasAttribute("disabled") || el.getAttribute("aria-disabled") === "true";
|
|
21
|
+
}
|
|
22
|
+
function setActive(panel, idx) {
|
|
23
|
+
const items = getVisibleItems(panel);
|
|
24
|
+
items.forEach((el, i) => el.classList.toggle("is-active", i === idx));
|
|
25
|
+
const active = items[idx];
|
|
26
|
+
if (active && typeof active.scrollIntoView === "function") {
|
|
27
|
+
active.scrollIntoView({ block: "nearest" });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function filterPanel(panel, query) {
|
|
31
|
+
const q = query.trim().toLowerCase();
|
|
32
|
+
let visibleCount = 0;
|
|
33
|
+
panel.querySelectorAll(".command-palette-item").forEach((item) => {
|
|
34
|
+
if (isDisabledItem(item)) return;
|
|
35
|
+
const label = (item.querySelector(".command-palette-item-label")?.textContent ?? "").toLowerCase();
|
|
36
|
+
const hint = (item.querySelector(".command-palette-item-hint")?.textContent ?? "").toLowerCase();
|
|
37
|
+
const keyword = (item.dataset.commandPaletteKeyword ?? "").toLowerCase();
|
|
38
|
+
const match = !q || label.includes(q) || hint.includes(q) || keyword.includes(q);
|
|
39
|
+
item.hidden = !match;
|
|
40
|
+
item.classList.remove("is-active");
|
|
41
|
+
if (match) visibleCount++;
|
|
42
|
+
});
|
|
43
|
+
panel.querySelectorAll(".command-palette-group").forEach((group) => {
|
|
44
|
+
const hasVisible = Array.from(group.querySelectorAll(".command-palette-item")).some(
|
|
45
|
+
(it) => !it.hidden
|
|
46
|
+
);
|
|
47
|
+
group.hidden = !hasVisible;
|
|
48
|
+
});
|
|
49
|
+
const empty = panel.querySelector(".command-palette-empty");
|
|
50
|
+
if (empty) empty.hidden = visibleCount !== 0;
|
|
51
|
+
if (visibleCount > 0) setActive(panel, 0);
|
|
52
|
+
}
|
|
53
|
+
function openCommandPalette(id) {
|
|
54
|
+
if (typeof document === "undefined") return;
|
|
55
|
+
if (openId === id) return;
|
|
56
|
+
if (openId) closeCommandPalette(openId);
|
|
57
|
+
const backdrop = findBackdrop(id);
|
|
58
|
+
if (!backdrop) return;
|
|
59
|
+
const panel = backdrop.querySelector(".command-palette");
|
|
60
|
+
if (!panel) return;
|
|
61
|
+
if (backdrop.parentElement !== document.body) document.body.appendChild(backdrop);
|
|
62
|
+
restoreFocusTo = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
63
|
+
openId = id;
|
|
64
|
+
backdrop.hidden = false;
|
|
65
|
+
prevBodyOverflow = document.body.style.overflow;
|
|
66
|
+
document.body.style.overflow = "hidden";
|
|
67
|
+
const input = panel.querySelector(".command-palette-input");
|
|
68
|
+
if (input) {
|
|
69
|
+
input.value = "";
|
|
70
|
+
input.focus();
|
|
71
|
+
} else {
|
|
72
|
+
panel.focus();
|
|
73
|
+
}
|
|
74
|
+
filterPanel(panel, "");
|
|
75
|
+
keyHandler = (e) => {
|
|
76
|
+
if (e.key === "Escape") {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
closeCommandPalette(id);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
const items = getVisibleItems(panel);
|
|
84
|
+
if (items.length === 0) return;
|
|
85
|
+
const cur = items.findIndex((el) => el.classList.contains("is-active"));
|
|
86
|
+
const next = e.key === "ArrowDown" ? (cur + 1) % items.length : (cur - 1 + items.length) % items.length;
|
|
87
|
+
setActive(panel, next);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (e.key === "Enter") {
|
|
91
|
+
const active = panel.querySelector(".command-palette-item.is-active");
|
|
92
|
+
if (active && !isDisabledItem(active)) {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
active.click();
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (e.key === "Tab") {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
const els = Array.from(panel.querySelectorAll(FOCUSABLE)).filter(
|
|
101
|
+
(el) => !el.hidden && el.offsetParent !== null
|
|
102
|
+
);
|
|
103
|
+
if (els.length === 0) return;
|
|
104
|
+
const cur = els.findIndex((el) => el === document.activeElement);
|
|
105
|
+
const next = e.shiftKey ? (cur - 1 + els.length) % els.length : (cur + 1) % els.length;
|
|
106
|
+
els[next]?.focus();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener("keydown", keyHandler);
|
|
110
|
+
}
|
|
111
|
+
function closeCommandPalette(id) {
|
|
112
|
+
if (typeof document === "undefined") return;
|
|
113
|
+
const backdrop = findBackdrop(id);
|
|
114
|
+
if (!backdrop) return;
|
|
115
|
+
backdrop.hidden = true;
|
|
116
|
+
if (openId !== id) return;
|
|
117
|
+
openId = null;
|
|
118
|
+
if (keyHandler) {
|
|
119
|
+
window.removeEventListener("keydown", keyHandler);
|
|
120
|
+
keyHandler = null;
|
|
121
|
+
}
|
|
122
|
+
document.body.style.overflow = prevBodyOverflow;
|
|
123
|
+
if (restoreFocusTo && restoreFocusTo.isConnected) restoreFocusTo.focus();
|
|
124
|
+
restoreFocusTo = null;
|
|
125
|
+
}
|
|
126
|
+
function initCommandPalette(root = document) {
|
|
127
|
+
if (typeof document === "undefined") return;
|
|
128
|
+
if (typeof window === "undefined") return;
|
|
129
|
+
root.querySelectorAll("[data-command-palette-open]").forEach((el) => el.setAttribute("aria-haspopup", "dialog"));
|
|
130
|
+
if (initialized) return;
|
|
131
|
+
initialized = true;
|
|
132
|
+
document.addEventListener("click", (e) => {
|
|
133
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
134
|
+
if (!target) return;
|
|
135
|
+
const opener = target.closest("[data-command-palette-open]");
|
|
136
|
+
if (opener?.dataset.commandPaletteOpen) {
|
|
137
|
+
openCommandPalette(opener.dataset.commandPaletteOpen);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const item = target.closest(".command-palette-item");
|
|
141
|
+
if (item) {
|
|
142
|
+
const backdrop = item.closest("[data-command-palette]");
|
|
143
|
+
if (backdrop?.dataset.commandPalette) {
|
|
144
|
+
if (!item.hasAttribute("data-command-palette-keep-open")) {
|
|
145
|
+
closeCommandPalette(backdrop.dataset.commandPalette);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (target instanceof HTMLElement && target.classList.contains("command-palette-backdrop") && target.dataset.commandPalette) {
|
|
151
|
+
closeCommandPalette(target.dataset.commandPalette);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
document.addEventListener("input", (e) => {
|
|
155
|
+
if (!(e.target instanceof HTMLElement)) return;
|
|
156
|
+
if (!e.target.matches(".command-palette-input")) return;
|
|
157
|
+
const backdrop = e.target.closest("[data-command-palette]");
|
|
158
|
+
const panel = backdrop?.querySelector(".command-palette");
|
|
159
|
+
if (panel) filterPanel(panel, e.target.value);
|
|
160
|
+
});
|
|
161
|
+
globalKeyHandler = (e) => {
|
|
162
|
+
if ((e.metaKey || e.ctrlKey) && (e.key === "k" || e.key === "K")) {
|
|
163
|
+
e.preventDefault();
|
|
164
|
+
const first = document.querySelector(".command-palette-backdrop[data-command-palette]");
|
|
165
|
+
if (first?.dataset.commandPalette) {
|
|
166
|
+
if (openId === first.dataset.commandPalette) closeCommandPalette(openId);
|
|
167
|
+
else openCommandPalette(first.dataset.commandPalette);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
window.addEventListener("keydown", globalKeyHandler);
|
|
172
|
+
}
|
|
173
|
+
export {
|
|
174
|
+
closeCommandPalette,
|
|
175
|
+
initCommandPalette,
|
|
176
|
+
openCommandPalette
|
|
177
|
+
};
|