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