@odoo/o-spreadsheet 16.4.14 → 16.4.16

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.
@@ -301,13 +301,6 @@ const NEWLINE = "\n";
301
301
  const FONT_SIZES = [6, 7, 8, 9, 10, 11, 12, 14, 18, 24, 36];
302
302
 
303
303
  //------------------------------------------------------------------------------
304
- /**
305
- * Stringify an object, like JSON.stringify, except that the first level of keys
306
- * is ordered.
307
- */
308
- function stringify(obj) {
309
- return JSON.stringify(obj, Object.keys(obj).sort());
310
- }
311
304
  /**
312
305
  * Remove quotes from a quoted string
313
306
  * ```js
@@ -527,7 +520,7 @@ function isObjectEmptyRecursive(argument) {
527
520
  */
528
521
  function getItemId(item, itemsDic) {
529
522
  for (let [key, value] of Object.entries(itemsDic)) {
530
- if (stringify(value) === stringify(item)) {
523
+ if (deepEquals(value, item)) {
531
524
  return parseInt(key, 10);
532
525
  }
533
526
  }
@@ -672,8 +665,7 @@ function transpose2dArray(matrix, callback = (val) => val) {
672
665
  *
673
666
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
674
667
  */
675
- const whiteSpaceCharacters = [
676
- " ",
668
+ const whiteSpaceSpecialCharacters = [
677
669
  "\t",
678
670
  "\f",
679
671
  "\v",
@@ -688,8 +680,7 @@ const whiteSpaceCharacters = [
688
680
  String.fromCharCode(parseInt("3000", 16)),
689
681
  String.fromCharCode(parseInt("feff", 16)),
690
682
  ];
691
- const whiteSpaceRegexp = new RegExp(whiteSpaceCharacters.join("|"), "g");
692
- const newLineRegex = /\r\n|\r|\n/g;
683
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
693
684
  /**
694
685
  * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
695
686
  * different newlines types by \n.
@@ -697,9 +688,9 @@ const newLineRegex = /\r\n|\r|\n/g;
697
688
  function replaceSpecialSpaces(text) {
698
689
  if (!text)
699
690
  return "";
700
- text = text.replace(whiteSpaceRegexp, " ");
701
- text = text.replace(newLineRegex, NEWLINE);
702
- return text;
691
+ if (!whiteSpaceRegexp.test(text))
692
+ return text;
693
+ return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
703
694
  }
704
695
  /**
705
696
  * Determine if the numbers are consecutive.
@@ -2347,8 +2338,10 @@ function convertInternalFormatToFormat(internalFormat) {
2347
2338
  const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
2348
2339
  // Same as above, but matches the exact string (nothing before or after)
2349
2340
  const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
2350
- /** Reference of a column header (eg. A, AB) */
2351
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
2341
+ /** Reference of a column header (eg. A, AB, $A) */
2342
+ const colHeader = new RegExp(/^\$?([A-Z]{1,3})+$/, "i");
2343
+ /** Reference of a row header (eg. 1, $1) */
2344
+ const rowHeader = new RegExp(/^\$?([0-9]{1,7})+$/, "i");
2352
2345
  /** Reference of a column (eg. A, $CA, Sheet1!B) */
2353
2346
  const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
2354
2347
  /** Reference of a row (eg. 1, 59, Sheet1!9) */
@@ -2378,6 +2371,9 @@ function isRowReference(xc) {
2378
2371
  function isColHeader(str) {
2379
2372
  return colHeader.test(str);
2380
2373
  }
2374
+ function isRowHeader(str) {
2375
+ return rowHeader.test(str);
2376
+ }
2381
2377
  /**
2382
2378
  * Return true if the given xc is the reference of a single cell,
2383
2379
  * without any specified sheet (e.g. A1)
@@ -3050,18 +3046,10 @@ class RangeImpl {
3050
3046
  if (isFullCol) {
3051
3047
  parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3052
3048
  parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3053
- if (zone.left === zone.right) {
3054
- parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
3055
- parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
3056
- }
3057
3049
  }
3058
3050
  if (isFullRow) {
3059
3051
  parts[0].colFixed = parts[0].colFixed || parts[1].colFixed;
3060
3052
  parts[1].colFixed = parts[0].colFixed || parts[1].colFixed;
3061
- if (zone.top === zone.bottom) {
3062
- parts[0].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3063
- parts[1].rowFixed = parts[0].rowFixed || parts[1].rowFixed;
3064
- }
3065
3053
  }
3066
3054
  return parts;
3067
3055
  }
@@ -4269,6 +4257,7 @@ exports.CommandResult = void 0;
4269
4257
  CommandResult[CommandResult["NoActiveSheet"] = 94] = "NoActiveSheet";
4270
4258
  CommandResult[CommandResult["InvalidLocale"] = 95] = "InvalidLocale";
4271
4259
  CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 96] = "AlreadyInPaintingFormatMode";
4260
+ CommandResult[CommandResult["NoChanges"] = 97] = "NoChanges";
4272
4261
  })(exports.CommandResult || (exports.CommandResult = {}));
4273
4262
 
4274
4263
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -4358,12 +4347,12 @@ function toString(value) {
4358
4347
  }
4359
4348
  }
4360
4349
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
4361
- function normalizeString(str) {
4350
+ const normalizeString = memoize(function normalizeString(str) {
4362
4351
  return str
4363
4352
  .toLowerCase()
4364
4353
  .normalize("NFD")
4365
4354
  .replace(/[\u0300-\u036f]/g, "");
4366
- }
4355
+ });
4367
4356
  /**
4368
4357
  * Normalize a value.
4369
4358
  * If the cell value is a string, this will set it to lowercase and replacing accent letters with plain letters
@@ -5649,6 +5638,9 @@ function* iterateChildren(el) {
5649
5638
  }
5650
5639
  function getOpenedMenus() {
5651
5640
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
5641
+ }
5642
+ function isMacOS() {
5643
+ return navigator.userAgent.toUpperCase().indexOf("MAC") >= 0;
5652
5644
  }
5653
5645
 
5654
5646
  /**
@@ -14174,7 +14166,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
14174
14166
  return result;
14175
14167
  }
14176
14168
  function tokenizeDebugger(chars) {
14177
- if (chars.current() === "?") {
14169
+ if (chars.current === "?") {
14178
14170
  chars.shift();
14179
14171
  return { type: "DEBUGGER", value: "?" };
14180
14172
  }
@@ -14185,7 +14177,7 @@ const misc = {
14185
14177
  ")": "RIGHT_PAREN",
14186
14178
  };
14187
14179
  function tokenizeMisc(chars) {
14188
- if (chars.current() in misc) {
14180
+ if (chars.current in misc) {
14189
14181
  const value = chars.shift();
14190
14182
  const type = misc[value];
14191
14183
  return { type, value };
@@ -14193,7 +14185,7 @@ function tokenizeMisc(chars) {
14193
14185
  return null;
14194
14186
  }
14195
14187
  function tokenizeArgsSeparator(chars, locale) {
14196
- if (chars.current() === locale.formulaArgSeparator) {
14188
+ if (chars.current === locale.formulaArgSeparator) {
14197
14189
  const value = chars.shift();
14198
14190
  const type = "ARG_SEPARATOR";
14199
14191
  return { type, value };
@@ -14218,14 +14210,13 @@ function tokenizeNumber(chars, locale) {
14218
14210
  return null;
14219
14211
  }
14220
14212
  function tokenizeString(chars) {
14221
- if (chars.current() === '"') {
14213
+ if (chars.current === '"') {
14222
14214
  const startChar = chars.shift();
14223
14215
  let letters = startChar;
14224
- while (chars.current() &&
14225
- (chars.current() !== startChar || letters[letters.length - 1] === "\\")) {
14216
+ while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
14226
14217
  letters += chars.shift();
14227
14218
  }
14228
- if (chars.current() === '"') {
14219
+ if (chars.current === '"') {
14229
14220
  letters += chars.shift();
14230
14221
  }
14231
14222
  return {
@@ -14252,14 +14243,14 @@ function tokenizeSymbol(chars) {
14252
14243
  let result = "";
14253
14244
  // there are two main cases to manage: either something which starts with
14254
14245
  // a ', like 'Sheet 2'A2, or a word-like element.
14255
- if (chars.current() === "'") {
14246
+ if (chars.current === "'") {
14256
14247
  let lastChar = chars.shift();
14257
14248
  result += lastChar;
14258
- while (chars.current()) {
14249
+ while (chars.current) {
14259
14250
  lastChar = chars.shift();
14260
14251
  result += lastChar;
14261
14252
  if (lastChar === "'") {
14262
- if (chars.current() && chars.current() === "'") {
14253
+ if (chars.current && chars.current === "'") {
14263
14254
  lastChar = chars.shift();
14264
14255
  result += lastChar;
14265
14256
  }
@@ -14275,7 +14266,7 @@ function tokenizeSymbol(chars) {
14275
14266
  };
14276
14267
  }
14277
14268
  }
14278
- while (chars.current() && separatorRegexp.test(chars.current())) {
14269
+ while (chars.current && separatorRegexp.test(chars.current)) {
14279
14270
  result += chars.shift();
14280
14271
  }
14281
14272
  if (result.length) {
@@ -14290,14 +14281,14 @@ function tokenizeSymbol(chars) {
14290
14281
  }
14291
14282
  function tokenizeSpace(chars) {
14292
14283
  let length = 0;
14293
- while (chars.current() === NEWLINE) {
14284
+ while (chars.current === NEWLINE) {
14294
14285
  length++;
14295
14286
  chars.shift();
14296
14287
  }
14297
14288
  if (length) {
14298
14289
  return { type: "SPACE", value: NEWLINE.repeat(length) };
14299
14290
  }
14300
- while (chars.current() === " ") {
14291
+ while (chars.current === " ") {
14301
14292
  length++;
14302
14293
  chars.shift();
14303
14294
  }
@@ -14316,17 +14307,20 @@ function tokenizeInvalidRange(chars) {
14316
14307
  class TokenizingChars {
14317
14308
  text;
14318
14309
  currentIndex = 0;
14310
+ current;
14319
14311
  constructor(text) {
14320
14312
  this.text = text;
14321
- }
14322
- current() {
14323
- return this.text[this.currentIndex];
14313
+ this.current = text[0];
14324
14314
  }
14325
14315
  shift() {
14326
- return this.text[this.currentIndex++];
14316
+ const current = this.current;
14317
+ const next = this.text[++this.currentIndex];
14318
+ this.current = next;
14319
+ return current;
14327
14320
  }
14328
14321
  advanceBy(length) {
14329
14322
  this.currentIndex += length;
14323
+ this.current = this.text[this.currentIndex];
14330
14324
  }
14331
14325
  isOver() {
14332
14326
  return this.currentIndex >= this.text.length;
@@ -14703,7 +14697,7 @@ const machine = {
14703
14697
  SPACE: goTo(State.RightRef),
14704
14698
  NUMBER: goTo(State.Found),
14705
14699
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
14706
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
14700
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
14707
14701
  },
14708
14702
  [State.RightColumnRef]: {
14709
14703
  SPACE: goTo(State.RightColumnRef),
@@ -14714,6 +14708,7 @@ const machine = {
14714
14708
  SPACE: goTo(State.RightRowRef),
14715
14709
  NUMBER: goTo(State.Found),
14716
14710
  REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
14711
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
14717
14712
  },
14718
14713
  [State.Found]: {},
14719
14714
  };
@@ -16871,7 +16866,7 @@ function createGaugeChartRuntime(chart, getters) {
16871
16866
  });
16872
16867
  return {
16873
16868
  chartJsConfig: config,
16874
- background: getters.getBackgroundOfSingleCellChart(chart.background, dataRange),
16869
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
16875
16870
  };
16876
16871
  }
16877
16872
 
@@ -17595,7 +17590,7 @@ function createScorecardChartRuntime(chart, getters) {
17595
17590
  };
17596
17591
  baselineCell = getters.getEvaluatedCell(baselinePosition);
17597
17592
  }
17598
- const background = getters.getBackgroundOfSingleCellChart(chart.background, chart.keyValue);
17593
+ const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
17599
17594
  const locale = getters.getLocale();
17600
17595
  return {
17601
17596
  title: _t(chart.title),
@@ -17604,7 +17599,7 @@ function createScorecardChartRuntime(chart, getters) {
17604
17599
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
17605
17600
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
17606
17601
  baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
17607
- fontColor: chartFontColor(background),
17602
+ fontColor,
17608
17603
  background,
17609
17604
  baselineStyle: chart.baselineMode !== "percentage" && baseline
17610
17605
  ? getters.getCellStyle({
@@ -25620,6 +25615,7 @@ class AbstractResizer extends owl.Component {
25620
25615
  draggerShadowThickness: 0,
25621
25616
  delta: 0,
25622
25617
  base: 0,
25618
+ position: "before",
25623
25619
  });
25624
25620
  _computeHandleDisplay(ev) {
25625
25621
  const position = this._getEvOffset(ev);
@@ -25744,11 +25740,13 @@ class AbstractResizer extends owl.Component {
25744
25740
  this.state.draggerLinePosition = dimensions.start;
25745
25741
  this.state.draggerShadowPosition = dimensions.start;
25746
25742
  this.state.base = elementIndex;
25743
+ this.state.position = "before";
25747
25744
  }
25748
25745
  else if (this._getSelectedZoneEnd() < elementIndex) {
25749
25746
  this.state.draggerLinePosition = dimensions.end;
25750
25747
  this.state.draggerShadowPosition = dimensions.end - this.state.draggerShadowThickness;
25751
- this.state.base = elementIndex + 1;
25748
+ this.state.base = elementIndex;
25749
+ this.state.position = "after";
25752
25750
  }
25753
25751
  else {
25754
25752
  this.state.draggerLinePosition = startDimensions.start;
@@ -25931,6 +25929,7 @@ class ColResizer extends AbstractResizer {
25931
25929
  dimension: "COL",
25932
25930
  base: this.state.base,
25933
25931
  elements,
25932
+ position: this.state.position,
25934
25933
  });
25935
25934
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
25936
25935
  this.env.raiseError(MergeErrorMessage);
@@ -26104,6 +26103,7 @@ class RowResizer extends AbstractResizer {
26104
26103
  dimension: "ROW",
26105
26104
  base: this.state.base,
26106
26105
  elements,
26106
+ position: this.state.position,
26107
26107
  });
26108
26108
  if (!result.isSuccessful && result.reasons.includes(2 /* CommandResult.WillRemoveExistingMerge */)) {
26109
26109
  this.env.raiseError(MergeErrorMessage);
@@ -26211,8 +26211,8 @@ function useWheelHandler(handler) {
26211
26211
  return val * (deltaMode === 0 ? 1 : DEFAULT_CELL_HEIGHT);
26212
26212
  }
26213
26213
  const onMouseWheel = (ev) => {
26214
- const deltaX = normalize(ev.shiftKey ? ev.deltaY : ev.deltaX, ev.deltaMode);
26215
- const deltaY = normalize(ev.shiftKey ? ev.deltaX : ev.deltaY, ev.deltaMode);
26214
+ const deltaX = normalize(ev.shiftKey && !isMacOS() ? ev.deltaY : ev.deltaX, ev.deltaMode);
26215
+ const deltaY = normalize(ev.shiftKey && !isMacOS() ? ev.deltaX : ev.deltaY, ev.deltaMode);
26216
26216
  handler(deltaX, deltaY);
26217
26217
  };
26218
26218
  return onMouseWheel;
@@ -31170,6 +31170,14 @@ class BordersPlugin extends CorePlugin {
31170
31170
  // ---------------------------------------------------------------------------
31171
31171
  // Command Handling
31172
31172
  // ---------------------------------------------------------------------------
31173
+ allowDispatch(cmd) {
31174
+ switch (cmd.type) {
31175
+ case "SET_BORDER":
31176
+ return this.checkBordersUnchanged(cmd);
31177
+ default:
31178
+ return 0 /* CommandResult.Success */;
31179
+ }
31180
+ }
31173
31181
  handle(cmd) {
31174
31182
  switch (cmd.type) {
31175
31183
  case "ADD_MERGE":
@@ -31589,6 +31597,14 @@ class BordersPlugin extends CorePlugin {
31589
31597
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
31590
31598
  }
31591
31599
  }
31600
+ checkBordersUnchanged(cmd) {
31601
+ const currentBorder = this.getCellBorder(cmd);
31602
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
31603
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
31604
+ return 97 /* CommandResult.NoChanges */;
31605
+ }
31606
+ return 0 /* CommandResult.Success */;
31607
+ }
31592
31608
  // ---------------------------------------------------------------------------
31593
31609
  // Import/Export
31594
31610
  // ---------------------------------------------------------------------------
@@ -31625,7 +31641,7 @@ class BordersPlugin extends CorePlugin {
31625
31641
  */
31626
31642
  function getBorderId(border) {
31627
31643
  for (let [key, value] of Object.entries(borders)) {
31628
- if (stringify(value) === stringify(border)) {
31644
+ if (deepEquals(value, border)) {
31629
31645
  return parseInt(key, 10);
31630
31646
  }
31631
31647
  }
@@ -31696,8 +31712,9 @@ class CellPlugin extends CorePlugin {
31696
31712
  allowDispatch(cmd) {
31697
31713
  switch (cmd.type) {
31698
31714
  case "UPDATE_CELL":
31715
+ return this.checkCellOutOfSheet(cmd);
31699
31716
  case "CLEAR_CELL":
31700
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
31717
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
31701
31718
  default:
31702
31719
  return 0 /* CommandResult.Success */;
31703
31720
  }
@@ -31856,14 +31873,9 @@ class CellPlugin extends CorePlugin {
31856
31873
  */
31857
31874
  getCellById(cellId) {
31858
31875
  // this must be as fast as possible
31859
- for (const sheetId in this.cells) {
31860
- const sheet = this.cells[sheetId];
31861
- const cell = sheet[cellId];
31862
- if (cell) {
31863
- return cell;
31864
- }
31865
- }
31866
- return undefined;
31876
+ const position = this.getters.getCellPosition(cellId);
31877
+ const sheet = this.cells[position.sheetId];
31878
+ return sheet[cellId];
31867
31879
  }
31868
31880
  /*
31869
31881
  * Reconstructs the original formula string based on a normalized form and its dependencies
@@ -32089,13 +32101,23 @@ class CellPlugin extends CorePlugin {
32089
32101
  dependencies: [],
32090
32102
  };
32091
32103
  }
32092
- checkCellOutOfSheet(sheetId, col, row) {
32104
+ checkCellOutOfSheet(cmd) {
32105
+ const { sheetId, col, row } = cmd;
32093
32106
  const sheet = this.getters.tryGetSheet(sheetId);
32094
32107
  if (!sheet)
32095
32108
  return 28 /* CommandResult.InvalidSheetId */;
32096
32109
  const sheetZone = this.getters.getSheetZone(sheetId);
32097
32110
  return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 19 /* CommandResult.TargetOutOfSheet */;
32098
32111
  }
32112
+ checkUselessClearCell(cmd) {
32113
+ const cell = this.getters.getCell(cmd);
32114
+ if (!cell)
32115
+ return 97 /* CommandResult.NoChanges */;
32116
+ if (!cell.content && !cell.style && !cell.format) {
32117
+ return 97 /* CommandResult.NoChanges */;
32118
+ }
32119
+ return 0 /* CommandResult.Success */;
32120
+ }
32099
32121
  }
32100
32122
  class FormulaCellWithDependencies {
32101
32123
  id;
@@ -34528,7 +34550,7 @@ class SheetPlugin extends CorePlugin {
34528
34550
  const elements = cmd.dimension === "COL"
34529
34551
  ? this.getNumberCols(cmd.sheetId)
34530
34552
  : this.getNumberRows(cmd.sheetId);
34531
- if (cmd.base < 0 || cmd.base > elements) {
34553
+ if (cmd.base < 0 || cmd.base >= elements) {
34532
34554
  return 88 /* CommandResult.InvalidHeaderIndex */;
34533
34555
  }
34534
34556
  else if (cmd.quantity <= 0) {
@@ -35164,10 +35186,13 @@ class SheetPlugin extends CorePlugin {
35164
35186
  // begin with the end.
35165
35187
  rows.sort((a, b) => b - a);
35166
35188
  for (let group of groupConsecutive(rows)) {
35189
+ // indexes are sorted in the descending order
35190
+ const from = group[group.length - 1];
35191
+ const to = group[0];
35167
35192
  // Move the cells.
35168
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
35169
- // Effectively delete the element and recompute the left-right/top-bottom.
35170
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
35193
+ this.moveCellOnRowsDeletion(sheet, from, to);
35194
+ // Effectively delete the rows
35195
+ this.updateRowsStructureOnDeletion(sheet, from, to);
35171
35196
  }
35172
35197
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
35173
35198
  if (count) {
@@ -35189,8 +35214,6 @@ class SheetPlugin extends CorePlugin {
35189
35214
  this.addEmptyRows(sheet, quantity);
35190
35215
  // Move the cells.
35191
35216
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
35192
- // Recompute the left-right/top-bottom.
35193
- this.updateRowsStructureOnAddition(sheet, row, quantity);
35194
35217
  if (index < sheet.panes.ySplit) {
35195
35218
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
35196
35219
  }
@@ -35291,35 +35314,20 @@ class SheetPlugin extends CorePlugin {
35291
35314
  }
35292
35315
  }
35293
35316
  }
35294
- updateRowsStructureOnDeletion(index, sheet) {
35317
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
35295
35318
  const rows = [];
35296
- const cellsQueue = sheet.rows.map((row) => row.cells);
35319
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
35297
35320
  for (let i in sheet.rows) {
35298
- if (Number(i) === index) {
35321
+ const row = Number(i);
35322
+ if (row >= deleteFromRow && row <= deleteToRow) {
35299
35323
  continue;
35300
35324
  }
35301
35325
  rows.push({
35302
- cells: cellsQueue.shift(),
35326
+ cells: cellsQueue.pop(),
35303
35327
  });
35304
35328
  }
35305
35329
  this.history.update("sheets", sheet.id, "rows", rows);
35306
35330
  }
35307
- /**
35308
- * Update the rows of the sheet after an addition:
35309
- * - Rename the rows
35310
- *
35311
- * @param sheet Sheet on which the deletion occurs
35312
- * @param addedRow Index of the added row
35313
- * @param rowsToAdd Number of the rows to add
35314
- */
35315
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
35316
- const rows = [];
35317
- const cellsQueue = sheet.rows.map((row) => row.cells);
35318
- sheet.rows.forEach(() => rows.push({
35319
- cells: cellsQueue.shift(),
35320
- }));
35321
- this.history.update("sheets", sheet.id, "rows", rows);
35322
- }
35323
35331
  /**
35324
35332
  * Add empty rows at the end of the rows
35325
35333
  *
@@ -35435,6 +35443,7 @@ class CompilationParametersBuilder {
35435
35443
  getters;
35436
35444
  computeCell;
35437
35445
  evalContext;
35446
+ rangeCache = {};
35438
35447
  constructor(context, getters, computeCell) {
35439
35448
  this.getters = getters;
35440
35449
  this.computeCell = computeCell;
@@ -35482,10 +35491,13 @@ class CompilationParametersBuilder {
35482
35491
  if (evaluatedCell === undefined) {
35483
35492
  return { value: null, format: this.getters.getCell(position)?.format };
35484
35493
  }
35494
+ if (evaluatedCell.type === CellValueType.error) {
35495
+ throw evaluatedCell.error;
35496
+ }
35485
35497
  return evaluatedCell;
35486
35498
  }
35487
35499
  getEvaluatedCellIfNotEmpty(position) {
35488
- const evaluatedCell = this.getEvaluatedCell(position);
35500
+ const evaluatedCell = this.computeCell(position);
35489
35501
  if (evaluatedCell.type === CellValueType.empty) {
35490
35502
  const cell = this.getters.getCell(position);
35491
35503
  if (!cell || (!cell.isFormula && cell.content === "")) {
@@ -35494,13 +35506,6 @@ class CompilationParametersBuilder {
35494
35506
  }
35495
35507
  return evaluatedCell;
35496
35508
  }
35497
- getEvaluatedCell(position) {
35498
- const evaluatedCell = this.computeCell(position);
35499
- if (evaluatedCell.type === CellValueType.error) {
35500
- throw evaluatedCell.error;
35501
- }
35502
- return evaluatedCell;
35503
- }
35504
35509
  /**
35505
35510
  * Return the values of the cell(s) used in reference, but always in the format of a range even
35506
35511
  * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
@@ -35520,25 +35525,40 @@ class CompilationParametersBuilder {
35520
35525
  if (!_zone) {
35521
35526
  return { value: [[]], format: [[]] };
35522
35527
  }
35528
+ const { top, left, bottom, right } = zone;
35529
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
35530
+ if (cacheKey in this.rangeCache) {
35531
+ const result = this.rangeCache[cacheKey];
35532
+ if (result instanceof EvaluationError) {
35533
+ throw result;
35534
+ }
35535
+ return result;
35536
+ }
35523
35537
  const height = _zone.bottom - _zone.top + 1;
35524
35538
  const width = _zone.right - _zone.left + 1;
35525
- const value = Array.from({ length: width }, () => Array.from({ length: height }));
35526
- const format = Array.from({ length: width }, () => Array.from({ length: height }));
35539
+ const value = new Array(width);
35540
+ const format = new Array(width);
35527
35541
  // Performance issue: nested loop is faster than a map here
35528
35542
  for (let col = _zone.left; col <= _zone.right; col++) {
35543
+ const colIndex = col - _zone.left;
35544
+ value[colIndex] = new Array(height);
35545
+ format[colIndex] = new Array(height);
35529
35546
  for (let row = _zone.top; row <= _zone.bottom; row++) {
35530
- const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId: sheetId, col, row });
35531
- if (evaluatedCell) {
35532
- const colIndex = col - _zone.left;
35533
- const rowIndex = row - _zone.top;
35534
- value[colIndex][rowIndex] = evaluatedCell.value;
35535
- if (evaluatedCell.format !== undefined) {
35536
- format[colIndex][rowIndex] = evaluatedCell.format;
35537
- }
35547
+ const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId, col, row });
35548
+ if (evaluatedCell?.type === CellValueType.error) {
35549
+ this.rangeCache[cacheKey] = evaluatedCell.error;
35550
+ throw evaluatedCell.error;
35551
+ }
35552
+ const rowIndex = row - _zone.top;
35553
+ value[colIndex][rowIndex] = evaluatedCell?.value;
35554
+ if (evaluatedCell?.format !== undefined) {
35555
+ format[colIndex][rowIndex] = evaluatedCell.format;
35538
35556
  }
35539
35557
  }
35540
35558
  }
35541
- return { value, format };
35559
+ const result = { value, format };
35560
+ this.rangeCache[cacheKey] = result;
35561
+ return result;
35542
35562
  }
35543
35563
  }
35544
35564
 
@@ -35736,6 +35756,7 @@ class Evaluator {
35736
35756
  this.formulaDependencies().addDependencies(positionId, dependencies);
35737
35757
  }
35738
35758
  updateCompilationParameters() {
35759
+ // rebuild the compilation parameters (with a clean cache)
35739
35760
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
35740
35761
  }
35741
35762
  evaluateCells(positions) {
@@ -35779,6 +35800,15 @@ class Evaluator {
35779
35800
  this.evaluatedCells = new Map();
35780
35801
  this.evaluate(this.getAllCells());
35781
35802
  }
35803
+ evaluateFormula(sheetId, formulaString) {
35804
+ const compiledFormula = compile(formulaString);
35805
+ const ranges = [];
35806
+ for (let xc of compiledFormula.dependencies) {
35807
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
35808
+ }
35809
+ this.updateCompilationParameters();
35810
+ return compiledFormula.execute(ranges, ...this.compilationParams).value;
35811
+ }
35782
35812
  getAllCells() {
35783
35813
  const positionIds = new JetSet();
35784
35814
  for (const sheetId of this.getters.getSheetIds()) {
@@ -35805,6 +35835,7 @@ class Evaluator {
35805
35835
  this.nextPositionsToUpdate = cells;
35806
35836
  let currentIteration = 0;
35807
35837
  while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
35838
+ this.updateCompilationParameters();
35808
35839
  const positionIds = Array.from(this.nextPositionsToUpdate);
35809
35840
  this.nextPositionsToUpdate.clear();
35810
35841
  for (let i = 0; i < positionIds.length; ++i) {
@@ -36189,7 +36220,6 @@ class PositionBitsEncoder {
36189
36220
  // of other cells depending on it, at the next iteration.
36190
36221
  //#endregion
36191
36222
  class EvaluationPlugin extends UIPlugin {
36192
- config;
36193
36223
  static getters = [
36194
36224
  "evaluateFormula",
36195
36225
  "getRangeFormattedValues",
@@ -36201,12 +36231,9 @@ class EvaluationPlugin extends UIPlugin {
36201
36231
  ];
36202
36232
  shouldRebuildDependenciesGraph = true;
36203
36233
  evaluator;
36204
- compilationParams;
36205
36234
  positionsToUpdate = [];
36206
36235
  constructor(config) {
36207
36236
  super(config);
36208
- this.config = config;
36209
- this.compilationParams = this.getCompilationParameters();
36210
36237
  this.evaluator = new Evaluator(config.custom, this.getters);
36211
36238
  }
36212
36239
  // ---------------------------------------------------------------------------
@@ -36231,10 +36258,6 @@ class EvaluationPlugin extends UIPlugin {
36231
36258
  case "EVALUATE_CELLS":
36232
36259
  this.evaluator.evaluateAllCells();
36233
36260
  break;
36234
- case "UPDATE_LOCALE":
36235
- this.compilationParams = this.getCompilationParameters();
36236
- this.evaluator.updateCompilationParameters();
36237
- break;
36238
36261
  }
36239
36262
  }
36240
36263
  finalize() {
@@ -36252,12 +36275,7 @@ class EvaluationPlugin extends UIPlugin {
36252
36275
  // Getters
36253
36276
  // ---------------------------------------------------------------------------
36254
36277
  evaluateFormula(sheetId, formulaString) {
36255
- const compiledFormula = compile(formulaString);
36256
- const ranges = [];
36257
- for (let xc of compiledFormula.dependencies) {
36258
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
36259
- }
36260
- return compiledFormula.execute(ranges, ...this.compilationParams).value;
36278
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
36261
36279
  }
36262
36280
  /**
36263
36281
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -36356,9 +36374,6 @@ class EvaluationPlugin extends UIPlugin {
36356
36374
  }
36357
36375
  return undefined;
36358
36376
  }
36359
- getCompilationParameters() {
36360
- return buildCompilationParameters(this.config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position));
36361
- }
36362
36377
  }
36363
36378
  function isBadExpression(formula) {
36364
36379
  try {
@@ -36523,7 +36538,7 @@ class CustomColorsPlugin extends UIPlugin {
36523
36538
  }
36524
36539
 
36525
36540
  class EvaluationChartPlugin extends UIPlugin {
36526
- static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
36541
+ static getters = ["getChartRuntime", "getStyleOfSingleCellChart"];
36527
36542
  charts = {};
36528
36543
  createRuntimeChart = chartRuntimeFactory(this.getters);
36529
36544
  handle(cmd) {
@@ -36561,25 +36576,26 @@ class EvaluationChartPlugin extends UIPlugin {
36561
36576
  return this.charts[figureId];
36562
36577
  }
36563
36578
  /**
36564
- * Get the background color of a chart based on the color of the first cell of the main range
36565
- * of the chart. In order of priority, it will return :
36566
- *
36567
- * - the chart background color if one is defined
36568
- * - the fill color of the cell if one is defined
36569
- * - the fill color of the cell from conditional formats if one is defined
36570
- * - the default chart color if no other color is defined
36579
+ * Get the background and textColor of a chart based on the color of the first cell of the main range of the chart.
36571
36580
  */
36572
- getBackgroundOfSingleCellChart(chartBackground, mainRange) {
36581
+ getStyleOfSingleCellChart(chartBackground, mainRange) {
36573
36582
  if (chartBackground)
36574
- return chartBackground;
36583
+ return { background: chartBackground, fontColor: chartFontColor(chartBackground) };
36575
36584
  if (!mainRange) {
36576
- return BACKGROUND_CHART_COLOR;
36585
+ return {
36586
+ background: BACKGROUND_CHART_COLOR,
36587
+ fontColor: chartFontColor(BACKGROUND_CHART_COLOR),
36588
+ };
36577
36589
  }
36578
36590
  const col = mainRange.zone.left;
36579
36591
  const row = mainRange.zone.top;
36580
36592
  const sheetId = mainRange.sheetId;
36581
36593
  const style = this.getters.getCellComputedStyle({ sheetId, col, row });
36582
- return style.fillColor || BACKGROUND_CHART_COLOR;
36594
+ const background = style.fillColor || BACKGROUND_CHART_COLOR;
36595
+ return {
36596
+ background,
36597
+ fontColor: style.textColor || chartFontColor(background),
36598
+ };
36583
36599
  }
36584
36600
  }
36585
36601
 
@@ -37278,19 +37294,21 @@ class AutofillPlugin extends UIPlugin {
37278
37294
  let col = zone.left;
37279
37295
  let row = zone.bottom;
37280
37296
  if (col > 0) {
37281
- let left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
37282
- while (left.type !== CellValueType.empty) {
37297
+ let leftPosition = { sheetId, col: col - 1, row };
37298
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
37299
+ this.getters.getCell(leftPosition)?.content) {
37283
37300
  row += 1;
37284
- left = this.getters.getEvaluatedCell({ sheetId, col: col - 1, row });
37301
+ leftPosition = { sheetId, col: col - 1, row };
37285
37302
  }
37286
37303
  }
37287
37304
  if (row === zone.bottom) {
37288
37305
  col = zone.right;
37289
37306
  if (col <= this.getters.getNumberCols(sheetId)) {
37290
- let right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
37291
- while (right.type !== CellValueType.empty) {
37307
+ let rightPosition = { sheetId, col: col + 1, row };
37308
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
37309
+ this.getters.getCell(rightPosition)?.content) {
37292
37310
  row += 1;
37293
- right = this.getters.getEvaluatedCell({ sheetId, col: col + 1, row });
37311
+ rightPosition = { sheetId, col: col + 1, row };
37294
37312
  }
37295
37313
  }
37296
37314
  }
@@ -40208,6 +40226,9 @@ class SelectionInputsManagerPlugin extends UIPlugin {
40208
40226
  // Other
40209
40227
  // ---------------------------------------------------------------------------
40210
40228
  initInput(id, initialRanges, inputHasSingleRange = false) {
40229
+ if (this.inputs[id]) {
40230
+ this.unfocus();
40231
+ }
40211
40232
  this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
40212
40233
  if (initialRanges.length === 0) {
40213
40234
  const input = this.inputs[id];
@@ -42075,8 +42096,8 @@ class ClipboardPlugin extends UIPlugin {
42075
42096
  this.lastPasteState = this.state;
42076
42097
  if (this.paintFormatStatus === "oneOff") {
42077
42098
  this.paintFormatStatus = "inactive";
42078
- this.status = "invisible";
42079
42099
  }
42100
+ this.status = "invisible";
42080
42101
  break;
42081
42102
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
42082
42103
  this.status = "invisible";
@@ -42323,6 +42344,10 @@ function setXcToReferenceType(xc, referenceType) {
42323
42344
  return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
42324
42345
  case "colrow":
42325
42346
  indexOfNumber = xc.search(/[0-9]/);
42347
+ if (indexOfNumber === -1 || indexOfNumber === 0) {
42348
+ // no row number (eg. A) or no column (eg. 1)
42349
+ return "$" + xc;
42350
+ }
42326
42351
  xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
42327
42352
  return "$" + xc;
42328
42353
  case "none":
@@ -43738,7 +43763,7 @@ class GridSelectionPlugin extends UIPlugin {
43738
43763
  sheetId: cmd.sheetId,
43739
43764
  base: cmd.base,
43740
43765
  quantity: thickness,
43741
- position: "before",
43766
+ position: cmd.position,
43742
43767
  });
43743
43768
  const isCol = cmd.dimension === "COL";
43744
43769
  const start = cmd.elements[0];
@@ -43755,12 +43780,13 @@ class GridSelectionPlugin extends UIPlugin {
43755
43780
  },
43756
43781
  ];
43757
43782
  const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
43783
+ const base = isBasedBefore ? cmd.base : cmd.base + 1;
43758
43784
  const pasteTarget = [
43759
43785
  {
43760
- left: isCol ? cmd.base : 0,
43761
- right: isCol ? cmd.base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
43762
- top: !isCol ? cmd.base : 0,
43763
- bottom: !isCol ? cmd.base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
43786
+ left: isCol ? base : 0,
43787
+ right: isCol ? base + thickness - 1 : this.getters.getNumberCols(cmd.sheetId) - 1,
43788
+ top: !isCol ? base : 0,
43789
+ bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
43764
43790
  },
43765
43791
  ];
43766
43792
  state.paste(pasteTarget, { selectTarget: true });
@@ -43797,6 +43823,11 @@ class GridSelectionPlugin extends UIPlugin {
43797
43823
  doesElementsHaveCommonMerges(id, cmd.base - 1, cmd.base)) {
43798
43824
  return 2 /* CommandResult.WillRemoveExistingMerge */;
43799
43825
  }
43826
+ const headers = [cmd.base, ...cmd.elements];
43827
+ const maxHeaderValue = isCol ? this.getters.getNumberCols(id) : this.getters.getNumberRows(id);
43828
+ if (headers.some((h) => h < 0 || h >= maxHeaderValue)) {
43829
+ return 88 /* CommandResult.InvalidHeaderIndex */;
43830
+ }
43800
43831
  return 0 /* CommandResult.Success */;
43801
43832
  }
43802
43833
  //-------------------------------------------
@@ -47893,9 +47924,9 @@ function revertChanges(revisions) {
47893
47924
  * Apply the changes of the given HistoryChange to the state
47894
47925
  */
47895
47926
  function applyChange(change, target) {
47896
- let val = change.root;
47897
- let key = change.path[change.path.length - 1];
47898
- for (let pathIndex = 0; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
47927
+ let val = change.path[0];
47928
+ const key = change.path[change.path.length - 1];
47929
+ for (let pathIndex = 1; pathIndex < change.path.slice(0, -1).length; pathIndex++) {
47899
47930
  const p = change.path[pathIndex];
47900
47931
  if (val[p] === undefined) {
47901
47932
  const nextPath = change.path[pathIndex + 1];
@@ -48576,13 +48607,14 @@ class StateObserver {
48576
48607
  }
48577
48608
  addChange(...args) {
48578
48609
  const val = args.pop();
48579
- const [root, ...path] = args;
48610
+ const root = args[0];
48580
48611
  let value = root;
48581
- let key = path[path.length - 1];
48582
- for (let pathIndex = 0; pathIndex <= path.length - 2; pathIndex++) {
48583
- const p = path[pathIndex];
48612
+ let key = args[args.length - 1];
48613
+ const pathLength = args.length - 2;
48614
+ for (let pathIndex = 1; pathIndex <= pathLength; pathIndex++) {
48615
+ const p = args[pathIndex];
48584
48616
  if (value[p] === undefined) {
48585
- const nextPath = path[pathIndex + 1];
48617
+ const nextPath = args[pathIndex + 1];
48586
48618
  value[p] = createEmptyStructure(nextPath);
48587
48619
  }
48588
48620
  value = value[p];
@@ -48591,8 +48623,7 @@ class StateObserver {
48591
48623
  return;
48592
48624
  }
48593
48625
  this.changes.push({
48594
- root,
48595
- path,
48626
+ path: args,
48596
48627
  before: value[key],
48597
48628
  after: val,
48598
48629
  });
@@ -50442,19 +50473,22 @@ class Model extends EventBus {
50442
50473
  * Check if the given command is allowed by all the plugins and the history.
50443
50474
  */
50444
50475
  checkDispatchAllowed(command) {
50445
- if (isCoreCommand(command)) {
50446
- return this.checkDispatchAllowedCoreCommand(command);
50476
+ const results = isCoreCommand(command)
50477
+ ? this.checkDispatchAllowedCoreCommand(command)
50478
+ : this.checkDispatchAllowedLocalCommand(command);
50479
+ if (results.some((r) => r !== 0 /* CommandResult.Success */)) {
50480
+ return new DispatchResult(results.flat());
50447
50481
  }
50448
- return this.checkDispatchAllowedLocalCommand(command);
50482
+ return DispatchResult.Success;
50449
50483
  }
50450
50484
  checkDispatchAllowedCoreCommand(command) {
50451
50485
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
50452
50486
  results.push(this.range.allowDispatch(command));
50453
- return new DispatchResult(results.flat());
50487
+ return results;
50454
50488
  }
50455
50489
  checkDispatchAllowedLocalCommand(command) {
50456
50490
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
50457
- return new DispatchResult(results.flat());
50491
+ return results;
50458
50492
  }
50459
50493
  finalize() {
50460
50494
  this.status = 3 /* Status.Finalizing */;
@@ -50778,6 +50812,6 @@ exports.setTranslationMethod = setTranslationMethod;
50778
50812
  exports.tokenize = tokenize;
50779
50813
 
50780
50814
 
50781
- __info__.version = '16.4.14';
50782
- __info__.date = '2023-11-16T11:59:51.900Z';
50783
- __info__.hash = '66c6f47';
50815
+ __info__.version = '16.4.16';
50816
+ __info__.date = '2023-12-05T09:37:14.210Z';
50817
+ __info__.hash = '1abbb92';