@odoo/o-spreadsheet 17.3.7 → 17.3.9

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.3.7
7
- * @date 2024-07-02T10:38:52.530Z
8
- * @hash c570df3
6
+ * @version 17.3.9
7
+ * @date 2024-07-11T06:37:05.603Z
8
+ * @hash 2346a16
9
9
  */
10
10
 
11
11
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -591,7 +591,7 @@ function getAddHeaderStartIndex(position, base) {
591
591
  /**
592
592
  * Compares two objects.
593
593
  */
594
- function deepEquals(o1, o2, ignoreFunctions) {
594
+ function deepEquals(o1, o2) {
595
595
  if (o1 === o2)
596
596
  return true;
597
597
  if ((o1 && !o2) || (o2 && !o1))
@@ -607,17 +607,13 @@ function deepEquals(o1, o2, ignoreFunctions) {
607
607
  }
608
608
  }
609
609
  for (const key in o1) {
610
- const typeOfO1Key = typeof o1[key];
611
- if (typeOfO1Key !== typeof o2[key])
610
+ if (typeof o1[key] !== typeof o2[key])
612
611
  return false;
613
- if (typeOfO1Key === "object") {
614
- if (!deepEquals(o1[key], o2[key], ignoreFunctions))
612
+ if (typeof o1[key] === "object") {
613
+ if (!deepEquals(o1[key], o2[key]))
615
614
  return false;
616
615
  }
617
616
  else {
618
- if (ignoreFunctions && typeOfO1Key === "function") {
619
- continue;
620
- }
621
617
  if (o1[key] !== o2[key])
622
618
  return false;
623
619
  }
@@ -1830,7 +1826,7 @@ const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSepa
1830
1826
  });
1831
1827
  const getNumberRegex = memoize(function getNumberRegex(locale) {
1832
1828
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
1833
- const thousandsSeparator = escapeRegExp(locale.thousandsSeparator);
1829
+ const thousandsSeparator = escapeRegExp(locale.thousandsSeparator || "");
1834
1830
  const pIntegerAndDecimals = `(\\d+(${thousandsSeparator}\\d{3,})*(${decimalSeparator}\\d*)?)`; // pattern that match integer number with or without decimal digits
1835
1831
  const pOnlyDecimals = `(${decimalSeparator}\\d+)`; // pattern that match only expression with decimal digits
1836
1832
  const pScientificFormat = "(e(\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -1857,7 +1853,7 @@ function isNumber(value, locale) {
1857
1853
  return getNumberRegex(locale).test(value.trim());
1858
1854
  }
1859
1855
  const getInvaluableSymbolsRegexp = memoize(function getInvaluableSymbolsRegexp(locale) {
1860
- return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator)}]`, "g");
1856
+ return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator || "")}]`, "g");
1861
1857
  });
1862
1858
  /**
1863
1859
  * Convert a string into a number. It assumes that the string actually represents
@@ -2764,6 +2760,9 @@ function getPredicate(descr, locale) {
2764
2760
  * If the character is a special regular expression character, it is escaped with "\\".
2765
2761
  */
2766
2762
  const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2763
+ if (operand === "*") {
2764
+ return /.+/;
2765
+ }
2767
2766
  let exp = "";
2768
2767
  let predecessor = "";
2769
2768
  for (let char of operand) {
@@ -2787,9 +2786,9 @@ const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2787
2786
  }
2788
2787
  return new RegExp("^" + exp + "$", "i");
2789
2788
  });
