@odoo/o-spreadsheet 17.1.0-alpha.3 → 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.3
6
- * @date 2023-11-16T12:04:09.636Z
7
- * @hash 2ef5b1a
5
+ * @version 17.1.0-alpha.4
6
+ * @date 2023-11-24T13:12:24.882Z
7
+ * @hash 255821b
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -228,7 +228,6 @@
228
228
  const SCROLLBAR_WIDTH = 15;
229
229
  const AUTOFILL_EDGE_LENGTH = 8;
230
230
  const ICON_EDGE_LENGTH = 18;
231
- const UNHIDE_ICON_EDGE_LENGTH = 14;
232
231
  const MIN_CF_ICON_MARGIN = 4;
233
232
  const MIN_CELL_TEXT_MARGIN = 4;
234
233
  const CF_ICON_EDGE_LENGTH = 15;
@@ -692,8 +691,7 @@
692
691
  *
693
692
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
694
693
  */
695
- const whiteSpaceCharacters = [
696
- " ",
694
+ const whiteSpaceSpecialCharacters = [
697
695
  "\t",
698
696
  "\f",
699
697
  "\v",
@@ -708,8 +706,7 @@
708
706
  String.fromCharCode(parseInt("3000", 16)),
709
707
  String.fromCharCode(parseInt("feff", 16)),
710
708
  ];
711
- const whiteSpaceRegexp = new RegExp(whiteSpaceCharacters.join("|"), "g");
712
- const newLineRegex = /\r\n|\r|\n/g;
709
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
713
710
  /**
714
711
  * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
715
712
  * different newlines types by \n.
@@ -717,9 +714,9 @@
717
714
  function replaceSpecialSpaces(text) {
718
715
  if (!text)
719
716
  return "";
720
- text = text.replace(whiteSpaceRegexp, " ");
721
- text = text.replace(newLineRegex, NEWLINE);
722
- return text;
717
+ if (!whiteSpaceRegexp.test(text))
718
+ return text;
719
+ return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
723
720
  }
724
721
  /**
725
722
  * Determine if the numbers are consecutive.
@@ -2061,6 +2058,7 @@
2061
2058
  CommandResult["InvalidNumberOfCriterionValues"] = "InvalidNumberOfCriterionValues";
2062
2059
  CommandResult["BlockingValidationRule"] = "BlockingValidationRule";
2063
2060
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2061
+ CommandResult["NoChanges"] = "NoChanges";
2064
2062
  })(exports.CommandResult || (exports.CommandResult = {}));
2065
2063
 
2066
2064
  const DEFAULT_LOCALES = [
@@ -2190,12 +2188,12 @@
2190
2188
  }
2191
2189
  }
2192
2190
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
2193
- function normalizeString(str) {
2191
+ const normalizeString = memoize(function normalizeString(str) {
2194
2192
  return str
2195
2193
  .toLowerCase()
2196
2194
  .normalize("NFD")
2197
2195
  .replace(/[\u0300-\u036f]/g, "");
2198
- }
2196
+ });
2199
2197
  const expectBooleanValueError = (value) => _t("The function [[FUNCTION_NAME]] expects a boolean value, but '%s' is a text, and cannot be coerced to a number.", value);
2200
2198
  function toBoolean(value) {
2201
2199
  switch (typeof value) {
@@ -3428,11 +3426,18 @@
3428
3426
  return singleCellReference.test(xc);
3429
3427
  }
3430
3428
  function splitReference(ref) {
3429
+ if (!ref.includes("!")) {
3430
+ return { xc: ref };
3431
+ }
3431
3432
  const parts = ref.split("!");
3432
3433
  const xc = parts.pop();
3433
3434
  const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
3434
3435
  return { sheetName, xc };
3435
3436
  }
3437
+ /** Return a reference SheetName!xc from the given arguments */
3438
+ function getFullReference(sheetName, xc) {
3439
+ return sheetName !== undefined ? `${getCanonicalSheetName(sheetName)}!${xc}` : xc;
3440
+ }
3436
3441
 
3437
3442
  /**
3438
3443
  * Convert from a cartesian reference to a Zone
@@ -4541,14 +4546,14 @@
4541
4546
  switch (context.textBaseline) {
4542
4547
  case "top":
4543
4548
  underlineY += boxHeight - 2 * strokeWidth;
4544
- strikeY += boxHeight - textHeight;
4549
+ strikeY += boxHeight / 2 - strokeWidth;
4545
4550
  break;
4546
4551
  case "middle":
4547
4552
  underlineY += boxHeight / 2 - strokeWidth;
4548
4553
  break;
4549
4554
  case "alphabetic":
4550
4555
  underlineY += 2 * strokeWidth;
4551
- strikeY -= textHeight / 2 - strokeWidth / 2;
4556
+ strikeY -= 3 * strokeWidth;
4552
4557
  break;
4553
4558
  case "bottom":
4554
4559
  underlineY = y;
@@ -4611,6 +4616,7 @@
4611
4616
  const children = item.children;
4612
4617
  const description = item.description;
4613
4618
  const icon = item.icon;
4619
+ const secondaryIcon = item.secondaryIcon;
4614
4620
  return {
4615
4621
  id: item.id || uuidGenerator$2.uuidv4(),
4616
4622
  name: typeof name === "function" ? name : () => name,
@@ -4629,6 +4635,7 @@
4629
4635
  isReadonlyAllowed: item.isReadonlyAllowed || false,
4630
4636
  separator: item.separator || false,
4631
4637
  icon: typeof icon === "function" ? icon : () => icon || "",
4638
+ secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
4632
4639
  description: typeof description === "function" ? description : () => description || "",
4633
4640
  textColor: item.textColor,
4634
4641
  sequence: item.sequence || 0,
@@ -6984,11 +6991,9 @@
6984
6991
  padding: ${MENU_VERTICAL_PADDING}px 0px;
6985
6992
  width: ${MENU_WIDTH}px;
6986
6993
  box-sizing: border-box !important;
6994
+ user-select: none;
6987
6995
 
6988
6996
  .o-menu-item {
6989
- display: flex;
6990
- justify-content: space-between;
6991
- align-items: center;
6992
6997
  box-sizing: border-box;
6993
6998
  height: ${MENU_ITEM_HEIGHT}px;
6994
6999
  padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
@@ -6999,20 +7004,12 @@
6999
7004
  min-width: 40%;
7000
7005
  }
7001
7006
 
7002
- &.o-menu-root {
7003
- display: flex;
7004
- justify-content: space-between;
7005
- }
7006
-
7007
7007
  .o-menu-item-icon {
7008
7008
  display: inline-block;
7009
7009
  margin: 0px 8px 0px 0px;
7010
7010
  width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
7011
7011
  line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
7012
7012
  }
7013
- .o-menu-item-root {
7014
- width: 10px;
7015
- }
7016
7013
 
7017
7014
  &:not(.disabled) {
7018
7015
  &:hover,
@@ -7355,7 +7352,7 @@
7355
7352
  return result;
7356
7353
  }
7357
7354
  function tokenizeDebugger(chars) {
7358
- if (chars.current() === "?") {
7355
+ if (chars.current === "?") {
7359
7356
  chars.shift();
7360
7357
  return { type: "DEBUGGER", value: "?" };
7361
7358
  }
@@ -7366,7 +7363,7 @@
7366
7363
  ")": "RIGHT_PAREN",
7367
7364
  };
7368
7365
  function tokenizeMisc(chars) {
7369
- if (chars.current() in misc$1) {
7366
+ if (chars.current in misc$1) {
7370
7367
  const value = chars.shift();
7371
7368
  const type = misc$1[value];
7372
7369
  return { type, value };
@@ -7374,7 +7371,7 @@
7374
7371
  return null;
7375
7372
  }
7376
7373
  function tokenizeArgsSeparator(chars, locale) {
7377
- if (chars.current() === locale.formulaArgSeparator) {
7374
+ if (chars.current === locale.formulaArgSeparator) {
7378
7375
  const value = chars.shift();
7379
7376
  const type = "ARG_SEPARATOR";
7380
7377
  return { type, value };
@@ -7399,14 +7396,13 @@
7399
7396
  return null;
7400
7397
  }
7401
7398
  function tokenizeString(chars) {
7402
- if (chars.current() === '"') {
7399
+ if (chars.current === '"') {
7403
7400
  const startChar = chars.shift();
7404
7401
  let letters = startChar;
7405
- while (chars.current() &&
7406
- (chars.current() !== startChar || letters[letters.length - 1] === "\\")) {
7402
+ while (chars.current && (chars.current !== startChar || letters[letters.length - 1] === "\\")) {
7407
7403
  letters += chars.shift();
7408
7404
  }
7409
- if (chars.current() === '"') {
7405
+ if (chars.current === '"') {
7410
7406
  letters += chars.shift();
7411
7407
  }
7412
7408
  return {
@@ -7433,14 +7429,14 @@
7433
7429
  let result = "";
7434
7430
  // there are two main cases to manage: either something which starts with
7435
7431
  // a ', like 'Sheet 2'A2, or a word-like element.
7436
- if (chars.current() === "'") {
7432
+ if (chars.current === "'") {
7437
7433
  let lastChar = chars.shift();
7438
7434
  result += lastChar;
7439
- while (chars.current()) {
7435
+ while (chars.current) {
7440
7436
  lastChar = chars.shift();
7441
7437
  result += lastChar;
7442
7438
  if (lastChar === "'") {
7443
- if (chars.current() && chars.current() === "'") {
7439
+ if (chars.current && chars.current === "'") {
7444
7440
  lastChar = chars.shift();
7445
7441
  result += lastChar;
7446
7442
  }
@@ -7456,7 +7452,7 @@
7456
7452
  };
7457
7453
  }
7458
7454
  }
7459
- while (chars.current() && separatorRegexp.test(chars.current())) {
7455
+ while (chars.current && separatorRegexp.test(chars.current)) {
7460
7456
  result += chars.shift();
7461
7457
  }
7462
7458
  if (result.length) {
@@ -7471,14 +7467,14 @@
7471
7467
  }
7472
7468
  function tokenizeSpace(chars) {
7473
7469
  let length = 0;
7474
- while (chars.current() === NEWLINE) {
7470
+ while (chars.current === NEWLINE) {
7475
7471
  length++;
7476
7472
  chars.shift();
7477
7473
  }
7478
7474
  if (length) {
7479
7475
  return { type: "SPACE", value: NEWLINE.repeat(length) };
7480
7476
  }
7481
- while (chars.current() === " ") {
7477
+ while (chars.current === " ") {
7482
7478
  length++;
7483
7479
  chars.shift();
7484
7480
  }
@@ -7497,17 +7493,20 @@
7497
7493
  class TokenizingChars {
7498
7494
  text;
7499
7495
  currentIndex = 0;
7496
+ current;
7500
7497
  constructor(text) {
7501
7498
  this.text = text;
7502
- }
7503
- current() {
7504
- return this.text[this.currentIndex];
7499
+ this.current = text[0];
7505
7500
  }
7506
7501
  shift() {
7507
- return this.text[this.currentIndex++];
7502
+ const current = this.current;
7503
+ const next = this.text[++this.currentIndex];
7504
+ this.current = next;
7505
+ return current;
7508
7506
  }
7509
7507
  advanceBy(length) {
7510
7508
  this.currentIndex += length;
7509
+ this.current = this.text[this.currentIndex];
7511
7510
  }
7512
7511
  isOver() {
7513
7512
  return this.currentIndex >= this.text.length;
@@ -11311,7 +11310,7 @@
11311
11310
  ) {
11312
11311
  const _array = toMatrix(arg);
11313
11312
  const _nbRows = toInteger(rows?.value, this.locale);
11314
- const _nbColumns = columns !== undefined ? toInteger(columns.value, this.local) : _array.length;
11313
+ const _nbColumns = columns !== undefined ? toInteger(columns.value, this.locale) : _array.length;
11315
11314
  assert(() => _nbRows >= _array[0].length, _t("The rows arguments (%s) must be greater or equal than the number of rows of the array.", _nbRows.toString()));
11316
11315
  assert(() => _nbColumns >= _array.length, _t("The columns arguments (%s) must be greater or equal than the number of columns of the array.", _nbColumns.toString()));
11317
11316
  return generateMatrix(_nbColumns, _nbRows, (col, row) => col >= _array.length || row >= _array[col].length ? padWith : _array[col][row]);
@@ -11800,6 +11799,10 @@
11800
11799
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11801
11800
  });
11802
11801
 
11802
+ function sum(values, locale) {
11803
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11804
+ }
11805
+
11803
11806
  const DEFAULT_FACTOR = 1;
11804
11807
  const DEFAULT_MODE = 0;
11805
11808
  const DEFAULT_PLACES = 0;
@@ -12751,7 +12754,7 @@
12751
12754
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
12752
12755
  },
12753
12756
  compute: function (...values) {
12754
- return reduceNumbers(values, (acc, a) => acc + a, 0, this.locale);
12757
+ return sum(values, this.locale);
12755
12758
  },
12756
12759
  isExported: true,
12757
12760
  };
@@ -12920,6 +12923,44 @@
12920
12923
  const dims = args[0].length;
12921
12924
  args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
12922
12925
  }
12926
+ function average(values, locale) {
12927
+ let count = 0;
12928
+ const sum = reduceNumbers(values, (acc, a) => {
12929
+ count += 1;
12930
+ return acc + a;
12931
+ }, 0, locale);
12932
+ assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12933
+ return sum / count;
12934
+ }
12935
+ function countNumbers(values, locale) {
12936
+ let count = 0;
12937
+ for (let n of values) {
12938
+ if (isMatrix(n)) {
12939
+ for (let i of n) {
12940
+ for (let j of i) {
12941
+ if (typeof j === "number") {
12942
+ count += 1;
12943
+ }
12944
+ }
12945
+ }
12946
+ }
12947
+ else if (typeof n !== "string" || isNumber(n, locale) || parseDateTime(n, locale)) {
12948
+ count += 1;
12949
+ }
12950
+ }
12951
+ return count;
12952
+ }
12953
+ function countAny(values) {
12954
+ return reduceAny(values, (acc, a) => (a !== undefined && a !== null ? acc + 1 : acc), 0);
12955
+ }
12956
+ function max(values, locale) {
12957
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12958
+ return result === -Infinity ? 0 : result;
12959
+ }
12960
+ function min(values, locale) {
12961
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12962
+ return result === Infinity ? 0 : result;
12963
+ }
12923
12964
 
12924
12965
  function filterAndFlatData(dataY, dataX) {
12925
12966
  const _flatDataY = [];
@@ -13190,13 +13231,7 @@
13190
13231
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13191
13232
  },
13192
13233
  compute: function (...values) {
13193
- let count = 0;
13194
- const sum = reduceNumbers(values, (acc, a) => {
13195
- count += 1;
13196
- return acc + a;
13197
- }, 0, this.locale);
13198
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
13199
- return sum / count;
13234
+ return average(values, this.locale);
13200
13235
  },
13201
13236
  isExported: true,
13202
13237
  };
@@ -13354,24 +13389,7 @@
13354
13389
  ],
13355
13390
  returns: ["NUMBER"],
13356
13391
  compute: function (...values) {
13357
- let count = 0;
13358
- for (let n of values) {
13359
- if (isMatrix(n)) {
13360
- for (let i of n) {
13361
- for (let j of i) {
13362
- if (typeof j === "number") {
13363
- count += 1;
13364
- }
13365
- }
13366
- }
13367
- }
13368
- else if (typeof n !== "string" ||
13369
- isNumber(n, this.locale) ||
13370
- parseDateTime(n, this.locale)) {
13371
- count += 1;
13372
- }
13373
- }
13374
- return count;
13392
+ return countNumbers(values, this.locale);
13375
13393
  },
13376
13394
  isExported: true,
13377
13395
  };
@@ -13386,7 +13404,7 @@
13386
13404
  ],
13387
13405
  returns: ["NUMBER"],
13388
13406
  compute: function (...values) {
13389
- return reduceAny(values, (acc, a) => (a !== undefined && a !== null ? acc + 1 : acc), 0);
13407
+ return countAny(values);
13390
13408
  },
13391
13409
  isExported: true,
13392
13410
  };
@@ -13616,8 +13634,7 @@
13616
13634
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13617
13635
  },
13618
13636
  compute: function (...values) {
13619
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, this.locale);
13620
- return result === -Infinity ? 0 : result;
13637
+ return max(values, this.locale);
13621
13638
  },
13622
13639
  isExported: true,
13623
13640
  };
@@ -13704,8 +13721,7 @@
13704
13721
  return isMatrix(value1) ? value1[0][0]?.format : value1?.format;
13705
13722
  },
13706
13723
  compute: function (...values) {
13707
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, this.locale);
13708
- return result === Infinity ? 0 : result;
13724
+ return min(values, this.locale);
13709
13725
  },
13710
13726
  isExported: true,
13711
13727
  };
@@ -17876,6 +17892,149 @@
17876
17892
  YIELDMAT: YIELDMAT
17877
17893
  });
17878
17894
 
17895
+ /**
17896
+ * Change the reference types inside the given token, if the token represent a range or a cell
17897
+ *
17898
+ * Eg. :
17899
+ * A1 => $A$1 => A$1 => $A1 => A1
17900
+ * A1:$B$1 => $A$1:B$1 => A$1:$B1 => $A1:B1 => A1:$B$1
17901
+ */
17902
+ function loopThroughReferenceType(token) {
17903
+ if (token.type !== "REFERENCE")
17904
+ return token;
17905
+ const { xc, sheetName } = splitReference(token.value);
17906
+ const [left, right] = xc.split(":");
17907
+ const updatedLeft = getTokenNextReferenceType(left);
17908
+ const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
17909
+ return { ...token, value: getFullReference(sheetName, updatedLeft + updatedRight) };
17910
+ }
17911
+ /**
17912
+ * Get a new token with a changed type of reference from the given cell token symbol.
17913
+ * Undefined behavior if given a token other than a cell or if the Xc contains a sheet reference
17914
+ *
17915
+ * A1 => $A$1 => A$1 => $A1 => A1
17916
+ */
17917
+ function getTokenNextReferenceType(xc) {
17918
+ switch (getReferenceType(xc)) {
17919
+ case "none":
17920
+ xc = setXcToFixedReferenceType(xc, "colrow");
17921
+ break;
17922
+ case "colrow":
17923
+ xc = setXcToFixedReferenceType(xc, "row");
17924
+ break;
17925
+ case "row":
17926
+ xc = setXcToFixedReferenceType(xc, "col");
17927
+ break;
17928
+ case "col":
17929
+ xc = setXcToFixedReferenceType(xc, "none");
17930
+ break;
17931
+ }
17932
+ return xc;
17933
+ }
17934
+ /**
17935
+ * Returns the given XC with the given reference type. The XC string should not contain a sheet name.
17936
+ */
17937
+ function setXcToFixedReferenceType(xc, referenceType) {
17938
+ if (xc.includes("!")) {
17939
+ throw new Error("The given XC should not contain a sheet name");
17940
+ }
17941
+ xc = xc.replace(/\$/g, "");
17942
+ let indexOfNumber;
17943
+ switch (referenceType) {
17944
+ case "col":
17945
+ return "$" + xc;
17946
+ case "row":
17947
+ indexOfNumber = xc.search(/[0-9]/);
17948
+ return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
17949
+ case "colrow":
17950
+ indexOfNumber = xc.search(/[0-9]/);
17951
+ xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
17952
+ return "$" + xc;
17953
+ case "none":
17954
+ return xc;
17955
+ }
17956
+ }
17957
+ /**
17958
+ * Return the type of reference used in the given XC of a cell.
17959
+ * Undefined behavior if the XC have a sheet reference
17960
+ */
17961
+ function getReferenceType(xcCell) {
17962
+ if (isColAndRowFixed(xcCell)) {
17963
+ return "colrow";
17964
+ }
17965
+ else if (isColFixed(xcCell)) {
17966
+ return "col";
17967
+ }
17968
+ else if (isRowFixed(xcCell)) {
17969
+ return "row";
17970
+ }
17971
+ return "none";
17972
+ }
17973
+ function isColFixed(xc) {
17974
+ return xc.startsWith("$");
17975
+ }
17976
+ function isRowFixed(xc) {
17977
+ return xc.includes("$", 1);
17978
+ }
17979
+ function isColAndRowFixed(xc) {
17980
+ return xc.startsWith("$") && xc.length > 1 && xc.slice(1).includes("$");
17981
+ }
17982
+
17983
+ // -----------------------------------------------------------------------------
17984
+ // CELL
17985
+ // -----------------------------------------------------------------------------
17986
+ // NOTE: missing from Excel: "color", "filename", "parentheses", "prefix", "protect" and "width"
17987
+ const CELL_INFO_TYPES = ["address", "col", "contents", "format", "row", "type"];
17988
+ const CELL = {
17989
+ description: _t("Gets information about a cell."),
17990
+ args: [
17991
+ arg("info_type (string)", _t("The type of information requested. Can be one of %s", CELL_INFO_TYPES.join(", "))),
17992
+ arg("reference (meta)", _t("The reference to the cell.")),
17993
+ ],
17994
+ returns: ["ANY"],
17995
+ compute: function (info, reference) {
17996
+ const _info = toString(info).toLowerCase();
17997
+ assert(() => CELL_INFO_TYPES.includes(_info), _t("The info_type should be one of %s.", CELL_INFO_TYPES.join(", ")));
17998
+ const sheetId = this.__originSheetId;
17999
+ const topLeftReference = reference.includes(":") ? reference.split(":")[0] : reference;
18000
+ let { sheetName, xc } = splitReference(topLeftReference);
18001
+ // only put the sheet name if the referenced range is in another sheet than the cell the formula is on
18002
+ sheetName = sheetName === this.getters.getSheetName(sheetId) ? undefined : sheetName;
18003
+ const fixedRef = getFullReference(sheetName, setXcToFixedReferenceType(xc, "colrow"));
18004
+ const range = this.getters.getRangeFromSheetXC(sheetId, fixedRef);
18005
+ switch (_info) {
18006
+ case "address":
18007
+ return this.getters.getRangeString(range, sheetId);
18008
+ case "col":
18009
+ return range.zone.left + 1;
18010
+ case "contents": {
18011
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18012
+ return this.getters.getEvaluatedCell(position).value;
18013
+ }
18014
+ case "format": {
18015
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18016
+ return this.getters.getEvaluatedCell(position).format || "";
18017
+ }
18018
+ case "row":
18019
+ return range.zone.top + 1;
18020
+ case "type": {
18021
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
18022
+ const type = this.getters.getEvaluatedCell(position).type;
18023
+ if (type === CellValueType.empty) {
18024
+ return "b"; // blank
18025
+ }
18026
+ else if (type === CellValueType.text) {
18027
+ return "l"; // label
18028
+ }
18029
+ else {
18030
+ return "v"; // value
18031
+ }
18032
+ }
18033
+ }
18034
+ return "";
18035
+ },
18036
+ isExported: true,
18037
+ };
17879
18038
  // -----------------------------------------------------------------------------
17880
18039
  // ISERR
17881
18040
  // -----------------------------------------------------------------------------
@@ -18031,6 +18190,7 @@
18031
18190
 
18032
18191
  var info = /*#__PURE__*/Object.freeze({
18033
18192
  __proto__: null,
18193
+ CELL: CELL,
18034
18194
  ISBLANK: ISBLANK,
18035
18195
  ISERR: ISERR,
18036
18196
  ISERROR: ISERROR,
@@ -18322,7 +18482,7 @@
18322
18482
  cellReference = rowPart + colPart;
18323
18483
  }
18324
18484
  if (sheet !== undefined) {
18325
- return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
18485
+ return getFullReference(toString(sheet), cellReference);
18326
18486
  }
18327
18487
  return cellReference;
18328
18488
  },
@@ -18338,7 +18498,7 @@
18338
18498
  ],
