@fluixi-ui/bulk-editor 0.2.1

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.
@@ -0,0 +1,1202 @@
1
+ import { createNativeElement, spread, insert, createComponent, delegateEvents } from "@fluixi/dom";
2
+ import { For } from "@fluixi/core";
3
+ import { createSignal, createMemo, createEffect, onCleanup, untrack } from "@fluixi/reactive/signal";
4
+ import { createBulkEditorState } from "./state.js";
5
+ delegateEvents(["click", "change", "input", "blur", "focus", "keydown", "mousedown", "dblclick"]);
6
+ const DEFAULT_COL_W = 130;
7
+ const toggleAttr = (el, name, on) => on ? el.setAttribute(name, "") : el.removeAttribute(name);
8
+ const Padlock = () => (() => {
9
+ const _el$ = createNativeElement("svg", true);
10
+ spread({
11
+ element: _el$,
12
+ props: {
13
+ viewBox: "0 0 24 24",
14
+ width: "12",
15
+ height: "12",
16
+ fill: "none",
17
+ stroke: "currentColor",
18
+ "stroke-width": "2",
19
+ "stroke-linecap": "round",
20
+ "stroke-linejoin": "round",
21
+ "aria-hidden": "true"
22
+ },
23
+ isSVG: true
24
+ });
25
+ insert(_el$, (() => {
26
+ const _el$2 = createNativeElement("rect", true);
27
+ spread({
28
+ element: _el$2,
29
+ props: {
30
+ x: "5",
31
+ y: "11",
32
+ width: "14",
33
+ height: "10",
34
+ rx: "2"
35
+ },
36
+ isSVG: true
37
+ });
38
+ return _el$2;
39
+ })());
40
+ insert(_el$, (() => {
41
+ const _el$2 = createNativeElement("path", true);
42
+ spread({
43
+ element: _el$2,
44
+ props: {
45
+ d: "M8 11V7a4 4 0 0 1 8 0v4"
46
+ },
47
+ isSVG: true
48
+ });
49
+ return _el$2;
50
+ })());
51
+ return _el$;
52
+ })();
53
+ const HINTS = [["↑↓←→", "navigate"], ["⇧+↑↓←→", "extend"], ["Click header", "column"], ["Ctrl+A", "all"], ["Enter/F2", "edit"], ["Del", "clear"], ["Ctrl+D", "fill down"], ["Ctrl+C/V", "copy/paste"]];
54
+ function BulkEditor(props) {
55
+ const state = createBulkEditorState(props);
56
+ const labelWidth = props.labelWidth ?? 200;
57
+ let containerEl;
58
+ const [scrollEl, setScrollEl] = createSignal(void 0);
59
+ const [focused, setFocused] = createSignal(false);
60
+ const colWidth = (col) => state.columnWidths().get(col.key) ?? col.width ?? DEFAULT_COL_W;
61
+ const colBox = (col) => `min-width:${colWidth(col)}px;width:${colWidth(col)}px`;
62
+ const align = (col) => col.align ?? "right";
63
+ const showFooter = () => props.footer ?? state.columns().some((c) => !!c.aggregate);
64
+ const [dragCol, setDragCol] = createSignal(null);
65
+ const [dropCol, setDropCol] = createSignal(null);
66
+ const endReorder = () => {
67
+ setDragCol(null);
68
+ setDropCol(null);
69
+ };
70
+ const startResize = (e, col) => {
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+ const startX = e.clientX;
74
+ const startW = colWidth(col);
75
+ trackDrag((ev) => state.setColumnWidth(col.key, startW + (ev.clientX - startX)));
76
+ };
77
+ const hasActions = createMemo(() => props.rows().some((r) => !r.isGroup && r.rowActions != null));
78
+ const dataIndex = createMemo(() => {
79
+ const m = /* @__PURE__ */ new Map();
80
+ state.dataRows().forEach((r, i) => m.set(r.id, i));
81
+ return m;
82
+ });
83
+ createEffect(() => {
84
+ const scroll = scrollEl();
85
+ if (!scroll) return;
86
+ const rng = state.selectionRange();
87
+ const ed = state.editing();
88
+ state.errors();
89
+ state.lockedCells();
90
+ state.rowCount();
91
+ state.colCount();
92
+ scroll.querySelectorAll("[data-cell]").forEach((el) => {
93
+ const [r, c] = el.dataset.cell.split(":").map(Number);
94
+ const inRng = rng ? r >= rng.r1 && r <= rng.r2 && c >= rng.c1 && c <= rng.c2 : false;
95
+ const isAnchor = state.isAnchor(r, c);
96
+ const isEdit = ed ? ed.row === r && ed.col === c : false;
97
+ toggleAttr(el, "data-selected", inRng && !isEdit);
98
+ toggleAttr(el, "data-anchor", isAnchor && !isEdit);
99
+ toggleAttr(el, "data-editing", isEdit);
100
+ toggleAttr(el, "data-locked", state.isCellLockedAt(r, c));
101
+ if (inRng) el.setAttribute("aria-selected", "true");
102
+ else el.removeAttribute("aria-selected");
103
+ const err = state.getErrorAt(r, c);
104
+ toggleAttr(el, "data-invalid", !!err);
105
+ if (err) {
106
+ el.setAttribute("title", err);
107
+ el.setAttribute("aria-invalid", "true");
108
+ } else {
109
+ el.removeAttribute("title");
110
+ el.removeAttribute("aria-invalid");
111
+ }
112
+ });
113
+ scroll.querySelectorAll("[data-col-head]").forEach((el) => {
114
+ const c = Number(el.dataset.colHead);
115
+ const full = !!rng && rng.c1 <= c && c <= rng.c2 && rng.r1 === 0 && rng.r2 === state.rowCount() - 1;
116
+ toggleAttr(el, "data-selected", full);
117
+ });
118
+ scroll.querySelectorAll("[data-row-lbl]").forEach((el) => {
119
+ const r = Number(el.dataset.rowLbl);
120
+ const full = !!rng && rng.r1 <= r && r <= rng.r2 && rng.c1 === 0 && rng.c2 === state.colCount() - 1;
121
+ toggleAttr(el, "data-selected", full);
122
+ const row = state.rowAt(r);
123
+ if (row) el.setAttribute("data-lock-state", state.getRowLockState(row.id));
124
+ });
125
+ });
126
+ const focusEditor = (r, c, selectAll) => requestAnimationFrame(() => {
127
+ const inp = scrollEl()?.querySelector(`[data-cell="${r}:${c}"] input`);
128
+ if (!inp) return;
129
+ inp.focus();
130
+ if (inp.type !== "number") {
131
+ if (selectAll) inp.select();
132
+ else inp.setSelectionRange(inp.value.length, inp.value.length);
133
+ }
134
+ });
135
+ const beginEdit = (r, c, initChar) => {
136
+ if (state.startEdit(r, c, initChar)) focusEditor(r, c, initChar === void 0);
137
+ };
138
+ const tabMove = (back) => {
139
+ const s = untrack(state.selection);
140
+ if (!s) return;
141
+ const {
142
+ row: r,
143
+ col: c
144
+ } = s.anchor;
145
+ const nc = state.colCount();
146
+ if (back) state.selectCell(c > 0 ? r : r - 1, c > 0 ? c - 1 : nc - 1);
147
+ else state.selectCell(c < nc - 1 ? r : r + 1, c < nc - 1 ? c + 1 : 0);
148
+ };
149
+ const copy = () => navigator.clipboard?.writeText(state.getSelectionTSV()).catch(() => {
150
+ });
151
+ const paste = async () => {
152
+ const text = await navigator.clipboard?.readText().catch(() => "");
153
+ if (text) state.pasteTSV(text);
154
+ };
155
+ const onKeyDown = (e) => {
156
+ const ed = state.editing();
157
+ const target = e.target;
158
+ const inField = target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement;
159
+ if (inField && !ed) return;
160
+ const s = state.selection();
161
+ if (!s && !ed) return;
162
+ if (ed) {
163
+ switch (e.key) {
164
+ case "Enter":
165
+ e.preventDefault();
166
+ state.commitEdit();
167
+ state.move(1, 0);
168
+ containerEl?.focus();
169
+ break;
170
+ case "Tab":
171
+ e.preventDefault();
172
+ state.commitEdit();
173
+ tabMove(e.shiftKey);
174
+ containerEl?.focus();
175
+ break;
176
+ case "Escape":
177
+ e.preventDefault();
178
+ state.cancelEdit();
179
+ containerEl?.focus();
180
+ break;
181
+ }
182
+ return;
183
+ }
184
+ const {
185
+ row: r,
186
+ col: c
187
+ } = s.anchor;
188
+ switch (e.key) {
189
+ case "ArrowUp":
190
+ e.preventDefault();
191
+ state.move(-1, 0, e.shiftKey);
192
+ break;
193
+ case "ArrowDown":
194
+ e.preventDefault();
195
+ state.move(1, 0, e.shiftKey);
196
+ break;
197
+ case "ArrowLeft":
198
+ e.preventDefault();
199
+ state.move(0, -1, e.shiftKey);
200
+ break;
201
+ case "ArrowRight":
202
+ e.preventDefault();
203
+ state.move(0, 1, e.shiftKey);
204
+ break;
205
+ case "Home":
206
+ e.preventDefault();
207
+ state.selectCell(r, 0, e.shiftKey);
208
+ break;
209
+ case "End":
210
+ e.preventDefault();
211
+ state.selectCell(r, state.colCount() - 1, e.shiftKey);
212
+ break;
213
+ case "Tab":
214
+ e.preventDefault();
215
+ tabMove(e.shiftKey);
216
+ break;
217
+ case "Enter":
218
+ case "F2":
219
+ e.preventDefault();
220
+ beginEdit(r, c);
221
+ break;
222
+ case "Delete":
223
+ case "Backspace":
224
+ e.preventDefault();
225
+ state.clearRange();
226
+ break;
227
+ case "Escape":
228
+ e.preventDefault();
229
+ state.clearSelection();
230
+ break;
231
+ case " ":
232
+ e.preventDefault();
233
+ if (state.colAt(c)?.type === "checkbox") state.toggleCheckbox(r, c);
234
+ else beginEdit(r, c);
235
+ break;
236
+ default:
237
+ if (e.ctrlKey || e.metaKey) {
238
+ const k = e.key.toLowerCase();
239
+ if (k === "a") {
240
+ e.preventDefault();
241
+ state.selectAll();
242
+ } else if (k === "d") {
243
+ e.preventDefault();
244
+ state.fillDown();
245
+ } else if (k === "c") {
246
+ e.preventDefault();
247
+ copy();
248
+ } else if (k === "v") {
249
+ e.preventDefault();
250
+ paste();
251
+ } else if (k === "z") {
252
+ e.preventDefault();
253
+ e.shiftKey ? state.redo() : state.undo();
254
+ } else if (k === "y") {
255
+ e.preventDefault();
256
+ state.redo();
257
+ }
258
+ } else if (!e.altKey && e.key.length === 1) {
259
+ e.preventDefault();
260
+ beginEdit(r, c, e.key);
261
+ }
262
+ }
263
+ };
264
+ const cellFromPoint = (x, y) => {
265
+ const el = document.elementFromPoint(x, y)?.closest("[data-cell]");
266
+ if (!el?.dataset.cell) return null;
267
+ const [r, c] = el.dataset.cell.split(":").map(Number);
268
+ return {
269
+ r,
270
+ c
271
+ };
272
+ };
273
+ const trackDrag = (onMove, onEnd) => {
274
+ const move = (ev) => onMove(ev);
275
+ const up = () => {
276
+ document.removeEventListener("mousemove", move);
277
+ document.removeEventListener("mouseup", up);
278
+ onEnd?.();
279
+ };
280
+ document.addEventListener("mousemove", move);
281
+ document.addEventListener("mouseup", up);
282
+ };
283
+ const onCellMouseDown = (e, r, c) => {
284
+ const col = state.colAt(c);
285
+ if (col?.type === "checkbox") return;
286
+ if (e.target.tagName === "INPUT") return;
287
+ e.preventDefault();
288
+ containerEl?.focus();
289
+ if (e.shiftKey && untrack(state.selection)) {
290
+ state.selectCell(r, c, true);
291
+ return;
292
+ }
293
+ state.selectCell(r, c);
294
+ trackDrag((ev) => {
295
+ const pos = cellFromPoint(ev.clientX, ev.clientY);
296
+ if (pos) state.selectCell(pos.r, pos.c, true);
297
+ });
298
+ };
299
+ const onFillHandleDown = (e) => {
300
+ e.preventDefault();
301
+ e.stopPropagation();
302
+ trackDrag((ev) => {
303
+ const pos = cellFromPoint(ev.clientX, ev.clientY);
304
+ if (pos) state.selectCell(pos.r, pos.c, true);
305
+ }, () => state.fillDown());
306
+ };
307
+ const [menu, setMenu] = createSignal(null);
308
+ const closeMenu = () => setMenu(null);
309
+ const openMenu = (e, r, c) => {
310
+ if (props.contextMenu === false) return;
311
+ e.preventDefault();
312
+ containerEl?.focus();
313
+ if (!state.isSelected(r, c)) state.selectCell(r, c);
314
+ setMenu({
315
+ x: e.clientX,
316
+ y: e.clientY,
317
+ kind: "cell",
318
+ r,
319
+ c
320
+ });
321
+ };
322
+ const openColumnMenu = (e, c) => {
323
+ if (props.contextMenu === false) return;
324
+ e.preventDefault();
325
+ setMenu({
326
+ x: e.clientX,
327
+ y: e.clientY,
328
+ kind: "column",
329
+ r: -1,
330
+ c
331
+ });
332
+ };
333
+ const cellMenuItems = (r, c) => {
334
+ const row = state.rowAt(r);
335
+ const col = state.colAt(c);
336
+ const rng = state.selectionRange();
337
+ const multi = !!rng && (rng.r2 > rng.r1 || rng.c2 > rng.c1);
338
+ const items = [];
339
+ if (row && col && !state.isColumnReadOnly(col)) {
340
+ const locked = state.isCellLockedAt(r, c);
341
+ items.push({
342
+ label: locked ? "Unlock cell" : "Lock cell",
343
+ run: () => state.toggleCellLock(row.id, col.key)
344
+ });
345
+ }
346
+ if (multi) {
347
+ items.push({
348
+ label: "Lock selection",
349
+ run: () => state.lockSelection(true)
350
+ });
351
+ items.push({
352
+ label: "Unlock selection",
353
+ run: () => state.lockSelection(false)
354
+ });
355
+ }
356
+ if (row) {
357
+ const locked = state.getRowLockState(row.id) === "all";
358
+ items.push({
359
+ label: locked ? "Unlock row" : "Lock row",
360
+ run: () => state.toggleRowLock(row.id)
361
+ });
362
+ }
363
+ items.push({
364
+ label: "",
365
+ separator: true
366
+ }, {
367
+ label: "Copy",
368
+ run: copy
369
+ }, {
370
+ label: "Clear",
371
+ run: () => state.clearRange()
372
+ });
373
+ if (props.contextMenuItems && row && col) {
374
+ const custom = props.contextMenuItems({
375
+ row,
376
+ col,
377
+ r,
378
+ c
379
+ });
380
+ if (custom.length) items.push({
381
+ label: "",
382
+ separator: true
383
+ }, ...custom);
384
+ }
385
+ return items;
386
+ };
387
+ const columnMenuItems = (c) => {
388
+ const col = state.colAt(c);
389
+ if (!col) return [];
390
+ const items = [];
391
+ if (props.sortable ?? true) {
392
+ items.push({
393
+ label: "Sort ascending",
394
+ run: () => state.setSort({
395
+ colKey: col.key,
396
+ dir: "asc"
397
+ })
398
+ });
399
+ items.push({
400
+ label: "Sort descending",
401
+ run: () => state.setSort({
402
+ colKey: col.key,
403
+ dir: "desc"
404
+ })
405
+ });
406
+ if (state.sort()?.colKey === col.key) items.push({
407
+ label: "Clear sort",
408
+ run: () => state.setSort(null)
409
+ });
410
+ }
411
+ if (!state.isColumnReadOnly(col)) {
412
+ const locked = state.getColumnLockState(col.key) === "all";
413
+ items.push({
414
+ label: "",
415
+ separator: true
416
+ }, {
417
+ label: locked ? "Unlock column" : "Lock column",
418
+ run: () => state.toggleColumnLock(col.key)
419
+ });
420
+ }
421
+ if (state.getColumnFilter(col.key)) items.push({
422
+ label: "Clear filter",
423
+ run: () => state.setColumnFilter(col.key, "")
424
+ });
425
+ if (state.columnWidths().has(col.key)) items.push({
426
+ label: "Reset width",
427
+ run: () => state.resetColumnWidth(col.key)
428
+ });
429
+ if (props.columnMenuItems) {
430
+ const custom = props.columnMenuItems({
431
+ col,
432
+ c
433
+ });
434
+ if (custom.length) items.push({
435
+ label: "",
436
+ separator: true
437
+ }, ...custom);
438
+ }
439
+ return items;
440
+ };
441
+ const menuItems = () => {
442
+ const m = menu();
443
+ if (!m) return [];
444
+ return m.kind === "column" ? columnMenuItems(m.c) : cellMenuItems(m.r, m.c);
445
+ };
446
+ createEffect(() => {
447
+ if (!menu()) return;
448
+ const onDown = (ev) => {
449
+ if (!ev.target.closest(".flx-ui-be-menu")) closeMenu();
450
+ };
451
+ const onScroll = () => closeMenu();
452
+ const onKey = (ev) => {
453
+ if (ev.key === "Escape") {
454
+ ev.stopPropagation();
455
+ closeMenu();
456
+ }
457
+ };
458
+ document.addEventListener("mousedown", onDown, true);
459
+ window.addEventListener("scroll", onScroll, true);
460
+ document.addEventListener("keydown", onKey, true);
461
+ onCleanup(() => {
462
+ document.removeEventListener("mousedown", onDown, true);
463
+ window.removeEventListener("scroll", onScroll, true);
464
+ document.removeEventListener("keydown", onKey, true);
465
+ });
466
+ });
467
+ onCleanup(() => setScrollEl(void 0));
468
+ const cellContent = (row, col, r, c) => {
469
+ if (col.type === "checkbox") {
470
+ return (() => {
471
+ const _el$ = createNativeElement("div");
472
+ spread({
473
+ element: _el$,
474
+ props: {
475
+ className: "flx-ui-be-check-wrap"
476
+ }
477
+ });
478
+ insert(_el$, (() => {
479
+ const _el$2 = createNativeElement("input");
480
+ spread({
481
+ element: _el$2,
482
+ props: {
483
+ className: "flx-ui-be-check",
484
+ type: "checkbox",
485
+ checked: () => state.getRawAt(r, c) === "1",
486
+ disabled: () => (col.disable?.({
487
+ row,
488
+ col
489
+ }) ?? false) || state.isCellLockedAt(r, c),
490
+ onClick: (e) => e.stopPropagation(),
491
+ onChange: (e) => {
492
+ e.stopPropagation();
493
+ state.setCellAt(r, c, e.target.checked ? "1" : "0");
494
+ }
495
+ }
496
+ });
497
+ return _el$2;
498
+ })());
499
+ return _el$;
500
+ })();
501
+ }
502
+ if (state.isEditing(r, c)) {
503
+ return (() => {
504
+ const _el$ = createNativeElement("input");
505
+ spread({
506
+ element: _el$,
507
+ props: {
508
+ className: "flx-ui-be-input",
509
+ type: "text",
510
+ inputmode: () => col.type === "number" ? "decimal" : void 0,
511
+ style: () => `text-align:${align(col)}`,
512
+ value: () => state.editBuffer(),
513
+ onInput: (e) => state.setEditBuffer(e.target.value),
514
+ onBlur: () => {
515
+ state.commitEdit();
516
+ containerEl?.focus();
517
+ },
518
+ onClick: (e) => e.stopPropagation()
519
+ }
520
+ });
521
+ return _el$;
522
+ })();
523
+ }
524
+ return (() => {
525
+ const _el$ = createNativeElement("div");
526
+ spread({
527
+ element: _el$,
528
+ props: {
529
+ className: "flx-ui-be-val",
530
+ style: () => `justify-content:${align(col) === "left" ? "flex-start" : align(col) === "center" ? "center" : "flex-end"}`
531
+ }
532
+ });
533
+ insert(_el$, () => {
534
+ const v = state.getDisplayAt(r, c);
535
+ return v ? v : (() => {
536
+ const _el$2 = createNativeElement("span");
537
+ spread({
538
+ element: _el$2,
539
+ props: {
540
+ className: "flx-ui-be-ph"
541
+ }
542
+ });
543
+ insert(_el$2, () => col.placeholder ?? "", null);
544
+ return _el$2;
545
+ })();
546
+ }, null);
547
+ return _el$;
548
+ })();
549
+ };
550
+ return (() => {
551
+ const _el$ = createNativeElement("div");
552
+ spread({
553
+ element: _el$,
554
+ props: {
555
+ className: () => props.class ? `flx-ui-bulk-editor ${props.class}` : "flx-ui-bulk-editor",
556
+ role: "grid",
557
+ tabindex: 0,
558
+ "aria-colcount": () => state.colCount() + 1,
559
+ ref: (el) => {
560
+ containerEl = el;
561
+ },
562
+ onFocus: () => setFocused(true),
563
+ onBlur: (e) => {
564
+ if (!containerEl?.contains(e.relatedTarget)) setFocused(false);
565
+ },
566
+ onKeyDown
567
+ }
568
+ });
569
+ insert(_el$, (() => {
570
+ const _el$2 = createNativeElement("div");
571
+ spread({
572
+ element: _el$2,
573
+ props: {
574
+ className: "flx-ui-be-scroll",
575
+ ref: (el) => setScrollEl(el),
576
+ style: () => props.maxHeight ? `max-height:${props.maxHeight}` : void 0
577
+ }
578
+ });
579
+ insert(_el$2, (() => {
580
+ const _el$3 = createNativeElement("table");
581
+ spread({
582
+ element: _el$3,
583
+ props: {
584
+ className: "flx-ui-be-table"
585
+ }
586
+ });
587
+ insert(_el$3, (() => {
588
+ const _el$4 = createNativeElement("thead");
589
+ insert(_el$4, (() => {
590
+ const _el$5 = createNativeElement("tr");
591
+ spread({
592
+ element: _el$5,
593
+ props: {
594
+ role: "row"
595
+ }
596
+ });
597
+ insert(_el$5, (() => {
598
+ const _el$6 = createNativeElement("th");
599
+ spread({
600
+ element: _el$6,
601
+ props: {
602
+ className: "flx-ui-be-corner",
603
+ style: () => `min-width:${labelWidth}px;width:${labelWidth}px`,
604
+ onClick: () => state.selectAll(),
605
+ title: "Select all (Ctrl+A)"
606
+ }
607
+ });
608
+ insert(_el$6, createMemo(() => typeof props.labelHeader === "function" ? props.labelHeader() : props.labelHeader ?? ""), null);
609
+ return _el$6;
610
+ })());
611
+ insert(_el$5, createMemo(() => createComponent(For, {
612
+ get each() {
613
+ return state.columns;
614
+ },
615
+ get children() {
616
+ return (col, ci) => (() => {
617
+ const _el$6 = createNativeElement("th");
618
+ spread({
619
+ element: _el$6,
620
+ props: {
621
+ className: "flx-ui-be-colh",
622
+ role: "columnheader",
623
+ "data-col-head": () => String(ci()),
624
+ "data-drop-target": () => dropCol() === col.key ? "" : void 0,
625
+ style: () => `${colBox(col)};text-align:${align(col)}`,
626
+ draggable: () => props.reorderable !== false,
627
+ onClick: () => state.selectColumn(ci()),
628
+ onContextMenu: (e) => openColumnMenu(e, ci()),
629
+ onDragStart: (e) => {
630
+ setDragCol(col.key);
631
+ e.dataTransfer?.setData("text/plain", col.key);
632
+ },
633
+ onDragEnter: () => {
634
+ if (dragCol() && dragCol() !== col.key) setDropCol(col.key);
635
+ },
636
+ onDragOver: (e) => {
637
+ if (dragCol() && dragCol() !== col.key) e.preventDefault();
638
+ },
639
+ onDrop: (e) => {
640
+ e.preventDefault();
641
+ const from = dragCol();
642
+ if (from) state.moveColumn(from, col.key);
643
+ endReorder();
644
+ },
645
+ onDragEnd: endReorder
646
+ }
647
+ });
648
+ insert(_el$6, createMemo(() => props.resizable !== false && (() => {
649
+ const _el$7 = createNativeElement("div");
650
+ spread({
651
+ element: _el$7,
652
+ props: {
653
+ className: "flx-ui-be-col-resize",
654
+ onMouseDown: (e) => startResize(e, col)
655
+ }
656
+ });
657
+ return _el$7;
658
+ })()), null);
659
+ insert(_el$6, (() => {
660
+ const _el$7 = createNativeElement("div");
661
+ spread({
662
+ element: _el$7,
663
+ props: {
664
+ className: "flx-ui-be-colh-inner"
665
+ }
666
+ });
667
+ insert(_el$7, createMemo(() => col.type === "checkbox" && (() => {
668
+ const _el$8 = createNativeElement("input");
669
+ spread({
670
+ element: _el$8,
671
+ props: {
672
+ type: "checkbox",
673
+ className: "flx-ui-be-head-check",
674
+ "aria-label": "Toggle all",
675
+ ref: (el) => createEffect(() => {
676
+ const st = state.getColumnCheckedState(col.key);
677
+ el.checked = st === "all";
678
+ el.indeterminate = st === "some";
679
+ }),
680
+ onClick: (e) => e.stopPropagation(),
681
+ onChange: (e) => {
682
+ e.stopPropagation();
683
+ state.toggleColumnChecked(col.key);
684
+ }
685
+ }
686
+ });
687
+ return _el$8;
688
+ })()), null);
689
+ insert(_el$7, (() => {
690
+ const _el$8 = createNativeElement("span");
691
+ spread({
692
+ element: _el$8,
693
+ props: {
694
+ className: "flx-ui-be-colh-label"
695
+ }
696
+ });
697
+ insert(_el$8, createMemo(() => typeof col.header === "function" ? col.header() : col.header), null);
698
+ return _el$8;
699
+ })());
700
+ insert(_el$7, () => col.headerExtra, null);
701
+ insert(_el$7, createMemo(() => (props.sortable ?? true) && (() => {
702
+ const _el$8 = createNativeElement("button");
703
+ spread({
704
+ element: _el$8,
705
+ props: {
706
+ type: "button",
707
+ className: "flx-ui-be-sort",
708
+ "aria-label": "Sort column",
709
+ onClick: (e) => {
710
+ e.stopPropagation();
711
+ state.sortBy(col.key);
712
+ }
713
+ }
714
+ });
715
+ insert(_el$8, () => {
716
+ const s = state.sort();
717
+ return s && s.colKey === col.key ? s.dir === "asc" ? "▲" : "▼" : "↕";
718
+ }, null);
719
+ return _el$8;
720
+ })()), null);
721
+ return _el$7;
722
+ })());
723
+ return _el$6;
724
+ })();
725
+ }
726
+ })), null);
727
+ insert(_el$5, createMemo(() => hasActions() && (() => {
728
+ const _el$6 = createNativeElement("th");
729
+ spread({
730
+ element: _el$6,
731
+ props: {
732
+ className: "flx-ui-be-actions-head"
733
+ }
734
+ });
735
+ return _el$6;
736
+ })()), null);
737
+ return _el$5;
738
+ })());
739
+ insert(_el$4, createMemo(() => props.filterRow && (() => {
740
+ const _el$5 = createNativeElement("tr");
741
+ spread({
742
+ element: _el$5,
743
+ props: {
744
+ role: "row",
745
+ className: "flx-ui-be-filter-row"
746
+ }
747
+ });
748
+ insert(_el$5, (() => {
749
+ const _el$6 = createNativeElement("th");
750
+ spread({
751
+ element: _el$6,
752
+ props: {
753
+ className: "flx-ui-be-filter-corner",
754
+ style: () => `min-width:${labelWidth}px;width:${labelWidth}px`
755
+ }
756
+ });
757
+ return _el$6;
758
+ })());
759
+ insert(_el$5, createMemo(() => createComponent(For, {
760
+ get each() {
761
+ return state.columns;
762
+ },
763
+ get children() {
764
+ return (col) => (() => {
765
+ const _el$6 = createNativeElement("th");
766
+ spread({
767
+ element: _el$6,
768
+ props: {
769
+ className: "flx-ui-be-filter-cell",
770
+ style: () => colBox(col)
771
+ }
772
+ });
773
+ insert(_el$6, (() => {
774
+ const _el$7 = createNativeElement("input");
775
+ spread({
776
+ element: _el$7,
777
+ props: {
778
+ className: "flx-ui-be-filter",
779
+ type: "text",
780
+ placeholder: () => col.type === "number" ? "e.g. >20" : "Filter…",
781
+ value: () => state.getColumnFilter(col.key),
782
+ onInput: (e) => state.setColumnFilter(col.key, e.target.value)
783
+ }
784
+ });
785
+ return _el$7;
786
+ })());
787
+ return _el$6;
788
+ })();
789
+ }
790
+ })), null);
791
+ insert(_el$5, createMemo(() => hasActions() && (() => {
792
+ const _el$6 = createNativeElement("th");
793
+ spread({
794
+ element: _el$6,
795
+ props: {
796
+ className: "flx-ui-be-filter-cell"
797
+ }
798
+ });
799
+ return _el$6;
800
+ })()), null);
801
+ return _el$5;
802
+ })()), null);
803
+ return _el$4;
804
+ })());
805
+ insert(_el$3, (() => {
806
+ const _el$4 = createNativeElement("tbody");
807
+ insert(_el$4, createMemo(() => createComponent(For, {
808
+ get each() {
809
+ return state.viewRows;
810
+ },
811
+ get children() {
812
+ return (row) => {
813
+ if (row.isGroup) {
814
+ return (() => {
815
+ const _el$5 = createNativeElement("tr");
816
+ spread({
817
+ element: _el$5,
818
+ props: {
819
+ role: "row",
820
+ className: "flx-ui-be-group"
821
+ }
822
+ });
823
+ insert(_el$5, (() => {
824
+ const _el$6 = createNativeElement("th");
825
+ spread({
826
+ element: _el$6,
827
+ props: {
828
+ className: "flx-ui-be-group-label",
829
+ style: () => `min-width:${labelWidth}px;width:${labelWidth}px`
830
+ }
831
+ });
832
+ insert(_el$6, (() => {
833
+ const _el$7 = createNativeElement("div");
834
+ spread({
835
+ element: _el$7,
836
+ props: {
837
+ className: "flx-ui-be-group-head"
838
+ }
839
+ });
840
+ insert(_el$7, createMemo(() => row.thumbnail && (() => {
841
+ const _el$8 = createNativeElement("img");
842
+ spread({
843
+ element: _el$8,
844
+ props: {
845
+ className: "flx-ui-be-group-thumb",
846
+ src: () => row.thumbnail,
847
+ alt: ""
848
+ }
849
+ });
850
+ return _el$8;
851
+ })()), null);
852
+ insert(_el$7, (() => {
853
+ const _el$8 = createNativeElement("span");
854
+ insert(_el$8, () => row.label, null);
855
+ return _el$8;
856
+ })());
857
+ insert(_el$7, createMemo(() => row.subLabel && (() => {
858
+ const _el$8 = createNativeElement("span");
859
+ spread({
860
+ element: _el$8,
861
+ props: {
862
+ className: "flx-ui-be-rowh-sub"
863
+ }
864
+ });
865
+ insert(_el$8, () => row.subLabel, null);
866
+ return _el$8;
867
+ })()), null);
868
+ return _el$7;
869
+ })());
870
+ return _el$6;
871
+ })());
872
+ insert(_el$5, createMemo(() => createComponent(For, {
873
+ get each() {
874
+ return state.columns;
875
+ },
876
+ get children() {
877
+ return (col) => (() => {
878
+ const _el$6 = createNativeElement("td");
879
+ spread({
880
+ element: _el$6,
881
+ props: {
882
+ className: "flx-ui-be-group-cell",
883
+ style: () => colBox(col)
884
+ }
885
+ });
886
+ insert(_el$6, createMemo(() => row.groupData ? row.groupData[col.key] : null), null);
887
+ return _el$6;
888
+ })();
889
+ }
890
+ })), null);
891
+ insert(_el$5, createMemo(() => hasActions() && (() => {
892
+ const _el$6 = createNativeElement("td");
893
+ spread({
894
+ element: _el$6,
895
+ props: {
896
+ className: "flx-ui-be-group-cell"
897
+ }
898
+ });
899
+ return _el$6;
900
+ })()), null);
901
+ return _el$5;
902
+ })();
903
+ }
904
+ const di = () => dataIndex().get(row.id) ?? 0;
905
+ return (() => {
906
+ const _el$5 = createNativeElement("tr");
907
+ spread({
908
+ element: _el$5,
909
+ props: {
910
+ role: "row"
911
+ }
912
+ });
913
+ insert(_el$5, (() => {
914
+ const _el$6 = createNativeElement("th");
915
+ spread({
916
+ element: _el$6,
917
+ props: {
918
+ className: "flx-ui-be-rowh",
919
+ role: "rowheader",
920
+ "data-row-lbl": () => String(di()),
921
+ style: () => `min-width:${labelWidth}px;width:${labelWidth}px`,
922
+ onClick: () => state.selectRow(di())
923
+ }
924
+ });
925
+ insert(_el$6, (() => {
926
+ const _el$7 = createNativeElement("div");
927
+ spread({
928
+ element: _el$7,
929
+ props: {
930
+ className: "flx-ui-be-rowh-inner"
931
+ }
932
+ });
933
+ insert(_el$7, (() => {
934
+ const _el$8 = createNativeElement("button");
935
+ spread({
936
+ element: _el$8,
937
+ props: {
938
+ className: "flx-ui-be-row-lock",
939
+ type: "button",
940
+ tabindex: () => -1,
941
+ "aria-label": "Lock row",
942
+ onMouseDown: (e) => e.stopPropagation(),
943
+ onClick: (e) => {
944
+ e.stopPropagation();
945
+ state.toggleRowLock(row.id);
946
+ }
947
+ }
948
+ });
949
+ insert(_el$8, createMemo(() => createComponent(Padlock, {})), null);
950
+ return _el$8;
951
+ })());
952
+ insert(_el$7, (() => {
953
+ const _el$8 = createNativeElement("div");
954
+ spread({
955
+ element: _el$8,
956
+ props: {
957
+ className: "flx-ui-be-rowh-text"
958
+ }
959
+ });
960
+ insert(_el$8, (() => {
961
+ const _el$9 = createNativeElement("div");
962
+ spread({
963
+ element: _el$9,
964
+ props: {
965
+ className: "flx-ui-be-rowh-label"
966
+ }
967
+ });
968
+ insert(_el$9, () => row.label, null);
969
+ return _el$9;
970
+ })());
971
+ insert(_el$8, createMemo(() => row.subLabel && (() => {
972
+ const _el$9 = createNativeElement("div");
973
+ spread({
974
+ element: _el$9,
975
+ props: {
976
+ className: "flx-ui-be-rowh-sub"
977
+ }
978
+ });
979
+ insert(_el$9, () => row.subLabel, null);
980
+ return _el$9;
981
+ })()), null);
982
+ return _el$8;
983
+ })());
984
+ return _el$7;
985
+ })());
986
+ return _el$6;
987
+ })());
988
+ insert(_el$5, createMemo(() => createComponent(For, {
989
+ get each() {
990
+ return state.columns;
991
+ },
992
+ get children() {
993
+ return (col, ci) => (() => {
994
+ const _el$6 = createNativeElement("td");
995
+ spread({
996
+ element: _el$6,
997
+ props: {
998
+ className: "flx-ui-be-cell",
999
+ role: "gridcell",
1000
+ "data-cell": () => `${di()}:${ci()}`,
1001
+ style: () => `${colBox(col)};text-align:${align(col)}`,
1002
+ onMouseDown: (e) => onCellMouseDown(e, di(), ci()),
1003
+ onDblClick: () => beginEdit(di(), ci()),
1004
+ onContextMenu: (e) => openMenu(e, di(), ci())
1005
+ }
1006
+ });
1007
+ insert(_el$6, () => cellContent(row, col, di(), ci()), null);
1008
+ insert(_el$6, createMemo(() => !state.isColumnReadOnly(col) && (() => {
1009
+ const _el$7 = createNativeElement("button");
1010
+ spread({
1011
+ element: _el$7,
1012
+ props: {
1013
+ className: "flx-ui-be-lock",
1014
+ type: "button",
1015
+ tabindex: () => -1,
1016
+ "aria-label": "Lock cell",
1017
+ onMouseDown: (e) => e.stopPropagation(),
1018
+ onClick: (e) => {
1019
+ e.stopPropagation();
1020
+ state.toggleCellLock(row.id, col.key);
1021
+ }
1022
+ }
1023
+ });
1024
+ insert(_el$7, createMemo(() => createComponent(Padlock, {})), null);
1025
+ return _el$7;
1026
+ })()), null);
1027
+ insert(_el$6, createMemo(() => state.isAnchor(di(), ci()) && !state.isEditing(di(), ci()) && (() => {
1028
+ const _el$7 = createNativeElement("div");
1029
+ spread({
1030
+ element: _el$7,
1031
+ props: {
1032
+ className: "flx-ui-be-fill-handle",
1033
+ onMouseDown: onFillHandleDown
1034
+ }
1035
+ });
1036
+ return _el$7;
1037
+ })()), null);
1038
+ return _el$6;
1039
+ })();
1040
+ }
1041
+ })), null);
1042
+ insert(_el$5, createMemo(() => hasActions() && (() => {
1043
+ const _el$6 = createNativeElement("td");
1044
+ spread({
1045
+ element: _el$6,
1046
+ props: {
1047
+ className: "flx-ui-be-actions",
1048
+ onMouseDown: (e) => e.stopPropagation()
1049
+ }
1050
+ });
1051
+ insert(_el$6, () => row.rowActions, null);
1052
+ return _el$6;
1053
+ })()), null);
1054
+ return _el$5;
1055
+ })();
1056
+ };
1057
+ }
1058
+ })), null);
1059
+ return _el$4;
1060
+ })());
1061
+ insert(_el$3, createMemo(() => showFooter() && (() => {
1062
+ const _el$4 = createNativeElement("tfoot");
1063
+ insert(_el$4, (() => {
1064
+ const _el$5 = createNativeElement("tr");
1065
+ spread({
1066
+ element: _el$5,
1067
+ props: {
1068
+ role: "row",
1069
+ className: "flx-ui-be-foot-row"
1070
+ }
1071
+ });
1072
+ insert(_el$5, (() => {
1073
+ const _el$6 = createNativeElement("th");
1074
+ spread({
1075
+ element: _el$6,
1076
+ props: {
1077
+ className: "flx-ui-be-foot-corner",
1078
+ style: () => `min-width:${labelWidth}px;width:${labelWidth}px`
1079
+ }
1080
+ });
1081
+ insert(_el$6, createMemo(() => typeof props.footerLabel === "function" ? props.footerLabel() : props.footerLabel ?? ""), null);
1082
+ return _el$6;
1083
+ })());
1084
+ insert(_el$5, createMemo(() => createComponent(For, {
1085
+ get each() {
1086
+ return state.columns;
1087
+ },
1088
+ get children() {
1089
+ return (col) => (() => {
1090
+ const _el$6 = createNativeElement("td");
1091
+ spread({
1092
+ element: _el$6,
1093
+ props: {
1094
+ className: "flx-ui-be-foot-cell",
1095
+ style: () => `${colBox(col)};text-align:${align(col)}`
1096
+ }
1097
+ });
1098
+ insert(_el$6, () => state.getColumnAggregate(col.key), null);
1099
+ return _el$6;
1100
+ })();
1101
+ }
1102
+ })), null);
1103
+ insert(_el$5, createMemo(() => hasActions() && (() => {
1104
+ const _el$6 = createNativeElement("td");
1105
+ spread({
1106
+ element: _el$6,
1107
+ props: {
1108
+ className: "flx-ui-be-foot-cell"
1109
+ }
1110
+ });
1111
+ return _el$6;
1112
+ })()), null);
1113
+ return _el$5;
1114
+ })());
1115
+ return _el$4;
1116
+ })()), null);
1117
+ return _el$3;
1118
+ })());
1119
+ return _el$2;
1120
+ })());
1121
+ insert(_el$, createMemo(() => focused() && state.hasSelection() && (() => {
1122
+ const _el$2 = createNativeElement("div");
1123
+ spread({
1124
+ element: _el$2,
1125
+ props: {
1126
+ className: "flx-ui-be-hints"
1127
+ }
1128
+ });
1129
+ insert(_el$2, createMemo(() => createComponent(For, {
1130
+ get each() {
1131
+ return () => HINTS;
1132
+ },
1133
+ get children() {
1134
+ return ([k, label]) => (() => {
1135
+ const _el$3 = createNativeElement("span");
1136
+ insert(_el$3, (() => {
1137
+ const _el$4 = createNativeElement("kbd");
1138
+ insert(_el$4, k);
1139
+ return _el$4;
1140
+ })());
1141
+ insert(_el$3, " ");
1142
+ insert(_el$3, label);
1143
+ return _el$3;
1144
+ })();
1145
+ }
1146
+ })), null);
1147
+ return _el$2;
1148
+ })()), null);
1149
+ insert(_el$, () => {
1150
+ const m = menu();
1151
+ return m ? (() => {
1152
+ const _el$2 = createNativeElement("div");
1153
+ spread({
1154
+ element: _el$2,
1155
+ props: {
1156
+ className: "flx-ui-be-menu",
1157
+ role: "menu",
1158
+ style: () => `left:${m.x}px;top:${m.y}px`
1159
+ }
1160
+ });
1161
+ insert(_el$2, createMemo(() => createComponent(For, {
1162
+ get each() {
1163
+ return menuItems;
1164
+ },
1165
+ get children() {
1166
+ return (item) => item.separator ? (() => {
1167
+ const _el$3 = createNativeElement("div");
1168
+ spread({
1169
+ element: _el$3,
1170
+ props: {
1171
+ role: "separator"
1172
+ }
1173
+ });
1174
+ return _el$3;
1175
+ })() : (() => {
1176
+ const _el$3 = createNativeElement("button");
1177
+ spread({
1178
+ element: _el$3,
1179
+ props: {
1180
+ type: "button",
1181
+ role: "menuitem",
1182
+ disabled: () => item.disabled,
1183
+ onClick: () => {
1184
+ item.run?.();
1185
+ closeMenu();
1186
+ }
1187
+ }
1188
+ });
1189
+ insert(_el$3, () => item.label, null);
1190
+ return _el$3;
1191
+ })();
1192
+ }
1193
+ })), null);
1194
+ return _el$2;
1195
+ })() : null;
1196
+ }, null);
1197
+ return _el$;
1198
+ })();
1199
+ }
1200
+ export {
1201
+ BulkEditor
1202
+ };