@ricsam/formula-engine 0.2.14 → 0.2.15

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.
@@ -60,6 +60,7 @@ var import_style_manager = require("./managers/style-manager.cjs");
60
60
  var import_copy_manager = require("./managers/copy-manager.cjs");
61
61
  var import_reference_manager = require("./managers/reference-manager.cjs");
62
62
  var import_range_metadata_manager = require("./managers/range-metadata-manager.cjs");
63
+ var import_undo_redo_manager = require("./managers/undo-redo-manager.cjs");
63
64
  var import_engine_snapshot = require("./engine-snapshot.cjs");
64
65
  var import_mutation_invalidation = require("./mutation-invalidation.cjs");
65
66
  var import_resource_keys = require("./resource-keys.cjs");
@@ -76,6 +77,8 @@ class FormulaEngine {
76
77
  rangeMetadataManager;
77
78
  copyManager;
78
79
  referenceManager;
80
+ undoRedoManager;
81
+ historyCheckpointDepth = 0;
79
82
  _workbookManager;
80
83
  _namedExpressionManager;
81
84
  _tableManager;
@@ -85,7 +88,8 @@ class FormulaEngine {
85
88
  _dependencyManager;
86
89
  _styleManager;
87
90
  _rangeMetadataManager;
88
- constructor() {
91
+ constructor(options = {}) {
92
+ this.undoRedoManager = new import_undo_redo_manager.UndoRedoManager(options.undoRedo);
89
93
  this.eventManager = new import_event_manager.EventManager;
90
94
  this.workbookManager = new import_workbook_manager.WorkbookManager;
91
95
  this.namedExpressionManager = new import_named_expression_manager.NamedExpressionManager;
@@ -109,8 +113,51 @@ class FormulaEngine {
109
113
  this._styleManager = this.styleManager;
110
114
  this._rangeMetadataManager = this.rangeMetadataManager;
111
115
  }
112
- static buildEmpty() {
113
- return new FormulaEngine;
116
+ static buildEmpty(options) {
117
+ return new FormulaEngine(options);
118
+ }
119
+ undo() {
120
+ if (!this.undoRedoManager.canUndo()) {
121
+ return false;
122
+ }
123
+ const target = this.undoRedoManager.popUndo();
124
+ if (!target) {
125
+ return false;
126
+ }
127
+ const current = this.serializeHistorySnapshot();
128
+ this.restoreHistorySnapshot(target);
129
+ this.undoRedoManager.pushRedo(current);
130
+ this.emitUpdate();
131
+ return true;
132
+ }
133
+ redo() {
134
+ if (!this.undoRedoManager.canRedo()) {
135
+ return false;
136
+ }
137
+ const target = this.undoRedoManager.popRedo();
138
+ if (!target) {
139
+ return false;
140
+ }
141
+ const current = this.serializeHistorySnapshot();
142
+ this.restoreHistorySnapshot(target);
143
+ this.undoRedoManager.pushUndo(current);
144
+ this.emitUpdate();
145
+ return true;
146
+ }
147
+ canUndo() {
148
+ return this.undoRedoManager.canUndo();
149
+ }
150
+ canRedo() {
151
+ return this.undoRedoManager.canRedo();
152
+ }
153
+ getUndoRedoState() {
154
+ return this.undoRedoManager.getState();
155
+ }
156
+ clearUndoRedoHistory() {
157
+ this.undoRedoManager.clear();
158
+ }
159
+ transact(callback) {
160
+ return this.withUndoRedoCheckpoint(callback);
114
161
  }
115
162
  emitMutation(footprint) {
116
163
  this.evaluationManager.invalidateFromMutation(footprint);
@@ -119,6 +166,21 @@ class FormulaEngine {
119
166
  emitUpdate() {
120
167
  this.eventManager.emitUpdate();
121
168
  }
169
+ withUndoRedoCheckpoint(callback) {
170
+ if (this.historyCheckpointDepth > 0) {
171
+ return callback();
172
+ }
173
+ const before = this.serializeHistorySnapshot();
174
+ this.historyCheckpointDepth++;
175
+ try {
176
+ const result = callback();
177
+ const after = this.serializeHistorySnapshot();
178
+ this.undoRedoManager.recordMutation(before, after);
179
+ return result;
180
+ } finally {
181
+ this.historyCheckpointDepth--;
182
+ }
183
+ }
122
184
  getExistingSheetContent(opts) {
123
185
  const sheet = this.workbookManager.getSheet(opts);
124
186
  return sheet ? new Map(sheet.content) : undefined;
@@ -195,8 +257,10 @@ class FormulaEngine {
195
257
  return this.evaluationManager.evaluationResultToSerializedValue(result, cellAddress, debug);
196
258
  }
197
259
  setCellMetadata(address, metadata) {
198
- this.workbookManager.setCellMetadata(address, metadata);
199
- this.emitUpdate();
260
+ return this.withUndoRedoCheckpoint(() => {
261
+ this.workbookManager.setCellMetadata(address, metadata);
262
+ this.emitUpdate();
263
+ });
200
264
  }
201
265
  getCellMetadata(address) {
202
266
  const metadata = this.workbookManager.getCellMetadata(address);
@@ -206,29 +270,37 @@ class FormulaEngine {
206
270
  return this.workbookManager.getSheetMetadataSerialized(opts);
207
271
  }
208
272
  setSheetMetadata(opts, metadata) {
209
- this.workbookManager.setSheetMetadata(opts, metadata);
210
- this.emitUpdate();
273
+ return this.withUndoRedoCheckpoint(() => {
274
+ this.workbookManager.setSheetMetadata(opts, metadata);
275
+ this.emitUpdate();
276
+ });
211
277
  }
212
278
  getSheetMetadata(opts) {
213
279
  return this.workbookManager.getSheetMetadata(opts);
214
280
  }
215
281
  setWorkbookMetadata(workbookName, metadata) {
216
- this.workbookManager.setWorkbookMetadata(workbookName, metadata);
217
- this.emitUpdate();
282
+ return this.withUndoRedoCheckpoint(() => {
283
+ this.workbookManager.setWorkbookMetadata(workbookName, metadata);
284
+ this.emitUpdate();
285
+ });
218
286
  }
219
287
  getWorkbookMetadata(workbookName) {
220
288
  return this.workbookManager.getWorkbookMetadata(workbookName);
221
289
  }
222
290
  addRangeMetadata(metadata) {
223
- const id = this.rangeMetadataManager.addRangeMetadata(metadata);
224
- this.emitUpdate();
225
- return id;
291
+ return this.withUndoRedoCheckpoint(() => {
292
+ const id = this.rangeMetadataManager.addRangeMetadata(metadata);
293
+ this.emitUpdate();
294
+ return id;
295
+ });
226
296
  }
227
297
  removeRangeMetadata(id) {
228
- const removed = this.rangeMetadataManager.removeRangeMetadata(id);
229
- if (removed) {
230
- this.emitUpdate();
231
- }
298
+ return this.withUndoRedoCheckpoint(() => {
299
+ const removed = this.rangeMetadataManager.removeRangeMetadata(id);
300
+ if (removed) {
301
+ this.emitUpdate();
302
+ }
303
+ });
232
304
  }
233
305
  getAllRangeMetadata() {
234
306
  return this.rangeMetadataManager.getAllRangeMetadata();
@@ -240,17 +312,29 @@ class FormulaEngine {
240
312
  return this.rangeMetadataManager.getRangeMetadataIntersectingWithRange(range);
241
313
  }
242
314
  clearRangeMetadata(range) {
243
- this.rangeMetadataManager.clearRangeMetadataInRange(range);
244
- this.emitUpdate();
315
+ return this.withUndoRedoCheckpoint(() => {
316
+ this.rangeMetadataManager.clearRangeMetadataInRange(range);
317
+ this.emitUpdate();
318
+ });
245
319
  }
246
320
  createRef(address) {
247
- return this.referenceManager.createRef(address);
321
+ return this.withUndoRedoCheckpoint(() => {
322
+ const id = this.referenceManager.createRef(address);
323
+ this.emitUpdate();
324
+ return id;
325
+ });
248
326
  }
249
327
  getRefAddress(refId) {
250
328
  return this.referenceManager.getRefAddress(refId);
251
329
  }
252
330
  deleteRef(refId) {
253
- return this.referenceManager.deleteRef(refId);
331
+ return this.withUndoRedoCheckpoint(() => {
332
+ const deleted = this.referenceManager.deleteRef(refId);
333
+ if (deleted) {
334
+ this.emitUpdate();
335
+ }
336
+ return deleted;
337
+ });
254
338
  }
255
339
  getInvalidRefs() {
256
340
  return this.referenceManager.getInvalidRefs();
@@ -265,21 +349,25 @@ class FormulaEngine {
265
349
  throw new Error("Not implemented");
266
350
  }
267
351
  addNamedExpression(opts) {
268
- this.assertNamedExpressionScopeExists(opts);
269
- this.namedExpressionManager.addNamedExpression(opts);
270
- this.emitMutation({
271
- touchedCells: [],
272
- resourceKeys: [import_resource_keys.getNamedExpressionResourceKey(opts)]
273
- });
274
- }
275
- removeNamedExpression(opts) {
276
- const removed = this.namedExpressionManager.removeNamedExpression(opts);
277
- if (removed) {
352
+ return this.withUndoRedoCheckpoint(() => {
353
+ this.assertNamedExpressionScopeExists(opts);
354
+ this.namedExpressionManager.addNamedExpression(opts);
278
355
  this.emitMutation({
279
356
  touchedCells: [],
280
357
  resourceKeys: [import_resource_keys.getNamedExpressionResourceKey(opts)]
281
358
  });
282
- }
359
+ });
360
+ }
361
+ removeNamedExpression(opts) {
362
+ return this.withUndoRedoCheckpoint(() => {
363
+ const removed = this.namedExpressionManager.removeNamedExpression(opts);
364
+ if (removed) {
365
+ this.emitMutation({
366
+ touchedCells: [],
367
+ resourceKeys: [import_resource_keys.getNamedExpressionResourceKey(opts)]
368
+ });
369
+ }
370
+ });
283
371
  }
284
372
  hasNamedExpression(opts) {
285
373
  const scope = opts.sheetName && opts.workbookName ? {
@@ -293,140 +381,130 @@ class FormulaEngine {
293
381
  });
294
382
  }
295
383
  updateNamedExpression(opts) {
296
- this.namedExpressionManager.updateNamedExpression(opts);
297
- this.emitMutation({
298
- touchedCells: [],
299
- resourceKeys: [import_resource_keys.getNamedExpressionResourceKey(opts)]
384
+ return this.withUndoRedoCheckpoint(() => {
385
+ this.namedExpressionManager.updateNamedExpression(opts);
386
+ this.emitMutation({
387
+ touchedCells: [],
388
+ resourceKeys: [import_resource_keys.getNamedExpressionResourceKey(opts)]
389
+ });
300
390
  });
301
391
  }
302
392
  renameNamedExpression(opts) {
303
- this.namedExpressionManager.renameNamedExpression(opts);
304
- const changedCells = this.workbookManager.updateAllFormulas((formula) => import_named_expression_renamer.renameNamedExpressionInFormula(formula, opts.expressionName, opts.newName));
305
- const changedNamedExpressions = this.namedExpressionManager.updateAllNamedExpressions((formula) => import_named_expression_renamer.renameNamedExpressionInFormula(formula, opts.expressionName, opts.newName));
306
- this.emitMutation({
307
- touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
308
- resourceKeys: [
309
- import_resource_keys.getNamedExpressionResourceKey({
310
- expressionName: opts.expressionName,
311
- workbookName: opts.workbookName,
312
- sheetName: opts.sheetName
313
- }),
314
- import_resource_keys.getNamedExpressionResourceKey({
315
- expressionName: opts.newName,
316
- workbookName: opts.workbookName,
317
- sheetName: opts.sheetName
318
- }),
319
- ...changedNamedExpressions
320
- ]
393
+ return this.withUndoRedoCheckpoint(() => {
394
+ this.namedExpressionManager.renameNamedExpression(opts);
395
+ const changedCells = this.workbookManager.updateAllFormulas((formula) => import_named_expression_renamer.renameNamedExpressionInFormula(formula, opts.expressionName, opts.newName));
396
+ const changedNamedExpressions = this.namedExpressionManager.updateAllNamedExpressions((formula) => import_named_expression_renamer.renameNamedExpressionInFormula(formula, opts.expressionName, opts.newName));
397
+ this.emitMutation({
398
+ touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
399
+ resourceKeys: [
400
+ import_resource_keys.getNamedExpressionResourceKey({
401
+ expressionName: opts.expressionName,
402
+ workbookName: opts.workbookName,
403
+ sheetName: opts.sheetName
404
+ }),
405
+ import_resource_keys.getNamedExpressionResourceKey({
406
+ expressionName: opts.newName,
407
+ workbookName: opts.workbookName,
408
+ sheetName: opts.sheetName
409
+ }),
410
+ ...changedNamedExpressions
411
+ ]
412
+ });
321
413
  });
322
414
  }
323
415
  setNamedExpressions(opts) {
324
- const allExpressions = this.namedExpressionManager.getNamedExpressions();
325
- let previousExpressions;
326
- if (opts.type === "global") {
327
- previousExpressions = new Map(allExpressions.globalExpressions);
328
- } else if (opts.type === "workbook") {
329
- previousExpressions = new Map(allExpressions.workbookExpressions.get(opts.workbookName) || []);
330
- } else {
331
- const sheetExpressions = allExpressions.sheetExpressions.get(opts.workbookName)?.get(opts.sheetName);
332
- previousExpressions = new Map(sheetExpressions || []);
333
- }
334
- this.namedExpressionManager.setNamedExpressions(opts);
335
- const scope = opts.type === "global" ? {} : opts.type === "workbook" ? { workbookName: opts.workbookName } : {
336
- workbookName: opts.workbookName,
337
- sheetName: opts.sheetName
338
- };
339
- this.emitMutation({
340
- touchedCells: [],
341
- resourceKeys: [
342
- ...import_mutation_invalidation.getNamedExpressionScopeResourceKeys(previousExpressions.keys(), scope),
343
- ...import_mutation_invalidation.getNamedExpressionScopeResourceKeys(opts.expressions.keys(), scope)
344
- ]
416
+ return this.withUndoRedoCheckpoint(() => {
417
+ const allExpressions = this.namedExpressionManager.getNamedExpressions();
418
+ let previousExpressions;
419
+ if (opts.type === "global") {
420
+ previousExpressions = new Map(allExpressions.globalExpressions);
421
+ } else if (opts.type === "workbook") {
422
+ previousExpressions = new Map(allExpressions.workbookExpressions.get(opts.workbookName) || []);
423
+ } else {
424
+ const sheetExpressions = allExpressions.sheetExpressions.get(opts.workbookName)?.get(opts.sheetName);
425
+ previousExpressions = new Map(sheetExpressions || []);
426
+ }
427
+ this.namedExpressionManager.setNamedExpressions(opts);
428
+ const scope = opts.type === "global" ? {} : opts.type === "workbook" ? { workbookName: opts.workbookName } : {
429
+ workbookName: opts.workbookName,
430
+ sheetName: opts.sheetName
431
+ };
432
+ this.emitMutation({
433
+ touchedCells: [],
434
+ resourceKeys: [
435
+ ...import_mutation_invalidation.getNamedExpressionScopeResourceKeys(previousExpressions.keys(), scope),
436
+ ...import_mutation_invalidation.getNamedExpressionScopeResourceKeys(opts.expressions.keys(), scope)
437
+ ]
438
+ });
345
439
  });
346
440
  }
347
441
  addTable(props) {
348
- const table = this.tableManager.addTable({
349
- ...props,
350
- getCellValue: (cellAddress) => this.getCellValue(cellAddress)
351
- });
352
- this.emitMutation({
353
- touchedCells: import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [table]),
354
- tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [table]),
355
- resourceKeys: [
356
- import_resource_keys.getTableResourceKey({
357
- workbookName: props.workbookName,
358
- tableName: props.tableName
359
- })
360
- ]
442
+ return this.withUndoRedoCheckpoint(() => {
443
+ const table = this.tableManager.addTable({
444
+ ...props,
445
+ getCellValue: (cellAddress) => this.getCellValue(cellAddress)
446
+ });
447
+ this.emitMutation({
448
+ touchedCells: import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [table]),
449
+ tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [table]),
450
+ resourceKeys: [
451
+ import_resource_keys.getTableResourceKey({
452
+ workbookName: props.workbookName,
453
+ tableName: props.tableName
454
+ })
455
+ ]
456
+ });
361
457
  });
362
458
  }
363
459
  renameTable(workbookName, names) {
364
- const oldTable = this.tableManager.getTable({
365
- workbookName,
366
- name: names.oldName
367
- });
368
- const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
369
- this.tableManager.renameTable(workbookName, names);
370
- const changedCells = this.workbookManager.updateAllFormulas((formula) => import_table_renamer.renameTableInFormula(formula, names.oldName, names.newName));
371
- const changedNamedExpressions = this.namedExpressionManager.updateAllNamedExpressions((formula) => import_table_renamer.renameTableInFormula(formula, names.oldName, names.newName));
372
- const newTable = this.tableManager.getTable({
373
- workbookName,
374
- name: names.newName
375
- });
376
- this.emitMutation({
377
- touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [oldTableSnapshot]), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [newTable]), import_mutation_invalidation.buildFormulaTouchedCells(changedCells)),
378
- tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot, newTable]),
379
- resourceKeys: [
380
- import_resource_keys.getTableResourceKey({
381
- workbookName,
382
- tableName: names.oldName
383
- }),
384
- import_resource_keys.getTableResourceKey({
385
- workbookName,
386
- tableName: names.newName
387
- }),
388
- ...changedNamedExpressions
389
- ]
460
+ return this.withUndoRedoCheckpoint(() => {
461
+ const oldTable = this.tableManager.getTable({
462
+ workbookName,
463
+ name: names.oldName
464
+ });
465
+ const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
466
+ this.tableManager.renameTable(workbookName, names);
467
+ const changedCells = this.workbookManager.updateAllFormulas((formula) => import_table_renamer.renameTableInFormula(formula, names.oldName, names.newName));
468
+ const changedNamedExpressions = this.namedExpressionManager.updateAllNamedExpressions((formula) => import_table_renamer.renameTableInFormula(formula, names.oldName, names.newName));
469
+ const newTable = this.tableManager.getTable({
470
+ workbookName,
471
+ name: names.newName
472
+ });
473
+ this.emitMutation({
474
+ touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [oldTableSnapshot]), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [newTable]), import_mutation_invalidation.buildFormulaTouchedCells(changedCells)),
475
+ tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot, newTable]),
476
+ resourceKeys: [
477
+ import_resource_keys.getTableResourceKey({
478
+ workbookName,
479
+ tableName: names.oldName
480
+ }),
481
+ import_resource_keys.getTableResourceKey({
482
+ workbookName,
483
+ tableName: names.newName
484
+ }),
485
+ ...changedNamedExpressions
486
+ ]
487
+ });
390
488
  });
391
489
  }
392
490
  updateTable(opts) {
393
- const oldTable = this.tableManager.getTable({
394
- workbookName: opts.workbookName,
395
- name: opts.tableName
396
- });
397
- const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
398
- this.tableManager.updateTable({
399
- ...opts,
400
- getCellValue: (cellAddress) => this.getCellValue(cellAddress)
401
- });
402
- const newTable = this.tableManager.getTable({
403
- workbookName: opts.workbookName,
404
- name: opts.tableName
405
- });
406
- this.emitMutation({
407
- touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [oldTableSnapshot]), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [newTable])),
408
- tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot, newTable]),
409
- resourceKeys: [
410
- import_resource_keys.getTableResourceKey({
411
- workbookName: opts.workbookName,
412
- tableName: opts.tableName
413
- })
414
- ]
415
- });
416
- }
417
- removeTable(opts) {
418
- const oldTable = this.tableManager.getTable({
419
- workbookName: opts.workbookName,
420
- name: opts.tableName
421
- });
422
- const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
423
- const found = this.tableManager.removeTable(opts);
424
- if (found) {
491
+ return this.withUndoRedoCheckpoint(() => {
492
+ const oldTable = this.tableManager.getTable({
493
+ workbookName: opts.workbookName,
494
+ name: opts.tableName
495
+ });
496
+ const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
497
+ this.tableManager.updateTable({
498
+ ...opts,
499
+ getCellValue: (cellAddress) => this.getCellValue(cellAddress)
500
+ });
501
+ const newTable = this.tableManager.getTable({
502
+ workbookName: opts.workbookName,
503
+ name: opts.tableName
504
+ });
425
505
  this.emitMutation({
426
- touchedCells: import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [
427
- oldTableSnapshot
428
- ]),
429
- tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot]),
506
+ touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [oldTableSnapshot]), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [newTable])),
507
+ tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot, newTable]),
430
508
  resourceKeys: [
431
509
  import_resource_keys.getTableResourceKey({
432
510
  workbookName: opts.workbookName,
@@ -434,7 +512,31 @@ class FormulaEngine {
434
512
  })
435
513
  ]
436
514
  });