18339
18499
  returns: ["NUMBER"],
18340
18500
  compute: function (cellReference) {
18341
- const _cellReference = cellReference || this.__originCellXC?.();
18501
+ const _cellReference = cellReference || this.__originCellXC();
18342
18502
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18343
18503
  const zone = toZone(_cellReference);
18344
18504
  return zone.left + 1;
@@ -18505,7 +18665,7 @@
18505
18665
  ],
18506
18666
  returns: ["NUMBER"],
18507
18667
  compute: function (cellReference) {
18508
- cellReference = cellReference || this.__originCellXC?.();
18668
+ cellReference = cellReference || this.__originCellXC();
18509
18669
  assert(() => !!cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18510
18670
  const zone = toZone(cellReference);
18511
18671
  return zone.top + 1;
@@ -23857,31 +24017,65 @@
23857
24017
  `;
23858
24018
  class FindAndReplacePanel extends owl.Component {
23859
24019
  static template = "o-spreadsheet-FindAndReplacePanel";
23860
- state = owl.useState(this.initialState());
24020
+ static components = { SelectionInput };
23861
24021
  debounceTimeoutId;
23862
- showFormulaState = false;
24022
+ initialShowFormulaState = false;
24023
+ dataRange = "";
23863
24024
  searchInput = owl.useRef("searchInput");
24025
+ replaceInput = owl.useRef("replaceInput");
23864
24026
  get hasSearchResult() {
23865
24027
  return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
23866
24028
  }
23867
24029
  get pendingSearch() {
23868
24030
  return this.debounceTimeoutId !== undefined;
23869
24031
  }
24032
+ get searchOptions() {
24033
+ return this.env.model.getters.getSearchOptions();
24034
+ }
24035
+ get toSearch() {
24036
+ return this.searchInput.el?.value || "";
24037
+ }
24038
+ get toReplace() {
24039
+ return this.replaceInput.el?.value || "";
24040
+ }
24041
+ get allSheetsMatchesCount() {
24042
+ return _t("%s in all sheets", this.env.model.getters.getAllSheetMatchesCount());
24043
+ }
24044
+ get currentSheetMatchesCount() {
24045
+ return _t("%(matches)s in sheet %(sheetName)s", {
24046
+ matches: this.env.model.getters.getActiveSheetMatchesCount(),
24047
+ sheetName: this.env.model.getters.getSheetName(this.env.model.getters.getActiveSheetId()),
24048
+ });
24049
+ }
24050
+ get specificRangeMatchesCount() {
24051
+ const range = this.searchOptions.specificRange;
24052
+ if (!range) {
24053
+ return "";
24054
+ }
24055
+ const { _sheetId, _zone } = range;
24056
+ return _t("%(matches)s in range %(range)s of sheet %(sheetName)s", {
24057
+ matches: this.env.model.getters.getSpecificRangeMatchesCount().toString(),
24058
+ range: zoneToXc(_zone),
24059
+ sheetName: this.env.model.getters.getSheetName(_sheetId),
24060
+ });
24061
+ }
23870
24062
  setup() {
23871
- this.showFormulaState = this.env.model.getters.shouldShowFormulas();
24063
+ this.initialShowFormulaState = this.env.model.getters.shouldShowFormulas();
23872
24064
  owl.onMounted(() => this.searchInput.el?.focus());
23873
24065
  owl.onWillUnmount(() => {
23874
24066
  clearTimeout(this.debounceTimeoutId);
23875
24067
  this.env.model.dispatch("CLEAR_SEARCH");
23876
- this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
24068
+ this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
23877
24069
  });
23878
24070
  owl.useEffect(() => {
23879
- this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
23880
- this.searchFormulas();
24071
+ const showFormula = this.env.model.getters.shouldShowFormulas();
24072
+ this.updateSearch({ searchFormulas: showFormula });
23881
24073
  }, () => [this.env.model.getters.shouldShowFormulas()]);
23882
24074
  }
23883
- onInput(ev) {
23884
- this.state.toSearch = ev.target.value;
24075
+ onFocusSearch() {
24076
+ this.updateDataRange();
24077
+ }
24078
+ onInput() {
23885
24079
  this.debouncedUpdateSearch();
23886
24080
  }
23887
24081
  onKeydownSearch(ev) {
@@ -23898,11 +24092,36 @@
23898
24092
  this.replace();
23899
24093
  }
23900
24094
  }
23901
- searchFormulas() {
24095
+ searchFormulas(ev) {
24096
+ const showFormula = ev.target.checked;
23902
24097
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
23903
- show: this.state.searchOptions.searchFormulas,
24098
+ show: showFormula,
23904
24099
  });
23905
- this.updateSearch();
24100
+ this.updateSearch({ searchFormulas: showFormula });
24101
+ }
24102
+ searchExactMatch(ev) {
24103
+ const exactMatch = ev.target.checked;
24104
+ this.updateSearch({ exactMatch });
24105
+ }
24106
+ searchMatchCase(ev) {
24107
+ const matchCase = ev.target.checked;
24108
+ this.updateSearch({ matchCase });
24109
+ }
24110
+ changeSearchScope(ev) {
24111
+ const searchScope = ev.target.value;
24112
+ this.updateSearch({ searchScope });
24113
+ }
24114
+ onSearchRangeChanged(ranges) {
24115
+ this.dataRange = ranges[0];
24116
+ }
24117
+ updateDataRange() {
24118
+ if (!this.dataRange) {
24119
+ return;
24120
+ }
24121
+ if (this.searchOptions.searchScope === "specificRange") {
24122
+ const specificRange = this.env.model.getters.getRangeFromSheetXC(this.env.model.getters.getActiveSheetId(), this.dataRange).rangeData;
24123
+ this.updateSearch({ specificRange });
24124
+ }
23906
24125
  }
23907
24126
  onSelectPreviousCell() {
23908
24127
  this.env.model.dispatch("SELECT_SEARCH_PREVIOUS_MATCH");
@@ -23910,10 +24129,14 @@
23910
24129
  onSelectNextCell() {
23911
24130
  this.env.model.dispatch("SELECT_SEARCH_NEXT_MATCH");
23912
24131
  }
23913
- updateSearch() {
24132
+ updateSearch(updateSearchOptions) {
24133
+ const searchOptions = {
24134
+ ...this.env.model.getters.getSearchOptions(),
24135
+ ...updateSearchOptions,
24136
+ };
23914
24137
  this.env.model.dispatch("UPDATE_SEARCH", {
23915
- toSearch: this.state.toSearch,
23916
- searchOptions: this.state.searchOptions,
24138
+ toSearch: this.toSearch,
24139
+ searchOptions,
23917
24140
  });
23918
24141
  }
23919
24142
  debouncedUpdateSearch() {
@@ -23925,28 +24148,14 @@
23925
24148
  }
23926
24149
  replace() {
23927
24150
  this.env.model.dispatch("REPLACE_SEARCH", {
23928
- replaceWith: this.state.replaceWith,
24151
+ replaceWith: this.toReplace,
23929
24152
  });
23930
24153
  }
23931
24154
  replaceAll() {
23932
24155
  this.env.model.dispatch("REPLACE_ALL_SEARCH", {
23933
- replaceWith: this.state.replaceWith,
24156
+ replaceWith: this.toReplace,
23934
24157
  });
23935
24158
  }
23936
- // ---------------------------------------------------------------------------
23937
- // Private
23938
- // ---------------------------------------------------------------------------
23939
- initialState() {
23940
- return {
23941
- toSearch: "",
23942
- replaceWith: "",
23943
- searchOptions: {
23944
- matchCase: false,
23945
- exactMatch: false,
23946
- searchFormulas: false,
23947
- },
23948
- };
23949
- }
23950
24159
  }
23951
24160
  FindAndReplacePanel.props = {
23952
24161
  onCloseSidePanel: Function,
@@ -27092,7 +27301,7 @@
27092
27301
  get cellReference() {
27093
27302
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27094
27303
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27095
- return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
27304
+ return getFullReference(prefixSheet ? this.env.model.getters.getSheetName(sheetId) : undefined, toXC(col, row));
27096
27305
  }
27097
27306
  get cellReferenceStyle() {
27098
27307
  const { x: left, y: top } = this.rect;
@@ -28535,22 +28744,15 @@
28535
28744
  height: 10000px;
28536
28745
  background-color: ${SELECTION_BORDER_COLOR};
28537
28746
  }
28538
- .o-unhide {
28539
- width: ${UNHIDE_ICON_EDGE_LENGTH}px;
28540
- height: ${UNHIDE_ICON_EDGE_LENGTH}px;
28541
- position: absolute;
28542
- overflow: hidden;
28543
- border-radius: 2px;
28544
- top: calc(${HEADER_HEIGHT}px / 2 - ${UNHIDE_ICON_EDGE_LENGTH}px / 2);
28747
+ .o-unhide-buttons {
28748
+ width: fit-content;
28749
+ gap: 5px;
28750
+ transform: translate(-50%, 0);
28545
28751
  }
28546
28752
  .o-unhide:hover {
28547
28753
  z-index: ${ComponentsImportance.Grid + 1};
28548
28754
  background-color: lightgrey;
28549
28755
  }
28550
- .o-unhide > svg {
28551
- position: relative;
28552
- top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28553
- }
28554
28756
  }
28555
28757
  `;
28556
28758
  AbstractResizer.props = {
@@ -28660,8 +28862,8 @@
28660
28862
  dimension: "COL",
28661
28863
  });
28662
28864
  }
