@vueye-table/headless 3.0.0-alpha.1 → 3.0.0-alpha.11
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/README.md +2 -0
- package/dist/index.d.ts +911 -19
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1428 -121
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
|
-
import { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid } from "@vueye-table/vue";
|
|
2
|
-
import { defineComponent, h, inject, nextTick, onMounted, onScopeDispose, provide, ref, useId, watch } from "vue";
|
|
1
|
+
import { injectDataGrid, injectDataTable, provideDataGrid, provideDataTable, useDataGrid, useVirtualColumns, useVirtualRows } from "@vueye-table/vue";
|
|
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
|
|
@@ -244,10 +259,6 @@ const DataTableColumnVisibility = defineComponent({
|
|
|
244
259
|
};
|
|
245
260
|
}
|
|
246
261
|
});
|
|
247
|
-
/**
|
|
248
|
-
* A polite live region describing what is shown, such as "1–10 of 57 rows". Screen readers
|
|
249
|
-
* announce it after a search, filter, or page change.
|
|
250
|
-
*/
|
|
251
262
|
const DataTableStatus = defineComponent({
|
|
252
263
|
name: "DataTableStatus",
|
|
253
264
|
slots: Object,
|
|
@@ -260,16 +271,737 @@ const DataTableStatus = defineComponent({
|
|
|
260
271
|
end: table.pageEnd,
|
|
261
272
|
rowCount: table.rowCount,
|
|
262
273
|
totalRowCount: table.totalRowCount,
|
|
263
|
-
selectedCount: table.selectedCount
|
|
274
|
+
selectedCount: table.selectedCount,
|
|
275
|
+
loadState: table.loadState,
|
|
276
|
+
loadedRowCount: table.loadedRowCount,
|
|
277
|
+
loadError: table.loadError,
|
|
278
|
+
canLoadMore: table.canLoadMore,
|
|
279
|
+
retry: () => table.retry(),
|
|
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
|
|
264
283
|
};
|
|
265
|
-
const
|
|
284
|
+
const count = `Loaded ${table.loadedRowCount.toLocaleString()} ${table.loadedRowCount === 1 ? "row" : "rows"}`;
|
|
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` : "");
|
|
266
286
|
return h(props.as, {
|
|
267
287
|
role: "status",
|
|
268
288
|
"aria-live": "polite"
|
|
269
|
-
}, 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." : ""));
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
//#endregion
|
|
295
|
+
//#region src/loading.ts
|
|
296
|
+
/** Keep this visual sentinel out of the live region and logical data-row count. */
|
|
297
|
+
function loadSentinel(table, colspan) {
|
|
298
|
+
return table.loadingMode === "cursor" && table.loadedRowCount > 0 && (table.loadState === "loading" || table.loadState === "streaming") ? h("tr", {
|
|
299
|
+
key: "vt-load-sentinel",
|
|
300
|
+
"data-load-sentinel": "",
|
|
301
|
+
"aria-hidden": "true"
|
|
302
|
+
}, [h("td", {
|
|
303
|
+
colspan: Math.max(1, colspan),
|
|
304
|
+
class: "vt-load-sentinel"
|
|
305
|
+
}, "Loading more rows…")]) : void 0;
|
|
306
|
+
}
|
|
307
|
+
/** A keyboard-accessible alternative to automatic virtual-end loading, with error recovery. */
|
|
308
|
+
const DataTableLoadMore = defineComponent({
|
|
309
|
+
name: "DataTableLoadMore",
|
|
310
|
+
props: {
|
|
311
|
+
label: {
|
|
312
|
+
type: String,
|
|
313
|
+
default: "Load more rows"
|
|
314
|
+
},
|
|
315
|
+
retryLabel: {
|
|
316
|
+
type: String,
|
|
317
|
+
default: "Retry loading"
|
|
318
|
+
},
|
|
319
|
+
loadingLabel: {
|
|
320
|
+
type: String,
|
|
321
|
+
default: "Loading rows…"
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
setup(props) {
|
|
325
|
+
const table = injectDataTable("<DataTableLoadMore>");
|
|
326
|
+
return () => table.loadState === "error" ? h("button", {
|
|
327
|
+
type: "button",
|
|
328
|
+
onClick: () => table.retry()
|
|
329
|
+
}, props.retryLabel) : table.loadingMode === "cursor" && table.loadState !== "done" ? h("button", {
|
|
330
|
+
type: "button",
|
|
331
|
+
disabled: !table.canLoadMore,
|
|
332
|
+
onClick: () => {
|
|
333
|
+
table.loadNext();
|
|
334
|
+
}
|
|
335
|
+
}, table.loadState === "loading" || table.loadState === "streaming" ? props.loadingLabel : props.label) : null;
|
|
336
|
+
}
|
|
337
|
+
});
|
|
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
|
+
});
|
|
270
484
|
};
|
|
271
485
|
}
|
|
272
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
|
+
|
|
506
|
+
//#endregion
|
|
507
|
+
//#region src/virtual.ts
|
|
508
|
+
/** Shared opt-in renderer props. Options are applied when the virtual renderer is created. */
|
|
509
|
+
const virtualProps = {
|
|
510
|
+
virtual: {
|
|
511
|
+
type: [Boolean, Object],
|
|
512
|
+
default: false
|
|
513
|
+
},
|
|
514
|
+
height: {
|
|
515
|
+
type: String,
|
|
516
|
+
default: void 0
|
|
517
|
+
},
|
|
518
|
+
rowHeight: {
|
|
519
|
+
type: Number,
|
|
520
|
+
default: 40
|
|
521
|
+
},
|
|
522
|
+
overscan: {
|
|
523
|
+
type: Number,
|
|
524
|
+
default: 5
|
|
525
|
+
},
|
|
526
|
+
virtualColumns: {
|
|
527
|
+
type: Boolean,
|
|
528
|
+
default: false
|
|
529
|
+
},
|
|
530
|
+
columnWidth: {
|
|
531
|
+
type: Number,
|
|
532
|
+
default: 120
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
function virtualOptions(props) {
|
|
536
|
+
return {
|
|
537
|
+
rowHeight: props.rowHeight,
|
|
538
|
+
overscan: props.overscan,
|
|
539
|
+
columnWidth: props.columnWidth,
|
|
540
|
+
...typeof props.virtual === "object" ? props.virtual : {}
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
const viewportKey = Symbol("vt-viewport");
|
|
544
|
+
const virtualKey = Symbol("vt-virtual-renderer");
|
|
545
|
+
function injectVirtual() {
|
|
546
|
+
return inject(virtualKey, void 0);
|
|
547
|
+
}
|
|
548
|
+
/** A scroll container for a headless table/grid. It supplies its element without global reads. */
|
|
549
|
+
const DataTableViewport = defineComponent({
|
|
550
|
+
name: "DataTableViewport",
|
|
551
|
+
slots: Object,
|
|
552
|
+
props: {
|
|
553
|
+
as: asProp("div"),
|
|
554
|
+
height: {
|
|
555
|
+
type: String,
|
|
556
|
+
default: "24rem"
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
setup(props, { slots, expose }) {
|
|
560
|
+
const element = shallowRef(null);
|
|
561
|
+
provide(viewportKey, element);
|
|
562
|
+
expose({ element });
|
|
563
|
+
return () => h(props.as, {
|
|
564
|
+
ref: element,
|
|
565
|
+
"data-virtual-viewport": "",
|
|
566
|
+
style: {
|
|
567
|
+
height: props.height,
|
|
568
|
+
overflow: "auto",
|
|
569
|
+
overflowAnchor: "none"
|
|
570
|
+
}
|
|
571
|
+
}, slots.default?.());
|
|
572
|
+
}
|
|
573
|
+
});
|
|
574
|
+
function createComponentVirtual(table, options, tableElement, grid, virtualColumns = false, gutter = 0) {
|
|
575
|
+
const scrollElement = inject(viewportKey, shallowRef(null));
|
|
576
|
+
const rows = useVirtualRows(table, {
|
|
577
|
+
scrollElement,
|
|
578
|
+
estimateRowHeight: options.rowHeight,
|
|
579
|
+
overscan: options.overscan,
|
|
580
|
+
initialCount: options.initialCount,
|
|
581
|
+
grid,
|
|
582
|
+
scrollMargin: () => tableElement()?.querySelector("thead")?.getBoundingClientRect().height ?? 0
|
|
583
|
+
});
|
|
584
|
+
onMounted(() => {
|
|
585
|
+
watch(() => [
|
|
586
|
+
rows.endIndex,
|
|
587
|
+
table.renderItems.length,
|
|
588
|
+
table.canLoadMore
|
|
589
|
+
], () => {
|
|
590
|
+
if (table.loadingMode === "cursor" && table.canLoadMore && rows.endIndex >= table.renderItems.length - 1 - table.endThreshold) table.loadNext();
|
|
591
|
+
}, { immediate: true });
|
|
592
|
+
});
|
|
593
|
+
const columns = grid && virtualColumns ? useVirtualColumns(grid, {
|
|
594
|
+
scrollElement,
|
|
595
|
+
estimateColumnWidth: (index) => table.columns[index]?.width ?? options.columnWidth ?? 120,
|
|
596
|
+
overscan: options.columnOverscan ?? 1,
|
|
597
|
+
initialCount: options.initialColumnCount ?? 5,
|
|
598
|
+
scrollMargin: gutter
|
|
599
|
+
}) : void 0;
|
|
600
|
+
const pinnedRows = computed(() => {
|
|
601
|
+
const result = [...rows.items];
|
|
602
|
+
const positions = /* @__PURE__ */ new Set([grid?.selection?.focus.row, grid?.editor?.position.row]);
|
|
603
|
+
for (const position of positions) {
|
|
604
|
+
if (position === void 0 || result.some((item) => item.renderItem.kind === "row" && item.renderItem.rowIndex === position)) continue;
|
|
605
|
+
const direct = table.renderItems[position];
|
|
606
|
+
const index = direct?.kind === "row" && direct.rowIndex === position ? position : table.renderItems.findIndex((item) => item.kind === "row" && item.rowIndex === position);
|
|
607
|
+
const item = rows.getItem(index);
|
|
608
|
+
if (item) result.push(item);
|
|
609
|
+
}
|
|
610
|
+
return result.sort((a, b) => a.index - b.index);
|
|
611
|
+
});
|
|
612
|
+
const pinnedColumns = computed(() => {
|
|
613
|
+
if (!columns) return void 0;
|
|
614
|
+
const result = [...columns.items];
|
|
615
|
+
const positions = /* @__PURE__ */ new Set([grid?.selection?.focus.column, grid?.editor?.position.column]);
|
|
616
|
+
for (const position of positions) {
|
|
617
|
+
if (position === void 0 || result.some((item) => item.index === position)) continue;
|
|
618
|
+
const item = columns.getItem(position);
|
|
619
|
+
if (item) result.push(item);
|
|
620
|
+
}
|
|
621
|
+
return result.sort((a, b) => a.index - b.index);
|
|
622
|
+
});
|
|
623
|
+
const binding = {
|
|
624
|
+
rows,
|
|
625
|
+
columns,
|
|
626
|
+
gutter,
|
|
627
|
+
get rowItems() {
|
|
628
|
+
return pinnedRows.value;
|
|
629
|
+
},
|
|
630
|
+
get columnItems() {
|
|
631
|
+
return pinnedColumns.value;
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
provide(virtualKey, binding);
|
|
635
|
+
return binding;
|
|
636
|
+
}
|
|
637
|
+
/** Create composables only for an enabled renderer, preserving the ordinary component path. */
|
|
638
|
+
const VirtualContent = defineComponent({
|
|
639
|
+
name: "DataTableVirtualContent",
|
|
640
|
+
props: {
|
|
641
|
+
table: {
|
|
642
|
+
type: Object,
|
|
643
|
+
required: true
|
|
644
|
+
},
|
|
645
|
+
options: {
|
|
646
|
+
type: Object,
|
|
647
|
+
required: true
|
|
648
|
+
},
|
|
649
|
+
tableElement: {
|
|
650
|
+
type: Function,
|
|
651
|
+
required: true
|
|
652
|
+
},
|
|
653
|
+
grid: {
|
|
654
|
+
type: Object,
|
|
655
|
+
default: void 0
|
|
656
|
+
},
|
|
657
|
+
virtualColumns: {
|
|
658
|
+
type: Boolean,
|
|
659
|
+
default: false
|
|
660
|
+
},
|
|
661
|
+
gutter: {
|
|
662
|
+
type: Number,
|
|
663
|
+
default: 0
|
|
664
|
+
},
|
|
665
|
+
render: {
|
|
666
|
+
type: Function,
|
|
667
|
+
required: true
|
|
668
|
+
}
|
|
669
|
+
},
|
|
670
|
+
setup(props) {
|
|
671
|
+
const binding = createComponentVirtual(props.table, props.options, props.tableElement, props.grid, props.virtualColumns, props.gutter);
|
|
672
|
+
return () => props.render(binding);
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
function rowSpacer(size, colspan, key) {
|
|
676
|
+
return size > 0 ? h("tr", {
|
|
677
|
+
key,
|
|
678
|
+
"aria-hidden": "true",
|
|
679
|
+
role: "presentation",
|
|
680
|
+
"data-virtual-spacer": "",
|
|
681
|
+
style: { height: `${size}px` }
|
|
682
|
+
}, [h("td", {
|
|
683
|
+
colspan: Math.max(1, colspan),
|
|
684
|
+
style: {
|
|
685
|
+
height: `${size}px`,
|
|
686
|
+
padding: "0",
|
|
687
|
+
border: "0",
|
|
688
|
+
lineHeight: "0"
|
|
689
|
+
}
|
|
690
|
+
})]) : null;
|
|
691
|
+
}
|
|
692
|
+
function columnSpacer(size, tag, key) {
|
|
693
|
+
return size > 0 ? h(tag, {
|
|
694
|
+
key,
|
|
695
|
+
"aria-hidden": "true",
|
|
696
|
+
role: "presentation",
|
|
697
|
+
"data-virtual-spacer": "",
|
|
698
|
+
style: {
|
|
699
|
+
width: `${size}px`,
|
|
700
|
+
minWidth: `${size}px`,
|
|
701
|
+
padding: "0",
|
|
702
|
+
border: "0"
|
|
703
|
+
}
|
|
704
|
+
}) : null;
|
|
705
|
+
}
|
|
706
|
+
function renderColumns(tableColumns, binding, render, tag) {
|
|
707
|
+
if (!binding?.columns || !binding.columnItems) return tableColumns.map(render);
|
|
708
|
+
const result = [];
|
|
709
|
+
let end = 0;
|
|
710
|
+
for (const item of binding.columnItems) {
|
|
711
|
+
result.push(columnSpacer(item.start - end, tag, `gap-${item.index}`), render(item.column, item.index));
|
|
712
|
+
end = item.start + item.size;
|
|
713
|
+
}
|
|
714
|
+
result.push(columnSpacer(binding.columns.totalSize - end, tag, "gap-end"));
|
|
715
|
+
return result;
|
|
716
|
+
}
|
|
717
|
+
function renderRows(binding, colspan, render) {
|
|
718
|
+
const result = [];
|
|
719
|
+
let end = 0;
|
|
720
|
+
for (const item of binding.rowItems) {
|
|
721
|
+
result.push(rowSpacer(item.start - end, colspan, `gap-${item.index}`), render(item));
|
|
722
|
+
end = item.start + item.size;
|
|
723
|
+
}
|
|
724
|
+
result.push(rowSpacer(binding.rows.totalSize - end, colspan, "gap-end"));
|
|
725
|
+
return result;
|
|
726
|
+
}
|
|
727
|
+
function virtualColgroup(tableColumns, binding) {
|
|
728
|
+
if (!binding.columns || !binding.columnItems) return null;
|
|
729
|
+
const cols = binding.gutter ? [h("col", { style: { width: `${binding.gutter}px` } })] : [];
|
|
730
|
+
let end = 0;
|
|
731
|
+
for (const item of binding.columnItems) {
|
|
732
|
+
if (item.start > end) cols.push(h("col", { style: { width: `${item.start - end}px` } }));
|
|
733
|
+
cols.push(h("col", { style: { width: `${item.size}px` } }));
|
|
734
|
+
end = item.start + item.size;
|
|
735
|
+
}
|
|
736
|
+
if (binding.columns.totalSize > end) cols.push(h("col", { style: { width: `${binding.columns.totalSize - end}px` } }));
|
|
737
|
+
return h("colgroup", cols);
|
|
738
|
+
}
|
|
739
|
+
function virtualRow(binding, row) {
|
|
740
|
+
return binding.rowItems.find((item) => item.renderItem.kind === "row" && item.renderItem.row.key === row.key);
|
|
741
|
+
}
|
|
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
|
+
context.focus.value = next.key;
|
|
843
|
+
virtual?.rows.scrollToKey(next.key);
|
|
844
|
+
const id = itemId(context, next);
|
|
845
|
+
nextTick(() => target.closest("table")?.ownerDocument.getElementById(id ?? "")?.focus());
|
|
846
|
+
}
|
|
847
|
+
const expandToggleProps = {
|
|
848
|
+
row: rowProp,
|
|
849
|
+
controls: {
|
|
850
|
+
type: String,
|
|
851
|
+
default: void 0
|
|
852
|
+
},
|
|
853
|
+
expandLabel: {
|
|
854
|
+
type: String,
|
|
855
|
+
default: "Expand row"
|
|
856
|
+
},
|
|
857
|
+
collapseLabel: {
|
|
858
|
+
type: String,
|
|
859
|
+
default: "Collapse row"
|
|
860
|
+
}
|
|
861
|
+
};
|
|
862
|
+
const treeCellProps = {
|
|
863
|
+
row: rowProp,
|
|
864
|
+
as: asProp("div")
|
|
865
|
+
};
|
|
866
|
+
const DataTableExpandToggle = defineComponent({
|
|
867
|
+
name: "DataTableExpandToggle",
|
|
868
|
+
props: expandToggleProps,
|
|
869
|
+
slots: Object,
|
|
870
|
+
setup(props, { slots }) {
|
|
871
|
+
const table = injectDataTable("<DataTableExpandToggle>");
|
|
872
|
+
const context = injectHierarchy();
|
|
873
|
+
const grid = injectDataGrid();
|
|
874
|
+
return () => props.row.canExpand ? h("button", {
|
|
875
|
+
type: "button",
|
|
876
|
+
"data-expand-toggle": "",
|
|
877
|
+
tabindex: table.tree ? -1 : void 0,
|
|
878
|
+
"aria-expanded": String(props.row.isExpanded),
|
|
879
|
+
"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),
|
|
880
|
+
"aria-label": props.row.isExpanded ? props.collapseLabel : props.expandLabel,
|
|
881
|
+
onMousedown: (event) => event.stopPropagation(),
|
|
882
|
+
onDblclick: (event) => event.stopPropagation(),
|
|
883
|
+
onClick: (event) => {
|
|
884
|
+
event.stopPropagation();
|
|
885
|
+
if (grid?.table === table) grid.focusCell({
|
|
886
|
+
row: table.rows.indexOf(props.row),
|
|
887
|
+
column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
|
|
888
|
+
});
|
|
889
|
+
table.toggleExpanded(props.row.key);
|
|
890
|
+
if (grid?.table === table) nextTick(() => event.target.closest("table")?.focus());
|
|
891
|
+
}
|
|
892
|
+
}, slots.default?.({
|
|
893
|
+
row: props.row,
|
|
894
|
+
expanded: props.row.isExpanded
|
|
895
|
+
}) ?? (props.row.isExpanded ? props.collapseLabel : props.expandLabel)) : null;
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
const DataTableTreeCell = defineComponent({
|
|
899
|
+
name: "DataTableTreeCell",
|
|
900
|
+
props: treeCellProps,
|
|
901
|
+
slots: Object,
|
|
902
|
+
setup(props, { slots }) {
|
|
903
|
+
const table = injectDataTable("<DataTableTreeCell>");
|
|
904
|
+
const context = injectHierarchy();
|
|
905
|
+
const grid = injectDataGrid();
|
|
906
|
+
return () => h(props.as, {
|
|
907
|
+
class: "vt-tree-cell",
|
|
908
|
+
"data-tree-cell": "",
|
|
909
|
+
style: { paddingInlineStart: `calc(var(--vt-tree-indent, 1.25rem) * ${props.row.depth})` }
|
|
910
|
+
}, [
|
|
911
|
+
h("span", { class: "vt-tree-toggle" }, slots.toggle?.({ row: props.row }) ?? h(DataTableExpandToggle, { row: props.row })),
|
|
912
|
+
h("span", { class: "vt-tree-label" }, [slots.default?.()]),
|
|
913
|
+
props.row.childStatus === "loading" ? h("span", { class: "vt-tree-loading" }, "Loading children…") : null,
|
|
914
|
+
props.row.childStatus === "error" ? h("span", { class: "vt-tree-error" }, ["Could not load children. ", h("button", {
|
|
915
|
+
type: "button",
|
|
916
|
+
class: "vt-tree-retry",
|
|
917
|
+
onMousedown: (event) => event.stopPropagation(),
|
|
918
|
+
onClick: (event) => {
|
|
919
|
+
event.stopPropagation();
|
|
920
|
+
const element = event.currentTarget.closest("table");
|
|
921
|
+
if (grid?.table === table) grid.focusCell({
|
|
922
|
+
row: table.rows.indexOf(props.row),
|
|
923
|
+
column: Math.max(0, table.columns.findIndex((column) => column.id === resolveTreeColumn(table, context?.treeColumn)))
|
|
924
|
+
});
|
|
925
|
+
table.toggleExpanded(props.row.key, true);
|
|
926
|
+
nextTick(() => {
|
|
927
|
+
if (grid?.table === table) element?.focus();
|
|
928
|
+
else element?.ownerDocument.getElementById(itemId(context, props.row) ?? "")?.focus();
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
}, "Retry children")]) : null
|
|
932
|
+
]);
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
/** One full-width detail; retained collapsed content remains hidden and unmeasured. */
|
|
936
|
+
const DataTableDetailRow = defineComponent({
|
|
937
|
+
name: "DataTableDetailRow",
|
|
938
|
+
props: {
|
|
939
|
+
row: rowProp,
|
|
940
|
+
colspan: {
|
|
941
|
+
type: Number,
|
|
942
|
+
default: void 0
|
|
943
|
+
},
|
|
944
|
+
hidden: {
|
|
945
|
+
type: Boolean,
|
|
946
|
+
default: false
|
|
947
|
+
},
|
|
948
|
+
keepAlive: {
|
|
949
|
+
type: Boolean,
|
|
950
|
+
default: void 0
|
|
951
|
+
}
|
|
952
|
+
},
|
|
953
|
+
slots: Object,
|
|
954
|
+
setup(props, { slots }) {
|
|
955
|
+
const table = injectDataTable("<DataTableDetailRow>");
|
|
956
|
+
const context = injectHierarchy();
|
|
957
|
+
const virtual = injectVirtual();
|
|
958
|
+
let opened = false;
|
|
959
|
+
return () => {
|
|
960
|
+
if (props.row.isExpanded && !props.hidden) opened = true;
|
|
961
|
+
if (!opened || (!props.row.isExpanded || props.hidden) && !(props.keepAlive ?? context?.keepAliveDetail)) return null;
|
|
962
|
+
const hidden = props.hidden || !props.row.isExpanded;
|
|
963
|
+
return h("tr", {
|
|
964
|
+
id: itemId(context, props.row, true),
|
|
965
|
+
"data-detail": "",
|
|
966
|
+
hidden,
|
|
967
|
+
...virtual && !hidden ? { ref: (target) => virtual.rows.measureElement(target, getRowItemKey(props.row.key, "detail")) } : {}
|
|
968
|
+
}, [h("td", {
|
|
969
|
+
colspan: Math.max(1, props.colspan ?? table.columns.length),
|
|
970
|
+
class: "vt-detail-cell"
|
|
971
|
+
}, [slots.default?.({ row: props.row })])]);
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
});
|
|
975
|
+
function createDetails(table, slot, colspan) {
|
|
976
|
+
const context = injectHierarchy();
|
|
977
|
+
const visited = /* @__PURE__ */ new Set();
|
|
978
|
+
const render = (row, rowIndex, hidden = false) => {
|
|
979
|
+
if (!hidden && context?.keepAliveDetail) visited.add(row.key);
|
|
980
|
+
return h(DataTableDetailRow, {
|
|
981
|
+
key: getRowItemKey(row.key, "detail"),
|
|
982
|
+
row,
|
|
983
|
+
colspan: colspan(),
|
|
984
|
+
hidden
|
|
985
|
+
}, { default: () => slot()?.({
|
|
986
|
+
row,
|
|
987
|
+
rowIndex
|
|
988
|
+
}) });
|
|
989
|
+
};
|
|
990
|
+
return {
|
|
991
|
+
render,
|
|
992
|
+
retained(visible) {
|
|
993
|
+
if (!context?.keepAliveDetail) return [];
|
|
994
|
+
return [...visited].flatMap((key) => {
|
|
995
|
+
const index = table.rows.findIndex((row) => row.key === key);
|
|
996
|
+
if (index < 0) {
|
|
997
|
+
visited.delete(key);
|
|
998
|
+
return [];
|
|
999
|
+
}
|
|
1000
|
+
return visible.has(key) ? [] : [render(table.rows[index], index, true)];
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
273
1005
|
|
|
274
1006
|
//#endregion
|
|
275
1007
|
//#region src/table.ts
|
|
@@ -285,15 +1017,36 @@ const DataTableRoot = defineComponent({
|
|
|
285
1017
|
type: Object,
|
|
286
1018
|
required: true
|
|
287
1019
|
},
|
|
288
|
-
as: asProp("table")
|
|
1020
|
+
as: asProp("table"),
|
|
1021
|
+
...virtualProps,
|
|
1022
|
+
...hierarchyProps,
|
|
1023
|
+
...columnLayoutProps
|
|
289
1024
|
},
|
|
290
1025
|
setup(props, { slots }) {
|
|
291
1026
|
provideDataTable(props.table);
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
1027
|
+
provideColumnOffset(() => props.leadingColumns);
|
|
1028
|
+
const hierarchy = provideHierarchy(props.table, props);
|
|
1029
|
+
const element = shallowRef();
|
|
1030
|
+
const editingGrid = injectEditingGrid();
|
|
1031
|
+
const render = (virtual) => h(props.as, {
|
|
1032
|
+
role: props.table.tree ? "treegrid" : void 0,
|
|
1033
|
+
"aria-rowcount": (props.table.tree ? props.table.processedRows.length : props.table.rowCount) + 1,
|
|
1034
|
+
"aria-colcount": props.table.columns.length + props.leadingColumns,
|
|
1035
|
+
"aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
|
|
1036
|
+
"data-empty": flag(props.table.rowCount === 0),
|
|
1037
|
+
onKeydown: (event) => treeKeydown(event, hierarchy, virtual),
|
|
1038
|
+
...virtual ? {
|
|
1039
|
+
ref: element,
|
|
1040
|
+
"data-virtual": ""
|
|
1041
|
+
} : {}
|
|
296
1042
|
}, slots.default?.({ table: props.table }) ?? [h(DataTableHeader), h(DataTableBody)]);
|
|
1043
|
+
return () => props.virtual ? h(VirtualContent, {
|
|
1044
|
+
table: props.table,
|
|
1045
|
+
...editingGrid ? { grid: editingGrid } : {},
|
|
1046
|
+
options: virtualOptions(props),
|
|
1047
|
+
tableElement: () => element.value,
|
|
1048
|
+
render
|
|
1049
|
+
}) : render();
|
|
297
1050
|
}
|
|
298
1051
|
});
|
|
299
1052
|
const DataTableCaption = defineComponent({
|
|
@@ -311,7 +1064,11 @@ const DataTableHeader = defineComponent({
|
|
|
311
1064
|
props: { as: asProp("thead") },
|
|
312
1065
|
setup(props, { slots }) {
|
|
313
1066
|
const table = injectDataTable("<DataTableHeader>");
|
|
314
|
-
|
|
1067
|
+
const virtual = injectVirtual();
|
|
1068
|
+
return () => h(props.as, slots.default?.({ columns: table.columns }) ?? h(DataTableHeaderRow, () => virtual?.columns ? h(DataTableVirtualColumns, null, { default: ({ column }) => [h(DataTableHeaderCell, {
|
|
1069
|
+
key: column.id,
|
|
1070
|
+
column
|
|
1071
|
+
})] }) : table.columns.map((column) => h(DataTableHeaderCell, {
|
|
315
1072
|
key: column.id,
|
|
316
1073
|
column
|
|
317
1074
|
}))));
|
|
@@ -325,6 +1082,23 @@ const DataTableHeaderRow = defineComponent({
|
|
|
325
1082
|
return () => h(props.as, { "aria-rowindex": 1 }, slots.default?.());
|
|
326
1083
|
}
|
|
327
1084
|
});
|
|
1085
|
+
/** Shared header/body column slice, with native spacer cells for omitted columns. */
|
|
1086
|
+
const DataTableVirtualColumns = defineComponent({
|
|
1087
|
+
name: "DataTableVirtualColumns",
|
|
1088
|
+
slots: Object,
|
|
1089
|
+
props: { as: {
|
|
1090
|
+
type: String,
|
|
1091
|
+
default: "th"
|
|
1092
|
+
} },
|
|
1093
|
+
setup(props, { slots }) {
|
|
1094
|
+
const table = injectDataTable("<DataTableVirtualColumns>");
|
|
1095
|
+
const virtual = injectVirtual();
|
|
1096
|
+
return () => h(Fragment, renderColumns(table.columns, virtual, (column, index) => slots.default?.({
|
|
1097
|
+
column,
|
|
1098
|
+
index
|
|
1099
|
+
}), props.as));
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
328
1102
|
/**
|
|
329
1103
|
* A column header with `aria-sort`. Without a slot it shows the header text inside a sort button
|
|
330
1104
|
* for sortable columns.
|
|
@@ -338,6 +1112,7 @@ const DataTableHeaderCell = defineComponent({
|
|
|
338
1112
|
},
|
|
339
1113
|
setup(props, { slots }) {
|
|
340
1114
|
const table = injectDataTable("<DataTableHeaderCell>");
|
|
1115
|
+
const columnOffset = injectColumnOffset();
|
|
341
1116
|
return () => {
|
|
342
1117
|
const { column } = props;
|
|
343
1118
|
const sort = table.getSort(column.id);
|
|
@@ -354,7 +1129,8 @@ const DataTableHeaderCell = defineComponent({
|
|
|
354
1129
|
"data-align": column.align,
|
|
355
1130
|
"data-sortable": flag(column.sortable),
|
|
356
1131
|
"data-sort": sort?.direction,
|
|
357
|
-
style: columnStyle(column)
|
|
1132
|
+
style: columnStyle(column),
|
|
1133
|
+
"aria-colindex": table.columns.indexOf(column) + columnOffset() + 1
|
|
358
1134
|
}, content);
|
|
359
1135
|
};
|
|
360
1136
|
}
|
|
@@ -397,38 +1173,114 @@ const DataTableSortButton = defineComponent({
|
|
|
397
1173
|
const DataTableBody = defineComponent({
|
|
398
1174
|
name: "DataTableBody",
|
|
399
1175
|
slots: Object,
|
|
400
|
-
props: {
|
|
1176
|
+
props: {
|
|
1177
|
+
as: asProp("tbody"),
|
|
1178
|
+
...virtualProps,
|
|
1179
|
+
colspan: {
|
|
1180
|
+
type: Number,
|
|
1181
|
+
default: void 0
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
401
1184
|
setup(props, { slots }) {
|
|
402
1185
|
const table = injectDataTable("<DataTableBody>");
|
|
1186
|
+
const virtual = injectVirtual();
|
|
1187
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
|
|
403
1188
|
return () => {
|
|
1189
|
+
if (props.virtual && !virtual) return h(VirtualDataTableBody, props, slots);
|
|
1190
|
+
if (virtual) return renderTableBody(table, virtual, props.as, props.colspan, slots, details);
|
|
404
1191
|
let content = slots.default?.({ rows: table.rows });
|
|
405
|
-
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.
|
|
406
|
-
key:
|
|
407
|
-
row
|
|
408
|
-
|
|
409
|
-
|
|
1192
|
+
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, {
|
|
1193
|
+
key: item.key,
|
|
1194
|
+
row: item.row,
|
|
1195
|
+
rowIndex: item.rowIndex
|
|
1196
|
+
}) : details.render(item.row, item.rowIndex));
|
|
1197
|
+
const visibleDetails = new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key));
|
|
1198
|
+
const retained = slots.default ? [] : details.retained(visibleDetails);
|
|
1199
|
+
const sentinel = loadSentinel(table, props.colspan ?? table.columns.length);
|
|
1200
|
+
return h(props.as, [
|
|
1201
|
+
...Array.isArray(content) ? content : content ? [content] : [],
|
|
1202
|
+
...retained,
|
|
1203
|
+
...sentinel ? [sentinel] : []
|
|
1204
|
+
]);
|
|
410
1205
|
};
|
|
411
1206
|
}
|
|
412
1207
|
});
|
|
1208
|
+
function renderTableBody(table, virtual, as, colspan, slots, details, element) {
|
|
1209
|
+
const width = colspan ?? table.columns.length;
|
|
1210
|
+
const attrs = element ? { ref: element } : {};
|
|
1211
|
+
const content = slots.default?.({ rows: virtual.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
|
|
1212
|
+
if (table.rows.length === 0 && slots.empty) return h(as, attrs, h("tr", { "data-empty": "" }, [h("td", { colspan: Math.max(1, width) }, slots.empty())]));
|
|
1213
|
+
if (content) return h(as, attrs, [
|
|
1214
|
+
rowSpacer(virtual.rows.paddingStart, width, "gap-start"),
|
|
1215
|
+
...content,
|
|
1216
|
+
rowSpacer(virtual.rows.paddingEnd, width, "gap-end"),
|
|
1217
|
+
...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
|
|
1218
|
+
]);
|
|
1219
|
+
return h(as, attrs, [
|
|
1220
|
+
...renderRows(virtual, width, (item) => item.renderItem.kind === "row" ? slots.row ? h(Fragment, { key: item.key }, [slots.row(item.renderItem)]) : h(DataTableRow, {
|
|
1221
|
+
key: item.key,
|
|
1222
|
+
row: item.renderItem.row,
|
|
1223
|
+
rowIndex: item.renderItem.rowIndex
|
|
1224
|
+
}) : details.render(item.renderItem.row, item.renderItem.rowIndex)),
|
|
1225
|
+
...details.retained(new Set(virtual.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
|
|
1226
|
+
...loadSentinel(table, width) ? [loadSentinel(table, width)] : []
|
|
1227
|
+
]);
|
|
1228
|
+
}
|
|
1229
|
+
const VirtualDataTableBody = defineComponent({
|
|
1230
|
+
name: "DataTableVirtualBody",
|
|
1231
|
+
props: {
|
|
1232
|
+
as: asProp("tbody"),
|
|
1233
|
+
...virtualProps,
|
|
1234
|
+
colspan: {
|
|
1235
|
+
type: Number,
|
|
1236
|
+
default: void 0
|
|
1237
|
+
}
|
|
1238
|
+
},
|
|
1239
|
+
slots: Object,
|
|
1240
|
+
setup(props, { slots }) {
|
|
1241
|
+
const table = injectDataTable("<DataTableBody>");
|
|
1242
|
+
const element = shallowRef();
|
|
1243
|
+
const virtual = createComponentVirtual(table, virtualOptions(props), () => element.value?.closest("table"));
|
|
1244
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length);
|
|
1245
|
+
return () => renderTableBody(table, virtual, props.as, props.colspan, slots, details, element);
|
|
1246
|
+
}
|
|
1247
|
+
});
|
|
413
1248
|
/** A body row; `aria-selected` and `data-selected` follow the selection. */
|
|
414
1249
|
const DataTableRow = defineComponent({
|
|
415
1250
|
name: "DataTableRow",
|
|
416
1251
|
slots: Object,
|
|
417
1252
|
props: {
|
|
418
1253
|
row: rowProp,
|
|
419
|
-
as: asProp("tr")
|
|
1254
|
+
as: asProp("tr"),
|
|
1255
|
+
rowIndex: {
|
|
1256
|
+
type: Number,
|
|
1257
|
+
default: void 0
|
|
1258
|
+
}
|
|
420
1259
|
},
|
|
421
1260
|
setup(props, { slots }) {
|
|
422
1261
|
const table = injectDataTable("<DataTableRow>");
|
|
1262
|
+
const virtual = injectVirtual();
|
|
1263
|
+
const hierarchy = injectHierarchy();
|
|
423
1264
|
return () => {
|
|
424
1265
|
const { row } = props;
|
|
425
1266
|
const selected = table.isSelected(row.key);
|
|
426
|
-
const
|
|
1267
|
+
const item = virtual ? virtualRow(virtual, row) : void 0;
|
|
1268
|
+
const position = props.rowIndex ?? item?.renderItem.rowIndex ?? table.rows.indexOf(row);
|
|
427
1269
|
return h(props.as, {
|
|
428
1270
|
"aria-rowindex": position === -1 ? void 0 : table.pageStart + position + 1,
|
|
429
1271
|
"aria-selected": table.selectionMode === "none" ? void 0 : String(selected),
|
|
430
1272
|
"data-selected": flag(selected),
|
|
431
|
-
"data-key": String(row.key)
|
|
1273
|
+
"data-key": String(row.key),
|
|
1274
|
+
"data-dirty": flag(row.isDirty),
|
|
1275
|
+
...hierarchyRowAttrs(table, row, hierarchy),
|
|
1276
|
+
tabindex: table.tree ? (hierarchy?.focusKey ?? table.rows[0]?.key) === row.key ? 0 : -1 : void 0,
|
|
1277
|
+
onFocus: () => {
|
|
1278
|
+
if (hierarchy) hierarchy.focus.value = row.key;
|
|
1279
|
+
},
|
|
1280
|
+
...virtual && item ? {
|
|
1281
|
+
ref: (element) => virtual.rows.measureElement(element, item.key),
|
|
1282
|
+
"data-virtual-row": ""
|
|
1283
|
+
} : {}
|
|
432
1284
|
}, slots.default?.({
|
|
433
1285
|
row,
|
|
434
1286
|
selected,
|
|
@@ -448,22 +1300,31 @@ const DataTableCell = defineComponent({
|
|
|
448
1300
|
props: {
|
|
449
1301
|
row: rowProp,
|
|
450
1302
|
column: columnProp,
|
|
451
|
-
as: asProp("td")
|
|
1303
|
+
as: asProp("td"),
|
|
1304
|
+
tree: {
|
|
1305
|
+
type: Boolean,
|
|
1306
|
+
default: true
|
|
1307
|
+
}
|
|
452
1308
|
},
|
|
453
1309
|
setup(props, { slots }) {
|
|
1310
|
+
const table = injectDataTable("<DataTableCell>");
|
|
1311
|
+
const columnOffset = injectColumnOffset();
|
|
1312
|
+
const hierarchy = injectHierarchy();
|
|
454
1313
|
return () => {
|
|
455
1314
|
const { row, column } = props;
|
|
456
1315
|
const value = row.getValue(column.id);
|
|
457
1316
|
const display = row.getDisplay(column.id);
|
|
458
|
-
|
|
459
|
-
"data-column": column.id,
|
|
460
|
-
"data-align": column.align
|
|
461
|
-
}, slots.default?.({
|
|
1317
|
+
const content = () => slots.default?.({
|
|
462
1318
|
row,
|
|
463
1319
|
column,
|
|
464
1320
|
value,
|
|
465
1321
|
display
|
|
466
|
-
}) ?? display
|
|
1322
|
+
}) ?? display;
|
|
1323
|
+
return h(props.as, {
|
|
1324
|
+
"data-column": column.id,
|
|
1325
|
+
"data-align": column.align,
|
|
1326
|
+
"aria-colindex": table.columns.indexOf(column) + columnOffset() + 1
|
|
1327
|
+
}, props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: content }) : content());
|
|
467
1328
|
};
|
|
468
1329
|
}
|
|
469
1330
|
});
|
|
@@ -478,6 +1339,298 @@ const DataTableEmpty = defineComponent({
|
|
|
478
1339
|
}
|
|
479
1340
|
});
|
|
480
1341
|
|
|
1342
|
+
//#endregion
|
|
1343
|
+
//#region src/editing.ts
|
|
1344
|
+
const editingKey = Symbol("vueye-table-editing");
|
|
1345
|
+
function injectEditingGrid() {
|
|
1346
|
+
return inject(editingKey, void 0)?.grid;
|
|
1347
|
+
}
|
|
1348
|
+
/** Renderless provider for editable data-table cells and row forms. */
|
|
1349
|
+
const DataTableEditRoot = defineComponent({
|
|
1350
|
+
name: "DataTableEditRoot",
|
|
1351
|
+
slots: Object,
|
|
1352
|
+
props: {
|
|
1353
|
+
table: {
|
|
1354
|
+
type: Object,
|
|
1355
|
+
required: true
|
|
1356
|
+
},
|
|
1357
|
+
mode: {
|
|
1358
|
+
type: String,
|
|
1359
|
+
default: "cell"
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
emits: {
|
|
1363
|
+
save: (_result) => true,
|
|
1364
|
+
cancel: (_key) => true
|
|
1365
|
+
},
|
|
1366
|
+
setup(props, { slots, emit, expose }) {
|
|
1367
|
+
const grid = useDataGrid(props.table);
|
|
1368
|
+
const draft = shallowRef();
|
|
1369
|
+
const context = {
|
|
1370
|
+
grid,
|
|
1371
|
+
get mode() {
|
|
1372
|
+
return props.mode;
|
|
1373
|
+
},
|
|
1374
|
+
get draft() {
|
|
1375
|
+
return draft.value;
|
|
1376
|
+
},
|
|
1377
|
+
start(row) {
|
|
1378
|
+
if (draft.value?.pending) return;
|
|
1379
|
+
if (draft.value) context.cancel(draft.value.rowKey);
|
|
1380
|
+
grid.cancelEdit();
|
|
1381
|
+
const column = props.table.columns.findIndex((candidate) => candidate.isEditable(row.original));
|
|
1382
|
+
grid.focusCell({
|
|
1383
|
+
row: props.table.rows.findIndex((candidate) => candidate.key === row.key),
|
|
1384
|
+
column
|
|
1385
|
+
});
|
|
1386
|
+
grid.startEdit();
|
|
1387
|
+
draft.value = props.table.editRow(row.key);
|
|
1388
|
+
},
|
|
1389
|
+
async save() {
|
|
1390
|
+
const active = draft.value;
|
|
1391
|
+
if (!active || active.pending) return void 0;
|
|
1392
|
+
const result = await active.save();
|
|
1393
|
+
if (draft.value === active && active.status !== "editing" && !active.pending) {
|
|
1394
|
+
draft.value = void 0;
|
|
1395
|
+
grid.cancelEdit();
|
|
1396
|
+
}
|
|
1397
|
+
if (result.status !== "rejected") emit("save", result);
|
|
1398
|
+
return result;
|
|
1399
|
+
},
|
|
1400
|
+
cancel(key) {
|
|
1401
|
+
if (draft.value?.rowKey === key && !draft.value.pending) {
|
|
1402
|
+
draft.value.cancel();
|
|
1403
|
+
draft.value = void 0;
|
|
1404
|
+
grid.cancelEdit();
|
|
1405
|
+
emit("cancel", key);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
provide(editingKey, context);
|
|
1410
|
+
expose({ editing: context });
|
|
1411
|
+
watch(() => props.table.rows, (rows) => {
|
|
1412
|
+
if (draft.value && !rows.some((row) => row.key === draft.value?.rowKey)) context.cancel(draft.value.rowKey);
|
|
1413
|
+
});
|
|
1414
|
+
onScopeDispose(() => draft.value?.cancel());
|
|
1415
|
+
return () => slots.default?.({ editing: context });
|
|
1416
|
+
}
|
|
1417
|
+
});
|
|
1418
|
+
/** A cell with on-demand editing; row mode shares one draft and one save operation. */
|
|
1419
|
+
const DataTableEditCell = defineComponent({
|
|
1420
|
+
name: "DataTableEditCell",
|
|
1421
|
+
props: {
|
|
1422
|
+
row: rowProp,
|
|
1423
|
+
column: columnProp,
|
|
1424
|
+
rowIndex: {
|
|
1425
|
+
type: Number,
|
|
1426
|
+
default: void 0
|
|
1427
|
+
},
|
|
1428
|
+
tree: {
|
|
1429
|
+
type: Boolean,
|
|
1430
|
+
default: true
|
|
1431
|
+
}
|
|
1432
|
+
},
|
|
1433
|
+
slots: Object,
|
|
1434
|
+
emits: {
|
|
1435
|
+
save: (_result) => true,
|
|
1436
|
+
cancel: (_key) => true
|
|
1437
|
+
},
|
|
1438
|
+
setup(props, { slots, emit }) {
|
|
1439
|
+
const table = injectDataTable("<DataTableEditCell>");
|
|
1440
|
+
const context = inject(editingKey);
|
|
1441
|
+
if (!context) throw new Error("<DataTableEditCell> requires <DataTableEditRoot>.");
|
|
1442
|
+
const { grid } = context;
|
|
1443
|
+
const id = useEditorId();
|
|
1444
|
+
const element = shallowRef();
|
|
1445
|
+
const position = () => ({
|
|
1446
|
+
row: props.rowIndex ?? table.rows.indexOf(props.row),
|
|
1447
|
+
column: table.columns.indexOf(props.column)
|
|
1448
|
+
});
|
|
1449
|
+
const active = computed(() => context.mode === "row" ? context.draft?.rowKey === props.row.key : grid.isEditing(position()));
|
|
1450
|
+
const pending = computed(() => context.mode === "row" ? !!context.draft?.pending : active.value && grid.lastResult?.status === "pending");
|
|
1451
|
+
const localText = shallowRef("");
|
|
1452
|
+
watch(active, (value) => {
|
|
1453
|
+
if (value) localText.value = editorText(props.row.getValue(props.column.id), grid.editorFor(position())?.kind ?? "text");
|
|
1454
|
+
});
|
|
1455
|
+
const focus = (direction) => {
|
|
1456
|
+
nextTick(() => {
|
|
1457
|
+
if (!direction) {
|
|
1458
|
+
element.value?.focus();
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const all = [...element.value?.closest("table")?.querySelectorAll("[data-edit-cell][data-editable]") ?? []];
|
|
1462
|
+
all[all.indexOf(element.value) + (direction === "left" ? -1 : 1)]?.focus();
|
|
1463
|
+
});
|
|
1464
|
+
};
|
|
1465
|
+
const submit = (value, direction) => {
|
|
1466
|
+
if (context.mode === "row") {
|
|
1467
|
+
if (value !== void 0) {
|
|
1468
|
+
if (typeof value === "string") {
|
|
1469
|
+
localText.value = value;
|
|
1470
|
+
context.draft?.setValue(props.column.id, value);
|
|
1471
|
+
context.draft?.setInput(props.column.id, value);
|
|
1472
|
+
} else context.draft?.setValue(props.column.id, value);
|
|
1473
|
+
}
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const result = grid.submitEdit(value === void 0 ? void 0 : typeof value === "string" ? { input: value } : { value });
|
|
1477
|
+
const finish = (final) => {
|
|
1478
|
+
if (final.status !== "rejected") {
|
|
1479
|
+
emit("save", final);
|
|
1480
|
+
focus(direction);
|
|
1481
|
+
}
|
|
1482
|
+
};
|
|
1483
|
+
if (result?.completion) result.completion.then(finish);
|
|
1484
|
+
else if (result) finish(result);
|
|
1485
|
+
return result;
|
|
1486
|
+
};
|
|
1487
|
+
return () => {
|
|
1488
|
+
const { row, column } = props;
|
|
1489
|
+
const editable = column.isEditable(row.original);
|
|
1490
|
+
const editing = editable && active.value;
|
|
1491
|
+
const draft = context.draft?.rowKey === row.key ? context.draft : void 0;
|
|
1492
|
+
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)) ?? [] : []);
|
|
1493
|
+
const spec = editing ? grid.editorFor(position()) ?? { kind: "text" } : void 0;
|
|
1494
|
+
const input = (text) => {
|
|
1495
|
+
localText.value = text;
|
|
1496
|
+
if (context.mode === "row") {
|
|
1497
|
+
draft?.setValue(column.id, text);
|
|
1498
|
+
draft?.setInput(column.id, text);
|
|
1499
|
+
} else grid.updateDraft(text);
|
|
1500
|
+
};
|
|
1501
|
+
const cancel = () => {
|
|
1502
|
+
if (pending.value) return;
|
|
1503
|
+
if (context.mode === "row") context.cancel(row.key);
|
|
1504
|
+
else {
|
|
1505
|
+
grid.cancelEdit();
|
|
1506
|
+
emit("cancel", row.key);
|
|
1507
|
+
}
|
|
1508
|
+
focus();
|
|
1509
|
+
};
|
|
1510
|
+
const editor = {
|
|
1511
|
+
row,
|
|
1512
|
+
column,
|
|
1513
|
+
value: context.mode === "row" ? Reflect.get(draft?.values ?? {}, column.id) : row.getValue(column.id),
|
|
1514
|
+
draft: context.mode === "row" ? localText.value : grid.editor?.draft ?? "",
|
|
1515
|
+
spec: spec ?? { kind: "text" },
|
|
1516
|
+
finish: (direction) => {
|
|
1517
|
+
if (context.mode === "row") context.save();
|
|
1518
|
+
else submit(void 0, direction === "left" || direction === "right" ? direction : void 0);
|
|
1519
|
+
},
|
|
1520
|
+
input,
|
|
1521
|
+
update: input,
|
|
1522
|
+
commit: submit,
|
|
1523
|
+
cancel,
|
|
1524
|
+
issues,
|
|
1525
|
+
pending: pending.value,
|
|
1526
|
+
attrs: issueAttrs(id, issues, pending.value)
|
|
1527
|
+
};
|
|
1528
|
+
return h(DataTableCell, {
|
|
1529
|
+
ref: (target) => {
|
|
1530
|
+
element.value = target && typeof target === "object" && "$el" in target ? target.$el : void 0;
|
|
1531
|
+
},
|
|
1532
|
+
row,
|
|
1533
|
+
column,
|
|
1534
|
+
tree: props.tree,
|
|
1535
|
+
"data-edit-cell": "",
|
|
1536
|
+
"data-editable": editable ? "" : void 0,
|
|
1537
|
+
"data-editing": editing ? "" : void 0,
|
|
1538
|
+
"data-dirty": row.isDirty ? "" : void 0,
|
|
1539
|
+
...issueAttrs(id, issues, pending.value),
|
|
1540
|
+
tabindex: editable ? 0 : void 0,
|
|
1541
|
+
onFocus: (event) => {
|
|
1542
|
+
if (event.target === element.value && !grid.editor) grid.focusCell(position());
|
|
1543
|
+
},
|
|
1544
|
+
onDblclick: () => {
|
|
1545
|
+
if (editable && context.mode === "cell") {
|
|
1546
|
+
grid.focusCell(position());
|
|
1547
|
+
grid.startEdit(editorText(row.getValue(column.id), grid.editorFor(position())?.kind ?? "text"));
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
onKeydown: (event) => {
|
|
1551
|
+
if (event.target === element.value && event.key === "Enter" && editable) {
|
|
1552
|
+
event.preventDefault();
|
|
1553
|
+
event.stopPropagation();
|
|
1554
|
+
if (context.mode === "row") context.start(row);
|
|
1555
|
+
else {
|
|
1556
|
+
grid.focusCell(position());
|
|
1557
|
+
grid.startEdit(editorText(row.getValue(column.id), grid.editorFor(position())?.kind ?? "text"));
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}, { default: ({ value, display }) => [editing ? slots.editor?.(editor) ?? h(DataCellEditor, {
|
|
1562
|
+
editor,
|
|
1563
|
+
spec: spec ?? { kind: "text" },
|
|
1564
|
+
autofocus: context.mode === "cell" || table.columns.find((candidate) => candidate.isEditable(row.original))?.id === column.id,
|
|
1565
|
+
blurCommit: context.mode === "cell",
|
|
1566
|
+
tabCommit: context.mode === "cell",
|
|
1567
|
+
finish: (direction) => {
|
|
1568
|
+
if (context.mode === "row") context.save();
|
|
1569
|
+
else submit(void 0, direction === "left" || direction === "right" ? direction : void 0);
|
|
1570
|
+
}
|
|
1571
|
+
}) : slots.default?.({
|
|
1572
|
+
row,
|
|
1573
|
+
value,
|
|
1574
|
+
display
|
|
1575
|
+
}) ?? display, editorIssues(id, issues, pending.value)] });
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
/** One active row form. Controls share the provider's draft and core save/undo path. */
|
|
1580
|
+
const DataTableRowActions = defineComponent({
|
|
1581
|
+
name: "DataTableRowActions",
|
|
1582
|
+
props: {
|
|
1583
|
+
row: rowProp,
|
|
1584
|
+
editable: {
|
|
1585
|
+
type: Boolean,
|
|
1586
|
+
default: true
|
|
1587
|
+
},
|
|
1588
|
+
removable: {
|
|
1589
|
+
type: Boolean,
|
|
1590
|
+
default: false
|
|
1591
|
+
}
|
|
1592
|
+
},
|
|
1593
|
+
setup(props) {
|
|
1594
|
+
const table = injectDataTable("<DataTableRowActions>");
|
|
1595
|
+
const context = inject(editingKey, void 0);
|
|
1596
|
+
const id = useEditorId();
|
|
1597
|
+
const element = shallowRef();
|
|
1598
|
+
watch(() => context?.draft?.rowKey === props.row.key, (active, previous) => {
|
|
1599
|
+
const target = element.value;
|
|
1600
|
+
if (previous && !active && !context?.draft && target?.closest("tr")?.contains(target.ownerDocument.activeElement)) nextTick(() => target.querySelector("button[data-edit-row]")?.focus());
|
|
1601
|
+
});
|
|
1602
|
+
return () => {
|
|
1603
|
+
const draft = context?.draft?.rowKey === props.row.key ? context.draft : void 0;
|
|
1604
|
+
const issues = [...new Map(draft?.issues.map((problem) => [problem.message, problem])).values()];
|
|
1605
|
+
const button = (label, action, disabled = false) => h("button", {
|
|
1606
|
+
type: "button",
|
|
1607
|
+
disabled,
|
|
1608
|
+
"aria-label": `${label}, row ${String(props.row.key)}`,
|
|
1609
|
+
"data-edit-row": label === "Edit row" ? "" : void 0,
|
|
1610
|
+
...label === "Save row" ? issueAttrs(id, issues, !!draft?.pending) : {},
|
|
1611
|
+
onClick: action
|
|
1612
|
+
}, label);
|
|
1613
|
+
return h("div", {
|
|
1614
|
+
ref: element,
|
|
1615
|
+
"data-row-actions": "",
|
|
1616
|
+
role: "group",
|
|
1617
|
+
"aria-label": `Row ${String(props.row.key)} actions`,
|
|
1618
|
+
"aria-busy": draft?.pending ? "true" : void 0
|
|
1619
|
+
}, [
|
|
1620
|
+
...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))] : [],
|
|
1621
|
+
h("details", { onToggle: (event) => {
|
|
1622
|
+
const menu = event.currentTarget;
|
|
1623
|
+
if (menu.open) nextTick(() => menu.scrollIntoView?.({
|
|
1624
|
+
block: "nearest",
|
|
1625
|
+
inline: "nearest"
|
|
1626
|
+
}));
|
|
1627
|
+
} }, [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])]),
|
|
1628
|
+
editorIssues(id, issues, !!draft?.pending)
|
|
1629
|
+
]);
|
|
1630
|
+
};
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
|
|
481
1634
|
//#endregion
|
|
482
1635
|
//#region src/grid.ts
|
|
483
1636
|
const gridContextKey = Symbol("vueye-table-grid-context");
|
|
@@ -510,12 +1663,26 @@ const DataGridRoot = defineComponent({
|
|
|
510
1663
|
label: {
|
|
511
1664
|
type: String,
|
|
512
1665
|
default: void 0
|
|
1666
|
+
},
|
|
1667
|
+
...virtualProps,
|
|
1668
|
+
...hierarchyProps,
|
|
1669
|
+
...columnLayoutProps,
|
|
1670
|
+
/** Width reserved before data columns, for a row-number/header column. */
|
|
1671
|
+
gutter: {
|
|
1672
|
+
type: Number,
|
|
1673
|
+
default: 0
|
|
513
1674
|
}
|
|
514
1675
|
},
|
|
515
|
-
|
|
1676
|
+
emits: {
|
|
1677
|
+
save: (_result) => true,
|
|
1678
|
+
cancel: (_key) => true
|
|
1679
|
+
},
|
|
1680
|
+
setup(props, { slots, expose, emit }) {
|
|
516
1681
|
provideDataTable(props.table);
|
|
517
|
-
|
|
1682
|
+
provideColumnOffset(() => props.leadingColumns);
|
|
1683
|
+
const grid = useDataGrid(props.table, { treeColumn: () => props.treeColumn });
|
|
518
1684
|
provideDataGrid(grid);
|
|
1685
|
+
provideHierarchy(props.table, props);
|
|
519
1686
|
const element = ref();
|
|
520
1687
|
const id = `vt-grid-${useId()}`;
|
|
521
1688
|
const focus = () => {
|
|
@@ -523,14 +1690,52 @@ const DataGridRoot = defineComponent({
|
|
|
523
1690
|
};
|
|
524
1691
|
provide(gridContextKey, {
|
|
525
1692
|
id,
|
|
526
|
-
focus
|
|
1693
|
+
focus,
|
|
1694
|
+
cancel: (key) => emit("cancel", key)
|
|
1695
|
+
});
|
|
1696
|
+
watch(() => grid.lastResult, (result) => {
|
|
1697
|
+
if (result && [
|
|
1698
|
+
"applied",
|
|
1699
|
+
"partial",
|
|
1700
|
+
"unchanged"
|
|
1701
|
+
].includes(result.status)) emit("save", result);
|
|
527
1702
|
});
|
|
528
1703
|
expose({
|
|
529
1704
|
grid,
|
|
530
1705
|
focus
|
|
531
1706
|
});
|
|
1707
|
+
let virtual;
|
|
532
1708
|
const onKeydown = (event) => {
|
|
533
1709
|
if (event.target !== element.value) return;
|
|
1710
|
+
if (event.key === " " && !grid.editor && !event.ctrlKey && !event.metaKey && !event.altKey && grid.toggleCheckbox()) {
|
|
1711
|
+
event.preventDefault();
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
if (virtual && !grid.editor && (event.ctrlKey || event.metaKey) && (event.key === "Home" || event.key === "End")) {
|
|
1715
|
+
grid.focusCell(event.key === "Home" ? {
|
|
1716
|
+
row: 0,
|
|
1717
|
+
column: 0
|
|
1718
|
+
} : {
|
|
1719
|
+
row: props.table.rows.length - 1,
|
|
1720
|
+
column: props.table.columns.length - 1
|
|
1721
|
+
}, { extend: event.shiftKey });
|
|
1722
|
+
event.preventDefault();
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
if (virtual && !grid.editor && (event.key === "PageUp" || event.key === "PageDown")) {
|
|
1726
|
+
const current = grid.selection?.focus ?? {
|
|
1727
|
+
row: 0,
|
|
1728
|
+
column: 0
|
|
1729
|
+
};
|
|
1730
|
+
const item = virtual.rowItems.find((entry) => entry.renderItem.rowIndex === current.row);
|
|
1731
|
+
const step = Math.max(1, Math.floor(virtual.rows.viewportSize / (item?.size ?? props.rowHeight)));
|
|
1732
|
+
grid.focusCell({
|
|
1733
|
+
row: current.row + (event.key === "PageDown" ? step : -step),
|
|
1734
|
+
column: current.column
|
|
1735
|
+
}, { extend: event.shiftKey });
|
|
1736
|
+
event.preventDefault();
|
|
1737
|
+
return;
|
|
1738
|
+
}
|
|
534
1739
|
if (grid.handleKey(event)) event.preventDefault();
|
|
535
1740
|
};
|
|
536
1741
|
const onCopy = (event, cut = false) => {
|
|
@@ -540,32 +1745,54 @@ const DataGridRoot = defineComponent({
|
|
|
540
1745
|
if (cut) grid.clear();
|
|
541
1746
|
};
|
|
542
1747
|
const onPaste = (event) => {
|
|
1748
|
+
if (grid.editor || event.defaultPrevented) return;
|
|
543
1749
|
const text = event.clipboardData?.getData("text/plain");
|
|
544
|
-
if (
|
|
1750
|
+
if (text === void 0 || text === "") return;
|
|
545
1751
|
event.preventDefault();
|
|
546
1752
|
grid.paste(text);
|
|
547
1753
|
};
|
|
548
|
-
|
|
1754
|
+
const render = (binding) => {
|
|
1755
|
+
virtual = binding;
|
|
549
1756
|
const focusPosition = grid.selection?.focus;
|
|
550
1757
|
return h(props.as, {
|
|
551
1758
|
ref: element,
|
|
552
1759
|
id,
|
|
553
|
-
role: "grid",
|
|
1760
|
+
role: props.table.tree ? "treegrid" : "grid",
|
|
554
1761
|
tabindex: 0,
|
|
555
1762
|
"aria-label": props.label,
|
|
556
1763
|
"aria-multiselectable": "true",
|
|
557
|
-
"aria-rowcount": props.table.rows.length + 1,
|
|
558
|
-
"aria-colcount": props.table.columns.length,
|
|
1764
|
+
"aria-rowcount": (props.table.tree ? props.table.processedRows.length : binding ? props.table.rowCount : props.table.rows.length) + 1,
|
|
1765
|
+
"aria-colcount": props.table.columns.length + props.leadingColumns,
|
|
1766
|
+
"aria-busy": props.table.loadState === "loading" || props.table.loadState === "streaming" ? "true" : void 0,
|
|
559
1767
|
"aria-activedescendant": focusPosition && !grid.editor ? cellId(id, focusPosition.row, focusPosition.column) : void 0,
|
|
560
1768
|
onKeydown,
|
|
561
1769
|
onCopy: (event) => onCopy(event),
|
|
562
1770
|
onCut: (event) => onCopy(event, true),
|
|
563
|
-
onPaste
|
|
564
|
-
|
|
1771
|
+
onPaste,
|
|
1772
|
+
...binding ? {
|
|
1773
|
+
"data-virtual": "",
|
|
1774
|
+
style: binding.columns ? {
|
|
1775
|
+
tableLayout: "fixed",
|
|
1776
|
+
width: `${binding.columns.totalSize + binding.gutter}px`
|
|
1777
|
+
} : void 0
|
|
1778
|
+
} : {}
|
|
1779
|
+
}, binding ? [virtualColgroup(props.table.columns, binding), slots.default?.({
|
|
1780
|
+
table: props.table,
|
|
1781
|
+
grid
|
|
1782
|
+
}) ?? [h(DataTableHeader), h(DataGridBody)]] : slots.default?.({
|
|
565
1783
|
table: props.table,
|
|
566
1784
|
grid
|
|
567
1785
|
}) ?? [h(DataTableHeader), h(DataGridBody)]);
|
|
568
1786
|
};
|
|
1787
|
+
return () => props.virtual ? h(VirtualContent, {
|
|
1788
|
+
table: props.table,
|
|
1789
|
+
grid,
|
|
1790
|
+
options: virtualOptions(props),
|
|
1791
|
+
virtualColumns: props.virtualColumns,
|
|
1792
|
+
gutter: props.gutter,
|
|
1793
|
+
tableElement: () => element.value,
|
|
1794
|
+
render
|
|
1795
|
+
}) : render();
|
|
569
1796
|
}
|
|
570
1797
|
});
|
|
571
1798
|
/**
|
|
@@ -575,70 +1802,103 @@ const DataGridRoot = defineComponent({
|
|
|
575
1802
|
const DataGridBody = defineComponent({
|
|
576
1803
|
name: "DataGridBody",
|
|
577
1804
|
slots: Object,
|
|
578
|
-
props: {
|
|
1805
|
+
props: {
|
|
1806
|
+
as: asProp("tbody"),
|
|
1807
|
+
...virtualProps,
|
|
1808
|
+
colspan: {
|
|
1809
|
+
type: Number,
|
|
1810
|
+
default: void 0
|
|
1811
|
+
},
|
|
1812
|
+
treeCell: {
|
|
1813
|
+
type: Boolean,
|
|
1814
|
+
default: true
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
579
1817
|
setup(props, { slots }) {
|
|
580
1818
|
const table = injectDataTable("<DataGridBody>");
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
row,
|
|
592
|
-
column,
|
|
593
|
-
rowIndex,
|
|
594
|
-
columnIndex
|
|
595
|
-
}, slots.cell ? { default: (cellProps) => slots.cell?.(cellProps) } : void 0))])));
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
/** The text box shown in a cell while it is edited. */
|
|
599
|
-
const DataGridEditor = defineComponent({
|
|
600
|
-
name: "DataGridEditor",
|
|
601
|
-
props: { label: {
|
|
602
|
-
type: String,
|
|
603
|
-
required: true
|
|
604
|
-
} },
|
|
605
|
-
setup(props) {
|
|
606
|
-
const { grid, context } = useGrid("<DataGridEditor>");
|
|
607
|
-
const input = ref();
|
|
608
|
-
onMounted(() => {
|
|
609
|
-
const element = input.value;
|
|
610
|
-
if (element) {
|
|
611
|
-
element.focus();
|
|
612
|
-
element.setSelectionRange(element.value.length, element.value.length);
|
|
1819
|
+
const { grid } = useGrid("<DataGridBody>");
|
|
1820
|
+
const virtual = injectVirtual();
|
|
1821
|
+
const hierarchy = injectHierarchy();
|
|
1822
|
+
const details = createDetails(table, () => slots.detail, () => props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
|
|
1823
|
+
const element = ref();
|
|
1824
|
+
const render = (binding) => {
|
|
1825
|
+
if (binding && slots.default) {
|
|
1826
|
+
const sentinel = loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
|
|
1827
|
+
const content = slots.default({ rows: binding.rowItems.filter((item) => item.renderItem.kind === "row").map((item) => item.renderItem.row) });
|
|
1828
|
+
return h(props.as, { ref: element }, sentinel ? [...content, sentinel] : content);
|
|
613
1829
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
1830
|
+
if (binding) return h(props.as, { ref: element }, [
|
|
1831
|
+
...renderRows(binding, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0), (item) => {
|
|
1832
|
+
if (item.renderItem.kind !== "row") return details.render(item.renderItem.row, item.renderItem.rowIndex);
|
|
1833
|
+
const { row, rowIndex } = item.renderItem;
|
|
1834
|
+
const columns = renderColumns(table.columns, binding, (column, columnIndex) => h(DataGridCell, {
|
|
1835
|
+
key: column.id,
|
|
1836
|
+
row,
|
|
1837
|
+
column,
|
|
1838
|
+
rowIndex,
|
|
1839
|
+
columnIndex,
|
|
1840
|
+
tree: props.treeCell
|
|
1841
|
+
}, {
|
|
1842
|
+
default: slots.cell,
|
|
1843
|
+
editor: slots.editor,
|
|
1844
|
+
$stable: true
|
|
1845
|
+
}), "td");
|
|
1846
|
+
return h("tr", {
|
|
1847
|
+
key: item.key,
|
|
1848
|
+
role: "row",
|
|
1849
|
+
"aria-rowindex": table.pageStart + rowIndex + 1,
|
|
1850
|
+
"data-key": String(row.key),
|
|
1851
|
+
"data-dirty": flag(row.isDirty),
|
|
1852
|
+
...hierarchyRowAttrs(table, row, hierarchy),
|
|
1853
|
+
"data-virtual-row": "",
|
|
1854
|
+
ref: (target) => binding.rows.measureElement(target, item.key)
|
|
1855
|
+
}, [slots.rowHeader?.({
|
|
1856
|
+
row,
|
|
1857
|
+
index: rowIndex
|
|
1858
|
+
}), ...columns]);
|
|
1859
|
+
}),
|
|
1860
|
+
...details.retained(new Set(binding.rowItems.filter((item) => item.renderItem.kind === "detail").map((item) => item.renderItem.row.key))),
|
|
1861
|
+
...loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0)) ? [loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0))] : []
|
|
1862
|
+
]);
|
|
1863
|
+
const content = slots.default?.({ rows: table.rows }) ?? table.renderItems.map((item) => item.kind === "detail" ? details.render(item.row, item.rowIndex) : (() => {
|
|
1864
|
+
const { row, rowIndex } = item;
|
|
1865
|
+
return h("tr", {
|
|
1866
|
+
key: item.key,
|
|
1867
|
+
role: "row",
|
|
1868
|
+
"aria-rowindex": rowIndex + 2,
|
|
1869
|
+
"data-key": String(row.key),
|
|
1870
|
+
"data-dirty": flag(row.isDirty),
|
|
1871
|
+
...hierarchyRowAttrs(table, row, hierarchy)
|
|
1872
|
+
}, [slots.rowHeader?.({
|
|
1873
|
+
row,
|
|
1874
|
+
index: rowIndex
|
|
1875
|
+
}), ...table.columns.map((column, columnIndex) => h(DataGridCell, {
|
|
1876
|
+
key: column.id,
|
|
1877
|
+
row,
|
|
1878
|
+
column,
|
|
1879
|
+
rowIndex,
|
|
1880
|
+
columnIndex,
|
|
1881
|
+
tree: props.treeCell
|
|
1882
|
+
}, {
|
|
1883
|
+
default: slots.cell,
|
|
1884
|
+
editor: slots.editor,
|
|
1885
|
+
$stable: true
|
|
1886
|
+
}))]);
|
|
1887
|
+
})());
|
|
1888
|
+
const sentinel = loadSentinel(table, props.colspan ?? table.columns.length + (slots.rowHeader ? 1 : 0));
|
|
1889
|
+
return h(props.as, [
|
|
1890
|
+
...content,
|
|
1891
|
+
...slots.default ? [] : details.retained(new Set(table.renderItems.filter((item) => item.kind === "detail").map((item) => item.row.key))),
|
|
1892
|
+
sentinel
|
|
1893
|
+
]);
|
|
618
1894
|
};
|
|
619
|
-
return () => h(
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
if (grid.editor) grid.commitEdit();
|
|
627
|
-
},
|
|
628
|
-
onKeydown: (event) => {
|
|
629
|
-
if (event.key === "Enter") {
|
|
630
|
-
event.preventDefault();
|
|
631
|
-
finish(event.shiftKey ? "up" : "down");
|
|
632
|
-
} else if (event.key === "Tab") {
|
|
633
|
-
event.preventDefault();
|
|
634
|
-
finish(event.shiftKey ? "left" : "right");
|
|
635
|
-
} else if (event.key === "Escape") {
|
|
636
|
-
event.preventDefault();
|
|
637
|
-
grid.cancelEdit();
|
|
638
|
-
context.focus();
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
});
|
|
1895
|
+
return () => props.virtual && !virtual ? h(VirtualContent, {
|
|
1896
|
+
table,
|
|
1897
|
+
grid,
|
|
1898
|
+
options: virtualOptions(props),
|
|
1899
|
+
tableElement: () => element.value?.closest("table"),
|
|
1900
|
+
render
|
|
1901
|
+
}) : render(virtual);
|
|
642
1902
|
}
|
|
643
1903
|
});
|
|
644
1904
|
/**
|
|
@@ -652,6 +1912,10 @@ const DataGridCell = defineComponent({
|
|
|
652
1912
|
row: rowProp,
|
|
653
1913
|
column: columnProp,
|
|
654
1914
|
as: asProp("td"),
|
|
1915
|
+
tree: {
|
|
1916
|
+
type: Boolean,
|
|
1917
|
+
default: true
|
|
1918
|
+
},
|
|
655
1919
|
/** The row's position on the page. Found by searching the rows when left out. */
|
|
656
1920
|
rowIndex: {
|
|
657
1921
|
type: Number,
|
|
@@ -665,14 +1929,18 @@ const DataGridCell = defineComponent({
|
|
|
665
1929
|
},
|
|
666
1930
|
setup(props, { slots }) {
|
|
667
1931
|
const table = injectDataTable("<DataGridCell>");
|
|
1932
|
+
const columnOffset = injectColumnOffset();
|
|
668
1933
|
const { grid, context } = useGrid("<DataGridCell>");
|
|
1934
|
+
const virtual = injectVirtual();
|
|
1935
|
+
const hierarchy = injectHierarchy();
|
|
669
1936
|
const element = ref();
|
|
1937
|
+
const issueId = useEditorId();
|
|
670
1938
|
const position = () => ({
|
|
671
1939
|
row: props.rowIndex ?? table.rows.indexOf(props.row),
|
|
672
1940
|
column: props.columnIndex ?? table.columns.indexOf(props.column)
|
|
673
1941
|
});
|
|
674
1942
|
watch(() => grid.isFocused(position()), (focused) => {
|
|
675
|
-
if (focused) element.value?.scrollIntoView?.({
|
|
1943
|
+
if (focused && !virtual) element.value?.scrollIntoView?.({
|
|
676
1944
|
block: "nearest",
|
|
677
1945
|
inline: "nearest"
|
|
678
1946
|
});
|
|
@@ -684,33 +1952,72 @@ const DataGridCell = defineComponent({
|
|
|
684
1952
|
const editable = column.isEditable(row.original);
|
|
685
1953
|
const value = row.getValue(column.id);
|
|
686
1954
|
const display = row.getDisplay(column.id);
|
|
1955
|
+
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)) ?? [];
|
|
1956
|
+
const pending = table.pendingCells.some((cell) => cell.rowKey === row.key && cell.column === column.id);
|
|
1957
|
+
const finish = (edit, direction) => {
|
|
1958
|
+
const result = grid.submitEdit(edit, direction);
|
|
1959
|
+
const focus = () => {
|
|
1960
|
+
if (!grid.editor) context.focus();
|
|
1961
|
+
};
|
|
1962
|
+
if (result?.completion) result.completion.then(focus);
|
|
1963
|
+
else focus();
|
|
1964
|
+
return result;
|
|
1965
|
+
};
|
|
687
1966
|
let content;
|
|
688
|
-
if (editing)
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
1967
|
+
if (editing) {
|
|
1968
|
+
const editor = {
|
|
1969
|
+
row,
|
|
1970
|
+
column,
|
|
1971
|
+
value,
|
|
1972
|
+
draft: grid.editor?.draft ?? "",
|
|
1973
|
+
spec: grid.editorFor(at) ?? { kind: "text" },
|
|
1974
|
+
finish: (direction) => {
|
|
1975
|
+
finish(void 0, direction);
|
|
1976
|
+
},
|
|
1977
|
+
input: (text) => grid.updateDraft(text),
|
|
1978
|
+
update: (text) => grid.updateDraft(text),
|
|
1979
|
+
commit: (next) => finish(next === void 0 ? void 0 : typeof next === "string" ? { input: next } : { value: next }),
|
|
1980
|
+
cancel: () => {
|
|
1981
|
+
if (!pending) {
|
|
1982
|
+
grid.cancelEdit();
|
|
1983
|
+
context.cancel(row.key);
|
|
1984
|
+
context.focus();
|
|
1985
|
+
}
|
|
1986
|
+
},
|
|
1987
|
+
issues,
|
|
1988
|
+
pending,
|
|
1989
|
+
attrs: issueAttrs(issueId, issues, pending)
|
|
1990
|
+
};
|
|
1991
|
+
content = slots.editor?.(editor) ?? h(DataCellEditor, {
|
|
1992
|
+
editor,
|
|
1993
|
+
spec: grid.editorFor(at) ?? { kind: "text" },
|
|
1994
|
+
finish: (direction) => finish(void 0, direction)
|
|
1995
|
+
});
|
|
1996
|
+
} else {
|
|
1997
|
+
const children = () => slots.default?.({
|
|
1998
|
+
row,
|
|
1999
|
+
column,
|
|
2000
|
+
value,
|
|
2001
|
+
display,
|
|
2002
|
+
editable
|
|
2003
|
+
}) ?? display;
|
|
2004
|
+
content = props.tree && table.tree && column.id === resolveTreeColumn(table, hierarchy?.treeColumn) ? h(DataTableTreeCell, { row }, { default: children }) : children();
|
|
2005
|
+
}
|
|
701
2006
|
return h(props.as, {
|
|
702
2007
|
ref: element,
|
|
703
2008
|
id: cellId(context.id, at.row, at.column),
|
|
704
2009
|
role: "gridcell",
|
|
705
2010
|
"aria-selected": String(grid.isSelected(at)),
|
|
706
2011
|
"aria-readonly": editable ? void 0 : "true",
|
|
707
|
-
"aria-colindex": at.column + 1,
|
|
2012
|
+
"aria-colindex": at.column + columnOffset() + 1,
|
|
708
2013
|
"data-column": column.id,
|
|
709
2014
|
"data-align": column.align,
|
|
710
2015
|
"data-focused": flag(grid.isFocused(at)),
|
|
711
2016
|
"data-selected": flag(grid.isSelected(at)),
|
|
712
2017
|
"data-editing": flag(editing),
|
|
713
2018
|
"data-readonly": flag(!editable),
|
|
2019
|
+
"data-dirty": flag(row.isDirty),
|
|
2020
|
+
...issueAttrs(issueId, issues, pending),
|
|
714
2021
|
style: columnStyle(column),
|
|
715
2022
|
onMousedown: (event) => {
|
|
716
2023
|
if (editing || event.button !== 0) return;
|
|
@@ -722,13 +2029,13 @@ const DataGridCell = defineComponent({
|
|
|
722
2029
|
},
|
|
723
2030
|
onDblclick: () => {
|
|
724
2031
|
grid.focusCell(at);
|
|
725
|
-
grid.startEdit();
|
|
2032
|
+
grid.startEdit(editorText(value, grid.editorFor(at)?.kind ?? "text"));
|
|
726
2033
|
}
|
|
727
|
-
}, content);
|
|
2034
|
+
}, [content, editorIssues(issueId, issues, pending)]);
|
|
728
2035
|
};
|
|
729
2036
|
}
|
|
730
2037
|
});
|
|
731
2038
|
|
|
732
2039
|
//#endregion
|
|
733
|
-
export { DataGridBody, DataGridCell, DataGridRoot, DataTableBody, DataTableCaption, DataTableCell, DataTableColumnVisibility, DataTableEmpty, DataTableHeader, DataTableHeaderCell, DataTableHeaderRow, DataTablePageSize, DataTablePagination, DataTableRoot, DataTableRow, DataTableSearch, DataTableSelectAll, DataTableSelectRow, DataTableSortButton, DataTableStatus, cellId, columnStyle };
|
|
2040
|
+
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 };
|
|
734
2041
|
//# sourceMappingURL=index.js.map
|