@vueye-table/headless 3.0.0-alpha.8 → 3.0.0-beta.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.
- package/dist/index.d.ts +515 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +961 -142
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useVirtualColumns, useVirtualRows } from "@vueye-table/vue";
|
|
2
2
|
import { Fragment, computed, defineComponent, h, inject, nextTick, onMounted, onScopeDispose, provide, ref, shallowRef, useId, watch } from "vue";
|
|
3
|
+
import { formatValue, getRowItemKey, gridCommand } from "@vueye-table/core";
|
|
3
4
|
|
|
4
5
|
//#region src/shared.ts
|
|
6
|
+
/** Leading native utility cells, independent of data-column coordinates. */
|
|
7
|
+
const columnLayoutProps = { leadingColumns: {
|
|
8
|
+
type: Number,
|
|
9
|
+
default: 0
|
|
10
|
+
} };
|
|
11
|
+
const columnOffsetKey = Symbol("vt-leading-columns");
|
|
12
|
+
function provideColumnOffset(offset) {
|
|
13
|
+
provide(columnOffsetKey, offset);
|
|
14
|
+
}
|
|
15
|
+
function injectColumnOffset() {
|
|
16
|
+
return inject(columnOffsetKey, () => 0);
|
|
17
|
+
}
|
|
5
18
|
/** The element a component renders. Every headless component accepts `as`. */
|
|
6
19
|
const asProp = (tag) => ({
|
|
7
20
|
type: String,
|
|
@@ -76,14 +89,16 @@ const DataTableSelectRow = defineComponent({
|
|
|
76
89
|
setup(props, { slots }) {
|
|
77
90
|
const table = injectDataTable("<DataTableSelectRow>");
|
|
78
91
|
return () => {
|
|
79
|
-
const selected = table.isSelected(props.row.key);
|
|
92
|
+
const selected = table.tree ? props.row.selection === "all" : table.isSelected(props.row.key);
|
|
93
|
+
const indeterminate = table.tree && props.row.selection === "some";
|
|
80
94
|
const toggle = () => table.toggleRow(props.row.key);
|
|
81
95
|
return slots.default?.({
|
|
82
96
|
selected,
|
|
97
|
+
indeterminate,
|
|
83
98
|
toggle
|
|
84
99
|
}) ?? checkbox({
|
|
85
100
|
checked: selected,
|
|
86
|
-
indeterminate
|
|
101
|
+
indeterminate,
|
|
87
102
|
label: props.label,
|
|
88
103
|
disabled: table.selectionMode === "none",
|
|
89
104
|
onChange: toggle
|
|
@@ -262,14 +277,16 @@ const DataTableStatus = defineComponent({
|
|
|
262
277
|
loadError: table.loadError,
|
|
263
278
|
canLoadMore: table.canLoadMore,
|
|
264
279
|
retry: () => table.retry(),
|
|
265
|
-
loadNext: () => table.loadNext()
|
|
280
|
+
loadNext: () => table.loadNext(),
|
|
281
|
+
loadingChildrenCount: table.tree ? table.processedRows.filter((row) => row.childStatus === "loading").length : 0,
|
|
282
|
+
childErrorCount: table.tree ? table.processedRows.filter((row) => row.childStatus === "error").length : 0
|
|
266
283
|
};
|
|
267
284
|
const count = `Loaded ${table.loadedRowCount.toLocaleString()} ${table.loadedRowCount === 1 ? "row" : "rows"}`;
|
|
268
285
|
const text = table.loadingMode ? table.loadState === "error" ? `${count}. Could not load rows. Retry loading.` : table.loadState === "aborted" ? `${count}. Loading stopped.` : table.loadState === "loading" || table.loadState === "streaming" ? `${count}…` : `${count}${table.loadState === "done" ? ". All rows loaded." : "."}` : table.rowCount === 0 ? "No rows" : `${table.pageStart}–${table.pageEnd} of ${table.rowCount} rows` + (table.selectedCount > 0 ? `, ${table.selectedCount} selected` : "");
|
|
269
286
|
return h(props.as, {
|
|
270
287
|
role: "status",
|
|
271
288
|
"aria-live": "polite"
|
|
272
|
-
}, slots.default?.(slotProps) ?? text);
|
|
289
|
+
}, slots.default?.(slotProps) ?? text + (slotProps.loadingChildrenCount ? " Loading children…" : "") + (slotProps.childErrorCount ? " Could not load children. Retry children in the affected row." : ""));
|
|
273
290
|
};
|
|
274
291
|
}
|
|
275
292
|
});
|
|
@@ -319,6 +336,173 @@ const DataTableLoadMore = defineComponent({
|
|
|
319
336
|
}
|
|
320
337
|
});
|
|
321
338
|
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/editor.ts
|
|
341
|
+
function editorText(value, kind) {
|
|
342
|
+
if (value === null || value === void 0) return "";
|
|
343
|
+
if (kind === "date" && value instanceof Date) return Number.isFinite(value.getTime()) ? value.toISOString().slice(0, 10) : "";
|
|
344
|
+
return formatValue(value);
|
|
345
|
+
}
|
|
346
|
+
const cellEditorProps = {
|
|
347
|
+
editor: {
|
|
348
|
+
type: Object,
|
|
349
|
+
required: true
|
|
350
|
+
},
|
|
351
|
+
spec: {
|
|
352
|
+
type: Object,
|
|
353
|
+
default: () => ({ kind: "text" })
|
|
354
|
+
},
|
|
355
|
+
/** Keyboard movement is owned by the surrounding cell. */
|
|
356
|
+
finish: {
|
|
357
|
+
type: Function,
|
|
358
|
+
default: void 0
|
|
359
|
+
},
|
|
360
|
+
autofocus: {
|
|
361
|
+
type: Boolean,
|
|
362
|
+
default: true
|
|
363
|
+
},
|
|
364
|
+
blurCommit: {
|
|
365
|
+
type: Boolean,
|
|
366
|
+
default: true
|
|
367
|
+
},
|
|
368
|
+
tabCommit: {
|
|
369
|
+
type: Boolean,
|
|
370
|
+
default: true
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
/** Plain native fields. Select results are searched and capped at 50 mounted options. */
|
|
374
|
+
const DataCellEditor = defineComponent({
|
|
375
|
+
name: "DataCellEditor",
|
|
376
|
+
props: cellEditorProps,
|
|
377
|
+
setup(props) {
|
|
378
|
+
const field = ref();
|
|
379
|
+
const query = ref("");
|
|
380
|
+
const options = props.spec.options ?? [];
|
|
381
|
+
const matches = computed(() => {
|
|
382
|
+
const search = query.value.trim().toLocaleLowerCase();
|
|
383
|
+
return options.map((value, index) => ({
|
|
384
|
+
value,
|
|
385
|
+
index
|
|
386
|
+
})).filter(({ value }) => String(value ?? "").toLocaleLowerCase().includes(search));
|
|
387
|
+
});
|
|
388
|
+
onMounted(() => {
|
|
389
|
+
if (!props.autofocus) return;
|
|
390
|
+
field.value?.focus();
|
|
391
|
+
if (field.value && "setSelectionRange" in field.value && ["text", "number"].includes(props.spec.kind)) field.value.setSelectionRange(field.value.value.length, field.value.value.length);
|
|
392
|
+
});
|
|
393
|
+
const finish = (direction) => {
|
|
394
|
+
if (props.finish) props.finish(direction);
|
|
395
|
+
else props.editor.commit();
|
|
396
|
+
};
|
|
397
|
+
const keydown = (event) => {
|
|
398
|
+
if (event.isComposing) return;
|
|
399
|
+
if (event.key === "Escape") {
|
|
400
|
+
event.preventDefault();
|
|
401
|
+
event.stopPropagation();
|
|
402
|
+
if (!props.editor.pending) props.editor.cancel();
|
|
403
|
+
} else if (event.key === "Enter" || event.key === "Tab") {
|
|
404
|
+
if (event.key === "Tab" && !props.tabCommit) return;
|
|
405
|
+
event.preventDefault();
|
|
406
|
+
event.stopPropagation();
|
|
407
|
+
if (!props.editor.pending) finish(event.key === "Tab" ? event.shiftKey ? "left" : "right" : event.shiftKey ? "up" : "down");
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const paste = (event) => {
|
|
411
|
+
const text = event.clipboardData?.getData("text/plain");
|
|
412
|
+
if (text === void 0 || props.editor.pending) return;
|
|
413
|
+
event.preventDefault();
|
|
414
|
+
const target = event.target;
|
|
415
|
+
const start = target.selectionStart ?? 0;
|
|
416
|
+
const end = target.selectionEnd ?? target.value.length;
|
|
417
|
+
props.editor.input(target.value.slice(0, start) + text + target.value.slice(end));
|
|
418
|
+
};
|
|
419
|
+
return () => {
|
|
420
|
+
const { editor, spec } = props;
|
|
421
|
+
const attrs = {
|
|
422
|
+
...editor.attrs,
|
|
423
|
+
"aria-label": `Edit ${editor.column.header}`,
|
|
424
|
+
"data-editor": "",
|
|
425
|
+
disabled: editor.pending,
|
|
426
|
+
onKeydown: keydown
|
|
427
|
+
};
|
|
428
|
+
if (spec.kind === "select") {
|
|
429
|
+
const shown = matches.value.slice(0, 50);
|
|
430
|
+
return h("div", {
|
|
431
|
+
"data-select-editor": "",
|
|
432
|
+
onKeydown: (event) => {
|
|
433
|
+
if (event.key === "Escape") keydown(event);
|
|
434
|
+
}
|
|
435
|
+
}, [
|
|
436
|
+
h("input", {
|
|
437
|
+
ref: field,
|
|
438
|
+
type: "search",
|
|
439
|
+
value: query.value,
|
|
440
|
+
"aria-label": `Search ${editor.column.header} options`,
|
|
441
|
+
disabled: editor.pending,
|
|
442
|
+
onInput: (event) => {
|
|
443
|
+
query.value = event.target.value;
|
|
444
|
+
},
|
|
445
|
+
onKeydown: (event) => {
|
|
446
|
+
if (event.key === "Enter" && shown[0]) {
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
editor.commit(shown[0].value);
|
|
449
|
+
} else keydown(event);
|
|
450
|
+
}
|
|
451
|
+
}),
|
|
452
|
+
h("select", {
|
|
453
|
+
...attrs,
|
|
454
|
+
value: options.findIndex((value) => Object.is(value, editor.value)),
|
|
455
|
+
size: Math.max(2, Math.min(5, shown.length)),
|
|
456
|
+
onChange: (event) => {
|
|
457
|
+
const index = Number(event.target.value);
|
|
458
|
+
if (index >= 0) editor.commit(options[index]);
|
|
459
|
+
}
|
|
460
|
+
}, shown.map(({ value, index }) => h("option", {
|
|
461
|
+
key: index,
|
|
462
|
+
value: index
|
|
463
|
+
}, String(value ?? "(empty)")))),
|
|
464
|
+
h("span", {
|
|
465
|
+
role: "status",
|
|
466
|
+
"data-editor-hint": ""
|
|
467
|
+
}, matches.value.length === 0 ? "No matching options. Change your search." : matches.value.length > 50 ? "Showing the first 50 matches. Refine your search." : `${matches.value.length} options`)
|
|
468
|
+
]);
|
|
469
|
+
}
|
|
470
|
+
return h("input", {
|
|
471
|
+
...attrs,
|
|
472
|
+
ref: field,
|
|
473
|
+
type: spec.kind === "date" ? "date" : spec.kind === "checkbox" ? "checkbox" : "text",
|
|
474
|
+
inputmode: spec.kind === "number" ? "decimal" : void 0,
|
|
475
|
+
value: editor.draft,
|
|
476
|
+
checked: spec.kind === "checkbox" ? editor.value === true : void 0,
|
|
477
|
+
onInput: spec.kind === "checkbox" ? void 0 : (event) => editor.input(event.target.value),
|
|
478
|
+
onChange: spec.kind === "checkbox" ? (event) => editor.commit(event.target.checked) : void 0,
|
|
479
|
+
onBlur: () => {
|
|
480
|
+
if (props.blurCommit && !editor.pending) finish();
|
|
481
|
+
},
|
|
482
|
+
onPaste: spec.kind === "checkbox" ? void 0 : paste
|
|
483
|
+
});
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
/** The cell owns the id so it remains stable while its editor mounts and unmounts. */
|
|
488
|
+
function issueAttrs(id, issues, pending) {
|
|
489
|
+
return {
|
|
490
|
+
"aria-invalid": issues.length ? "true" : void 0,
|
|
491
|
+
"aria-describedby": issues.length ? id : void 0,
|
|
492
|
+
"aria-busy": pending ? "true" : void 0
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
function editorIssues(id, issues, pending = false) {
|
|
496
|
+
return issues.length || pending ? h("span", {
|
|
497
|
+
id,
|
|
498
|
+
"data-edit-issues": "",
|
|
499
|
+
role: "status"
|
|
500
|
+
}, pending ? "Checking changes…" : issues.map((problem) => problem.message).join(" ")) : null;
|
|
501
|
+
}
|
|
502
|
+
function useEditorId() {
|
|
503
|
+
return `vt-edit-${useId()}`;
|
|
504
|
+
}
|
|
505
|
+
|
|
322
506
|
//#endregion
|
|
323
507
|
//#region src/virtual.ts
|
|
324
508
|
/** Shared opt-in renderer props. Options are applied when the virtual renderer is created. */
|
|
@@ -556,6 +740,270 @@ function virtualRow(binding, row) {
|
|
|
556
740
|
return binding.rowItems.find((item) => item.renderItem.kind === "row" && item.renderItem.row.key === row.key);
|
|
557
741
|
}
|
|
558
742
|
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/hierarchy.ts
|
|
745
|
+
const hierarchyProps = {
|
|
746
|
+
treeColumn: {
|
|
747
|
+
type: String,
|
|
748
|
+
default: void 0
|
|
749
|
+
},
|
|
750
|
+
keepAliveDetail: {
|
|
751
|
+
type: Boolean,
|
|
752
|
+
default: false
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
const hierarchyKey = Symbol("vueye-table-hierarchy");
|
|
756
|
+
function provideHierarchy(table, props) {
|
|
757
|
+
const focus = shallowRef();
|
|
758
|
+
const focusKey = computed(() => table.rows.some((row) => row.key === focus.value) ? focus.value : table.rows[0]?.key);
|
|
759
|
+
const context = {
|
|
760
|
+
id: `vt-tree-${useId()}`,
|
|
761
|
+
table,
|
|
762
|
+
get treeColumn() {
|
|
763
|
+
return props.treeColumn;
|
|
764
|
+
},
|
|
765
|
+
get keepAliveDetail() {
|
|
766
|
+
return props.keepAliveDetail;
|
|
767
|
+
},
|
|
768
|
+
focus,
|
|
769
|
+
get focusKey() {
|
|
770
|
+
return focusKey.value;
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
provide(hierarchyKey, context);
|
|
774
|
+
return context;
|
|
775
|
+
}
|
|
776
|
+
function injectHierarchy() {
|
|
777
|
+
return inject(hierarchyKey, void 0);
|
|
778
|
+
}
|
|
779
|
+
function resolveTreeColumn(table, column) {
|
|
780
|
+
return table.columns.find((candidate) => candidate.id === column)?.id ?? table.columns[0]?.id;
|
|
781
|
+
}
|
|
782
|
+
function itemId(context, row, detail = false) {
|
|
783
|
+
return context ? `${context.id}-${encodeURIComponent(getRowItemKey(row.key, detail ? "detail" : "row"))}` : void 0;
|
|
784
|
+
}
|
|
785
|
+
const siblingCache = /* @__PURE__ */ new WeakMap();
|
|
786
|
+
function hierarchyRowAttrs(table, row, context) {
|
|
787
|
+
if (!table.tree) return {};
|
|
788
|
+
let positions = siblingCache.get(table.processedRows);
|
|
789
|
+
if (!positions) {
|
|
790
|
+
const groups = /* @__PURE__ */ new Map();
|
|
791
|
+
for (const candidate of table.processedRows) {
|
|
792
|
+
const group = groups.get(candidate.parentKey) ?? [];
|
|
793
|
+
group.push(candidate);
|
|
794
|
+
groups.set(candidate.parentKey, group);
|
|
795
|
+
}
|
|
796
|
+
const indices = new Map(table.processedRows.map((candidate, index) => [candidate.key, index]));
|
|
797
|
+
const result = /* @__PURE__ */ new Map();
|
|
798
|
+
for (const group of groups.values()) group.forEach((candidate, index) => result.set(candidate.key, {
|
|
799
|
+
position: index + 1,
|
|
800
|
+
size: group.length,
|
|
801
|
+
index: indices.get(candidate.key)
|
|
802
|
+
}));
|
|
803
|
+
positions = result;
|
|
804
|
+
siblingCache.set(table.processedRows, result);
|
|
805
|
+
}
|
|
806
|
+
const position = positions.get(row.key);
|
|
807
|
+
return {
|
|
808
|
+
id: itemId(context, row),
|
|
809
|
+
"data-tree-key": getRowItemKey(row.key),
|
|
810
|
+
"aria-level": row.depth + 1,
|
|
811
|
+
"aria-expanded": row.canExpand ? String(row.isExpanded) : void 0,
|
|
812
|
+
"aria-setsize": position?.size,
|
|
813
|
+
"aria-posinset": position?.position,
|
|
814
|
+
"aria-rowindex": position ? position.index + 2 : void 0,
|
|
815
|
+
"aria-busy": row.childStatus === "loading" ? "true" : void 0
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
/** Row-focused table navigation; cell-focused grids use the same pure core commands. */
|
|
819
|
+
function treeKeydown(event, context, virtual) {
|
|
820
|
+
const table = context.table;
|
|
821
|
+
const target = event.target;
|
|
822
|
+
if (!table.tree || !target?.closest) return;
|
|
823
|
+
if (target.closest("input, textarea, select, [contenteditable=true]") || target.closest("button") && !target.closest("[data-expand-toggle]")) return;
|
|
824
|
+
const key = target.closest("tr")?.getAttribute("data-tree-key");
|
|
825
|
+
const index = table.rows.findIndex((row) => getRowItemKey(row.key) === key);
|
|
826
|
+
const row = table.rows[index];
|
|
827
|
+
if (!row) return;
|
|
828
|
+
const command = gridCommand(event, {
|
|
829
|
+
canExpand: row.canExpand,
|
|
830
|
+
expanded: row.isExpanded
|
|
831
|
+
});
|
|
832
|
+
let next = row;
|
|
833
|
+
if (command?.type === "tree") {
|
|
834
|
+
if (command.action === "expand" || command.action === "collapse") table.toggleExpanded(row.key, command.action === "expand");
|
|
835
|
+
else if (command.action === "siblings") for (const sibling of table.processedRows.filter((candidate) => candidate.parentKey === row.parentKey && candidate.canExpand)) table.toggleExpanded(sibling.key, true);
|
|
836
|
+
else if (command.action === "child") next = table.rows.find((candidate) => candidate.parentKey === row.key) ?? row;
|
|
837
|
+
else next = table.rows.find((candidate) => candidate.key === row.parentKey) ?? row;
|
|
838
|
+
} else if (event.key === "ArrowDown" || event.key === "ArrowUp") next = table.rows[index + (event.key === "ArrowDown" ? 1 : -1)] ?? row;
|
|
839
|
+
else if (event.key === "Home" || event.key === "End") next = table.rows[event.key === "Home" ? 0 : table.rows.length - 1] ?? row;
|
|
840
|
+
else return;
|
|
841
|
+
event.preventDefault();
|
|
842
|
+
const root = target.closest("table");
|
|
843
|
+
context.focus.value = next.key;
|
|
844
|
+
virtual?.rows.scrollToKey(next.key);
|
|
845
|
+
const id = itemId(context, next);
|
|
846
|
+
nextTick(() => root?.ownerDocument.getElementById(id ?? "")?.focus());
|
|
847
|
+
}
|
|
848
|
+
const expandToggleProps = {
|
|
849
|
+
row: rowProp,
|
|
850
|
+
controls: {
|
|
851
|
+
type: String,
|
|
852
|
+
default: void 0
|
|
853
|
+
},
|
|
854
|
+
expandLabel: {
|
|
855
|
+
type: String,
|
|
856
|
+
default: "Expand row"
|
|
857
|
+
},
|
|
858
|
+
collapseLabel: {
|
|
859
|
+
type: String,
|
|
860
|
+
default: "Collapse row"
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const treeCellProps = {
|
|
864
|
+
row: rowProp,
|
|
865
|
+
as: asProp("div")
|
|
866
|
+
};
|
|
867
|
+
const DataTableExpandToggle = defineComponent({
|
|
868
|
+
name: "DataTableExpandToggle",
|
|
869
|
+
props: expandToggleProps,
|
|
870
|
+
slots: Object,
|
|
871
|
+
setup(props, { slots }) {
|
|
872
|
+
const table = injectDataTable("<DataTableExpandToggle>");
|
|
873
|
+
const context = injectHierarchy();
|
|
874
|
+
const grid = injectDataGrid();
|
|
875
|
+
return () => props.row.canExpand ? h("button", {
|
|
876
|
+
type: "button",
|
|
877
|
+
"data-expand-toggle": "",
|
|
878
|
+
tabindex: table.tree ? -1 : void 0,
|
|
879
|
+
"aria-expanded": String(props.row.isExpanded),
|
|
880
|
+
"aria-controls": props.controls ?? (!table.tree ? itemId(context, props.row, true) : props.row.isExpanded ? table.rows.filter((row) => row.parentKey === props.row.key).map((row) => itemId(context, row)).filter(Boolean).join(" ") || void 0 : void 0),
|
|
881
|
+
"aria-label": props.row.isExpanded ? props.collapseLabel : props.expandLabel,
|
|
882
|
+
onMousedown: (event) => event.stopPropagation(),
|
|
883
|
+
onDblclick: (event) => event.stopPropagation(),
|
|
884
|
+
onClick: (event) => {
|
|
885
|
+
event.stopPropagation();
|
|
886
|
+
if (grid?.table === table) grid.focusCell({
|
|
887
|
+
row: table.rows.indexOf(props.row),
|
|
888
|
+
column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
|
|
889
|
+
});
|
|
890
|
+
table.toggleExpanded(props.row.key);
|
|
891
|
+
if (grid?.table === table) nextTick(() => event.target.closest("table")?.focus());
|
|
892
|
+
}
|
|
893
|
+
}, slots.default?.({
|
|
894
|
+
row: props.row,
|
|
895
|
+
expanded: props.row.isExpanded
|
|
896
|
+
}) ?? (props.row.isExpanded ? props.collapseLabel : props.expandLabel)) : null;
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
const DataTableTreeCell = defineComponent({
|
|
900
|
+
name: "DataTableTreeCell",
|
|
901
|
+
props: treeCellProps,
|
|
902
|
+
slots: Object,
|
|
903
|
+
setup(props, { slots }) {
|
|
904
|
+
const table = injectDataTable("<DataTableTreeCell>");
|
|
905
|
+
const context = injectHierarchy();
|
|
906
|
+
const grid = injectDataGrid();
|
|
907
|
+
return () => h(props.as, {
|
|
908
|
+
class: "vt-tree-cell",
|
|
909
|
+
"data-tree-cell": "",
|
|
910
|
+
style: { paddingInlineStart: `calc(var(--vt-tree-indent, 1.25rem) * ${props.row.depth})` }
|
|
911
|
+
}, [
|
|
912
|
+
h("span", { class: "vt-tree-toggle" }, slots.toggle?.({ row: props.row }) ?? h(DataTableExpandToggle, { row: props.row })),
|
|
913
|
+
h("span", { class: "vt-tree-label" }, [slots.default?.()]),
|
|
914
|
+
props.row.childStatus === "loading" ? h("span", { class: "vt-tree-loading" }, "Loading children…") : null,
|
|
915
|
+
props.row.childStatus === "error" ? h("span", { class: "vt-tree-error" }, ["Could not load children. ", h("button", {
|
|
916
|
+
type: "button",
|
|
917
|
+
class: "vt-tree-retry",
|
|
918
|
+
onMousedown: (event) => event.stopPropagation(),
|
|
919
|
+
onClick: (event) => {
|
|
920
|
+
event.stopPropagation();
|
|
921
|
+
const element = event.currentTarget.closest("table");
|
|
922
|
+
if (grid?.table === table) grid.focusCell({
|
|
923
|
+
row: table.rows.indexOf(props.row),
|
|
924
|
+
column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
|
|
925
|
+
});
|
|
926
|
+
table.toggleExpanded(props.row.key, true);
|
|
927
|
+
nextTick(() => {
|
|
928
|
+
if (grid?.table === table) element?.focus();
|
|
929
|
+
else element?.ownerDocument.getElementById(itemId(context, props.row) ?? "")?.focus();
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
}, "Retry children")]) : null
|
|
933
|
+
]);
|
|
934
|
+
}
|
|
935
|
+
});
|
|
936
|
+
/** One full-width detail; retained collapsed content remains hidden and unmeasured. */
|
|
937
|
+
const DataTableDetailRow = defineComponent({
|
|
938
|
+
name: "DataTableDetailRow",
|
|
939
|
+
props: {
|
|
940
|
+
row: rowProp,
|
|
941
|
+
colspan: {
|
|
942
|
+
type: Number,
|
|
943
|
+
default: void 0
|
|
944
|
+
},
|
|
945
|
+
hidden: {
|
|
946
|
+
type: Boolean,
|
|
947
|
+
default: false
|
|
948
|
+
},
|
|
949
|
+
keepAlive: {
|
|
950
|
+
type: Boolean,
|
|
951
|
+
default: void 0
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
slots: Object,
|
|
955
|
+
setup(props, { slots }) {
|
|
956
|
+
const table = injectDataTable("<DataTableDetailRow>");
|
|
957
|
+
const context = injectHierarchy();
|
|
958
|
+
const virtual = injectVirtual();
|
|
959
|
+
let opened = false;
|
|
960
|
+
return () => {
|
|
961
|
+
if (props.row.isExpanded && !props.hidden) opened = true;
|
|
962
|
+
if (!opened || (!props.row.isExpanded || props.hidden) && !(props.keepAlive ?? context?.keepAliveDetail)) return null;
|
|
963
|
+
const hidden = props.hidden || !props.row.isExpanded;
|
|
964
|
+
return h("tr", {
|
|
965
|
+
id: itemId(context, props.row, true),
|
|
966
|
+
"data-detail": "",
|
|
967
|
+
hidden,
|
|
968
|
+
...virtual && !hidden ? { ref: (target) => virtual.rows.measureElement(target, getRowItemKey(props.row.key, "detail")) } : {}
|
|
969
|
+
}, [h("td", {
|
|
970
|
+
colspan: Math.max(1, props.colspan ?? table.columns.length),
|
|
971
|
+
class: "vt-detail-cell"
|
|
972
|
+
}, [slots.default?.({ row: props.row })])]);
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
});
|
|
976
|
+
function createDetails(table, slot, colspan) {
|
|
977
|
+
const context = injectHierarchy();
|
|
978
|
+
const visited = /* @__PURE__ */ new Set();
|
|
979
|
+
const render = (row, rowIndex, hidden = false) => {
|
|
980
|
+
if (!hidden && context?.keepAliveDetail) visited.add(row.key);
|
|
981
|
+
return h(DataTableDetailRow, {
|
|
982
|
+
key: getRowItemKey(row.key, "detail"),
|
|
983
|
+
row,
|
|
984
|
+
colspan: colspan(),
|
|
985
|
+
hidden
|
|
986
|
+
}, { default: () => slot()?.({
|
|
987
|
+
row,
|
|
988
|
+
rowIndex
|
|
989
|
+
}) });
|
|
990
|
+
};
|
|
991
|
+
return {
|
|
992
|
+
render,
|
|
993
|
+
retained(visible) {
|
|
994
|
+
if (!context?.keepAliveDetail) return [];
|
|
995
|
+
return [...visited].flatMap((key) => {
|
|
996
|
+
const index = table.rows.findIndex((row) => row.key === key);
|
|
997
|
+
if (index < 0) {
|
|
998
|
+
visited.delete(key);
|
|
999
|
+
return [];
|
|
1000
|
+
}
|
|
1001
|
+
return visible.has(key) ? [] : [render(table.rows[index], index, true)];
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
|
|
559
1007
|
//#endregion
|
|
560
1008
|
//#region src/table.ts
|
|
561
1009
|
/**
|
|
@@ -571,16 +1019,23 @@ const DataTableRoot = defineComponent({
|
|
|
571
1019
|
required: true
|
|
572
1020
|
},
|
|
573
1021
|
as: asProp("table"),
|
|
574
|
-
...virtualProps
|
|
1022
|
+
...virtualProps,
|
|
1023
|
+
...hierarchyProps,
|
|
1024
|
+
...columnLayoutProps
|
|
575
1025
|
},
|
|
576
1026
|
setup(props, { slots }) {
|
|
577
1027
|
provideDataTable(props.table);
|
|
1028
|
+
provideColumnOffset(() => props.leadingColumns);
|
|
1029
|
+
const hierarchy = provideHierarchy(props.table, props);
|
|
578
1030
|
const element = shallowRef();
|
|
1031
|
+
const editingGrid = injectEditingGrid();
|
|
579
1032
|
const render = (virtual) => h(props.as, {
|
|
580
|
-
|
|
581
|
-
"aria-
|
|
1033
|
+
role: props.table.tree ? "treegrid" : void 0,
|
|
1034
|
+
"aria-rowcount": (props.table.tree ? props.table.processedRows.length : props.table.rowCount) + 1,
|
|
1035
|
+
"aria-colcount": props.table.columns.length + props.leadingColumns,
|
|
582
1036
|
"aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
|
|
583
1037
|
"data-empty": flag(props.table.rowCount === 0),
|
|
1038
|
+
onKeydown: (event) => treeKeydown(event, hierarchy, virtual),
|
|
584
1039
|
...virtual ? {
|
|
585
1040
|
ref: element,
|
|
586
1041
|
"data-virtual": ""
|
|
@@ -588,6 +1043,7 @@ const DataTableRoot = defineComponent({
|
|
|
588
1043
|
}, slots.default?.({ table: props.table }) ?? [h(DataTableHeader), h(DataTableBody)]);
|
|
589
1044
|
return () => props.virtual ? h(VirtualContent, {
|
|
590
1045
|
table: props.table,
|
|
1046
|
+
...editingGrid ? { grid: editingGrid } : {},
|
|
591
1047
|
options: virtualOptions(props),
|
|
592
1048
|
tableElement: () => element.value,
|
|
593
1049
|
render
|
|
@@ -657,7 +1113,7 @@ const DataTableHeaderCell = defineComponent({
|
|
|
657
1113
|
},
|
|
658
1114
|
setup(props, { slots }) {
|
|
659
1115
|
const table = injectDataTable("<DataTableHeaderCell>");
|
|
660
|
-
const
|
|
1116
|
+
const columnOffset = injectColumnOffset();
|
|
661
1117
|
return () => {
|
|
662
1118
|
const { column } = props;
|
|
663
1119
|
const sort = table.getSort(column.id);
|
|
@@ -675,7 +1131,7 @@ const DataTableHeaderCell = defineComponent({
|
|
|
675
1131
|
"data-sortable": flag(column.sortable),
|
|
676
1132
|
"data-sort": sort?.direction,
|
|
677
1133
|
style: columnStyle(column),
|
|
678
|
-
|
|
1134
|
+
"aria-colindex": table.columns.indexOf(column) + columnOffset() + 1
|
|
679
1135
|
}, content);
|
|
680
1136
|
};
|
|
681
1137
|
}
|
|
@@ -729,20 +1185,28 @@ const DataTableBody = defineComponent({
|
|
|
729
1185
|
setup(props, { slots }) {
|
|
730
1186
|
const table = injectDataTable("<DataTableBody>");
|
|
731
1187
|
const virtual = injectVirtual();
|
|
1188
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
|
|
732
1189
|
return () => {
|
|
733
1190
|
if (props.virtual && !virtual) return h(VirtualDataTableBody, props, slots);
|
|
734
|
-
if (virtual) return renderTableBody(table, virtual, props.as, props.colspan, slots);
|
|
1191
|
+
if (virtual) return renderTableBody(table, virtual, props.as, props.colspan, slots, details);
|
|
735
1192
|
let content = slots.default?.({ rows: table.rows });
|
|
736
|
-
if (!content) content = table.rows.length === 0 && slots.empty ? h("tr", { "data-empty": "" }, [h("td", { colspan: Math.max(1, table.columns.length) }, slots.empty())]) : table.
|
|
737
|
-
key:
|
|
738
|
-
row
|
|
739
|
-
|
|
1193
|
+
if (!content) content = table.rows.length === 0 && slots.empty ? h("tr", { "data-empty": "" }, [h("td", { colspan: Math.max(1, table.columns.length) }, slots.empty())]) : table.renderItems.map((item) => item.kind === "row" ? slots.row ? h(Fragment, { key: item.key }, [slots.row(item)]) : h(DataTableRow, {
|
|
1194
|
+
key: item.key,
|
|
1195
|
+
row: item.row,
|
|
1196
|
+
rowIndex: item.rowIndex
|
|
1197
|
+
}) : details.render(item.row, item.rowIndex));
|
|
1198
|
+
const visibleDetails = new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key));
|
|
1199
|
+
const retained = slots.default ? [] : details.retained(visibleDetails);
|
|
740
1200
|
const sentinel = loadSentinel(table, props.colspan ?? table.columns.length);
|
|
741
|
-
return h(props.as,
|
|
1201
|
+
return h(props.as, [
|
|
1202
|
+
...Array.isArray(content) ? content : content ? [content] : [],
|
|
1203
|
+
...retained,
|
|
1204
|
+
...sentinel ? [sentinel] : []
|
|
1205
|
+
]);
|
|
742
1206
|
};
|
|
743
1207
|
}
|
|
744
1208
|
});
|
|
745
|
-
function renderTableBody(table, virtual, as, colspan, slots, element) {
|
|
1209
|
+
function renderTableBody(table, virtual, as, colspan, slots, details, element) {
|
|
746
1210
|
const width = colspan ?? table.columns.length;
|
|
747
1211
|
const attrs = element ? { ref: element } : {};
|
|
748
1212
|
const content = slots.default?.({ rows: virtual.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
|
|
@@ -753,15 +1217,15 @@ function renderTableBody(table, virtual, as, colspan, slots, element) {
|
|
|
753
1217
|
rowSpacer(virtual.rows.paddingEnd, width, "gap-end"),
|
|
754
1218
|
...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
|
|
755
1219
|
]);
|
|
756
|
-
return h(as, attrs, [
|
|
757
|
-
key: item.key,
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
"
|
|
763
|
-
|
|
764
|
-
|
|
1220
|
+
return h(as, attrs, [
|
|
1221
|
+
...renderRows(virtual, width, (item) => item.renderItem.kind === "row" ? slots.row ? h(Fragment, { key: item.key }, [slots.row(item.renderItem)]) : h(DataTableRow, {
|
|
1222
|
+
key: item.key,
|
|
1223
|
+
row: item.renderItem.row,
|
|
1224
|
+
rowIndex: item.renderItem.rowIndex
|
|
1225
|
+
}) : details.render(item.renderItem.row, item.renderItem.rowIndex)),
|
|
1226
|
+
...details.retained(new Set(virtual.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
|
|
1227
|
+
...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
|
|
1228
|
+
]);
|
|
765
1229
|
}
|
|
766
1230
|
const VirtualDataTableBody = defineComponent({
|
|
767
1231
|
name: "DataTableVirtualBody",
|
|
@@ -778,7 +1242,8 @@ const VirtualDataTableBody = defineComponent({
|
|
|
778
1242
|
const table = injectDataTable("<DataTableBody>");
|
|
779
1243
|
const element = shallowRef();
|
|
780
1244
|
const virtual = createComponentVirtual(table, virtualOptions(props), () => element.value?.closest("table"));
|
|
781
|
-
|
|
1245
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
|
|
1246
|
+
return () => renderTableBody(table, virtual, props.as, props.colspan, slots, details, element);
|
|
782
1247
|
}
|
|
783
1248
|
});
|
|
784
1249
|
/** A body row; `aria-selected` and `data-selected` follow the selection. */
|
|
@@ -796,6 +1261,7 @@ const DataTableRow = defineComponent({
|
|
|
796
1261
|
setup(props, { slots }) {
|
|
797
1262
|
const table = injectDataTable("<DataTableRow>");
|
|
798
1263
|
const virtual = injectVirtual();
|
|
1264
|
+
const hierarchy = injectHierarchy();
|
|
799
1265
|
return () => {
|
|
800
1266
|
const { row } = props;
|
|
801
1267
|
const selected = table.isSelected(row.key);
|
|
@@ -806,6 +1272,12 @@ const DataTableRow = defineComponent({
|
|
|
806
1272
|
"aria-selected": table.selectionMode === "none" ? void 0 : String(selected),
|
|
807
1273
|
"data-selected": flag(selected),
|
|
808
1274
|
"data-key": String(row.key),
|
|
1275
|
+
"data-dirty": flag(row.isDirty),
|
|
1276
|
+
...hierarchyRowAttrs(table, row, hierarchy),
|
|
1277
|
+
tabindex: table.tree ? (hierarchy?.focusKey ?? table.rows[0]?.key) === row.key ? 0 : -1 : void 0,
|
|
1278
|
+
onFocus: () => {
|
|
1279
|
+
if (hierarchy) hierarchy.focus.value = row.key;
|
|
1280
|
+
},
|
|
809
1281
|
...virtual && item ? {
|
|
810
1282
|
ref: (element) => virtual.rows.measureElement(element, item.key),
|
|
811
1283
|
"data-virtual-row": ""
|
|
@@ -829,22 +1301,31 @@ const DataTableCell = defineComponent({
|
|
|
829
1301
|
props: {
|
|
830
1302
|
row: rowProp,
|
|
831
1303
|
column: columnProp,
|
|
832
|
-
as: asProp("td")
|
|
1304
|
+
as: asProp("td"),
|
|
1305
|
+
tree: {
|
|
1306
|
+
type: Boolean,
|
|
1307
|
+
default: true
|
|
1308
|
+
}
|
|
833
1309
|
},
|
|
834
1310
|
setup(props, { slots }) {
|
|
1311
|
+
const table = injectDataTable("<DataTableCell>");
|
|
1312
|
+
const columnOffset = injectColumnOffset();
|
|
1313
|
+
const hierarchy = injectHierarchy();
|
|
835
1314
|
return () => {
|
|
836
1315
|
const { row, column } = props;
|
|
837
1316
|
const value = row.getValue(column.id);
|
|
838
1317
|
const display = row.getDisplay(column.id);
|
|
839
|
-
|
|
840
|
-
"data-column": column.id,
|
|
841
|
-
"data-align": column.align
|
|
842
|
-
}, slots.default?.({
|
|
1318
|
+
const content = () => slots.default?.({
|
|
843
1319
|
row,
|
|
844
1320
|
column,
|
|
845
1321
|
value,
|
|
846
1322
|
display
|
|
847
|
-
}) ?? display
|
|
1323
|
+
}) ?? display;
|
|
1324
|
+
return h(props.as, {
|
|
1325
|
+
"data-column": column.id,
|
|
1326
|
+
"data-align": column.align,
|
|
1327
|
+
"aria-colindex": table.columns.indexOf(column) + columnOffset() + 1
|
|
1328
|
+
}, props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: content }) : content());
|
|
848
1329
|
};
|
|
849
1330
|
}
|
|
850
1331
|
});
|
|
@@ -859,6 +1340,298 @@ const DataTableEmpty = defineComponent({
|
|
|
859
1340
|
}
|
|
860
1341
|
});
|
|
861
1342
|
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region src/editing.ts
|
|
1345
|
+
const editingKey = Symbol("vueye-table-editing");
|
|
1346
|
+
function injectEditingGrid() {
|
|
1347
|
+
return inject(editingKey, void 0)?.grid;
|
|
1348
|
+
}
|
|
1349
|
+
/** Renderless provider for editable data-table cells and row forms. */
|
|
1350
|
+
const DataTableEditRoot = defineComponent({
|
|
1351
|
+
name: "DataTableEditRoot",
|
|
1352
|
+
slots: Object,
|
|
1353
|
+
props: {
|
|
1354
|
+
table: {
|
|
1355
|
+
type: Object,
|
|
1356
|
+
required: true
|
|
1357
|
+
},
|
|
1358
|
+
mode: {
|
|
1359
|
+
type: String,
|
|
1360
|
+
default: "cell"
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
emits: {
|
|
1364
|
+
save: (_result) => true,
|
|
1365
|
+
cancel: (_key) => true
|
|
1366
|
+
},
|
|
1367
|
+
setup(props, { slots, emit, expose }) {
|
|
1368
|
+
const grid = useDataGrid(props.table);
|
|
1369
|
+
const draft = shallowRef();
|
|
1370
|
+
const context = {
|
|
1371
|
+
grid,
|
|
1372
|
+
get mode() {
|
|
1373
|
+
return props.mode;
|
|
1374
|
+
},
|
|
1375
|
+
get draft() {
|
|
1376
|
+
return draft.value;
|
|
1377
|
+
},
|
|
1378
|
+
start(row) {
|
|
1379
|
+
if (draft.value?.pending) return;
|
|
1380
|
+
if (draft.value) context.cancel(draft.value.rowKey);
|
|
1381
|
+
grid.cancelEdit();
|
|
1382
|
+
const column = props.table.columns.findIndex((candidate) => candidate.isEditable(row.original));
|
|
1383
|
+
grid.focusCell({
|
|
1384
|
+
row: props.table.rows.findIndex((candidate) => candidate.key === row.key),
|
|
1385
|
+
column
|
|
1386
|
+
});
|
|
1387
|
+
grid.startEdit();
|
|
1388
|
+
draft.value = props.table.editRow(row.key);
|
|
1389
|
+
},
|
|
1390
|
+
async save() {
|
|
1391
|
+
const active = draft.value;
|
|
1392
|
+
if (!active || active.pending) return void 0;
|
|
1393
|
+
const result = await active.save();
|
|
1394
|
+
if (draft.value === active && active.status !== "editing" && !active.pending) {
|
|
1395
|
+
draft.value = void 0;
|
|
1396
|
+
grid.cancelEdit();
|
|
1397
|
+
}
|
|
1398
|
+
if (result.status !== "rejected") emit("save", result);
|
|
1399
|
+
return result;
|
|
1400
|
+
},
|
|
1401
|
+
cancel(key) {
|
|
1402
|
+
if (draft.value?.rowKey === key && !draft.value.pending) {
|
|
1403
|
+
draft.value.cancel();
|
|
1404
|
+
draft.value = void 0;
|
|
1405
|
+
grid.cancelEdit();
|
|
1406
|
+
emit("cancel", key);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
};
|
|
1410
|
+
provide(editingKey, context);
|
|
1411
|
+
expose({ editing: context });
|
|
1412
|
+
watch(() => props.table.rows, (rows) => {
|
|
1413
|
+
if (draft.value && !rows.some((row) => row.key === draft.value?.rowKey)) context.cancel(draft.value.rowKey);
|
|
1414
|
+
});
|
|
1415
|
+
onScopeDispose(() => draft.value?.cancel());
|
|
1416
|
+
return () => slots.default?.({ editing: context });
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
/** A cell with on-demand editing; row mode shares one draft and one save operation. */
|
|
1420
|
+
const DataTableEditCell = defineComponent({
|
|
1421
|
+
name: "DataTableEditCell",
|
|
1422
|
+
props: {
|
|
1423
|
+
row: rowProp,
|
|
1424
|
+
column: columnProp,
|
|
1425
|
+
rowIndex: {
|
|
1426
|
+
type: Number,
|
|
1427
|
+
default: void 0
|
|
1428
|
+
},
|
|
1429
|
+
tree: {
|
|
1430
|
+
type: Boolean,
|
|
1431
|
+
default: true
|
|
1432
|
+
}
|
|
1433
|
+
},
|
|
1434
|
+
slots: Object,
|
|
1435
|
+
emits: {
|
|
1436
|
+
save: (_result) => true,
|
|
1437
|
+
cancel: (_key) => true
|
|
1438
|
+
},
|
|
1439
|
+
setup(props, { slots, emit }) {
|
|
1440
|
+
const table = injectDataTable("<DataTableEditCell>");
|
|
1441
|
+
const context = inject(editingKey);
|
|
1442
|
+
if (!context) throw new Error("<DataTableEditCell> requires <DataTableEditRoot>.");
|
|
1443
|
+
const { grid } = context;
|
|
1444
|
+
const id = useEditorId();
|
|
1445
|
+
const element = shallowRef();
|
|
1446
|
+
const position = () => ({
|
|
1447
|
+
row: props.rowIndex ?? table.rows.indexOf(props.row),
|
|
1448
|
+
column: table.columns.indexOf(props.column)
|
|
1449
|
+
});
|
|
1450
|
+
const active = computed(() => context.mode === "row" ? context.draft?.rowKey === props.row.key : grid.isEditing(position()));
|
|
1451
|
+
const pending = computed(() => context.mode === "row" ? !!context.draft?.pending : active.value && grid.lastResult?.status === "pending");
|
|
1452
|
+
const localText = shallowRef("");
|
|
1453
|
+
watch(active, (value) => {
|
|
1454
|
+
if (value) localText.value = editorText(props.row.getValue(props.column.id), grid.editorFor(position())?.kind ?? "text");
|
|
1455
|
+
});
|
|
1456
|
+
const focus = (direction) => {
|
|
1457
|
+
nextTick(() => {
|
|
1458
|
+
if (!direction) {
|
|
1459
|
+
element.value?.focus();
|
|
1460
|
+
return;
|
|
1461
|
+
}
|
|
1462
|
+
const all = [...element.value?.closest("table")?.querySelectorAll("[data-edit-cell][data-editable]") ?? []];
|
|
1463
|
+
all[all.indexOf(element.value) + (direction === "left" ? -1 : 1)]?.focus();
|
|
1464
|
+
});
|
|
1465
|
+
};
|
|
1466
|
+
const submit = (value, direction) => {
|
|
1467
|
+
if (context.mode === "row") {
|
|
1468
|
+
if (value !== void 0) {
|
|
1469
|
+
if (typeof value === "string") {
|
|
1470
|
+
localText.value = value;
|
|
1471
|
+
context.draft?.setValue(props.column.id, value);
|
|
1472
|
+
context.draft?.setInput(props.column.id, value);
|
|
1473
|
+
} else context.draft?.setValue(props.column.id, value);
|
|
1474
|
+
}
|
|
1475
|
+
return;
|
|
1476
|
+
}
|
|
1477
|
+
const result = grid.submitEdit(value === void 0 ? void 0 : typeof value === "string" ? { input: value } : { value });
|
|
1478
|
+
const finish = (final) => {
|
|
1479
|
+
if (final.status !== "rejected") {
|
|
1480
|
+
emit("save", final);
|
|
1481
|
+
focus(direction);
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
if (result?.completion) result.completion.then(finish);
|
|
1485
|
+
else if (result) finish(result);
|
|
1486
|
+
return result;
|
|
1487
|
+
};
|
|
1488
|
+
return () => {
|
|
1489
|
+
const { row, column } = props;
|
|
1490
|
+
const editable = column.isEditable(row.original);
|
|
1491
|
+
const editing = editable && active.value;
|
|
1492
|
+
const draft = context.draft?.rowKey === row.key ? context.draft : void 0;
|
|
1493
|
+
const issues = draft?.issues.filter((problem) => problem.column === column.id) ?? (row.cellIssues?.get(column.id) ? [row.cellIssues.get(column.id)] : active.value ? grid.lastResult?.issues.filter((problem) => problem.rowKey === row.key && (problem.column === column.id || !problem.column)) ?? [] : []);
|
|
1494
|
+
const spec = editing ? grid.editorFor(position()) ?? { kind: "text" } : void 0;
|
|
1495
|
+
const input = (text) => {
|
|
1496
|
+
localText.value = text;
|
|
1497
|
+
if (context.mode === "row") {
|
|
1498
|
+
draft?.setValue(column.id, text);
|
|
1499
|
+
draft?.setInput(column.id, text);
|
|
1500
|
+
} else grid.updateDraft(text);
|
|
1501
|
+
};
|
|
1502
|
+
const cancel = () => {
|
|
1503
|
+
if (pending.value) return;
|
|
1504
|
+
if (context.mode === "row") context.cancel(row.key);
|
|
1505
|
+
else {
|
|
1506
|
+
grid.cancelEdit();
|
|
1507
|
+
emit("cancel", row.key);
|
|
1508
|
+
}
|
|
1509
|
+
focus();
|
|
1510
|
+
};
|
|
1511
|
+
const editor = {
|
|
1512
|
+
row,
|
|
1513
|
+
column,
|
|
1514
|
+
value: context.mode === "row" ? Reflect.get(draft?.values ?? {}, column.id) : row.getValue(column.id),
|
|
1515
|
+
draft: context.mode === "row" ? localText.value : grid.editor?.draft ?? "",
|
|
1516
|
+
spec: spec ?? { kind: "text" },
|
|
1517
|
+
finish: (direction) => {
|
|
1518
|
+
if (context.mode === "row") context.save();
|
|
1519
|
+
else submit(void 0, direction === "left" || direction === "right" ? direction : void 0);
|
|
1520
|
+
},
|
|
1521
|
+
input,
|
|
1522
|
+
update: input,
|
|
1523
|
+
commit: submit,
|
|
1524
|
+
cancel,
|
|
1525
|
+
issues,
|
|
1526
|
+
pending: pending.value,
|
|
1527
|
+
attrs: issueAttrs(id, issues, pending.value)
|
|
1528
|
+
};
|
|
1529
|
+
return h(DataTableCell, {
|
|
1530
|
+
ref: (target) => {
|
|
1531
|
+
element.value = target && typeof target === "object" && "$el" in target ? target.$el : void 0;
|
|
1532
|
+
},
|
|
1533
|
+
row,
|
|
1534
|
+
column,
|
|
1535
|
+
tree: props.tree,
|
|
1536
|
+
"data-edit-cell": "",
|
|
1537
|
+
"data-editable": editable ? "" : void 0,
|
|
1538
|
+
"data-editing": editing ? "" : void 0,
|
|
1539
|
+
"data-dirty": row.isDirty ? "" : void 0,
|
|
1540
|
+
...issueAttrs(id, issues, pending.value),
|
|
1541
|
+
tabindex: editable ? 0 : void 0,
|
|
1542
|
+
onFocus: (event) => {
|
|
1543
|
+
if (event.target === element.value && !grid.editor) grid.focusCell(position());
|
|
1544
|
+
},
|
|
1545
|
+
onDblclick: () => {
|
|
1546
|
+
if (editable && context.mode === "cell") {
|
|
1547
|
+
grid.focusCell(position());
|
|
1548
|
+
grid.startEdit(editorText(row.getValue(column.id), grid.editorFor(position())?.kind ?? "text"));
|
|
1549
|
+
}
|
|
1550
|
+
},
|
|
1551
|
+
onKeydown: (event) => {
|
|
1552
|
+
if (event.target === element.value && event.key === "Enter" && editable) {
|
|
1553
|
+
event.preventDefault();
|
|
1554
|
+
event.stopPropagation();
|
|
1555
|
+
if (context.mode === "row") context.start(row);
|
|
1556
|
+
else {
|
|
1557
|
+
grid.focusCell(position());
|
|
1558
|
+
grid.startEdit(editorText(row.getValue(column.id), grid.editorFor(position())?.kind ?? "text"));
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}, { default: ({ value, display }) => [editing ? slots.editor?.(editor) ?? h(DataCellEditor, {
|
|
1563
|
+
editor,
|
|
1564
|
+
spec: spec ?? { kind: "text" },
|
|
1565
|
+
autofocus: context.mode === "cell" || table.columns.find((candidate) => candidate.isEditable(row.original))?.id === column.id,
|
|
1566
|
+
blurCommit: context.mode === "cell",
|
|
1567
|
+
tabCommit: context.mode === "cell",
|
|
1568
|
+
finish: (direction) => {
|
|
1569
|
+
if (context.mode === "row") context.save();
|
|
1570
|
+
else submit(void 0, direction === "left" || direction === "right" ? direction : void 0);
|
|
1571
|
+
}
|
|
1572
|
+
}) : slots.default?.({
|
|
1573
|
+
row,
|
|
1574
|
+
value,
|
|
1575
|
+
display
|
|
1576
|
+
}) ?? display, editorIssues(id, issues, pending.value)] });
|
|
1577
|
+
};
|
|
1578
|
+
}
|
|
1579
|
+
});
|
|
1580
|
+
/** One active row form. Controls share the provider's draft and core save/undo path. */
|
|
1581
|
+
const DataTableRowActions = defineComponent({
|
|
1582
|
+
name: "DataTableRowActions",
|
|
1583
|
+
props: {
|
|
1584
|
+
row: rowProp,
|
|
1585
|
+
editable: {
|
|
1586
|
+
type: Boolean,
|
|
1587
|
+
default: true
|
|
1588
|
+
},
|
|
1589
|
+
removable: {
|
|
1590
|
+
type: Boolean,
|
|
1591
|
+
default: false
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
setup(props) {
|
|
1595
|
+
const table = injectDataTable("<DataTableRowActions>");
|
|
1596
|
+
const context = inject(editingKey, void 0);
|
|
1597
|
+
const id = useEditorId();
|
|
1598
|
+
const element = shallowRef();
|
|
1599
|
+
watch(() => context?.draft?.rowKey === props.row.key, (active, previous) => {
|
|
1600
|
+
const target = element.value;
|
|
1601
|
+
if (previous && !active && !context?.draft && target?.closest("tr")?.contains(target.ownerDocument.activeElement)) nextTick(() => target.querySelector("button[data-edit-row]")?.focus());
|
|
1602
|
+
});
|
|
1603
|
+
return () => {
|
|
1604
|
+
const draft = context?.draft?.rowKey === props.row.key ? context.draft : void 0;
|
|
1605
|
+
const issues = [...new Map(draft?.issues.map((problem) => [problem.message, problem])).values()];
|
|
1606
|
+
const button = (label, action, disabled = false) => h("button", {
|
|
1607
|
+
type: "button",
|
|
1608
|
+
disabled,
|
|
1609
|
+
"aria-label": `${label}, row ${String(props.row.key)}`,
|
|
1610
|
+
"data-edit-row": label === "Edit row" ? "" : void 0,
|
|
1611
|
+
...label === "Save row" ? issueAttrs(id, issues, !!draft?.pending) : {},
|
|
1612
|
+
onClick: action
|
|
1613
|
+
}, label);
|
|
1614
|
+
return h("div", {
|
|
1615
|
+
ref: element,
|
|
1616
|
+
"data-row-actions": "",
|
|
1617
|
+
role: "group",
|
|
1618
|
+
"aria-label": `Row ${String(props.row.key)} actions`,
|
|
1619
|
+
"aria-busy": draft?.pending ? "true" : void 0
|
|
1620
|
+
}, [
|
|
1621
|
+
...context?.mode === "row" && props.editable ? draft ? [button("Save row", () => context.save(), draft.pending), button("Cancel", () => context.cancel(props.row.key), draft.pending)] : [button("Edit row", () => context.start(props.row))] : [],
|
|
1622
|
+
h("details", { onToggle: (event) => {
|
|
1623
|
+
const menu = event.currentTarget;
|
|
1624
|
+
if (menu.open) nextTick(() => menu.scrollIntoView?.({
|
|
1625
|
+
block: "nearest",
|
|
1626
|
+
inline: "nearest"
|
|
1627
|
+
}));
|
|
1628
|
+
} }, [h("summary", { "aria-label": `Actions for row ${String(props.row.key)}` }, "Actions"), h("div", { "data-row-menu": "" }, [button("Revert row", () => table.revert([props.row.key]), !props.row.isDirty || !!draft), props.removable ? button("Remove row", () => table.removeRows([props.row.key]), !!draft) : null])]),
|
|
1629
|
+
editorIssues(id, issues, !!draft?.pending)
|
|
1630
|
+
]);
|
|
1631
|
+
};
|
|
1632
|
+
}
|
|
1633
|
+
});
|
|
1634
|
+
|
|
862
1635
|
//#endregion
|
|
863
1636
|
//#region src/grid.ts
|
|
864
1637
|
const gridContextKey = Symbol("vueye-table-grid-context");
|
|
@@ -893,24 +1666,42 @@ const DataGridRoot = defineComponent({
|
|
|
893
1666
|
default: void 0
|
|
894
1667
|
},
|
|
895
1668
|
...virtualProps,
|
|
1669
|
+
...hierarchyProps,
|
|
1670
|
+
...columnLayoutProps,
|
|
896
1671
|
/** Width reserved before data columns, for a row-number/header column. */
|
|
897
1672
|
gutter: {
|
|
898
1673
|
type: Number,
|
|
899
1674
|
default: 0
|
|
900
1675
|
}
|
|
901
1676
|
},
|
|
902
|
-
|
|
1677
|
+
emits: {
|
|
1678
|
+
save: (_result) => true,
|
|
1679
|
+
cancel: (_key) => true
|
|
1680
|
+
},
|
|
1681
|
+
setup(props, { slots, expose, emit }) {
|
|
903
1682
|
provideDataTable(props.table);
|
|
904
|
-
|
|
1683
|
+
provideColumnOffset(() => props.leadingColumns);
|
|
1684
|
+
const grid = useDataGrid(props.table, { treeColumn: () => props.treeColumn });
|
|
905
1685
|
provideDataGrid(grid);
|
|
1686
|
+
provideHierarchy(props.table, props);
|
|
906
1687
|
const element = ref();
|
|
907
1688
|
const id = `vt-grid-${useId()}`;
|
|
908
1689
|
const focus = () => {
|
|
909
|
-
nextTick(() =>
|
|
1690
|
+
nextTick(() => {
|
|
1691
|
+
if (!grid.editor) element.value?.focus({ preventScroll: true });
|
|
1692
|
+
});
|
|
910
1693
|
};
|
|
911
1694
|
provide(gridContextKey, {
|
|
912
1695
|
id,
|
|
913
|
-
focus
|
|
1696
|
+
focus,
|
|
1697
|
+
cancel: (key) => emit("cancel", key)
|
|
1698
|
+
});
|
|
1699
|
+
watch(() => grid.lastResult, (result) => {
|
|
1700
|
+
if (result && [
|
|
1701
|
+
"applied",
|
|
1702
|
+
"partial",
|
|
1703
|
+
"unchanged"
|
|
1704
|
+
].includes(result.status)) emit("save", result);
|
|
914
1705
|
});
|
|
915
1706
|
expose({
|
|
916
1707
|
grid,
|
|
@@ -919,6 +1710,10 @@ const DataGridRoot = defineComponent({
|
|
|
919
1710
|
let virtual;
|
|
920
1711
|
const onKeydown = (event) => {
|
|
921
1712
|
if (event.target !== element.value) return;
|
|
1713
|
+
if (event.key === " " && !grid.editor && !event.ctrlKey && !event.metaKey && !event.altKey && grid.toggleCheckbox()) {
|
|
1714
|
+
event.preventDefault();
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
922
1717
|
if (virtual && !grid.editor && (event.ctrlKey || event.metaKey) && (event.key === "Home" || event.key === "End")) {
|
|
923
1718
|
grid.focusCell(event.key === "Home" ? {
|
|
924
1719
|
row: 0,
|
|
@@ -953,8 +1748,9 @@ const DataGridRoot = defineComponent({
|
|
|
953
1748
|
if (cut) grid.clear();
|
|
954
1749
|
};
|
|
955
1750
|
const onPaste = (event) => {
|
|
1751
|
+
if (grid.editor || event.defaultPrevented) return;
|
|
956
1752
|
const text = event.clipboardData?.getData("text/plain");
|
|
957
|
-
if (
|
|
1753
|
+
if (text === void 0 || text === "") return;
|
|
958
1754
|
event.preventDefault();
|
|
959
1755
|
grid.paste(text);
|
|
960
1756
|
};
|
|
@@ -964,12 +1760,12 @@ const DataGridRoot = defineComponent({
|
|
|
964
1760
|
return h(props.as, {
|
|
965
1761
|
ref: element,
|
|
966
1762
|
id,
|
|
967
|
-
role: "grid",
|
|
1763
|
+
role: props.table.tree ? "treegrid" : "grid",
|
|
968
1764
|
tabindex: 0,
|
|
969
1765
|
"aria-label": props.label,
|
|
970
1766
|
"aria-multiselectable": "true",
|
|
971
|
-
"aria-rowcount": (binding ? props.table.rowCount : props.table.rows.length) + 1,
|
|
972
|
-
"aria-colcount": props.table.columns.length,
|
|
1767
|
+
"aria-rowcount": (props.table.tree ? props.table.processedRows.length : binding ? props.table.rowCount : props.table.rows.length) + 1,
|
|
1768
|
+
"aria-colcount": props.table.columns.length + props.leadingColumns,
|
|
973
1769
|
"aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
|
|
974
1770
|
"aria-activedescendant": focusPosition && !grid.editor ? cellId(id, focusPosition.row, focusPosition.column) : void 0,
|
|
975
1771
|
onKeydown,
|
|
@@ -1015,12 +1811,18 @@ const DataGridBody = defineComponent({
|
|
|
1015
1811
|
colspan: {
|
|
1016
1812
|
type: Number,
|
|
1017
1813
|
default: void 0
|
|
1814
|
+
},
|
|
1815
|
+
treeCell: {
|
|
1816
|
+
type: Boolean,
|
|
1817
|
+
default: true
|
|
1018
1818
|
}
|
|
1019
1819
|
},
|
|
1020
1820
|
setup(props, { slots }) {
|
|
1021
1821
|
const table = injectDataTable("<DataGridBody>");
|
|
1022
1822
|
const { grid } = useGrid("<DataGridBody>");
|
|
1023
1823
|
const virtual = injectVirtual();
|
|
1824
|
+
const hierarchy = injectHierarchy();
|
|
1825
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
|
|
1024
1826
|
const element = ref();
|
|
1025
1827
|
const render = (binding) => {
|
|
1026
1828
|
if (binding && slots.default) {
|
|
@@ -1028,53 +1830,70 @@ const DataGridBody = defineComponent({
|
|
|
1028
1830
|
const content = slots.default({ rows: binding.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
|
|
1029
1831
|
return h(props.as, { ref: element }, sentinel ? [...content, sentinel] : content);
|
|
1030
1832
|
}
|
|
1031
|
-
if (binding) return h(props.as, { ref: element }, [
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1833
|
+
if (binding) return h(props.as, { ref: element }, [
|
|
1834
|
+
...renderRows(binding, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0), (item) => {
|
|
1835
|
+
if (item.renderItem.kind !== "row") return details.render(item.renderItem.row, item.renderItem.rowIndex);
|
|
1836
|
+
const { row, rowIndex } = item.renderItem;
|
|
1837
|
+
const columns = renderColumns(table.columns, binding, (column, columnIndex) => h(DataGridCell, {
|
|
1838
|
+
key: column.id,
|
|
1839
|
+
row,
|
|
1840
|
+
column,
|
|
1841
|
+
rowIndex,
|
|
1842
|
+
columnIndex,
|
|
1843
|
+
tree: props.treeCell
|
|
1844
|
+
}, {
|
|
1845
|
+
default: slots.cell,
|
|
1846
|
+
editor: slots.editor,
|
|
1847
|
+
$stable: true
|
|
1848
|
+
}), "td");
|
|
1849
|
+
return h("tr", {
|
|
1850
|
+
key: item.key,
|
|
1851
|
+
role: "row",
|
|
1852
|
+
"aria-rowindex": table.pageStart + rowIndex + 1,
|
|
1853
|
+
"data-key": String(row.key),
|
|
1854
|
+
"data-dirty": flag(row.isDirty),
|
|
1855
|
+
...hierarchyRowAttrs(table, row, hierarchy),
|
|
1856
|
+
"data-virtual-row": "",
|
|
1857
|
+
ref: (target) => binding.rows.measureElement(target, item.key)
|
|
1858
|
+
}, [slots.rowHeader?.({
|
|
1859
|
+
row,
|
|
1860
|
+
index: rowIndex
|
|
1861
|
+
}), ...columns]);
|
|
1862
|
+
}),
|
|
1863
|
+
...details.retained(new Set(binding.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
|
|
1864
|
+
...loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0)) ? [loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0))] : []
|
|
1865
|
+
]);
|
|
1866
|
+
const content = slots.default?.({ rows: table.rows }) ?? table.renderItems.map((item) => item.kind === "detail" ? details.render(item.row, item.rowIndex) : (() => {
|
|
1867
|
+
const { row, rowIndex } = item;
|
|
1049
1868
|
return h("tr", {
|
|
1050
1869
|
key: item.key,
|
|
1051
1870
|
role: "row",
|
|
1052
|
-
"aria-rowindex":
|
|
1871
|
+
"aria-rowindex": rowIndex + 2,
|
|
1053
1872
|
"data-key": String(row.key),
|
|
1054
|
-
"data-
|
|
1055
|
-
|
|
1873
|
+
"data-dirty": flag(row.isDirty),
|
|
1874
|
+
...hierarchyRowAttrs(table, row, hierarchy)
|
|
1056
1875
|
}, [slots.rowHeader?.({
|
|
1057
1876
|
row,
|
|
1058
1877
|
index: rowIndex
|
|
1059
|
-
}), ...columns
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
column,
|
|
1073
|
-
rowIndex,
|
|
1074
|
-
columnIndex
|
|
1075
|
-
}, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0))]));
|
|
1878
|
+
}), ...table.columns.map((column, columnIndex) => h(DataGridCell, {
|
|
1879
|
+
key: column.id,
|
|
1880
|
+
row,
|
|
1881
|
+
column,
|
|
1882
|
+
rowIndex,
|
|
1883
|
+
columnIndex,
|
|
1884
|
+
tree: props.treeCell
|
|
1885
|
+
}, {
|
|
1886
|
+
default: slots.cell,
|
|
1887
|
+
editor: slots.editor,
|
|
1888
|
+
$stable: true
|
|
1889
|
+
}))]);
|
|
1890
|
+
})());
|
|
1076
1891
|
const sentinel = loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
|
|
1077
|
-
return h(props.as,
|
|
1892
|
+
return h(props.as, [
|
|
1893
|
+
...content,
|
|
1894
|
+
...slots.default ? [] : details.retained(new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key))),
|
|
1895
|
+
sentinel
|
|
1896
|
+
]);
|
|
1078
1897
|
};
|
|
1079
1898
|
return () => props.virtual && !virtual ? h(VirtualContent, {
|
|
1080
1899
|
table,
|
|
@@ -1085,52 +1904,6 @@ const DataGridBody = defineComponent({
|
|
|
1085
1904
|
}) : render(virtual);
|
|
1086
1905
|
}
|
|
1087
1906
|
});
|
|
1088
|
-
/** The text box shown in a cell while it is edited. */
|
|
1089
|
-
const DataGridEditor = defineComponent({
|
|
1090
|
-
name: "DataGridEditor",
|
|
1091
|
-
props: { label: {
|
|
1092
|
-
type: String,
|
|
1093
|
-
required: true
|
|
1094
|
-
} },
|
|
1095
|
-
setup(props) {
|
|
1096
|
-
const { grid, context } = useGrid("<DataGridEditor>");
|
|
1097
|
-
const input = ref();
|
|
1098
|
-
onMounted(() => {
|
|
1099
|
-
const element = input.value;
|
|
1100
|
-
if (element) {
|
|
1101
|
-
element.focus();
|
|
1102
|
-
element.setSelectionRange(element.value.length, element.value.length);
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
const finish = (then) => {
|
|
1106
|
-
grid.commitEdit(then);
|
|
1107
|
-
context.focus();
|
|
1108
|
-
};
|
|
1109
|
-
return () => h("input", {
|
|
1110
|
-
ref: input,
|
|
1111
|
-
value: grid.editor?.draft ?? "",
|
|
1112
|
-
"aria-label": props.label,
|
|
1113
|
-
"data-editor": "",
|
|
1114
|
-
onInput: (event) => grid.updateDraft(event.target.value),
|
|
1115
|
-
onBlur: () => {
|
|
1116
|
-
if (grid.editor) grid.commitEdit();
|
|
1117
|
-
},
|
|
1118
|
-
onKeydown: (event) => {
|
|
1119
|
-
if (event.key === "Enter") {
|
|
1120
|
-
event.preventDefault();
|
|
1121
|
-
finish(event.shiftKey ? "up" : "down");
|
|
1122
|
-
} else if (event.key === "Tab") {
|
|
1123
|
-
event.preventDefault();
|
|
1124
|
-
finish(event.shiftKey ? "left" : "right");
|
|
1125
|
-
} else if (event.key === "Escape") {
|
|
1126
|
-
event.preventDefault();
|
|
1127
|
-
grid.cancelEdit();
|
|
1128
|
-
context.focus();
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
});
|
|
1132
|
-
}
|
|
1133
|
-
});
|
|
1134
1907
|
/**
|
|
1135
1908
|
* A grid cell. A click selects it, Shift-click or dragging extends the range, and a double click
|
|
1136
1909
|
* edits it. The `editor` slot replaces the default text box.
|
|
@@ -1142,6 +1915,10 @@ const DataGridCell = defineComponent({
|
|
|
1142
1915
|
row: rowProp,
|
|
1143
1916
|
column: columnProp,
|
|
1144
1917
|
as: asProp("td"),
|
|
1918
|
+
tree: {
|
|
1919
|
+
type: Boolean,
|
|
1920
|
+
default: true
|
|
1921
|
+
},
|
|
1145
1922
|
/** The row's position on the page. Found by searching the rows when left out. */
|
|
1146
1923
|
rowIndex: {
|
|
1147
1924
|
type: Number,
|
|
@@ -1155,9 +1932,12 @@ const DataGridCell = defineComponent({
|
|
|
1155
1932
|
},
|
|
1156
1933
|
setup(props, { slots }) {
|
|
1157
1934
|
const table = injectDataTable("<DataGridCell>");
|
|
1935
|
+
const columnOffset = injectColumnOffset();
|
|
1158
1936
|
const { grid, context } = useGrid("<DataGridCell>");
|
|
1159
1937
|
const virtual = injectVirtual();
|
|
1938
|
+
const hierarchy = injectHierarchy();
|
|
1160
1939
|
const element = ref();
|
|
1940
|
+
const issueId = useEditorId();
|
|
1161
1941
|
const position = () => ({
|
|
1162
1942
|
row: props.rowIndex ?? table.rows.indexOf(props.row),
|
|
1163
1943
|
column: props.columnIndex ?? table.columns.indexOf(props.column)
|
|
@@ -1175,33 +1955,72 @@ const DataGridCell = defineComponent({
|
|
|
1175
1955
|
const editable = column.isEditable(row.original);
|
|
1176
1956
|
const value = row.getValue(column.id);
|
|
1177
1957
|
const display = row.getDisplay(column.id);
|
|
1958
|
+
const issues = row.cellIssues?.get(column.id) ? [row.cellIssues.get(column.id)] : grid.lastResult?.issues.filter((problem) => problem.rowKey === row.key && (problem.column === column.id || !problem.column)) ?? [];
|
|
1959
|
+
const pending = table.pendingCells.some((cell) => cell.rowKey === row.key && cell.column === column.id);
|
|
1960
|
+
const finish = (edit, direction) => {
|
|
1961
|
+
const result = grid.submitEdit(edit, direction);
|
|
1962
|
+
const focus = () => {
|
|
1963
|
+
if (!grid.editor) context.focus();
|
|
1964
|
+
};
|
|
1965
|
+
if (result?.completion) result.completion.then(focus);
|
|
1966
|
+
else focus();
|
|
1967
|
+
return result;
|
|
1968
|
+
};
|
|
1178
1969
|
let content;
|
|
1179
|
-
if (editing)
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1970
|
+
if (editing) {
|
|
1971
|
+
const editor = {
|
|
1972
|
+
row,
|
|
1973
|
+
column,
|
|
1974
|
+
value,
|
|
1975
|
+
draft: grid.editor?.draft ?? "",
|
|
1976
|
+
spec: grid.editorFor(at) ?? { kind: "text" },
|
|
1977
|
+
finish: (direction) => {
|
|
1978
|
+
finish(void 0, direction);
|
|
1979
|
+
},
|
|
1980
|
+
input: (text) => grid.updateDraft(text),
|
|
1981
|
+
update: (text) => grid.updateDraft(text),
|
|
1982
|
+
commit: (next) => finish(next === void 0 ? void 0 : typeof next === "string" ? { input: next } : { value: next }),
|
|
1983
|
+
cancel: () => {
|
|
1984
|
+
if (!pending) {
|
|
1985
|
+
grid.cancelEdit();
|
|
1986
|
+
context.cancel(row.key);
|
|
1987
|
+
context.focus();
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
issues,
|
|
1991
|
+
pending,
|
|
1992
|
+
attrs: issueAttrs(issueId, issues, pending)
|
|
1993
|
+
};
|
|
1994
|
+
content = slots.editor?.(editor) ?? h(DataCellEditor, {
|
|
1995
|
+
editor,
|
|
1996
|
+
spec: grid.editorFor(at) ?? { kind: "text" },
|
|
1997
|
+
finish: (direction) => finish(void 0, direction)
|
|
1998
|
+
});
|
|
1999
|
+
} else {
|
|
2000
|
+
const children = () => slots.default?.({
|
|
2001
|
+
row,
|
|
2002
|
+
column,
|
|
2003
|
+
value,
|
|
2004
|
+
display,
|
|
2005
|
+
editable
|
|
2006
|
+
}) ?? display;
|
|
2007
|
+
content = props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: children }) : children();
|
|
2008
|
+
}
|
|
1192
2009
|
return h(props.as, {
|
|
1193
2010
|
ref: element,
|
|
1194
2011
|
id: cellId(context.id, at.row, at.column),
|
|
1195
2012
|
role: "gridcell",
|
|
1196
2013
|
"aria-selected": String(grid.isSelected(at)),
|
|
1197
2014
|
"aria-readonly": editable ? void 0 : "true",
|
|
1198
|
-
"aria-colindex": at.column + 1,
|
|
2015
|
+
"aria-colindex": at.column + columnOffset() + 1,
|
|
1199
2016
|
"data-column": column.id,
|
|
1200
2017
|
"data-align": column.align,
|
|
1201
2018
|
"data-focused": flag(grid.isFocused(at)),
|
|
1202
2019
|
"data-selected": flag(grid.isSelected(at)),
|
|
1203
2020
|
"data-editing": flag(editing),
|
|
1204
2021
|
"data-readonly": flag(!editable),
|
|
2022
|
+
"data-dirty": flag(row.isDirty),
|
|
2023
|
+
...issueAttrs(issueId, issues, pending),
|
|
1205
2024
|
style: columnStyle(column),
|
|
1206
2025
|
onMousedown: (event) => {
|
|
1207
2026
|
if (editing || event.button !== 0) return;
|
|
@@ -1213,13 +2032,13 @@ const DataGridCell = defineComponent({
|
|
|
1213
2032
|
},
|
|
1214
2033
|
onDblclick: () => {
|
|
1215
2034
|
grid.focusCell(at);
|
|
1216
|
-
grid.startEdit();
|
|
2035
|
+
grid.startEdit(editorText(value, grid.editorFor(at)?.kind ?? "text"));
|
|
1217
2036
|
}
|
|
1218
|
-
}, content);
|
|
2037
|
+
}, [content, editorIssues(issueId, issues, pending)]);
|
|
1219
2038
|
};
|
|
1220
2039
|
}
|
|
1221
2040
|
});
|
|
1222
2041
|
|
|
1223
2042
|
//#endregion
|
|
1224
|
-
export { DataGridBody, DataGridCell, DataGridRoot, DataTableBody, DataTableCaption, DataTableCell, DataTableColumnVisibility, DataTableEmpty, DataTableHeader, DataTableHeaderCell, DataTableHeaderRow, DataTableLoadMore, DataTablePageSize, DataTablePagination, DataTableRoot, DataTableRow, DataTableSearch, DataTableSelectAll, DataTableSelectRow, DataTableSortButton, DataTableStatus, DataTableViewport, DataTableVirtualColumns, cellId, columnStyle, injectVirtual as injectVirtualRenderer, virtualProps };
|
|
2043
|
+
export { DataCellEditor, DataGridBody, DataGridCell, DataGridRoot, DataTableBody, DataTableCaption, DataTableCell, DataTableColumnVisibility, DataTableDetailRow, DataTableEditCell, DataTableEditRoot, DataTableEmpty, DataTableExpandToggle, DataTableHeader, DataTableHeaderCell, DataTableHeaderRow, DataTableLoadMore, DataTablePageSize, DataTablePagination, DataTableRoot, DataTableRow, DataTableRowActions, DataTableSearch, DataTableSelectAll, DataTableSelectRow, DataTableSortButton, DataTableStatus, DataTableTreeCell, DataTableViewport, DataTableVirtualColumns, cellEditorProps, cellId, columnStyle, expandToggleProps, hierarchyProps, injectVirtual as injectVirtualRenderer, resolveTreeColumn, treeCellProps, virtualProps };
|
|
1225
2044
|
//# sourceMappingURL=index.js.map
|