28663
- unhideStyleValue(hiddenIndex) {
28664
- return this._getDimensionsInViewport(hiddenIndex).start;
28865
+ getUnhideButtonStyle(hiddenIndex) {
28866
+ return cssPropertiesToCss({ left: this._getDimensionsInViewport(hiddenIndex).start + "px" });
28665
28867
  }
28666
28868
  }
28667
28869
  css /* scss */ `
@@ -28707,18 +28909,9 @@
28707
28909
  height: 1px;
28708
28910
  background-color: ${SELECTION_BORDER_COLOR};
28709
28911
  }
28710
- .o-unhide {
28711
- width: ${UNHIDE_ICON_EDGE_LENGTH}px;
28712
- height: ${UNHIDE_ICON_EDGE_LENGTH}px;
28713
- position: absolute;
28714
- overflow: hidden;
28715
- border-radius: 2px;
28716
- left: calc(${HEADER_WIDTH}px - ${UNHIDE_ICON_EDGE_LENGTH}px - 2px);
28717
- }
28718
- .o-unhide > svg {
28719
- position: relative;
28720
- left: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28721
- top: calc(${UNHIDE_ICON_EDGE_LENGTH}px / 2 - ${ICON_EDGE_LENGTH}px / 2);
28912
+ .o-unhide-buttons {
28913
+ height: fit-content;
28914
+ transform: translate(0, -50%);
28722
28915
  }
28723
28916
  .o-unhide:hover {
28724
28917
  z-index: ${ComponentsImportance.Grid + 1};
@@ -28833,8 +29026,8 @@
28833
29026
  elements: hiddenElements,
28834
29027
  });
28835
29028
  }
28836
- unhideStyleValue(hiddenIndex) {
28837
- return this._getDimensionsInViewport(hiddenIndex).start;
29029
+ getUnhideButtonStyle(hiddenIndex) {
29030
+ return cssPropertiesToCss({ top: this._getDimensionsInViewport(hiddenIndex).start + "px" });
28838
29031
  }
28839
29032
  }
28840
29033
  css /* scss */ `
