@fluixi-ui/bulk-editor 0.3.0-alpha.18 → 0.3.0-alpha.20

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