@odoo/o-spreadsheet 17.1.0-alpha.2 → 17.1.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.1.0-alpha.2
6
- * @date 2023-11-03T12:24:57.341Z
7
- * @hash 0595868
5
+ * @version 17.1.0-alpha.4
6
+ * @date 2023-11-24T13:12:24.882Z
7
+ * @hash 255821b
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -228,7 +228,6 @@
228
228
  const SCROLLBAR_WIDTH = 15;
229
229
  const AUTOFILL_EDGE_LENGTH = 8;
230
230
  const ICON_EDGE_LENGTH = 18;
231
- const UNHIDE_ICON_EDGE_LENGTH = 14;
232
231
  const MIN_CF_ICON_MARGIN = 4;
233
232
  const MIN_CELL_TEXT_MARGIN = 4;
234
233
  const CF_ICON_EDGE_LENGTH = 15;
@@ -692,8 +691,7 @@
692
691
  *
693
692
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
694
693
  */
695
- const whiteSpaceCharacters = [
696
- " ",
694
+ const whiteSpaceSpecialCharacters = [
697
695
  "\t",
698
696
  "\f",
699
697
  "\v",
@@ -708,8 +706,7 @@
708
706
  String.fromCharCode(parseInt("3000", 16)),
709
707
  String.fromCharCode(parseInt("feff", 16)),
710
708
  ];
711
- const whiteSpaceRegexp = new RegExp(whiteSpaceCharacters.join("|"), "g");
712
- const newLineRegex = /\r\n|\r|\n/g;
709
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
713
710
  /**
714
711
  * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
715
712
  * different newlines types by \n.
@@ -717,9 +714,9 @@
717
714
  function replaceSpecialSpaces(text) {
718
715
  if (!text)
719
716
  return "";
720
- text = text.replace(whiteSpaceRegexp, " ");
721
- text = text.replace(newLineRegex, NEWLINE);
722
- return text;
717
+ if (!whiteSpaceRegexp.test(text))
718
+ return text;
719
+ return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
723
720
  }
724
721
  /**
725
722
  * Determine if the numbers are consecutive.
@@ -2061,6 +2058,7 @@
2061
2058
  CommandResult["InvalidNumberOfCriterionValues"] = "InvalidNumberOfCriterionValues";
2062
2059
  CommandResult["BlockingValidationRule"] = "BlockingValidationRule";
2063
2060
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2061
+ CommandResult["NoChanges"] = "NoChanges";
2064
2062
  })(exports.CommandResult || (exports.CommandResult = {}));
2065
2063
 
2066
2064
  const DEFAULT_LOCALES = [
@@ -2190,12 +2188,12 @@
2190
2188
  }
2191
2189
  }
2192
2190
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
2193
- function normalizeString(str) {
2191
+ const normalizeString = memoize(function normalizeString(str) {
2194
2192
  return str
2195
2193
  .toLowerCase()
2196
2194
  .normalize("NFD")
2197
2195
  .replace(/[\u0300-\u036f]/g, "");
2198
- }
2196
+ });
2199
2197
  const expectBooleanValueError = (value) => _t("The function [[FUNCTION_NAME]] expects a boolean value, but '%s' is a text, and cannot be coerced to a number.", value);
2200
2198
  function toBoolean(value) {
2201
2199
  switch (typeof value) {
@@ -3428,11 +3426,18 @@
3428
3426
  return singleCellReference.test(xc);
3429
3427
  }
3430
3428
  function splitReference(ref) {
3429
+ if (!ref.includes("!")) {
3430
+ return { xc: ref };
3431
+ }
3431
3432
  const parts = ref.split("!");
3432
3433
  const xc = parts.pop();
3433
3434
  const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
3434
3435
  return { sheetName, xc };
3435
3436
  }
3437
+ /** Return a reference SheetName!xc from the given arguments */
3438
+ function getFullReference(sheetName, xc) {
3439
+ return sheetName !== undefined ? `${getCanonicalSheetName(sheetName)}!${xc}` : xc;
3440
+ }
3436
3441
 
3437
3442
  /**
3438
3443
  * Convert from a cartesian reference to a Zone
@@ -4541,14 +4546,14 @@
4541
4546
  switch (context.textBaseline) {
4542
4547
  case "top":
4543
4548
  underlineY += boxHeight - 2 * strokeWidth;
4544
- strikeY += boxHeight - textHeight;
4549
+ strikeY += boxHeight / 2 - strokeWidth;
4545
4550
  break;
4546
4551
  case "middle":
4547
4552
  underlineY += boxHeight / 2 - strokeWidth;
4548
4553
  break;
4549
4554
  case "alphabetic":
4550
4555
  underlineY += 2 * strokeWidth;
4551
- strikeY -= textHeight / 2 - strokeWidth / 2;
4556
+ strikeY -= 3 * strokeWidth;
4552
4557
  break;
4553
4558
  case "bottom":
4554
4559
  underlineY = y;
@@ -4611,6 +4616,7 @@
4611
4616
  const children = item.children;
4612
4617
  const description = item.description;
4613
4618
  const icon = item.icon;
4619
+ const secondaryIcon = item.secondaryIcon;
4614
4620
  return {
4615
4621
  id: item.id || uuidGenerator$2.uuidv4(),
4616
4622
  name: typeof name === "function" ? name : () => name,
@@ -4629,6 +4635,7 @@
4629
4635
  isReadonlyAllowed: item.isReadonlyAllowed || false,
4630
4636
  separator: item.separator || false,
4631
4637
  icon: typeof icon === "function" ? icon : () => icon || "",
4638
+ secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
4632
4639
  description: typeof description === "function" ? description : () => description || "",
4633
4640
  textColor: item.textColor,
4634
4641
  sequence: item.sequence || 0,
@@ -5967,7 +5974,7 @@
5967
5974
  return { cellData: {} };
5968
5975
  }
5969
5976
  const sheetId = data.sheetId;
5970
- const content = getters.getTranslatedCellFormula(sheetId, x, y, cell.compiledFormula, cell.dependencies);
5977
+ const content = getters.getTranslatedCellFormula(sheetId, x, y, cell.compiledFormula);
5971
5978
  return {
5972
5979
  cellData: {
5973
5980
  border: data.border,
@@ -6984,11 +6991,9 @@
6984
6991
  padding: ${MENU_VERTICAL_PADDING}px 0px;
6985
6992
  width: ${MENU_WIDTH}px;
6986
6993
  box-sizing: border-box !important;
6994
+ user-select: none;
6987
6995
 
6988
6996
  .o-menu-item {
6989
- display: flex;
6990
- justify-content: space-between;
6991
- align-items: center;
6992
6997
  box-sizing: border-box;
6993
6998
  height: ${MENU_ITEM_HEIGHT}px;
6994
6999
  padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
@@ -6999,20 +7004,12 @@
6999
7004
  min-width: 40%;
7000
7005
  }
7001
7006
 
7002
- &.o-menu-root {
7003
- display: flex;
7004
- justify-content: space-between;
7005
- }
7006
-
7007
7007
  .o-menu-item-icon {
7008
7008
  display: inline-block;
7009
7009
  margin: 0px 8px 0px 0px;
7010
7010
  width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
7011
7011
  line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
7012
7012
  }
7013
- .o-menu-item-root {
7014
- width: 10px;
7015
- }
7016
7013
 
7017
7014
  &:not(.disabled) {
7018
7015
  &:hover,
@@ -7355,7 +7352,7 @@
7355
7352
  return result;
7356
7353
  }
7357
7354
  function tokenizeDebugger(chars) {
7358
- if (chars.current() === "?") {
7355
+ if (chars.current === "?") {
7359
7356
  chars.shift();
7360
7357
  return { type: "DEBUGGER", value: "?" };
7361
7358
  }
@@ -7366,7 +7363,7 @@
7366
7363
  ")": "RIGHT_PAREN",
7367
7364
  };
7368
7365
  function tokenizeMisc(chars) {
7369
- if (chars.current() in misc$1) {
7366
+ if (chars.current in misc$1) {
7370
7367
  const value = chars.shift();
7371
7368
  const type = misc$1[value];
7372
7369
  return { type, value };
@@ -7374,7 +7371,7 @@
7374
7371
  return null;
7375
7372
  }
7376
7373
  function tokenizeArgsSeparator(chars, locale) {
7377
- if (chars.current() === locale.formulaArgSeparator) {
7374
+ if (chars.current === locale.formulaArgSeparator) {
7378
7375
  const value = chars.shift();
7379
7376
  const type = "ARG_SEPARATOR";
7380
7377
  return { type, value };
@@ -7399,14 +7396,13 @@
7399
7396
  return null;
7400
7397
  }
7401
7398
  function tokenizeString(chars) {
7402
- if (chars.current() === '"') {
7399
+ if (chars.current === '"') {
7403
7400
  const startChar = chars.shift();
7404
7401
  let letters = startChar;
7405
- while (chars.current() &&
7406
- (chars.current() !== startChar || letters[letters.length - 1] === "\\")) {
7402
+ while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
7407
7403
  letters += chars.shift();
7408
7404
  }
7409
- if (chars.current() === '"') {
7405
+ if (chars.current === '"') {
7410
7406
  letters += chars.shift();
7411
7407
  }
7412
7408
  return {
@@ -7433,14 +7429,14 @@
7433
7429
  let result = "";
7434
7430
  // there are two main cases to manage: either something which starts with
7435
7431
  // a ', like 'Sheet 2'A2, or a word-like element.
7436
- if (chars.current() === "'") {
7432
+ if (chars.current === "'") {
7437
7433
  let lastChar = chars.shift();
7438
7434
  result += lastChar;
7439
- while (chars.current()) {
7435
+ while (chars.current) {
7440
7436
  lastChar = chars.shift();
7441
7437
  result += lastChar;
7442
7438
  if (lastChar === "'") {
7443
- if (chars.current() && chars.current() === "'") {
7439
+ if (chars.current && chars.current === "'") {
7444
7440
  lastChar = chars.shift();
7445
7441
  result += lastChar;
7446
7442
  }
@@ -7456,7 +7452,7 @@
7456
7452
  };
7457
7453
  }
7458
7454
  }
7459
- while (chars.current() && separatorRegexp.test(chars.current())) {
7455
+ while (chars.current && separatorRegexp.test(chars.current)) {
7460
7456
  result += chars.shift();
7461
7457
  }
7462
7458
  if (result.length) {
@@ -7471,14 +7467,14 @@
7471
7467
  }
7472
7468
  function tokenizeSpace(chars) {
7473
7469
  let length = 0;
7474
- while (chars.current() === NEWLINE) {
7470
+ while (chars.current === NEWLINE) {
7475
7471
  length++;
7476
7472
  chars.shift();
7477
7473
  }
7478
7474
  if (length) {
7479
7475
  return { type: "SPACE", value: NEWLINE.repeat(length) };
7480
7476
  }
7481
- while (chars.current() === " ") {
7477
+ while (chars.current === " ") {
7482
7478
  length++;
7483
7479
  chars.shift();
7484
7480
  }
@@ -7497,17 +7493,20 @@
7497
7493
  class TokenizingChars {
7498
7494
  text;
7499
7495
  currentIndex = 0;
7496
+ current;
7500
7497
  constructor(text) {
7501
7498
  this.text = text;
7502
- }
7503
- current() {
7504
- return this.text[this.currentIndex];
7499
+ this.current = text[0];
7505
7500
  }
7506
7501
  shift() {
7507
- return this.text[this.currentIndex++];
7502
+ const current = this.current;
7503
+ const next = this.text[++this.currentIndex];
7504
+ this.current = next;
7505
+ return current;
7508
7506
  }
7509
7507
  advanceBy(length) {
7510
7508
  this.currentIndex += length;
7509
+ this.current = this.text[this.currentIndex];
7511
7510
  }
7512
7511
  isOver() {
7513
7512
  return this.currentIndex >= this.text.length;
@@ -10719,6 +10718,17 @@
10719
10718
  const IS_ONLY_ONE_RANGE = (env) => {
10720
10719
  return env.model.getters.getSelectedZones().length === 1;
10721
10720
  };
10721
+ const CAN_INSERT_HEADER = (env, dimension) => {
10722
+ if (!IS_ONLY_ONE_RANGE(env)) {
10723
+ return false;
10724
+ }
10725
+ const activeHeaders = dimension === "COL" ? env.model.getters.getActiveCols() : env.model.getters.getActiveRows();
10726
+ const ortogonalActiveHeaders = dimension === "COL" ? env.model.getters.getActiveRows() : env.model.getters.getActiveCols();
10727
+ const sheetId = env.model.getters.getActiveSheetId();
10728
+ const zone = env.model.getters.getSelectedZone();
10729
+ const allSheetSelected = isEqual(zone, env.model.getters.getSheetZone(sheetId));
10730
+ return isConsecutive(activeHeaders) && (ortogonalActiveHeaders.size === 0 || allSheetSelected);
10731
+ };
10722
10732
 
10723
10733
  const undo = {
10724
10734
  name: _t("Undo"),
@@ -11300,7 +11310,7 @@
11300
11310
  ) {
11301
11311
  const _array = toMatrix(arg);
11302
11312
  const _nbRows = toInteger(rows?.value, this.locale);
11303
- const _nbColumns = columns !== undefined ? toInteger(columns.value, this.local) : _array.length;
11313
+ const _nbColumns = columns !== undefined ? toInteger(columns.value, this.locale) : _array.length;
11304
11314
  assert(() => _nbRows >= _array[0].length, _t("The rows arguments (%s) must be greater or equal than the number of rows of the array.", _nbRows.toString()));
11305
11315
  assert(() => _nbColumns >= _array.length, _t("The columns arguments (%s) must be greater or equal than the number of columns of the array.", _nbColumns.toString()));
11306
11316
  return generateMatrix(_nbColumns, _nbRows, (col, row) => col >= _array.length || row >= _array[col].length ? padWith : _array[col][row]);
@@ -11789,6 +11799,10 @@
11789
11799
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11790
11800
  });
11791
11801
 
11802
+ function sum(values, locale) {
11803
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11804
+ }
11805
+
11792
11806
  const DEFAULT_FACTOR = 1;
11793
11807
  const DEFAULT_MODE = 0;
11794
11808
  const DEFAULT_PLACES = 0;
@@ -12740,7 +12754,7 @@
12740
12754
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
12741
12755
  },
12742
12756
  compute: function (...values) {
12743
- return reduceNumbers(values, (acc, a) => acc + a, 0, this.locale);
12757
+ return sum(values, this.locale);
12744
12758
  },
12745
12759
  isExported: true,
12746
12760
  };
@@ -12909,6 +12923,44 @@
12909
12923
  const dims = args[0].length;
12910
12924
  args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
12911
12925
  }
12926
+ function average(values, locale) {
12927
+ let count = 0;
12928
+ const sum = reduceNumbers(values, (acc, a) => {
12929
+ count += 1;
12930
+ return acc + a;
12931
+ }, 0, locale);
12932
+ assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12933
+ return sum / count;
12934
+ }
12935
+ function countNumbers(values, locale) {
12936
+ let count = 0;
12937
+ for (let n of values) {
12938
+ if (isMatrix(n)) {
12939
+ for (let i of n) {
12940
+ for (let j of i) {
12941
+ if (typeof j === "number") {
12942
+ count += 1;
12943
+ }
12944
+ }
12945
+ }
12946
+ }
12947
+ else if (typeof n !== "string" || isNumber(n, locale) || parseDateTime(n, locale)) {
12948
+ count += 1;
12949
+ }
12950
+ }
12951
+ return count;
12952
+ }
12953
+ function countAny(values) {
12954
+ return reduceAny(values, (acc, a) => (a !== undefined && a !== null ? acc + 1 : acc), 0);
12955
+ }
12956
+ function max(values, locale) {
12957
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12958
+ return result === -Infinity ? 0 : result;
12959
+ }
12960
+ function min(values, locale) {
12961
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12962
+ return result === Infinity ? 0 : result;
12963
+ }
12912
12964
 
12913
12965
  function filterAndFlatData(dataY, dataX) {
12914
12966
  const _flatDataY = [];
@@ -13179,13 +13231,7 @@
13179
13231
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13180
13232
  },
13181
13233
  compute: function (...values) {
13182
- let count = 0;
13183
- const sum = reduceNumbers(values, (acc, a) => {
13184
- count += 1;
13185
- return acc + a;
13186
- }, 0, this.locale);
13187
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
13188
- return sum / count;
13234
+ return average(values, this.locale);
13189
13235
  },
13190
13236
  isExported: true,
13191
13237
  };
@@ -13343,24 +13389,7 @@
13343
13389
  ],
13344
13390
  returns: ["NUMBER"],
13345
13391
  compute: function (...values) {
13346
- let count = 0;
13347
- for (let n of values) {
13348
- if (isMatrix(n)) {
13349
- for (let i of n) {
13350
- for (let j of i) {
13351
- if (typeof j === "number") {
13352
- count += 1;
13353
- }
13354
- }
13355
- }
13356
- }
13357
- else if (typeof n !== "string" ||
13358
- isNumber(n, this.locale) ||
13359
- parseDateTime(n, this.locale)) {
13360
- count += 1;
13361
- }
13362
- }
13363
- return count;
13392
+ return countNumbers(values, this.locale);
13364
13393
  },
13365
13394
  isExported: true,
13366
13395
  };
@@ -13375,7 +13404,7 @@
13375
13404
  ],
13376
13405
  returns: ["NUMBER"],
13377
13406
  compute: function (...values) {
13378
- return reduceAny(values, (acc, a) => (a !== undefined && a !== null ? acc + 1 : acc), 0);
13407
+ return countAny(values);
13379
13408
  },
13380
13409
  isExported: true,
13381
13410
  };
@@ -13605,8 +13634,7 @@
13605
13634
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13606
13635
  },
13607
13636
  compute: function (...values) {
13608
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, this.locale);
13609
- return result === -Infinity ? 0 : result;
13637
+ return max(values, this.locale);
13610
13638
  },
13611
13639
  isExported: true,
13612
13640
  };
@@ -13693,8 +13721,7 @@
13693
13721
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13694
13722
  },
13695
13723
  compute: function (...values) {
13696
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, this.locale);
13697
- return result === Infinity ? 0 : result;
13724
+ return min(values, this.locale);
13698
13725
  },
13699
13726
  isExported: true,
13700
13727
  };
@@ -17865,6 +17892,149 @@
17865
17892
  YIELDMAT: YIELDMAT
17866
17893
  });
17867
17894
 
17895
+ /**
17896
+ * Change the reference types inside the given token, if the token represent a range or a cell
17897
+ *
17898
+ * Eg. :
17899
+ * A1 => $A$1 => A$1 => $A1 => A1
17900
+ * A1:$B$1 => $A$1:B$1 => A$1:$B1 => $A1:B1 => A1:$B$1
17901
+ */
17902
+ function loopThroughReferenceType(token) {
17903
+ if (token.type !== "REFERENCE")
17904
+ return token;
17905
+ const { xc, sheetName } = splitReference(token.value);
17906
+ const [left, right] = xc.split(":");
17907
+ const updatedLeft = getTokenNextReferenceType(left);
17908
+ const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
17909
+ return { ...token, value: getFullReference(sheetName, updatedLeft + updatedRight) };
17910
+ }
17911
+ /**
17912
+ * Get a new token with a changed type of reference from the given cell token symbol.
17913
+ * Undefined behavior if given a token other than a cell or if the Xc contains a sheet reference
17914
+ *
17915
+ * A1 => $A$1 => A$1 => $A1 => A1
17916
+ */
17917
+ function getTokenNextReferenceType(xc) {
17918
+ switch (getReferenceType(xc)) {
17919
+ case "none":
17920
+ xc = setXcToFixedReferenceType(xc, "colrow");
17921
+ break;
17922
+ case "colrow":
17923
+ xc = setXcToFixedReferenceType(xc, "row");
17924
+ break;
17925
+ case "row":
17926
+ xc = setXcToFixedReferenceType(xc, "col");
17927
+ break;
17928
+ case "col":
17929
+ xc = setXcToFixedReferenceType(xc, "none");
17930
+ break;
17931
+ }
17932
+ return xc;
17933
+ }
17934
+ /**
17935
+ * Returns the given XC with the given reference type. The XC string should not contain a sheet name.
17936
+ */
17937
+ function setXcToFixedReferenceType(xc, referenceType) {
17938
+ if (xc.includes("!")) {
17939
+ throw new Error("The given XC should not contain a sheet name");
17940
+ }
17941
+ xc = xc.replace(/\$/g, "");
17942
+ let indexOfNumber;
17943
+ switch (referenceType) {
17944
+ case "col":
17945
+ return "$" + xc;
17946
+ case "row":
17947
+ indexOfNumber = xc.search(/[0-9]/);
17948
+ return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
17949
+ case "colrow":
17950
+ indexOfNumber = xc.search(/[0-9]/);
17951
+ xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
17952
+ return "$" + xc;
17953
+ case "none":
17954
+ return xc;
17955
+ }
17956
+ }
17957
+ /**
17958
+ * Return the type of reference used in the given XC of a cell.
17959
+ * Undefined behavior if the XC have a sheet reference
17960
+ */
17961
+ function getReferenceType(xcCell) {
17962
+ if (isColAndRowFixed(xcCell)) {
17963
+ return "colrow";
17964
+ }
17965
+ else if (isColFixed(xcCell)) {
17966
+ return "col";
17967
+ }
17968
+ else if (isRowFixed(xcCell)) {
17969
+ return "row";
17970
+ }
17971
+ return "none";
17972
+ }
17973
+ function isColFixed(xc) {
17974
+ return xc.startsWith("$");
17975
+ }
17976
+ function isRowFixed(xc) {
17977
+ return xc.includes("$", 1);
17978
+ }
17979
+ function isColAndRowFixed(xc) {
17980
+ return xc.startsWith("$") && xc.length > 1 && xc.slice(1).includes("$");
17981
+ }
17982
+
17983
+ // -----------------------------------------------------------------------------
17984
+ // CELL
17985
+ // -----------------------------------------------------------------------------
17986
+ // NOTE: missing from Excel: "color", "filename", "parentheses", "prefix", "protect" and "width"
17987
+ const CELL_INFO_TYPES = ["address", "col", "contents", "format", "row", "type"];
17988
+ const CELL = {
17989
+ description: _t("Gets information about a cell."),
17990
+ args: [
17991
+ arg("info_type (string)", _t("The type of information requested. Can be one of %s", CELL_INFO_TYPES.join(", "))),
17992
+ arg("reference (meta)", _t("The reference to the cell.")),
17993
+ ],
17994
+ returns: ["ANY"],
17995
+ compute: function (info, reference) {
17996
+ const _info = toString(info).toLowerCase();
17997
+ assert(() => CELL_INFO_TYPES.includes(_info), _t("The info_type should be one of %s.", CELL_INFO_TYPES.join(", ")));
17998
+ const sheetId = this.__originSheetId;
17999
+ const topLeftReference = reference.includes(":") ? reference.split(":")[0] : reference;
18000
+ let { sheetName, xc } = splitReference(topLeftReference);
18001
+ // only put the sheet name if the referenced range is in another sheet than the cell the formula is on
18002
+ sheetName = sheetName === this.getters.getSheetName(sheetId) ? undefined : sheetName;
18003
+ const fixedRef = getFullReference(sheetName, setXcToFixedReferenceType(xc, "colrow"));
18004
+ const range = this.getters.getRangeFromSheetXC(sheetId, fixedRef);
18005
+ switch (_info) {
18006
+ case "address":
18007
+ return this.getters.getRangeString(range, sheetId);
18008
+ case "col":
18009
+ return range.zone.left + 1;
18010
+ case "contents": {
18011
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18012
+ return this.getters.getEvaluatedCell(position).value;
18013
+ }
18014
+ case "format": {
18015
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18016
+ return this.getters.getEvaluatedCell(position).format || "";
18017
+ }
18018
+ case "row":
18019
+ return range.zone.top + 1;
18020
+ case "type": {
18021
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18022
+ const type = this.getters.getEvaluatedCell(position).type;
18023
+ if (type === CellValueType.empty) {
18024
+ return "b"; // blank
18025
+ }
18026
+ else if (type === CellValueType.text) {
18027
+ return "l"; // label
18028
+ }
18029
+ else {
18030
+ return "v"; // value
18031
+ }
18032
+ }
18033
+ }
18034
+ return "";
18035
+ },
18036
+ isExported: true,
18037
+ };
17868
18038
  // -----------------------------------------------------------------------------
17869
18039
  // ISERR
17870
18040
  // -----------------------------------------------------------------------------
@@ -18020,6 +18190,7 @@
18020
18190
 
18021
18191
  var info = /*#__PURE__*/Object.freeze({
18022
18192
  __proto__: null,
18193
+ CELL: CELL,
18023
18194
  ISBLANK: ISBLANK,
18024
18195
  ISERR: ISERR,
18025
18196
  ISERROR: ISERROR,
@@ -18311,7 +18482,7 @@
18311
18482
  cellReference = rowPart + colPart;
18312
18483
  }
18313
18484
  if (sheet !== undefined) {
18314
- return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
18485
+ return getFullReference(toString(sheet), cellReference);
18315
18486
  }
18316
18487
  return cellReference;
18317
18488
  },
@@ -18327,7 +18498,7 @@
18327
18498
  ],
