@odoo/o-spreadsheet 17.0.71 → 17.0.73

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.0.71
6
- * @date 2025-08-21T06:37:34.192Z
7
- * @hash 1592964
5
+ * @version 17.0.73
6
+ * @date 2025-09-11T08:40:46.367Z
7
+ * @hash a11c622
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';
@@ -11420,8 +11420,11 @@ function invertMatrix(M) {
11420
11420
  // (a) Swap 2 rows. This multiply the determinant by -1.
11421
11421
  // (b) Multiply a row by a scalar. This multiply the determinant by that scalar.
11422
11422
  // (c) Add to a row a multiple of another row. This does not change the determinant.
11423
+ if (M.length < 1 || M[0].length < 1) {
11424
+ throw new Error("invertMatrix: an empty matrix cannot be inverted.");
11425
+ }
11423
11426
  if (M.length !== M[0].length) {
11424
- throw new Error(`Function [[FUNCTION_NAME]] invert matrix error, only square matrices are invertible`);
11427
+ throw new Error("invertMatrix: only square matrices are invertible");
11425
11428
  }
11426
11429
  let determinant = 1;
11427
11430
  const dim = M.length;
@@ -11490,8 +11493,11 @@ function swapMatrixRows(matrix, row1, row2) {
11490
11493
  * Note: we use indexing [col][row] instead of the standard mathematical notation [row][col]
11491
11494
  */
11492
11495
  function multiplyMatrices(matrix1, matrix2) {
11496
+ if (matrix1.length < 1 || matrix2.length < 1) {
11497
+ throw new Error("multiplyMatrices: empty matrices cannot be multiplied.");
11498
+ }
11493
11499
  if (matrix1.length !== matrix2[0].length) {
11494
- throw new Error(_t("Cannot multiply matrices : incompatible matrices size."));
11500
+ throw new Error("multiplyMatrices: incompatible matrices size.");
11495
11501
  }
11496
11502
  const rowsM1 = matrix1[0].length;
11497
11503
  const colsM2 = matrix2.length;
@@ -11759,6 +11765,7 @@ const MMULT = {
11759
11765
  compute: function (matrix1, matrix2) {
11760
11766
  const _matrix1 = toMatrix(matrix1);
11761
11767
  const _matrix2 = toMatrix(matrix2);
11768
+ assert(() => _matrix1.length > 0 && _matrix2.length > 0, _t("The first and second arguments of [[FUNCTION_NAME]] must be non-empty matrices."));
11762
11769
  assert(() => _matrix1.length === _matrix2[0].length, _t("In [[FUNCTION_NAME]], the number of columns of the first matrix (%s) must be equal to the \
11763
11770
  number of rows of the second matrix (%s).", _matrix1.length.toString(), _matrix2[0].length.toString()));
11764
11771
  if (!isNumberMatrix(_matrix1) || !isNumberMatrix(_matrix2)) {
@@ -13248,6 +13255,16 @@ function assertSameNumberOfElements(...args) {
13248
13255
  const dims = args[0].length;
13249
13256
  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())));
13250
13257
  }
13258
+ function assertNonEmptyMatrix(matrix, argName) {
13259
+ assert(() => matrix.length > 0 && matrix[0].length > 0, _t("[[FUNCTION_NAME]] expects the provided values of %(argName)s to be a non-empty matrix.", {
13260
+ argName,
13261
+ }));
13262
+ }
13263
+ function assertNonEmpty(...data) {
13264
+ if (data.length === 0 || data.some((arg) => arg.length === 0)) {
13265
+ throw new NotAvailableError(_t("[[FUNCTION_NAME]] has no valid input data."));
13266
+ }
13267
+ }
13251
13268
 
13252
13269
  function filterAndFlatData(dataY, dataX) {
13253
13270
  const _flatDataY = [];
@@ -13778,6 +13795,7 @@ const FORECAST = {
13778
13795
  returns: ["NUMBER"],
13779
13796
  compute: function (x, dataY, dataX) {
13780
13797
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
13798
+ assertNonEmpty(flatDataX, flatDataY);
13781
13799
  return predictLinearValues([flatDataY], [flatDataX], matrixMap(toMatrix(x), (value) => toNumber(value, this.locale)), true);
13782
13800
  },
13783
13801
  isExported: true,
@@ -13795,6 +13813,7 @@ const GROWTH = {
13795
13813
  ],
13796
13814
  returns: ["NUMBER"],
13797
13815
  compute: function (knownDataY, knownDataX = [], newDataX = [], b = true) {
13816
+ assertNonEmptyMatrix(knownDataY, "known_data_y");
13798
13817
  return expM(predictLinearValues(logM(tryCastAsNumberMatrix(knownDataY, "the first argument (known_data_y)")), tryCastAsNumberMatrix(knownDataX, "the second argument (known_data_x)"), tryCastAsNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b)));
13799
13818
  },
13800
13819
  };
@@ -13810,6 +13829,7 @@ const INTERCEPT = {
13810
13829
  returns: ["NUMBER"],
13811
13830
  compute: function (dataY, dataX) {
13812
13831
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
13832
+ assertNonEmpty(flatDataX, flatDataY);
13813
13833
  const [[], [intercept]] = fullLinearRegression([flatDataX], [flatDataY]);
13814
13834
  return intercept;
13815
13835
  },
@@ -13861,6 +13881,7 @@ const LINEST = {
13861
13881
  ],
13862
13882
  returns: ["NUMBER"],
13863
13883
  compute: function (dataY, dataX = [], calculateB = true, verbose = false) {
13884
+ assertNonEmptyMatrix(dataY, "data_y");
13864
13885
  return fullLinearRegression(tryCastAsNumberMatrix(dataX, "the first argument (data_y)"), tryCastAsNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
13865
13886
  },
13866
13887
  isExported: true,
@@ -13878,6 +13899,7 @@ const LOGEST = {
13878
13899
  ],
13879
13900
  returns: ["NUMBER"],
13880
13901
  compute: function (dataY, dataX = [], calculateB = true, verbose = false) {
13902
+ assertNonEmptyMatrix(dataY, "data_y");
13881
13903
  const coeffs = fullLinearRegression(tryCastAsNumberMatrix(dataX, "the second argument (data_x)"), logM(tryCastAsNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
13882
13904
  for (let i = 0; i < coeffs.length; i++) {
13883
13905
  coeffs[i][0] = Math.exp(coeffs[i][0]);
@@ -13900,9 +13922,7 @@ const MATTHEWS = {
13900
13922
  const flatX = dataX.flat();
13901
13923
  const flatY = dataY.flat();
13902
13924
  assertSameNumberOfElements(flatX, flatY);
13903
- if (flatX.length === 0) {
13904
- throw new Error(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
13905
- }
13925
+ assertNonEmpty(flatX, flatY);
13906
13926
  const n = flatX.length;
13907
13927
  let trueN = 0, trueP = 0, falseP = 0, falseN = 0;
13908
13928
  for (let i = 0; i < n; ++i) {
@@ -14096,12 +14116,7 @@ const PEARSON = {
14096
14116
  returns: ["NUMBER"],
14097
14117
  compute: function (dataY, dataX) {
14098
14118
  const { flatDataX, flatDataY } = filterAndFlatData(dataX, dataY);
14099
- if (flatDataX.length === 0) {
14100
- throw new Error(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
14101
- }
14102
- if (flatDataX.length < 2) {
14103
- throw new Error(_t("[[FUNCTION_NAME]] needs at least two values for both parameters"));
14104
- }
14119
+ assertNonEmpty(flatDataX, flatDataY);
14105
14120
  const n = flatDataX.length;
14106
14121
  let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0, sumYY = 0;
14107
14122
  for (let i = 0; i < n; i++) {
@@ -14187,6 +14202,7 @@ const POLYFIT_COEFFS = {
14187
14202
  returns: ["RANGE<NUMBER>"],
14188
14203
  compute: function (dataY, dataX, order, intercept = true) {
14189
14204
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
14205
+ assertNonEmpty(flatDataX, flatDataY);
14190
14206
  return polynomialRegression(flatDataY, flatDataX, toNumber(order, this.locale), toBoolean(intercept));
14191
14207
  },
14192
14208
  isExported: false,
@@ -14207,6 +14223,7 @@ const POLYFIT_FORECAST = {
14207
14223
  compute: function (x, dataY, dataX, order, intercept = true) {
14208
14224
  const _order = toNumber(order, this.locale);
14209
14225
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
14226
+ assertNonEmpty(flatDataX, flatDataY);
14210
14227
  const coeffs = polynomialRegression(flatDataY, flatDataX, _order, toBoolean(intercept)).flat();
14211
14228
  return matrixMap(toMatrix(x), (xij) => evaluatePolynomial(coeffs, toNumber(xij, this.locale), _order));
14212
14229
  },
@@ -14332,6 +14349,7 @@ const SLOPE = {
14332
14349
  returns: ["NUMBER"],
14333
14350
  compute: function (dataY, dataX) {
14334
14351
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
14352
+ assertNonEmpty(flatDataX, flatDataY);
14335
14353
  const [[slope]] = fullLinearRegression([flatDataX], [flatDataY]);
14336
14354
  return slope;
14337
14355
  },
@@ -14382,6 +14400,7 @@ const SPEARMAN = {
14382
14400
  returns: ["NUMBER"],
14383
14401
  compute: function (dataX, dataY) {
14384
14402
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
14403
+ assertNonEmpty(flatDataX, flatDataY);
14385
14404
  const n = flatDataX.length;
14386
14405
  const order = flatDataX.map((e, i) => [e, flatDataY[i]]);
14387
14406
  order.sort((a, b) => a[0] - b[0]);
@@ -14499,6 +14518,7 @@ const STEYX = {
14499
14518
  returns: ["NUMBER"],
14500
14519
  compute: function (dataY, dataX) {
14501
14520
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
14521
+ assertNonEmpty(flatDataX, flatDataY);
14502
14522
  const data = fullLinearRegression([flatDataX], [flatDataY], true, true);
14503
14523
  return data[1][2];
14504
14524
  },
@@ -14517,6 +14537,7 @@ const TREND = {
14517
14537
  ],
14518
14538
  returns: ["NUMBER"],
14519
14539
  compute: function (knownDataY, knownDataX = [], newDataX = [], b = true) {
14540
+ assertNonEmptyMatrix(knownDataY, "known_data_y");
14520
14541
  return predictLinearValues(tryCastAsNumberMatrix(knownDataY, "the first argument (known_data_y)"), tryCastAsNumberMatrix(knownDataX, "the second argument (known_data_x)"), tryCastAsNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b));
14521
14542
  },
14522
14543
  };
@@ -31643,7 +31664,7 @@ const SUPPORTED_HORIZONTAL_ALIGNMENTS = [
31643
31664
  ];
31644
31665
  const SUPPORTED_VERTICAL_ALIGNMENTS = ["top", "center", "bottom"];
31645
31666
  const SUPPORTED_FONTS = ["Arial"];
31646
- const SUPPORTED_FILL_PATTERNS = ["solid"];
31667
+ const SUPPORTED_FILL_PATTERNS = ["solid", "none"];
31647
31668
  const SUPPORTED_CF_TYPES = [
31648
31669
  "expression",
31649
31670
  "cellIs",
@@ -31827,7 +31848,7 @@ const SUBTOTAL_FUNCTION_CONVERSION_MAP = {
31827
31848
  };
31828
31849
  /** Mapping between Excel format indexes (see XLSX_FORMAT_MAP) and some supported formats */
31829
31850
  const XLSX_FORMATS_CONVERSION_MAP = {
31830
- 0: "",
31851
+ 0: "General",
31831
31852
  1: "0",
31832
31853
  2: "0.00",
31833
31854
  3: "#,#00",
@@ -32126,11 +32147,11 @@ const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/
32126
32147
  * Excel format are defined in openXML §18.8.31
32127
32148
  */
32128
32149
  function convertXlsxFormat(numFmtId, formats, warningManager) {
32129
- if (numFmtId === 0) {
32130
- return undefined;
32131
- }
32132
32150
  // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
32133
32151
  let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || formats.find((f) => f.id === numFmtId)?.format;
32152
+ if (format === "General") {
32153
+ return undefined;
32154
+ }
32134
32155
  if (format) {
32135
32156
  try {
32136
32157
  let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
@@ -34432,10 +34453,11 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
34432
34453
  });
34433
34454
  }
34434
34455
  extractRows(worksheet) {
34456
+ const spilledCells = new Set();
34435
34457
  return this.mapOnElements({ parent: worksheet, query: "sheetData row" }, (rowElement) => {
34436
34458
  return {
34437
34459
  index: this.extractAttr(rowElement, "r", { required: true })?.asNum(),
34438
- cells: this.extractCells(rowElement),
34460
+ cells: this.extractCells(rowElement, spilledCells),
34439
34461
  height: this.extractAttr(rowElement, "ht")?.asNum(),
34440
34462
  customHeight: this.extractAttr(rowElement, "customHeight")?.asBool(),
34441
34463
  hidden: this.extractAttr(rowElement, "hidden")?.asBool(),
@@ -34443,14 +34465,26 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
34443
34465
  };
34444
34466
  });
34445
34467
  }
34446
- extractCells(row) {
34468
+ extractCells(row, spilledCells) {
34447
34469
  return this.mapOnElements({ parent: row, query: "c" }, (cellElement) => {
34470
+ const xc = this.extractAttr(cellElement, "r", { required: true })?.asString();
34471
+ const formula = this.extractCellFormula(cellElement);
34472
+ if (formula?.ref && formula.sharedIndex === undefined) {
34473
+ const zone = toZone(formula.ref);
34474
+ for (const { col, row } of positions(zone)) {
34475
+ const followerXc = toXC(col, row);
34476
+ if (followerXc !== xc) {
34477
+ spilledCells.add(followerXc);
34478
+ }
34479
+ }
34480
+ }
34481
+ const isSpilled = spilledCells.has(xc);
34448
34482
  return {
34449
- xc: this.extractAttr(cellElement, "r", { required: true })?.asString(),
34483
+ xc,
34450
34484
  styleIndex: this.extractAttr(cellElement, "s")?.asNum(),
34451
34485
  type: CELL_TYPE_CONVERSION_MAP[this.extractAttr(cellElement, "t", { default: "n" })?.asString()],
34452
- value: this.extractChildTextContent(cellElement, "v"),
34453
- formula: this.extractCellFormula(cellElement),
34486
+ value: isSpilled ? undefined : this.extractChildTextContent(cellElement, "v") ?? undefined,
34487
+ formula: isSpilled ? undefined : formula,
34454
34488
  };
34455
34489
  });
34456
34490
  }
@@ -34458,11 +34492,14 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
34458
34492
  const formulaElement = this.querySelector(cellElement, "f");
34459
34493
  if (!formulaElement)
34460
34494
  return undefined;
34461
- return {
34462
- content: this.extractTextContent(formulaElement),
34463
- sharedIndex: this.extractAttr(formulaElement, "si")?.asNum(),
34464
- ref: this.extractAttr(formulaElement, "ref")?.asString(),
34465
- };
34495
+ const content = this.extractTextContent(formulaElement);
34496
+ const sharedIndex = this.extractAttr(formulaElement, "si")?.asNum();
34497
+ const ref = this.extractAttr(formulaElement, "ref")?.asString();
34498
+ // This is the case of spilled cells of array formulas where <f> is empty
34499
+ if ((content === undefined || content.trim() === "") && sharedIndex === undefined) {
34500
+ return undefined;
34501
+ }
34502
+ return { content, sharedIndex, ref };
34466
34503
  }
34467
34504
  extractHyperLinks(worksheet) {
34468
34505
  return this.mapOnElements({ parent: worksheet, query: "hyperlink" }, (linkElement) => {
@@ -45496,7 +45533,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
45496
45533
  queuedBordersToAdd = {};
45497
45534
  queuedCFChanges = {};
45498
45535
  queuedDVChanges = {};
45499
- constructor(zones, operation, getters, dispatch, selection) {
45536
+ constructor(zones, operation, getters, dispatch, selection, mode = "copyPaste") {
45500
45537
  super(operation, getters, dispatch, selection);
45501
45538
  if (!zones.length) {
45502
45539
  this.cells = [[]];
@@ -45526,7 +45563,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
45526
45563
  const spreader = getters.getArrayFormulaSpreadingOn(position);
45527
45564
  let cell = getters.getCell(position);
45528
45565
  const evaluatedCell = getters.getEvaluatedCell(position);
45529
- if (spreader) {
45566
+ if (mode !== "shiftCells" && spreader) {
45530
45567
  const isSpreaderCopied = rowsIndex.includes(spreader.row) && columnsIndex.includes(spreader.col);
45531
45568
  const content = isSpreaderCopied
45532
45569
  ? ""
@@ -49087,12 +49124,12 @@ class ClipboardPlugin extends UIPlugin {
49087
49124
  }
49088
49125
  case "INSERT_CELL": {
49089
49126
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
49090
- const state = this.getClipboardStateForCopyCells(cut, "CUT");
49127
+ const state = this.getClipboardStateForCopyCells(cut, "CUT", "shiftCells");
49091
49128
  return state.isPasteAllowed(paste);
49092
49129
  }
49093
49130
  case "DELETE_CELL": {
49094
49131
  const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
49095
- const state = this.getClipboardStateForCopyCells(cut, "CUT");
49132
+ const state = this.getClipboardStateForCopyCells(cut, "CUT", "shiftCells");
49096
49133
  return state.isPasteAllowed(paste);
49097
49134
  }
49098
49135
  case "ACTIVATE_PAINT_FORMAT": {
@@ -49173,13 +49210,13 @@ class ClipboardPlugin extends UIPlugin {
49173
49210
  });
49174
49211
  break;
49175
49212
  }
49176
- const state = this.getClipboardStateForCopyCells(cut, "CUT");
49213
+ const state = this.getClipboardStateForCopyCells(cut, "CUT", "shiftCells");
49177
49214
  state.paste(paste);
49178
49215
  break;
49179
49216
  }
49180
49217
  case "INSERT_CELL": {
49181
49218
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
49182
- const state = this.getClipboardStateForCopyCells(cut, "CUT");
49219
+ const state = this.getClipboardStateForCopyCells(cut, "CUT", "shiftCells");
49183
49220
  state.paste(paste);
49184
49221
  break;
49185
49222
  }
@@ -49329,8 +49366,8 @@ class ClipboardPlugin extends UIPlugin {
49329
49366
  }
49330
49367
  return { cut: [cut], paste: [paste] };
49331
49368
  }
49332
- getClipboardStateForCopyCells(zones, operation) {
49333
- return new ClipboardCellsState(zones, operation, this.getters, this.dispatch, this.selection);
49369
+ getClipboardStateForCopyCells(zones, operation, mode = "copyPaste") {
49370
+ return new ClipboardCellsState(zones, operation, this.getters, this.dispatch, this.selection, mode);
49334
49371
  }
49335
49372
  /**
49336
49373
  * Get the clipboard state from the given zones.
@@ -50900,7 +50937,7 @@ class GridSelectionPlugin extends UIPlugin {
50900
50937
  bottom: !isCol ? end + deltaRow : this.getters.getNumberRows(cmd.sheetId) - 1,
50901
50938
  },
50902
50939
  ];
50903
- const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection);
50940
+ const state = new ClipboardCellsState(target, "CUT", this.getters, this.dispatch, this.selection, "shiftCells");
50904
50941
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
50905
50942
  const pasteTarget = [
50906
50943
  {
@@ -58417,6 +58454,6 @@ const constants = {
58417
58454
  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 };
58418
58455
 
58419
58456
 
58420
- __info__.version = "17.0.71";
58421
- __info__.date = "2025-08-21T06:37:34.192Z";
58422
- __info__.hash = "1592964";
58457
+ __info__.version = "17.0.73";
58458
+ __info__.date = "2025-09-11T08:40:46.367Z";
58459
+ __info__.hash = "a11c622";