@@ -29341,7 +29534,6 @@
29341
29534
  owl.onMounted(() => this.focus());
29342
29535
  this.props.exposeFocus(() => this.focus());
29343
29536
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29344
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29345
29537
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29346
29538
  this.moveCanvas(deltaX, deltaY);
29347
29539
  this.hoveredCell.col = undefined;
@@ -29778,6 +29970,7 @@
29778
29970
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
29779
29971
  const target = this.env.model.getters.getSelectedZones();
29780
29972
  const clipboardString = this.env.model.getters.getClipboardTextContent();
29973
+ const isCutOperation = this.env.model.getters.isCutOperation();
29781
29974
  if (clipboardString === content) {
29782
29975
  // the paste actually comes from o-spreadsheet itself
29783
29976
  interactivePaste(this.env, target);
@@ -29785,7 +29978,7 @@
29785
29978
  else {
29786
29979
  interactivePasteFromOS(this.env, target, content);
29787
29980
  }
29788
- if (this.env.model.getters.isCutOperation()) {
29981
+ if (isCutOperation) {
29789
29982
  await this.env.clipboard.write({ [ClipboardMIMEType.PlainText]: "" });
29790
29983
  }
29791
29984
  }
@@ -31398,12 +31591,6 @@
31398
31591
  }
31399
31592
  function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
31400
31593
  let { sheetName, xc } = splitReference(range);
31401
- if (sheetName) {
31402
- sheetName = getCanonicalSheetName(sheetName) + "!";
31403
- }
31404
- else {
31405
- sheetName = "";
31406
- }
31407
31594
  let zone = toUnboundedZone(xc);
31408
31595
  if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
31409
31596
  const height = zone.bottom - zone.top + 1;
@@ -31416,7 +31603,7 @@
31416
31603
  }
31417
31604
  }
31418
31605
  const dataXC = zoneToXc(zone);
31419
- return sheetName + dataXC;
31606
+ return getFullReference(sheetName, dataXC);
31420
31607
  }
31421
31608
 
31422
31609
  /**
@@ -34079,6 +34266,14 @@
34079
34266
  // ---------------------------------------------------------------------------
34080
34267
  // Command Handling
34081
34268
  // ---------------------------------------------------------------------------
34269
+ allowDispatch(cmd) {
34270
+ switch (cmd.type) {
34271
+ case "SET_BORDER":
34272
+ return this.checkBordersUnchanged(cmd);
34273
+ default:
34274
+ return "Success" /* CommandResult.Success */;
34275
+ }
34276
+ }
34082
34277
  handle(cmd) {
34083
34278
  switch (cmd.type) {
34084
34279
  case "ADD_MERGE":
@@ -34498,6 +34693,14 @@
34498
34693
  this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
34499
34694
  }
34500
34695
  }
34696
+ checkBordersUnchanged(cmd) {
34697
+ const currentBorder = this.getCellBorder(cmd);
34698
+ const areAllNewBordersUndefined = !cmd.border?.bottom && !cmd.border?.left && !cmd.border?.right && !cmd.border?.top;
34699
+ if ((!currentBorder && areAllNewBordersUndefined) || deepEquals(currentBorder, cmd.border)) {
34700
+ return "NoChanges" /* CommandResult.NoChanges */;
34701
+ }
34702
+ return "Success" /* CommandResult.Success */;
34703
+ }
34501
34704
  // ---------------------------------------------------------------------------
34502
34705
  // Import/Export
34503
34706
  // ---------------------------------------------------------------------------