18328
18499
  returns: ["NUMBER"],
18329
18500
  compute: function (cellReference) {
18330
- const _cellReference = cellReference || this.__originCellXC?.();
18501
+ const _cellReference = cellReference || this.__originCellXC();
18331
18502
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18332
18503
  const zone = toZone(_cellReference);
18333
18504
  return zone.left + 1;
@@ -18494,7 +18665,7 @@
18494
18665
  ],
18495
18666
  returns: ["NUMBER"],
18496
18667
  compute: function (cellReference) {
18497
- cellReference = cellReference || this.__originCellXC?.();
18668
+ cellReference = cellReference || this.__originCellXC();
18498
18669
  assert(() => !!cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18499
18670
  const zone = toZone(cellReference);
18500
18671
  return zone.top + 1;
@@ -19396,9 +19567,7 @@
19396
19567
  const number = getRowsNumber(env);
19397
19568
  return number === 1 ? _t("Insert row") : _t("Insert %s rows", number.toString());
19398
19569
  },
19399
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
19400
- IS_ONLY_ONE_RANGE(env) &&
19401
- env.model.getters.getActiveCols().size === 0,
19570
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
19402
19571
  icon: "o-spreadsheet-Icon.INSERT_ROW",
19403
19572
  };
19404
19573
  const rowInsertRowBefore = {
@@ -19407,9 +19576,7 @@
19407
19576
  return number === 1 ? _t("Insert row above") : _t("Insert %s rows above", number.toString());
19408
19577
  },
19409
19578
  execute: INSERT_ROWS_BEFORE_ACTION,
19410
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
19411
- IS_ONLY_ONE_RANGE(env) &&
19412
- env.model.getters.getActiveCols().size === 0,
19579
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
19413
19580
  icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
19414
19581
  };
19415
19582
  const topBarInsertRowsBefore = {
@@ -19440,9 +19607,7 @@
19440
19607
  const number = getRowsNumber(env);
19441
19608
  return number === 1 ? _t("Insert row below") : _t("Insert %s rows below", number.toString());
19442
19609
  },
19443
- isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
19444
- IS_ONLY_ONE_RANGE(env) &&
19445
- env.model.getters.getActiveCols().size === 0,
19610
+ isVisible: (env) => CAN_INSERT_HEADER(env, "ROW"),
19446
19611
  icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER",
19447
19612
  };
19448
19613
  const topBarInsertRowsAfter = {
@@ -19460,9 +19625,7 @@
19460
19625
  const number = getColumnsNumber(env);
19461
19626
  return number === 1 ? _t("Insert column") : _t("Insert %s columns", number.toString());
19462
19627
  },
19463
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
19464
- IS_ONLY_ONE_RANGE(env) &&
19465
- env.model.getters.getActiveRows().size === 0,
19628
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
19466
19629
  icon: "o-spreadsheet-Icon.INSERT_COL",
19467
19630
  };
19468
19631
  const colInsertColsBefore = {
@@ -19473,9 +19636,7 @@
19473
19636
  : _t("Insert %s columns left", number.toString());
19474
19637
  },
19475
19638
  execute: INSERT_COLUMNS_BEFORE_ACTION,
19476
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
19477
- IS_ONLY_ONE_RANGE(env) &&
19478
- env.model.getters.getActiveRows().size === 0,
19639
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
19479
19640
  icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
19480
19641
  };
19481
19642
  const topBarInsertColsBefore = {
@@ -19508,9 +19669,7 @@
19508
19669
  : _t("Insert %s columns right", number.toString());
19509
19670
  },
19510
19671
  execute: INSERT_COLUMNS_AFTER_ACTION,
19511
- isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
19512
- IS_ONLY_ONE_RANGE(env) &&
19513
- env.model.getters.getActiveRows().size === 0,
19672
+ isVisible: (env) => CAN_INSERT_HEADER(env, "COL"),
19514
19673
  icon: "o-spreadsheet-Icon.INSERT_COL_AFTER",