437
- }
515
+ });
516
+ }
517
+ removeTable(opts) {
518
+ return this.withUndoRedoCheckpoint(() => {
519
+ const oldTable = this.tableManager.getTable({
520
+ workbookName: opts.workbookName,
521
+ name: opts.tableName
522
+ });
523
+ const oldTableSnapshot = oldTable ? { ...oldTable, headers: new Map(oldTable.headers) } : undefined;
524
+ const found = this.tableManager.removeTable(opts);
525
+ if (found) {
526
+ this.emitMutation({
527
+ touchedCells: import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, [
528
+ oldTableSnapshot
529
+ ]),
530
+ tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [oldTableSnapshot]),
531
+ resourceKeys: [
532
+ import_resource_keys.getTableResourceKey({
533
+ workbookName: opts.workbookName,
534
+ tableName: opts.tableName
535
+ })
536
+ ]
537
+ });
538
+ }
539
+ });
438
540
  }
439
541
  getAllTables() {
440
542
  return Array.from(this.tableManager.tables.values()).flatMap((tables) => Array.from(tables.values()).map((table) => ({
@@ -455,20 +557,22 @@ class FormulaEngine {
455
557
  });
456
558
  }
457
559
  resetTables(tables) {
458
- const oldTables = this.getAllTables();
459
- const newTables = Array.from(tables.values()).flatMap((workbookTables) => Array.from(workbookTables.values()));
460
- const resourceKeys = new Set;
461
- for (const table of [...oldTables, ...newTables]) {
462
- resourceKeys.add(import_resource_keys.getTableResourceKey({
463
- workbookName: table.workbookName,
464
- tableName: table.name
465
- }));
466
- }
467
- this.tableManager.resetTables(tables);
468
- this.emitMutation({
469
- touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, oldTables), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, newTables)),
470
- tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [...oldTables, ...newTables]),
471
- resourceKeys: Array.from(resourceKeys)
560
+ return this.withUndoRedoCheckpoint(() => {
561
+ const oldTables = this.getAllTables();
562
+ const newTables = Array.from(tables.values()).flatMap((workbookTables) => Array.from(workbookTables.values()));
563
+ const resourceKeys = new Set;
564
+ for (const table of [...oldTables, ...newTables]) {
565
+ resourceKeys.add(import_resource_keys.getTableResourceKey({
566
+ workbookName: table.workbookName,
567
+ tableName: table.name
568
+ }));
569
+ }
570
+ this.tableManager.resetTables(tables);
571
+ this.emitMutation({
572
+ touchedCells: import_mutation_invalidation.mergeTouchedCells(import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, oldTables), import_mutation_invalidation.buildTableTouchedCells(this.workbookManager, newTables)),
573
+ tableContextChangedCells: import_mutation_invalidation.buildTableContextChangedCells(this.workbookManager, [...oldTables, ...newTables]),
574
+ resourceKeys: Array.from(resourceKeys)
575
+ });
472
576
  });
473
577
  }