@@ -35324,7 +35527,6 @@
35324
35527
  static getters = [
35325
35528
  "zoneToXC",
35326
35529
  "getCells",
35327
- "getFormulaCellContent",
35328
35530
  "getTranslatedCellFormula",
35329
35531
  "getCellStyle",
35330
35532
  "getCellById",
@@ -35353,8 +35555,9 @@
35353
35555
  allowDispatch(cmd) {
35354
35556
  switch (cmd.type) {
35355
35557
  case "UPDATE_CELL":
35558
+ return this.checkCellOutOfSheet(cmd);
35356
35559
  case "CLEAR_CELL":
35357
- return this.checkCellOutOfSheet(cmd.sheetId, cmd.col, cmd.row);
35560
+ return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35358
35561
  default:
35359
35562
  return "Success" /* CommandResult.Success */;
35360
35563
  }
@@ -35523,24 +35726,18 @@
35523
35726
  */
35524
35727
  getCellById(cellId) {
35525
35728
  // this must be as fast as possible
35526
- for (const sheetId in this.cells) {
35527
- const sheet = this.cells[sheetId];
35528
- const cell = sheet[cellId];
35529
- if (cell) {
35530
- return cell;
35531
- }
35532
- }
35533
- return undefined;
35729
+ const position = this.getters.getCellPosition(cellId);
35730
+ const sheet = this.cells[position.sheetId];
35731
+ return sheet[cellId];
35534
35732
  }
35535
35733
  /*
35536
35734
  * Reconstructs the original formula string based on a normalized form and its dependencies
35537
35735
  */
35538
35736
  getFormulaCellContent(sheetId, compiledFormula, dependencies) {
35539
- const ranges = dependencies || compiledFormula.dependencies;
35540
35737
  let rangeIndex = 0;
35541
35738
  return concat(compiledFormula.tokens.map((token) => {
35542
35739
  if (token.type === "REFERENCE") {
35543
- const range = ranges[rangeIndex++];
35740
+ const range = dependencies[rangeIndex++];
35544
35741
  return this.getters.getRangeString(range, sheetId);
35545
35742
  }
35546
35743
  return token.value;
@@ -35761,13 +35958,23 @@
35761
35958
  },
35762
35959
  };
35763
35960
  }
35764
- checkCellOutOfSheet(sheetId, col, row) {
35961
+ checkCellOutOfSheet(cmd) {
35962
+ const { sheetId, col, row } = cmd;
35765
35963
  const sheet = this.getters.tryGetSheet(sheetId);
35766
35964
  if (!sheet)
35767
35965
  return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
35768
35966
  const sheetZone = this.getters.getSheetZone(sheetId);
35769
35967
  return isInside(col, row, sheetZone) ? "Success" /* CommandResult.Success */ : "TargetOutOfSheet" /* CommandResult.TargetOutOfSheet */;
35770
35968
  }
35969
+ checkUselessClearCell(cmd) {
35970
+ const cell = this.getters.getCell(cmd);
35971
+ if (!cell)
35972
+ return "NoChanges" /* CommandResult.NoChanges */;
35973
+ if (!cell.content && !cell.style && !cell.format) {
35974
+ return "NoChanges" /* CommandResult.NoChanges */;
35975
+ }
35976
+ return "Success" /* CommandResult.Success */;
35977
+ }
35771
35978
  }
35772
35979
  class FormulaCellWithDependencies {
35773
35980
  id;
@@ -36457,6 +36664,9 @@
36457
36664
  return this.rules[sheetId].find((rule) => rule.id === id);
36458
36665
  }
36459
36666
  getValidationRuleForCell({ sheetId, col, row }) {
36667
+ if (!this.rules[sheetId]) {
36668
+ return undefined;
36669
+ }
36460
36670
  for (const rule of this.rules[sheetId]) {
36461
36671
  for (const range of rule.ranges) {
36462
36672
  if (isInside(col, row, range.zone)) {
@@ -37629,7 +37839,7 @@
37629
37839
  const rangeString = this.getters.getRangeString(expandedRange, forSheetId);
37630
37840
  if (this.isSingleCellOrMerge(rangeImpl.sheetId, rangeImpl.zone)) {
37631
37841
  const { sheetName, xc } = splitReference(rangeString);
37632
- return `${sheetName !== undefined ? getCanonicalSheetName(sheetName) + "!" : ""}${xc.split(":")[0]}`;
37842
+ return getFullReference(sheetName, xc.split(":")[0]);
37633
37843
  }
37634
37844
  return rangeString;
37635
37845
  }
@@ -39054,10 +39264,13 @@
39054
39264
  // begin with the end.
39055
39265
  rows.sort((a, b) => b - a);
39056
39266
  for (let group of groupConsecutive(rows)) {
39267
+ // indexes are sorted in the descending order
39268
+ const from = group[group.length - 1];
39269
+ const to = group[0];
39057
39270
  // Move the cells.
39058
- this.moveCellOnRowsDeletion(sheet, group[group.length - 1], group[0]);
39059
- // Effectively delete the element and recompute the left-right/top-bottom.
39060
- group.map((row) => this.updateRowsStructureOnDeletion(row, sheet));
39271
+ this.moveCellOnRowsDeletion(sheet, from, to);
39272
+ // Effectively delete the rows
39273
+ this.updateRowsStructureOnDeletion(sheet, from, to);
39061
39274
  }
39062
39275
  const count = rows.filter((row) => row < sheet.panes.ySplit).length;
39063
39276
  if (count) {
@@ -39079,8 +39292,6 @@
39079
39292
  this.addEmptyRows(sheet, quantity);
39080
39293
  // Move the cells.
39081
39294
  this.moveCellsOnAddition(sheet, index, quantity, "rows");
39082
- // Recompute the left-right/top-bottom.
39083
- this.updateRowsStructureOnAddition(sheet, row, quantity);
39084
39295
  if (index < sheet.panes.ySplit) {
39085
39296
  this.setPaneDivisions(sheet.id, sheet.panes.ySplit + quantity, "ROW");
39086
39297
  }
@@ -39181,35 +39392,20 @@
39181
39392
  }
39182
39393
  }
39183
39394
  }
39184
- updateRowsStructureOnDeletion(index, sheet) {
39395
+ updateRowsStructureOnDeletion(sheet, deleteFromRow, deleteToRow) {
39185
39396
  const rows = [];
39186
- const cellsQueue = sheet.rows.map((row) => row.cells);
39397
+ const cellsQueue = sheet.rows.map((row) => row.cells).reverse();
39187
39398
  for (let i in sheet.rows) {
39188
- if (Number(i) === index) {
39399
+ const row = Number(i);
39400
+ if (row >= deleteFromRow && row <= deleteToRow) {
39189
39401
  continue;
39190
39402
  }
39191
39403
  rows.push({
39192
- cells: cellsQueue.shift(),
39404
+ cells: cellsQueue.pop(),
39193
39405
  });
39194
39406
  }
39195
39407
  this.history.update("sheets", sheet.id, "rows", rows);
39196
39408
  }
39197
- /**
39198
- * Update the rows of the sheet after an addition:
39199
- * - Rename the rows
39200
- *
39201
- * @param sheet Sheet on which the deletion occurs
39202
- * @param addedRow Index of the added row
39203
- * @param rowsToAdd Number of the rows to add
39204
- */
39205
- updateRowsStructureOnAddition(sheet, addedRow, rowsToAdd) {
39206
- const rows = [];
39207
- const cellsQueue = sheet.rows.map((row) => row.cells);
39208
- sheet.rows.forEach(() => rows.push({
39209
- cells: cellsQueue.shift(),
39210
- }));
39211
- this.history.update("sheets", sheet.id, "rows", rows);
39212
- }
39213
39409
  /**
39214
39410
  * Add empty rows at the end of the rows
39215
39411
  *
@@ -39736,6 +39932,7 @@
39736
39932
  getters;
39737
39933
  computeCell;
39738
39934
  evalContext;
39935
+ rangeCache = {};
39739
39936
  constructor(context, getters, computeCell) {
39740
39937
  this.getters = getters;
39741
39938
  this.computeCell = computeCell;
@@ -39758,7 +39955,8 @@
39758
39955
  refFn(range, isMeta, functionName, paramNumber) {
39759
39956
  if (isMeta) {
39760
39957
  // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
39761
- return { value: zoneToXc(range.zone) };
39958
+ const sheetName = this.getters.getSheetName(range.sheetId);
39959
+ return { value: getFullReference(sheetName, zoneToXc(range.zone)) };
39762
39960
  }
39763
39961
  if (!isZoneValid(range.zone)) {
39764
39962
  throw new InvalidReferenceError();
@@ -39821,17 +40019,24 @@
39821
40019
  if (!_zone) {
39822
40020
  return [[]];
39823
40021
  }
40022
+ const { top, left, bottom, right } = zone;
40023
+ const cacheKey = `${sheetId}-${top}-${left}-${bottom}-${right}`;
40024
+ if (cacheKey in this.rangeCache) {
40025
+ return this.rangeCache[cacheKey];
40026
+ }
39824
40027
  const height = _zone.bottom - _zone.top + 1;
39825
40028
  const width = _zone.right - _zone.left + 1;
39826
- const matrix = Array.from({ length: width }, () => Array.from({ length: height }));
40029
+ const matrix = new Array(width);
39827
40030
  // Performance issue: nested loop is faster than a map here
39828
40031
  for (let col = _zone.left; col <= _zone.right; col++) {
40032
+ const colIndex = col - _zone.left;
40033
+ matrix[colIndex] = new Array(height);
39829
40034
  for (let row = _zone.top; row <= _zone.bottom; row++) {
39830
- const colIndex = col - _zone.left;
39831
40035
  const rowIndex = row - _zone.top;
39832
40036
  matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
39833
40037
  }
39834
40038
  }
40039
+ this.rangeCache[cacheKey] = matrix;
39835
40040
  return matrix;
39836
40041
  }
39837
40042
  }
@@ -40037,6 +40242,7 @@
40037
40242
  this.formulaDependencies().addDependencies(positionId, dependencies);
40038
40243
  }
40039
40244
  updateCompilationParameters() {
40245
+ // rebuild the compilation parameters (with a clean cache)
40040
40246
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
40041
40247
  }
40042
40248
  evaluateCells(positions) {
@@ -40080,6 +40286,16 @@
40080
40286
  this.evaluatedCells = new Map();
40081
40287
  this.evaluate(this.getAllCells());
40082
40288
  }
40289
+ evaluateFormula(sheetId, formulaString) {
40290
+ const compiledFormula = compile(formulaString);
40291
+ const ranges = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
40292
+ this.updateCompilationParameters();
40293
+ const result = updateEvalContextAndExecute({ ...compiledFormula, dependencies: ranges }, this.compilationParams, sheetId);
40294
+ if (isMatrix(result)) {
40295
+ return matrixMap(result, (cell) => cell.value);
40296
+ }
40297
+ return result.value;
40298
+ }
40083
40299
  getAllCells() {
40084
40300
  const positionIds = new JetSet();
40085
40301
  for (const sheetId of this.getters.getSheetIds()) {
@@ -40106,6 +40322,7 @@
40106
40322
  this.nextPositionsToUpdate = cells;
40107
40323
  let currentIteration = 0;
40108
40324
  while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
40325
+ this.updateCompilationParameters();
40109
40326
  const positionIds = Array.from(this.nextPositionsToUpdate);
40110
40327
  this.nextPositionsToUpdate.clear();
40111
40328
  for (let i = 0; i < positionIds.length; ++i) {
@@ -40129,7 +40346,8 @@
40129
40346
  if (!this.blockedArrayFormulas.has(positionId)) {
40130
40347
  this.invalidateSpreading(positionId);
40131
40348
  }
40132
- const cell = this.getCell(positionId);
40349
+ const cellPosition = this.decodePosition(positionId);
40350
+ const cell = this.getters.getCell(cellPosition);
40133
40351
  if (cell === undefined) {
40134
40352
  return createEvaluatedCell("", { locale: this.getters.getLocale() });
40135
40353
  }
@@ -40140,7 +40358,7 @@
40140
40358
  }
40141
40359
  this.cellsBeingComputed.add(cellId);
40142
40360
  return cell.isFormula
40143
- ? this.computeFormulaCell(cell)
40361
+ ? this.computeFormulaCell(cellPosition.sheetId, cell)
40144
40362
  : evaluateLiteral(cell.content, { format: cell.format, locale: this.getters.getLocale() });
40145
40363
  }
40146
40364
  catch (e) {
@@ -40166,14 +40384,9 @@
40166
40384
  e.message = e.message.replace("[[FUNCTION_NAME]]", __lastFnCalled);
40167
40385
  return errorCell(e);
40168
40386
  }
40169
- computeFormulaCell(cellData) {
40387
+ computeFormulaCell(sheetId, cellData) {
40170
40388
  const cellId = cellData.id;
40171
- this.compilationParams[2].__originCellXC = () => {
40172
- // compute the value lazily for performance reasons
40173
- const position = this.compilationParams[2].getters.getCellPosition(cellId);
40174
- return toXC(position.col, position.row);
40175
- };
40176
- const formulaReturn = cellData.compiledFormula.execute(cellData.compiledFormula.dependencies, ...this.compilationParams);
40389
+ const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, sheetId, cellId);
40177
40390
  if (!isMatrix(formulaReturn)) {
40178
40391
  return createEvaluatedCell(formulaReturn.value, {
40179
40392
  format: cellData.format || formulaReturn.format,
@@ -40375,6 +40588,18 @@
40375
40588
  return sheetId;
40376
40589
  }
40377
40590
  }
40591
+ function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
40592
+ compilationParams[2].__originCellXC = lazy(() => {
40593
+ if (!cellId) {
40594
+ return undefined;
40595
+ }
40596
+ // compute the value lazily for performance reasons
40597
+ const position = compilationParams[2].getters.getCellPosition(cellId);
40598
+ return toXC(position.col, position.row);
40599
+ });
40600
+ compilationParams[2].__originSheetId = sheetId;
40601
+ return compiledFormula.execute(compiledFormula.dependencies, ...compilationParams);
40602
+ }
40378
40603
 
40379
40604
  //#region
40380
40605
  // ---------------------------------------------------------------------------
@@ -40469,7 +40694,6 @@
40469
40694
  // of other cells depending on it, at the next iteration.
40470
40695
  //#endregion
40471
40696
  class EvaluationPlugin extends UIPlugin {
40472
- config;
40473
40697
  static getters = [
40474
40698
  "evaluateFormula",
40475
40699
  "getCorrespondingFormulaCell",
@@ -40480,15 +40704,13 @@
40480
40704
  "getEvaluatedCells",
40481
40705
  "getEvaluatedCellsInZone",
40482
40706
  "getSpreadPositionsOf",
40707
+ "getArrayFormulaSpreadingOn",
40483
40708
  ];
40484
40709
  shouldRebuildDependenciesGraph = true;
40485
40710
  evaluator;
40486
- compilationParams;
40487
40711
  positionsToUpdate = [];
40488
40712
  constructor(config) {
40489
40713
  super(config);
40490
- this.config = config;
40491
- this.compilationParams = this.getCompilationParameters();
40492
40714
  this.evaluator = new Evaluator(config.custom, this.getters);
40493
40715
  }
40494
40716
  // ---------------------------------------------------------------------------
@@ -40513,10 +40735,6 @@
40513
40735
  case "EVALUATE_CELLS":
40514
40736
  this.evaluator.evaluateAllCells();
40515
40737
  break;
40516
- case "UPDATE_LOCALE":
40517
- this.compilationParams = this.getCompilationParameters();
40518
- this.evaluator.updateCompilationParameters();
40519
- break;
40520
40738
  }
40521
40739
  }
40522
40740
  finalize() {
@@ -40534,16 +40752,7 @@
40534
40752
  // Getters
40535
40753
  // ---------------------------------------------------------------------------
40536
40754
  evaluateFormula(sheetId, formulaString) {
40537
- const compiledFormula = compile(formulaString);
40538
- const ranges = [];
40539
- for (let xc of compiledFormula.dependencies) {
40540
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
40541
- }
40542
- const array = compiledFormula.execute(ranges, ...this.compilationParams);
40543
- if (isMatrix(array)) {
40544
- return array.map((col) => col.map((row) => row.value));
40545
- }
40546
- return array.value;
40755
+ return this.evaluator.evaluateFormula(sheetId, formulaString);
40547
40756
  }
40548
40757
  /**
40549
40758
  * Return the value of each cell in the range as they are displayed in the grid.
@@ -40592,6 +40801,9 @@
40592
40801
  getSpreadPositionsOf(position) {
40593
40802
  return this.evaluator.getSpreadPositionsOf(position);
40594
40803
  }
40804
+ getArrayFormulaSpreadingOn(position) {
40805
+ return this.evaluator.getArrayFormulaSpreadingOn(position);
40806
+ }
40595
40807
  // ---------------------------------------------------------------------------
40596
40808
  // Export
40597
40809
  // ---------------------------------------------------------------------------
@@ -40635,7 +40847,7 @@
40635
40847
  else if (cell && cell.content) {
40636
40848
  return undefined;
40637
40849
  }
40638
- const spreadingFormulaPosition = this.evaluator.getArrayFormulaSpreadingOn(position);
40850
+ const spreadingFormulaPosition = this.getArrayFormulaSpreadingOn(position);
40639
40851
  if (spreadingFormulaPosition === undefined) {
40640
40852
  return undefined;
40641
40853
  }
@@ -40645,9 +40857,6 @@
40645
40857
  }
40646
40858
  return undefined;
40647
40859
  }
40648
- getCompilationParameters() {
40649
- return buildCompilationParameters(this.config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position));
40650
- }
40651
40860
  }
40652
40861
  function isBadExpression(tokens) {
40653
40862
  try {
@@ -43346,11 +43555,26 @@
43346
43555
  let cellsInRow = [];
43347
43556
  for (let col of columnsIndex) {
43348
43557
  const position = { col, row, sheetId };
43558
+ const spreader = getters.getArrayFormulaSpreadingOn(position);
43559
+ let cell = getters.getCell(position);
43560
+ const evaluatedCell = getters.getEvaluatedCell(position);
43561
+ if (spreader) {
43562
+ const isSpreaderCopied = rowsIndex.includes(spreader.row) && columnsIndex.includes(spreader.col);
43563
+ const content = isSpreaderCopied
43564
+ ? ""
43565
+ : formatValue(evaluatedCell.value, { locale: getters.getLocale() });
43566
+ cell = {
43567
+ id: cell?.id || "",
43568
+ style: cell?.style,
43569
+ format: evaluatedCell.format,
43570
+ content,
43571
+ isFormula: false,
43572
+ };
43573
+ }
43349
43574
  cellsInRow.push({
43350
- cell: getters.getCell(position),
43351
- style: getters.getCellComputedStyle(position),
43352
- evaluatedCell: getters.getEvaluatedCell(position),
43575
+ cell,
43353
43576
  border: getters.getCellBorder(position) || undefined,
43577
+ evaluatedCell,
43354
43578
  position,
43355
43579
  });
43356
43580
  }
@@ -43472,13 +43696,6 @@
43472
43696
  });
43473
43697
  }
43474
43698
  this.pasteCopiedTables(target);
43475
- this.cells.forEach((row) => {
43476
- row.forEach((c) => {
43477
- if (c.cell) {
43478
- c.cell = undefined;
43479
- }
43480
- });
43481
- });
43482
43699
  }
43483
43700
  /**
43484
43701
  * The clipped zone is copied as many times as it fits in the target.
@@ -43552,7 +43769,7 @@
43552
43769
  clearClippedZones() {
43553
43770
  for (const row of this.cells) {
43554
43771
  for (const cell of row) {
43555
- if (cell.cell) {
43772
+ if (cell?.cell) {
43556
43773
  this.dispatch("CLEAR_CELL", cell.position);
43557
43774
  }
43558
43775
  }
@@ -43578,6 +43795,9 @@
43578
43795
  const rowCells = this.cells[r];
43579
43796
  for (let c = 0; c < width; c++) {
43580
43797
  const origin = rowCells[c];
43798
+ if (!origin) {
43799
+ continue;
43800
+ }
43581
43801
  const position = { col: col + c, row: row + r, sheetId: sheetId };
43582
43802
  // TODO: refactor this part. the "Paste merge" action is also executed with
43583
43803
  // MOVE_RANGES in pasteFromCut. Adding a condition on the operation type here
@@ -43601,53 +43821,42 @@
43601
43821
  pasteCell(origin, target, operation, clipboardOption) {
43602
43822
  const { sheetId, col, row } = target;
43603
43823
  const targetCell = this.getters.getEvaluatedCell(target);
43604
- if (clipboardOption?.pasteOption !== "onlyValue") {
43605
- const targetBorders = this.getters.getCellBorder(target);
43606
- const originBorders = origin.border;
43607
- const border = {
43608
- top: targetBorders?.top || originBorders?.top,
43609
- bottom: targetBorders?.bottom || originBorders?.bottom,
43610
- left: targetBorders?.left || originBorders?.left,
43611
- right: targetBorders?.right || originBorders?.right,
43612
- };
43613
- this.dispatch("SET_BORDER", { sheetId, col, row, border });
43824
+ if (clipboardOption?.pasteOption === "onlyValue") {
43825
+ const locale = this.getters.getLocale();
43826
+ const content = formatValue(origin.evaluatedCell.value, { locale });
43827
+ this.dispatch("UPDATE_CELL", { ...target, content });
43828
+ return;
43614
43829
  }
43615
- if (origin.cell) {
43616
- if (clipboardOption?.pasteOption === "onlyFormat") {
43617
- this.dispatch("UPDATE_CELL", {
43618
- ...target,
43619
- style: origin.cell.style,
43620
- format: origin.evaluatedCell.format,
43621
- });
43622
- return;
43623
- }
43624
- if (clipboardOption?.pasteOption === "onlyValue") {
43625
- const locale = this.getters.getLocale();
43626
- const content = formatValue(origin.evaluatedCell.value, { locale });
43627
- this.dispatch("UPDATE_CELL", { ...target, content });
43628
- return;
43629
- }
43630
- let content = origin.cell.content;
43631
- if (origin.cell.isFormula && operation === "COPY") {
43632
- content = this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula);
43633
- }
43830
+ const targetBorders = this.getters.getCellBorder(target);
43831
+ const originBorders = origin.border;
43832
+ const border = {
43833
+ top: targetBorders?.top || originBorders?.top,
43834
+ bottom: targetBorders?.bottom || originBorders?.bottom,
43835
+ left: targetBorders?.left || originBorders?.left,
43836
+ right: targetBorders?.right || originBorders?.right,
43837
+ };
43838
+ this.dispatch("SET_BORDER", { sheetId, col, row, border });
43839
+ if (clipboardOption?.pasteOption === "onlyFormat") {
43840
+ this.dispatch("UPDATE_CELL", {
43841
+ ...target,
43842
+ style: origin.cell?.style ?? null,
43843
+ format: origin.cell?.format ?? origin.evaluatedCell.format ?? targetCell.format,
43844
+ });
43845
+ return;
43846
+ }
43847
+ const content = origin.cell && origin.cell.isFormula && operation === "COPY"
43848
+ ? this.getters.getTranslatedCellFormula(sheetId, col - origin.position.col, row - origin.position.row, origin.cell.compiledFormula)
43849
+ : origin.cell?.content;
43850
+ if (content !== "" || origin.cell?.format || origin.cell?.style) {
43634
43851
  this.dispatch("UPDATE_CELL", {
43635
43852
  ...target,
43636
43853
  content,
43637
- style: origin.cell.style || null,
43638
- format: origin.cell.format,
43854
+ style: origin.cell?.style || null,
43855
+ format: origin.cell?.format,
43639
43856
  });
43640
43857
  }
43641
43858
  else if (targetCell) {
43642
- if (clipboardOption?.pasteOption === "onlyValue") {
43643
- this.dispatch("UPDATE_CELL", { ...target, content: "" });
43644
- }
43645
- else if (clipboardOption?.pasteOption === "onlyFormat") {
43646
- this.dispatch("UPDATE_CELL", { ...target, style: null, format: "" });
43647
- }
43648
- else {
43649
- this.dispatch("CLEAR_CELL", target);
43650
- }
43859
+ this.dispatch("CLEAR_CELL", target);
43651
43860
  }
43652
43861
  }
43653
43862
  /**
@@ -43709,22 +43918,28 @@
43709
43918
  return (this.cells
43710
43919
  .map((cells) => {
43711
43920
  return cells
43712
- .map((c) => this.getters.shouldShowFormulas() && c.cell?.isFormula
43921
+ .map((c) => this.getters.shouldShowFormulas() && c?.cell?.isFormula
43713
43922
  ? c.cell?.content || ""
43714
- : c.evaluatedCell?.formattedValue || "")
43923
+ : c?.evaluatedCell?.formattedValue || "")
43715
43924
  .join("\t");
43716
43925
  })
43717
43926
  .join("\n") || "\t");
43718
43927
  }
43719
43928
  getHTMLContent() {
43720
43929
  if (this.cells.length === 1 && this.cells[0].length === 1) {
43930
+ if (!this.cells[0][0]) {
43931
+ return "";
43932
+ }
43721
43933
  return this.getters.getCellText(this.cells[0][0].position);
43722
43934
  }
43723
43935
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
43724
43936
  for (const row of this.cells) {
43725
43937
  htmlTable += "<tr>";
43726
43938
  for (const cell of row) {
43727
- const cssStyle = cssPropertiesToCss(cellStyleToCss(cell.style));
43939
+ if (!cell) {
43940
+ continue;
43941
+ }
43942
+ const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
43728
43943
  const cellText = this.getters.getCellText(cell.position);
43729
43944
  htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
43730
43945
  }
@@ -44052,24 +44267,49 @@
44052
44267
  */
44053
44268
  class FindAndReplacePlugin extends UIPlugin {
44054
44269
  static layers = [3 /* LAYERS.Search */];
44055
- static getters = ["getSearchMatches", "getCurrentSelectedMatchIndex"];
44056
- searchMatches = [];
44270
+ static getters = [
44271
+ "getSearchMatches",
44272
+ "getCurrentSelectedMatchIndex",
44273
+ "getSearchOptions",
44274
+ "getAllSheetMatchesCount",
44275
+ "getActiveSheetMatchesCount",
44276
+ "getSpecificRangeMatchesCount",
44277
+ ];
44278
+ allSheetsMatches = [];
44279
+ activeSheetMatches = [];
44280
+ specificRangeMatches = [];
44281
+ // fixme: why do we make selectedMatchIndex on top of a selected
44282
+ // property in the matches?
44057
44283
  selectedMatchIndex = null;
44058
44284
  currentSearchRegex = null;
44059
44285
  searchOptions = {
44060
44286
  matchCase: false,
44061
44287
  exactMatch: false,
44062
44288
  searchFormulas: false,
44289
+ searchScope: "allSheets",
44290
+ specificRange: undefined,
44063
44291
  };
44064
44292
  toSearch = "";
44065
44293
  isSearchDirty = false;
44294
+ get searchMatches() {
44295
+ switch (this.searchOptions.searchScope) {
44296
+ case "allSheets":
44297
+ return this.allSheetsMatches;
44298
+ case "activeSheet":
44299
+ return this.activeSheetMatches;
44300
+ case "specificRange":
44301
+ return this.specificRangeMatches;
44302
+ }
44303
+ }
44066
44304
  // ---------------------------------------------------------------------------
44067
44305
  // Command Handling
44068
44306
  // ---------------------------------------------------------------------------
44069
44307
  handle(cmd) {
44070
44308
  switch (cmd.type) {
44071
44309
  case "UPDATE_SEARCH":
44072
- this.updateSearch(cmd.toSearch, cmd.searchOptions);
44310
+ const rangeData = cmd.searchOptions.specificRange;
44311
+ const specificRange = rangeData && this.getters.getRangeFromRangeData(rangeData);
44312
+ this.updateSearch(cmd.toSearch, { ...cmd.searchOptions, specificRange });
44073
44313
  break;
44074
44314
  case "CLEAR_SEARCH":
44075
44315
  this.clearSearch();
@@ -44099,7 +44339,9 @@
44099
44339
  this.isSearchDirty = true;
44100
44340
  break;
44101
44341
  case "ACTIVATE_SHEET":
44102
- this.refreshSearch();
44342
+ if (this.searchOptions.searchScope === "activeSheet") {
44343
+ this.isSearchDirty = true;
44344
+ }
44103
44345
  break;
44104
44346
  }
44105
44347
  }
@@ -44118,6 +44360,18 @@
44118
44360
  getCurrentSelectedMatchIndex() {
44119
44361
  return this.selectedMatchIndex;
44120
44362
  }
44363
+ getSearchOptions() {
44364
+ return { ...this.searchOptions, specificRange: this.searchOptions.specificRange?.rangeData };
44365
+ }
44366
+ getAllSheetMatchesCount() {
44367
+ return this.allSheetsMatches.length;
44368
+ }
44369
+ getActiveSheetMatchesCount() {
44370
+ return this.activeSheetMatches.length;
44371
+ }
44372
+ getSpecificRangeMatchesCount() {
44373
+ return this.specificRangeMatches.length;
44374
+ }
44121
44375
  // ---------------------------------------------------------------------------
44122
44376
  // Search
44123
44377
  // ---------------------------------------------------------------------------
@@ -44138,8 +44392,8 @@
44138
44392
  * refresh the matches according to the current search options
44139
44393
  */
44140
44394
  refreshSearch() {
44141
- const matches = this.findMatches();
44142
- this.searchMatches = matches;
44395
+ this.selectedMatchIndex = null;
44396
+ this.findMatches();
44143
44397
  this.selectNextCell(Direction.current);
44144
44398
  }
44145
44399
  /**
@@ -44154,45 +44408,65 @@
44154
44408
  }
44155
44409
  this.currentSearchRegex = RegExp(searchValue, flags);
44156
44410
  }
44411
+ getSheetsInSearchOrder() {
44412
+ switch (this.searchOptions.searchScope) {
44413
+ case "allSheets":
44414
+ const sheetIds = this.getters.getSheetIds();
44415
+ const activeSheetIndex = sheetIds.findIndex((id) => id === this.getters.getActiveSheetId());
44416
+ return [
44417
+ sheetIds[activeSheetIndex],
44418
+ ...sheetIds.slice(activeSheetIndex + 1),
44419
+ ...sheetIds.slice(0, activeSheetIndex),
44420
+ ];
44421
+ case "activeSheet":
44422
+ return [this.getters.getActiveSheetId()];
44423
+ case "specificRange":
44424
+ const specificRange = this.searchOptions.specificRange;
44425
+ if (!specificRange) {
44426
+ return [];
44427
+ }
44428
+ return specificRange ? [specificRange.sheetId] : [];
44429
+ }
44430
+ }
44157
44431
  /**
44158
44432
  * Find matches using the current regex
44159
44433
  */
44160
44434
  findMatches() {
44161
- const sheetId = this.getters.getActiveSheetId();
44162
- const cells = this.getters.getCells(sheetId);
44163
44435
  const matches = [];
44164
- if (this.toSearch && this.currentSearchRegex) {
44165
- for (const cell of Object.values(cells)) {
44166
- const { col, row } = this.getters.getCellPosition(cell.id);
44167
- const cellPosition = { sheetId, col, row };
44436
+ if (this.toSearch) {
44437
+ for (const sheetId of this.getters.getSheetIds()) {
44438
+ matches.push(...this.findMatchesInSheet(sheetId));
44439
+ }
44440
+ }
44441
+ // set results
44442
+ this.allSheetsMatches = matches;
44443
+ this.activeSheetMatches = matches.filter((match) => match.sheetId === this.getters.getActiveSheetId());
44444
+ if (this.searchOptions.specificRange) {
44445
+ const { sheetId, zone } = this.searchOptions.specificRange;
44446
+ this.specificRangeMatches = matches.filter((match) => match.sheetId === sheetId && isInside(match.col, match.row, zone));
44447
+ }
44448
+ else {
44449
+ this.specificRangeMatches = [];
44450
+ }
44451
+ }
44452
+ findMatchesInSheet(sheetId) {
44453
+ const matches = [];
44454
+ const { left, right, top, bottom } = this.getters.getSheetZone(sheetId);
44455
+ for (let row = top; row <= bottom; row++) {
44456
+ for (let col = left; col <= right; col++) {
44168
44457
  const isColHidden = this.getters.isColHidden(sheetId, col);
44169
44458
  const isRowHidden = this.getters.isRowHidden(sheetId, row);
44170
44459
  if (isColHidden || isRowHidden) {
44171
44460
  continue;
44172
44461
  }
44173
- if (this.currentSearchRegex.test(this.getSearchableString(cellPosition))) {
44174
- const match = { col, row, selected: false };
44462
+ const cellPosition = { sheetId, col, row };
44463
+ if (this.currentSearchRegex?.test(this.getSearchableString(cellPosition))) {
44464
+ const match = { sheetId, col, row };
44175
44465
  matches.push(match);
44176
44466
  }
44177
- for (const spreadPosition of this.getters.getSpreadPositionsOf(cellPosition)) {
44178
- if (this.currentSearchRegex.test(this.getSearchableString(spreadPosition))) {
44179
- const match = {
44180
- col: spreadPosition.col,
44181
- row: spreadPosition.row,
44182
- selected: false,
44183
- };
44184
- matches.push(match);
44185
- }
44186
- }
44187
44467
  }
44188
44468
  }
44189
- return matches.sort(this.sortByRowThenColumn);
44190
- }
44191
- sortByRowThenColumn(a, b) {
44192
- if (a.row === b.row) {
44193
- return a.col - b.col;
44194
- }
44195
- return a.row > b.row ? 1 : -1;
44469
+ return matches;
44196
44470
  }
44197
44471
  /**
44198
44472
  * Changes the selected search cell. Given a direction it will
@@ -44210,30 +44484,47 @@
44210
44484
  }
44211
44485
  let nextIndex;
44212
44486
  if (this.selectedMatchIndex === null) {
44213
- nextIndex = 0;
44487
+ let nextMatchIndex = -1;
44488
+ // if search is not available in current sheet will select in next sheet
44489
+ for (const sheetId of this.getSheetsInSearchOrder()) {
44490
+ nextMatchIndex = matches.findIndex((match) => match.sheetId === sheetId);
44491
+ if (nextMatchIndex !== -1) {
44492
+ break;
44493
+ }
44494
+ }
44495
+ nextIndex = nextMatchIndex;
44214
44496
  }
44215
44497
  else {
44216
44498
  nextIndex = this.selectedMatchIndex + indexChange;
44217
44499
  }
44218
- //modulo of negative value to be able to cycle in both directions with previous and next
44219
- nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;
44500
+ // loop index value inside the array (index -1 => last index)
44501
+ nextIndex = (nextIndex + matches.length) % matches.length;
44220
44502
  this.selectedMatchIndex = nextIndex;
44503
+ const selectedMatch = matches[nextIndex];
44504
+ // Switch to the sheet where the match is located
44505
+ if (this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
44506
+ this.dispatch("ACTIVATE_SHEET", {
44507
+ sheetIdFrom: this.getters.getActiveSheetId(),
44508
+ sheetIdTo: selectedMatch.sheetId,
44509
+ });
44510
+ }
44221
44511
  // we want grid selection to capture the selection stream
44222
44512
  this.selection.getBackToDefault();
44223
- this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);
44224
- for (let index = 0; index < this.searchMatches.length; index++) {
44225
- this.searchMatches[index].selected = index === this.selectedMatchIndex;
44226
- }
44513
+ this.selection.selectCell(selectedMatch.col, selectedMatch.row);
44227
44514
  }
44228
44515
  clearSearch() {
44229
44516
  this.toSearch = "";
44230
- this.searchMatches = [];
44231
44517
  this.selectedMatchIndex = null;
44232
44518
  this.currentSearchRegex = null;
44519
+ this.allSheetsMatches = [];
44520
+ this.activeSheetMatches = [];
44521
+ this.specificRangeMatches = [];
44233
44522
  this.searchOptions = {
44234
44523
  matchCase: false,
44235
44524
  exactMatch: false,
44236
44525
  searchFormulas: false,
44526
+ searchScope: "allSheets",
44527
+ specificRange: undefined,
44237
44528
  };
44238
44529
  }
44239
44530
  // ---------------------------------------------------------------------------
@@ -44243,9 +44534,7 @@
44243
44534
  if (!this.currentSearchRegex) {
44244
44535
  return;
44245
44536
  }
44246
- const sheetId = this.getters.getActiveSheetId();
44247
- const position = { sheetId, ...selectedMatch };
44248
- const cell = this.getters.getCell(position);
44537
+ const cell = this.getters.getCell(selectedMatch);
44249
44538
  if (!cell?.content) {
44250
44539
  return;
44251
44540
  }
@@ -44253,10 +44542,10 @@
44253
44542
  return;
44254
44543
  }
44255
44544
  const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + "g");
44256
- const toReplace = this.getSearchableString(position);
44545
+ const toReplace = this.getSearchableString(selectedMatch);
44257
44546
  const content = toReplace.replace(replaceRegex, replaceWith);
44258
44547
  const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
44259
- this.dispatch("UPDATE_CELL", { ...position, content: canonicalContent });
44548
+ this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
44260
44549
  }
44261
44550
  /**
44262
44551
  * Replace the value of the currently selected match
@@ -44287,7 +44576,10 @@
44287
44576
  drawGrid(renderingContext) {
44288
44577
  const { ctx } = renderingContext;
44289
44578
  const sheetId = this.getters.getActiveSheetId();
44290
- for (const match of this.searchMatches) {
44579
+ for (const [index, match] of this.searchMatches.entries()) {
44580
+ if (match.sheetId !== sheetId) {
44581
+ continue; // Skip drawing matches from other sheets
44582
+ }
44291
44583
  const merge = this.getters.getMerge({ sheetId, col: match.col, row: match.row });
44292
44584
  const left = merge ? merge.left : match.col;
44293
44585
  const right = merge ? merge.right : match.col;
@@ -44297,12 +44589,21 @@
44297
44589
  if (width > 0 && height > 0) {
44298
44590
  ctx.fillStyle = BACKGROUND_COLOR;
44299
44591
  ctx.fillRect(x, y, width, height);
44300
- if (match.selected) {
44592
+ if (index === this.selectedMatchIndex) {
44301
44593
  ctx.strokeStyle = BORDER_COLOR;
44302
44594
  ctx.strokeRect(x, y, width, height);
44303
44595
  }
44304
44596
  }
44305
44597
  }
44598
+ if (this.searchOptions.searchScope === "specificRange") {
44599
+ const range = this.searchOptions.specificRange;
44600
+ if (!range || range.sheetId !== sheetId) {
44601
+ return;
44602
+ }
44603
+ const { x, y, width, height } = this.getters.getVisibleRect(range.zone);
44604
+ ctx.strokeStyle = BORDER_COLOR;
44605
+ ctx.strokeRect(x, y, width, height);
44606
+ }
44306
44607
  }
44307
44608
  }
44308
44609
 
@@ -46768,11 +47069,14 @@
46768
47069
  }
46769
47070
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
46770
47071
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
47072
+ if (this.state.operation === "CUT") {
47073
+ this.state = undefined;
47074
+ }
46771
47075
  this.lastPasteState = this.state;
46772
47076
  if (this.paintFormatStatus === "oneOff") {
46773
47077
  this.paintFormatStatus = "inactive";
46774
- this.status = "invisible";
46775
47078
  }
47079
+ this.status = "invisible";
46776
47080
  break;
46777
47081
  case "COPY_PASTE_CELLS_ABOVE":
46778
47082
  {
@@ -46999,92 +47303,6 @@
46999
47303
  }
47000
47304
  }
47001
47305
 
47002
- /**
47003
- * Change the reference types inside the given token, if the token represent a range or a cell
47004
- *
47005
- * Eg. :
47006
- * A1 => $A$1 => A$1 => $A1 => A1
47007
- * A1:$B$1 => $A$1:B$1 => A$1:$B1 => $A1:B1 => A1:$B$1
47008
- */
47009
- function loopThroughReferenceType(token) {
47010
- if (token.type !== "REFERENCE")
47011
- return token;
47012
- const { xc, sheetName } = splitReference(token.value);
47013
- const [left, right] = xc.split(":");
47014
- const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
47015
- const updatedLeft = getTokenNextReferenceType(left);
47016
- const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
47017
- return { ...token, value: sheetRef + updatedLeft + updatedRight };
47018
- }
47019
- /**
47020
- * Get a new token with a changed type of reference from the given cell token symbol.
47021
- * Undefined behavior if given a token other than a cell or if the Xc contains a sheet reference
47022
- *
47023
- * A1 => $A$1 => A$1 => $A1 => A1
47024
- */
47025
- function getTokenNextReferenceType(xc) {
47026
- switch (getReferenceType(xc)) {
47027
- case "none":
47028
- xc = setXcToReferenceType(xc, "colrow");
47029
- break;
47030
- case "colrow":
47031
- xc = setXcToReferenceType(xc, "row");
47032
- break;
47033
- case "row":
47034
- xc = setXcToReferenceType(xc, "col");
47035
- break;
47036
- case "col":
47037
- xc = setXcToReferenceType(xc, "none");
47038
- break;
47039
- }
47040
- return xc;
47041
- }
47042
- /**
47043
- * Returns the given XC with the given reference type.
47044
- */
47045
- function setXcToReferenceType(xc, referenceType) {
47046
- xc = xc.replace(/\$/g, "");
47047
- let indexOfNumber;
47048
- switch (referenceType) {
47049
- case "col":
47050
- return "$" + xc;
47051
- case "row":
47052
- indexOfNumber = xc.search(/[0-9]/);
47053
- return xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47054
- case "colrow":
47055
- indexOfNumber = xc.search(/[0-9]/);
47056
- xc = xc.slice(0, indexOfNumber) + "$" + xc.slice(indexOfNumber);
47057
- return "$" + xc;
47058
- case "none":
47059
- return xc;
47060
- }
47061
- }
47062
- /**
47063
- * Return the type of reference used in the given XC of a cell.
47064
- * Undefined behavior if the XC have a sheet reference
47065
- */
47066
- function getReferenceType(xcCell) {
47067
- if (isColAndRowFixed(xcCell)) {
47068
- return "colrow";
47069
- }
47070
- else if (isColFixed(xcCell)) {
47071
- return "col";
47072
- }
47073
- else if (isRowFixed(xcCell)) {
47074
- return "row";
47075
- }
47076
- return "none";
47077
- }
47078
- function isColFixed(xc) {
47079
- return xc.startsWith("$");
47080
- }
47081
- function isRowFixed(xc) {
47082
- return xc.includes("$", 1);
47083
- }
47084
- function isColAndRowFixed(xc) {
47085
- return xc.startsWith("$") && xc.length > 1 && xc.slice(1).includes("$");
47086
- }
47087
-
47088
47306
  const CELL_DELETED_MESSAGE = _t("The cell you are trying to edit has been deleted.");
47089
47307
  class EditionPlugin extends UIPlugin {
47090
47308
  static getters = [
@@ -48002,32 +48220,32 @@
48002
48220
  {
48003
48221
  name: _t("Sum"),
48004
48222
  types: [CellValueType.number],
48005
- compute: (values, locale) => SUM.compute.bind({ locale })([values]),
48223
+ compute: (values, locale) => sum([[values]], locale),
48006
48224
  },
48007
48225
  {
48008
48226
  name: _t("Avg"),
48009
48227
  types: [CellValueType.number],
48010
- compute: (values, locale) => AVERAGE.compute.bind({ locale })([values]),
48228
+ compute: (values, locale) => average([[values]], locale),
48011
48229
  },
48012
48230
  {
48013
48231
  name: _t("Min"),
48014
48232
  types: [CellValueType.number],
48015
- compute: (values, locale) => MIN.compute.bind({ locale })([values]),
48233
+ compute: (values, locale) => min([[values]], locale),
48016
48234
  },
48017
48235
  {
48018
48236
  name: _t("Max"),
48019
48237
  types: [CellValueType.number],
48020
- compute: (values, locale) => MAX.compute.bind({ locale })([values]),
48238
+ compute: (values, locale) => max([[values]], locale),
48021
48239
  },
48022
48240
  {
48023
48241
  name: _t("Count"),
48024
48242
  types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
48025
- compute: (values, locale) => COUNTA.compute.bind({ locale })([values]),
48243
+ compute: (values) => countAny([[values]]),
48026
48244
  },
48027
48245
  {
48028
48246
  name: _t("Count Numbers"),
48029
48247
  types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
48030
- compute: (values, locale) => COUNT.compute.bind({ locale })([values]),
48248
+ compute: (values, locale) => countNumbers([[values]], locale),
48031
48249
  },
48032
48250
  ];
48033
48251
  /**
@@ -51942,6 +52160,7 @@
51942
52160
  };
51943
52161
  sidePanel;
51944
52162
  composer;
52163
+ spreadsheetRef = owl.useRef("spreadsheet");
51945
52164
  _focusGrid;
51946
52165
  keyDownMapping;
51947
52166
  isViewportTooSmall = false;
@@ -51976,6 +52195,18 @@
51976
52195
  clipboard: this.env.clipboard || instantiateClipboard(),
51977
52196
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
51978
52197
  });
52198
+ owl.useEffect(() => {
52199
+ /**
52200
+ * Only refocus the grid if the active element is not a child of the spreadsheet
52201
+ * (i.e. activeElement is outside of the spreadsheetRef component)
52202
+ * and spreadsheet is a child of that element. Anything else means that the focus
52203
+ * is on an element that needs to keep it.
52204
+ */
52205
+ if (!this.spreadsheetRef.el.contains(document.activeElement) &&
52206
+ document.activeElement?.contains(this.spreadsheetRef.el)) {
52207
+ this.focusGrid();
52208
+ }
52209
+ }, () => [this.env.model.getters.getActiveSheetId()]);
51979
52210
  owl.useExternalListener(window, "resize", () => this.render(true));
51980
52211
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
51981
52212
  this.bindModelEvents();
@@ -52057,7 +52288,7 @@
52057
52288
  }
52058
52289
  focusGrid() {
52059
52290
  if (!this._focusGrid) {
52060
- throw new Error("_focusGrid should be exposed by the grid component");
52291
+ return;
52061
52292
  }
52062
52293
  this._focusGrid();
52063
52294
  }
@@ -55450,19 +55681,22 @@
55450
55681
  * Check if the given command is allowed by all the plugins and the history.
55451
55682
  */
55452
55683
  checkDispatchAllowed(command) {
55453
- if (isCoreCommand(command)) {
55454
- return this.checkDispatchAllowedCoreCommand(command);
55684
+ const results = isCoreCommand(command)
55685
+ ? this.checkDispatchAllowedCoreCommand(command)
55686
+ : this.checkDispatchAllowedLocalCommand(command);
55687
+ if (results.some((r) => r !== "Success" /* CommandResult.Success */)) {
55688
+ return new DispatchResult(results.flat());
55455
55689
  }
55456
- return this.checkDispatchAllowedLocalCommand(command);
55690
+ return DispatchResult.Success;
55457
55691
  }
55458
55692
  checkDispatchAllowedCoreCommand(command) {
55459
55693
  const results = this.corePlugins.map((handler) => handler.allowDispatch(command));
55460
55694
  results.push(this.range.allowDispatch(command));
55461
- return new DispatchResult(results.flat());
55695
+ return results;
55462
55696
  }
55463
55697
  checkDispatchAllowedLocalCommand(command) {
55464
55698
  const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
55465
- return new DispatchResult(results.flat());
55699
+ return results;
55466
55700
  }
55467
55701
  finalize() {
55468
55702
  this.status = 3 /* Status.Finalizing */;
@@ -55757,6 +55991,7 @@
55757
55991
  FigureComponent,
55758
55992
  Menu,
55759
55993
  SelectionInput,
55994
+ ValidationMessages,
55760
55995
  };
55761
55996
  const hooks = {
55762
55997
  useDragAndDropListItems,
@@ -55809,9 +56044,9 @@
55809
56044
  exports.tokenize = tokenize;
55810
56045
 
55811
56046
 
55812
- __info__.version = "17.1.0-alpha.3";
55813
- __info__.date = "2023-11-16T12:04:09.636Z";
55814
- __info__.hash = "2ef5b1a";
56047
+ __info__.version = "17.1.0-alpha.4";
56048
+ __info__.date = "2023-11-24T13:12:24.882Z";
56049
+ __info__.hash = "255821b";
55815
56050
 
55816
56051
 
55817
56052
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);