@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
|
@@ -155,6 +155,8 @@ function svgIcon(d, size = 12) {
|
|
|
155
155
|
var ICON_SEARCH = "M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm10 2-4.3-4.3";
|
|
156
156
|
var ICON_FUNNEL = "M22 3H2l8 9.46V19l4 2v-8.54L22 3Z";
|
|
157
157
|
var ICON_CHEVRON = "m6 9 6 6 6-6";
|
|
158
|
+
var ICON_COLUMNS = "M3 3h7v18H3zM14 3h7v18h-7z";
|
|
159
|
+
var ICON_DOWNLOAD = "M12 3v12m0 0-4-4m4 4 4-4M5 21h14";
|
|
158
160
|
function defaultCompare(a, b) {
|
|
159
161
|
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
160
162
|
return String(a ?? "").localeCompare(String(b ?? ""), "zh-CN", { numeric: true });
|
|
@@ -169,7 +171,11 @@ function noopHandle() {
|
|
|
169
171
|
setFilter: noop,
|
|
170
172
|
setSort: noop,
|
|
171
173
|
refresh: noop,
|
|
172
|
-
destroy: noop
|
|
174
|
+
destroy: noop,
|
|
175
|
+
getData: () => [],
|
|
176
|
+
exportCSV: noop,
|
|
177
|
+
showColumn: noop,
|
|
178
|
+
hideColumn: noop
|
|
173
179
|
};
|
|
174
180
|
}
|
|
175
181
|
function createTable(el, opts) {
|
|
@@ -184,13 +190,18 @@ function createTable(el, opts) {
|
|
|
184
190
|
const filters = /* @__PURE__ */ new Map();
|
|
185
191
|
const selected = /* @__PURE__ */ new Set();
|
|
186
192
|
const expanded = /* @__PURE__ */ new Set();
|
|
193
|
+
const colWidths = /* @__PURE__ */ new Map();
|
|
194
|
+
const colHidden = /* @__PURE__ */ new Map();
|
|
195
|
+
let lastClickedIndex = -1;
|
|
187
196
|
let destroyed = false;
|
|
188
197
|
const virtual = opts.virtual === true;
|
|
189
|
-
const
|
|
198
|
+
const compact = opts.density === "compact";
|
|
199
|
+
const rowH = opts.rowHeight ?? (compact ? 32 : 44);
|
|
190
200
|
const pageSizes = opts.pageSizes ?? [10, 20, 50];
|
|
191
201
|
const selectable = opts.selectable === true;
|
|
192
202
|
const expandable = !virtual ? opts.expandable : void 0;
|
|
193
|
-
const
|
|
203
|
+
const resizable = opts.resizable !== false;
|
|
204
|
+
const cols = () => opts.columns.filter((c) => !(c.hidden || colHidden.get(c.key)));
|
|
194
205
|
const keyOf = (row, index) => {
|
|
195
206
|
if (typeof opts.rowKey === "function") return opts.rowKey(row);
|
|
196
207
|
if (typeof opts.rowKey === "string") return String(row[opts.rowKey]);
|
|
@@ -202,7 +213,12 @@ function createTable(el, opts) {
|
|
|
202
213
|
keyCache = /* @__PURE__ */ new WeakMap();
|
|
203
214
|
data.forEach((r, i) => keyCache.set(r, keyOf(r, i)));
|
|
204
215
|
}
|
|
216
|
+
let pipelineCache = null;
|
|
217
|
+
function invalidate() {
|
|
218
|
+
pipelineCache = null;
|
|
219
|
+
}
|
|
205
220
|
function pipeline() {
|
|
221
|
+
if (pipelineCache) return pipelineCache;
|
|
206
222
|
let rows = data;
|
|
207
223
|
if (search) {
|
|
208
224
|
const q = search.toLowerCase();
|
|
@@ -223,6 +239,7 @@ function createTable(el, opts) {
|
|
|
223
239
|
const cmp = col?.sortFn ? (a, b) => col.sortFn(a, b) : (a, b) => defaultCompare(a[sortKey], b[sortKey]);
|
|
224
240
|
rows = [...rows].sort((a, b) => sortDir === "asc" ? cmp(a, b) : -cmp(a, b));
|
|
225
241
|
}
|
|
242
|
+
pipelineCache = rows;
|
|
226
243
|
return rows;
|
|
227
244
|
}
|
|
228
245
|
el.textContent = "";
|
|
@@ -230,21 +247,55 @@ function createTable(el, opts) {
|
|
|
230
247
|
root.className = "datatable";
|
|
231
248
|
if (opts.striped) root.classList.add("datatable--striped");
|
|
232
249
|
if (virtual) root.classList.add("datatable--virtual");
|
|
250
|
+
if (compact) root.classList.add("datatable--compact");
|
|
251
|
+
if (opts.columns.some((c) => c.frozen)) root.classList.add("datatable--frozen");
|
|
233
252
|
let searchInput = null;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
253
|
+
const needsToolbar = opts.searchable || opts.columnVisibility || opts.exportable;
|
|
254
|
+
let toolbar = null;
|
|
255
|
+
if (needsToolbar) {
|
|
256
|
+
toolbar = document.createElement("div");
|
|
257
|
+
toolbar.className = "dt-toolbar";
|
|
258
|
+
if (opts.searchable) {
|
|
259
|
+
const box = document.createElement("div");
|
|
260
|
+
box.className = "dt-search";
|
|
261
|
+
box.appendChild(svgIcon(ICON_SEARCH, 14));
|
|
262
|
+
searchInput = document.createElement("input");
|
|
263
|
+
searchInput.className = "dt-search-input";
|
|
264
|
+
searchInput.type = "search";
|
|
265
|
+
searchInput.placeholder = opts.searchPlaceholder ?? "\u641C\u7D22\u2026";
|
|
266
|
+
searchInput.setAttribute("aria-label", "\u641C\u7D22\u8868\u683C");
|
|
267
|
+
box.appendChild(searchInput);
|
|
268
|
+
toolbar.appendChild(box);
|
|
269
|
+
}
|
|
270
|
+
const actions = document.createElement("div");
|
|
271
|
+
actions.className = "dt-toolbar-actions";
|
|
272
|
+
if (opts.columnVisibility) {
|
|
273
|
+
const colBtn = document.createElement("button");
|
|
274
|
+
colBtn.type = "button";
|
|
275
|
+
colBtn.className = "dt-tool-btn";
|
|
276
|
+
colBtn.setAttribute("aria-label", "\u5217\u8BBE\u7F6E");
|
|
277
|
+
colBtn.appendChild(svgIcon(ICON_COLUMNS, 14));
|
|
278
|
+
colBtn.addEventListener("click", (ev) => {
|
|
279
|
+
ev.stopPropagation();
|
|
280
|
+
if (openPanel?.key === "__cols") closeFilterPanel();
|
|
281
|
+
else {
|
|
282
|
+
closeFilterPanel();
|
|
283
|
+
openColumnPanel(colBtn);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
actions.appendChild(colBtn);
|
|
287
|
+
}
|
|
288
|
+
if (opts.exportable) {
|
|
289
|
+
const expBtn = document.createElement("button");
|
|
290
|
+
expBtn.type = "button";
|
|
291
|
+
expBtn.className = "dt-tool-btn";
|
|
292
|
+
expBtn.setAttribute("aria-label", "\u5BFC\u51FA CSV");
|
|
293
|
+
expBtn.appendChild(svgIcon(ICON_DOWNLOAD, 14));
|
|
294
|
+
expBtn.addEventListener("click", () => exportCSV());
|
|
295
|
+
actions.appendChild(expBtn);
|
|
296
|
+
}
|
|
297
|
+
if (actions.children.length > 0) toolbar.appendChild(actions);
|
|
298
|
+
root.appendChild(toolbar);
|
|
248
299
|
}
|
|
249
300
|
const scroll = document.createElement("div");
|
|
250
301
|
scroll.className = "dt-scroll";
|
|
@@ -269,11 +320,29 @@ function createTable(el, opts) {
|
|
|
269
320
|
foot.className = "dt-foot";
|
|
270
321
|
root.appendChild(foot);
|
|
271
322
|
el.appendChild(root);
|
|
323
|
+
function frozenOffsets() {
|
|
324
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
325
|
+
if (!opts.columns.some((c) => c.frozen)) return offsets;
|
|
326
|
+
const visible = cols();
|
|
327
|
+
let acc = 0;
|
|
328
|
+
if (selectable) acc += 40;
|
|
329
|
+
if (expandable) acc += 34;
|
|
330
|
+
for (const c of visible) {
|
|
331
|
+
if (c.frozen) offsets.set(c.key, acc);
|
|
332
|
+
const w = colWidths.get(c.key) ?? c.width ?? "minmax(0,1fr)";
|
|
333
|
+
acc += parsePx(w);
|
|
334
|
+
}
|
|
335
|
+
return offsets;
|
|
336
|
+
}
|
|
337
|
+
function parsePx(w) {
|
|
338
|
+
const m = /^(\d+(?:\.\d+)?)px/.exec(w);
|
|
339
|
+
return m ? Number(m[1]) : 0;
|
|
340
|
+
}
|
|
272
341
|
function template() {
|
|
273
342
|
const parts = [];
|
|
274
343
|
if (selectable) parts.push("40px");
|
|
275
344
|
if (expandable) parts.push("34px");
|
|
276
|
-
for (const c of cols()) parts.push(c.width ?? "minmax(0,1fr)");
|
|
345
|
+
for (const c of cols()) parts.push(colWidths.get(c.key) ?? c.width ?? "minmax(0,1fr)");
|
|
277
346
|
return parts.join(" ");
|
|
278
347
|
}
|
|
279
348
|
function fillCell(cell, col, row, rowIndex) {
|
|
@@ -355,6 +424,7 @@ function createTable(el, opts) {
|
|
|
355
424
|
const cell = document.createElement("div");
|
|
356
425
|
cell.className = "dt-cell dt-cell--head";
|
|
357
426
|
if (col.align) cell.dataset.align = col.align;
|
|
427
|
+
if (col.frozen) cell.classList.add("dt-cell--frozen");
|
|
358
428
|
const title = document.createElement("span");
|
|
359
429
|
title.className = "dt-head-title";
|
|
360
430
|
title.textContent = col.title;
|
|
@@ -376,10 +446,12 @@ function createTable(el, opts) {
|
|
|
376
446
|
sortDir = "asc";
|
|
377
447
|
} else if (sortDir === "asc") sortDir = "desc";
|
|
378
448
|
else sortKey = null;
|
|
449
|
+
invalidate();
|
|
450
|
+
opts.onSortChange?.(sortKey, sortDir);
|
|
379
451
|
renderAll();
|
|
380
452
|
};
|
|
381
453
|
cell.addEventListener("click", (ev) => {
|
|
382
|
-
if (ev.target.closest(".dt-filter-btn")) return;
|
|
454
|
+
if (ev.target.closest(".dt-filter-btn, .dt-resize")) return;
|
|
383
455
|
toggleSort();
|
|
384
456
|
});
|
|
385
457
|
cell.addEventListener("keydown", (ev) => {
|
|
@@ -407,9 +479,102 @@ function createTable(el, opts) {
|
|
|
407
479
|
});
|
|
408
480
|
cell.appendChild(btn);
|
|
409
481
|
}
|
|
482
|
+
if (resizable && col !== cols()[cols().length - 1]) {
|
|
483
|
+
const handle = document.createElement("div");
|
|
484
|
+
handle.className = "dt-resize";
|
|
485
|
+
handle.setAttribute("role", "separator");
|
|
486
|
+
handle.setAttribute("aria-orientation", "vertical");
|
|
487
|
+
handle.setAttribute("aria-label", `\u8C03\u6574\u300C${col.title}\u300D\u5217\u5BBD`);
|
|
488
|
+
attachResize(handle, col);
|
|
489
|
+
cell.appendChild(handle);
|
|
490
|
+
}
|
|
410
491
|
headRow.appendChild(cell);
|
|
411
492
|
}
|
|
412
493
|
}
|
|
494
|
+
function attachResize(handle, col) {
|
|
495
|
+
handle.addEventListener("pointerdown", (ev) => {
|
|
496
|
+
ev.preventDefault();
|
|
497
|
+
ev.stopPropagation();
|
|
498
|
+
const startX = ev.clientX;
|
|
499
|
+
const startW = handle.closest(".dt-cell").getBoundingClientRect().width;
|
|
500
|
+
handle.setPointerCapture(ev.pointerId);
|
|
501
|
+
const onMove = (e) => {
|
|
502
|
+
const delta = e.clientX - startX;
|
|
503
|
+
const next = Math.max(48, Math.round(startW + delta));
|
|
504
|
+
colWidths.set(col.key, `${next}px`);
|
|
505
|
+
headRow.style.gridTemplateColumns = template();
|
|
506
|
+
body.querySelectorAll(".dt-row").forEach((r) => {
|
|
507
|
+
r.style.gridTemplateColumns = template();
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
const onUp = (e) => {
|
|
511
|
+
handle.releasePointerCapture(e.pointerId);
|
|
512
|
+
handle.removeEventListener("pointermove", onMove);
|
|
513
|
+
handle.removeEventListener("pointerup", onUp);
|
|
514
|
+
};
|
|
515
|
+
handle.addEventListener("pointermove", onMove);
|
|
516
|
+
handle.addEventListener("pointerup", onUp);
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
function openColumnPanel(btn) {
|
|
520
|
+
const panel2 = document.createElement("div");
|
|
521
|
+
panel2.className = "dt-filter-panel dt-col-panel";
|
|
522
|
+
panel2.hidden = true;
|
|
523
|
+
panel2.addEventListener("click", (ev) => ev.stopPropagation());
|
|
524
|
+
const list = document.createElement("div");
|
|
525
|
+
list.className = "dt-filter-list";
|
|
526
|
+
for (const col of opts.columns) {
|
|
527
|
+
if (col.key === "__select" || col.key === "__expand") continue;
|
|
528
|
+
const row = document.createElement("label");
|
|
529
|
+
row.className = "dt-filter-item";
|
|
530
|
+
const isHidden = colHidden.get(col.key) ?? col.hidden ?? false;
|
|
531
|
+
const box = makeCheck(!isHidden, col.title);
|
|
532
|
+
box.addEventListener("change", () => {
|
|
533
|
+
if (box.checked) colHidden.delete(col.key);
|
|
534
|
+
else colHidden.set(col.key, true);
|
|
535
|
+
invalidate();
|
|
536
|
+
renderAll();
|
|
537
|
+
});
|
|
538
|
+
const text = document.createElement("span");
|
|
539
|
+
text.textContent = col.title;
|
|
540
|
+
row.append(box, text);
|
|
541
|
+
list.appendChild(row);
|
|
542
|
+
}
|
|
543
|
+
panel2.appendChild(list);
|
|
544
|
+
document.body.appendChild(panel2);
|
|
545
|
+
openPanel = { key: "__cols", el: panel2 };
|
|
546
|
+
const rect = btn.getBoundingClientRect();
|
|
547
|
+
const pr = panel2.getBoundingClientRect();
|
|
548
|
+
let x = rect.right - pr.width;
|
|
549
|
+
let y = rect.bottom + 4;
|
|
550
|
+
if (x < 8) x = 8;
|
|
551
|
+
if (y + pr.height > window.innerHeight - 8) y = rect.top - pr.height - 4;
|
|
552
|
+
panel2.style.left = `${x}px`;
|
|
553
|
+
panel2.style.top = `${y}px`;
|
|
554
|
+
panel2.hidden = false;
|
|
555
|
+
window.addEventListener("scroll", onPanelScroll, true);
|
|
556
|
+
window.addEventListener("resize", onPanelScroll);
|
|
557
|
+
}
|
|
558
|
+
function exportCSV(filename) {
|
|
559
|
+
const rows = pipeline();
|
|
560
|
+
const visible = cols();
|
|
561
|
+
const header = visible.map((c) => csvEscape(c.title)).join(",");
|
|
562
|
+
const lines = rows.map(
|
|
563
|
+
(row) => visible.map((c) => csvEscape(String(row[c.key] ?? ""))).join(",")
|
|
564
|
+
);
|
|
565
|
+
const csv = "\uFEFF" + [header, ...lines].join("\r\n");
|
|
566
|
+
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
|
567
|
+
const url = URL.createObjectURL(blob);
|
|
568
|
+
const a = document.createElement("a");
|
|
569
|
+
a.href = url;
|
|
570
|
+
a.download = filename ?? "export.csv";
|
|
571
|
+
a.click();
|
|
572
|
+
URL.revokeObjectURL(url);
|
|
573
|
+
}
|
|
574
|
+
function csvEscape(s) {
|
|
575
|
+
if (/[",\r\n]/.test(s)) return `"${s.replace(/"/g, '""')}"`;
|
|
576
|
+
return s;
|
|
577
|
+
}
|
|
413
578
|
function buildFilterPanel(col) {
|
|
414
579
|
const panel2 = document.createElement("div");
|
|
415
580
|
panel2.className = "dt-filter-panel";
|
|
@@ -428,6 +593,7 @@ function createTable(el, opts) {
|
|
|
428
593
|
else cur.delete(v);
|
|
429
594
|
if (cur.size === values.length) filters.delete(col.key);
|
|
430
595
|
else filters.set(col.key, cur);
|
|
596
|
+
invalidate();
|
|
431
597
|
page = 1;
|
|
432
598
|
renderAll();
|
|
433
599
|
});
|
|
@@ -444,6 +610,7 @@ function createTable(el, opts) {
|
|
|
444
610
|
reset.textContent = "\u91CD\u7F6E";
|
|
445
611
|
reset.addEventListener("click", () => {
|
|
446
612
|
filters.delete(col.key);
|
|
613
|
+
invalidate();
|
|
447
614
|
page = 1;
|
|
448
615
|
closeFilterPanel();
|
|
449
616
|
renderAll();
|
|
@@ -459,15 +626,40 @@ function createTable(el, opts) {
|
|
|
459
626
|
tr.style.gridTemplateColumns = template();
|
|
460
627
|
if (virtual) tr.style.height = `${rowH}px`;
|
|
461
628
|
if (selected.has(key)) tr.classList.add("is-selected");
|
|
629
|
+
if (opts.rowClassName) {
|
|
630
|
+
const extra = opts.rowClassName(row, rowIndex);
|
|
631
|
+
if (extra) tr.classList.add(...extra.split(/\s+/).filter(Boolean));
|
|
632
|
+
}
|
|
462
633
|
if (selectable) {
|
|
463
634
|
const cell = document.createElement("div");
|
|
464
635
|
cell.className = "dt-cell dt-cell--check";
|
|
636
|
+
if (frozenOffsets().size > 0 && selectable) {
|
|
637
|
+
cell.classList.add("dt-cell--frozen");
|
|
638
|
+
cell.style.left = "0px";
|
|
639
|
+
}
|
|
465
640
|
const box = makeCheck(selected.has(key), "\u9009\u62E9\u8BE5\u884C");
|
|
466
|
-
box.addEventListener("click", (ev) =>
|
|
641
|
+
box.addEventListener("click", (ev) => {
|
|
642
|
+
box._shift = ev.shiftKey;
|
|
643
|
+
ev.stopPropagation();
|
|
644
|
+
});
|
|
467
645
|
box.addEventListener("change", () => {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
646
|
+
const shift = box._shift === true;
|
|
647
|
+
if (shift && lastClickedIndex >= 0 && lastClickedIndex !== rowIndex) {
|
|
648
|
+
const rows = pipeline();
|
|
649
|
+
const [lo, hi] = lastClickedIndex < rowIndex ? [lastClickedIndex, rowIndex] : [rowIndex, lastClickedIndex];
|
|
650
|
+
const willSelect = box.checked;
|
|
651
|
+
for (let i = lo; i <= hi; i++) {
|
|
652
|
+
const k = keyOfRow(rows[i]);
|
|
653
|
+
if (willSelect) selected.add(k);
|
|
654
|
+
else selected.delete(k);
|
|
655
|
+
}
|
|
656
|
+
renderBody();
|
|
657
|
+
} else {
|
|
658
|
+
if (box.checked) selected.add(key);
|
|
659
|
+
else selected.delete(key);
|
|
660
|
+
tr.classList.toggle("is-selected", box.checked);
|
|
661
|
+
}
|
|
662
|
+
lastClickedIndex = rowIndex;
|
|
471
663
|
emitSelection();
|
|
472
664
|
renderHead();
|
|
473
665
|
renderFoot(pipeline().length);
|
|
@@ -493,10 +685,16 @@ function createTable(el, opts) {
|
|
|
493
685
|
cell.appendChild(btn);
|
|
494
686
|
tr.appendChild(cell);
|
|
495
687
|
}
|
|
688
|
+
const offsets = frozenOffsets();
|
|
496
689
|
for (const col of cols()) {
|
|
497
690
|
const cell = document.createElement("div");
|
|
498
691
|
cell.className = "dt-cell";
|
|
499
692
|
if (col.align) cell.dataset.align = col.align;
|
|
693
|
+
if (col.frozen) {
|
|
694
|
+
cell.classList.add("dt-cell--frozen");
|
|
695
|
+
const off = offsets.get(col.key);
|
|
696
|
+
if (off != null) cell.style.left = `${off}px`;
|
|
697
|
+
}
|
|
500
698
|
fillCell(cell, col, row, rowIndex);
|
|
501
699
|
tr.appendChild(cell);
|
|
502
700
|
}
|
|
@@ -663,6 +861,7 @@ function createTable(el, opts) {
|
|
|
663
861
|
window.clearTimeout(searchTimer);
|
|
664
862
|
searchTimer = window.setTimeout(() => {
|
|
665
863
|
search = searchInput?.value.trim() ?? "";
|
|
864
|
+
invalidate();
|
|
666
865
|
page = 1;
|
|
667
866
|
renderAll();
|
|
668
867
|
}, 160);
|
|
@@ -676,7 +875,7 @@ function createTable(el, opts) {
|
|
|
676
875
|
function onDocPointerDown(ev) {
|
|
677
876
|
if (!openPanel) return;
|
|
678
877
|
const t = ev.target;
|
|
679
|
-
if (t instanceof Node && (openPanel.el.contains(t) || headRow.contains(t))) return;
|
|
878
|
+
if (t instanceof Node && (openPanel.el.contains(t) || headRow.contains(t) || toolbar?.contains(t))) return;
|
|
680
879
|
closeFilterPanel();
|
|
681
880
|
}
|
|
682
881
|
document.addEventListener("pointerdown", onDocPointerDown, true);
|
|
@@ -700,6 +899,7 @@ function createTable(el, opts) {
|
|
|
700
899
|
setData(next) {
|
|
701
900
|
data = next;
|
|
702
901
|
rekey();
|
|
902
|
+
invalidate();
|
|
703
903
|
page = 1;
|
|
704
904
|
renderAll();
|
|
705
905
|
},
|
|
@@ -714,21 +914,41 @@ function createTable(el, opts) {
|
|
|
714
914
|
setSearch(q) {
|
|
715
915
|
search = q;
|
|
716
916
|
if (searchInput) searchInput.value = q;
|
|
917
|
+
invalidate();
|
|
717
918
|
page = 1;
|
|
718
919
|
renderAll();
|
|
719
920
|
},
|
|
720
921
|
setFilter(key, values) {
|
|
721
922
|
if (values === null) filters.delete(key);
|
|
722
923
|
else filters.set(key, new Set(values));
|
|
924
|
+
invalidate();
|
|
723
925
|
page = 1;
|
|
724
926
|
renderAll();
|
|
725
927
|
},
|
|
726
928
|
setSort(key, dir = "asc") {
|
|
727
929
|
sortKey = key;
|
|
728
930
|
sortDir = dir;
|
|
931
|
+
invalidate();
|
|
932
|
+
opts.onSortChange?.(sortKey, sortDir);
|
|
933
|
+
renderAll();
|
|
934
|
+
},
|
|
935
|
+
getData() {
|
|
936
|
+
return pipeline();
|
|
937
|
+
},
|
|
938
|
+
exportCSV,
|
|
939
|
+
showColumn(key) {
|
|
940
|
+
colHidden.delete(key);
|
|
941
|
+
opts.columns.find((c) => c.key === key).hidden = false;
|
|
942
|
+
invalidate();
|
|
943
|
+
renderAll();
|
|
944
|
+
},
|
|
945
|
+
hideColumn(key) {
|
|
946
|
+
colHidden.set(key, true);
|
|
947
|
+
invalidate();
|
|
729
948
|
renderAll();
|
|
730
949
|
},
|
|
731
950
|
refresh() {
|
|
951
|
+
invalidate();
|
|
732
952
|
renderAll();
|
|
733
953
|
},
|
|
734
954
|
destroy() {
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// src/behaviors/date-picker.ts
|
|
2
|
+
var openPicker = null;
|
|
3
|
+
var panelEl = null;
|
|
4
|
+
var outsideHandler = null;
|
|
5
|
+
var escHandler = null;
|
|
6
|
+
function pad(n) {
|
|
7
|
+
return n < 10 ? "0" + n : String(n);
|
|
8
|
+
}
|
|
9
|
+
function toDateStr(d) {
|
|
10
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
11
|
+
}
|
|
12
|
+
function parseDateStr(s) {
|
|
13
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s.trim());
|
|
14
|
+
if (!m) return null;
|
|
15
|
+
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
|
16
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
17
|
+
}
|
|
18
|
+
function format(d, fmt) {
|
|
19
|
+
if (!d) return "";
|
|
20
|
+
return fmt.replace(/yyyy/g, String(d.getFullYear())).replace(/yy/g, String(d.getFullYear()).slice(-2)).replace(/mm/g, pad(d.getMonth() + 1)).replace(/m/g, String(d.getMonth() + 1)).replace(/dd/g, pad(d.getDate())).replace(/d/g, String(d.getDate()));
|
|
21
|
+
}
|
|
22
|
+
function ensurePanel() {
|
|
23
|
+
if (panelEl && panelEl.isConnected) return panelEl;
|
|
24
|
+
const el = document.createElement("div");
|
|
25
|
+
el.className = "date-picker-panel";
|
|
26
|
+
el.setAttribute("role", "dialog");
|
|
27
|
+
el.setAttribute("aria-label", "\u9009\u62E9\u65E5\u671F");
|
|
28
|
+
el.hidden = true;
|
|
29
|
+
document.body.appendChild(el);
|
|
30
|
+
panelEl = el;
|
|
31
|
+
return el;
|
|
32
|
+
}
|
|
33
|
+
function positionPanel(panel, trigger) {
|
|
34
|
+
const r = trigger.getBoundingClientRect();
|
|
35
|
+
const pw = 280;
|
|
36
|
+
const ph = panel.offsetHeight || 320;
|
|
37
|
+
let left = r.left;
|
|
38
|
+
let top = r.bottom + 4;
|
|
39
|
+
if (left + pw > window.innerWidth - 12) left = window.innerWidth - pw - 12;
|
|
40
|
+
if (left < 12) left = 12;
|
|
41
|
+
if (top + ph > window.innerHeight - 12) top = r.top - ph - 4;
|
|
42
|
+
if (top < 12) top = 12;
|
|
43
|
+
panel.style.left = left + "px";
|
|
44
|
+
panel.style.top = top + "px";
|
|
45
|
+
}
|
|
46
|
+
var CAL_ICON = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" x2="16" y1="2" y2="6"/><line x1="8" x2="8" y1="2" y2="6"/><line x1="3" x2="21" y1="10" y2="10"/></svg>';
|
|
47
|
+
var WEEK_LABELS_ZH = ["\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D", "\u65E5"];
|
|
48
|
+
var WEEK_LABELS_SUN = ["\u65E5", "\u4E00", "\u4E8C", "\u4E09", "\u56DB", "\u4E94", "\u516D"];
|
|
49
|
+
function renderCalendar(panel, ctx) {
|
|
50
|
+
const { view, selected, min, max, weekStart, onPick } = ctx;
|
|
51
|
+
const year = view.getFullYear();
|
|
52
|
+
const month = view.getMonth();
|
|
53
|
+
const labels = weekStart === 0 ? WEEK_LABELS_SUN : WEEK_LABELS_ZH;
|
|
54
|
+
const firstDay = new Date(year, month, 1);
|
|
55
|
+
const offsetRaw = firstDay.getDay();
|
|
56
|
+
const leadDays = weekStart === 0 ? offsetRaw : (offsetRaw + 6) % 7;
|
|
57
|
+
const gridStart = new Date(year, month, 1 - leadDays);
|
|
58
|
+
const today = /* @__PURE__ */ new Date();
|
|
59
|
+
today.setHours(0, 0, 0, 0);
|
|
60
|
+
const cells = [];
|
|
61
|
+
for (let i = 0; i < 42; i++) {
|
|
62
|
+
cells.push(new Date(gridStart.getFullYear(), gridStart.getMonth(), gridStart.getDate() + i));
|
|
63
|
+
}
|
|
64
|
+
let html = `<div class="date-picker-head">
|
|
65
|
+
<button class="date-picker-nav" data-dp-nav="-1" type="button" aria-label="\u4E0A\u4E00\u6708">\u2039</button>
|
|
66
|
+
<span class="date-picker-title">${year} \u5E74 ${month + 1} \u6708</span>
|
|
67
|
+
<button class="date-picker-nav" data-dp-nav="1" type="button" aria-label="\u4E0B\u4E00\u6708">\u203A</button>
|
|
68
|
+
</div><div class="date-picker-weekdays">${labels.map((l) => `<span>${l}</span>`).join("")}</div><div class="date-picker-grid">`;
|
|
69
|
+
for (const d of cells) {
|
|
70
|
+
const isOutside = d.getMonth() !== month;
|
|
71
|
+
const ds = toDateStr(d);
|
|
72
|
+
const isToday = d.getTime() === today.getTime();
|
|
73
|
+
const isSelected = selected && ds === toDateStr(selected);
|
|
74
|
+
const disabled = min && d < min || max && d > max;
|
|
75
|
+
const classes = ["date-picker-cell"];
|
|
76
|
+
if (isOutside) classes.push("is-outside");
|
|
77
|
+
if (isToday) classes.push("is-today");
|
|
78
|
+
if (isSelected) classes.push("is-selected");
|
|
79
|
+
if (disabled) classes.push("is-disabled");
|
|
80
|
+
html += `<button type="button" class="${classes.join(" ")}" data-dp-date="${ds}"${disabled ? " disabled" : ""}>${d.getDate()}</button>`;
|
|
81
|
+
}
|
|
82
|
+
html += `</div><div class="date-picker-foot">
|
|
83
|
+
<button type="button" class="date-picker-today" data-dp-today>\u4ECA\u5929</button>
|
|
84
|
+
<button type="button" class="date-picker-clear" data-dp-clear>\u6E05\u9664</button>
|
|
85
|
+
</div>`;
|
|
86
|
+
panel.innerHTML = html;
|
|
87
|
+
panel.querySelector('[data-dp-nav="-1"]')?.addEventListener("click", () => {
|
|
88
|
+
ctx.view = new Date(year, month - 1, 15);
|
|
89
|
+
renderCalendar(panel, { ...ctx, view: ctx.view });
|
|
90
|
+
});
|
|
91
|
+
panel.querySelector('[data-dp-nav="1"]')?.addEventListener("click", () => {
|
|
92
|
+
ctx.view = new Date(year, month + 1, 15);
|
|
93
|
+
renderCalendar(panel, { ...ctx, view: ctx.view });
|
|
94
|
+
});
|
|
95
|
+
panel.querySelectorAll("[data-dp-date]").forEach((btn) => {
|
|
96
|
+
btn.addEventListener("click", () => {
|
|
97
|
+
const ds = btn.dataset.dpDate;
|
|
98
|
+
if (!ds) return;
|
|
99
|
+
const d = parseDateStr(ds);
|
|
100
|
+
if (d) onPick(d);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
panel.querySelector("[data-dp-today]")?.addEventListener("click", () => {
|
|
104
|
+
onPick(new Date(today.getFullYear(), today.getMonth(), today.getDate()));
|
|
105
|
+
});
|
|
106
|
+
panel.querySelector("[data-dp-clear]")?.addEventListener("click", () => {
|
|
107
|
+
onPick(null);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function closePanel() {
|
|
111
|
+
if (!panelEl) return;
|
|
112
|
+
panelEl.hidden = true;
|
|
113
|
+
openPicker?.querySelector(".date-picker-trigger")?.classList.remove("is-open");
|
|
114
|
+
openPicker = null;
|
|
115
|
+
if (outsideHandler) {
|
|
116
|
+
document.removeEventListener("mousedown", outsideHandler);
|
|
117
|
+
outsideHandler = null;
|
|
118
|
+
}
|
|
119
|
+
if (escHandler) {
|
|
120
|
+
window.removeEventListener("keydown", escHandler);
|
|
121
|
+
escHandler = null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function setupPicker(container) {
|
|
125
|
+
const el = container;
|
|
126
|
+
if (el.__icenDatePickerInit) return;
|
|
127
|
+
el.__icenDatePickerInit = true;
|
|
128
|
+
const fmt = el.dataset.datePickerFormat ?? "yyyy-mm-dd";
|
|
129
|
+
const minStr = el.dataset.datePickerMin;
|
|
130
|
+
const maxStr = el.dataset.datePickerMax;
|
|
131
|
+
const weekStart = Number(el.dataset.datePickerWeekStart ?? "1");
|
|
132
|
+
const placeholder = el.dataset.datePickerPlaceholder ?? "\u9009\u62E9\u65E5\u671F";
|
|
133
|
+
const min = minStr ? parseDateStr(minStr) : null;
|
|
134
|
+
const max = maxStr ? parseDateStr(maxStr) : null;
|
|
135
|
+
const hidden = el.querySelector('input[type="hidden"]');
|
|
136
|
+
const initialStr = hidden?.value ?? "";
|
|
137
|
+
const initialDate = initialStr ? parseDateStr(initialStr) : null;
|
|
138
|
+
let trigger = el.querySelector(".date-picker-trigger");
|
|
139
|
+
if (!trigger) {
|
|
140
|
+
trigger = document.createElement("button");
|
|
141
|
+
trigger.type = "button";
|
|
142
|
+
trigger.className = "date-picker-trigger";
|
|
143
|
+
trigger.innerHTML = `<span class="date-picker-value${initialDate ? "" : " is-empty"}"></span><span class="date-picker-icon">${CAL_ICON}</span>`;
|
|
144
|
+
el.prepend(trigger);
|
|
145
|
+
}
|
|
146
|
+
trigger.setAttribute("aria-haspopup", "dialog");
|
|
147
|
+
const valueEl = trigger.querySelector(".date-picker-value");
|
|
148
|
+
function paint() {
|
|
149
|
+
const v = hidden?.value ?? "";
|
|
150
|
+
const d = v ? parseDateStr(v) : null;
|
|
151
|
+
if (valueEl) {
|
|
152
|
+
valueEl.textContent = d ? format(d, fmt) : placeholder;
|
|
153
|
+
valueEl.classList.toggle("is-empty", !d);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
paint();
|
|
157
|
+
if (hidden) {
|
|
158
|
+
const obs = new MutationObserver(() => paint());
|
|
159
|
+
obs.observe(hidden, { attributes: true, attributeFilter: ["value"] });
|
|
160
|
+
}
|
|
161
|
+
trigger.addEventListener("click", () => {
|
|
162
|
+
if (openPicker === el) {
|
|
163
|
+
closePanel();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
closePanel();
|
|
167
|
+
openPicker = el;
|
|
168
|
+
trigger.classList.add("is-open");
|
|
169
|
+
const panel = ensurePanel();
|
|
170
|
+
panel.hidden = false;
|
|
171
|
+
const selected = hidden?.value ? parseDateStr(hidden.value) : null;
|
|
172
|
+
const view = selected ?? /* @__PURE__ */ new Date();
|
|
173
|
+
view.setDate(15);
|
|
174
|
+
positionPanel(panel, trigger);
|
|
175
|
+
renderCalendar(panel, {
|
|
176
|
+
view,
|
|
177
|
+
selected,
|
|
178
|
+
min,
|
|
179
|
+
max,
|
|
180
|
+
weekStart,
|
|
181
|
+
onPick: (d) => {
|
|
182
|
+
if (d === null) {
|
|
183
|
+
if (hidden) {
|
|
184
|
+
hidden.value = "";
|
|
185
|
+
hidden.dispatchEvent(new Event("change", { bubbles: true }));
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
if (hidden) {
|
|
189
|
+
hidden.value = toDateStr(d);
|
|
190
|
+
hidden.dispatchEvent(new Event("change", { bubbles: true }));
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
paint();
|
|
194
|
+
closePanel();
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
outsideHandler = (ev) => {
|
|
198
|
+
const t = ev.target instanceof Node ? ev.target : null;
|
|
199
|
+
if (!t) return;
|
|
200
|
+
if (panel.contains(t) || el.contains(t)) return;
|
|
201
|
+
closePanel();
|
|
202
|
+
};
|
|
203
|
+
document.addEventListener("mousedown", outsideHandler);
|
|
204
|
+
escHandler = (ev) => {
|
|
205
|
+
if (ev.key === "Escape") {
|
|
206
|
+
ev.preventDefault();
|
|
207
|
+
closePanel();
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
window.addEventListener("keydown", escHandler);
|
|
211
|
+
const reposition = () => {
|
|
212
|
+
if (openPicker === el && panel && !panel.hidden) positionPanel(panel, trigger);
|
|
213
|
+
};
|
|
214
|
+
window.addEventListener("scroll", reposition, { passive: true, once: false });
|
|
215
|
+
window.addEventListener("resize", reposition, { passive: true });
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function initDatePicker(root = document) {
|
|
219
|
+
if (typeof document === "undefined") return;
|
|
220
|
+
if (typeof window === "undefined") return;
|
|
221
|
+
const pickers = [];
|
|
222
|
+
if (root instanceof Element && root.matches(".date-picker[data-date-picker]")) pickers.push(root);
|
|
223
|
+
pickers.push(...Array.from(root.querySelectorAll(".date-picker[data-date-picker]")));
|
|
224
|
+
for (const p of pickers) setupPicker(p);
|
|
225
|
+
}
|
|
226
|
+
export {
|
|
227
|
+
initDatePicker
|
|
228
|
+
};
|