474
578
  getTables(workbookName) {
@@ -478,14 +582,18 @@ class FormulaEngine {
478
582
  return this.tableManager.isCellInTable(cellAddress);
479
583
  }
480
584
  addConditionalStyle(style) {
481
- this.styleManager.addConditionalStyle(style);
482
- this.emitUpdate();
585
+ return this.withUndoRedoCheckpoint(() => {
586
+ this.styleManager.addConditionalStyle(style);
587
+ this.emitUpdate();
588
+ });
483
589
  }
484
590
  removeConditionalStyle(workbookName, index) {
485
- const removed = this.styleManager.removeConditionalStyle(workbookName, index);
486
- if (removed) {
487
- this.emitUpdate();
488
- }
591
+ return this.withUndoRedoCheckpoint(() => {
592
+ const removed = this.styleManager.removeConditionalStyle(workbookName, index);
593
+ if (removed) {
594
+ this.emitUpdate();
595
+ }
596
+ });
489
597
  }
490
598
  getConditionalStyleCount(workbookName) {
491
599
  const allStyles = this.styleManager.getAllConditionalStyles();
@@ -504,14 +612,18 @@ class FormulaEngine {
504
612
  return this.styleManager.getAllConditionalStyles();
505
613
  }
506
614
  addCellStyle(style) {
507
- this.styleManager.addCellStyle(style);
508
- this.emitUpdate();
615
+ return this.withUndoRedoCheckpoint(() => {
616
+ this.styleManager.addCellStyle(style);
617
+ this.emitUpdate();
618
+ });
509
619
  }
510
620
  removeCellStyle(workbookName, index) {
511
- const removed = this.styleManager.removeCellStyle(workbookName, index);
512
- if (removed) {
513
- this.emitUpdate();
514
- }
621
+ return this.withUndoRedoCheckpoint(() => {
622
+ const removed = this.styleManager.removeCellStyle(workbookName, index);
623
+ if (removed) {
624
+ this.emitUpdate();
625
+ }
626
+ });
515
627
  }
516
628
  getCellStyleCount(workbookName) {
517
629
  const allStyles = this.styleManager.getAllCellStyles();
@@ -524,8 +636,10 @@ class FormulaEngine {
524
636
  return this.styleManager.getStyleForRange(range);
525
637
  }
526
638
  clearCellStyles(range) {
527
- this.styleManager.clearCellStyles(range);
528
- this.emitUpdate();
639
+ return this.withUndoRedoCheckpoint(() => {
640
+ this.styleManager.clearCellStyles(range);
641
+ this.emitUpdate();
642
+ });
529
643
  }
530
644
  getTopLeftCell(cells) {
531
645
  let topLeft = cells[0];
@@ -555,100 +669,106 @@ class FormulaEngine {
555
669
  return this.dedupeAddresses(options.cut ? [...source, ...targetCells] : targetCells);
556
670
  }
557
671
  pasteCells(source, target, options) {
558
- if (source.length === 0) {
559
- return;
560
- }
561
- const touchedAddresses = this.getPasteTouchedAddresses(source, target, options);
562
- const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
563
- this.copyManager.pasteCells(source, target, options);
564
- const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
565
- this.emitMutation({
566
- touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
567
- address,
568
- before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
569
- after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
570
- }))),
571
- resourceKeys: []
672
+ return this.withUndoRedoCheckpoint(() => {
673
+ if (source.length === 0) {
674
+ return;
675
+ }
676
+ const touchedAddresses = this.getPasteTouchedAddresses(source, target, options);
677
+ const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
678
+ this.copyManager.pasteCells(source, target, options);
679
+ const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
680
+ this.emitMutation({
681
+ touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
682
+ address,
683
+ before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
684
+ after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
685
+ }))),
686
+ resourceKeys: []
687
+ });
572
688
  });
