@svgrid/grid 3.0.2 → 3.0.4
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 -1
- package/dist/GridMenus.svelte +44 -2
- package/dist/SvChartMenu.svelte +69 -0
- package/dist/SvChartMenu.svelte.d.ts +11 -0
- package/dist/SvChartPanes.svelte +113 -0
- package/dist/SvChartPanes.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.d.ts +110 -39
- package/dist/SvGrid.controller.svelte.js +655 -200
- package/dist/SvGrid.css +105 -2
- package/dist/SvGrid.svelte +386 -514
- package/dist/SvGrid.types.d.ts +210 -9
- package/dist/SvGridCellEditor.svelte +17 -0
- package/dist/SvGridChart.svelte +2257 -329
- package/dist/SvGridChart.svelte.d.ts +6 -106
- package/dist/SvGridChart.types.d.ts +338 -0
- package/dist/SvGridChart.types.js +1 -0
- package/dist/SvGridChartBuilder.svelte +323 -0
- package/dist/SvGridChartBuilder.svelte.d.ts +34 -0
- package/dist/SvGridChartPanel.svelte +221 -275
- package/dist/SvGridChartPickers.svelte +417 -0
- package/dist/SvGridChartPickers.svelte.d.ts +70 -0
- package/dist/SvGridChartView.svelte +13 -14
- package/dist/SvModal.svelte +7 -1
- package/dist/SvModal.svelte.d.ts +5 -0
- package/dist/ai.d.ts +40 -8
- package/dist/ai.js +135 -24
- package/dist/aria.d.ts +11 -0
- package/dist/build-api.js +108 -22
- package/dist/cdn/GridMenus-7kbpnnBW.js +635 -0
- package/dist/cdn/GridMenus-BL7ZgQvU.js +636 -0
- package/dist/cdn/SvChartMenu-Bl6PBbkT.js +58 -0
- package/dist/cdn/SvChartMenu-FBSMINA6.js +59 -0
- package/dist/cdn/{SvDateRangeInput-DMLKmGEc.js → SvDateRangeInput-BeU_TkbQ.js} +15 -14
- package/dist/cdn/{SvDateRangeInput-CaOuMs8O.js → SvDateRangeInput-yX8vzleW.js} +38 -37
- package/dist/cdn/{SvDateTimePicker-sonaH0oh.js → SvDateTimePicker-DQwt4UAs.js} +139 -180
- package/dist/cdn/SvDateTimePicker-vNU6bZ-q.js +775 -0
- package/dist/cdn/{SvGridCellEditor-CjEJMJKc.js → SvGridCellEditor-B1p-vCK5.js} +188 -178
- package/dist/cdn/{SvGridCellEditor-BWnTi2N7.js → SvGridCellEditor-D_0q4xAu.js} +98 -88
- package/dist/cdn/SvGridChart-C3EWAZaM.js +3574 -0
- package/dist/cdn/SvGridChart-DzLkSwCH.js +3572 -0
- package/dist/cdn/SvGridChartBuilder-BE2T1ykB.js +783 -0
- package/dist/cdn/SvGridChartBuilder-CfII62sZ.js +784 -0
- package/dist/cdn/SvGridChartPanel-CCX5_Wgd.js +675 -0
- package/dist/cdn/SvGridChartPanel-xf4sWVTo.js +699 -0
- package/dist/cdn/SvGridChartView-CfuXmY5I.js +48 -0
- package/dist/cdn/SvGridChartView-DYwabQWj.js +47 -0
- package/dist/cdn/SvMenuList-CLN8OuIK.js +452 -0
- package/dist/cdn/SvMenuList-DkGhNKjf.js +451 -0
- package/dist/cdn/SvModal-BAE-pjZX.js +396 -0
- package/dist/cdn/SvModal-CIZWCcad.js +397 -0
- package/dist/cdn/chart-Dz7SqMXH.js +4341 -0
- package/dist/cdn/chart-export-BXqueeWJ.js +135 -0
- package/dist/cdn/chart-export-pdf-DONFUnNY.js +110 -0
- package/dist/cdn/chart-panel-messages-CUbf2R4i.js +1052 -0
- package/dist/cdn/chart-panel-messages-CmNrMdsr.js +1053 -0
- package/dist/cdn/chart-summary-BJW_tg_X.js +138 -0
- package/dist/cdn/chart-trend-CaN9mDEV.js +50 -0
- package/dist/cdn/chart-validate-HcOVFAfJ.js +188 -0
- package/dist/cdn/{column-resize-DsfNXMom.js → column-resize-DpmOLfRp.js} +3 -3
- package/dist/cdn/{date-format-BnnHlqGw.js → date-format-CtquV-p3.js} +583 -603
- package/dist/cdn/{date-format-BNii4zeD.js → date-format-D6KFzU_W.js} +347 -367
- package/dist/cdn/dismissable-DAHetSNk.js +44 -0
- package/dist/cdn/editor-contract-LXgQJAgd.js +22 -0
- package/dist/cdn/focus-trap-BBOQYBma.js +65 -0
- package/dist/cdn/{row-resize-BRcimkUT.js → row-resize-niQCp040.js} +44 -36
- package/dist/cdn/{src-DNbz94Ct.js → src-BKhZ6eXd.js} +10227 -9787
- package/dist/cdn/{src-BJ2opd0j.js → src-V1uu8iE9.js} +6568 -6128
- package/dist/cdn/svgrid.js +26 -15
- package/dist/cdn/svgrid.svelte-external.js +26 -15
- package/dist/chart-axes.d.ts +53 -0
- package/dist/chart-axes.js +351 -0
- package/dist/chart-cartesian.d.ts +88 -0
- package/dist/chart-cartesian.js +1862 -0
- package/dist/chart-decimate.d.ts +51 -0
- package/dist/chart-decimate.js +199 -0
- package/dist/chart-export-pdf.d.ts +47 -0
- package/dist/chart-export-pdf.js +187 -0
- package/dist/chart-export.d.ts +2 -1
- package/dist/chart-export.js +18 -3
- package/dist/chart-financial.d.ts +91 -0
- package/dist/chart-financial.js +175 -0
- package/dist/chart-flow.d.ts +10 -0
- package/dist/chart-flow.js +298 -0
- package/dist/chart-format.d.ts +18 -0
- package/dist/chart-format.js +65 -0
- package/dist/chart-grid.d.ts +8 -0
- package/dist/chart-grid.js +190 -0
- package/dist/chart-hierarchy.d.ts +17 -0
- package/dist/chart-hierarchy.js +146 -0
- package/dist/chart-indicators.d.ts +115 -0
- package/dist/chart-indicators.js +389 -0
- package/dist/chart-messages.d.ts +95 -0
- package/dist/chart-messages.js +92 -0
- package/dist/chart-motion.d.ts +16 -0
- package/dist/chart-motion.js +141 -0
- package/dist/chart-panel-messages.d.ts +265 -0
- package/dist/chart-panel-messages.js +285 -0
- package/dist/chart-pivot.d.ts +87 -0
- package/dist/chart-pivot.js +156 -0
- package/dist/chart-polar.d.ts +25 -0
- package/dist/chart-polar.js +700 -0
- package/dist/chart-samples.d.ts +16 -0
- package/dist/chart-samples.js +153 -0
- package/dist/chart-scale.d.ts +113 -0
- package/dist/chart-scale.js +405 -0
- package/dist/chart-stats.d.ts +111 -0
- package/dist/chart-stats.js +419 -0
- package/dist/chart-stream.d.ts +45 -0
- package/dist/chart-stream.js +91 -0
- package/dist/chart-summary.d.ts +27 -0
- package/dist/chart-summary.js +205 -0
- package/dist/chart-sync.svelte.d.ts +32 -0
- package/dist/chart-sync.svelte.js +23 -0
- package/dist/chart-table.d.ts +35 -0
- package/dist/chart-table.js +165 -0
- package/dist/chart-trend.d.ts +19 -0
- package/dist/chart-trend.js +61 -0
- package/dist/chart-types.d.ts +1456 -0
- package/dist/chart-types.js +6 -0
- package/dist/chart-validate.d.ts +43 -0
- package/dist/chart-validate.js +272 -0
- package/dist/chart-zoom.d.ts +40 -0
- package/dist/chart-zoom.js +145 -0
- package/dist/chart.d.ts +186 -865
- package/dist/chart.js +460 -2204
- package/dist/clipboard.d.ts +1 -0
- package/dist/clipboard.js +137 -18
- package/dist/column-resize.d.ts +3 -0
- package/dist/column-resize.js +4 -1
- package/dist/columns.js +3 -0
- package/dist/command-context.d.ts +19 -0
- package/dist/command-context.js +116 -0
- package/dist/core.d.ts +7 -0
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.d.ts +2 -8
- package/dist/editing.js +262 -33
- package/dist/fill-patterns.d.ts +0 -5
- package/dist/fill-patterns.js +56 -2
- package/dist/grid-messages.d.ts +10 -3
- package/dist/grid-messages.js +5 -0
- package/dist/history.d.ts +83 -0
- package/dist/history.js +133 -0
- package/dist/index.d.ts +24 -6
- package/dist/index.js +20 -2
- package/dist/keyboard-handlers.js +88 -31
- package/dist/keyboard.d.ts +55 -0
- package/dist/keyboard.js +132 -0
- package/dist/menus.d.ts +3 -1
- package/dist/menus.js +7 -2
- package/dist/merges.d.ts +97 -0
- package/dist/merges.js +147 -0
- package/dist/row-resize.d.ts +3 -0
- package/dist/row-resize.js +14 -0
- package/dist/scroll-sync.d.ts +1 -0
- package/dist/scroll-sync.js +24 -0
- package/dist/selection.d.ts +16 -0
- package/dist/selection.js +108 -16
- package/dist/shortcut-registry.d.ts +145 -0
- package/dist/shortcut-registry.js +44 -0
- package/dist/svgrid-wrapper.types.d.ts +213 -22
- package/dist/virtualization/virtualizer.js +38 -17
- package/package.json +6 -1
- package/src/GridMenus.svelte +44 -2
- package/src/SvChartMenu.svelte +69 -0
- package/src/SvChartPanes.svelte +113 -0
- package/src/SvChartPanes.test.ts +66 -0
- package/src/SvGrid.controller.svelte.ts +629 -172
- package/src/SvGrid.css +105 -2
- package/src/SvGrid.svelte +386 -514
- package/src/SvGrid.types.ts +188 -8
- package/src/SvGridCellEditor.svelte +17 -0
- package/src/SvGridChart.svelte +2257 -329
- package/src/SvGridChart.test.ts +1419 -1
- package/src/SvGridChart.types.ts +336 -0
- package/src/SvGridChartBuilder.svelte +323 -0
- package/src/SvGridChartPanel.svelte +221 -275
- package/src/SvGridChartPickers.svelte +417 -0
- package/src/SvGridChartView.svelte +13 -14
- package/src/SvModal.svelte +7 -1
- package/src/SvModal.test.ts +9 -0
- package/src/ai.test.ts +57 -2
- package/src/ai.ts +166 -22
- package/src/aria.d.ts +11 -0
- package/src/build-api.coverage.test.ts +184 -0
- package/src/build-api.ts +107 -18
- package/src/chart-axes.test.ts +573 -0
- package/src/chart-axes.ts +342 -0
- package/src/chart-cartesian.ts +1863 -0
- package/src/chart-decimate.test.ts +249 -0
- package/src/chart-decimate.ts +186 -0
- package/src/chart-export-pdf.test.ts +91 -0
- package/src/chart-export-pdf.ts +207 -0
- package/src/chart-export.test.ts +14 -0
- package/src/chart-export.ts +13 -3
- package/src/chart-financial.ts +208 -0
- package/src/chart-flow.ts +286 -0
- package/src/chart-format.ts +67 -0
- package/src/chart-grid.ts +179 -0
- package/src/chart-hierarchy.ts +135 -0
- package/src/chart-indicators.test.ts +443 -0
- package/src/chart-indicators.ts +384 -0
- package/src/chart-messages.test.ts +21 -0
- package/src/chart-messages.ts +184 -0
- package/src/chart-motion.test.ts +71 -0
- package/src/chart-motion.ts +148 -0
- package/src/chart-panel-messages.test.ts +29 -0
- package/src/chart-panel-messages.ts +550 -0
- package/src/chart-pivot.test.ts +54 -0
- package/src/chart-pivot.ts +225 -0
- package/src/chart-polar.ts +674 -0
- package/src/chart-samples.ts +106 -0
- package/src/chart-scale.ts +394 -0
- package/src/chart-series-types.test.ts +714 -0
- package/src/chart-stats.ts +409 -0
- package/src/chart-stream.test.ts +75 -0
- package/src/chart-stream.ts +114 -0
- package/src/chart-summary.test.ts +90 -0
- package/src/chart-summary.ts +197 -0
- package/src/chart-sync.svelte.ts +41 -0
- package/src/chart-table.test.ts +76 -0
- package/src/chart-table.ts +178 -0
- package/src/chart-trend.ts +47 -0
- package/src/chart-types.ts +1309 -0
- package/src/chart-validate.test.ts +100 -0
- package/src/chart-validate.ts +247 -0
- package/src/chart-zoom.test.ts +137 -0
- package/src/chart-zoom.ts +124 -0
- package/src/chart.coverage.test.ts +81 -0
- package/src/chart.test.ts +56 -3
- package/src/chart.ts +557 -2784
- package/src/clipboard.test.ts +151 -0
- package/src/clipboard.ts +145 -16
- package/src/column-resize.ts +7 -1
- package/src/columns.ts +2 -0
- package/src/command-context.test.ts +200 -0
- package/src/command-context.ts +137 -0
- package/src/core.ts +7 -0
- package/src/editing.test.ts +281 -3
- package/src/editing.ts +256 -38
- package/src/fill-patterns.test.ts +35 -0
- package/src/fill-patterns.ts +56 -2
- package/src/grid-messages.ts +16 -3
- package/src/history.test.ts +196 -0
- package/src/history.ts +162 -0
- package/src/icon-seam.test.ts +3 -2
- package/src/index.ts +150 -2
- package/src/keyboard-handlers.coverage.test.ts +126 -6
- package/src/keyboard-handlers.ts +91 -30
- package/src/keyboard-shortcuts.seam.test.ts +200 -0
- package/src/keyboard.test.ts +124 -1
- package/src/keyboard.ts +142 -0
- package/src/menus.test.ts +26 -0
- package/src/menus.ts +10 -3
- package/src/merges.test.ts +112 -0
- package/src/merges.ts +168 -0
- package/src/pivot.grid.test.ts +40 -5
- package/src/row-resize.test.ts +44 -0
- package/src/row-resize.ts +17 -0
- package/src/scroll-sync.test.ts +42 -1
- package/src/scroll-sync.ts +25 -0
- package/src/selection.multi-range.test.ts +34 -0
- package/src/selection.test.ts +30 -2
- package/src/selection.ts +111 -19
- package/src/shortcut-registry.test.ts +97 -0
- package/src/shortcut-registry.ts +181 -0
- package/src/svgrid-wrapper.types.ts +184 -13
- package/src/svgrid.behavior.test.ts +135 -0
- package/src/svgrid.charting.test.ts +513 -3
- package/src/svgrid.context-menu.test.ts +31 -0
- package/src/svgrid.new-features.wrapper.test.ts +10 -3
- package/src/virtualization/virtualizer.ts +39 -17
- package/dist/cdn/GridMenus-DyFA-QxA.js +0 -602
- package/dist/cdn/GridMenus-M2XP9t0_.js +0 -603
- package/dist/cdn/SvDateTimePicker-CCbDZNZB.js +0 -816
- package/dist/cdn/SvGridChart-DAb9RbRs.js +0 -1480
- package/dist/cdn/SvGridChart-Yl-IXZFO.js +0 -1481
- package/dist/cdn/SvGridChartPanel-DayQpDsR.js +0 -834
- package/dist/cdn/SvGridChartPanel-aqf8n-vJ.js +0 -810
- package/dist/cdn/SvGridChartView-CxfakxR0.js +0 -56
- package/dist/cdn/SvGridChartView-Dm_YEx9L.js +0 -55
- package/dist/cdn/chart-CKJRCPOk.js +0 -1652
package/dist/editing.js
CHANGED
|
@@ -3,9 +3,12 @@
|
|
|
3
3
|
// ($state/$derived/$effect) stays in the controller.
|
|
4
4
|
import { parseEditorValue, } from "./index.js";
|
|
5
5
|
import "./sv-grid-scrollbar.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getEntryStep } from "./keyboard.js";
|
|
7
7
|
import { getCellKey, toValueArray, } from "./SvGrid.helpers.js";
|
|
8
8
|
import { createDataIndexLookup, getColumnBaseValue, isGroupRow, } from "./cell-values.js";
|
|
9
|
+
import { pushHistory, nextGroupId, runHistoryGroup } from "./history.js";
|
|
10
|
+
import { hasGridShortcuts, runGridShortcuts } from "./shortcut-registry.js";
|
|
11
|
+
import { buildCommandContext } from "./command-context.js";
|
|
9
12
|
export function createEditing(ctx) {
|
|
10
13
|
/**
|
|
11
14
|
* Resolve `columnDef.editable` for one row × column pair.
|
|
@@ -100,10 +103,27 @@ export function createEditing(ctx) {
|
|
|
100
103
|
rowRef: row.original,
|
|
101
104
|
};
|
|
102
105
|
ctx.setActiveCell(rowIndex, colIndex);
|
|
103
|
-
|
|
106
|
+
selectForEdit(rowIndex, colIndex);
|
|
104
107
|
return true;
|
|
105
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* The selection an edit starts with. A cell already inside the selection
|
|
111
|
+
* leaves it standing: typing into the active cell of a selected block
|
|
112
|
+
* used to collapse the block to that one cell, so Enter could never walk
|
|
113
|
+
* on inside it. A cell outside the selection becomes the selection.
|
|
114
|
+
*/
|
|
115
|
+
function selectForEdit(rowIndex, colIndex) {
|
|
116
|
+
if (ctx.isCellInSelectedRange(rowIndex, colIndex))
|
|
117
|
+
return;
|
|
118
|
+
ctx.setSelection(rowIndex, colIndex);
|
|
119
|
+
}
|
|
106
120
|
function saveEditingCell() {
|
|
121
|
+
// One group: a consumer's onCellValueChange may record a step of its own
|
|
122
|
+
// (a spreadsheet gives "12%" a percent format as it lands), and that
|
|
123
|
+
// belongs to the same Ctrl+Z as the value.
|
|
124
|
+
runHistoryGroup(ctx, () => commitEditingCell());
|
|
125
|
+
}
|
|
126
|
+
function commitEditingCell() {
|
|
107
127
|
if (!ctx.editingCell)
|
|
108
128
|
return;
|
|
109
129
|
const editing = ctx.editingCell;
|
|
@@ -154,20 +174,7 @@ export function createEditing(ctx) {
|
|
|
154
174
|
before: oldValue,
|
|
155
175
|
after: finalValue,
|
|
156
176
|
};
|
|
157
|
-
|
|
158
|
-
truncated.push(step);
|
|
159
|
-
// Cap the buffer at UNDO_LIMIT; drop the OLDEST entries so the
|
|
160
|
-
// pointer stays valid relative to the newest steps.
|
|
161
|
-
if (truncated.length > ctx.UNDO_LIMIT) {
|
|
162
|
-
const drop = truncated.length - ctx.UNDO_LIMIT;
|
|
163
|
-
ctx.history = truncated.slice(drop);
|
|
164
|
-
ctx.historyPtr = ctx.history.length - 1;
|
|
165
|
-
}
|
|
166
|
-
else {
|
|
167
|
-
ctx.history = truncated;
|
|
168
|
-
ctx.historyPtr = ctx.history.length - 1;
|
|
169
|
-
}
|
|
170
|
-
ctx.historyVersion += 1;
|
|
177
|
+
pushHistory(ctx, [step]);
|
|
171
178
|
}
|
|
172
179
|
// Notify the consumer AFTER the row has been updated so any callback-
|
|
173
180
|
// driven recompute (cascade totals, server save, undo stack) sees the
|
|
@@ -187,6 +194,13 @@ export function createEditing(ctx) {
|
|
|
187
194
|
/** Apply an undo / redo step directly to the underlying row, bypassing
|
|
188
195
|
* the editor pipeline so we don't accidentally re-push to the stack. */
|
|
189
196
|
function applyHistoryStep(step, direction) {
|
|
197
|
+
if (step.custom) {
|
|
198
|
+
if (direction === 'undo')
|
|
199
|
+
step.custom.undo();
|
|
200
|
+
else
|
|
201
|
+
step.custom.redo();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
190
204
|
const row = ctx.allRows.find((r) => r.id === step.rowId);
|
|
191
205
|
const col = ctx.allColumns.find((c) => c.id === step.columnId);
|
|
192
206
|
if (!row?.original || !col)
|
|
@@ -217,26 +231,115 @@ export function createEditing(ctx) {
|
|
|
217
231
|
* move, Tab committed the edit and then let the browser walk focus out of the
|
|
218
232
|
* grid entirely (#48). Shared by every editor type, including the textarea.
|
|
219
233
|
*/
|
|
220
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Commit the edit and step the cursor the way data entry expects: Tab a
|
|
236
|
+
* column to the right (wrapping at the row's end), Enter a row down, Shift
|
|
237
|
+
* reversing either. Enter used to commit and stay, so a column of numbers
|
|
238
|
+
* took a click or an arrow between every value; Excel and every other
|
|
239
|
+
* sheet move down, and the accessibility page has always said this grid
|
|
240
|
+
* does too. At the last row or column the cursor stays put.
|
|
241
|
+
*/
|
|
242
|
+
function commitAndMove(intent) {
|
|
221
243
|
const active = ctx.activeCell;
|
|
222
244
|
saveEditingCell();
|
|
223
245
|
ctx.gridRootEl?.focus({ preventScroll: true });
|
|
224
246
|
if (!active)
|
|
225
247
|
return;
|
|
226
|
-
|
|
248
|
+
// The same stepping as Enter and Tab on the grid root: a Tab run's Enter
|
|
249
|
+
// goes back to the run's first column, and a selected block keeps the
|
|
250
|
+
// cursor inside it.
|
|
251
|
+
const step = getEntryStep(active, intent, {
|
|
227
252
|
maxRow: ctx.allRows.length - 1,
|
|
228
253
|
maxCol: ctx.allColumns.length - 1,
|
|
254
|
+
tabOrigin: ctx.tabRunOrigin,
|
|
255
|
+
range: ctx.activeRangeRect(),
|
|
256
|
+
collapsed: {
|
|
257
|
+
isRowCollapsed: (i) => ctx.isRowCollapsed(i),
|
|
258
|
+
isColumnCollapsed: (i) => !!ctx.collapsedColumns[ctx.allColumns[i]?.id],
|
|
259
|
+
},
|
|
229
260
|
});
|
|
261
|
+
const next = step.cell;
|
|
230
262
|
ctx.setActiveCell(next.rowIndex, next.colIndex);
|
|
231
|
-
ctx.
|
|
263
|
+
ctx.tabRunOrigin = step.tabOrigin;
|
|
264
|
+
if (!step.withinRange)
|
|
265
|
+
ctx.setSelection(next.rowIndex, next.colIndex);
|
|
232
266
|
ctx.scrollActiveCellIntoView(next.rowIndex, next.colIndex);
|
|
233
267
|
}
|
|
268
|
+
function commitAndMoveByTab(shiftKey) {
|
|
269
|
+
commitAndMove(shiftKey ? "tabPrev" : "tabNext");
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Ctrl+Enter: the entry goes into every cell of the selected block and the
|
|
273
|
+
* cursor stays where it is, as in Excel, where it is how a block is filled
|
|
274
|
+
* with one value in one go. Each cell takes the same raw entry through the
|
|
275
|
+
* ordinary commit, so its column's parser, the history and
|
|
276
|
+
* `onCellValueChange` all see it, and the whole block is one undo. A cell
|
|
277
|
+
* that cannot be edited is left alone. With nothing but the active cell
|
|
278
|
+
* selected it is a commit that does not move.
|
|
279
|
+
*/
|
|
280
|
+
function commitToSelection() {
|
|
281
|
+
const editing = ctx.editingCell;
|
|
282
|
+
if (!editing)
|
|
283
|
+
return;
|
|
284
|
+
const raw = editing.value;
|
|
285
|
+
const active = ctx.activeCell;
|
|
286
|
+
const range = ctx.activeRangeRect();
|
|
287
|
+
runHistoryGroup(ctx, () => {
|
|
288
|
+
commitEditingCell();
|
|
289
|
+
if (!active || !range)
|
|
290
|
+
return;
|
|
291
|
+
for (let r = range.minRow; r <= range.maxRow; r += 1) {
|
|
292
|
+
for (let c = range.minCol; c <= range.maxCol; c += 1) {
|
|
293
|
+
if (r === active.rowIndex && c === active.colIndex)
|
|
294
|
+
continue;
|
|
295
|
+
const row = ctx.allRows[r];
|
|
296
|
+
const column = ctx.allColumns[c];
|
|
297
|
+
if (!row || !column || isGroupRow(row) || !isCellEditable(column, row))
|
|
298
|
+
continue;
|
|
299
|
+
ctx.editingCell = {
|
|
300
|
+
rowId: row.id,
|
|
301
|
+
columnId: column.id,
|
|
302
|
+
editorType: (column.columnDef.editorType ?? "text"),
|
|
303
|
+
value: raw,
|
|
304
|
+
rowRef: row.original,
|
|
305
|
+
};
|
|
306
|
+
commitEditingCell();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
ctx.gridRootEl?.focus({ preventScroll: true });
|
|
311
|
+
}
|
|
234
312
|
function onEditorKeyDown(event) {
|
|
235
313
|
event.stopPropagation();
|
|
236
|
-
|
|
314
|
+
// Registered commands get the key first here too, with `editing: true`, so
|
|
315
|
+
// a handler can claim a combination that only means something mid-edit
|
|
316
|
+
// (Alt+Enter for a newline, F4 to cycle a reference between relative and
|
|
317
|
+
// absolute) before the editor's own Enter / Tab / Escape handling runs.
|
|
318
|
+
if (hasGridShortcuts() && runGridShortcuts(event, buildCommandContext(ctx, true))) {
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (event.key === "Enter" && event.altKey) {
|
|
322
|
+
// A line break, in an editor that can hold one (`editorMultiline`).
|
|
323
|
+
// In a single-line editor Alt+Enter is just Enter.
|
|
324
|
+
const el = event.currentTarget;
|
|
325
|
+
if (el instanceof HTMLTextAreaElement) {
|
|
326
|
+
event.preventDefault();
|
|
327
|
+
const start = el.selectionStart ?? el.value.length;
|
|
328
|
+
const end = el.selectionEnd ?? start;
|
|
329
|
+
el.value = el.value.slice(0, start) + "\n" + el.value.slice(end);
|
|
330
|
+
el.selectionStart = el.selectionEnd = start + 1;
|
|
331
|
+
updateEditingCellValue(el.value);
|
|
332
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (event.key === "Enter" && (event.ctrlKey || event.metaKey)) {
|
|
237
337
|
event.preventDefault();
|
|
238
|
-
|
|
239
|
-
|
|
338
|
+
commitToSelection();
|
|
339
|
+
}
|
|
340
|
+
else if (event.key === "Enter") {
|
|
341
|
+
event.preventDefault();
|
|
342
|
+
commitAndMove(event.shiftKey ? "moveUp" : "moveDown");
|
|
240
343
|
}
|
|
241
344
|
else if (event.key === "Tab") {
|
|
242
345
|
event.preventDefault();
|
|
@@ -316,7 +419,7 @@ export function createEditing(ctx) {
|
|
|
316
419
|
rowRef: row.original,
|
|
317
420
|
};
|
|
318
421
|
ctx.setActiveCell(rowIndex, colIndex);
|
|
319
|
-
|
|
422
|
+
selectForEdit(rowIndex, colIndex);
|
|
320
423
|
}
|
|
321
424
|
async function pasteFromClipboard() {
|
|
322
425
|
// Preferred path: the async Clipboard API. Requires a secure context
|
|
@@ -325,6 +428,34 @@ export function createEditing(ctx) {
|
|
|
325
428
|
// On plain HTTP (a XAMPP/Apache LAN host) `navigator.clipboard` is
|
|
326
429
|
// undefined - there the Ctrl+V keydown handler skips preventDefault and
|
|
327
430
|
// lets the browser deliver a native `paste` event to `onGridPaste`.
|
|
431
|
+
if (ctx.props.onPasteClipboard && navigator.clipboard?.read) {
|
|
432
|
+
// Both types, for a handler that reads the HTML.
|
|
433
|
+
try {
|
|
434
|
+
const items = await navigator.clipboard.read();
|
|
435
|
+
let text = "";
|
|
436
|
+
let html = null;
|
|
437
|
+
for (const item of items) {
|
|
438
|
+
if (item.types.includes("text/html"))
|
|
439
|
+
html = await (await item.getType("text/html")).text();
|
|
440
|
+
if (item.types.includes("text/plain"))
|
|
441
|
+
text = await (await item.getType("text/plain")).text();
|
|
442
|
+
}
|
|
443
|
+
if (text || html)
|
|
444
|
+
applyPastedPayload({ text, html, source: "async" });
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
// No permission or nothing readable: the text-only read below may still work.
|
|
448
|
+
if (!navigator.clipboard.readText)
|
|
449
|
+
return;
|
|
450
|
+
try {
|
|
451
|
+
applyPastedPayload({ text: await navigator.clipboard.readText(), html: null, source: "async" });
|
|
452
|
+
}
|
|
453
|
+
catch {
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
328
459
|
if (!navigator.clipboard?.readText)
|
|
329
460
|
return;
|
|
330
461
|
let text;
|
|
@@ -336,6 +467,27 @@ export function createEditing(ctx) {
|
|
|
336
467
|
}
|
|
337
468
|
applyPastedText(text);
|
|
338
469
|
}
|
|
470
|
+
/**
|
|
471
|
+
* With `onPasteClipboard` set, Ctrl+V leaves the key to the browser so the
|
|
472
|
+
* `paste` event arrives with the HTML. A browser that does not deliver
|
|
473
|
+
* that event to the grid root (Firefox, a non-editable element) would
|
|
474
|
+
* paste nothing, so the async read is armed to run shortly after unless
|
|
475
|
+
* the event lands first and disarms it.
|
|
476
|
+
*/
|
|
477
|
+
let pasteFallback = null;
|
|
478
|
+
function armPasteFallback() {
|
|
479
|
+
if (pasteFallback)
|
|
480
|
+
clearTimeout(pasteFallback);
|
|
481
|
+
pasteFallback = setTimeout(() => {
|
|
482
|
+
pasteFallback = null;
|
|
483
|
+
void pasteFromClipboard();
|
|
484
|
+
}, 80);
|
|
485
|
+
}
|
|
486
|
+
function disarmPasteFallback() {
|
|
487
|
+
if (pasteFallback)
|
|
488
|
+
clearTimeout(pasteFallback);
|
|
489
|
+
pasteFallback = null;
|
|
490
|
+
}
|
|
339
491
|
/**
|
|
340
492
|
* Native `paste` ClipboardEvent fallback (insecure-context path). A real
|
|
341
493
|
* Ctrl/Cmd+V on the focused grid root fires this with `clipboardData`
|
|
@@ -351,6 +503,18 @@ export function createEditing(ctx) {
|
|
|
351
503
|
// so the edit "wouldn't update".
|
|
352
504
|
if (ctx.editingCell)
|
|
353
505
|
return;
|
|
506
|
+
if (ctx.props.onPasteClipboard) {
|
|
507
|
+
// The event is the preferred path for a handler: it carries the HTML
|
|
508
|
+
// as the source wrote it, needs no permission, and works over HTTP.
|
|
509
|
+
disarmPasteFallback();
|
|
510
|
+
const text = event.clipboardData?.getData("text/plain") ?? "";
|
|
511
|
+
const html = event.clipboardData?.getData("text/html") || null;
|
|
512
|
+
if (!text && !html)
|
|
513
|
+
return;
|
|
514
|
+
event.preventDefault();
|
|
515
|
+
applyPastedPayload({ text, html, source: "event" });
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
354
518
|
// When the async API is available the keydown handler already pasted via
|
|
355
519
|
// pasteFromClipboard(); ignore the (suppressed) native event so we don't
|
|
356
520
|
// paste twice.
|
|
@@ -363,6 +527,22 @@ export function createEditing(ctx) {
|
|
|
363
527
|
applyPastedText(text);
|
|
364
528
|
}
|
|
365
529
|
function applyPastedText(text) {
|
|
530
|
+
// One history group for the whole paste, so whatever a consumer's
|
|
531
|
+
// processCellFromClipboard records alongside the values (a format, via
|
|
532
|
+
// recordUndo) comes back with them on a single Ctrl+Z.
|
|
533
|
+
runHistoryGroup(ctx, () => applyPastedCells(text));
|
|
534
|
+
}
|
|
535
|
+
/** A paste with both types: the handler first, the plain text otherwise. */
|
|
536
|
+
function applyPastedPayload(payload) {
|
|
537
|
+
runHistoryGroup(ctx, () => {
|
|
538
|
+
const handled = ctx.props.onPasteClipboard?.(payload);
|
|
539
|
+
if (handled === true)
|
|
540
|
+
return;
|
|
541
|
+
if (payload.text)
|
|
542
|
+
applyPastedCells(payload.text);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
function applyPastedCells(text) {
|
|
366
546
|
const anchor = ctx.selectionRange.anchor ?? ctx.grid.getState().activeCell;
|
|
367
547
|
if (!anchor)
|
|
368
548
|
return;
|
|
@@ -390,6 +570,12 @@ export function createEditing(ctx) {
|
|
|
390
570
|
: Math.max(...lines.map((l) => l.split("\t").length));
|
|
391
571
|
const next = ctx.internalData.slice();
|
|
392
572
|
const dataIndexOf = createDataIndexLookup(next);
|
|
573
|
+
// Every cell the paste changes: one grouped history entry for the lot,
|
|
574
|
+
// so Ctrl+Z takes the whole paste back, and one onCellValueChange each,
|
|
575
|
+
// so a consumer keeping its own model behind the grid (a formula engine)
|
|
576
|
+
// learns the values. The loop used to swap the rows in and stop, which
|
|
577
|
+
// left the undo stack blind to a paste and the consumer never told.
|
|
578
|
+
const changed = [];
|
|
393
579
|
for (let i = 0; i < rowSpan; i += 1) {
|
|
394
580
|
const targetRowIndex = startRow + i;
|
|
395
581
|
const row = ctx.allRows[targetRowIndex];
|
|
@@ -409,23 +595,69 @@ export function createEditing(ctx) {
|
|
|
409
595
|
const updated = {
|
|
410
596
|
...originalRow,
|
|
411
597
|
};
|
|
598
|
+
const rowChanges = [];
|
|
599
|
+
const record = (column, before, after) => {
|
|
600
|
+
if (before === after)
|
|
601
|
+
return;
|
|
602
|
+
rowChanges.push({
|
|
603
|
+
row: updated, rowId: row.id, rowIndex: dataIndex, columnId: column.id,
|
|
604
|
+
field: column.columnDef.field, before, after,
|
|
605
|
+
});
|
|
606
|
+
};
|
|
412
607
|
for (let j = 0; j < colSpan; j += 1) {
|
|
413
608
|
const column = ctx.allColumns[startCol + j];
|
|
414
609
|
if (!column?.columnDef.field)
|
|
415
610
|
continue;
|
|
416
611
|
if (!isCellEditableAt(targetRowIndex, startCol + j))
|
|
417
612
|
continue;
|
|
613
|
+
const before = updated[column.columnDef.field];
|
|
418
614
|
const editorType = (column.columnDef.editorType ??
|
|
419
615
|
"text");
|
|
420
616
|
const raw = fillRange ? lines[0] : sourceCells?.[j] ?? "";
|
|
421
|
-
|
|
617
|
+
const parsedValue = parseEditorValue(editorType, raw, {
|
|
422
618
|
dateOnly: column.columnDef.cellDataType === "dateString",
|
|
423
619
|
});
|
|
620
|
+
// The inbound half of the clipboard pair. A consumer (or a feature
|
|
621
|
+
// pack doing paste-special) gets the raw text AND what the grid would
|
|
622
|
+
// have written, and can return either. `undefined` leaves the cell
|
|
623
|
+
// alone, which is how "paste values only" skips a formula column.
|
|
624
|
+
const hook = ctx.props.processCellFromClipboard;
|
|
625
|
+
if (hook) {
|
|
626
|
+
const decided = hook({
|
|
627
|
+
text: raw,
|
|
628
|
+
parsedValue,
|
|
629
|
+
row: originalRow,
|
|
630
|
+
rowIndex: targetRowIndex,
|
|
631
|
+
columnId: column.id,
|
|
632
|
+
});
|
|
633
|
+
if (decided === undefined)
|
|
634
|
+
continue;
|
|
635
|
+
updated[column.columnDef.field] = decided;
|
|
636
|
+
record(column, before, decided);
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
updated[column.columnDef.field] = parsedValue;
|
|
640
|
+
record(column, before, parsedValue);
|
|
424
641
|
}
|
|
642
|
+
if (!rowChanges.length)
|
|
643
|
+
continue;
|
|
425
644
|
next[dataIndex] = updated;
|
|
645
|
+
changed.push(...rowChanges);
|
|
426
646
|
}
|
|
647
|
+
if (!changed.length)
|
|
648
|
+
return;
|
|
427
649
|
ctx.internalData = next;
|
|
428
650
|
ctx.grid.store.setState((prev) => ({ ...prev }));
|
|
651
|
+
pushHistory(ctx, changed.map((c) => ({
|
|
652
|
+
rowId: c.rowId, columnId: c.columnId, field: c.field, before: c.before, after: c.after,
|
|
653
|
+
})), nextGroupId());
|
|
654
|
+
if (ctx.props.onCellValueChange) {
|
|
655
|
+
for (const c of changed) {
|
|
656
|
+
ctx.props.onCellValueChange({
|
|
657
|
+
rowIndex: c.rowIndex, columnId: c.columnId, oldValue: c.before, newValue: c.after, row: c.row,
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
429
661
|
}
|
|
430
662
|
/** Programmatically begin editing a cell (mirrors a double-click). */
|
|
431
663
|
function startEditing(rowIndex, columnId) {
|
|
@@ -531,15 +763,11 @@ export function createEditing(ctx) {
|
|
|
531
763
|
}
|
|
532
764
|
// Record the whole-row change as consecutive history steps.
|
|
533
765
|
if (changed.length) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
hist = hist.slice(hist.length - ctx.UNDO_LIMIT);
|
|
540
|
-
ctx.history = hist;
|
|
541
|
-
ctx.historyPtr = ctx.history.length - 1;
|
|
542
|
-
ctx.historyVersion += 1;
|
|
766
|
+
// One full-row commit is ONE undo, not one per changed column.
|
|
767
|
+
pushHistory(ctx, changed.map((c) => ({
|
|
768
|
+
rowId: fr.rowId, columnId: c.columnId, field: c.field,
|
|
769
|
+
before: c.before, after: c.after,
|
|
770
|
+
})), nextGroupId());
|
|
543
771
|
}
|
|
544
772
|
ctx.grid.store.setState((prev) => ({ ...prev }));
|
|
545
773
|
if (ctx.props.onCellValueChange && changed.length) {
|
|
@@ -581,5 +809,6 @@ export function createEditing(ctx) {
|
|
|
581
809
|
onCellDoubleClick,
|
|
582
810
|
pasteFromClipboard,
|
|
583
811
|
onGridPaste,
|
|
812
|
+
armPasteFallback,
|
|
584
813
|
};
|
|
585
814
|
}
|
package/dist/fill-patterns.d.ts
CHANGED
|
@@ -22,9 +22,4 @@
|
|
|
22
22
|
* A single source value falls through to the cycle case (repeats), which
|
|
23
23
|
* matches Excel's default fill behavior.
|
|
24
24
|
*/
|
|
25
|
-
/**
|
|
26
|
-
* Compute the values to write into `targetCount` new cells given the
|
|
27
|
-
* `sourceValues` already in the selection. See module header for the
|
|
28
|
-
* pattern-detection rules.
|
|
29
|
-
*/
|
|
30
25
|
export declare function buildFillPattern(sourceValues: ReadonlyArray<unknown>, targetCount: number): unknown[];
|
package/dist/fill-patterns.js
CHANGED
|
@@ -149,10 +149,48 @@ function arithmeticStep(nums) {
|
|
|
149
149
|
* `sourceValues` already in the selection. See module header for the
|
|
150
150
|
* pattern-detection rules.
|
|
151
151
|
*/
|
|
152
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
153
|
+
/**
|
|
154
|
+
* The sources as day stamps when every one is a date: `Date` objects, or
|
|
155
|
+
* `YYYY-MM-DD` text (read as UTC so a fill never crosses a day boundary
|
|
156
|
+
* with the time zone). Anything else means "not a date series".
|
|
157
|
+
*/
|
|
158
|
+
function datesOf(values) {
|
|
159
|
+
if (values.every((v) => v instanceof Date && Number.isFinite(v.getTime()))) {
|
|
160
|
+
return { stamps: values.map((v) => v.getTime()), iso: false };
|
|
161
|
+
}
|
|
162
|
+
if (values.every((v) => typeof v === 'string' && ISO_DATE.test(v))) {
|
|
163
|
+
const stamps = values.map((v) => Date.parse(`${v}T00:00:00Z`));
|
|
164
|
+
return stamps.every((t) => Number.isFinite(t)) ? { stamps, iso: true } : null;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
152
168
|
export function buildFillPattern(sourceValues, targetCount) {
|
|
153
169
|
if (sourceValues.length === 0 || targetCount <= 0)
|
|
154
170
|
return [];
|
|
155
|
-
// --------
|
|
171
|
+
// -------- 0) Dates step by the day ---------------------------------
|
|
172
|
+
// Before the numeric rule, since a Date coerces to a finite number of
|
|
173
|
+
// milliseconds, and before the prefix rule, which read 2024-01-15 as the
|
|
174
|
+
// number 2024 with a suffix and filled 2025-01-15. Two dates set the step
|
|
175
|
+
// (a week apart fills weekly); one date steps by a day, as Excel's does.
|
|
176
|
+
const dates = datesOf(sourceValues);
|
|
177
|
+
if (dates) {
|
|
178
|
+
const DAY = 86400000;
|
|
179
|
+
const days = dates.stamps.map((t) => Math.round(t / DAY));
|
|
180
|
+
const step = days.length >= 2 ? arithmeticStep(days) : 1;
|
|
181
|
+
if (step !== null) {
|
|
182
|
+
const last = days[days.length - 1];
|
|
183
|
+
return Array.from({ length: targetCount }, (_, i) => {
|
|
184
|
+
const stamp = (last + step * (i + 1)) * DAY;
|
|
185
|
+
return dates.iso ? new Date(stamp).toISOString().slice(0, 10) : new Date(stamp);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// -------- 1) Numbers: the linear trend (2+ values) -----------------
|
|
190
|
+
// Excel's AutoFill of two or more numbers continues their least-squares
|
|
191
|
+
// line: 1, 2, 3 goes on 4, 5, 6, and 1, 2, 4 goes on 5.33, 6.83, ...
|
|
192
|
+
// rather than repeating. An even step is the same line, so the common
|
|
193
|
+
// case is unchanged.
|
|
156
194
|
const nums = sourceValues.map((v) => Number(v));
|
|
157
195
|
const allNumericLike = sourceValues.every((v) => v !== null && v !== '' && v !== undefined) &&
|
|
158
196
|
nums.every((n) => Number.isFinite(n));
|
|
@@ -162,6 +200,20 @@ export function buildFillPattern(sourceValues, targetCount) {
|
|
|
162
200
|
const last = nums[nums.length - 1];
|
|
163
201
|
return Array.from({ length: targetCount }, (_, i) => last + step * (i + 1));
|
|
164
202
|
}
|
|
203
|
+
const n = nums.length;
|
|
204
|
+
const meanX = (n - 1) / 2;
|
|
205
|
+
const meanY = nums.reduce((a, b) => a + b, 0) / n;
|
|
206
|
+
let sxy = 0;
|
|
207
|
+
let sxx = 0;
|
|
208
|
+
for (let i = 0; i < n; i += 1) {
|
|
209
|
+
sxy += (i - meanX) * (nums[i] - meanY);
|
|
210
|
+
sxx += (i - meanX) ** 2;
|
|
211
|
+
}
|
|
212
|
+
const slope = sxx === 0 ? 0 : sxy / sxx;
|
|
213
|
+
const intercept = meanY - slope * meanX;
|
|
214
|
+
// Rounded to the precision floating point gives 5.333333333333333, so
|
|
215
|
+
// 10, 20, 40 fills 50, 60 and not 50.00000000000001.
|
|
216
|
+
return Array.from({ length: targetCount }, (_, i) => Number((intercept + slope * (n + i)).toPrecision(15)));
|
|
165
217
|
}
|
|
166
218
|
// -------- 2) Known string sequence (days / months / quarters) -----
|
|
167
219
|
const allStrings = sourceValues.every((v) => typeof v === 'string');
|
|
@@ -179,7 +231,9 @@ export function buildFillPattern(sourceValues, targetCount) {
|
|
|
179
231
|
if (allStrings && sourceValues.length >= 1) {
|
|
180
232
|
const stringSources = sourceValues;
|
|
181
233
|
const split = findPrefixNumberSuffix(stringSources);
|
|
182
|
-
|
|
234
|
+
// A bare number as text ('5') is a number, not 'Item 5': one of them
|
|
235
|
+
// copies, as a numeric 5 does, and two or more were a progression above.
|
|
236
|
+
if (split && (split.prefix !== '' || split.suffix !== '')) {
|
|
183
237
|
const step = split.numbers.length >= 2
|
|
184
238
|
? arithmeticStep(split.numbers) ?? 1
|
|
185
239
|
: 1;
|
package/dist/grid-messages.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import type { GridChartPanelMessages } from './chart-panel-messages.js';
|
|
2
|
+
/**
|
|
3
|
+
* Every string `localization.text` accepts: the grid chrome's own
|
|
4
|
+
* ({@link GridChromeMessages}) and the chart panel's
|
|
5
|
+
* ({@link GridChartPanelMessages}).
|
|
6
|
+
*/
|
|
7
|
+
export type GridMessages = GridChromeMessages & GridChartPanelMessages;
|
|
1
8
|
/** Every localizable chrome string, grouped by area in the comments. */
|
|
2
|
-
export type
|
|
9
|
+
export type GridChromeMessages = {
|
|
3
10
|
noRows: string;
|
|
4
11
|
loading: string;
|
|
5
12
|
columns: string;
|
|
@@ -74,10 +81,10 @@ export type GridMessages = {
|
|
|
74
81
|
upsellLicenseLink: string;
|
|
75
82
|
};
|
|
76
83
|
/** English defaults - the literal strings the grid shipped before localization. */
|
|
77
|
-
export declare const defaultGridMessages:
|
|
84
|
+
export declare const defaultGridMessages: GridChromeMessages;
|
|
78
85
|
/**
|
|
79
86
|
* Merge a consumer's `localeText` over the English defaults. Undefined/empty
|
|
80
87
|
* values fall back to the default (via `resolveMessages`), so a partial map only
|
|
81
88
|
* replaces the keys it sets.
|
|
82
89
|
*/
|
|
83
|
-
export declare function resolveGridMessages(overrides?: Partial<GridMessages> | null):
|
|
90
|
+
export declare function resolveGridMessages(overrides?: Partial<GridMessages> | null): GridChromeMessages;
|
package/dist/grid-messages.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
*
|
|
8
8
|
* This mirrors the editor kit's `resolveMessages` pattern (editor-contract.ts) so
|
|
9
9
|
* chrome and editors share one localization idiom.
|
|
10
|
+
*
|
|
11
|
+
* The chart panel's strings are the other half of `GridMessages`
|
|
12
|
+
* (`GridChartPanelMessages` in chart-panel-messages.ts): same map, same
|
|
13
|
+
* `localization.text`, but their English defaults load with the panel, not
|
|
14
|
+
* with every grid.
|
|
10
15
|
*/
|
|
11
16
|
import { resolveMessages } from './editor-contract.js';
|
|
12
17
|
/** English defaults - the literal strings the grid shipped before localization. */
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Undo / redo over the grid's history array, group-aware.
|
|
3
|
+
*
|
|
4
|
+
* The history is one ordered array of per-cell steps plus a pointer to the
|
|
5
|
+
* last applied step (the model lives in SvGrid.controller.svelte.ts). A step
|
|
6
|
+
* describes ONE cell, which is right for a single edit and wrong for anything
|
|
7
|
+
* that writes many cells at once: `applyMoveRange` in clipboard.ts pushes one
|
|
8
|
+
* step per moved cell, so undoing a 15x20 range move takes 300 presses of
|
|
9
|
+
* Ctrl+Z, and with UNDO_LIMIT at 200 the same move evicts the entire history
|
|
10
|
+
* behind it.
|
|
11
|
+
*
|
|
12
|
+
* `groupId` fixes that without changing the storage model. Steps written
|
|
13
|
+
* inside one logical action share an id; undo and redo walk the whole run of
|
|
14
|
+
* steps carrying that id and apply them together, so a paste, a fill-down or
|
|
15
|
+
* a range move is one press, the way it is in a spreadsheet. Steps with no
|
|
16
|
+
* groupId behave exactly as before, one step per press.
|
|
17
|
+
*
|
|
18
|
+
* Undo applies a group in REVERSE order. Two steps that touched the same cell
|
|
19
|
+
* inside one action have to be unwound newest-first or the older `before`
|
|
20
|
+
* value is overwritten by the newer one and the cell lands on the wrong value.
|
|
21
|
+
*/
|
|
22
|
+
export type HistoryStep = {
|
|
23
|
+
rowId: string;
|
|
24
|
+
columnId: string;
|
|
25
|
+
field: string;
|
|
26
|
+
before: unknown;
|
|
27
|
+
after: unknown;
|
|
28
|
+
/** Set when this step is part of a multi-cell action. Steps sharing an id
|
|
29
|
+
* are contiguous in the array and undo / redo as one. */
|
|
30
|
+
groupId?: string;
|
|
31
|
+
/**
|
|
32
|
+
* A step that is not a cell write: something outside the grid's data that
|
|
33
|
+
* still belongs in the same Ctrl+Z stream, such as a cell format kept in a
|
|
34
|
+
* consumer's store. The cell fields are empty on such a step and the two
|
|
35
|
+
* functions do the work. Never serialised, since a closure cannot be.
|
|
36
|
+
*/
|
|
37
|
+
custom?: {
|
|
38
|
+
undo: () => void;
|
|
39
|
+
redo: () => void;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/** Ctx surface these helpers touch. The controller satisfies it structurally. */
|
|
43
|
+
type HistoryCtx = {
|
|
44
|
+
history: HistoryStep[];
|
|
45
|
+
historyPtr: number;
|
|
46
|
+
historyVersion: number;
|
|
47
|
+
UNDO_LIMIT: number;
|
|
48
|
+
/** Set while a `runHistoryGroup` call is on the stack. Every step pushed
|
|
49
|
+
* inside it joins that group, so a command does not have to thread a group
|
|
50
|
+
* id down through the writers it calls. */
|
|
51
|
+
historyGroupId: string | undefined;
|
|
52
|
+
applyHistoryStep(step: HistoryStep, direction: 'undo' | 'redo'): void;
|
|
53
|
+
};
|
|
54
|
+
/** Undo one action. Returns false when there is nothing left to undo. */
|
|
55
|
+
export declare function undoHistory(ctx: HistoryCtx): boolean;
|
|
56
|
+
/** Redo one action. Returns false when there is nothing left to redo. */
|
|
57
|
+
export declare function redoHistory(ctx: HistoryCtx): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Append steps at the pointer, truncating any forward history (the standard
|
|
60
|
+
* "an edit invalidates redo" rule) and capping the buffer at UNDO_LIMIT by
|
|
61
|
+
* dropping the OLDEST entries.
|
|
62
|
+
*
|
|
63
|
+
* Every writer goes through here rather than open-coding the slice / push /
|
|
64
|
+
* cap dance, which editing.ts and clipboard.ts previously did three times
|
|
65
|
+
* with three slightly different shapes.
|
|
66
|
+
*/
|
|
67
|
+
export declare function pushHistory(ctx: HistoryCtx, steps: ReadonlyArray<HistoryStep>, groupId?: string): void;
|
|
68
|
+
/** A fresh group id. Monotonic, not random: ids never leave the session and a
|
|
69
|
+
* counter is comparable in a test snapshot. */
|
|
70
|
+
export declare function nextGroupId(): string;
|
|
71
|
+
/**
|
|
72
|
+
* Run `fn` with every history step it writes tagged as one undoable action.
|
|
73
|
+
*
|
|
74
|
+
* This is ambient rather than a wrapper that re-tags afterwards, because
|
|
75
|
+
* `pushHistory` can drop the oldest entries when the buffer hits UNDO_LIMIT.
|
|
76
|
+
* A re-tagging implementation would compute its slice bounds from a length
|
|
77
|
+
* that had shifted underneath it and stamp the wrong steps.
|
|
78
|
+
*
|
|
79
|
+
* Nested calls join the outer group: one user action is one undo, however many
|
|
80
|
+
* layers of helper it goes through.
|
|
81
|
+
*/
|
|
82
|
+
export declare function runHistoryGroup<T>(ctx: HistoryCtx, fn: () => T): T;
|
|
83
|
+
export {};
|