@odoo/o-spreadsheet 16.3.16 → 16.3.18

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.
@@ -1018,6 +1018,22 @@ function lazy(fn) {
1018
1018
  lazyValue.map = (callback) => lazy(() => callback(lazyValue()));
1019
1019
  return lazyValue;
1020
1020
  }
1021
+ /**
1022
+ * Creates a version of the function that's memoized on the value of its first
1023
+ * argument, if any.
1024
+ */
1025
+ function memoize(func) {
1026
+ const cache = new Map();
1027
+ const funcName = func.name ? func.name + " (memoized)" : "memoized";
1028
+ return {
1029
+ [funcName](...args) {
1030
+ if (!cache.has(args[0])) {
1031
+ cache.set(args[0], func(...args));
1032
+ }
1033
+ return cache.get(args[0]);
1034
+ },
1035
+ }[funcName];
1036
+ }
1021
1037
  /**
1022
1038
  * Find the next defined value after the given index in an array of strings. If there is no defined value
1023
1039
  * after the index, return the closest defined value before the index. Return an empty string if no
@@ -3690,12 +3706,12 @@ function toString(value) {
3690
3706
  }
3691
3707
  }
3692
3708
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
3693
- function normalizeString(str) {
3709
+ const normalizeString = memoize(function normalizeString(str) {
3694
3710
  return str
3695
3711
  .toLowerCase()
3696
3712
  .normalize("NFD")
3697
3713
  .replace(/[\u0300-\u036f]/g, "");
3698
- }
3714
+ });
3699
3715
  /**
3700
3716
  * Normalize a value.
3701
3717
  * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters
@@ -4426,6 +4442,7 @@ var CommandResult;
4426
4442
  CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
4427
4443
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4428
4444
  CommandResult[CommandResult["NoActiveSheet"] = 92] = "NoActiveSheet";
4445
+ CommandResult[CommandResult["NoChanges"] = 93] = "NoChanges";
4429
4446
  })(CommandResult || (CommandResult = {}));
4430
4447
 
4431
4448
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -9384,6 +9401,17 @@ const SORT_CELLS_DESCENDING = (env) => {
9384
9401
  };
9385
9402
  const IS_ONLY_ONE_RANGE = (env) => {
9386
9403
  return env.model.getters.getSelectedZones().length === 1;
9404
+ };
9405
+ const CAN_INSERT_HEADER = (env, dimension) => {
9406
+ if (!IS_ONLY_ONE_RANGE(env)) {
9407
+ return false;
9408
+ }
9409
+ const activeHeaders = dimension === "COL" ? env.model.getters.getActiveCols() : env.model.getters.getActiveRows();
9410
+ const ortogonalActiveHeaders = dimension === "COL" ? env.model.getters.getActiveRows() : env.model.getters.getActiveCols();
9411
+ const sheetId = env.model.getters.getActiveSheetId();
9412
+ const zone = env.model.getters.getSelectedZone();
9413
+ const allSheetSelected = isEqual(zone, env.model.getters.getSheetZone(sheetId));
9414
+ return isConsecutive(activeHeaders) && (ortogonalActiveHeaders.size === 0 || allSheetSelected);
9387
9415
  };
9388
9416
 
9389
9417
  const undo = {
@@ -16318,17 +16346,13 @@ for (let category of categories) {
16318
16346
 
16319
16347
  const insertRow = {
16320
16348
  name: MENU_INSERT_ROWS_NAME,
16321
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16322
- IS_ONLY_ONE_RANGE(env) &&
16323
- env.model.getters.getActiveCols().size === 0,
16349
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
16324
16350
  icon: "o-spreadsheet-Icon.INSERT_ROW",
16325
16351
  };
16326
16352
  const rowInsertRowBefore = {
16327
16353
  name: ROW_INSERT_ROWS_BEFORE_NAME,
16328
16354
  execute: INSERT_ROWS_BEFORE_ACTION,
16329
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16330
- IS_ONLY_ONE_RANGE(env) &&
16331
- env.model.getters.getActiveCols().size === 0,
16355
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
16332
16356
  icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16333
16357
  };
16334
16358
  const topBarInsertRowsBefore = {
@@ -16344,9 +16368,7 @@ const cellInsertRowsBefore = {
16344
16368
  const rowInsertRowsAfter = {
16345
16369
  execute: INSERT_ROWS_AFTER_ACTION,
16346
16370
  name: ROW_INSERT_ROWS_AFTER_NAME,
16347
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16348
- IS_ONLY_ONE_RANGE(env) &&
16349
- env.model.getters.getActiveCols().size === 0,
16371
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
16350
16372
  icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER",
16351
16373
  };
16352
16374
  const topBarInsertRowsAfter = {
@@ -16355,17 +16377,13 @@ const topBarInsertRowsAfter = {
16355
16377
  };
16356
16378
  const insertCol = {
16357
16379
  name: MENU_INSERT_COLUMNS_NAME,
16358
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16359
- IS_ONLY_ONE_RANGE(env) &&
16360
- env.model.getters.getActiveRows().size === 0,
16380
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
16361
16381
  icon: "o-spreadsheet-Icon.INSERT_COL",
16362
16382
  };
16363
16383
  const colInsertColsBefore = {
16364
16384
  name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,
16365
16385
  execute: INSERT_COLUMNS_BEFORE_ACTION,
16366
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16367
- IS_ONLY_ONE_RANGE(env) &&
16368
- env.model.getters.getActiveRows().size === 0,
16386
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
16369
16387
  icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16370
16388
  };
16371
16389
  const topBarInsertColsBefore = {
@@ -16381,9 +16399,7 @@ const cellInsertColsBefore = {
16381
16399
  const colInsertColsAfter = {
16382
16400
  name: COLUMN_INSERT_COLUMNS_AFTER_NAME,
16383
16401
  execute: INSERT_COLUMNS_AFTER_ACTION,
16384
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16385
- IS_ONLY_ONE_RANGE(env) &&
16386
- env.model.getters.getActiveRows().size === 0,
16402
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
16387
16403
  icon: "o-spreadsheet-Icon.INSERT_COL_AFTER",
16388
16404
  };
16389
16405
  const topBarInsertColsAfter = {
@@ -19710,7 +19726,7 @@ css /* scss */ `
19710
19726
  }
19711
19727
  .o-input-count {
19712
19728
  width: fit-content;
19713
- padding: 4 0 4 4;
19729
+ padding: 4px 0 4px 4px;
19714
19730
  }
19715
19731
  }
19716
19732
  }
@@ -28992,6 +29008,14 @@ class BordersPlugin extends CorePlugin {
28992
29008
  // ---------------------------------------------------------------------------
28993
29009
  // Command Handling
28994
29010
  // ---------------------------------------------------------------------------
29011
+ allowDispatch(cmd) {
29012
+ switch (cmd.type) {
29013
+ case "SET_BORDER":
29014
+ return this.checkBordersUnchanged(cmd);
29015
+ default:
29016
+ return 0 /* CommandResult.Success */;
29017
+ }
29018
+ }
28995
29019
  handle(cmd) {
28996
29020
  switch (cmd.type) {
28997
29021
  case "ADD_MERGE":
@@ -29411,6 +29435,14 @@ class BordersPlugin extends CorePlugin {
29411
29435
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29412
29436
  }
29413
29437
  }
29438
+ checkBordersUnchanged(cmd) {
29439
+ const currentBorder = this.getCellBorder(cmd);
29440
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
29441
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
29442
+ return 93 /* CommandResult.NoChanges */;
29443
+ }
29444
+ return 0 /* CommandResult.Success */;
29445
+ }
29414
29446
  // ---------------------------------------------------------------------------
29415
29447
  // Import/Export
29416
29448
  // ---------------------------------------------------------------------------
@@ -29518,8 +29550,9 @@ class CellPlugin extends CorePlugin {
29518
29550
  allowDispatch(cmd) {
29519
29551
  switch (cmd.type) {
29520
29552
  case "UPDATE_CELL":
29553
+ return this.checkCellOutOfSheet(cmd);
29521
29554
  case "CLEAR_CELL":
29522
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
29555
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
29523
29556
  default:
29524
29557
  return 0 /* CommandResult.Success */;
29525
29558
  }
@@ -29678,14 +29711,9 @@ class CellPlugin extends CorePlugin {
29678
29711
  */
29679
29712
  getCellById(cellId) {
29680
29713
  // this must be as fast as possible
29681
- for (const sheetId in this.cells) {
29682
- const sheet = this.cells[sheetId];
29683
- const cell = sheet[cellId];
29684
- if (cell) {
29685
- return cell;
29686
- }
29687
- }
29688
- return undefined;
29714
+ const position = this.getters.getCellPosition(cellId);
29715
+ const sheet = this.cells[position.sheetId];
29716
+ return sheet[cellId];
29689
29717
  }
29690
29718
  /*
29691
29719
  * Reconstructs the original formula string based on a normalized form and its dependencies
@@ -29922,13 +29950,23 @@ class CellPlugin extends CorePlugin {
29922
29950
  dependencies: [],
29923
29951
  };
29924
29952
  }
29925
- checkCellOutOfSheet(sheetId, col, row) {
29953
+ checkCellOutOfSheet(cmd) {
29954
+ const { sheetId, col, row } = cmd;
29926
29955
  const sheet = this.getters.tryGetSheet(sheetId);
29927
29956
  if (!sheet)
29928
29957
  return 27 /* CommandResult.InvalidSheetId */;
29929
29958
  const sheetZone = this.getters.getSheetZone(sheetId);
29930
29959
  return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;
29931
29960
  }
29961
+ checkUselessClearCell(cmd) {
29962
+ const cell = this.getters.getCell(cmd);
29963
+ if (!cell)
29964
+ return 93 /* CommandResult.NoChanges */;
29965
+ if (!cell.content && !cell.style && !cell.format) {
29966
+ return 93 /* CommandResult.NoChanges */;
29967
+ }
29968
+ return 0 /* CommandResult.Success */;
29969
+ }
29932
29970
  }
29933
29971
 
29934
29972
  class ChartPlugin extends CorePlugin {
@@ -30844,6 +30882,13 @@ class FiltersPlugin extends CorePlugin {
30844
30882
  }
30845
30883
  onDeleteColumnsRows(cmd) {
30846
30884
  for (const table of this.getFilterTables(cmd.sheetId)) {
30885
+ // Remove the filter tables whose data filter headers are in the removed rows.
30886
+ if (cmd.dimension === "ROW" && cmd.elements.includes(table.zone.top)) {
30887
+ const tables = { ...this.tables[cmd.sheetId] };
30888
+ delete tables[table.id];
30889
+ this.history.update("tables", cmd.sheetId, tables);
30890
+ continue;
30891
+ }
30847
30892
  const zone = reduceZoneOnDeletion(table.zone, cmd.dimension === "COL" ? "left" : "top", cmd.elements);
30848
30893
  if (!zone) {
30849
30894
  const tables = { ...this.tables[cmd.sheetId] };
@@ -33024,10 +33069,13 @@ class SheetPlugin extends CorePlugin {
33024
33069
  // begin with the end.
33025
33070
  rows.sort((a, b) => b - a);
33026
33071
  for (let group of groupConsecutive(rows)) {
33072
+ // indexes are sorted in the descending order
33073
+ const from = group[group.length - 1];
33074
+ const to = group[0];
33027
33075
  // Move the cells.
33028
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
33029
- // Effectively delete the element and recompute the left-right/top-bottom.
33030
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
33076
+ this.moveCellOnRowsDeletion(sheet, from, to);
33077
+ // Effectively delete the rows
33078
+ this.updateRowsStructureOnDeletion(sheet, from, to);
33031
33079
  }
33032
33080
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
33033
33081
  if (count) {
@@ -33049,8 +33097,6 @@ class SheetPlugin extends CorePlugin {
33049
33097
  this.addEmptyRows(sheet, quantity);
33050
33098
  // Move the cells.
33051
33099
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
33052
- // Recompute the left-right/top-bottom.
33053
- this.updateRowsStructureOnAddition(sheet, row, quantity);
33054
33100
  if (index < sheet.panes.ySplit) {
33055
33101
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
33056
33102
  }
@@ -33151,35 +33197,20 @@ class SheetPlugin extends CorePlugin {
33151
33197
  }
33152
33198
  }
33153
33199
  }
33154
- updateRowsStructureOnDeletion(index, sheet) {
33200
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
33155
33201
  const rows = [];
33156
- const cellsQueue = sheet.rows.map((row) => row.cells);
33202
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
33157
33203
  for (let i in sheet.rows) {
33158
- if (Number(i) === index) {
33204
+ const row = Number(i);
33205
+ if (row >= deleteFromRow && row <= deleteToRow) {
33159
33206
  continue;
33160
33207
  }
33161
33208
  rows.push({
33162
- cells: cellsQueue.shift(),
33209
+ cells: cellsQueue.pop(),
33163
33210
  });
33164
33211
  }
33165
33212
  this.history.update("sheets", sheet.id, "rows", rows);
33166
33213
  }
33167
- /**
33168
- * Update the rows of the sheet after an addition:
33169
- * - Rename the rows
33170
- *
33171
- * @param sheet Sheet on which the deletion occurs
33172
- * @param addedRow Index of the added row
33173
- * @param rowsToAdd Number of the rows to add
33174
- */
33175
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
33176
- const rows = [];
33177
- const cellsQueue = sheet.rows.map((row) => row.cells);
33178
- sheet.rows.forEach(() => rows.push({
33179
- cells: cellsQueue.shift(),
33180
- }));
33181
- this.history.update("sheets", sheet.id, "rows", rows);
33182
- }
33183
33214
  /**
33184
33215
  * Add empty rows at the end of the rows
33185
33216
  *
@@ -33630,6 +33661,7 @@ class EvaluationPlugin extends UIPlugin {
33630
33661
  }
33631
33662
  return evaluatedCell;
33632
33663
  };
33664
+ const rangeCache = {};
33633
33665
  /**
33634
33666
  * Return the values of the cell(s) used in reference, but always in the format of a range even
33635
33667
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -33646,21 +33678,26 @@ class EvaluationPlugin extends UIPlugin {
33646
33678
  // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
33647
33679
  // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
33648
33680
  const sheetZone = getters.getSheetZone(sheetId);
33649
- const result = [];
33650
33681
  const zone = intersection(range.zone, sheetZone);
33651
33682
  if (!zone) {
33652
- result.push([]);
33653
- return result;
33683
+ return [[]];
33684
+ }
33685
+ const { top, left, bottom, right } = zone;
33686
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
33687
+ if (cacheKey in rangeCache) {
33688
+ return rangeCache[cacheKey];
33654
33689
  }
33690
+ const result = new Array(right - left + 1);
33655
33691
  // Performance issue: nested loop is faster than a map here
33656
- for (let col = zone.left; col <= zone.right; col++) {
33657
- const rowValues = [];
33658
- for (let row = zone.top; row <= zone.bottom; row++) {
33692
+ for (let col = left; col <= right; col++) {
33693
+ const rowValues = new Array(bottom - top + 1);
33694
+ for (let row = top; row <= bottom; row++) {
33659
33695
  const cell = evalContext.getters.getCell({ sheetId: range.sheetId, col, row });
33660
- rowValues.push(cell ? getEvaluatedCell(cell) : undefined);
33696
+ rowValues[row - top] = cell ? getEvaluatedCell(cell) : undefined;
33661
33697
  }
33662
- result.push(rowValues);
33698
+ result[col - left] = rowValues;
33663
33699
  }
33700
+ rangeCache[cacheKey] = result;
33664
33701
  return result;
33665
33702
  }
33666
33703
  /**
@@ -34192,6 +34229,8 @@ class FilterEvaluationPlugin extends UIPlugin {
34192
34229
  case "EVALUATE_CELLS":
34193
34230
  case "ACTIVATE_SHEET":
34194
34231
  case "REMOVE_FILTER_TABLE":
34232
+ case "ADD_COLUMNS_ROWS":
34233
+ case "REMOVE_COLUMNS_ROWS":
34195
34234
  this.isEvaluationDirty = true;
34196
34235
  break;
34197
34236
  case "START":
@@ -44930,9 +44969,9 @@ function revertChanges(revisions) {
44930
44969
  * Apply the changes of the given HistoryChange to the state
44931
44970
  */
44932
44971
  function applyChange(change, target) {
44933
- let val = change.root;
44934
- let key = change.path[change.path.length - 1];
44935
- for (let pathIndex = 0; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
44972
+ let val = change.path[0];
44973
+ const key = change.path[change.path.length - 1];
44974
+ for (let pathIndex = 1; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
44936
44975
  const p = change.path[pathIndex];
44937
44976
  if (val[p] === undefined) {
44938
44977
  const nextPath = change.path[pathIndex + 1];
@@ -45599,13 +45638,14 @@ class StateObserver {
45599
45638
  }
45600
45639
  addChange(...args) {
45601
45640
  const val = args.pop();
45602
- const [root, ...path] = args;
45641
+ const root = args[0];
45603
45642
  let value = root;
45604
- let key = path[path.length - 1];
45605
- for (let pathIndex = 0; pathIndex <= path.length - 2; pathIndex++) {
45606
- const p = path[pathIndex];
45643
+ let key = args[args.length - 1];
45644
+ const pathLength = args.length - 2;
45645
+ for (let pathIndex = 1; pathIndex <= pathLength; pathIndex++) {
45646
+ const p = args[pathIndex];
45607
45647
  if (value[p] === undefined) {
45608
- const nextPath = path[pathIndex + 1];
45648
+ const nextPath = args[pathIndex + 1];
45609
45649
  value[p] = createEmptyStructure(nextPath);
45610
45650
  }
45611
45651
  value = value[p];
@@ -45614,8 +45654,7 @@ class StateObserver {
45614
45654
  return;
45615
45655
  }
45616
45656
  this.changes.push({
45617
- root,
45618
- path,
45657
+ path: args,
45619
45658
  before: value[key],
45620
45659
  after: val,
45621
45660
  });
@@ -47465,19 +47504,22 @@ class Model extends EventBus {
47465
47504
  * Check if the given command is allowed by all the plugins and the history.
47466
47505
  */
47467
47506
  checkDispatchAllowed(command) {
47468
- if (isCoreCommand(command)) {
47469
- return this.checkDispatchAllowedCoreCommand(command);
47507
+ const results = isCoreCommand(command)
47508
+ ? this.checkDispatchAllowedCoreCommand(command)
47509
+ : this.checkDispatchAllowedLocalCommand(command);
47510
+ if (results.some((r) => r !== 0 /* CommandResult.Success */)) {
47511
+ return new DispatchResult(results.flat());
47470
47512
  }
47471
- return this.checkDispatchAllowedLocalCommand(command);
47513
+ return DispatchResult.Success;
47472
47514
  }
47473
47515
  checkDispatchAllowedCoreCommand(command) {
47474
47516
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
47475
47517
  results.push(this.range.allowDispatch(command));
47476
- return new DispatchResult(results.flat());
47518
+ return results;
47477
47519
  }
47478
47520
  checkDispatchAllowedLocalCommand(command) {
47479
47521
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
47480
- return new DispatchResult(results.flat());
47522
+ return results;
47481
47523
  }
47482
47524
  finalize() {
47483
47525
  this.status = 3 /* Status.Finalizing */;
@@ -47765,6 +47807,6 @@ function addFunction(functionName, functionDescription) {
47765
47807
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
47766
47808
 
47767
47809
 
47768
- __info__.version = '16.3.16';
47769
- __info__.date = '2023-11-02T09:32:13.671Z';
47770
- __info__.hash = '09607eb';
47810
+ __info__.version = '16.3.18';
47811
+ __info__.date = '2023-11-24T13:10:15.278Z';
47812
+ __info__.hash = '45f745e';