573
689
  }
574
690
  fillAreas(seedRange, targetRanges, options) {
575
- const touchedAddresses = this.dedupeAddresses([
576
- ...targetRanges.flatMap((targetRange) => import_mutation_invalidation.getFiniteRangeAddresses(targetRange)),
577
- ...options.cut ? import_mutation_invalidation.getFiniteRangeAddresses(seedRange) : []
578
- ]);
579
- const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
580
- this.copyManager.fillAreas(seedRange, targetRanges, options);
581
- const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
582
- this.emitMutation({
583
- touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
584
- address,
585
- before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
586
- after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
587
- }))),
588
- resourceKeys: []
691
+ return this.withUndoRedoCheckpoint(() => {
692
+ const touchedAddresses = this.dedupeAddresses([
693
+ ...targetRanges.flatMap((targetRange) => import_mutation_invalidation.getFiniteRangeAddresses(targetRange)),
694
+ ...options.cut ? import_mutation_invalidation.getFiniteRangeAddresses(seedRange) : []
695
+ ]);
696
+ const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
697
+ this.copyManager.fillAreas(seedRange, targetRanges, options);
698
+ const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
699
+ this.emitMutation({
700
+ touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
701
+ address,
702
+ before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
703
+ after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
704
+ }))),
705
+ resourceKeys: []
706
+ });
589
707
  });
590
708
  }