2790
- function evaluatePredicate(value, criterion) {
2789
+ function evaluatePredicate(value = "", criterion) {
2791
2790
  const { operator, operand } = criterion;
2792
- if (value === undefined || operand === undefined || value === null || operand === null) {
2791
+ if (operand === undefined || value === null || operand === null) {
2793
2792
  return false;
2794
2793
  }
2795
2794
  if (typeof operand === "number" && operator === "=") {
@@ -5894,19 +5893,22 @@ class TokenizingChars {
5894
5893
  }
5895
5894
 
5896
5895
  function isValidLocale(locale) {
5897
- if (!(locale &&
5898
- typeof locale === "object" &&
5899
- typeof locale.name === "string" &&
5900
- typeof locale.code === "string" &&
5901
- typeof locale.thousandsSeparator === "string" &&
5902
- typeof locale.decimalSeparator === "string" &&
5903
- typeof locale.dateFormat === "string" &&
5904
- typeof locale.timeFormat === "string" &&
5905
- typeof locale.formulaArgSeparator === "string")) {
5896
+ if (!locale ||
5897
+ typeof locale !== "object" ||
5898
+ !(!locale.thousandsSeparator || typeof locale.thousandsSeparator === "string")) {
5906
5899
  return false;
5907
5900
  }
5908
- if (!Object.values(locale).every((v) => v)) {
5909
- return false;
5901
+ for (const property of [
5902
+ "code",
5903
+ "name",
5904
+ "decimalSeparator",
5905
+ "dateFormat",
5906
+ "timeFormat",
5907
+ "formulaArgSeparator",
5908
+ ]) {
5909
+ if (!locale[property] || typeof locale[property] !== "string") {
5910
+ return false;
5911
+ }
5910
5912
  }
5911
5913
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
5912
5914
  return false;
@@ -6004,7 +6006,10 @@ function canonicalizeNumberLiteral(content, locale) {
6004
6006
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
6005
6007
  return content;
6006
6008
  }
6007
- return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
6009
+ if (locale.thousandsSeparator) {
6010
+ content = content.replaceAll(locale.thousandsSeparator, "");
6011
+ }
6012
+ return content.replace(locale.decimalSeparator, ".");
6008
6013
  }
6009
6014
  /**
6010
6015
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -6993,6 +6998,146 @@ function transformRangeData(range, executed) {
6993
6998
  return undefined;
6994
6999
  }
6995
7000
 
7001
+ var State;
7002
+ (function (State) {
7003
+ /**
7004
+ * Initial state.
7005
+ * Expecting any reference for the left part of a range
7006
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7007
+ */
7008
+ State[State["LeftRef"] = 0] = "LeftRef";
7009
+ /**
7010
+ * Expecting any reference for the right part of a range
7011
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7012
+ */
7013
+ State[State["RightRef"] = 1] = "RightRef";
7014
+ /**
7015
+ * Expecting the separator without any constraint on the right part
7016
+ */
7017
+ State[State["Separator"] = 2] = "Separator";
7018
+ /**
7019
+ * Expecting the separator for a full column range
7020
+ */
7021
+ State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7022
+ /**
7023
+ * Expecting the separator for a full row range
7024
+ */
7025
+ State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7026
+ /**
7027
+ * Expecting the right part of a full column range
7028
+ * e.g. "1", "A1"
7029
+ */
7030
+ State[State["RightColumnRef"] = 5] = "RightColumnRef";
7031
+ /**
7032
+ * Expecting the right part of a full row range
7033
+ * e.g. "A", "A1"
7034
+ */
7035
+ State[State["RightRowRef"] = 6] = "RightRowRef";
7036
+ /**
7037
+ * Final state. A range has been matched
7038
+ */
7039
+ State[State["Found"] = 7] = "Found";
7040
+ })(State || (State = {}));
7041
+ const goTo = (state, guard = () => true) => [
7042
+ {
7043
+ goTo: state,
7044
+ guard,
7045
+ },
7046
+ ];
7047
+ const goToMulti = (state, guard = () => true) => ({
7048
+ goTo: state,
7049
+ guard,
7050
+ });
7051
+ const machine = {
7052
+ [State.LeftRef]: {
7053
+ REFERENCE: goTo(State.Separator),
7054
+ NUMBER: goTo(State.FullRowSeparator),
7055
+ SYMBOL: [
7056
+ goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7057
+ goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7058
+ ],
7059
+ },
7060
+ [State.FullColumnSeparator]: {
7061
+ SPACE: goTo(State.FullColumnSeparator),
7062
+ OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7063
+ },
7064
+ [State.FullRowSeparator]: {
7065
+ SPACE: goTo(State.FullRowSeparator),
7066
+ OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7067
+ },
7068
+ [State.Separator]: {
7069
+ SPACE: goTo(State.Separator),
7070
+ OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7071
+ },
7072
+ [State.RightRef]: {
7073
+ SPACE: goTo(State.RightRef),
7074
+ NUMBER: goTo(State.Found),
7075
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7076
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7077
+ },
7078
+ [State.RightColumnRef]: {
7079
+ SPACE: goTo(State.RightColumnRef),
7080
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7081
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7082
+ },
7083
+ [State.RightRowRef]: {
7084
+ SPACE: goTo(State.RightRowRef),
7085
+ NUMBER: goTo(State.Found),
7086
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7087
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7088
+ },
7089
+ [State.Found]: {},
7090
+ };
7091
+ /**
7092
+ * Check if the list of tokens starts with a sequence of tokens representing
7093
+ * a range.
7094
+ * If a range is found, the sequence is removed from the list and is returned
7095
+ * as a single token.
7096
+ */
7097
+ function matchReference(tokens) {
7098
+ let head = 0;
7099
+ let transitions = machine[State.LeftRef];
7100
+ let matchedTokens = "";
7101
+ while (transitions !== undefined) {
7102
+ const token = tokens[head++];
7103
+ if (!token) {
7104
+ return null;
7105
+ }
7106
+ const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7107
+ const nextState = transition ? transition.goTo : undefined;
7108
+ switch (nextState) {
7109
+ case undefined:
7110
+ return null;
7111
+ case State.Found:
7112
+ matchedTokens += token.value;
7113
+ tokens.splice(0, head);
7114
+ return {
7115
+ type: "REFERENCE",
7116
+ value: matchedTokens,
7117
+ };
7118
+ default:
7119
+ transitions = machine[nextState];
7120
+ matchedTokens += token.value;
7121
+ break;
7122
+ }
7123
+ }
7124
+ return null;
7125
+ }
7126
+ /**
7127
+ * Take the result of the tokenizer and transform it to be usable in the
7128
+ * manipulations of range
7129
+ *
7130
+ * @param formula
7131
+ */
7132
+ function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7133
+ const tokens = tokenize(formula, locale);
7134
+ const result = [];
7135
+ while (tokens.length) {
7136
+ result.push(matchReference(tokens) || tokens.shift());
7137
+ }
7138
+ return result;
7139
+ }
7140
+
6996
7141
  const functionRegex = /[a-zA-Z0-9\_]+(\.[a-zA-Z0-9\_]+)*/;
6997
7142
  const UNARY_OPERATORS_PREFIX = ["-", "+"];
6998
7143
  const UNARY_OPERATORS_POSTFIX = ["%"];
@@ -7141,7 +7286,7 @@ function parseExpression(tokens, parent_priority = 0) {
7141
7286
  * Parse an expression (as a string) into an AST.
7142
7287
  */
7143
7288
  function parse(str) {
7144
- return parseTokens(tokenize(str));
7289
+ return parseTokens(rangeTokenize(str));
7145
7290
  }
7146
7291
  function parseTokens(tokens) {
7147
7292
  tokens = tokens.filter((x) => x.type !== "SPACE");
@@ -7277,146 +7422,6 @@ function rightOperandToFormula(operationAST) {
7277
7422
  return needParenthesis ? `(${astToFormula(rightOperation)})` : astToFormula(rightOperation);
7278
7423
  }
7279
7424
 
7280
- var State;
7281
- (function (State) {
7282
- /**
7283
- * Initial state.
7284
- * Expecting any reference for the left part of a range
7285
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7286
- */
7287
- State[State["LeftRef"] = 0] = "LeftRef";
7288
- /**
7289
- * Expecting any reference for the right part of a range
7290
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7291
- */
7292
- State[State["RightRef"] = 1] = "RightRef";
7293
- /**
7294
- * Expecting the separator without any constraint on the right part
7295
- */
7296
- State[State["Separator"] = 2] = "Separator";
7297
- /**
7298
- * Expecting the separator for a full column range
7299
- */
7300
- State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7301
- /**
7302
- * Expecting the separator for a full row range
7303
- */
7304
- State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7305
- /**
7306
- * Expecting the right part of a full column range
7307
- * e.g. "1", "A1"
7308
- */
7309
- State[State["RightColumnRef"] = 5] = "RightColumnRef";
7310
- /**
7311
- * Expecting the right part of a full row range
7312
- * e.g. "A", "A1"
7313
- */
7314
- State[State["RightRowRef"] = 6] = "RightRowRef";
7315
- /**
7316
- * Final state. A range has been matched
7317
- */
7318
- State[State["Found"] = 7] = "Found";
7319
- })(State || (State = {}));
7320
- const goTo = (state, guard = () => true) => [
7321
- {
7322
- goTo: state,
7323
- guard,
7324
- },
7325
- ];
7326
- const goToMulti = (state, guard = () => true) => ({
7327
- goTo: state,
7328
- guard,
7329
- });
7330
- const machine = {
7331
- [State.LeftRef]: {
7332
- REFERENCE: goTo(State.Separator),
7333
- NUMBER: goTo(State.FullRowSeparator),
7334
- SYMBOL: [
7335
- goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7336
- goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7337
- ],
7338
- },
7339
- [State.FullColumnSeparator]: {
7340
- SPACE: goTo(State.FullColumnSeparator),
7341
- OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7342
- },
7343
- [State.FullRowSeparator]: {
7344
- SPACE: goTo(State.FullRowSeparator),
7345
- OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7346
- },
7347
- [State.Separator]: {
7348
- SPACE: goTo(State.Separator),
7349
- OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7350
- },
7351
- [State.RightRef]: {
7352
- SPACE: goTo(State.RightRef),
7353
- NUMBER: goTo(State.Found),
7354
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7355
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7356
- },
7357
- [State.RightColumnRef]: {
7358
- SPACE: goTo(State.RightColumnRef),
7359
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7360
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7361
- },
7362
- [State.RightRowRef]: {
7363
- SPACE: goTo(State.RightRowRef),
7364
- NUMBER: goTo(State.Found),
7365
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7366
- SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7367
- },
7368
- [State.Found]: {},
7369
- };
7370
- /**
7371
- * Check if the list of tokens starts with a sequence of tokens representing
7372
- * a range.
7373
- * If a range is found, the sequence is removed from the list and is returned
7374
- * as a single token.
7375
- */
7376
- function matchReference(tokens) {
7377
- let head = 0;
7378
- let transitions = machine[State.LeftRef];
7379
- let matchedTokens = "";
7380
- while (transitions !== undefined) {
7381
- const token = tokens[head++];
7382
- if (!token) {
7383
- return null;
7384
- }
7385
- const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7386
- const nextState = transition ? transition.goTo : undefined;
7387
- switch (nextState) {
7388
- case undefined:
7389
- return null;
7390
- case State.Found:
7391
- matchedTokens += token.value;
7392
- tokens.splice(0, head);
7393
- return {
7394
- type: "REFERENCE",
7395
- value: matchedTokens,
7396
- };
7397
- default:
7398
- transitions = machine[nextState];
7399
- matchedTokens += token.value;
7400
- break;
7401
- }
7402
- }
7403
- return null;
7404
- }
7405
- /**
7406
- * Take the result of the tokenizer and transform it to be usable in the
7407
- * manipulations of range
7408
- *
7409
- * @param formula
7410
- */
7411
- function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7412
- const tokens = tokenize(formula, locale);
7413
- const result = [];
7414
- while (tokens.length) {
7415
- result.push(matchReference(tokens) || tokens.shift());
7416
- }
7417
- return result;
7418
- }
7419
-
7420
7425
  /**
7421
7426
  * Add the following information on tokens:
7422
7427
  * - length
@@ -9834,7 +9839,6 @@ class ChartJsComponent extends Component {
9834
9839
  };
9835
9840
  canvas = useRef("graphContainer");
9836
9841
  chart;
9837
- currentRuntime;
9838
9842
  get background() {
9839
9843
  return this.chartRuntime.background;
9840
9844
  }
@@ -9851,18 +9855,11 @@ class ChartJsComponent extends Component {
9851
9855
  setup() {
9852
9856
  onMounted(() => {
9853
9857
  const runtime = this.chartRuntime;
9854
- this.currentRuntime = runtime;
9855
9858
  // Note: chartJS modify the runtime in place, so it's important to give it a copy
9856
9859
  this.createChart(deepCopy(runtime.chartJsConfig));
9857
9860
  });
9858
9861
  onWillUnmount(() => this.chart?.destroy());
9859
- useEffect(() => {
9860
- const runtime = this.chartRuntime;
9861
- if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
9862
- this.currentRuntime = runtime;
9863
- this.updateChartJs(deepCopy(runtime));
9864
- }
9865
- });
9862
+ useEffect(() => this.updateChartJs(deepCopy(this.chartRuntime)), () => [this.chartRuntime]);
9866
9863
  }
9867
9864
  createChart(chartData) {
9868
9865
  const canvas = this.canvas.el;
@@ -22696,7 +22693,7 @@ function truncateLabel(label) {
22696
22693
  /**
22697
22694
  * Get a default chart js configuration
22698
22695
  */
22699
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22696
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels = true }) {
22700
22697
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22701
22698
  const options = {
22702
22699
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -23925,10 +23922,6 @@ function createLineOrScatterChartRuntime(chart, getters) {
23925
23922
  const colors = new ColorGenerator();
23926
23923
  const definition = chart.getDefinition();
23927
23924
  for (let [index, { label, data }] of dataSetsValues.entries()) {
23928
- if (["linear", "time"].includes(axisType)) {
23929
- // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
23930
- data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
23931
- }
23932
23925
  const color = colors.next();
23933
23926
  let backgroundRGBA = colorToRGBA(color);
23934
23927
  if (stacked) {
@@ -23944,6 +23937,10 @@ function createLineOrScatterChartRuntime(chart, getters) {
23944
23937
  return value;
23945
23938
  });
23946
23939
  }
23940
+ if (["linear", "time"].includes(axisType)) {
23941
+ // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
23942
+ data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
23943
+ }
23947
23944
  const backgroundColor = rgbaToHex(backgroundRGBA);
23948
23945
  const dataset = {
23949
23946
  label,
@@ -36261,7 +36258,13 @@ class SettingsPanel extends Component {
36261
36258
  }
36262
36259
  async loadLocales() {
36263
36260
  this.loadedLocales = (await this.env.loadLocales())
36264
- .filter(isValidLocale)
36261
+ .filter((locale) => {
36262
+ const isValid = isValidLocale(locale);
36263
+ if (!isValid) {
36264
+ console.warn(`Invalid locale: ${locale["code"]} ${locale}`);
36265
+ }
36266
+ return isValid;
36267
+ })
36265
36268
  .sort((a, b) => a.name.localeCompare(b.name));
36266
36269
  }
36267
36270
  get numberFormatPreview() {
@@ -41413,6 +41416,7 @@ class Grid extends Component {
41413
41416
  return;
41414
41417
  }
41415
41418
  if (clipboardData.types.indexOf(ClipboardMIMEType.PlainText) > -1) {
41419
+ ev.preventDefault();
41416
41420
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
41417
41421
  const target = this.env.model.getters.getSelectedZones();
41418
41422
  const clipboardString = this.env.model.getters.getClipboardTextContent();
@@ -45124,7 +45128,7 @@ function getRelationFile(file, xmls) {
45124
45128
  return relsFile;
45125
45129
  }
45126
45130
 
45127
- const EXCEL_IMPORT_VERSION = 16;
45131
+ const EXCEL_IMPORT_VERSION = 17;
45128
45132
  class XlsxReader {
45129
45133
  warningManager;
45130
45134
  xmls;
@@ -45256,7 +45260,7 @@ function normalizeV9(formula) {
45256
45260
  * a breaking change is made in the way the state is handled, and an upgrade
45257
45261
  * function should be defined
45258
45262
  */
45259
- const CURRENT_VERSION = 16;
45263
+ const CURRENT_VERSION = 17;
45260
45264
  const INITIAL_SHEET_ID = "Sheet1";
45261
45265
  /**
45262
45266
  * This function tries to load anything that could look like a valid
@@ -46203,6 +46207,21 @@ class BordersPlugin extends CorePlugin {
46203
46207
  return [];
46204
46208
  return Object.keys(sheetBorders).map((index) => parseInt(index, 10));
46205
46209
  }
46210
+ /**
46211
+ * Get all the rows which contains at least a border
46212
+ */
46213
+ getRowsWithBorders(sheetId) {
46214
+ const sheetBorders = this.borders[sheetId]?.filter(isDefined);
46215
+ if (!sheetBorders)
46216
+ return [];
46217
+ const rowsWithBorders = new Set();
46218
+ for (const rowBorders of sheetBorders) {
46219
+ for (const rowBorder in rowBorders) {
46220
+ rowsWithBorders.add(parseInt(rowBorder, 10));
46221
+ }
46222
+ }
46223
+ return Array.from(rowsWithBorders);
46224
+ }
46206
46225
  /**
46207
46226
  * Get the range of all the rows in the sheet
46208
46227
  */
@@ -46252,7 +46271,7 @@ class BordersPlugin extends CorePlugin {
46252
46271
  destructive: false,
46253
46272
  });
46254
46273
  }
46255
- this.getRowsRange(sheetId)
46274
+ this.getRowsWithBorders(sheetId)
46256
46275
  .filter((row) => row >= start)
46257
46276
  .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
46258
46277
  .forEach((row) => {
@@ -52584,7 +52603,7 @@ class PositionSet {
52584
52603
  return this.sheets[position.sheetId].getValue(position) === 1;
52585
52604
  }
52586
52605
  clear() {
52587
- const insertions = this.insertions;
52606
+ const insertions = [...this];
52588
52607
  this.insertions = [];
52589
52608
  for (const sheetId in this.sheets) {
52590
52609
  this.sheets[sheetId].clear();
@@ -52922,6 +52941,7 @@ class Evaluator {
52922
52941
  }
52923
52942
  finally {
52924
52943
  this.cellsBeingComputed.delete(cellId);
52944
+ this.nextPositionsToUpdate.delete(position);
52925
52945
  }
52926
52946
  }
52927
52947
  computeAndSave(position) {
@@ -52948,8 +52968,33 @@ class Evaluator {
52948
52968
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
52949
52969
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
52950
52970
  this.spreadValues(formulaPosition, formulaReturn));
52971
+ this.invalidatePositionsDependingOnSpread(formulaPosition, nbColumns, nbRows);
52951
52972
  return createEvaluatedCell(nullValueToZeroValue(formulaReturn[0][0]), this.getters.getLocale(), cellData);
52952
52973
  }
52974
+ invalidatePositionsDependingOnSpread(arrayFormulaPosition, nbColumns, nbRows) {
52975
+ // the result matrix is split in 2 zones to exclude the array formula position
52976
+ const top = arrayFormulaPosition.row;
52977
+ const left = arrayFormulaPosition.col;
52978
+ const bottom = top + nbRows - 1;
52979
+ const leftColumnZone = {
52980
+ top: top + 1,
52981
+ bottom,
52982
+ left,
52983
+ right: left,
52984
+ };
52985
+ const rightPartZone = {
52986
+ top,
52987
+ bottom,
52988
+ left: left + 1,
52989
+ right: left + nbColumns - 1,
52990
+ };
52991
+ const sheetId = arrayFormulaPosition.sheetId;
52992
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn([
52993
+ { sheetId, zone: rightPartZone },
52994
+ { sheetId, zone: leftColumnZone },
52995
+ ]);
52996
+ this.nextPositionsToUpdate.addMany(invalidatedPositions);
52997
+ }
52953
52998
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
52954
52999
  const numberOfCols = this.getters.getNumberCols(sheetId);
52955
53000
  const numberOfRows = this.getters.getNumberRows(sheetId);
@@ -53004,9 +53049,6 @@ class Evaluator {
53004
53049
  const cell = this.getters.getCell(position);
53005
53050
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
53006
53051
  this.evaluatedCells.set(position, evaluatedCell);
53007
- // check if formula dependencies present in the spread zone
53008
- // if so, they need to be recomputed
53009
- this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([position]));
53010
53052
  };
53011
53053
  }
53012
53054
  invalidateSpreading(position) {
@@ -67001,6 +67043,6 @@ const constants = {
67001
67043
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
67002
67044
 
67003
67045
 
67004
- __info__.version = "17.3.7";
67005
- __info__.date = "2024-07-02T10:38:52.530Z";
67006
- __info__.hash = "c570df3";
67046
+ __info__.version = "17.3.9";
67047
+ __info__.date = "2024-07-11T06:37:05.603Z";
67048
+ __info__.hash = "2346a16";