19515
19674
  };
19516
19675
  const topBarInsertColsAfter = {
@@ -23851,38 +24010,72 @@
23851
24010
  }
23852
24011
  .o-input-count {
23853
24012
  width: fit-content;
23854
- padding: 4 0 4 4;
24013
+ padding: 4px 0 4px 4px;
23855
24014
  }
23856
24015
  }
23857
24016
  }
23858
24017
  `;
23859
24018
  class FindAndReplacePanel extends owl.Component {
23860
24019
  static template = "o-spreadsheet-FindAndReplacePanel";
23861
- state = owl.useState(this.initialState());
24020
+ static components = { SelectionInput };
23862
24021
  debounceTimeoutId;
23863
- showFormulaState = false;
24022
+ initialShowFormulaState = false;
24023
+ dataRange = "";
23864
24024
  searchInput = owl.useRef("searchInput");
24025
+ replaceInput = owl.useRef("replaceInput");
23865
24026
  get hasSearchResult() {
23866
24027
  return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
23867
24028
  }
23868
24029
  get pendingSearch() {
23869
24030
  return this.debounceTimeoutId !== undefined;
23870
24031
  }
24032
+ get searchOptions() {
24033
+ return this.env.model.getters.getSearchOptions();
24034
+ }
24035
+ get toSearch() {
24036
+ return this.searchInput.el?.value || "";
24037
+ }
24038
+ get toReplace() {
24039
+ return this.replaceInput.el?.value || "";
24040
+ }
24041
+ get allSheetsMatchesCount() {
24042
+ return _t("%s in all sheets", this.env.model.getters.getAllSheetMatchesCount());
24043
+ }
24044
+ get currentSheetMatchesCount() {
24045
+ return _t("%(matches)s in sheet %(sheetName)s", {
24046
+ matches: this.env.model.getters.getActiveSheetMatchesCount(),
24047
+ sheetName: this.env.model.getters.getSheetName(this.env.model.getters.getActiveSheetId()),
24048
+ });
24049
+ }
24050
+ get specificRangeMatchesCount() {
24051
+ const range = this.searchOptions.specificRange;
24052
+ if (!range) {
24053
+ return "";
24054
+ }
24055
+ const { _sheetId, _zone } = range;
24056
+ return _t("%(matches)s in range %(range)s of sheet %(sheetName)s", {
24057
+ matches: this.env.model.getters.getSpecificRangeMatchesCount().toString(),
24058
+ range: zoneToXc(_zone),
24059
+ sheetName: this.env.model.getters.getSheetName(_sheetId),
24060
+ });
24061
+ }
23871
24062
  setup() {
23872
- this.showFormulaState = this.env.model.getters.shouldShowFormulas();
24063
+ this.initialShowFormulaState = this.env.model.getters.shouldShowFormulas();
23873
24064
  owl.onMounted(() => this.searchInput.el?.focus());
23874
24065
  owl.onWillUnmount(() => {
23875
24066
  clearTimeout(this.debounceTimeoutId);
23876
24067
  this.env.model.dispatch("CLEAR_SEARCH");
23877
- this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
24068
+ this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
23878
24069
  });
23879
24070
  owl.useEffect(() => {
23880
- this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
23881
- this.searchFormulas();
24071
+ const showFormula = this.env.model.getters.shouldShowFormulas();
24072
+ this.updateSearch({ searchFormulas: showFormula });
23882
24073
  }, () => [this.env.model.getters.shouldShowFormulas()]);
23883
24074
  }
23884
- onInput(ev) {
23885
- this.state.toSearch = ev.target.value;
24075
+ onFocusSearch() {
24076
+ this.updateDataRange();
24077
+ }
24078
+ onInput() {
23886
24079
  this.debouncedUpdateSearch();
23887
24080
  }
23888
24081
  onKeydownSearch(ev) {
@@ -23899,11 +24092,36 @@
23899
24092
  this.replace();
23900
24093
  }
23901
24094
  }
23902
- searchFormulas() {
24095
+ searchFormulas(ev) {
24096
+ const showFormula = ev.target.checked;
23903
24097
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
23904
- show: this.state.searchOptions.searchFormulas,
24098
+ show: showFormula,
23905
24099
  });
23906
- this.updateSearch();
24100
+ this.updateSearch({ searchFormulas: showFormula });
24101
+ }
24102
+ searchExactMatch(ev) {
24103
+ const exactMatch = ev.target.checked;
24104
+ this.updateSearch({ exactMatch });
24105
+ }
24106
+ searchMatchCase(ev) {
24107
+ const matchCase = ev.target.checked;
24108
+ this.updateSearch({ matchCase });
24109
+ }
24110
+ changeSearchScope(ev) {
24111
+ const searchScope = ev.target.value;
24112
+ this.updateSearch({ searchScope });
24113
+ }
24114
+ onSearchRangeChanged(ranges) {
24115
+ this.dataRange = ranges[0];
24116
+ }
24117
+ updateDataRange() {
24118
+ if (!this.dataRange) {
24119
+ return;
24120
+ }
24121
+ if (this.searchOptions.searchScope === "specificRange") {
24122
+ const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange).rangeData;
24123
+ this.updateSearch({ specificRange });
24124
+ }
23907
24125
  }
23908
24126
  onSelectPreviousCell() {
23909
24127
  this.env.model.dispatch("SELECT_SEARCH_PREVIOUS_MATCH");
@@ -23911,10 +24129,14 @@
23911
24129
  onSelectNextCell() {
23912
24130
  this.env.model.dispatch("SELECT_SEARCH_NEXT_MATCH");
23913
24131
  }
23914
- updateSearch() {
24132
+ updateSearch(updateSearchOptions) {
24133
+ const searchOptions = {
24134
+ ...this.env.model.getters.getSearchOptions(),
24135
+ ...updateSearchOptions,
24136
+ };
23915
24137
  this.env.model.dispatch("UPDATE_SEARCH", {
23916
- toSearch: this.state.toSearch,
23917
- searchOptions: this.state.searchOptions,
24138
+ toSearch: this.toSearch,
24139
+ searchOptions,
23918
24140
  });
23919
24141
  }
23920
24142
  debouncedUpdateSearch() {
@@ -23926,28 +24148,14 @@
23926
24148
  }
23927
24149
  replace() {
23928
24150
  this.env.model.dispatch("REPLACE_SEARCH", {
23929
- replaceWith: this.state.replaceWith,
24151
+ replaceWith: this.toReplace,
23930
24152
  });
23931
24153
  }
23932
24154
  replaceAll() {
23933
24155
  this.env.model.dispatch("REPLACE_ALL_SEARCH", {
23934
- replaceWith: this.state.replaceWith,
24156
+ replaceWith: this.toReplace,
23935
24157
  });
23936
24158
  }
23937
- // ---------------------------------------------------------------------------
23938
- // Private
23939
- // ---------------------------------------------------------------------------
23940
- initialState() {
23941
- return {
23942
- toSearch: "",
23943
- replaceWith: "",
23944
- searchOptions: {
23945
- matchCase: false,
23946
- exactMatch: false,
23947
- searchFormulas: false,
23948
- },
23949
- };
23950
- }
23951
24159
  }
23952
24160
  FindAndReplacePanel.props = {
23953
24161
  onCloseSidePanel: Function,
@@ -27093,7 +27301,7 @@
27093
27301
  get cellReference() {
27094
27302
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27095
27303
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27096
- return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
27304
+ return getFullReference(prefixSheet ? this.env.model.getters.getSheetName(sheetId) : undefined, toXC(col, row));
27097
27305
  }
27098
27306
  get cellReferenceStyle() {
27099
27307
  const { x: left, y: top } = this.rect;
@@ -28536,22 +28744,15 @@
28536
28744
  height: 10000px;
28537
28745
  background-color: ${SELECTION_BORDER_COLOR};
28538
28746
  }
28539
- .o-unhide {
28540
- width: ${UNHIDE_ICON_EDGE_LENGTH}px;
28541
- height: ${UNHIDE_ICON_EDGE_LENGTH}px;
28542
- position: absolute;
28543
- overflow: hidden;
28544
- border-radius: 2px;
28545
- top: calc(${HEADER_HEIGHT}px / 2 - ${UNHIDE_ICON_EDGE_LENGTH}px / 2);
28747
+ .o-unhide-buttons {
28748
+ width: fit-content;
28749
+ gap: 5px;
28750
+ transform: translate(-50%, 0);
28546
28751
  }
28547
28752
  .o-unhide:hover {
28548
28753
  z-index: ${ComponentsImportance.Grid + 1};
28549
28754
  background-color: lightgrey;
28550
28755
  }
28551
- .o-unhide > svg {
28552
- position: relative;
28553
- top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28554
- }
28555
28756
  }
28556
28757
  `;
28557
28758
  AbstractResizer.props = {
@@ -28661,8 +28862,8 @@
28661
28862
  dimension: "COL",
28662
28863
  });
28663
28864
  }
28664
- unhideStyleValue(hiddenIndex) {
28665
- return this._getDimensionsInViewport(hiddenIndex).start;
28865
+ getUnhideButtonStyle(hiddenIndex) {
28866
+ return cssPropertiesToCss({ left: this._getDimensionsInViewport(hiddenIndex).start + "px" });
28666
28867
  }
28667
28868
  }
28668
28869
  css /* scss */ `
@@ -28708,18 +28909,9 @@
28708
28909
  height: 1px;
28709
28910
  background-color: ${SELECTION_BORDER_COLOR};
28710
28911
  }
28711
- .o-unhide {
28712
- width: ${UNHIDE_ICON_EDGE_LENGTH}px;
28713
- height: ${UNHIDE_ICON_EDGE_LENGTH}px;
28714
- position: absolute;
28715
- overflow: hidden;
28716
- border-radius: 2px;
28717
- left: calc(${HEADER_WIDTH}px - ${UNHIDE_ICON_EDGE_LENGTH}px - 2px);
28718
- }
28719
- .o-unhide > svg {
28720
- position: relative;
28721
- left: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28722
- top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28912
+ .o-unhide-buttons {
28913
+ height: fit-content;
28914
+ transform: translate(0, -50%);
28723
28915
  }
28724
28916
  .o-unhide:hover {
28725
28917
  z-index: ${ComponentsImportance.Grid + 1};
@@ -28834,8 +29026,8 @@
28834
29026
  elements: hiddenElements,
28835
29027
  });
28836
29028
  }
28837
- unhideStyleValue(hiddenIndex) {
28838
- return this._getDimensionsInViewport(hiddenIndex).start;
29029
+ getUnhideButtonStyle(hiddenIndex) {
29030
+ return cssPropertiesToCss({ top: this._getDimensionsInViewport(hiddenIndex).start + "px" });
28839
29031
  }
28840
29032
  }
28841
29033
  css /* scss */ `
@@ -29342,7 +29534,6 @@
29342
29534
  owl.onMounted(() => this.focus());
29343
29535
  this.props.exposeFocus(() => this.focus());
29344
29536
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29345
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29346
29537
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29347
29538
  this.moveCanvas(deltaX, deltaY);
29348
29539
  this.hoveredCell.col = undefined;
@@ -29779,6 +29970,7 @@
29779
29970
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
29780
29971
  const target = this.env.model.getters.getSelectedZones();
29781
29972
  const clipboardString = this.env.model.getters.getClipboardTextContent();
29973
+ const isCutOperation = this.env.model.getters.isCutOperation();
29782
29974
  if (clipboardString === content) {
29783
29975
  // the paste actually comes from o-spreadsheet itself
29784
29976
  interactivePaste(this.env, target);
@@ -29786,7 +29978,7 @@
29786
29978
  else {
29787
29979
  interactivePasteFromOS(this.env, target, content);
29788
29980
  }
29789
- if (this.env.model.getters.isCutOperation()) {
29981
+ if (isCutOperation) {
29790
29982
  await this.env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
29791
29983
  }
29792
29984
  }
@@ -31399,12 +31591,6 @@
31399
31591
  }
31400
31592
  function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
31401
31593
  let { sheetName, xc } = splitReference(range);
31402
- if (sheetName) {
31403
- sheetName = getCanonicalSheetName(sheetName) + "!";
31404
- }
31405
- else {
31406
- sheetName = "";
31407
- }
31408
31594
  let zone = toUnboundedZone(xc);
31409
31595
  if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
31410
31596
  const height = zone.bottom - zone.top + 1;
@@ -31417,7 +31603,7 @@
31417
31603
  }
31418
31604
  }
31419
31605
  const dataXC = zoneToXc(zone);
31420
- return sheetName + dataXC;
31606
+ return getFullReference(sheetName, dataXC);
31421
31607
  }
31422
31608
 
31423
31609
  /**
@@ -34080,6 +34266,14 @@
34080
34266
  // ---------------------------------------------------------------------------
34081
34267
  // Command Handling
34082
34268
  // ---------------------------------------------------------------------------
34269
+ allowDispatch(cmd) {
34270
+ switch (cmd.type) {
34271
+ case "SET_BORDER":
34272
+ return this.checkBordersUnchanged(cmd);
34273
+ default:
34274
+ return "Success" /* CommandResult.Success */;
34275
+ }
34276
+ }
34083
34277
  handle(cmd) {
34084
34278
  switch (cmd.type) {
34085
34279
  case "ADD_MERGE":
@@ -34499,6 +34693,14 @@
34499
34693
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
34500
34694
  }
34501
34695
  }
34696
+ checkBordersUnchanged(cmd) {
34697
+ const currentBorder = this.getCellBorder(cmd);
34698
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
34699
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
34700
+ return "NoChanges" /* CommandResult.NoChanges */;
34701
+ }
34702
+ return "Success" /* CommandResult.Success */;
34703
+ }
34502
34704
  // ---------------------------------------------------------------------------
34503
34705
  // Import/Export
34504
34706
  // ---------------------------------------------------------------------------
@@ -34967,6 +35169,9 @@
34967
35169
  // -----------------------------------------------------------------------------