591
709
  smartPaste(sourceCells, pasteSelection, options) {
592
- if (sourceCells.length === 0) {
593
- return;
594
- }
595
- if (options.cut === true) {
596
- for (const area of pasteSelection.areas) {
597
- const target = {
598
- workbookName: pasteSelection.workbookName,
599
- sheetName: pasteSelection.sheetName,
600
- colIndex: area.start.col,
601
- rowIndex: area.start.row
602
- };
603
- this.pasteCells(sourceCells, target, options);
710
+ return this.withUndoRedoCheckpoint(() => {
711
+ if (sourceCells.length === 0) {
712
+ return;
604
713
  }
605
- return;
606
- }
607
- const sourceBounds = this.getBoundsFromCells(sourceCells);
608
- const sourceWidth = sourceBounds.maxCol - sourceBounds.minCol + 1;
609
- const sourceHeight = sourceBounds.maxRow - sourceBounds.minRow + 1;
610
- const seedRange = {
611
- workbookName: sourceCells[0].workbookName,
612
- sheetName: sourceCells[0].sheetName,
613
- range: {
614
- start: { col: sourceBounds.minCol, row: sourceBounds.minRow },
615
- end: {
616
- col: { type: "number", value: sourceBounds.maxCol },
617
- row: { type: "number", value: sourceBounds.maxRow }
714
+ if (options.cut === true) {
715
+ for (const area of pasteSelection.areas) {
716
+ const target = {
717
+ workbookName: pasteSelection.workbookName,
718
+ sheetName: pasteSelection.sheetName,
719
+ colIndex: area.start.col,
720
+ rowIndex: area.start.row
721
+ };
722
+ this.pasteCells(sourceCells, target, options);
618
723
  }
724
+ return;
619
725
  }
620
- };
621
- for (const area of pasteSelection.areas) {
622
- const pasteStartCol = area.start.col;
623
- const pasteStartRow = area.start.row;
624
- const pasteEndCol = area.end.col.type === "number" ? area.end.col.value : pasteStartCol;
625
- const pasteEndRow = area.end.row.type === "number" ? area.end.row.value : pasteStartRow;
626
- const pasteWidth = pasteEndCol - pasteStartCol + 1;
627
- const pasteHeight = pasteEndRow - pasteStartRow + 1;
628
- const shouldFill = pasteWidth > sourceWidth || pasteHeight > sourceHeight;
629
- if (shouldFill) {
630
- const targetRange = {
631
- workbookName: pasteSelection.workbookName,
632
- sheetName: pasteSelection.sheetName,
633
- range: {
634
- start: { col: pasteStartCol, row: pasteStartRow },
635
- end: {
636
- col: { type: "number", value: pasteEndCol },
637
- row: { type: "number", value: pasteEndRow }
638
- }
726
+ const sourceBounds = this.getBoundsFromCells(sourceCells);
727
+ const sourceWidth = sourceBounds.maxCol - sourceBounds.minCol + 1;
728
+ const sourceHeight = sourceBounds.maxRow - sourceBounds.minRow + 1;
729
+ const seedRange = {
730
+ workbookName: sourceCells[0].workbookName,
731
+ sheetName: sourceCells[0].sheetName,
732
+ range: {
733
+ start: { col: sourceBounds.minCol, row: sourceBounds.minRow },
734
+ end: {
735
+ col: { type: "number", value: sourceBounds.maxCol },
736
+ row: { type: "number", value: sourceBounds.maxRow }
639
737
  }
640
- };
641
- this.fillAreas(seedRange, [targetRange], options);
642
- } else {
643
- const target = {
644
- workbookName: pasteSelection.workbookName,
645
- sheetName: pasteSelection.sheetName,
646
- colIndex: pasteStartCol,
647
- rowIndex: pasteStartRow
648
- };
649
- this.pasteCells(sourceCells, target, options);
738
+ }
739
+ };
740
+ for (const area of pasteSelection.areas) {
741
+ const pasteStartCol = area.start.col;
742
+ const pasteStartRow = area.start.row;
743
+ const pasteEndCol = area.end.col.type === "number" ? area.end.col.value : pasteStartCol;
744
+ const pasteEndRow = area.end.row.type === "number" ? area.end.row.value : pasteStartRow;
745
+ const pasteWidth = pasteEndCol - pasteStartCol + 1;
746
+ const pasteHeight = pasteEndRow - pasteStartRow + 1;
747
+ const shouldFill = pasteWidth > sourceWidth || pasteHeight > sourceHeight;
748
+ if (shouldFill) {
749
+ const targetRange = {
750
+ workbookName: pasteSelection.workbookName,
751
+ sheetName: pasteSelection.sheetName,
752
+ range: {
753
+ start: { col: pasteStartCol, row: pasteStartRow },
754
+ end: {
755
+ col: { type: "number", value: pasteEndCol },
756
+ row: { type: "number", value: pasteEndRow }
757
+ }
758
+ }
759
+ };
760
+ this.fillAreas(seedRange, [targetRange], options);
761
+ } else {
762
+ const target = {
763
+ workbookName: pasteSelection.workbookName,
764
+ sheetName: pasteSelection.sheetName,
765
+ colIndex: pasteStartCol,
766
+ rowIndex: pasteStartRow
767
+ };
768
+ this.pasteCells(sourceCells, target, options);
769
+ }
650
770
  }
651
- }
771
+ });
652
772
  }
653
773
  getBoundsFromCells(cells) {
654
774
  if (cells.length === 0) {
@@ -667,72 +787,84 @@ class FormulaEngine {
667
787
  return { minCol, minRow, maxCol, maxRow };
668
788
  }
669
789
  moveCell(source, target) {
670
- this.pasteCells([source], target, {
671
- cut: true,
672
- type: "formula",
673
- include: "all"
790
+ return this.withUndoRedoCheckpoint(() => {
791
+ this.pasteCells([source], target, {
792
+ cut: true,
793
+ type: "formula",
794
+ include: "all"
795
+ });
674
796
  });
675
797
  }
676
798
  moveRange(sourceRange, target) {
677
- const cells = this.copyManager.expandRangeToCells(sourceRange);
678
- this.pasteCells(cells, target, {
679
- cut: true,
680
- type: "formula",
681
- include: "all"
799
+ return this.withUndoRedoCheckpoint(() => {
800
+ const cells = this.copyManager.expandRangeToCells(sourceRange);
801
+ this.pasteCells(cells, target, {
802
+ cut: true,
803
+ type: "formula",
804
+ include: "all"
805
+ });
682
806
  });
683
807
  }
684
808
  addSheet(opts) {
685
- const sheet = this.workbookManager.addSheet(opts);
686
- this.namedExpressionManager.addSheet(opts);
687
- this.emitMutation({
688
- touchedCells: [],
689
- resourceKeys: [import_resource_keys.getSheetResourceKey(opts)]
809
+ return this.withUndoRedoCheckpoint(() => {
810
+ const sheet = this.workbookManager.addSheet(opts);
811
+ this.namedExpressionManager.addSheet(opts);
812
+ this.emitMutation({
813
+ touchedCells: [],
814
+ resourceKeys: [import_resource_keys.getSheetResourceKey(opts)]
815
+ });
816
+ return sheet;
690
817
  });
691
- return sheet;
692
818
  }
693
819
  createSheet(opts) {
694
- const sheetName = opts.sheetName ?? this.workbookManager.getNextAvailableSheetName(opts.workbookName, opts.baseName);
695
- return this.addSheet({
696
- workbookName: opts.workbookName,
697
- sheetName
820
+ return this.withUndoRedoCheckpoint(() => {
821
+ const sheetName = opts.sheetName ?? this.workbookManager.getNextAvailableSheetName(opts.workbookName, opts.baseName);
822
+ return this.addSheet({
823
+ workbookName: opts.workbookName,
824
+ sheetName
825
+ });
698
826
  });
699
827
  }
700
828
  removeSheet(opts) {
701
- const resourceKeys = this.getSheetResourceKeys(opts);
702
- this.workbookManager.removeSheet(opts);
703
- this.namedExpressionManager.removeSheet(opts);
704
- this.tableManager.removeSheet(opts);
705
- this.styleManager.removeSheetStyles(opts.workbookName, opts.sheetName);
706
- this.rangeMetadataManager.removeSheetRangeMetadata(opts.workbookName, opts.sheetName);
707
- this.referenceManager.invalidateSheet(opts.workbookName, opts.sheetName);
708
- this.emitMutation({
709
- touchedCells: [],
710
- resourceKeys,
711
- removedScopes: [{ type: "sheet", ...opts }]
829
+ return this.withUndoRedoCheckpoint(() => {
830
+ const resourceKeys = this.getSheetResourceKeys(opts);
831
+ this.workbookManager.removeSheet(opts);
832
+ this.namedExpressionManager.removeSheet(opts);
833
+ this.tableManager.removeSheet(opts);
834
+ this.styleManager.removeSheetStyles(opts.workbookName, opts.sheetName);
835
+ this.rangeMetadataManager.removeSheetRangeMetadata(opts.workbookName, opts.sheetName);
836
+ this.referenceManager.invalidateSheet(opts.workbookName, opts.sheetName);
837
+ this.emitMutation({
838
+ touchedCells: [],
839
+ resourceKeys,
840
+ removedScopes: [{ type: "sheet", ...opts }]
841
+ });
712
842
  });
713
843
  }
714
844
  renameSheet(opts) {
715
- const oldResourceKeys = this.getSheetResourceKeys(opts);
716
- this.workbookManager.renameSheet(opts);
717
- this.namedExpressionManager.renameSheet(opts);
718
- this.tableManager.updateTablesForSheetRename(opts);
719
- this.styleManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
720
- this.rangeMetadataManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
721
- const changedCells = this.workbookManager.updateAllFormulas((formula) => import_sheet_renamer.renameSheetInFormula({
722
- formula,
723
- oldSheetName: opts.sheetName,
724
- newSheetName: opts.newSheetName
725
- }));
726
- this.referenceManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
727
- this.emitMutation({
728
- touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
729
- resourceKeys: Array.from(new Set([
730
- ...oldResourceKeys,
731
- ...this.getSheetResourceKeys({
732
- workbookName: opts.workbookName,
733
- sheetName: opts.newSheetName
734
- })
735
- ]))
845
+ return this.withUndoRedoCheckpoint(() => {
846
+ const oldResourceKeys = this.getSheetResourceKeys(opts);
847
+ this.workbookManager.renameSheet(opts);
848
+ this.namedExpressionManager.renameSheet(opts);
849
+ this.tableManager.updateTablesForSheetRename(opts);
850
+ this.styleManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
851
+ this.rangeMetadataManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
852
+ const changedCells = this.workbookManager.updateAllFormulas((formula) => import_sheet_renamer.renameSheetInFormula({
853
+ formula,
854
+ oldSheetName: opts.sheetName,
855
+ newSheetName: opts.newSheetName
856
+ }));
857
+ this.referenceManager.updateSheetName(opts.workbookName, opts.sheetName, opts.newSheetName);
858
+ this.emitMutation({
859
+ touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
860
+ resourceKeys: Array.from(new Set([
861
+ ...oldResourceKeys,
862
+ ...this.getSheetResourceKeys({
863
+ workbookName: opts.workbookName,
864
+ sheetName: opts.newSheetName
865
+ })
866
+ ]))
867
+ });
736
868
  });
737
869
  }
738
870
  hasSheet(opts) {
@@ -763,239 +895,259 @@ class FormulaEngine {
763
895
  return this.workbookManager.search(query, options);
764
896
  }
765
897
  replace(query, replacement, target, options) {
766
- const prepared = this.workbookManager.prepareReplace(query, replacement, target, options);
767
- this.workbookManager.setCellContent(prepared.address, prepared.afterContent);
768
- this.emitMutation({
769
- touchedCells: import_mutation_invalidation.buildTouchedCells([
770
- {
771
- address: prepared.address,
772
- before: prepared.beforeContent,
773
- after: prepared.afterContent
774
- }
775
- ]),
776
- resourceKeys: []
898
+ return this.withUndoRedoCheckpoint(() => {
899
+ const prepared = this.workbookManager.prepareReplace(query, replacement, target, options);
900
+ this.workbookManager.setCellContent(prepared.address, prepared.afterContent);
901
+ this.emitMutation({
902
+ touchedCells: import_mutation_invalidation.buildTouchedCells([
903
+ {
904
+ address: prepared.address,
905
+ before: prepared.beforeContent,
906
+ after: prepared.afterContent
907
+ }
908
+ ]),
909
+ resourceKeys: []
910
+ });
911
+ return prepared.change;
777
912
  });
778
- return prepared.change;
779
913
  }
780
914
  replaceAll(query, replacement, options) {
781
- const preparedReplacements = this.workbookManager.prepareReplaceAll(query, replacement, options);
782
- if (preparedReplacements.length === 0) {
783
- return [];
784
- }
785
- for (const prepared of preparedReplacements) {
786
- this.workbookManager.setCellContent(prepared.address, prepared.afterContent);
787
- }
788
- this.emitMutation({
789
- touchedCells: import_mutation_invalidation.buildTouchedCells(preparedReplacements.map((prepared) => ({
790
- address: prepared.address,
791
- before: prepared.beforeContent,
792
- after: prepared.afterContent
793
- }))),
794
- resourceKeys: []
915
+ return this.withUndoRedoCheckpoint(() => {
916
+ const preparedReplacements = this.workbookManager.prepareReplaceAll(query, replacement, options);
917
+ if (preparedReplacements.length === 0) {
918
+ return [];
919
+ }
920
+ for (const prepared of preparedReplacements) {
921
+ this.workbookManager.setCellContent(prepared.address, prepared.afterContent);
922
+ }
923
+ this.emitMutation({
924
+ touchedCells: import_mutation_invalidation.buildTouchedCells(preparedReplacements.map((prepared) => ({
925
+ address: prepared.address,
926
+ before: prepared.beforeContent,
927
+ after: prepared.afterContent
928
+ }))),
929
+ resourceKeys: []
930
+ });
931
+ return preparedReplacements.flatMap((prepared) => prepared.changes);
795
932
  });
796
- return preparedReplacements.flatMap((prepared) => prepared.changes);
797
933
  }
798
934
  addWorkbook(workbookName) {
799
- this.workbookManager.addWorkbook(workbookName);
800
- this.namedExpressionManager.addWorkbook(workbookName);
801
- this.tableManager.addWorkbook(workbookName);
802
- this.emitMutation({
803
- touchedCells: [],
804
- resourceKeys: [import_resource_keys.getWorkbookResourceKey(workbookName)]
935
+ return this.withUndoRedoCheckpoint(() => {
936
+ this.workbookManager.addWorkbook(workbookName);
937
+ this.namedExpressionManager.addWorkbook(workbookName);
938
+ this.tableManager.addWorkbook(workbookName);
939
+ this.emitMutation({
940
+ touchedCells: [],
941
+ resourceKeys: [import_resource_keys.getWorkbookResourceKey(workbookName)]
942
+ });
805
943
  });
806
944
  }
807
945
  removeWorkbook(workbookName) {
808
- const resourceKeys = this.getWorkbookResourceKeys(workbookName);
809
- this.workbookManager.removeWorkbook(workbookName);
810
- this.namedExpressionManager.removeWorkbook(workbookName);
811
- this.tableManager.removeWorkbook(workbookName);
812
- this.styleManager.removeWorkbookStyles(workbookName);
813
- this.rangeMetadataManager.removeWorkbookRangeMetadata(workbookName);
814
- this.referenceManager.invalidateWorkbook(workbookName);
815
- this.emitMutation({
816
- touchedCells: [],
817
- resourceKeys,
818
- removedScopes: [{ type: "workbook", workbookName }]
946
+ return this.withUndoRedoCheckpoint(() => {
947
+ const resourceKeys = this.getWorkbookResourceKeys(workbookName);
948
+ this.workbookManager.removeWorkbook(workbookName);
949
+ this.namedExpressionManager.removeWorkbook(workbookName);
950
+ this.tableManager.removeWorkbook(workbookName);
951
+ this.styleManager.removeWorkbookStyles(workbookName);
952
+ this.rangeMetadataManager.removeWorkbookRangeMetadata(workbookName);
953
+ this.referenceManager.invalidateWorkbook(workbookName);
954
+ this.emitMutation({
955
+ touchedCells: [],
956
+ resourceKeys,
957
+ removedScopes: [{ type: "workbook", workbookName }]
958
+ });
819
959
  });
820
960
  }
821
961
  hasWorkbook(workbookName) {
822
962
  return this.workbookManager.getWorkbooks().has(workbookName);
823
963
  }
824
964
  cloneWorkbook(fromWorkbookName, toWorkbookName) {
825
- const sourceWorkbook = this.workbookManager.getWorkbooks().get(fromWorkbookName);
826
- if (!sourceWorkbook) {
827
- throw new Error(`Source workbook "${fromWorkbookName}" not found`);
828
- }
829
- if (this.workbookManager.getWorkbooks().has(toWorkbookName)) {
830
- throw new Error(`Target workbook "${toWorkbookName}" already exists`);
831
- }
832
- this.workbookManager.addWorkbook(toWorkbookName);
833
- this.namedExpressionManager.addWorkbook(toWorkbookName);
834
- this.tableManager.addWorkbook(toWorkbookName);
835
- for (const [sheetName, sheet] of sourceWorkbook.sheets) {
836
- this.workbookManager.addSheet({
837
- workbookName: toWorkbookName,
838
- sheetName
839
- });
840
- this.namedExpressionManager.addSheet({
841
- workbookName: toWorkbookName,
842
- sheetName
843
- });
844
- this.workbookManager.setSheetContent({ workbookName: toWorkbookName, sheetName }, new Map(sheet.content));
845
- const targetSheet = this.workbookManager.getSheet({
846
- workbookName: toWorkbookName,
847
- sheetName
848
- });
849
- if (targetSheet) {
850
- targetSheet.metadata = new Map(sheet.metadata);
851
- if (sheet.sheetMetadata !== undefined) {
852
- targetSheet.sheetMetadata = structuredClone(sheet.sheetMetadata);
853
- }
965
+ return this.withUndoRedoCheckpoint(() => {
966
+ const sourceWorkbook = this.workbookManager.getWorkbooks().get(fromWorkbookName);
967
+ if (!sourceWorkbook) {
968
+ throw new Error(`Source workbook "${fromWorkbookName}" not found`);
854
969
  }
855
- }
856
- const targetWorkbook = this.workbookManager.getWorkbooks().get(toWorkbookName);
857
- if (targetWorkbook && sourceWorkbook.workbookMetadata !== undefined) {
858
- targetWorkbook.workbookMetadata = structuredClone(sourceWorkbook.workbookMetadata);
859
- }
860
- const namedExpressions = this.namedExpressionManager.getNamedExpressions();
861
- const sourceWorkbookExpressions = namedExpressions.workbookExpressions.get(fromWorkbookName);
862
- if (sourceWorkbookExpressions) {
863
- for (const [expressionName, expression] of sourceWorkbookExpressions) {
864
- this.namedExpressionManager.addNamedExpression({
865
- expressionName,
866
- expression: expression.expression,
867
- workbookName: toWorkbookName
970
+ if (this.workbookManager.getWorkbooks().has(toWorkbookName)) {
971
+ throw new Error(`Target workbook "${toWorkbookName}" already exists`);
972
+ }
973
+ this.workbookManager.addWorkbook(toWorkbookName);
974
+ this.namedExpressionManager.addWorkbook(toWorkbookName);
975
+ this.tableManager.addWorkbook(toWorkbookName);
976
+ for (const [sheetName, sheet] of sourceWorkbook.sheets) {
977
+ this.workbookManager.addSheet({
978
+ workbookName: toWorkbookName,
979
+ sheetName
980
+ });
981
+ this.namedExpressionManager.addSheet({
982
+ workbookName: toWorkbookName,
983
+ sheetName
984
+ });
985
+ this.workbookManager.setSheetContent({ workbookName: toWorkbookName, sheetName }, new Map(sheet.content));
986
+ const targetSheet = this.workbookManager.getSheet({
987
+ workbookName: toWorkbookName,
988
+ sheetName
868
989
  });
990
+ if (targetSheet) {
991
+ targetSheet.metadata = new Map(sheet.metadata);
992
+ if (sheet.sheetMetadata !== undefined) {
993
+ targetSheet.sheetMetadata = structuredClone(sheet.sheetMetadata);
994
+ }
995
+ }
869
996
  }
870
- }
871
- const sourceSheetExpressions = namedExpressions.sheetExpressions.get(fromWorkbookName);
872
- if (sourceSheetExpressions) {
873
- for (const [sheetName, expressions] of sourceSheetExpressions) {
874
- for (const [expressionName, expression] of expressions) {
997
+ const targetWorkbook = this.workbookManager.getWorkbooks().get(toWorkbookName);
998
+ if (targetWorkbook && sourceWorkbook.workbookMetadata !== undefined) {
999
+ targetWorkbook.workbookMetadata = structuredClone(sourceWorkbook.workbookMetadata);
1000
+ }
1001
+ const namedExpressions = this.namedExpressionManager.getNamedExpressions();
1002
+ const sourceWorkbookExpressions = namedExpressions.workbookExpressions.get(fromWorkbookName);
1003
+ if (sourceWorkbookExpressions) {
1004
+ for (const [expressionName, expression] of sourceWorkbookExpressions) {
875
1005
  this.namedExpressionManager.addNamedExpression({
876
1006
  expressionName,
877
1007
  expression: expression.expression,
878
- workbookName: toWorkbookName,
879
- sheetName
1008
+ workbookName: toWorkbookName
880
1009
  });
881
1010
  }
882
1011
  }
883
- }
884
- const sourceTables = this.tableManager.tables.get(fromWorkbookName);
885
- if (sourceTables) {
886
- for (const [tableName] of sourceTables) {
887
- this.tableManager.copyTable({ workbookName: fromWorkbookName, tableName }, { workbookName: toWorkbookName, tableName });
1012
+ const sourceSheetExpressions = namedExpressions.sheetExpressions.get(fromWorkbookName);
1013
+ if (sourceSheetExpressions) {
1014
+ for (const [sheetName, expressions] of sourceSheetExpressions) {
1015
+ for (const [expressionName, expression] of expressions) {
1016
+ this.namedExpressionManager.addNamedExpression({
1017
+ expressionName,
1018
+ expression: expression.expression,
1019
+ workbookName: toWorkbookName,
1020
+ sheetName
1021
+ });
1022
+ }
1023
+ }
888
1024
  }
889
- }
890
- for (const style of this.styleManager.getAllConditionalStyles()) {
891
- if (style.areas.some((area) => area.workbookName === fromWorkbookName)) {
892
- this.styleManager.addConditionalStyle({
893
- ...style,
894
- areas: style.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
895
- });
1025
+ const sourceTables = this.tableManager.tables.get(fromWorkbookName);
1026
+ if (sourceTables) {
1027
+ for (const [tableName] of sourceTables) {
1028
+ this.tableManager.copyTable({ workbookName: fromWorkbookName, tableName }, { workbookName: toWorkbookName, tableName });
1029
+ }
896
1030
  }
897
- }
898
- for (const style of this.styleManager.getAllCellStyles()) {
899
- if (style.areas.some((area) => area.workbookName === fromWorkbookName)) {
900
- this.styleManager.addCellStyle({
901
- ...style,
902
- areas: style.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
903
- });
1031
+ for (const style of this.styleManager.getAllConditionalStyles()) {
1032
+ if (style.areas.some((area) => area.workbookName === fromWorkbookName)) {
1033
+ this.styleManager.addConditionalStyle({
1034
+ ...style,
1035
+ areas: style.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
1036
+ });
1037
+ }
904
1038
  }
905
- }
906
- for (const metadata of this.rangeMetadataManager.getAllRangeMetadata()) {
907
- if (metadata.areas.some((area) => area.workbookName === fromWorkbookName)) {
908
- this.rangeMetadataManager.addRangeMetadata({
909
- metadata: metadata.metadata,
910
- areas: metadata.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
911
- });
1039
+ for (const style of this.styleManager.getAllCellStyles()) {
1040
+ if (style.areas.some((area) => area.workbookName === fromWorkbookName)) {
1041
+ this.styleManager.addCellStyle({
1042
+ ...style,
1043
+ areas: style.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
1044
+ });
1045
+ }
912
1046
  }
913
- }
914
- this.workbookManager.updateFormulasForWorkbook(toWorkbookName, (formula) => import_workbook_renamer.renameWorkbookInFormula({
915
- formula,
916
- oldWorkbookName: fromWorkbookName,
917
- newWorkbookName: toWorkbookName
918
- }));
919
- this.emitMutation({
920
- touchedCells: [],
921
- resourceKeys: [import_resource_keys.getWorkbookResourceKey(toWorkbookName)]
1047
+ for (const metadata of this.rangeMetadataManager.getAllRangeMetadata()) {
1048
+ if (metadata.areas.some((area) => area.workbookName === fromWorkbookName)) {
1049
+ this.rangeMetadataManager.addRangeMetadata({
1050
+ metadata: metadata.metadata,
1051
+ areas: metadata.areas.map((area) => area.workbookName === fromWorkbookName ? { ...area, workbookName: toWorkbookName } : area)
1052
+ });
1053
+ }
1054
+ }
1055
+ this.workbookManager.updateFormulasForWorkbook(toWorkbookName, (formula) => import_workbook_renamer.renameWorkbookInFormula({
1056
+ formula,
1057
+ oldWorkbookName: fromWorkbookName,
1058
+ newWorkbookName: toWorkbookName
1059
+ }));
1060
+ this.emitMutation({
1061
+ touchedCells: [],
1062
+ resourceKeys: [import_resource_keys.getWorkbookResourceKey(toWorkbookName)]
1063
+ });
922
1064
  });
923
1065
  }
924
1066
  renameWorkbook(opts) {
925
- const oldResourceKeys = this.getWorkbookResourceKeys(opts.workbookName);
926
- this.workbookManager.renameWorkbook(opts);
927
- this.namedExpressionManager.renameWorkbook(opts);
928
- this.tableManager.updateTablesForWorkbookRename(opts);
929
- this.styleManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
930
- this.rangeMetadataManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
931
- const changedCells = this.workbookManager.updateAllFormulas((formula) => import_workbook_renamer.renameWorkbookInFormula({
932
- formula,
933
- oldWorkbookName: opts.workbookName,
934
- newWorkbookName: opts.newWorkbookName
935
- }));
936
- this.referenceManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
937
- this.emitMutation({
938
- touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
939
- resourceKeys: Array.from(new Set([
940
- ...oldResourceKeys,
941
- ...this.getWorkbookResourceKeys(opts.newWorkbookName)
942
- ]))
1067
+ return this.withUndoRedoCheckpoint(() => {
1068
+ const oldResourceKeys = this.getWorkbookResourceKeys(opts.workbookName);
1069
+ this.workbookManager.renameWorkbook(opts);
1070
+ this.namedExpressionManager.renameWorkbook(opts);
1071
+ this.tableManager.updateTablesForWorkbookRename(opts);
1072
+ this.styleManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
1073
+ this.rangeMetadataManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
1074
+ const changedCells = this.workbookManager.updateAllFormulas((formula) => import_workbook_renamer.renameWorkbookInFormula({
1075
+ formula,
1076
+ oldWorkbookName: opts.workbookName,
1077
+ newWorkbookName: opts.newWorkbookName
1078
+ }));
1079
+ this.referenceManager.updateWorkbookName(opts.workbookName, opts.newWorkbookName);
1080
+ this.emitMutation({
1081
+ touchedCells: import_mutation_invalidation.buildFormulaTouchedCells(changedCells),
1082
+ resourceKeys: Array.from(new Set([
1083
+ ...oldResourceKeys,
1084
+ ...this.getWorkbookResourceKeys(opts.newWorkbookName)
1085
+ ]))
1086
+ });
943
1087
  });
944
1088
  }
945
1089
  getWorkbooks() {
946
1090
  return this.workbookManager.getWorkbooks();
947
1091
  }
948
1092
  setSheetContent(opts, content) {
949
- const previousContent = this.getExistingSheetContent(opts);
950
- this.workbookManager.setSheetContent(opts, content);
951
- this.emitMutation({
952
- touchedCells: import_mutation_invalidation.buildSheetContentTouchedCells(opts, previousContent, content),
953
- resourceKeys: []
1093
+ return this.withUndoRedoCheckpoint(() => {
1094
+ const previousContent = this.getExistingSheetContent(opts);
1095
+ this.workbookManager.setSheetContent(opts, content);
1096
+ this.emitMutation({
1097
+ touchedCells: import_mutation_invalidation.buildSheetContentTouchedCells(opts, previousContent, content),
1098
+ resourceKeys: []
1099
+ });
954
1100
  });
955
1101
  }
956
1102
  setCellContent(address, content) {
957
- const previousValue = this.workbookManager.getCellContent(address);
958
- this.workbookManager.setCellContent(address, content);
959
- this.emitMutation({
960
- touchedCells: import_mutation_invalidation.buildTouchedCells([
961
- {
962
- address,
963
- before: previousValue,
964
- after: content
965
- }
966
- ]),
967
- resourceKeys: []
1103
+ return this.withUndoRedoCheckpoint(() => {
1104
+ const previousValue = this.workbookManager.getCellContent(address);
1105
+ this.workbookManager.setCellContent(address, content);
1106
+ this.emitMutation({
1107
+ touchedCells: import_mutation_invalidation.buildTouchedCells([
1108
+ {
1109
+ address,
1110
+ before: previousValue,
1111
+ after: content
1112
+ }
1113
+ ]),
1114
+ resourceKeys: []
1115
+ });
968
1116
  });
969
1117
  }
970
1118
  autoFill(opts, seedRange, fillRanges, direction) {
971
- const touchedAddresses = this.dedupeAddresses(fillRanges.flatMap((range) => import_mutation_invalidation.getFiniteRangeAddresses({
972
- workbookName: opts.workbookName,
973
- sheetName: opts.sheetName,
974
- range
975
- })));
976
- const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
977
- this.autoFillManager.fill(opts, seedRange, fillRanges, direction);
978
- const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
979
- this.emitMutation({
980
- touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
981
- address,
982
- before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
983
- after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
984
- }))),
985
- resourceKeys: []
1119
+ return this.withUndoRedoCheckpoint(() => {
1120
+ const touchedAddresses = this.dedupeAddresses(fillRanges.flatMap((range) => import_mutation_invalidation.getFiniteRangeAddresses({
1121
+ workbookName: opts.workbookName,
1122
+ sheetName: opts.sheetName,
1123
+ range
1124
+ })));
1125
+ const before = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
1126
+ this.autoFillManager.fill(opts, seedRange, fillRanges, direction);
1127
+ const after = import_mutation_invalidation.captureCellContents(this.workbookManager, touchedAddresses);
1128
+ this.emitMutation({
1129
+ touchedCells: import_mutation_invalidation.buildTouchedCells(touchedAddresses.map((address) => ({
1130
+ address,
1131
+ before: before.get(import_mutation_invalidation.getMutationAddressKey(address)),
1132
+ after: after.get(import_mutation_invalidation.getMutationAddressKey(address))
1133
+ }))),
1134
+ resourceKeys: []
1135
+ });
986
1136
  });
987
1137
  }
988
1138
  clearSpreadsheetRange(address) {
989
- const clearedCells = Array.from(this.workbookManager.iterateCellsInRange(address));
990
- const before = import_mutation_invalidation.captureCellContents(this.workbookManager, clearedCells);
991
- this.workbookManager.clearSpreadsheetRange(address);
992
- this.emitMutation({
993
- touchedCells: import_mutation_invalidation.buildTouchedCells(clearedCells.map((cellAddress) => ({
994
- address: cellAddress,
995
- before: before.get(import_mutation_invalidation.getMutationAddressKey(cellAddress)),
996
- after: undefined
997
- }))),
998
- resourceKeys: []
1139
+ return this.withUndoRedoCheckpoint(() => {
1140
+ const clearedCells = Array.from(this.workbookManager.iterateCellsInRange(address));
1141
+ const before = import_mutation_invalidation.captureCellContents(this.workbookManager, clearedCells);
1142
+ this.workbookManager.clearSpreadsheetRange(address);
1143
+ this.emitMutation({
1144
+ touchedCells: import_mutation_invalidation.buildTouchedCells(clearedCells.map((cellAddress) => ({
1145
+ address: cellAddress,
1146
+ before: before.get(import_mutation_invalidation.getMutationAddressKey(cellAddress)),
1147
+ after: undefined
1148
+ }))),
1149
+ resourceKeys: []
1150
+ });
999
1151
  });
1000
1152
  }
1001
1153
  getState() {
@@ -1014,6 +1166,17 @@ class FormulaEngine {
1014
1166
  }
1015
1167
  buildSerializedSnapshot() {
1016
1168
  const evaluationSnapshots = this.dependencyManager.toSnapshot(this.evaluationManager);
1169
+ const historySnapshot = this.buildHistorySnapshot();
1170
+ return {
1171
+ version: import_engine_snapshot.ENGINE_SNAPSHOT_VERSION,
1172
+ managers: {
1173
+ ...historySnapshot.managers,
1174
+ dependency: evaluationSnapshots.dependency,
1175
+ cache: evaluationSnapshots.cache
1176
+ }
1177
+ };
1178
+ }
1179
+ buildHistorySnapshot() {
1017
1180
  const workbookSnapshot = this.workbookManager.toSnapshot();
1018
1181
  return {
1019
1182
  version: import_engine_snapshot.ENGINE_SNAPSHOT_VERSION,
@@ -1023,12 +1186,13 @@ class FormulaEngine {
1023
1186
  table: this.tableManager.toSnapshot(),
1024
1187
  style: this.styleManager.toSnapshot(),
1025
1188
  rangeMetadata: this.rangeMetadataManager.toSnapshot(),
1026
- reference: this.referenceManager.toSnapshot(),
1027
- dependency: evaluationSnapshots.dependency,
1028
- cache: evaluationSnapshots.cache
1189
+ reference: this.referenceManager.toSnapshot()
1029
1190
  }
1030
1191
  };
1031
1192
  }
1193
+ serializeHistorySnapshot() {
1194
+ return import_map_serializer.serialize(this.buildHistorySnapshot());
1195
+ }
1032
1196
  buildNamedExpressionSnapshot(workbookSnapshot) {
1033
1197
  const namedExpressions = this.namedExpressionManager.toSnapshot();
1034
1198
  const workbookExpressions = new Map;
@@ -1052,14 +1216,15 @@ class FormulaEngine {
1052
1216
  serializeEngine() {
1053
1217
  return import_map_serializer.serialize(this.buildSerializedSnapshot());
1054
1218
  }
1055
- resetToSerializedEngine(data) {
1056
- const deserialized = import_map_serializer.deserialize(data);
1057
- if (!deserialized || typeof deserialized !== "object" || !("version" in deserialized) || deserialized.version !== import_engine_snapshot.ENGINE_SNAPSHOT_VERSION || !deserialized.managers) {
1219
+ assertSupportedSnapshot(snapshot) {
1220
+ if (!snapshot || typeof snapshot !== "object" || !("version" in snapshot) || snapshot.version !== import_engine_snapshot.ENGINE_SNAPSHOT_VERSION || !snapshot.managers) {
1058
1221
  throw new Error(`Unsupported serialized engine format. Expected EngineSnapshot version ${import_engine_snapshot.ENGINE_SNAPSHOT_VERSION}.`);
1059
1222
  }
1223
+ }
1224
+ restoreDataManagersFromSnapshot(managers) {
1060
1225
  this.namedExpressionManager.clear();
1061
- this.workbookManager.restoreFromSnapshot(deserialized.managers.workbook);
1062
- deserialized.managers.workbook.forEach((workbook) => {
1226
+ this.workbookManager.restoreFromSnapshot(managers.workbook);
1227
+ managers.workbook.forEach((workbook) => {
1063
1228
  this.namedExpressionManager.addWorkbook(workbook.name);
1064
1229
  workbook.sheets.forEach((sheet) => {
1065
1230
  this.namedExpressionManager.addSheet({
@@ -1068,17 +1233,29 @@ class FormulaEngine {
1068
1233
  });
1069
1234
  });
1070
1235
  });
1071
- this.namedExpressionManager.restoreFromSnapshot(deserialized.managers.namedExpression);
1072
- this.tableManager.restoreFromSnapshot(deserialized.managers.table);
1073
- this.styleManager.restoreFromSnapshot(deserialized.managers.style);
1074
- this.rangeMetadataManager.restoreFromSnapshot(deserialized.managers.rangeMetadata);
1075
- this.referenceManager.restoreFromSnapshot(deserialized.managers.reference);
1236
+ this.namedExpressionManager.restoreFromSnapshot(managers.namedExpression);
1237
+ this.tableManager.restoreFromSnapshot(managers.table);
1238
+ this.styleManager.restoreFromSnapshot(managers.style);
1239
+ this.rangeMetadataManager.restoreFromSnapshot(managers.rangeMetadata);
1240
+ this.referenceManager.restoreFromSnapshot(managers.reference);
1241
+ }
1242
+ restoreHistorySnapshot(data) {
1243
+ const deserialized = import_map_serializer.deserialize(data);
1244
+ this.assertSupportedSnapshot(deserialized);
1245
+ this.restoreDataManagersFromSnapshot(deserialized.managers);
1246
+ this.evaluationManager.clearEvaluationCache();
1247
+ }
1248
+ resetToSerializedEngine(data) {
1249
+ const deserialized = import_map_serializer.deserialize(data);
1250
+ this.assertSupportedSnapshot(deserialized);
1251
+ this.restoreDataManagersFromSnapshot(deserialized.managers);
1076
1252
  this.dependencyManager.restoreFromSnapshot({
1077
1253
  dependency: deserialized.managers.dependency,
1078
1254
  cache: deserialized.managers.cache
1079
1255
  }, this.evaluationManager);
1256
+ this.clearUndoRedoHistory();
1080
1257
  this.eventManager.emitUpdate();
1081
1258
  }
1082
1259
  }
1083
1260
 
1084
- //# debugId=E6611E5B614618F964756E2164756E21
1261
+ //# debugId=618331A008C422E664756E2164756E21