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