@affino/datagrid-vue 0.3.30 → 0.3.32
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/app/useDataGridAppClipboard.d.ts +8 -3
- package/dist/app/useDataGridAppClipboard.d.ts.map +1 -1
- package/dist/app/useDataGridAppClipboard.js +99 -21
- package/dist/app/useDataGridAppFill.d.ts +28 -3
- package/dist/app/useDataGridAppFill.d.ts.map +1 -1
- package/dist/app/useDataGridAppFill.js +25 -3
- package/dist/app/useDataGridAppInlineEditing.d.ts +11 -1
- package/dist/app/useDataGridAppInlineEditing.d.ts.map +1 -1
- package/dist/app/useDataGridAppInlineEditing.js +44 -3
- package/dist/app/useDataGridAppIntentHistory.d.ts.map +1 -1
- package/dist/app/useDataGridAppIntentHistory.js +24 -3
- package/dist/app/useDataGridAppInteractionController.d.ts +105 -5
- package/dist/app/useDataGridAppInteractionController.d.ts.map +1 -1
- package/dist/app/useDataGridAppInteractionController.js +479 -39
- package/dist/app/useDataGridAppViewport.d.ts +4 -1
- package/dist/app/useDataGridAppViewport.d.ts.map +1 -1
- package/dist/app/useDataGridAppViewport.js +39 -4
- package/dist/composables/useDataGridRuntime.d.ts +1 -1
- package/dist/composables/useDataGridRuntime.d.ts.map +1 -1
- package/dist/composables/useDataGridRuntime.js +41 -1
- package/package.json +3 -3
|
@@ -2,6 +2,52 @@ import { computed, nextTick, ref } from "vue";
|
|
|
2
2
|
import { invokeDataGridCellInteraction, resolveDataGridCellKeyboardAction, toggleDataGridCellValue, } from "@affino/datagrid-core";
|
|
3
3
|
import { buildDataGridFillMatrix, canToggleDataGridFillBehavior, resolveDataGridDefaultFillBehavior, } from "../composables/dataGridFillBehavior";
|
|
4
4
|
import { useDataGridAxisAutoScrollDelta, useDataGridCellNavigation, useDataGridCellPointerDownRouter, useDataGridDragPointerSelection, useDataGridFillHandleStart, useDataGridFillSelectionLifecycle, useDataGridHistoryActionRunner, useDataGridKeyboardCommandRouter, useDataGridPointerCellCoordResolver, useDataGridPointerAutoScroll, useDataGridPointerPreviewRouter, useDataGridRangeMoveLifecycle, useDataGridRangeMoveStart, useDataGridRangeMutationEngine, } from "../advanced";
|
|
5
|
+
import { resolveMissingRowIndexInRange } from "./useDataGridAppClipboard";
|
|
6
|
+
function getFillRangeStart(range) {
|
|
7
|
+
const candidate = range;
|
|
8
|
+
return Number.isFinite(candidate.startRow)
|
|
9
|
+
? Math.max(0, Math.trunc(Number(candidate.startRow)))
|
|
10
|
+
: Math.max(0, Math.trunc(Number(candidate.start ?? 0)));
|
|
11
|
+
}
|
|
12
|
+
function getFillRangeEnd(range) {
|
|
13
|
+
const candidate = range;
|
|
14
|
+
return Number.isFinite(candidate.endRow)
|
|
15
|
+
? Math.max(0, Math.trunc(Number(candidate.endRow)))
|
|
16
|
+
: Math.max(0, Math.trunc(Number(candidate.end ?? getFillRangeStart(range))));
|
|
17
|
+
}
|
|
18
|
+
function formatServerFillAffectedRange(range, fallbackColumns) {
|
|
19
|
+
const start = getFillRangeStart(range);
|
|
20
|
+
const end = getFillRangeEnd(range);
|
|
21
|
+
const startColumn = Number.isFinite(range.startColumn)
|
|
22
|
+
? range.startColumn
|
|
23
|
+
: fallbackColumns.startColumn;
|
|
24
|
+
const endColumn = Number.isFinite(range.endColumn)
|
|
25
|
+
? range.endColumn
|
|
26
|
+
: fallbackColumns.endColumn;
|
|
27
|
+
return `${start}..${end} x ${startColumn}..${endColumn}`;
|
|
28
|
+
}
|
|
29
|
+
function normalizeServerFillInvalidationRange(range) {
|
|
30
|
+
if (!range) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const candidate = range;
|
|
34
|
+
const rawStart = Number.isFinite(candidate.startRow)
|
|
35
|
+
? candidate.startRow
|
|
36
|
+
: candidate.start;
|
|
37
|
+
const rawEnd = Number.isFinite(candidate.endRow)
|
|
38
|
+
? candidate.endRow
|
|
39
|
+
: candidate.end;
|
|
40
|
+
const start = Number(rawStart);
|
|
41
|
+
const end = Number(rawEnd ?? rawStart);
|
|
42
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
start: Math.max(0, Math.trunc(start)),
|
|
47
|
+
end: Math.max(Math.max(0, Math.trunc(start)), Math.trunc(end)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const ROW_SELECTION_COLUMN_KEY = "__datagrid_row_selection__";
|
|
5
51
|
import { useDataGridAppFill, } from "./useDataGridAppFill";
|
|
6
52
|
import { restoreDataGridFocus } from "./dataGridFocusRestore";
|
|
7
53
|
const DRAG_SELECTION_POINTER_THRESHOLD_PX = 4;
|
|
@@ -30,10 +76,53 @@ export function useDataGridAppInteractionController(options) {
|
|
|
30
76
|
}
|
|
31
77
|
return options.readCell(row, columnKey).trim().length > 0;
|
|
32
78
|
};
|
|
79
|
+
const isServerBackedRowModel = () => {
|
|
80
|
+
const controllerRowModel = options.runtimeRowModel;
|
|
81
|
+
const runtimeRowModel = options.runtime.rowModel;
|
|
82
|
+
return controllerRowModel?.dataSource != null || runtimeRowModel?.dataSource != null;
|
|
83
|
+
};
|
|
84
|
+
const resolveSelectableColumnIndexes = () => {
|
|
85
|
+
return options.visibleColumns.value
|
|
86
|
+
.map((column, columnIndex) => ({ column, columnIndex }))
|
|
87
|
+
.filter(({ column }) => column.key !== ROW_SELECTION_COLUMN_KEY)
|
|
88
|
+
.map(({ columnIndex }) => columnIndex);
|
|
89
|
+
};
|
|
33
90
|
const resolveDirectionalSemanticJumpTarget = (current, direction, event) => {
|
|
34
91
|
if (!(event.ctrlKey || event.metaKey) || event.altKey) {
|
|
35
92
|
return undefined;
|
|
36
93
|
}
|
|
94
|
+
if (event.shiftKey && isServerBackedRowModel()) {
|
|
95
|
+
const lastRowIndex = Math.max(0, options.totalRows.value - 1);
|
|
96
|
+
const selectableColumnIndexes = resolveSelectableColumnIndexes();
|
|
97
|
+
const firstSelectableColumnIndex = selectableColumnIndexes[0] ?? 0;
|
|
98
|
+
const lastSelectableColumnIndex = selectableColumnIndexes[selectableColumnIndexes.length - 1] ?? Math.max(0, options.visibleColumns.value.length - 1);
|
|
99
|
+
switch (direction) {
|
|
100
|
+
case "down":
|
|
101
|
+
return options.normalizeCellCoord({
|
|
102
|
+
...current,
|
|
103
|
+
rowIndex: lastRowIndex,
|
|
104
|
+
rowId: getBodyRowAtIndex(lastRowIndex)?.rowId ?? null,
|
|
105
|
+
}) ?? current;
|
|
106
|
+
case "up":
|
|
107
|
+
return options.normalizeCellCoord({
|
|
108
|
+
...current,
|
|
109
|
+
rowIndex: 0,
|
|
110
|
+
rowId: getBodyRowAtIndex(0)?.rowId ?? null,
|
|
111
|
+
}) ?? current;
|
|
112
|
+
case "right":
|
|
113
|
+
return options.normalizeCellCoord({
|
|
114
|
+
...current,
|
|
115
|
+
columnIndex: lastSelectableColumnIndex,
|
|
116
|
+
rowId: getBodyRowAtIndex(current.rowIndex)?.rowId ?? null,
|
|
117
|
+
}) ?? current;
|
|
118
|
+
case "left":
|
|
119
|
+
return options.normalizeCellCoord({
|
|
120
|
+
...current,
|
|
121
|
+
columnIndex: firstSelectableColumnIndex,
|
|
122
|
+
rowId: getBodyRowAtIndex(current.rowIndex)?.rowId ?? null,
|
|
123
|
+
}) ?? current;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
37
126
|
const currentCellIsNonEmpty = isSemanticNavigationCellNonEmpty(current.rowIndex, current.columnIndex);
|
|
38
127
|
if (currentCellIsNonEmpty == null) {
|
|
39
128
|
return undefined;
|
|
@@ -174,7 +263,7 @@ export function useDataGridAppInteractionController(options) {
|
|
|
174
263
|
}
|
|
175
264
|
return event.key.length === 1;
|
|
176
265
|
};
|
|
177
|
-
const applyDirectCellEdit = (row, rowIndex, columnIndex, columnKey, nextValue, label) => {
|
|
266
|
+
const applyDirectCellEdit = async (row, rowIndex, columnIndex, columnKey, nextValue, label) => {
|
|
178
267
|
if (row.kind === "group" || row.rowId == null) {
|
|
179
268
|
return false;
|
|
180
269
|
}
|
|
@@ -183,7 +272,7 @@ export function useDataGridAppInteractionController(options) {
|
|
|
183
272
|
if (resolvedRow.kind === "group" || resolvedRow.rowId == null) {
|
|
184
273
|
return false;
|
|
185
274
|
}
|
|
186
|
-
options.runtime.api.rows.applyEdits([
|
|
275
|
+
await options.runtime.api.rows.applyEdits([
|
|
187
276
|
{
|
|
188
277
|
rowId: resolvedRow.rowId,
|
|
189
278
|
data: {
|
|
@@ -397,12 +486,12 @@ export function useDataGridAppInteractionController(options) {
|
|
|
397
486
|
setSelectionFromRange: (range, activePosition) => {
|
|
398
487
|
applySelectionRangeWithActivePosition(range, activePosition);
|
|
399
488
|
},
|
|
400
|
-
recordIntent: (descriptor, beforeSnapshot) => {
|
|
489
|
+
recordIntent: (descriptor, beforeSnapshot, afterSnapshotOverride) => {
|
|
401
490
|
void options.recordIntentTransaction({
|
|
402
491
|
intent: descriptor.intent,
|
|
403
492
|
label: descriptor.label,
|
|
404
493
|
affectedRange: descriptor.affectedRange ?? null,
|
|
405
|
-
}, beforeSnapshot);
|
|
494
|
+
}, beforeSnapshot, afterSnapshotOverride);
|
|
406
495
|
},
|
|
407
496
|
setLastAction: () => undefined,
|
|
408
497
|
});
|
|
@@ -416,6 +505,179 @@ export function useDataGridAppInteractionController(options) {
|
|
|
416
505
|
resolveSelectionRange: options.resolveSelectionRange,
|
|
417
506
|
rangesEqual: options.rangesEqual,
|
|
418
507
|
buildFillMatrixFromRange: options.buildFillMatrixFromRange,
|
|
508
|
+
shouldUseServerFill: (_baseRange, previewRange) => {
|
|
509
|
+
const controllerRowModel = options.runtimeRowModel;
|
|
510
|
+
const runtimeRowModelDataSource = controllerRowModel?.dataSource;
|
|
511
|
+
const runtimeRowModelFromRuntime = options.runtime.rowModel?.dataSource;
|
|
512
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_exists", controllerRowModel != null);
|
|
513
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_dataSource_exists", runtimeRowModelDataSource != null);
|
|
514
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_dataSource_keys", Object.keys(runtimeRowModelDataSource ?? {}).length > 0);
|
|
515
|
+
options.reportFillPlumbingDetail?.("controller_runtimeRowModel_dataSource_keys", Object.keys(runtimeRowModelDataSource ?? {}).join(","));
|
|
516
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_commit_type", typeof runtimeRowModelDataSource?.commitFillOperation === "function");
|
|
517
|
+
options.reportFillPlumbingState?.("controller_runtime_rowModel_keys", Object.keys(runtimeRowModelFromRuntime ?? {}).length > 0);
|
|
518
|
+
options.reportFillPlumbingDetail?.("controller_runtime_rowModel_keys", Object.keys(runtimeRowModelFromRuntime ?? {}).join(","));
|
|
519
|
+
options.reportFillPlumbingState?.("controller_runtime_rowModel_commit_type", typeof runtimeRowModelFromRuntime?.commitFillOperation === "function");
|
|
520
|
+
const dataSource = runtimeRowModelDataSource ?? runtimeRowModelFromRuntime;
|
|
521
|
+
options.reportFillPlumbingState?.("commitFillOperation_available", typeof dataSource?.commitFillOperation === "function");
|
|
522
|
+
options.reportFillPlumbingState?.("server_fill_dispatch_attempted", true);
|
|
523
|
+
if (typeof dataSource?.commitFillOperation !== "function") {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
const rowCount = Math.max(0, previewRange.endRow - previewRange.startRow + 1);
|
|
527
|
+
return rowCount >= 256;
|
|
528
|
+
},
|
|
529
|
+
commitServerFill: async ({ baseRange, previewRange, behavior }) => {
|
|
530
|
+
const runtimeRowModelDataSource = options.runtimeRowModel?.dataSource;
|
|
531
|
+
const runtimeRowModelFallback = options.runtime.rowModel?.dataSource;
|
|
532
|
+
const dataSource = runtimeRowModelDataSource ?? runtimeRowModelFallback;
|
|
533
|
+
if (!dataSource?.commitFillOperation) {
|
|
534
|
+
options.reportFillWarning?.("server fill execution not implemented yet");
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const snapshot = options.runtime.api.rows.getSnapshot();
|
|
538
|
+
const operationId = `fill-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
539
|
+
const sourceMatrix = options.buildFillMatrixFromRange(baseRange);
|
|
540
|
+
const fillMatrix = buildDataGridFillMatrix({
|
|
541
|
+
baseRange,
|
|
542
|
+
previewRange,
|
|
543
|
+
sourceMatrix,
|
|
544
|
+
behavior,
|
|
545
|
+
});
|
|
546
|
+
const materializedTargetRowIds = [];
|
|
547
|
+
const optimisticUpdatesByRowId = new Map();
|
|
548
|
+
let optimisticFillCandidate = options.captureRowsSnapshotForRowIds != null;
|
|
549
|
+
if (optimisticFillCandidate) {
|
|
550
|
+
for (let rowIndex = previewRange.startRow; rowIndex <= previewRange.endRow; rowIndex += 1) {
|
|
551
|
+
const targetRow = getBodyRowAtIndex(rowIndex);
|
|
552
|
+
const isMaterialized = options.isRowMaterializedAtIndex
|
|
553
|
+
? options.isRowMaterializedAtIndex(rowIndex)
|
|
554
|
+
: !!targetRow && targetRow.__placeholder !== true;
|
|
555
|
+
if (!isMaterialized || !targetRow || targetRow.kind === "group" || targetRow.rowId == null || targetRow.__placeholder === true) {
|
|
556
|
+
optimisticFillCandidate = false;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
materializedTargetRowIds.push(targetRow.rowId);
|
|
560
|
+
for (let columnIndex = previewRange.startColumn; columnIndex <= previewRange.endColumn; columnIndex += 1) {
|
|
561
|
+
const columnKey = options.visibleColumns.value[columnIndex]?.key;
|
|
562
|
+
if (!columnKey || columnKey === ROW_SELECTION_COLUMN_KEY) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
const rowOffset = rowIndex - previewRange.startRow;
|
|
566
|
+
const columnOffset = columnIndex - previewRange.startColumn;
|
|
567
|
+
const nextValue = fillMatrix[rowOffset]?.[columnOffset] ?? "";
|
|
568
|
+
const current = optimisticUpdatesByRowId.get(targetRow.rowId) ?? {};
|
|
569
|
+
current[columnKey] = nextValue;
|
|
570
|
+
optimisticUpdatesByRowId.set(targetRow.rowId, current);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
let optimisticRollbackUpdates = null;
|
|
575
|
+
if (optimisticFillCandidate && materializedTargetRowIds.length > 0 && optimisticUpdatesByRowId.size > 0) {
|
|
576
|
+
const baselineSnapshot = options.captureRowsSnapshotForRowIds?.(materializedTargetRowIds);
|
|
577
|
+
const baselineRows = baselineSnapshot?.rows ?? [];
|
|
578
|
+
if (baselineRows.length === materializedTargetRowIds.length) {
|
|
579
|
+
const baselineByRowId = new Map();
|
|
580
|
+
for (const entry of baselineRows) {
|
|
581
|
+
baselineByRowId.set(entry.rowId, entry.row);
|
|
582
|
+
}
|
|
583
|
+
optimisticRollbackUpdates = materializedTargetRowIds.flatMap(rowId => {
|
|
584
|
+
const row = baselineByRowId.get(rowId);
|
|
585
|
+
return row ? [{ rowId, data: row }] : [];
|
|
586
|
+
});
|
|
587
|
+
if (optimisticRollbackUpdates.length > 0) {
|
|
588
|
+
options.reportFillPlumbingState?.("server_fill_optimistic_applied", true);
|
|
589
|
+
await Promise.resolve(options.runtime.api.rows.applyEdits(Array.from(optimisticUpdatesByRowId.entries()).map(([rowId, data]) => ({
|
|
590
|
+
rowId,
|
|
591
|
+
data: data,
|
|
592
|
+
}))));
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
optimisticRollbackUpdates = null;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
optimisticFillCandidate = false;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
let result = null;
|
|
603
|
+
try {
|
|
604
|
+
result = await dataSource.commitFillOperation({
|
|
605
|
+
operationId,
|
|
606
|
+
projection: {
|
|
607
|
+
sortModel: snapshot.sortModel ?? [],
|
|
608
|
+
filterModel: snapshot.filterModel ?? null,
|
|
609
|
+
groupBy: snapshot.groupBy ?? null,
|
|
610
|
+
groupExpansion: snapshot.groupExpansion ?? { expandedByDefault: false, toggledGroupKeys: [] },
|
|
611
|
+
treeData: null,
|
|
612
|
+
pivot: null,
|
|
613
|
+
pagination: snapshot.pagination ?? {
|
|
614
|
+
enabled: false,
|
|
615
|
+
pageSize: 0,
|
|
616
|
+
currentPage: 0,
|
|
617
|
+
pageCount: 0,
|
|
618
|
+
totalRowCount: 0,
|
|
619
|
+
startIndex: 0,
|
|
620
|
+
endIndex: 0,
|
|
621
|
+
},
|
|
622
|
+
},
|
|
623
|
+
sourceRange: baseRange,
|
|
624
|
+
targetRange: previewRange,
|
|
625
|
+
fillColumns: options.visibleColumns.value
|
|
626
|
+
.slice(previewRange.startColumn, previewRange.endColumn + 1)
|
|
627
|
+
.map(column => String(column.key)),
|
|
628
|
+
referenceColumns: options.visibleColumns.value
|
|
629
|
+
.slice(baseRange.startColumn, baseRange.endColumn + 1)
|
|
630
|
+
.map(column => String(column.key)),
|
|
631
|
+
mode: behavior,
|
|
632
|
+
metadata: { origin: "double-click-fill", behaviorSource: "default" },
|
|
633
|
+
});
|
|
634
|
+
options.reportFillPlumbingState?.("commitFillOperation_called", true);
|
|
635
|
+
options.reportFillPlumbingState?.("server_fill_operationId", true);
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
if (optimisticRollbackUpdates && optimisticRollbackUpdates.length > 0) {
|
|
639
|
+
await Promise.resolve(options.runtime.api.rows.applyEdits(optimisticRollbackUpdates));
|
|
640
|
+
}
|
|
641
|
+
options.reportFillWarning?.("server fill commit failed");
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
if (!result) {
|
|
645
|
+
if (optimisticRollbackUpdates && optimisticRollbackUpdates.length > 0) {
|
|
646
|
+
await Promise.resolve(options.runtime.api.rows.applyEdits(optimisticRollbackUpdates));
|
|
647
|
+
}
|
|
648
|
+
options.reportFillWarning?.("server fill commit failed");
|
|
649
|
+
return null;
|
|
650
|
+
}
|
|
651
|
+
const invalidationRange = result.invalidation?.kind === "range" ? result.invalidation.range : previewRange;
|
|
652
|
+
const normalizedInvalidationRange = normalizeServerFillInvalidationRange(invalidationRange);
|
|
653
|
+
options.reportFillPlumbingDetail?.("server_fill_raw_invalidation", JSON.stringify(invalidationRange ?? null));
|
|
654
|
+
options.reportFillPlumbingDetail?.("server_fill_normalized_invalidation", normalizedInvalidationRange ? `${normalizedInvalidationRange.start}..${normalizedInvalidationRange.end}` : "none");
|
|
655
|
+
options.reportFillPlumbingDetail?.("server_fill_affected_range", formatServerFillAffectedRange(normalizedInvalidationRange ?? invalidationRange, previewRange));
|
|
656
|
+
const affectedRowCount = result.affectedRowCount ?? 0;
|
|
657
|
+
const warnings = result.warnings ?? [];
|
|
658
|
+
options.reportFillWarning?.(warnings[0] ?? (affectedRowCount > 0 ? "server fill committed" : "server fill no-op"));
|
|
659
|
+
options.reportFillPlumbingState?.("server_fill_affectedRowCount", true);
|
|
660
|
+
if (!options.refreshServerFillViewport) {
|
|
661
|
+
const runtimeRowModel = options.runtimeRowModel;
|
|
662
|
+
const runtimeRowModelFallback = options.runtime.rowModel;
|
|
663
|
+
const rowsApi = options.runtime.api.rows;
|
|
664
|
+
const invalidateTarget = runtimeRowModel?.invalidateRange ?? runtimeRowModelFallback?.invalidateRange;
|
|
665
|
+
if (normalizedInvalidationRange && typeof invalidateTarget === "function") {
|
|
666
|
+
invalidateTarget(normalizedInvalidationRange);
|
|
667
|
+
options.reportFillPlumbingState?.("server_fill_invalidation_applied", true);
|
|
668
|
+
}
|
|
669
|
+
await Promise.resolve(rowsApi.refresh?.());
|
|
670
|
+
}
|
|
671
|
+
return {
|
|
672
|
+
operationId: result.operationId,
|
|
673
|
+
revision: result.revision,
|
|
674
|
+
affectedRange: invalidationRange,
|
|
675
|
+
invalidation: result.invalidation ?? null,
|
|
676
|
+
affectedRowCount,
|
|
677
|
+
affectedCellCount: result.affectedCellCount ?? affectedRowCount,
|
|
678
|
+
warnings,
|
|
679
|
+
};
|
|
680
|
+
},
|
|
419
681
|
applyClipboardEdits: options.applyClipboardEdits,
|
|
420
682
|
isCellEditableAt: (rowIndex, columnIndex) => {
|
|
421
683
|
const row = getBodyRowAtIndex(rowIndex);
|
|
@@ -429,9 +691,23 @@ export function useDataGridAppInteractionController(options) {
|
|
|
429
691
|
lastAppliedFill.value = session;
|
|
430
692
|
activeFillBehavior.value = session?.behavior ?? activeFillBehavior.value;
|
|
431
693
|
},
|
|
694
|
+
setLastServerFillSession: session => {
|
|
695
|
+
if (!session) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
options.recordServerFillTransaction?.({
|
|
699
|
+
intent: "fill",
|
|
700
|
+
label: "Server fill",
|
|
701
|
+
affectedRange: session.affectedRange ?? null,
|
|
702
|
+
operationId: session.operationId,
|
|
703
|
+
revision: session.revision,
|
|
704
|
+
mode: session.behavior,
|
|
705
|
+
});
|
|
706
|
+
},
|
|
707
|
+
syncServerFillViewport: options.refreshServerFillViewport,
|
|
432
708
|
syncViewport: options.syncViewport,
|
|
433
709
|
});
|
|
434
|
-
const applyCommittedFillRange = (baseRange, previewRange, behavior) => {
|
|
710
|
+
const applyCommittedFillRange = async (baseRange, previewRange, behavior) => {
|
|
435
711
|
const resolveRemovedFillRange = (previousRange, nextRange) => {
|
|
436
712
|
if (options.rangesEqual(previousRange, nextRange)) {
|
|
437
713
|
return null;
|
|
@@ -497,13 +773,13 @@ export function useDataGridAppInteractionController(options) {
|
|
|
497
773
|
previewRange,
|
|
498
774
|
sourceMatrix,
|
|
499
775
|
});
|
|
500
|
-
options.applyClipboardEdits(removedRange, [[""]], { recordHistory: false });
|
|
776
|
+
await options.applyClipboardEdits(removedRange, [[""]], { recordHistory: false });
|
|
501
777
|
if (options.rangesEqual(baseRange, previewRange)) {
|
|
502
778
|
options.applySelectionRange(baseRange);
|
|
503
779
|
lastAppliedFill.value = null;
|
|
504
780
|
}
|
|
505
781
|
else {
|
|
506
|
-
options.applyClipboardEdits(previewRange, buildDataGridFillMatrix({
|
|
782
|
+
await options.applyClipboardEdits(previewRange, buildDataGridFillMatrix({
|
|
507
783
|
baseRange,
|
|
508
784
|
previewRange,
|
|
509
785
|
sourceMatrix,
|
|
@@ -520,19 +796,20 @@ export function useDataGridAppInteractionController(options) {
|
|
|
520
796
|
}),
|
|
521
797
|
};
|
|
522
798
|
}
|
|
799
|
+
const afterSnapshot = captureRowsSnapshotForRanges([removedRange, previewRange]);
|
|
523
800
|
options.syncViewport();
|
|
524
801
|
void options.recordIntentTransaction({
|
|
525
802
|
intent: "fill",
|
|
526
|
-
label: "Fill
|
|
803
|
+
label: "Fill edit",
|
|
527
804
|
affectedRange: previewRange,
|
|
528
|
-
}, beforeSnapshot);
|
|
805
|
+
}, beforeSnapshot, afterSnapshot);
|
|
529
806
|
if (behavior) {
|
|
530
807
|
activeFillBehavior.value = behavior;
|
|
531
808
|
}
|
|
532
809
|
return true;
|
|
533
810
|
}
|
|
534
811
|
}
|
|
535
|
-
const applied = applyFillRange(baseRange, previewRange, behavior);
|
|
812
|
+
const applied = await applyFillRange(baseRange, previewRange, behavior);
|
|
536
813
|
if (applied && behavior) {
|
|
537
814
|
activeFillBehavior.value = behavior;
|
|
538
815
|
}
|
|
@@ -806,7 +1083,7 @@ export function useDataGridAppInteractionController(options) {
|
|
|
806
1083
|
if (!baseRange || !previewRange) {
|
|
807
1084
|
return;
|
|
808
1085
|
}
|
|
809
|
-
applyCommittedFillRange(baseRange, previewRange);
|
|
1086
|
+
void applyCommittedFillRange(baseRange, previewRange);
|
|
810
1087
|
},
|
|
811
1088
|
setFillDragging: value => {
|
|
812
1089
|
isFillDragging.value = value;
|
|
@@ -1065,9 +1342,14 @@ export function useDataGridAppInteractionController(options) {
|
|
|
1065
1342
|
if (!range) {
|
|
1066
1343
|
return false;
|
|
1067
1344
|
}
|
|
1345
|
+
const missingRowIndex = resolveMissingRowIndexInRange(getBodyRowAtIndex, range);
|
|
1346
|
+
if (missingRowIndex != null) {
|
|
1347
|
+
options.reportFillWarning?.("Selected range includes unloaded rows. Load rows or use server operation.");
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1068
1350
|
const beforeSnapshot = captureRowsSnapshotForRanges([range]);
|
|
1069
1351
|
options.clearPendingClipboardOperation(false);
|
|
1070
|
-
const applied = options.applyClipboardEdits(range, [[""]], { recordHistory: false });
|
|
1352
|
+
const applied = await options.applyClipboardEdits(range, [[""]], { recordHistory: false });
|
|
1071
1353
|
if (applied <= 0) {
|
|
1072
1354
|
return false;
|
|
1073
1355
|
}
|
|
@@ -1211,6 +1493,33 @@ export function useDataGridAppInteractionController(options) {
|
|
|
1211
1493
|
const isNonEmptyFillReferenceValue = (row, columnKey) => {
|
|
1212
1494
|
return options.readCell(row, columnKey).trim().length > 0;
|
|
1213
1495
|
};
|
|
1496
|
+
const buildFillProjectionContext = () => {
|
|
1497
|
+
const getSnapshot = options.runtime.api.rows.getSnapshot;
|
|
1498
|
+
if (typeof getSnapshot !== "function") {
|
|
1499
|
+
return null;
|
|
1500
|
+
}
|
|
1501
|
+
const snapshot = getSnapshot();
|
|
1502
|
+
if (!snapshot) {
|
|
1503
|
+
return null;
|
|
1504
|
+
}
|
|
1505
|
+
return {
|
|
1506
|
+
sortModel: snapshot.sortModel ?? [],
|
|
1507
|
+
filterModel: snapshot.filterModel ?? null,
|
|
1508
|
+
groupBy: snapshot.groupBy ?? null,
|
|
1509
|
+
groupExpansion: snapshot.groupExpansion ?? { expandedByDefault: false, toggledGroupKeys: [] },
|
|
1510
|
+
treeData: null,
|
|
1511
|
+
pivot: null,
|
|
1512
|
+
pagination: snapshot.pagination ?? {
|
|
1513
|
+
enabled: false,
|
|
1514
|
+
pageSize: 0,
|
|
1515
|
+
currentPage: 0,
|
|
1516
|
+
pageCount: 0,
|
|
1517
|
+
totalRowCount: 0,
|
|
1518
|
+
startIndex: 0,
|
|
1519
|
+
endIndex: 0,
|
|
1520
|
+
},
|
|
1521
|
+
};
|
|
1522
|
+
};
|
|
1214
1523
|
const resolveReferenceColumnExtentEndRow = (baseRange, columnIndex) => {
|
|
1215
1524
|
const columnKey = options.visibleColumns.value[columnIndex]?.key;
|
|
1216
1525
|
if (!columnKey) {
|
|
@@ -1261,52 +1570,183 @@ export function useDataGridAppInteractionController(options) {
|
|
|
1261
1570
|
}
|
|
1262
1571
|
return evaluateDirection(baseRange.endColumn + 1, 1);
|
|
1263
1572
|
};
|
|
1573
|
+
const resolveServerAwareFillBoundary = async (baseRange) => {
|
|
1574
|
+
if (!options.resolveFillBoundary) {
|
|
1575
|
+
options.reportFillPlumbingState?.("interaction_controller_option", false);
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
options.reportFillPlumbingState?.("interaction_controller_option", true);
|
|
1579
|
+
const fillColumns = [];
|
|
1580
|
+
for (let columnIndex = baseRange.startColumn; columnIndex <= baseRange.endColumn; columnIndex += 1) {
|
|
1581
|
+
const columnKey = options.visibleColumns.value[columnIndex]?.key;
|
|
1582
|
+
if (columnKey) {
|
|
1583
|
+
fillColumns.push(columnKey);
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
const leftReferenceColumns = [];
|
|
1587
|
+
for (let columnIndex = baseRange.startColumn - 1; columnIndex >= 0; columnIndex -= 1) {
|
|
1588
|
+
const columnKey = options.visibleColumns.value[columnIndex]?.key;
|
|
1589
|
+
if (columnKey) {
|
|
1590
|
+
leftReferenceColumns.push(columnKey);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
const rightReferenceColumns = [];
|
|
1594
|
+
for (let columnIndex = baseRange.endColumn + 1; columnIndex < options.visibleColumns.value.length; columnIndex += 1) {
|
|
1595
|
+
const columnKey = options.visibleColumns.value[columnIndex]?.key;
|
|
1596
|
+
if (columnKey) {
|
|
1597
|
+
rightReferenceColumns.push(columnKey);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
const projection = buildFillProjectionContext();
|
|
1601
|
+
options.reportFillPlumbingState?.("double_click_fill_columns", fillColumns.length > 0);
|
|
1602
|
+
options.reportFillPlumbingState?.("double_click_left_refs", leftReferenceColumns.length > 0);
|
|
1603
|
+
options.reportFillPlumbingState?.("double_click_right_refs", rightReferenceColumns.length > 0);
|
|
1604
|
+
options.reportFillPlumbingState?.("double_click_projection", projection != null);
|
|
1605
|
+
if (!projection) {
|
|
1606
|
+
return null;
|
|
1607
|
+
}
|
|
1608
|
+
const resolve = async (referenceColumns, direction) => {
|
|
1609
|
+
if (referenceColumns.length === 0) {
|
|
1610
|
+
options.reportFillPlumbingState?.(`double_click_resolve_${direction}_skipped_no_refs`, true);
|
|
1611
|
+
return null;
|
|
1612
|
+
}
|
|
1613
|
+
options.reportFillPlumbingState?.(`double_click_resolve_${direction}_called`, true);
|
|
1614
|
+
return options.resolveFillBoundary({
|
|
1615
|
+
direction,
|
|
1616
|
+
baseRange,
|
|
1617
|
+
fillColumns,
|
|
1618
|
+
referenceColumns,
|
|
1619
|
+
projection,
|
|
1620
|
+
startRowIndex: baseRange.startRow,
|
|
1621
|
+
startColumnIndex: direction === "left" ? baseRange.startColumn - 1 : baseRange.endColumn + 1,
|
|
1622
|
+
limit: 500,
|
|
1623
|
+
});
|
|
1624
|
+
};
|
|
1625
|
+
const left = await resolve(leftReferenceColumns, "left");
|
|
1626
|
+
options.reportFillPlumbingState?.("double_click_left_result", left != null);
|
|
1627
|
+
if (left && left.boundaryKind !== "unresolved" && left.endRowIndex != null) {
|
|
1628
|
+
options.reportFillPlumbingState?.("double_click_left_selected", true);
|
|
1629
|
+
return { endRow: left.endRowIndex, boundaryKind: left.boundaryKind, resolvedByServer: true };
|
|
1630
|
+
}
|
|
1631
|
+
const right = await resolve(rightReferenceColumns, "right");
|
|
1632
|
+
options.reportFillPlumbingState?.("double_click_right_result", right != null);
|
|
1633
|
+
if (right && right.boundaryKind !== "unresolved" && right.endRowIndex != null) {
|
|
1634
|
+
options.reportFillPlumbingState?.("double_click_right_selected", true);
|
|
1635
|
+
return { endRow: right.endRowIndex, boundaryKind: right.boundaryKind, resolvedByServer: true };
|
|
1636
|
+
}
|
|
1637
|
+
if (left?.boundaryKind === "unresolved" || right?.boundaryKind === "unresolved") {
|
|
1638
|
+
options.reportFillWarning?.("server fill boundary unresolved; using loaded-cache fallback");
|
|
1639
|
+
}
|
|
1640
|
+
return null;
|
|
1641
|
+
};
|
|
1264
1642
|
const startFillHandleDoubleClick = (event) => {
|
|
1643
|
+
options.reportFillPlumbingState?.("double_click_handler", true);
|
|
1265
1644
|
if (options.mode.value !== "base" || !isFillHandleEnabled.value) {
|
|
1645
|
+
options.reportFillPlumbingState?.("double_click_handler_skipped", true);
|
|
1266
1646
|
return;
|
|
1267
1647
|
}
|
|
1268
1648
|
const baseRange = options.resolveSelectionRange();
|
|
1269
1649
|
if (!baseRange) {
|
|
1650
|
+
options.reportFillPlumbingState?.("double_click_handler_skipped_no_range", true);
|
|
1270
1651
|
return;
|
|
1271
1652
|
}
|
|
1272
1653
|
const anchorRow = getBodyRowAtIndex(baseRange.endRow);
|
|
1273
1654
|
const anchorColumnKey = options.visibleColumns.value[baseRange.endColumn]?.key;
|
|
1274
1655
|
if (!anchorRow || !anchorColumnKey) {
|
|
1656
|
+
options.reportFillPlumbingState?.("double_click_handler_skipped_no_anchor", true);
|
|
1275
1657
|
return;
|
|
1276
1658
|
}
|
|
1277
1659
|
if (!options.isCellEditable(anchorRow, baseRange.endRow, anchorColumnKey, baseRange.endColumn)) {
|
|
1660
|
+
options.reportFillPlumbingState?.("double_click_handler_skipped_not_editable", true);
|
|
1278
1661
|
return;
|
|
1279
1662
|
}
|
|
1280
|
-
const targetEndRow = resolveBestNeighborFillEndRow(baseRange);
|
|
1281
|
-
const previewRange = targetEndRow > baseRange.endRow
|
|
1282
|
-
? options.normalizeClipboardRange({
|
|
1283
|
-
startRow: baseRange.startRow,
|
|
1284
|
-
endRow: targetEndRow,
|
|
1285
|
-
startColumn: baseRange.startColumn,
|
|
1286
|
-
endColumn: baseRange.endColumn,
|
|
1287
|
-
})
|
|
1288
|
-
: null;
|
|
1289
|
-
if (!previewRange || options.rangesEqual(baseRange, previewRange)) {
|
|
1290
|
-
return;
|
|
1291
|
-
}
|
|
1292
|
-
const sourceMatrix = options.buildFillMatrixFromRange(baseRange);
|
|
1293
|
-
const behavior = activeFillBehavior.value ?? resolveDataGridDefaultFillBehavior({
|
|
1294
|
-
baseRange,
|
|
1295
|
-
previewRange,
|
|
1296
|
-
sourceMatrix,
|
|
1297
|
-
});
|
|
1298
1663
|
const preferredFocusCoord = resolveFillOriginFocusCoord();
|
|
1299
1664
|
event.preventDefault();
|
|
1300
1665
|
event.stopPropagation();
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1666
|
+
void resolveServerAwareFillBoundary(baseRange).then(resolved => {
|
|
1667
|
+
options.reportFillPlumbingState?.("double_click_resolved_server_branch", !!resolved?.resolvedByServer);
|
|
1668
|
+
options.reportFillPlumbingState?.("double_click_resolved_endrow", typeof resolved?.endRow === "number");
|
|
1669
|
+
const targetEndRow = resolved?.endRow ?? resolveBestNeighborFillEndRow(baseRange);
|
|
1670
|
+
const previewRange = targetEndRow > baseRange.endRow
|
|
1671
|
+
? options.normalizeClipboardRange({
|
|
1672
|
+
startRow: baseRange.startRow,
|
|
1673
|
+
endRow: targetEndRow,
|
|
1674
|
+
startColumn: baseRange.startColumn,
|
|
1675
|
+
endColumn: baseRange.endColumn,
|
|
1676
|
+
})
|
|
1305
1677
|
: null;
|
|
1306
|
-
|
|
1307
|
-
|
|
1678
|
+
if (!previewRange || options.rangesEqual(baseRange, previewRange)) {
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
const sourceMatrix = options.buildFillMatrixFromRange(baseRange);
|
|
1682
|
+
const behavior = activeFillBehavior.value ?? resolveDataGridDefaultFillBehavior({
|
|
1683
|
+
baseRange,
|
|
1684
|
+
previewRange,
|
|
1685
|
+
sourceMatrix,
|
|
1686
|
+
});
|
|
1687
|
+
if (resolved?.resolvedByServer) {
|
|
1688
|
+
options.reportFillPlumbingState?.("double_click_server_branch_entered", true);
|
|
1689
|
+
const resolvedRowCount = previewRange.endRow - previewRange.startRow + 1;
|
|
1690
|
+
const threshold = 500;
|
|
1691
|
+
const rowModel = options.runtimeRowModel;
|
|
1692
|
+
const dataSource = rowModel?.dataSource ?? options.runtime.rowModel?.dataSource;
|
|
1693
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_exists", rowModel != null);
|
|
1694
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_dataSource_exists", dataSource != null);
|
|
1695
|
+
options.reportFillPlumbingState?.("controller_runtimeRowModel_commit_type", typeof rowModel?.dataSource?.commitFillOperation === "function");
|
|
1696
|
+
options.reportFillPlumbingState?.("controller_runtime_rowModel_commit_type", typeof options.runtime.rowModel?.dataSource?.commitFillOperation === "function");
|
|
1697
|
+
const canCommitServerFill = typeof dataSource?.commitFillOperation === "function";
|
|
1698
|
+
options.reportFillPlumbingState?.("commitFillOperation_available", canCommitServerFill);
|
|
1699
|
+
if (resolvedRowCount > threshold) {
|
|
1700
|
+
if (!canCommitServerFill) {
|
|
1701
|
+
options.reportFillPlumbingState?.("double_click_blocked_large", true);
|
|
1702
|
+
options.reportFillWarning?.("server fill execution not implemented yet");
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
let allLoaded = true;
|
|
1707
|
+
for (let rowIndex = previewRange.startRow; rowIndex <= previewRange.endRow; rowIndex += 1) {
|
|
1708
|
+
const isMaterialized = options.isRowMaterializedAtIndex
|
|
1709
|
+
? options.isRowMaterializedAtIndex(rowIndex)
|
|
1710
|
+
: !!getBodyRowAtIndex(rowIndex) && getBodyRowAtIndex(rowIndex).__placeholder !== true;
|
|
1711
|
+
if (!isMaterialized) {
|
|
1712
|
+
allLoaded = false;
|
|
1713
|
+
break;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
if (!allLoaded && !canCommitServerFill) {
|
|
1717
|
+
options.reportFillPlumbingState?.("double_click_blocked_unloaded", true);
|
|
1718
|
+
options.reportFillWarning?.("server fill execution not implemented yet");
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
if (canCommitServerFill) {
|
|
1722
|
+
options.reportFillPlumbingState?.("server-fill-committed", true);
|
|
1723
|
+
void Promise.resolve(applyFillRange(baseRange, previewRange, behavior)).then((applied) => {
|
|
1724
|
+
if (!applied) {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
activeFillBehavior.value = behavior;
|
|
1728
|
+
const restoredCoord = preferredFocusCoord
|
|
1729
|
+
? restoreSelectionActiveCellToCoord(preferredFocusCoord)
|
|
1730
|
+
: null;
|
|
1731
|
+
restoreActiveCellFocus(restoredCoord ?? preferredFocusCoord);
|
|
1732
|
+
});
|
|
1733
|
+
return;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
options.reportFillPlumbingState?.("double_click_batch_commit_path", true);
|
|
1737
|
+
void applyCommittedFillRange(baseRange, previewRange, behavior).then(applied => {
|
|
1738
|
+
if (!applied) {
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
activeFillBehavior.value = behavior;
|
|
1742
|
+
const restoredCoord = preferredFocusCoord
|
|
1743
|
+
? restoreSelectionActiveCellToCoord(preferredFocusCoord)
|
|
1744
|
+
: null;
|
|
1745
|
+
restoreActiveCellFocus(restoredCoord ?? preferredFocusCoord);
|
|
1746
|
+
});
|
|
1747
|
+
});
|
|
1308
1748
|
};
|
|
1309
|
-
const applyLastFillBehavior = (behavior) => {
|
|
1749
|
+
const applyLastFillBehavior = async (behavior) => {
|
|
1310
1750
|
if (!isFillHandleEnabled.value) {
|
|
1311
1751
|
return false;
|
|
1312
1752
|
}
|
|
@@ -1315,7 +1755,7 @@ export function useDataGridAppInteractionController(options) {
|
|
|
1315
1755
|
return false;
|
|
1316
1756
|
}
|
|
1317
1757
|
const preferredFocusCoord = resolveFillOriginFocusCoord();
|
|
1318
|
-
const applied = applyCommittedFillRange(session.baseRange, session.previewRange, behavior);
|
|
1758
|
+
const applied = await applyCommittedFillRange(session.baseRange, session.previewRange, behavior);
|
|
1319
1759
|
if (applied) {
|
|
1320
1760
|
activeFillBehavior.value = behavior;
|
|
1321
1761
|
const restoredCoord = preferredFocusCoord
|
|
@@ -1459,7 +1899,7 @@ export function useDataGridAppInteractionController(options) {
|
|
|
1459
1899
|
if (keyboardAction === "toggle" && columnSnapshot && columnKey) {
|
|
1460
1900
|
event.preventDefault();
|
|
1461
1901
|
options.setCellSelection(row, rowOffset, columnIndex, event.shiftKey);
|
|
1462
|
-
applyDirectCellEdit(row, rowIndex, columnIndex, columnKey, toggleDataGridCellValue({
|
|
1902
|
+
void applyDirectCellEdit(row, rowIndex, columnIndex, columnKey, toggleDataGridCellValue({
|
|
1463
1903
|
column: columnSnapshot.column,
|
|
1464
1904
|
row: row.kind !== "group" ? row.data : undefined,
|
|
1465
1905
|
}), `Toggle ${columnKey}`);
|