34968
35170
  function compile(formula) {
34969
35171
  const tokens = rangeTokenize(formula);
35172
+ return compileTokens(tokens);
35173
+ }
35174
+ function compileTokens(tokens) {
34970
35175
  const { dependencies, constantValues } = formulaArguments(tokens);
34971
35176
  const cacheKey = compilationCacheKey(tokens, dependencies, constantValues);
34972
35177
  if (!functionCache[cacheKey]) {
@@ -35322,7 +35527,6 @@
35322
35527
  static getters = [
35323
35528
  "zoneToXC",
35324
35529
  "getCells",
35325
- "getFormulaCellContent",
35326
35530
  "getTranslatedCellFormula",
35327
35531
  "getCellStyle",
35328
35532
  "getCellById",
@@ -35333,11 +35537,11 @@
35333
35537
  for (const sheet of Object.keys(this.cells)) {
35334
35538
  for (const cell of Object.values(this.cells[sheet] || {})) {
35335
35539
  if (cell.isFormula) {
35336
- for (const range of cell.dependencies) {
35540
+ for (const range of cell.compiledFormula.dependencies) {
35337
35541
  if (!sheetId || range.sheetId === sheetId) {
35338
35542
  const change = applyChange(range);
35339
35543
  if (change.changeType !== "NONE") {
35340
- this.history.update("cells", sheet, cell.id, "dependencies", cell.dependencies.indexOf(range), change.range);
35544
+ this.history.update("cells", sheet, cell.id, "compiledFormula", "dependencies", cell.compiledFormula.dependencies.indexOf(range), change.range);
35341
35545
  }
35342
35546
  }
35343
35547
  }
@@ -35351,8 +35555,9 @@
35351
35555
  allowDispatch(cmd) {
35352
35556
  switch (cmd.type) {
35353
35557
  case "UPDATE_CELL":
35558
+ return this.checkCellOutOfSheet(cmd);
35354
35559
  case "CLEAR_CELL":
35355
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
35560
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35356
35561
  default:
35357
35562
  return "Success" /* CommandResult.Success */;
35358
35563
  }
@@ -35521,24 +35726,18 @@
35521
35726
  */
35522
35727
  getCellById(cellId) {
35523
35728
  // this must be as fast as possible
35524
- for (const sheetId in this.cells) {
35525
- const sheet = this.cells[sheetId];
35526
- const cell = sheet[cellId];
35527
- if (cell) {
35528
- return cell;
35529
- }
35530
- }
35531
- return undefined;
35729
+ const position = this.getters.getCellPosition(cellId);
35730
+ const sheet = this.cells[position.sheetId];
35731
+ return sheet[cellId];
35532
35732
  }
35533
35733
  /*
35534
35734
  * Reconstructs the original formula string based on a normalized form and its dependencies
35535
35735
  */
35536
- getFormulaCellContent(sheetId, cell, dependencies) {
35537
- const ranges = dependencies || cell.dependencies;
35736
+ getFormulaCellContent(sheetId, compiledFormula, dependencies) {
35538
35737
  let rangeIndex = 0;
35539
- return concat(cell.compiledFormula.tokens.map((token) => {
35738
+ return concat(compiledFormula.tokens.map((token) => {
35540
35739
  if (token.type === "REFERENCE") {
35541
- const range = ranges[rangeIndex++];
35740
+ const range = dependencies[rangeIndex++];
35542
35741
  return this.getters.getRangeString(range, sheetId);
35543
35742
  }
35544
35743
  return token.value;
@@ -35547,12 +35746,9 @@
35547
35746
  /*
35548
35747
  * Constructs a formula string based on an initial formula and a translation vector
35549
35748
  */
35550
- getTranslatedCellFormula(sheetId, offsetX, offsetY, compiledFormula, dependencies) {
35551
- const adaptedDependencies = this.getters.createAdaptedRanges(dependencies, offsetX, offsetY, sheetId);
35552
- return this.getFormulaCellContent(sheetId, {
35553
- compiledFormula,
35554
- dependencies: adaptedDependencies,
35555
- });
35749
+ getTranslatedCellFormula(sheetId, offsetX, offsetY, compiledFormula) {
35750
+ const adaptedDependencies = this.getters.createAdaptedRanges(compiledFormula.dependencies, offsetX, offsetY, sheetId);
35751
+ return this.getFormulaCellContent(sheetId, compiledFormula, adaptedDependencies);
35556
35752
  }
35557
35753
  getCellStyle(position) {
35558
35754
  return this.getters.getCell(position)?.style || {};
@@ -35732,8 +35928,10 @@
35732
35928
  style,
35733
35929
  format,
35734
35930
  isFormula: true,
35735
- compiledFormula,
35736
- dependencies: [],
35931
+ compiledFormula: {
35932
+ ...compiledFormula,
35933
+ dependencies: [],
35934
+ },
35737
35935
  };
35738
35936
  }
35739
35937
  /**
@@ -35742,7 +35940,7 @@
35742
35940
  */
35743
35941
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
35744
35942
  const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
35745
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getFormulaCellContent.bind(this));
35943
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
35746
35944
  }
35747
35945
  createErrorFormula(id, content, format, style, error) {
35748
35946
  return {
@@ -35752,46 +35950,72 @@
35752
35950
  format,
35753
35951
  isFormula: true,
35754
35952
  compiledFormula: {
35755
- dependencies: [],
35756
35953
  tokens: tokenize(content),
35954
+ dependencies: [],
35757
35955
  execute: function () {
35758
35956
  throw error;
35759
35957
  },
35760
35958
  },
35761
- dependencies: [],
35762
35959
  };
35763
35960
  }
35764
- checkCellOutOfSheet(sheetId, col, row) {
35961
+ checkCellOutOfSheet(cmd) {
35962
+ const { sheetId, col, row } = cmd;
35765
35963
  const sheet = this.getters.tryGetSheet(sheetId);
35766
35964
  if (!sheet)
35767
35965
  return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
35768
35966
  const sheetZone = this.getters.getSheetZone(sheetId);
35769
35967
  return isInside(col, row, sheetZone) ? "Success" /* CommandResult.Success */ : "TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */;
35770
35968
  }
35969
+ checkUselessClearCell(cmd) {
35970
+ const cell = this.getters.getCell(cmd);
35971
+ if (!cell)
35972
+ return "NoChanges" /* CommandResult.NoChanges */;
35973
+ if (!cell.content && !cell.style && !cell.format) {
35974
+ return "NoChanges" /* CommandResult.NoChanges */;
35975
+ }
35976
+ return "Success" /* CommandResult.Success */;
35977
+ }
35771
35978
  }
35772
35979
  class FormulaCellWithDependencies {
35773
35980
  id;
35774
- compiledFormula;
35775
35981
  format;
35776
35982
  style;
35777
- dependencies;
35778
35983
  sheetId;
35779
- getFormulaCellContent;
35984
+ getRangeString;
35780
35985
  isFormula = true;
35781
- constructor(id, compiledFormula, format, style, dependencies, sheetId, getFormulaCellContent) {
35986
+ compiledFormula;
35987
+ constructor(id, compiledFormula, format, style, dependencies, sheetId, getRangeString) {
35782
35988
  this.id = id;
35783
- this.compiledFormula = compiledFormula;
35784
35989
  this.format = format;
35785
35990
  this.style = style;
35786
- this.dependencies = dependencies;
35787
35991
  this.sheetId = sheetId;
35788
- this.getFormulaCellContent = getFormulaCellContent;
35992
+ this.getRangeString = getRangeString;
35993
+ let rangeIndex = 0;
35994
+ const tokens = compiledFormula.tokens.map((token) => {
35995
+ if (token.type === "REFERENCE") {
35996
+ const index = rangeIndex++;
35997
+ return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
35998
+ }
35999
+ return token;
36000
+ });
36001
+ this.compiledFormula = {
36002
+ ...compiledFormula,
36003
+ dependencies,
36004
+ tokens,
36005
+ };
35789
36006
  }
35790
36007
  get content() {
35791
- return this.getFormulaCellContent(this.sheetId, {
35792
- dependencies: this.dependencies,
35793
- compiledFormula: this.compiledFormula,
35794
- });
36008
+ return concat(this.compiledFormula.tokens.map((token) => token.value));
36009
+ }
36010
+ }
36011
+ class RangeReferenceToken {
36012
+ getValue;
36013
+ type = "REFERENCE";
36014
+ constructor(getValue) {
36015
+ this.getValue = getValue;
36016
+ }
36017
+ get value() {
36018
+ return this.getValue();
35795
36019
  }
35796
36020
  }
35797
36021
 
@@ -36440,6 +36664,9 @@
36440
36664
  return this.rules[sheetId].find((rule) => rule.id === id);
36441
36665
  }
36442
36666
  getValidationRuleForCell({ sheetId, col, row }) {
36667
+ if (!this.rules[sheetId]) {
36668
+ return undefined;
36669
+ }
36443
36670
  for (const rule of this.rules[sheetId]) {
36444
36671
  for (const range of rule.ranges) {
36445
36672
  if (isInside(col, row, range.zone)) {
@@ -36936,6 +37163,13 @@
36936
37163
  }
36937
37164
  onDeleteColumnsRows(cmd) {
36938
37165
  for (const table of this.getFilterTables(cmd.sheetId)) {
37166
+ // Remove the filter tables whose data filter headers are in the removed rows.
37167
+ if (cmd.dimension === "ROW" && cmd.elements.includes(table.zone.top)) {
37168
+ const tables = { ...this.tables[cmd.sheetId] };
37169
+ delete tables[table.id];
37170
+ this.history.update("tables", cmd.sheetId, tables);
37171
+ continue;
37172
+ }
36939
37173
  const zone = reduceZoneOnDeletion(table.zone, cmd.dimension === "COL" ? "left" : "top", cmd.elements);
36940
37174
  if (!zone) {
36941
37175
  const tables = { ...this.tables[cmd.sheetId] };
@@ -37605,7 +37839,7 @@
37605
37839
  const rangeString = this.getters.getRangeString(expandedRange, forSheetId);
37606
37840
  if (this.isSingleCellOrMerge(rangeImpl.sheetId, rangeImpl.zone)) {
37607
37841
  const { sheetName, xc } = splitReference(rangeString);
37608
- return `${sheetName !== undefined ? getCanonicalSheetName(sheetName) + "!" : ""}${xc.split(":")[0]}`;
37842
+ return getFullReference(sheetName, xc.split(":")[0]);
37609
37843
  }
37610
37844
  return rangeString;
37611
37845
  }
@@ -39030,10 +39264,13 @@
39030
39264
  // begin with the end.
39031
39265
  rows.sort((a, b) => b - a);
39032
39266
  for (let group of groupConsecutive(rows)) {
39267
+ // indexes are sorted in the descending order
39268
+ const from = group[group.length - 1];
39269
+ const to = group[0];
39033
39270
  // Move the cells.
39034
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
39035
- // Effectively delete the element and recompute the left-right/top-bottom.
39036
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
39271
+ this.moveCellOnRowsDeletion(sheet, from, to);
39272
+ // Effectively delete the rows
39273
+ this.updateRowsStructureOnDeletion(sheet, from, to);
39037
39274
  }
39038
39275
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
39039
39276
  if (count) {
@@ -39055,8 +39292,6 @@
39055
39292
  this.addEmptyRows(sheet, quantity);
39056
39293
  // Move the cells.
39057
39294
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
39058
- // Recompute the left-right/top-bottom.
39059
- this.updateRowsStructureOnAddition(sheet, row, quantity);
39060
39295
  if (index < sheet.panes.ySplit) {
39061
39296
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
39062
39297
  }
@@ -39157,35 +39392,20 @@
39157
39392
  }
39158
39393
  }
39159
39394
  }
39160
- updateRowsStructureOnDeletion(index, sheet) {
39395
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
39161
39396
  const rows = [];
39162
- const cellsQueue = sheet.rows.map((row) => row.cells);
39397
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
39163
39398
  for (let i in sheet.rows) {
39164
- if (Number(i) === index) {
39399
+ const row = Number(i);
39400
+ if (row >= deleteFromRow && row <= deleteToRow) {
39165
39401
  continue;
39166
39402
  }
39167
39403
  rows.push({
39168
- cells: cellsQueue.shift(),
39404
+ cells: cellsQueue.pop(),
39169
39405
  });
39170
39406
  }
39171
39407
  this.history.update("sheets", sheet.id, "rows", rows);
39172
39408
  }
39173
- /**
39174
- * Update the rows of the sheet after an addition:
39175
- * - Rename the rows
39176
- *
39177
- * @param sheet Sheet on which the deletion occurs
39178
- * @param addedRow Index of the added row
39179
- * @param rowsToAdd Number of the rows to add
39180
- */
39181
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
39182
- const rows = [];
39183
- const cellsQueue = sheet.rows.map((row) => row.cells);
39184
- sheet.rows.forEach(() => rows.push({
39185
- cells: cellsQueue.shift(),
39186
- }));
39187
- this.history.update("sheets", sheet.id, "rows", rows);
39188
- }
39189
39409
  /**
39190
39410
  * Add empty rows at the end of the rows
39191
39411
  *
@@ -39712,6 +39932,7 @@
39712
39932
  getters;
39713
39933
  computeCell;
39714
39934
  evalContext;
39935
+ rangeCache = {};
39715
39936
  constructor(context, getters, computeCell) {
39716
39937
  this.getters = getters;
39717
39938
  this.computeCell = computeCell;
@@ -39734,7 +39955,8 @@
39734
39955
  refFn(range, isMeta, functionName, paramNumber) {
39735
39956
  if (isMeta) {
39736
39957
  // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
39737
- return { value: zoneToXc(range.zone) };
39958
+ const sheetName = this.getters.getSheetName(range.sheetId);
39959
+ return { value: getFullReference(sheetName, zoneToXc(range.zone)) };
39738
39960
  }
39739
39961
  if (!isZoneValid(range.zone)) {
39740
39962
  throw new InvalidReferenceError();
@@ -39797,17 +40019,24 @@
39797
40019
  if (!_zone) {
39798
40020
  return [[]];
39799
40021
  }
40022
+ const { top, left, bottom, right } = zone;
40023
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
40024
+ if (cacheKey in this.rangeCache) {
40025
+ return this.rangeCache[cacheKey];
40026
+ }
39800
40027
  const height = _zone.bottom - _zone.top + 1;
39801
40028
  const width = _zone.right - _zone.left + 1;
39802
- const matrix = Array.from({ length: width }, () => Array.from({ length: height }));
40029
+ const matrix = new Array(width);
39803
40030
  // Performance issue: nested loop is faster than a map here
39804
40031
  for (let col = _zone.left; col <= _zone.right; col++) {
40032
+ const colIndex = col - _zone.left;
40033
+ matrix[colIndex] = new Array(height);
39805
40034
  for (let row = _zone.top; row <= _zone.bottom; row++) {
39806
- const colIndex = col - _zone.left;
39807
40035
  const rowIndex = row - _zone.top;
39808
40036
  matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
39809
40037
  }
39810
40038
  }
40039
+ this.rangeCache[cacheKey] = matrix;
39811
40040
  return matrix;
39812
40041
  }
39813
40042
  }
@@ -39984,6 +40213,13 @@
39984
40213
  return (this.evaluatedCells.get(this.encodePosition(position)) ||
39985
40214
  createEvaluatedCell("", { locale: this.getters.getLocale() }));
39986
40215
  }
40216
+ getSpreadPositionsOf(position) {
40217
+ const positionId = this.encodePosition(position);
40218
+ if (!this.spreadingRelations.isArrayFormula(positionId)) {
40219
+ return [];
40220
+ }
40221
+ return Array.from(this.spreadingRelations.getArrayResultPositionIds(positionId)).map(this.decodePosition.bind(this));
40222
+ }
39987
40223
  getArrayFormulaSpreadingOn(position) {
39988
40224
  const positionId = this.encodePosition(position);
39989
40225
  const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
@@ -40006,6 +40242,7 @@
40006
40242
  this.formulaDependencies().addDependencies(positionId, dependencies);
40007
40243
  }
40008
40244
  updateCompilationParameters() {
40245
+ // rebuild the compilation parameters (with a clean cache)
40009
40246
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40010
40247
  }
40011
40248
  evaluateCells(positions) {
@@ -40049,6 +40286,16 @@
40049
40286
  this.evaluatedCells = new Map();
40050
40287
  this.evaluate(this.getAllCells());
40051
40288
  }
40289
+ evaluateFormula(sheetId, formulaString) {
40290
+ const compiledFormula = compile(formulaString);
40291
+ const ranges = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
40292
+ this.updateCompilationParameters();
40293
+ const result = updateEvalContextAndExecute({ ...compiledFormula, dependencies: ranges }, this.compilationParams, sheetId);
40294
+ if (isMatrix(result)) {
40295
+ return matrixMap(result, (cell) => cell.value);
40296
+ }
40297
+ return result.value;
40298
+ }
40052
40299
  getAllCells() {
40053
40300
  const positionIds = new JetSet();
40054
40301
  for (const sheetId of this.getters.getSheetIds()) {
@@ -40075,6 +40322,7 @@
40075
40322
  this.nextPositionsToUpdate = cells;
40076
40323
  let currentIteration = 0;
40077
40324
  while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
40325
+ this.updateCompilationParameters();
40078
40326
  const positionIds = Array.from(this.nextPositionsToUpdate);
40079
40327
  this.nextPositionsToUpdate.clear();
40080
40328
  for (let i = 0; i < positionIds.length; ++i) {
@@ -40098,7 +40346,8 @@
40098
40346
  if (!this.blockedArrayFormulas.has(positionId)) {
40099
40347
  this.invalidateSpreading(positionId);
40100
40348
  }
40101
- const cell = this.getCell(positionId);
40349
+ const cellPosition = this.decodePosition(positionId);
40350
+ const cell = this.getters.getCell(cellPosition);
40102
40351
  if (cell === undefined) {
40103
40352
  return createEvaluatedCell("", { locale: this.getters.getLocale() });
40104
40353
  }
@@ -40109,7 +40358,7 @@
40109
40358
  }
40110
40359
  this.cellsBeingComputed.add(cellId);
40111
40360
  return cell.isFormula
40112
- ? this.computeFormulaCell(cell)
40361
+ ? this.computeFormulaCell(cellPosition.sheetId, cell)
40113
40362
  : evaluateLiteral(cell.content, { format: cell.format, locale: this.getters.getLocale() });
40114
40363
  }
40115
40364
  catch (e) {
@@ -40135,14 +40384,9 @@
40135
40384
  e.message = e.message.replace("[[FUNCTION_NAME]]", __lastFnCalled);
40136
40385
  return errorCell(e);
40137
40386
  }
40138
- computeFormulaCell(cellData) {
40387
+ computeFormulaCell(sheetId, cellData) {
40139
40388
  const cellId = cellData.id;
40140
- this.compilationParams[2].__originCellXC = () => {
40141
- // compute the value lazily for performance reasons
40142
- const position = this.compilationParams[2].getters.getCellPosition(cellId);
40143
- return toXC(position.col, position.row);
40144
- };
40145
- const formulaReturn = cellData.compiledFormula.execute(cellData.dependencies, ...this.compilationParams);
40389
+ const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, sheetId, cellId);
40146
40390
  if (!isMatrix(formulaReturn)) {
40147
40391
  return createEvaluatedCell(formulaReturn.value, {
40148
40392
  format: cellData.format || formulaReturn.format,
@@ -40242,7 +40486,7 @@
40242
40486
  return [];
40243
40487
  }
40244
40488
  const dependencies = [];
40245
- for (const range of cell.dependencies) {
40489
+ for (const range of cell.compiledFormula.dependencies) {
40246
40490
  if (range.invalidSheetName || range.invalidXc) {
40247
40491
  continue;
40248
40492
  }
@@ -40344,6 +40588,18 @@
40344
40588
  return sheetId;
40345
40589
  }
40346
40590
  }
40591
+ function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
40592
+ compilationParams[2].__originCellXC = lazy(() => {
40593
+ if (!cellId) {
40594
+ return undefined;
40595
+ }
40596
+ // compute the value lazily for performance reasons
40597
+ const position = compilationParams[2].getters.getCellPosition(cellId);
40598
+ return toXC(position.col, position.row);
40599
+ });
40600
+ compilationParams[2].__originSheetId = sheetId;
40601
+ return compiledFormula.execute(compiledFormula.dependencies, ...compilationParams);
40602
+ }
40347
40603
 
40348
40604
  //#region
40349
40605
  // ---------------------------------------------------------------------------
@@ -40438,7 +40694,6 @@
40438
40694
  // of other cells depending on it, at the next iteration.
40439
40695
  //#endregion
40440
40696
  class EvaluationPlugin extends UIPlugin {
40441
- config;
40442
40697
  static getters = [
40443
40698
  "evaluateFormula",
40444
40699
  "getCorrespondingFormulaCell",
@@ -40448,15 +40703,14 @@
40448
40703
  "getEvaluatedCell",
40449
40704
  "getEvaluatedCells",
40450
40705
  "getEvaluatedCellsInZone",
40706
+ "getSpreadPositionsOf",
40707
+ "getArrayFormulaSpreadingOn",
40451
40708
  ];
40452
40709
  shouldRebuildDependenciesGraph = true;
40453
40710
  evaluator;
40454
- compilationParams;
40455
40711
  positionsToUpdate = [];
40456
40712
  constructor(config) {
40457
40713
  super(config);
40458
- this.config = config;
40459
- this.compilationParams = this.getCompilationParameters();
40460
40714
  this.evaluator = new Evaluator(config.custom, this.getters);
40461
40715
  }
40462
40716
  // ---------------------------------------------------------------------------
@@ -40481,10 +40735,6 @@
40481
40735
  case "EVALUATE_CELLS":
40482
40736
  this.evaluator.evaluateAllCells();
40483
40737
  break;
40484
- case "UPDATE_LOCALE":
40485
- this.compilationParams = this.getCompilationParameters();
40486
- this.evaluator.updateCompilationParameters();
40487
- break;
40488
40738
  }
40489
40739
  }
40490
40740
  finalize() {
@@ -40502,16 +40752,7 @@
40502
40752
  // Getters
40503
40753
  // ---------------------------------------------------------------------------
40504
40754
  evaluateFormula(sheetId, formulaString) {
40505
- const compiledFormula = compile(formulaString);
40506
- const ranges = [];
40507
- for (let xc of compiledFormula.dependencies) {
40508
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
40509
- }
40510
- const array = compiledFormula.execute(ranges, ...this.compilationParams);
40511
- if (isMatrix(array)) {
40512
- return array.map((col) => col.map((row) => row.value));
40513
- }
40514
- return array.value;
40755
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
40515
40756
  }
40516
40757
  /**
40517
40758
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -40557,6 +40798,12 @@
40557
40798
  getEvaluatedCellsInZone(sheetId, zone) {
40558
40799
  return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
40559
40800
  }
40801
+ getSpreadPositionsOf(position) {
40802
+ return this.evaluator.getSpreadPositionsOf(position);
40803
+ }
40804
+ getArrayFormulaSpreadingOn(position) {
40805
+ return this.evaluator.getArrayFormulaSpreadingOn(position);
40806
+ }
40560
40807
  // ---------------------------------------------------------------------------
40561
40808
  // Export
40562
40809
  // ---------------------------------------------------------------------------
@@ -40594,13 +40841,13 @@
40594
40841
  */
40595
40842
  getCorrespondingFormulaCell(position) {
40596
40843
  const cell = this.getters.getCell(position);
40597
- if (cell && cell.content) {
40598
- if (cell.isFormula && !isBadExpression(cell.content)) {
40599
- return cell;
40600
- }
40844
+ if (cell && cell.isFormula) {
40845
+ return isBadExpression(cell.compiledFormula.tokens) ? undefined : cell;
40846
+ }
40847
+ else if (cell && cell.content) {
40601
40848
  return undefined;
40602
40849
  }
40603
- const spreadingFormulaPosition = this.evaluator.getArrayFormulaSpreadingOn(position);
40850
+ const spreadingFormulaPosition = this.getArrayFormulaSpreadingOn(position);
40604
40851
  if (spreadingFormulaPosition === undefined) {
40605
40852
  return undefined;
40606
40853
  }
@@ -40610,13 +40857,10 @@
40610
40857
  }
40611
40858
  return undefined;
40612
40859
  }
40613
- getCompilationParameters() {
40614
- return buildCompilationParameters(this.config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position));
40615
- }
40616
40860
  }
40617
- function isBadExpression(formula) {
40861
+ function isBadExpression(tokens) {
40618
40862
  try {
40619
- compile(formula);
40863
+ compileTokens(tokens);
40620
40864
  return false;
40621
40865
  }
40622
40866
  catch (error) {
@@ -40785,22 +41029,20 @@
40785
41029
  invalidateCFEvaluationCommands.has(cmd.type) ||
40786
41030
  cmd.type === "EVALUATE_CELLS" ||
40787
41031
  cmd.type === "UPDATE_CELL") {
40788
- if (cmd.type !== "UNDO" && cmd.type !== "REDO") {
40789
- for (const chartId in this.charts) {
40790
- this.history.update("charts", chartId, undefined);
40791
- }
41032
+ for (const chartId in this.charts) {
41033
+ this.charts[chartId] = undefined;
40792
41034
  }
40793
41035
  }
40794
41036
  switch (cmd.type) {
40795
41037
  case "UPDATE_CHART":
40796
41038
  case "CREATE_CHART":
40797
41039
  case "DELETE_FIGURE":
40798
- this.history.update("charts", cmd.id, undefined);
41040
+ this.charts[cmd.id] = undefined;
40799
41041
  break;
40800
41042
  case "DELETE_SHEET":
40801
41043
  for (let chartId in this.charts) {
40802
41044
  if (!this.getters.isChartDefined(chartId)) {
40803
- this.history.update("charts", chartId, undefined);
41045
+ this.charts[chartId] = undefined;
40804
41046
  }
40805
41047
  }
40806
41048
  break;
@@ -40812,8 +41054,7 @@
40812
41054
  if (!chart) {
40813
41055
  throw new Error(`No chart for the given id: ${figureId}`);
40814
41056
  }
40815
- const runtime = this.createRuntimeChart(chart);
40816
- this.history.update("charts", figureId, runtime);
41057
+ this.charts[figureId] = this.createRuntimeChart(chart);
40817
41058
  }
40818
41059
  return this.charts[figureId];
40819
41060
  }
@@ -40965,7 +41206,10 @@
40965
41206
  const values = cf.rule.values.map((value, i) => {
40966
41207
  const compiledFormula = formulas[i];
40967
41208
  if (compiledFormula) {
40968
- return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, compiledFormula, compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)));
41209
+ return this.getters.getTranslatedCellFormula(sheetId, col - zone.left, row - zone.top, {
41210
+ ...compiledFormula,
41211
+ dependencies: compiledFormula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41212
+ });
40969
41213
  }
40970
41214
  return value;
40971
41215
  });
@@ -41337,7 +41581,10 @@
41337
41581
  }
41338
41582
  try {
41339
41583
  const formula = compile(value);
41340
- const translatedFormula = this.getters.getTranslatedCellFormula(sheetId, offset.col, offset.row, formula, formula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)));
41584
+ const translatedFormula = this.getters.getTranslatedCellFormula(sheetId, offset.col, offset.row, {
41585
+ ...formula,
41586
+ dependencies: formula.dependencies.map((d) => this.getters.getRangeFromSheetXC(sheetId, d)),
41587
+ });
41341
41588
  const evaluated = this.getters.evaluateFormula(sheetId, translatedFormula);
41342
41589
  return evaluated && !isMatrix(evaluated) ? evaluated.toString() : "";
41343
41590
  }
@@ -43308,11 +43555,26 @@
43308
43555
  let cellsInRow = [];
43309
43556
  for (let col of columnsIndex) {
43310
43557
  const position = { col, row, sheetId };
43558
+ const spreader = getters.getArrayFormulaSpreadingOn(position);
43559
+ let cell = getters.getCell(position);
43560
+ const evaluatedCell = getters.getEvaluatedCell(position);
43561
+ if (spreader) {
43562
+ const isSpreaderCopied = rowsIndex.includes(spreader.row) && columnsIndex.includes(spreader.col);
43563
+ const content = isSpreaderCopied
43564
+ ? ""
43565
+ : formatValue(evaluatedCell.value, { locale: getters.getLocale() });
43566
+ cell = {
43567
+ id: cell?.id || "",
43568
+ style: cell?.style,
43569
+ format: evaluatedCell.format,
43570
+ content,
43571
+ isFormula: false,
43572
+ };
43573
+ }
43311
43574
  cellsInRow.push({
43312
- cell: getters.getCell(position),
43313
- style: getters.getCellComputedStyle(position),
43314
- evaluatedCell: getters.getEvaluatedCell(position),
43575
+ cell,
43315
43576
  border: getters.getCellBorder(position) || undefined,
43577
+ evaluatedCell,
43316
43578
  position,
43317
43579
  });
43318
43580
  }
@@ -43434,13 +43696,6 @@
43434
43696
  });
43435
43697
  }
43436
43698
  this.pasteCopiedTables(target);
43437
- this.cells.forEach((row) => {
43438
- row.forEach((c) => {
43439
- if (c.cell) {
43440
- c.cell = undefined;
43441
- }
43442
- });
43443
- });
43444
43699
  }
43445
43700
  /**
43446
43701
  * The clipped zone is copied as many times as it fits in the target.
@@ -43514,7 +43769,7 @@
43514
43769
  clearClippedZones() {
43515
43770
  for (const row of this.cells) {
43516
43771
  for (const cell of row) {
43517
- if (cell.cell) {
43772
+ if (cell?.cell) {
43518
43773
  this.dispatch("CLEAR_CELL", cell.position);
43519
43774
  }
43520
43775
  }
@@ -43540,6 +43795,9 @@
43540
43795
  const rowCells = this.cells[r];
43541
43796
  for (let c = 0; c < width; c++) {
43542
43797
  const origin = rowCells[c];
43798
+ if (!origin) {
43799
+ continue;
43800
+ }
43543
43801
  const position = { col: col + c, row: row + r, sheetId: sheetId };
43544
43802
  // TODO: refactor this part. the "Paste merge" action is also executed with
43545
43803
  // MOVE_RANGES in pasteFromCut. Adding a condition on the operation type here
@@ -43563,53 +43821,42 @@
43563
43821
  pasteCell(origin, target, operation, clipboardOption) {
43564
43822
  const { sheetId, col, row } = target;
43565
43823
  const targetCell = this.getters.getEvaluatedCell(target);
43566
- if (clipboardOption?.pasteOption !== "onlyValue") {
43567
- const targetBorders = this.getters.getCellBorder(target);
43568
- const originBorders = origin.border;
43569
- const border = {
43570
- top: targetBorders?.top || originBorders?.top,
43571
- bottom: targetBorders?.bottom || originBorders?.bottom,
43572
- left: targetBorders?.left || originBorders?.left,
43573
- right: targetBorders?.right || originBorders?.right,
43574
- };
43575
- this.dispatch("SET_BORDER", { sheetId, col, row, border });
43824
+ if (clipboardOption?.pasteOption === "onlyValue") {
43825
+ const locale = this.getters.getLocale();
43826
+ const content = formatValue(origin.evaluatedCell.value, { locale });
43827
+ this.dispatch("UPDATE_CELL", { ...target, content });
43828
+ return;
43576
43829
  }
43577
- if (origin.cell) {
43578
- if (clipboardOption?.pasteOption === "onlyFormat") {
43579
- this.dispatch("UPDATE_CELL", {
43580
- ...target,
43581
- style: origin.cell.style,
43582
- format: origin.evaluatedCell.format,
43583
- });
43584
- return;
43585
- }
43586
- if (clipboardOption?.pasteOption === "onlyValue") {
43587
- const locale = this.getters.getLocale();
43588
- const content = formatValue(origin.evaluatedCell.value, { locale });
43589
- this.dispatch("UPDATE_CELL", { ...target, content });
43590
- return;
43591
- }
43592
- let content = origin.cell.content;
43593
- if (origin.cell.isFormula && operation === "COPY") {
43594
- content = this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula, origin.cell.dependencies);
43595
- }
43830
+ const targetBorders = this.getters.getCellBorder(target);
43831
+ const originBorders = origin.border;
43832
+ const border = {
43833
+ top: targetBorders?.top || originBorders?.top,
43834
+ bottom: targetBorders?.bottom || originBorders?.bottom,
43835
+ left: targetBorders?.left || originBorders?.left,
43836
+ right: targetBorders?.right || originBorders?.right,
43837
+ };
43838
+ this.dispatch("SET_BORDER", { sheetId, col, row, border });
43839
+ if (clipboardOption?.pasteOption === "onlyFormat") {
43840
+ this.dispatch("UPDATE_CELL", {
43841
+ ...target,
43842
+ style: origin.cell?.style ?? null,
43843
+ format: origin.cell?.format ?? origin.evaluatedCell.format ?? targetCell.format,
43844
+ });
43845
+ return;
43846
+ }
43847
+ const content = origin.cell && origin.cell.isFormula && operation === "COPY"
43848
+ ? this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula)
43849
+ : origin.cell?.content;
43850
+ if (content !== "" || origin.cell?.format || origin.cell?.style) {
43596
43851
  this.dispatch("UPDATE_CELL", {
43597
43852
  ...target,
43598
43853
  content,
43599
- style: origin.cell.style || null,
43600
- format: origin.cell.format,
43854
+ style: origin.cell?.style || null,
43855
+ format: origin.cell?.format,
43601
43856
  });
43602
43857
  }
43603
43858
  else if (targetCell) {
43604
- if (clipboardOption?.pasteOption === "onlyValue") {
43605
- this.dispatch("UPDATE_CELL", { ...target, content: "" });
43606
- }
43607
- else if (clipboardOption?.pasteOption === "onlyFormat") {
43608
- this.dispatch("UPDATE_CELL", { ...target, style: null, format: "" });
43609
- }
43610
- else {
43611
- this.dispatch("CLEAR_CELL", target);
43612
- }
43859
+ this.dispatch("CLEAR_CELL", target);
43613
43860
  }
43614
43861
  }
43615
43862
  /**
@@ -43671,22 +43918,28 @@
43671
43918
  return (this.cells
43672
43919
  .map((cells) => {
43673
43920
  return cells
43674
- .map((c) => this.getters.shouldShowFormulas() && c.cell?.isFormula
43921
+ .map((c) => this.getters.shouldShowFormulas() && c?.cell?.isFormula
43675
43922
  ? c.cell?.content || ""
43676
- : c.evaluatedCell?.formattedValue || "")
43923
+ : c?.evaluatedCell?.formattedValue || "")
43677
43924
  .join("\t");
43678
43925
  })
43679
43926
  .join("\n") || "\t");
43680
43927
  }
43681
43928
  getHTMLContent() {
43682
43929
  if (this.cells.length === 1 && this.cells[0].length === 1) {
43930
+ if (!this.cells[0][0]) {
43931
+ return "";
43932
+ }
43683
43933
  return this.getters.getCellText(this.cells[0][0].position);
43684
43934
  }
43685
43935
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
43686
43936
  for (const row of this.cells) {
43687
43937
  htmlTable += "<tr>";
43688
43938
  for (const cell of row) {
43689
- const cssStyle = cssPropertiesToCss(cellStyleToCss(cell.style));
43939
+ if (!cell) {
43940
+ continue;
43941
+ }
43942
+ const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
43690
43943
  const cellText = this.getters.getCellText(cell.position);
43691
43944
  htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
43692
43945
  }
@@ -44014,24 +44267,49 @@
44014
44267
  */
44015
44268
  class FindAndReplacePlugin extends UIPlugin {
44016
44269
  static layers = [3 /* LAYERS.Search */];
44017
- static getters = ["getSearchMatches", "getCurrentSelectedMatchIndex"];
44018
- searchMatches = [];
44270
+ static getters = [
44271
+ "getSearchMatches",
44272
+ "getCurrentSelectedMatchIndex",
44273
+ "getSearchOptions",
44274
+ "getAllSheetMatchesCount",
44275
+ "getActiveSheetMatchesCount",
44276
+ "getSpecificRangeMatchesCount",
44277
+ ];
44278
+ allSheetsMatches = [];
44279
+ activeSheetMatches = [];
44280
+ specificRangeMatches = [];
44281
+ // fixme: why do we make selectedMatchIndex on top of a selected
44282
+ // property in the matches?
44019
44283
  selectedMatchIndex = null;
44020
44284
  currentSearchRegex = null;
44021
44285
  searchOptions = {
44022
44286
  matchCase: false,
44023
44287
  exactMatch: false,
44024
44288
  searchFormulas: false,
44289
+ searchScope: "allSheets",
44290
+ specificRange: undefined,
44025
44291
  };
44026
44292
  toSearch = "";
44027
44293
  isSearchDirty = false;
44294
+ get searchMatches() {
44295
+ switch (this.searchOptions.searchScope) {
44296
+ case "allSheets":
44297
+ return this.allSheetsMatches;
44298
+ case "activeSheet":
44299
+ return this.activeSheetMatches;
44300
+ case "specificRange":
44301
+ return this.specificRangeMatches;
44302
+ }
44303
+ }
44028
44304
  // ---------------------------------------------------------------------------
44029
44305
  // Command Handling
44030
44306
  // ---------------------------------------------------------------------------
44031
44307
  handle(cmd) {
44032
44308
  switch (cmd.type) {
44033
44309
  case "UPDATE_SEARCH":
44034
- this.updateSearch(cmd.toSearch, cmd.searchOptions);
44310
+ const rangeData = cmd.searchOptions.specificRange;
44311
+ const specificRange = rangeData && this.getters.getRangeFromRangeData(rangeData);
44312
+ this.updateSearch(cmd.toSearch, { ...cmd.searchOptions, specificRange });
44035
44313
  break;
44036
44314
  case "CLEAR_SEARCH":
44037
44315
  this.clearSearch();
@@ -44061,7 +44339,9 @@
44061
44339
  this.isSearchDirty = true;
44062
44340
  break;
44063
44341
  case "ACTIVATE_SHEET":
44064
- this.refreshSearch();
44342
+ if (this.searchOptions.searchScope === "activeSheet") {
44343
+ this.isSearchDirty = true;
44344
+ }
44065
44345
  break;
44066
44346
  }
44067
44347
  }
@@ -44080,6 +44360,18 @@
44080
44360
  getCurrentSelectedMatchIndex() {
44081
44361
  return this.selectedMatchIndex;
44082
44362
  }
44363
+ getSearchOptions() {
44364
+ return { ...this.searchOptions, specificRange: this.searchOptions.specificRange?.rangeData };
44365
+ }
44366
+ getAllSheetMatchesCount() {
44367
+ return this.allSheetsMatches.length;
44368
+ }
44369
+ getActiveSheetMatchesCount() {
44370
+ return this.activeSheetMatches.length;
44371
+ }
44372
+ getSpecificRangeMatchesCount() {
44373
+ return this.specificRangeMatches.length;
44374
+ }
44083
44375
  // ---------------------------------------------------------------------------
44084
44376
  // Search
44085
44377
  // ---------------------------------------------------------------------------
@@ -44100,8 +44392,8 @@
44100
44392
  * refresh the matches according to the current search options
44101
44393
  */
44102
44394
  refreshSearch() {
44103
- const matches = this.findMatches();
44104
- this.searchMatches = matches;
44395
+ this.selectedMatchIndex = null;
44396
+ this.findMatches();
44105
44397
  this.selectNextCell(Direction.current);
44106
44398
  }
44107
44399
  /**
@@ -44116,36 +44408,65 @@
44116
44408
  }
44117
44409
  this.currentSearchRegex = RegExp(searchValue, flags);
44118
44410
  }
44411
+ getSheetsInSearchOrder() {
44412
+ switch (this.searchOptions.searchScope) {
44413
+ case "allSheets":
44414
+ const sheetIds = this.getters.getSheetIds();
44415
+ const activeSheetIndex = sheetIds.findIndex((id) => id === this.getters.getActiveSheetId());
44416
+ return [
44417
+ sheetIds[activeSheetIndex],
44418
+ ...sheetIds.slice(activeSheetIndex + 1),
44419
+ ...sheetIds.slice(0, activeSheetIndex),
44420
+ ];
44421
+ case "activeSheet":
44422
+ return [this.getters.getActiveSheetId()];
44423
+ case "specificRange":
44424
+ const specificRange = this.searchOptions.specificRange;
44425
+ if (!specificRange) {
44426
+ return [];
44427
+ }
44428
+ return specificRange ? [specificRange.sheetId] : [];
44429
+ }
44430
+ }
44119
44431
  /**
44120
44432
  * Find matches using the current regex
44121
44433
  */
44122
44434
  findMatches() {
44123
- const sheetId = this.getters.getActiveSheetId();
44124
- const cells = this.getters.getCells(sheetId);
44125
44435
  const matches = [];
44126
44436
  if (this.toSearch) {
44127
- for (const cell of Object.values(cells)) {
44128
- const { col, row } = this.getters.getCellPosition(cell.id);
44437
+ for (const sheetId of this.getters.getSheetIds()) {
44438
+ matches.push(...this.findMatchesInSheet(sheetId));
44439
+ }
44440
+ }
44441
+ // set results
44442
+ this.allSheetsMatches = matches;
44443
+ this.activeSheetMatches = matches.filter((match) => match.sheetId === this.getters.getActiveSheetId());
44444
+ if (this.searchOptions.specificRange) {
44445
+ const { sheetId, zone } = this.searchOptions.specificRange;
44446
+ this.specificRangeMatches = matches.filter((match) => match.sheetId === sheetId && isInside(match.col, match.row, zone));
44447
+ }
44448
+ else {
44449
+ this.specificRangeMatches = [];
44450
+ }
44451
+ }
44452
+ findMatchesInSheet(sheetId) {
44453
+ const matches = [];
44454
+ const { left, right, top, bottom } = this.getters.getSheetZone(sheetId);
44455
+ for (let row = top; row <= bottom; row++) {
44456
+ for (let col = left; col <= right; col++) {
44129
44457
  const isColHidden = this.getters.isColHidden(sheetId, col);
44130
44458
  const isRowHidden = this.getters.isRowHidden(sheetId, row);
44131
44459
  if (isColHidden || isRowHidden) {
44132
44460
  continue;
44133
44461
  }
44134
- if (cell &&
44135
- this.currentSearchRegex &&
44136
- this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
44137
- const match = { col, row, selected: false };
44462
+ const cellPosition = { sheetId, col, row };
44463
+ if (this.currentSearchRegex?.test(this.getSearchableString(cellPosition))) {
44464
+ const match = { sheetId, col, row };
44138
44465
  matches.push(match);
44139
44466
  }
44140
44467
  }
44141
44468
  }
44142
- return matches.sort(this.sortByRowThenColumn);
44143
- }
44144
- sortByRowThenColumn(a, b) {
44145
- if (a.row === b.row) {
44146
- return a.col - b.col;
44147
- }
44148
- return a.row > b.row ? 1 : -1;
44469
+ return matches;
44149
44470
  }
44150
44471
  /**
44151
44472
  * Changes the selected search cell. Given a direction it will
@@ -44163,30 +44484,47 @@
44163
44484
  }
44164
44485
  let nextIndex;
44165
44486
  if (this.selectedMatchIndex === null) {
44166
- nextIndex = 0;
44487
+ let nextMatchIndex = -1;
44488
+ // if search is not available in current sheet will select in next sheet
44489
+ for (const sheetId of this.getSheetsInSearchOrder()) {
44490
+ nextMatchIndex = matches.findIndex((match) => match.sheetId === sheetId);
44491
+ if (nextMatchIndex !== -1) {
44492
+ break;
44493
+ }
44494
+ }
44495
+ nextIndex = nextMatchIndex;
44167
44496
  }
44168
44497
  else {
44169
44498
  nextIndex = this.selectedMatchIndex + indexChange;
44170
44499
  }
44171
- //modulo of negative value to be able to cycle in both directions with previous and next
44172
- nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;
44500
+ // loop index value inside the array (index -1 => last index)
44501
+ nextIndex = (nextIndex + matches.length) % matches.length;
44173
44502
  this.selectedMatchIndex = nextIndex;
44503
+ const selectedMatch = matches[nextIndex];
44504
+ // Switch to the sheet where the match is located
44505
+ if (this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
44506
+ this.dispatch("ACTIVATE_SHEET", {
44507
+ sheetIdFrom: this.getters.getActiveSheetId(),
44508
+ sheetIdTo: selectedMatch.sheetId,
44509
+ });
44510
+ }
44174
44511
  // we want grid selection to capture the selection stream
44175
44512
  this.selection.getBackToDefault();
44176
- this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);
44177
- for (let index = 0; index < this.searchMatches.length; index++) {
44178
- this.searchMatches[index].selected = index === this.selectedMatchIndex;
44179
- }
44513
+ this.selection.selectCell(selectedMatch.col, selectedMatch.row);
44180
44514
  }
44181
44515
  clearSearch() {
44182
44516
  this.toSearch = "";
44183
- this.searchMatches = [];
44184
44517
  this.selectedMatchIndex = null;
44185
44518
  this.currentSearchRegex = null;
44519
+ this.allSheetsMatches = [];
44520
+ this.activeSheetMatches = [];
44521
+ this.specificRangeMatches = [];
44186
44522
  this.searchOptions = {
44187
44523
  matchCase: false,
44188
44524
  exactMatch: false,
44189
44525
  searchFormulas: false,
44526
+ searchScope: "allSheets",
44527
+ specificRange: undefined,
44190
44528
  };
44191
44529
  }
44192
44530
  // ---------------------------------------------------------------------------
@@ -44196,17 +44534,18 @@
44196
44534
  if (!this.currentSearchRegex) {
44197
44535
  return;
44198
44536
  }
44199
- const sheetId = this.getters.getActiveSheetId();
44200
- const cell = this.getters.getCell({ sheetId, ...selectedMatch });
44201
- const { col, row } = selectedMatch;
44537
+ const cell = this.getters.getCell(selectedMatch);
44538
+ if (!cell?.content) {
44539
+ return;
44540
+ }
44202
44541
  if (cell?.isFormula && !this.searchOptions.searchFormulas) {
44203
44542
  return;
44204
44543
  }
44205
44544
  const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + "g");
44206
- const toReplace = this.getSearchableString({ sheetId, col, row });
44545
+ const toReplace = this.getSearchableString(selectedMatch);
44207
44546
  const content = toReplace.replace(replaceRegex, replaceWith);
44208
44547
  const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
44209
- this.dispatch("UPDATE_CELL", { sheetId, col, row, content: canonicalContent });
44548
+ this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
44210
44549
  }
44211
44550
  /**
44212
44551
  * Replace the value of the currently selected match
@@ -44237,7 +44576,10 @@
44237
44576
  drawGrid(renderingContext) {
44238
44577
  const { ctx } = renderingContext;
44239
44578
  const sheetId = this.getters.getActiveSheetId();
44240
- for (const match of this.searchMatches) {
44579
+ for (const [index, match] of this.searchMatches.entries()) {
44580
+ if (match.sheetId !== sheetId) {
44581
+ continue; // Skip drawing matches from other sheets
44582
+ }
44241
44583
  const merge = this.getters.getMerge({ sheetId, col: match.col, row: match.row });
44242
44584
  const left = merge ? merge.left : match.col;
44243
44585
  const right = merge ? merge.right : match.col;
@@ -44247,12 +44589,21 @@
44247
44589
  if (width > 0 && height > 0) {
44248
44590
  ctx.fillStyle = BACKGROUND_COLOR;
44249
44591
  ctx.fillRect(x, y, width, height);
44250
- if (match.selected) {
44592
+ if (index === this.selectedMatchIndex) {
44251
44593
  ctx.strokeStyle = BORDER_COLOR;
44252
44594
  ctx.strokeRect(x, y, width, height);
44253
44595
  }
44254
44596
  }
44255
44597
  }
44598
+ if (this.searchOptions.searchScope === "specificRange") {
44599
+ const range = this.searchOptions.specificRange;
44600
+ if (!range || range.sheetId !== sheetId) {
44601
+ return;
44602
+ }
44603
+ const { x, y, width, height } = this.getters.getVisibleRect(range.zone);
44604
+ ctx.strokeStyle = BORDER_COLOR;
44605
+ ctx.strokeRect(x, y, width, height);
44606
+ }
44256
44607
  }
44257
44608
  }
44258
44609
 
@@ -45535,7 +45886,6 @@
45535
45886
  }
45536
45887
 
45537
45888
  class SortPlugin extends UIPlugin {
45538
- static getters = ["getContiguousZone"];
45539
45889
  allowDispatch(cmd) {
45540
45890
  switch (cmd.type) {
45541
45891
  case "SORT_CELLS":
@@ -45583,143 +45933,6 @@
45583
45933
  }
45584
45934
  return "Success" /* CommandResult.Success */;
45585
45935
  }
45586
- // getContiguousZone helpers
45587
- /**
45588
- * safe-version of expandZone to make sure we don't get out of the grid
45589
- */
45590
- expand(sheetId, z) {
45591
- const { left, right, top, bottom } = this.getters.expandZone(sheetId, z);
45592
- return {
45593
- left: Math.max(0, left),
45594
- right: Math.min(this.getters.getNumberCols(sheetId) - 1, right),
45595
- top: Math.max(0, top),
45596
- bottom: Math.min(this.getters.getNumberRows(sheetId) - 1, bottom),
45597
- };
45598
- }
45599
- /**
45600
- * verifies the presence of at least one non-empty cell in the given zone
45601
- */
45602
- checkExpandedValues(sheetId, z) {
45603
- const expandedZone = this.expand(sheetId, z);
45604
- let cell;
45605
- if (this.getters.doesIntersectMerge(sheetId, expandedZone)) {
45606
- const { left, right, top, bottom } = expandedZone;
45607
- for (let c = left; c <= right; c++) {
45608
- for (let r = top; r <= bottom; r++) {
45609
- const { col, row } = this.getters.getMainCellPosition({ sheetId, col: c, row: r });
45610
- cell = this.getters.getEvaluatedCell({ sheetId, col, row });
45611
- if (cell.formattedValue) {
45612
- return true;
45613
- }
45614
- }
45615
- }
45616
- }
45617
- else {
45618
- for (let cell of this.getters.getEvaluatedCellsInZone(sheetId, expandedZone)) {
45619
- if (cell.formattedValue) {
45620
- return true;
45621
- }
45622
- }
45623
- }
45624
- return false;
45625
- }
45626
- /**
45627
- * This function will expand the provided zone in directions (top, bottom, left, right) for which there
45628
- * are non-null cells on the external boundary of the zone in the given direction.
45629
- *
45630
- * Example:
45631
- * A B C D E
45632
- * ___ ___ ___ ___ ___
45633
- * 1 | | D | | | |
45634
- * ___ ___ ___ ___ ___
45635
- * 2 | 5 | | 1 | D | |
45636
- * ___ ___ ___ ___ ___
45637
- * 3 | | | A | X | |
45638
- * ___ ___ ___ ___ ___
45639
- * 4 | | | | | |
45640
- * ___ ___ ___ ___ ___
45641
- *
45642
- * Let's consider a provided zone corresponding to (C2:D3) - (left:2, right: 3, top:1, bottom:2)
45643
- * - the top external boundary is (B1:E1)
45644
- * Since we have B1='D' != "", we expand to the top: => (C1:D3)
45645
- * The top boundary having reached the top of the grid, we cannot expand in that direction anymore
45646
- *
45647
- * - the left boundary is (B1:B4)
45648
- * since we have B1 again, we expand to the left => (B1:D3)
45649
- *
45650
- * - the right and bottom boundaries are a dead end for now as (E1:E4) and (A4:E4) are empty.
45651
- *
45652
- * - the left boundary is now (A1:A4)
45653
- * Since we have A2=5 != "", we can therefore expand to the left => (A1:D3)
45654
- *
45655
- * This will be the final zone as left and top have reached the boundaries of the grid and
45656
- * the other boundaries (E1:E4) and (A4:E4) are empty.
45657
- *
45658
- * @param sheetId UID of concerned sheet
45659
- * @param zone Zone
45660
- *
45661
- */
45662
- getContiguousZone(sheetId, zone) {
45663
- let { top, bottom, left, right } = zone;
45664
- let canExpand;
45665
- let stop = false;
45666
- while (!stop) {
45667
- stop = true;
45668
- /** top row external boundary */
45669
- if (top > 0) {
45670
- canExpand = this.checkExpandedValues(sheetId, {
45671
- left: left - 1,
45672
- right: right + 1,
45673
- top: top - 1,
45674
- bottom: top - 1,
45675
- });
45676
- if (canExpand) {
45677
- stop = false;
45678
- top--;
45679
- }
45680
- }
45681
- /** left column external boundary */
45682
- if (left > 0) {
45683
- canExpand = this.checkExpandedValues(sheetId, {
45684
- left: left - 1,
45685
- right: left - 1,
45686
- top: top - 1,
45687
- bottom: bottom + 1,
45688
- });
45689
- if (canExpand) {
45690
- stop = false;
45691
- left--;
45692
- }
45693
- }
45694
- /** right column external boundary */
45695
- if (right < this.getters.getNumberCols(sheetId) - 1) {
45696
- canExpand = this.checkExpandedValues(sheetId, {
45697
- left: right + 1,
45698
- right: right + 1,
45699
- top: top - 1,
45700
- bottom: bottom + 1,
45701
- });
45702
- if (canExpand) {
45703
- stop = false;
45704
- right++;
45705
- }
45706
- }
45707
- /** bottom row external boundary */
45708
- if (bottom < this.getters.getNumberRows(sheetId) - 1) {
45709
- canExpand = this.checkExpandedValues(sheetId, {
45710
- left: left - 1,
45711
- right: right + 1,
45712
- top: bottom + 1,
45713
- bottom: bottom + 1,
45714
- });
45715
- if (canExpand) {
45716
- stop = false;
45717
- bottom++;
45718
- }
45719
- }
45720
- }
45721
- return { left, right, top, bottom };
45722
- }
45723
45936
  /**
45724
45937
  * This function evaluates if the top row of a provided zone can be considered as a `header`
45725
45938
  * by checking the following criteria:
@@ -45784,7 +45997,7 @@
45784
45997
  if (cell.isFormula) {
45785
45998
  const position = this.getters.getCellPosition(cell.id);
45786
45999
  // we only have a vertical offset
45787
- content = this.getters.getTranslatedCellFormula(sheetId, 0, newRow - position.row, cell.compiledFormula, cell.dependencies);
46000
+ content = this.getters.getTranslatedCellFormula(sheetId, 0, newRow - position.row, cell.compiledFormula);
45788
46001
  }
45789
46002
  newCellValues.style = cell.style;
45790
46003
  newCellValues.content = content;
@@ -45853,6 +46066,8 @@
45853
46066
  "getTextWidth",
45854
46067
  "getCellText",
45855
46068
  "getCellMultiLineText",
46069
+ "getContiguousZone",
46070
+ "isCellEmpty",
45856
46071
  ];
45857
46072
  ctx = document.createElement("canvas").getContext("2d");
45858
46073
  // ---------------------------------------------------------------------------
@@ -45942,9 +46157,52 @@
45942
46157
  const hasListIcon = !this.getters.isReadonly() && this.getters.cellHasListDataValidationIcon(position);
45943
46158
  return isFilterHeader || hasListIcon;
45944
46159
  }
45945
- // ---------------------------------------------------------------------------
45946
- // Grid manipulation
45947
- // ---------------------------------------------------------------------------
46160
+ /**
46161
+ * Expands the given zone until bordered by empty cells or reached the sheet boundaries.
46162
+ */
46163
+ getContiguousZone(sheetId, zoneToExpand) {
46164
+ /** Try to expand the zone by one col/row in any direction to include a new non-empty cell */
46165
+ const expandZone = (zone) => {
46166
+ for (const col of range(zone.left, zone.right + 1)) {
46167
+ if (!this.isCellEmpty({ sheetId, col, row: zone.top - 1 })) {
46168
+ return { ...zone, top: zone.top - 1 };
46169
+ }
46170
+ if (!this.isCellEmpty({ sheetId, col, row: zone.bottom + 1 })) {
46171
+ return { ...zone, bottom: zone.bottom + 1 };
46172
+ }
46173
+ }
46174
+ for (const row of range(zone.top, zone.bottom + 1)) {
46175
+ if (!this.isCellEmpty({ sheetId, col: zone.left - 1, row })) {
46176
+ return { ...zone, left: zone.left - 1 };
46177
+ }
46178
+ if (!this.isCellEmpty({ sheetId, col: zone.right + 1, row })) {
46179
+ return { ...zone, right: zone.right + 1 };
46180
+ }
46181
+ }
46182
+ return zone;
46183
+ };
46184
+ let hasExpanded = false;
46185
+ let zone = zoneToExpand;
46186
+ do {
46187
+ hasExpanded = false;
46188
+ const newZone = expandZone(zone);
46189
+ if (!isEqual(zone, newZone)) {
46190
+ hasExpanded = true;
46191
+ zone = newZone;
46192
+ continue;
46193
+ }
46194
+ } while (hasExpanded);
46195
+ return zone;
46196
+ }
46197
+ /**
46198
+ * Check if a cell is empty. If the cell is part of a merge,
46199
+ * check if the merge containing the cell is empty.
46200
+ */
46201
+ isCellEmpty(position) {
46202
+ const mainPosition = this.getters.getMainCellPosition(position);
46203
+ const cell = this.getters.getEvaluatedCell(mainPosition);
46204
+ return cell.type === CellValueType.empty;
46205
+ }
45948
46206
  getColMaxWidth(sheetId, index) {
45949
46207
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
45950
46208
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
@@ -46811,11 +47069,14 @@
46811
47069
  }
46812
47070
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
46813
47071
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
47072
+ if (this.state.operation === "CUT") {
47073
+ this.state = undefined;
47074
+ }
46814
47075
  this.lastPasteState = this.state;
46815
47076
  if (this.paintFormatStatus === "oneOff") {
46816
47077
  this.paintFormatStatus = "inactive";
46817
- this.status = "invisible";
46818
47078
  }
47079
+ this.status = "invisible";
46819
47080
  break;
46820
47081
  case "COPY_PASTE_CELLS_ABOVE":
46821
47082
  {
@@ -47042,92 +47303,6 @@
47042
47303
  }
47043
47304
  }
47044
47305
 
47045
- /**
47046
- * Change the reference types inside the given token, if the token represent a range or a cell
47047
- *
47048
- * Eg. :
47049
- * A1 => $A$1 => A$1 => $A1 => A1
47050
- * A1:$B$1 => $A$1:B$1 => A$1:$B1 => $A1:B1 => A1:$B$1
47051
- */
47052
- function loopThroughReferenceType(token) {
47053
- if (token.type !== "REFERENCE")
47054
- return token;
47055
- const { xc, sheetName } = splitReference(token.value);
47056
- const [left, right] = xc.split(":");
47057
- const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
47058
- const updatedLeft = getTokenNextReferenceType(left);
47059
- const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
47060
- return { ...token, value: sheetRef + updatedLeft + updatedRight };
47061
- }
47062
- /**
47063
- * Get a new token with a changed type of reference from the given cell token symbol.
47064
- * Undefined behavior if given a token other than a cell or if the Xc contains a sheet reference
47065
- *
47066
- * A1 => $A$1 => A$1 => $A1 => A1
47067
- */
47068
- function getTokenNextReferenceType(xc) {
47069
- switch (getReferenceType(xc)) {
47070
- case "none":
47071
- xc = setXcToReferenceType(xc, "colrow");
47072
- break;
47073
- case "colrow":
47074
- xc = setXcToReferenceType(xc, "row");
47075
- break;
47076
- case "row":
47077
- xc = setXcToReferenceType(xc, "col");
47078
- break;
47079
- case "col":
47080
- xc = setXcToReferenceType(xc, "none");
47081
- break;
47082
- }
47083
- return xc;
47084
- }
47085
- /**
47086
- * Returns the given XC with the given reference type.
47087
- */
47088
- function setXcToReferenceType(xc, referenceType) {
47089
- xc = xc.replace(/\$/g, "");
47090
- let indexOfNumber;
47091
- switch (referenceType) {
47092
- case "col":
47093
- return "$" + xc;
47094
- case "row":
47095
- indexOfNumber = xc.search(/[0-9]/);
47096
- return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47097
- case "colrow":
47098
- indexOfNumber = xc.search(/[0-9]/);
47099
- xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47100
- return "$" + xc;
47101
- case "none":
47102
- return xc;
47103
- }
47104
- }
47105
- /**
47106
- * Return the type of reference used in the given XC of a cell.
47107
- * Undefined behavior if the XC have a sheet reference
47108
- */
47109
- function getReferenceType(xcCell) {
47110
- if (isColAndRowFixed(xcCell)) {
47111
- return "colrow";
47112
- }
47113
- else if (isColFixed(xcCell)) {
47114
- return "col";
47115
- }
47116
- else if (isRowFixed(xcCell)) {
47117
- return "row";
47118
- }
47119
- return "none";
47120
- }
47121
- function isColFixed(xc) {
47122
- return xc.startsWith("$");
47123
- }
47124
- function isRowFixed(xc) {
47125
- return xc.includes("$", 1);
47126
- }
47127
- function isColAndRowFixed(xc) {
47128
- return xc.startsWith("$") && xc.length > 1 && xc.slice(1).includes("$");
47129
- }
47130
-
47131
47306
  const CELL_DELETED_MESSAGE = _t("The cell you are trying to edit has been deleted.");
47132
47307
  class EditionPlugin extends UIPlugin {
47133
47308
  static getters = [
@@ -47849,6 +48024,8 @@
47849
48024
  case "EVALUATE_CELLS":
47850
48025
  case "ACTIVATE_SHEET":
47851
48026
  case "REMOVE_FILTER_TABLE":
48027
+ case "ADD_COLUMNS_ROWS":
48028
+ case "REMOVE_COLUMNS_ROWS":
47852
48029
  this.isEvaluationDirty = true;
47853
48030
  break;
47854
48031
  case "START":
@@ -48043,32 +48220,32 @@
48043
48220
  {
48044
48221
  name: _t("Sum"),
48045
48222
  types: [CellValueType.number],
48046
- compute: (values, locale) => SUM.compute.bind({ locale })([values]),
48223
+ compute: (values, locale) => sum([[values]], locale),
48047
48224
  },
48048
48225
  {
48049
48226
  name: _t("Avg"),
48050
48227
  types: [CellValueType.number],
48051
- compute: (values, locale) => AVERAGE.compute.bind({ locale })([values]),
48228
+ compute: (values, locale) => average([[values]], locale),
48052
48229
  },
48053
48230
  {
48054
48231
  name: _t("Min"),
48055
48232
  types: [CellValueType.number],
48056
- compute: (values, locale) => MIN.compute.bind({ locale })([values]),
48233
+ compute: (values, locale) => min([[values]], locale),
48057
48234
  },
48058
48235
  {
48059
48236
  name: _t("Max"),
48060
48237
  types: [CellValueType.number],
48061
- compute: (values, locale) => MAX.compute.bind({ locale })([values]),
48238
+ compute: (values, locale) => max([[values]], locale),
48062
48239
  },
48063
48240
  {
48064
48241
  name: _t("Count"),
48065
48242
  types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
48066
- compute: (values, locale) => COUNTA.compute.bind({ locale })([values]),
48243
+ compute: (values) => countAny([[values]]),
48067
48244
  },
48068
48245
  {
48069
48246
  name: _t("Count Numbers"),
48070
48247
  types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
48071
- compute: (values, locale) => COUNT.compute.bind({ locale })([values]),
48248
+ compute: (values, locale) => countNumbers([[values]], locale),
48072
48249
  },
48073
48250
  ];
48074
48251
  /**
@@ -51983,6 +52160,7 @@
51983
52160
  };
51984
52161
  sidePanel;
51985
52162
  composer;
52163
+ spreadsheetRef = owl.useRef("spreadsheet");
51986
52164
  _focusGrid;
51987
52165
  keyDownMapping;
51988
52166
  isViewportTooSmall = false;
@@ -52017,6 +52195,18 @@
52017
52195
  clipboard: this.env.clipboard || instantiateClipboard(),
52018
52196
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
52019
52197
  });
52198
+ owl.useEffect(() => {
52199
+ /**
52200
+ * Only refocus the grid if the active element is not a child of the spreadsheet
52201
+ * (i.e. activeElement is outside of the spreadsheetRef component)
52202
+ * and spreadsheet is a child of that element. Anything else means that the focus
52203
+ * is on an element that needs to keep it.
52204
+ */
52205
+ if (!this.spreadsheetRef.el.contains(document.activeElement) &&
52206
+ document.activeElement?.contains(this.spreadsheetRef.el)) {
52207
+ this.focusGrid();
52208
+ }
52209
+ }, () => [this.env.model.getters.getActiveSheetId()]);
52020
52210
  owl.useExternalListener(window, "resize", () => this.render(true));
52021
52211
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
52022
52212
  this.bindModelEvents();
@@ -52098,7 +52288,7 @@
52098
52288
  }
52099
52289
  focusGrid() {
52100
52290
  if (!this._focusGrid) {
52101
- throw new Error("_focusGrid should be exposed by the grid component");
52291
+ return;
52102
52292
  }
52103
52293
  this._focusGrid();
52104
52294
  }
@@ -53367,7 +53557,7 @@
53367
53557
  scrollIntoView: false,
53368
53558
  });
53369
53559
  }
53370
- const tableZone = this.expandZoneToTable(anchor.zone);
53560
+ const tableZone = this.getters.getContiguousZone(sheetId, anchor.zone);
53371
53561
  return !deepEquals(tableZone, anchor.zone)
53372
53562
  ? this.modifyAnchor({ ...anchor, zone: tableZone }, "updateAnchor", {
53373
53563
  scrollIntoView: false,
@@ -53380,7 +53570,8 @@
53380
53570
  * cells bordering it
53381
53571
  */
53382
53572
  selectTableAroundSelection() {
53383
- const tableZone = this.expandZoneToTable(this.anchor.zone);
53573
+ const sheetId = this.getters.getActiveSheetId();
53574
+ const tableZone = this.getters.getContiguousZone(sheetId, this.anchor.zone);
53384
53575
  return this.modifyAnchor({ ...this.anchor, zone: tableZone }, "updateAnchor", {
53385
53576
  scrollIntoView: false,
53386
53577
  });
@@ -53557,11 +53748,12 @@
53557
53748
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
53558
53749
  */
53559
53750
  getEndOfCluster(startPosition, dim, dir) {
53560
- const sheet = this.getters.getActiveSheet();
53751
+ const sheetId = this.getters.getActiveSheetId();
53561
53752
  let currentPosition = startPosition;
53562
53753
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
53563
53754
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
53564
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
53755
+ let mode = !this.getters.isCellEmpty({ ...currentPosition, sheetId }) &&
53756
+ !this.getters.isCellEmpty({ ...nextCellPosition, sheetId })
53565
53757
  ? "endOfCluster"
53566
53758
  : "nextCluster";
53567
53759
  while (true) {
@@ -53571,7 +53763,7 @@
53571
53763
  currentPosition.row === nextCellPosition.row) {
53572
53764
  break;
53573
53765
  }
53574
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
53766
+ const isNextCellEmpty = this.getters.isCellEmpty({ ...nextCellPosition, sheetId });
53575
53767
  if (mode === "endOfCluster" && isNextCellEmpty) {
53576
53768
  break;
53577
53769
  }
@@ -53584,15 +53776,6 @@
53584
53776
  }
53585
53777
  return dim === "cols" ? currentPosition.col : currentPosition.row;
53586
53778
  }
53587
- /**
53588
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
53589
- * check if the merge containing the cell is empty.
53590
- */
53591
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
53592
- const position = this.getters.getMainCellPosition({ sheetId, col, row });
53593
- const cell = this.getters.getEvaluatedCell(position);
53594
- return cell.type === CellValueType.empty;
53595
- }
53596
53779
  /** Computes the next cell position in the given direction by crossing through merges and skipping hidden cells.
53597
53780
  *
53598
53781
  * This has the same behaviour as getNextAvailablePosition() for certain arguments, but use this method instead
@@ -53611,45 +53794,6 @@
53611
53794
  getPosition() {
53612
53795
  return { ...this.anchor.cell };
53613
53796
  }
53614
- /**
53615
- * Expand the given zone to a table.
53616
- * We define a table by the smallest zone that contain the anchor and that have only empty
53617
- * cells bordering it
53618
- */
53619
- expandZoneToTable(zoneToExpand) {
53620
- /** Try to expand the zone by one col/row in any direction to include a new non-empty cell */
53621
- const expandZone = (zone) => {
53622
- for (const col of range(zone.left, zone.right + 1)) {
53623
- if (!this.isCellEmpty({ col, row: zone.top - 1 })) {
53624
- return { ...zone, top: zone.top - 1 };
53625
- }
53626
- if (!this.isCellEmpty({ col, row: zone.bottom + 1 })) {
53627
- return { ...zone, bottom: zone.bottom + 1 };
53628
- }
53629
- }
53630
- for (const row of range(zone.top, zone.bottom + 1)) {
53631
- if (!this.isCellEmpty({ col: zone.left - 1, row })) {
53632
- return { ...zone, left: zone.left - 1 };
53633
- }
53634
- if (!this.isCellEmpty({ col: zone.right + 1, row })) {
53635
- return { ...zone, right: zone.right + 1 };
53636
- }
53637
- }
53638
- return zone;
53639
- };
53640
- let hasExpanded = false;
53641
- let zone = zoneToExpand;
53642
- do {
53643
- hasExpanded = false;
53644
- const newZone = expandZone(zone);
53645
- if (!isEqual(zone, newZone)) {
53646
- hasExpanded = true;
53647
- zone = newZone;
53648
- continue;
53649
- }
53650
- } while (hasExpanded);
53651
- return zone;
53652
- }
53653
53797
  }
53654
53798
 
53655
53799
  class StateObserver {
@@ -55537,19 +55681,22 @@
55537
55681
  * Check if the given command is allowed by all the plugins and the history.
55538
55682
  */
55539
55683
  checkDispatchAllowed(command) {
55540
- if (isCoreCommand(command)) {
55541
- return this.checkDispatchAllowedCoreCommand(command);
55684
+ const results = isCoreCommand(command)
55685
+ ? this.checkDispatchAllowedCoreCommand(command)
55686
+ : this.checkDispatchAllowedLocalCommand(command);
55687
+ if (results.some((r) => r !== "Success" /* CommandResult.Success */)) {
55688
+ return new DispatchResult(results.flat());
55542
55689
  }
55543
- return this.checkDispatchAllowedLocalCommand(command);
55690
+ return DispatchResult.Success;
55544
55691
  }
55545
55692
  checkDispatchAllowedCoreCommand(command) {
55546
55693
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
55547
55694
  results.push(this.range.allowDispatch(command));
55548
- return new DispatchResult(results.flat());
55695
+ return results;
55549
55696
  }
55550
55697
  checkDispatchAllowedLocalCommand(command) {
55551
55698
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
55552
- return new DispatchResult(results.flat());
55699
+ return results;
55553
55700
  }
55554
55701
  finalize() {
55555
55702
  this.status = 3 /* Status.Finalizing */;
@@ -55844,6 +55991,7 @@
55844
55991
  FigureComponent,
55845
55992
  Menu,
55846
55993
  SelectionInput,
55994
+ ValidationMessages,
55847
55995
  };
55848
55996
  const hooks = {
55849
55997
  useDragAndDropListItems,
@@ -55872,6 +56020,7 @@
55872
56020
  exports.addFunction = addFunction;
55873
56021
  exports.astToFormula = astToFormula;
55874
56022
  exports.compile = compile;
56023
+ exports.compileTokens = compileTokens;
55875
56024
  exports.components = components;
55876
56025
  exports.constants = constants;
55877
56026
  exports.convertAstNodes = convertAstNodes;
@@ -55887,6 +56036,7 @@
55887
56036
  exports.links = links;
55888
56037
  exports.load = load;
55889
56038
  exports.parse = parse;
56039
+ exports.parseTokens = parseTokens;
55890
56040
  exports.readonlyAllowedCommands = readonlyAllowedCommands;
55891
56041
  exports.registries = registries;
55892
56042
  exports.setDefaultSheetViewSize = setDefaultSheetViewSize;
@@ -55894,9 +56044,9 @@
55894
56044
  exports.tokenize = tokenize;
55895
56045
 
55896
56046
 
55897
- __info__.version = "17.1.0-alpha.2";
55898
- __info__.date = "2023-11-03T12:24:57.341Z";
55899
- __info__.hash = "0595868";
56047
+ __info__.version = "17.1.0-alpha.4";
56048
+ __info__.date = "2023-11-24T13:12:24.882Z";
56049
+ __info__.hash = "255821b";
55900
56050
 
55901
56051
 
55902
56052
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);