@odoo/o-spreadsheet 18.1.11 → 18.1.13

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 18.1.11
6
- * @date 2025-03-12T15:31:44.276Z
7
- * @hash 7de2363
5
+ * @version 18.1.13
6
+ * @date 2025-03-26T12:48:31.680Z
7
+ * @hash 45ec54c
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1122,7 +1122,10 @@ function rgbaStringToHex(color) {
1122
1122
  }
1123
1123
  else if (stringVals.length === 4) {
1124
1124
  const alpha = parseFloat(stringVals.pop() || "1");
1125
- alphaHex = Math.round((alpha || 1) * 255);
1125
+ if (isNaN(alpha)) {
1126
+ throw new Error("invalid alpha value");
1127
+ }
1128
+ alphaHex = Math.round(alpha * 255);
1126
1129
  }
1127
1130
  const vals = stringVals.map((val) => parseInt(val, 10));
1128
1131
  if (alphaHex !== 255) {
@@ -6278,11 +6281,13 @@ function getDefaultCellHeight(ctx, cell, colSize) {
6278
6281
  if (!cell || (!cell.isFormula && !cell.content)) {
6279
6282
  return DEFAULT_CELL_HEIGHT;
6280
6283
  }
6281
- const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6282
- const numberOfLines = cell.isFormula
6283
- ? 1
6284
- : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
6285
- const fontSize = computeTextFontSizeInPixels(cell.style);
6284
+ const content = cell.isFormula ? "" : cell.content;
6285
+ return getCellContentHeight(ctx, content, cell.style, colSize);
6286
+ }
6287
+ function getCellContentHeight(ctx, content, style, colSize) {
6288
+ const maxWidth = style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6289
+ const numberOfLines = splitTextToWidth(ctx, content, style, maxWidth).length;
6290
+ const fontSize = computeTextFontSizeInPixels(style);
6286
6291
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
6287
6292
  }
6288
6293
  function getDefaultContextFont(fontSize, bold = false, italic = false) {
@@ -6990,7 +6995,7 @@ function isValidLocale(locale) {
6990
6995
  */
6991
6996
  function canonicalizeNumberContent(content, locale) {
6992
6997
  return content.startsWith("=")
6993
- ? canonicalizeFormula$1(content, locale)
6998
+ ? canonicalizeFormula(content, locale)
6994
6999
  : canonicalizeNumberLiteral(content, locale);
6995
7000
  }
6996
7001
  /**
@@ -7005,7 +7010,7 @@ function canonicalizeNumberContent(content, locale) {
7005
7010
  */
7006
7011
  function canonicalizeContent(content, locale) {
7007
7012
  return content.startsWith("=")
7008
- ? canonicalizeFormula$1(content, locale)
7013
+ ? canonicalizeFormula(content, locale)
7009
7014
  : canonicalizeLiteral(content, locale);
7010
7015
  }
7011
7016
  /**
@@ -7021,15 +7026,21 @@ function localizeContent(content, locale) {
7021
7026
  ? localizeFormula(content, locale)
7022
7027
  : localizeLiteral(content, locale);
7023
7028
  }
7029
+ /** Change a number string to its canonical form (en_US locale) */
7030
+ function canonicalizeNumberValue(content, locale) {
7031
+ return content.startsWith("=")
7032
+ ? canonicalizeFormula(content, locale)
7033
+ : canonicalizeNumberLiteral(content, locale);
7034
+ }
7024
7035
  /** Change a formula to its canonical form (en_US locale) */
7025
- function canonicalizeFormula$1(formula, locale) {
7026
- return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
7036
+ function canonicalizeFormula(formula, locale) {
7037
+ return _localizeFormula(formula, locale, DEFAULT_LOCALE);
7027
7038
  }
7028
7039
  /** Change a formula from the canonical form to the given locale */
7029
7040
  function localizeFormula(formula, locale) {
7030
- return _localizeFormula$1(formula, DEFAULT_LOCALE, locale);
7041
+ return _localizeFormula(formula, DEFAULT_LOCALE, locale);
7031
7042
  }
7032
- function _localizeFormula$1(formula, fromLocale, toLocale) {
7043
+ function _localizeFormula(formula, fromLocale, toLocale) {
7033
7044
  if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
7034
7045
  fromLocale.decimalSeparator === toLocale.decimalSeparator) {
7035
7046
  return formula;
@@ -7184,37 +7195,6 @@ function getDateTimeFormat(locale) {
7184
7195
  return locale.dateFormat + " " + locale.timeFormat;
7185
7196
  }
7186
7197
 
7187
- /** Change a number string to its canonical form (en_US locale) */
7188
- function canonicalizeNumberValue(content, locale) {
7189
- return content.startsWith("=")
7190
- ? canonicalizeFormula(content, locale)
7191
- : canonicalizeNumberLiteral(content, locale);
7192
- }
7193
- /** Change a formula to its canonical form (en_US locale) */
7194
- function canonicalizeFormula(formula, locale) {
7195
- return _localizeFormula(formula, locale, DEFAULT_LOCALE);
7196
- }
7197
- function _localizeFormula(formula, fromLocale, toLocale) {
7198
- if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
7199
- fromLocale.decimalSeparator === toLocale.decimalSeparator) {
7200
- return formula;
7201
- }
7202
- const tokens = tokenize(formula, fromLocale);
7203
- let localizedFormula = "";
7204
- for (const token of tokens) {
7205
- if (token.type === "NUMBER") {
7206
- localizedFormula += token.value.replace(fromLocale.decimalSeparator, toLocale.decimalSeparator);
7207
- }
7208
- else if (token.type === "ARG_SEPARATOR") {
7209
- localizedFormula += toLocale.formulaArgSeparator;
7210
- }
7211
- else {
7212
- localizedFormula += token.value;
7213
- }
7214
- }
7215
- return localizedFormula;
7216
- }
7217
-
7218
7198
  function boolAnd(args) {
7219
7199
  let foundBoolean = false;
7220
7200
  let acc = true;
@@ -8478,13 +8458,6 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
8478
8458
  this.clearClippedZones(content);
8479
8459
  const selection = target[0];
8480
8460
  this.pasteZone(sheetId, selection.left, selection.top, content.cells, options);
8481
- this.dispatch("MOVE_RANGES", {
8482
- target: content.zones,
8483
- sheetId: content.sheetId,
8484
- targetSheetId: sheetId,
8485
- col: selection.left,
8486
- row: selection.top,
8487
- });
8488
8461
  }
8489
8462
  /**
8490
8463
  * Clear the clipped zones: remove the cells and clear the formatting
@@ -8993,14 +8966,15 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
8993
8966
  }
8994
8967
  merges.push(mergesInRow);
8995
8968
  }
8996
- return { merges };
8969
+ return { merges, sheetId };
8997
8970
  }
8998
8971
  /**
8999
8972
  * Paste the clipboard content in the given target
9000
8973
  */
9001
8974
  paste(target, content, options) {
9002
8975
  if (options.isCutOperation) {
9003
- return;
8976
+ const copiedMerges = content.merges.flat().filter(isDefined);
8977
+ this.dispatch("REMOVE_MERGE", { sheetId: content.sheetId, target: copiedMerges });
9004
8978
  }
9005
8979
  this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
9006
8980
  }
@@ -9035,6 +9009,27 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
9035
9009
  }
9036
9010
  }
9037
9011
 
9012
+ class ReferenceClipboardHandler extends AbstractCellClipboardHandler {
9013
+ copy(data) {
9014
+ return {
9015
+ zones: data.clippedZones,
9016
+ sheetId: data.sheetId,
9017
+ };
9018
+ }
9019
+ paste(target, content, options) {
9020
+ if (options.isCutOperation) {
9021
+ const selection = target.zones[0];
9022
+ this.dispatch("MOVE_RANGES", {
9023
+ target: content.zones,
9024
+ sheetId: content.sheetId,
9025
+ targetSheetId: target.sheetId,
9026
+ col: selection.left,
9027
+ row: selection.top,
9028
+ });
9029
+ }
9030
+ }
9031
+ }
9032
+
9038
9033
  class SheetClipboardHandler extends AbstractCellClipboardHandler {
9039
9034
  isPasteAllowed(sheetId, target, content, options) {
9040
9035
  if (!("cells" in content)) {
@@ -9198,7 +9193,8 @@ clipboardHandlersRegistries.cellHandlers
9198
9193
  .add("merge", MergeClipboardHandler)
9199
9194
  .add("border", BorderClipboardHandler)
9200
9195
  .add("table", TableClipboardHandler)
9201
- .add("conditionalFormat", ConditionalFormatClipboardHandler);
9196
+ .add("conditionalFormat", ConditionalFormatClipboardHandler)
9197
+ .add("references", ReferenceClipboardHandler);
9202
9198
 
9203
9199
  function transformZone(zone, executed) {
9204
9200
  if (executed.type === "REMOVE_COLUMNS_ROWS") {
@@ -9573,6 +9569,7 @@ class ComposerFocusStore extends SpreadsheetStore {
9573
9569
  }
9574
9570
 
9575
9571
  const TREND_LINE_XAXIS_ID = "x1";
9572
+ const MOVING_AVERAGE_TREND_LINE_XAXIS_ID = "xMovingAverage";
9576
9573
  /**
9577
9574
  * This file contains helpers that are common to different charts (mainly
9578
9575
  * line, bar and pie charts)
@@ -9913,6 +9910,9 @@ function getPieColors(colors, dataSetsValues) {
9913
9910
  }
9914
9911
  return pieColors;
9915
9912
  }
9913
+ function isTrendLineAxis(axisID) {
9914
+ return axisID === TREND_LINE_XAXIS_ID || axisID === MOVING_AVERAGE_TREND_LINE_XAXIS_ID;
9915
+ }
9916
9916
 
9917
9917
  /** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
9918
9918
  const chartShowValuesPlugin = {
@@ -9957,7 +9957,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
9957
9957
  const yMin = chart.chartArea.top;
9958
9958
  const textsPositions = {};
9959
9959
  for (const dataset of chart._metasets) {
9960
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID || dataset.hidden) {
9960
+ if (isTrendLineAxis(dataset.axisID) || dataset.hidden) {
9961
9961
  continue;
9962
9962
  }
9963
9963
  for (let i = 0; i < dataset._parsed.length; i++) {
@@ -10000,7 +10000,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10000
10000
  const xMin = chart.chartArea.left;
10001
10001
  const textsPositions = {};
10002
10002
  for (const dataset of chart._metasets) {
10003
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
10003
+ if (isTrendLineAxis(dataset.axisID)) {
10004
10004
  return; // ignore trend lines
10005
10005
  }
10006
10006
  for (let i = 0; i < dataset._parsed.length; i++) {
@@ -20364,11 +20364,26 @@ const SEARCH = {
20364
20364
  const _searchFor = toString(searchFor).toLowerCase();
20365
20365
  const _textToSearch = toString(textToSearch).toLowerCase();
20366
20366
  const _startingAt = toNumber(startingAt, this.locale);
20367
- assert(() => _textToSearch !== "", _t("The text_to_search must be non-empty."));
20368
- assert(() => _startingAt >= 1, _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt.toString()));
20367
+ if (_textToSearch === "") {
20368
+ return {
20369
+ value: CellErrorType.GenericError,
20370
+ message: _t("The text_to_search must be non-empty."),
20371
+ };
20372
+ }
20373
+ if (_startingAt < 1) {
20374
+ return {
20375
+ value: CellErrorType.GenericError,
20376
+ message: _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt),
20377
+ };
20378
+ }
20369
20379
  const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);
20370
- assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch));
20371
- return result + 1;
20380
+ if (result === -1) {
20381
+ return {
20382
+ value: CellErrorType.GenericError,
20383
+ message: _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch),
20384
+ };
20385
+ }
20386
+ return { value: result + 1 };
20372
20387
  },
20373
20388
  isExported: true,
20374
20389
  };
@@ -20995,8 +21010,8 @@ class AbstractComposerStore extends SpreadsheetStore {
20995
21010
  this.computeParenthesisRelatedToCursor();
20996
21011
  }
20997
21012
  cancelEdition() {
20998
- this.cancelEditionAndActivateSheet();
20999
21013
  this.resetContent();
21014
+ this.cancelEditionAndActivateSheet();
21000
21015
  }
21001
21016
  setCurrentContent(content, selection) {
21002
21017
  if (selection && !this.isSelectionValid(content.length, selection.start, selection.end)) {
@@ -21014,8 +21029,8 @@ class AbstractComposerStore extends SpreadsheetStore {
21014
21029
  switch (cmd.type) {
21015
21030
  case "SELECT_FIGURE":
21016
21031
  if (cmd.id) {
21017
- this.cancelEditionAndActivateSheet();
21018
21032
  this.resetContent();
21033
+ this.cancelEditionAndActivateSheet();
21019
21034
  }
21020
21035
  break;
21021
21036
  case "START_CHANGE_HIGHLIGHT":
@@ -21703,11 +21718,14 @@ function compileTokens(tokens) {
21703
21718
  }
21704
21719
  }
21705
21720
  function compileTokensOrThrow(tokens) {
21706
- const { dependencies, constantValues, symbols } = formulaArguments(tokens);
21707
- const cacheKey = compilationCacheKey(tokens, dependencies, constantValues);
21721
+ const { dependencies, literalValues, symbols } = formulaArguments(tokens);
21722
+ const cacheKey = compilationCacheKey(tokens);
21708
21723
  if (!functionCache[cacheKey]) {
21709
21724
  const ast = parseTokens([...tokens]);
21710
21725
  const scope = new Scope();
21726
+ let stringCount = 0;
21727
+ let numberCount = 0;
21728
+ let dependencyCount = 0;
21711
21729
  if (ast.type === "BIN_OPERATION" && ast.value === ":") {
21712
21730
  throw new BadExpressionError(_t("Invalid formula"));
21713
21731
  }
@@ -21781,16 +21799,15 @@ function compileTokensOrThrow(tokens) {
21781
21799
  case "BOOLEAN":
21782
21800
  return code.return(`{ value: ${ast.value} }`);
21783
21801
  case "NUMBER":
21784
- return code.return(`{ value: this.constantValues.numbers[${constantValues.numbers.indexOf(ast.value)}] }`);
21802
+ return code.return(`this.literalValues.numbers[${numberCount++}]`);
21785
21803
  case "STRING":
21786
- return code.return(`{ value: this.constantValues.strings[${constantValues.strings.indexOf(ast.value)}] }`);
21804
+ return code.return(`this.literalValues.strings[${stringCount++}]`);
21787
21805
  case "REFERENCE":
21788
- const referenceIndex = dependencies.indexOf(ast.value);
21789
21806
  if ((!isMeta && ast.value.includes(":")) || hasRange) {
21790
- return code.return(`range(deps[${referenceIndex}])`);
21807
+ return code.return(`range(deps[${dependencyCount++}])`);
21791
21808
  }
21792
21809
  else {
21793
- return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"})`);
21810
+ return code.return(`ref(deps[${dependencyCount++}], ${isMeta ? "true" : "false"})`);
21794
21811
  }
21795
21812
  case "FUNCALL":
21796
21813
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
@@ -21822,7 +21839,7 @@ function compileTokensOrThrow(tokens) {
21822
21839
  const compiledFormula = {
21823
21840
  execute: functionCache[cacheKey],
21824
21841
  dependencies,
21825
- constantValues,
21842
+ literalValues,
21826
21843
  symbols,
21827
21844
  tokens,
21828
21845
  isBadExpression: false,
@@ -21835,33 +21852,31 @@ function compileTokensOrThrow(tokens) {
21835
21852
  * References, numbers and strings are replaced with placeholders because
21836
21853
  * the compiled formula does not depend on their actual value.
21837
21854
  * Both `=A1+1+"2"` and `=A2+2+"3"` are compiled to the exact same function.
21838
- *
21839
21855
  * Spaces are also ignored to compute the cache key.
21840
21856
  *
21841
- * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|0|+|1|+SUM(|N0|,|N0|,|S0|)`
21857
+ * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|C|+|C|+SUM(|N|,|N|,|S|)`
21842
21858
  */
21843
- function compilationCacheKey(tokens, dependencies, constantValues, symbols) {
21859
+ function compilationCacheKey(tokens) {
21844
21860
  let cacheKey = "";
21845
21861
  for (const token of tokens) {
21846
21862
  switch (token.type) {
21847
21863
  case "STRING":
21848
- const value = removeStringQuotes(token.value);
21849
- cacheKey += `|S${constantValues.strings.indexOf(value)}|`;
21864
+ cacheKey += "|S|";
21850
21865
  break;
21851
21866
  case "NUMBER":
21852
- cacheKey += `|N${constantValues.numbers.indexOf(parseNumber(token.value, DEFAULT_LOCALE))}|`;
21867
+ cacheKey += "|N|";
21853
21868
  break;
21854
21869
  case "REFERENCE":
21855
21870
  case "INVALID_REFERENCE":
21856
21871
  if (token.value.includes(":")) {
21857
- cacheKey += `R|${dependencies.indexOf(token.value)}|`;
21872
+ cacheKey += "|R|";
21858
21873
  }
21859
21874
  else {
21860
- cacheKey += `C|${dependencies.indexOf(token.value)}|`;
21875
+ cacheKey += "|C|";
21861
21876
  }
21862
21877
  break;
21863
21878
  case "SPACE":
21864
- cacheKey += "";
21879
+ // ignore spaces
21865
21880
  break;
21866
21881
  default:
21867
21882
  cacheKey += token.value;
@@ -21874,7 +21889,7 @@ function compilationCacheKey(tokens, dependencies, constantValues, symbols) {
21874
21889
  * Return formula arguments which are references, strings and numbers.
21875
21890
  */
21876
21891
  function formulaArguments(tokens) {
21877
- const constantValues = {
21892
+ const literalValues = {
21878
21893
  numbers: [],
21879
21894
  strings: [],
21880
21895
  };
@@ -21888,15 +21903,11 @@ function formulaArguments(tokens) {
21888
21903
  break;
21889
21904
  case "STRING":
21890
21905
  const value = removeStringQuotes(token.value);
21891
- if (!constantValues.strings.includes(value)) {
21892
- constantValues.strings.push(value);
21893
- }
21906
+ literalValues.strings.push({ value });
21894
21907
  break;
21895
21908
  case "NUMBER": {
21896
21909
  const value = parseNumber(token.value, DEFAULT_LOCALE);
21897
- if (!constantValues.numbers.includes(value)) {
21898
- constantValues.numbers.push(value);
21899
- }
21910
+ literalValues.numbers.push({ value });
21900
21911
  break;
21901
21912
  }
21902
21913
  case "SYMBOL": {
@@ -21907,7 +21918,7 @@ function formulaArguments(tokens) {
21907
21918
  }
21908
21919
  return {
21909
21920
  dependencies,
21910
- constantValues,
21921
+ literalValues,
21911
21922
  symbols,
21912
21923
  };
21913
21924
  }
@@ -22331,7 +22342,7 @@ autoCompleteProviders.add("pivot_group_values", {
22331
22342
  text,
22332
22343
  description: usedLabel,
22333
22344
  htmlContent: [{ value: text, color }],
22334
- fuzzySearchKey: value + usedLabel,
22345
+ fuzzySearchKey: text + usedLabel,
22335
22346
  };
22336
22347
  });
22337
22348
  },
@@ -22828,9 +22839,9 @@ const XLSX_CHART_TYPES = [
22828
22839
  /** In XLSX color format (no #) */
22829
22840
  const AUTO_COLOR = "000000";
22830
22841
  const XLSX_ICONSET_MAP = {
22831
- arrow: "3Arrows",
22842
+ arrows: "3Arrows",
22832
22843
  smiley: "3Symbols",
22833
- dot: "3TrafficLights1",
22844
+ dots: "3TrafficLights1",
22834
22845
  };
22835
22846
  const NAMESPACE = {
22836
22847
  styleSheet: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
@@ -23501,6 +23512,7 @@ const ICON_SET_CONVERSION_MAP = {
23501
23512
  };
23502
23513
  /** Map between legend position in XLSX file and human readable position */
23503
23514
  const DRAWING_LEGEND_POSITION_CONVERSION_MAP = {
23515
+ none: "none",
23504
23516
  b: "bottom",
23505
23517
  t: "top",
23506
23518
  l: "left",
@@ -26263,7 +26275,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
26263
26275
  default: "ffffff",
26264
26276
  }).asString(),
26265
26277
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
26266
- default: "b",
26278
+ default: "none",
26267
26279
  }).asString()],
26268
26280
  stacked: barChartGrouping === "stacked",
26269
26281
  fontColor: "000000",
@@ -26297,7 +26309,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
26297
26309
  default: "ffffff",
26298
26310
  }).asString(),
26299
26311
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
26300
- default: "b",
26312
+ default: "none",
26301
26313
  }).asString()],
26302
26314
  stacked: barChartGrouping === "stacked",
26303
26315
  fontColor: "000000",
@@ -28909,7 +28921,8 @@ function getChartLabelValues(getters, dataSets, labelRange) {
28909
28921
  }
28910
28922
  }
28911
28923
  else if (dataSets.length === 1) {
28912
- for (let i = 0; i < getData(getters, dataSets[0]).length; i++) {
28924
+ const dataLength = getData(getters, dataSets[0]).length;
28925
+ for (let i = 0; i < dataLength; i++) {
28913
28926
  labels.formattedValues.push("");
28914
28927
  labels.values.push("");
28915
28928
  }
@@ -29092,7 +29105,7 @@ function getLineChartDatasets(definition, args) {
29092
29105
  function getScatterChartDatasets(definition, args) {
29093
29106
  const dataSets = getLineChartDatasets(definition, args);
29094
29107
  for (const dataSet of dataSets) {
29095
- if (dataSet.xAxisID !== TREND_LINE_XAXIS_ID) {
29108
+ if (!isTrendLineAxis(dataSet.xAxisID)) {
29096
29109
  dataSet.showLine = false;
29097
29110
  }
29098
29111
  }
@@ -29219,7 +29232,9 @@ function getTrendingLineDataSet(dataset, config, data) {
29219
29232
  const borderColor = config.color || lightenColor(rgbaToHex(defaultBorderColor), 0.5);
29220
29233
  return {
29221
29234
  type: "line",
29222
- xAxisID: TREND_LINE_XAXIS_ID,
29235
+ xAxisID: config.type === "trailingMovingAverage"
29236
+ ? MOVING_AVERAGE_TREND_LINE_XAXIS_ID
29237
+ : TREND_LINE_XAXIS_ID,
29223
29238
  yAxisID: dataset.yAxisID,
29224
29239
  label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
29225
29240
  data,
@@ -29294,22 +29309,19 @@ function getPieChartLegend(definition, args) {
29294
29309
  const { dataSetsValues } = args;
29295
29310
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
29296
29311
  const colors = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
29312
+ const fontColor = chartFontColor(definition.background);
29297
29313
  return {
29298
29314
  ...getLegendDisplayOptions(definition),
29299
29315
  labels: {
29300
- color: chartFontColor(definition.background),
29301
29316
  usePointStyle: true,
29302
- //@ts-ignore
29303
- generateLabels: (c) =>
29304
- //@ts-ignore
29305
- c.data.labels.map((label, index) => ({
29306
- text: label,
29317
+ generateLabels: (c) => c.data.labels?.map((label, index) => ({
29318
+ text: String(label),
29307
29319
  strokeStyle: colors[index],
29308
29320
  fillStyle: colors[index],
29309
29321
  pointStyle: "rect",
29310
- hidden: false,
29311
29322
  lineWidth: 2,
29312
- })),
29323
+ fontColor,
29324
+ })) || [],
29313
29325
  filter: (legendItem, data) => {
29314
29326
  return "datasetIndex" in legendItem
29315
29327
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -29442,7 +29454,7 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29442
29454
  color: fontColor,
29443
29455
  usePointStyle: true,
29444
29456
  generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
29445
- if (dataset["xAxisID"] === TREND_LINE_XAXIS_ID) {
29457
+ if (isTrendLineAxis(dataset["xAxisID"])) {
29446
29458
  return {
29447
29459
  text: dataset.label ?? "",
29448
29460
  fontColor,
@@ -29500,6 +29512,11 @@ function getBarChartScales(definition, args) {
29500
29512
  offset: false,
29501
29513
  display: false,
29502
29514
  };
29515
+ scales[MOVING_AVERAGE_TREND_LINE_XAXIS_ID] = {
29516
+ ...scales.x,
29517
+ offset: false,
29518
+ display: false,
29519
+ };
29503
29520
  }
29504
29521
  return scales;
29505
29522
  }
@@ -29533,6 +29550,10 @@ function getLineChartScales(definition, args) {
29533
29550
  ...scales.x,
29534
29551
  display: false,
29535
29552
  };
29553
+ scales[MOVING_AVERAGE_TREND_LINE_XAXIS_ID] = {
29554
+ ...scales.x,
29555
+ display: false,
29556
+ };
29536
29557
  if (axisType === "category" || axisType === "time") {
29537
29558
  /* We add a second x axis here to draw the trend lines, with the labels length being
29538
29559
  * set so that the second axis points match the classical x axis
@@ -29541,6 +29562,8 @@ function getLineChartScales(definition, args) {
29541
29562
  scales[TREND_LINE_XAXIS_ID]["type"] = "category";
29542
29563
  scales[TREND_LINE_XAXIS_ID]["labels"] = range(0, maxLength).map((x) => x.toString());
29543
29564
  scales[TREND_LINE_XAXIS_ID]["offset"] = false;
29565
+ scales[MOVING_AVERAGE_TREND_LINE_XAXIS_ID]["type"] = "category";
29566
+ scales[MOVING_AVERAGE_TREND_LINE_XAXIS_ID]["offset"] = false;
29544
29567
  }
29545
29568
  }
29546
29569
  return scales;
@@ -29786,9 +29809,7 @@ function getBarChartTooltip(definition, args) {
29786
29809
  return {
29787
29810
  callbacks: {
29788
29811
  title: function (tooltipItems) {
29789
- return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
29790
- ? undefined
29791
- : "";
29812
+ return tooltipItems.some((item) => !isTrendLineAxis(item.dataset.xAxisID)) ? undefined : "";
29792
29813
  },
29793
29814
  label: function (tooltipItem) {
29794
29815
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
@@ -29811,7 +29832,7 @@ function getLineChartTooltip(definition, args) {
29811
29832
  if (axisType === "linear") {
29812
29833
  tooltip.callbacks.label = (tooltipItem) => {
29813
29834
  const dataSetPoint = tooltipItem.parsed.y;
29814
- let label = tooltipItem.dataset.xAxisID === TREND_LINE_XAXIS_ID
29835
+ let label = isTrendLineAxis(tooltipItem.dataset.xAxisID)
29815
29836
  ? ""
29816
29837
  : tooltipItem.parsed.x;
29817
29838
  if (typeof label === "string" && isNumber(label, locale)) {
@@ -29836,8 +29857,7 @@ function getLineChartTooltip(definition, args) {
29836
29857
  };
29837
29858
  }
29838
29859
  tooltip.callbacks.title = function (tooltipItems) {
29839
- const displayTooltipTitle = axisType !== "linear" &&
29840
- tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
29860
+ const displayTooltipTitle = axisType !== "linear" && tooltipItems.some((item) => !isTrendLineAxis(item.dataset.xAxisID));
29841
29861
  return displayTooltipTitle ? undefined : "";
29842
29862
  };
29843
29863
  return tooltip;
@@ -33994,6 +34014,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
33994
34014
  CHART_COMMON_OPTIONS: CHART_COMMON_OPTIONS,
33995
34015
  GaugeChart: GaugeChart,
33996
34016
  LineChart: LineChart,
34017
+ MOVING_AVERAGE_TREND_LINE_XAXIS_ID: MOVING_AVERAGE_TREND_LINE_XAXIS_ID,
33997
34018
  PieChart: PieChart,
33998
34019
  ScorecardChart: ScorecardChart$1,
33999
34020
  TREND_LINE_XAXIS_ID: TREND_LINE_XAXIS_ID,
@@ -34023,6 +34044,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
34023
34044
  getDefinedAxis: getDefinedAxis,
34024
34045
  getPieColors: getPieColors,
34025
34046
  getSmartChartDefinition: getSmartChartDefinition,
34047
+ isTrendLineAxis: isTrendLineAxis,
34026
34048
  shouldRemoveFirstLabel: shouldRemoveFirstLabel,
34027
34049
  toExcelDataset: toExcelDataset,
34028
34050
  toExcelLabelRange: toExcelLabelRange,
@@ -36070,9 +36092,7 @@ class FormulaFingerprintStore extends SpreadsheetStore {
36070
36092
  }
36071
36093
  }
36072
36094
  }
36073
- // removes the index placeholders from the normalized formula
36074
- // =|N0|+|N1|+|N0| -> =|N|+|N|+|N|
36075
- const normalizedFormula = cell.compiledFormula.normalizedFormula.replace(/(|\w)(\d)(|)/g, "$1$3");
36095
+ const normalizedFormula = cell.compiledFormula.normalizedFormula;
36076
36096
  return hash(fingerprintVector) + normalizedFormula;
36077
36097
  }
36078
36098
  getLiteralFingerprint(position) {
@@ -39063,9 +39083,11 @@ class SeriesDesignEditor extends owl.Component {
39063
39083
  if (!runtime || !("chartJsConfig" in runtime)) {
39064
39084
  return [];
39065
39085
  }
39066
- return runtime.chartJsConfig.data.datasets.map((d) => d.label);
39086
+ return runtime.chartJsConfig.data.datasets
39087
+ .filter((d) => !isTrendLineAxis(d["xAxisID"] ?? ""))
39088
+ .map((d) => d.label);
39067
39089
  }
39068
- updateSerieEditor(ev) {
39090
+ updateEditedSeries(ev) {
39069
39091
  this.state.index = ev.target.selectedIndex;
39070
39092
  }
39071
39093
  updateDataSeriesColor(color) {
@@ -39078,7 +39100,7 @@ class SeriesDesignEditor extends owl.Component {
39078
39100
  };
39079
39101
  this.props.updateChart(this.props.figureId, { dataSets });
39080
39102
  }
39081
- getDataSerieColor() {
39103
+ getDataSeriesColor() {
39082
39104
  const dataSets = this.props.definition.dataSets;
39083
39105
  if (!dataSets?.[this.state.index])
39084
39106
  return "";
@@ -39098,7 +39120,7 @@ class SeriesDesignEditor extends owl.Component {
39098
39120
  };
39099
39121
  this.props.updateChart(this.props.figureId, { dataSets });
39100
39122
  }
39101
- getDataSerieLabel() {
39123
+ getDataSeriesLabel() {
39102
39124
  const dataSets = this.props.definition.dataSets;
39103
39125
  return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
39104
39126
  }
@@ -39211,7 +39233,7 @@ class SeriesWithAxisDesignEditor extends owl.Component {
39211
39233
  }
39212
39234
  this.updateTrendLineValue(index, { window });
39213
39235
  }
39214
- getDataSerieColor(index) {
39236
+ getDataSeriesColor(index) {
39215
39237
  const dataSets = this.props.definition.dataSets;
39216
39238
  if (!dataSets?.[index])
39217
39239
  return "";
@@ -39222,7 +39244,7 @@ class SeriesWithAxisDesignEditor extends owl.Component {
39222
39244
  }
39223
39245
  getTrendLineColor(index) {
39224
39246
  return (this.getTrendLineConfiguration(index)?.color ??
39225
- setColorAlpha(this.getDataSerieColor(index), 0.5));
39247
+ setColorAlpha(this.getDataSeriesColor(index), 0.5));
39226
39248
  }
39227
39249
  updateTrendLineColor(index, color) {
39228
39250
  this.updateTrendLineValue(index, { color });
@@ -46838,6 +46860,7 @@ class RemoveDuplicatesPanel extends owl.Component {
46838
46860
  columns: {},
46839
46861
  });
46840
46862
  setup() {
46863
+ this.updateColumns();
46841
46864
  owl.onWillUpdateProps(() => this.updateColumns());
46842
46865
  }
46843
46866
  toggleHasHeader() {
@@ -48625,8 +48648,8 @@ class CellComposerStore extends AbstractComposerStore {
48625
48648
  const sheetIdExists = !!this.getters.tryGetSheet(this.sheetId);
48626
48649
  if (!sheetIdExists && this.editionMode !== "inactive") {
48627
48650
  this.sheetId = this.getters.getActiveSheetId();
48628
- this.cancelEditionAndActivateSheet();
48629
48651
  this.resetContent();
48652
+ this.cancelEditionAndActivateSheet();
48630
48653
  this.notificationStore.raiseError(CELL_DELETED_MESSAGE);
48631
48654
  }
48632
48655
  break;
@@ -64913,12 +64936,7 @@ class SheetUIPlugin extends UIPlugin {
64913
64936
  }
64914
64937
  break;
64915
64938
  case "AUTORESIZE_ROWS":
64916
- this.dispatch("RESIZE_COLUMNS_ROWS", {
64917
- elements: cmd.rows,
64918
- dimension: "ROW",
64919
- size: null,
64920
- sheetId: cmd.sheetId,
64921
- });
64939
+ this.autoResizeRows(cmd.sheetId, cmd.rows);
64922
64940
  break;
64923
64941
  }
64924
64942
  }
@@ -65082,6 +65100,48 @@ class SheetUIPlugin extends UIPlugin {
65082
65100
  }
65083
65101
  return "Success" /* CommandResult.Success */;
65084
65102
  }
65103
+ autoResizeRows(sheetId, rows) {
65104
+ const rowSizes = [];
65105
+ for (const row of rows) {
65106
+ let evaluatedRowSize = 0;
65107
+ for (const cellId of this.getters.getRowCells(sheetId, row)) {
65108
+ const cell = this.getters.getCellById(cellId);
65109
+ if (!cell) {
65110
+ continue;
65111
+ }
65112
+ const position = this.getters.getCellPosition(cell.id);
65113
+ const colSize = this.getters.getColSize(sheetId, position.col);
65114
+ if (cell.isFormula) {
65115
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
65116
+ const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
65117
+ if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
65118
+ evaluatedRowSize = evaluatedSize;
65119
+ }
65120
+ }
65121
+ else {
65122
+ const content = cell.content;
65123
+ const dynamicRowSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
65124
+ // Only keep the size of evaluated cells if it's bigger than the dynamic row size
65125
+ if (dynamicRowSize >= evaluatedRowSize && dynamicRowSize > DEFAULT_CELL_HEIGHT) {
65126
+ evaluatedRowSize = 0;
65127
+ }
65128
+ }
65129
+ }
65130
+ rowSizes.push(evaluatedRowSize || null);
65131
+ }
65132
+ const groupedSizes = new Map(rowSizes.map((size) => [size, []]));
65133
+ for (let i = 0; i < rowSizes.length; i++) {
65134
+ groupedSizes.get(rowSizes[i])?.push(rows[i]);
65135
+ }
65136
+ for (const [size, rows] of groupedSizes) {
65137
+ this.dispatch("RESIZE_COLUMNS_ROWS", {
65138
+ elements: rows,
65139
+ dimension: "ROW",
65140
+ size,
65141
+ sheetId,
65142
+ });
65143
+ }
65144
+ }
65085
65145
  }
65086
65146
 
65087
65147
  class TableComputedStylePlugin extends UIPlugin {
@@ -70267,7 +70327,9 @@ css /* scss */ `
70267
70327
  border: 1px solid;
70268
70328
  font-family: ${DEFAULT_FONT};
70269
70329
 
70270
- .o-composer:empty:not(:focus):not(.active)::before {
70330
+ /* In readonly we always show the fx icon if the composer is empty, not matter the focus */
70331
+ .o-composer:empty:not(:focus):not(.active)::before,
70332
+ &.o-topbar-composer-readonly .o-composer:empty::before {
70271
70333
  content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
70272
70334
  position: relative;
70273
70335
  top: 20%;
@@ -73608,10 +73670,14 @@ function addIconSetRule(cf, rule) {
73608
73670
  continue;
73609
73671
  }
73610
73672
  const cfValueObjectNodes = cfValueObject.map((attrs) => escapeXml /*xml*/ `<cfvo ${formatAttributes(attrs)} />`);
73673
+ const iconSetAttrs = [["iconSet", getIconSet(rule.icons)]];
73674
+ if (isIconSetReversed(rule.icons)) {
73675
+ iconSetAttrs.push(["reverse", "1"]);
73676
+ }
73611
73677
  conditionalFormats.push(escapeXml /*xml*/ `
73612
73678
  <conditionalFormatting sqref="${range}">
73613
73679
  <cfRule ${formatAttributes(ruleAttributes)}>
73614
- <iconSet iconSet="${getIconSet(rule.icons)}">
73680
+ <iconSet ${formatAttributes(iconSetAttrs)}>
73615
73681
  ${joinXmlNodes(cfValueObjectNodes)}
73616
73682
  </iconSet>
73617
73683
  </cfRule>
@@ -73629,9 +73695,21 @@ function commonCfAttributes(cf) {
73629
73695
  ["stopIfTrue", cf.stopIfTrue ? 1 : 0],
73630
73696
  ];
73631
73697
  }
73698
+ function isIconSetReversed(iconSet) {
73699
+ const defaultIconSet = ICON_SETS[detectIconsType(iconSet)];
73700
+ return iconSet.upper === defaultIconSet.bad && iconSet.lower === defaultIconSet.good;
73701
+ }
73632
73702
  function getIconSet(iconSet) {
73633
- return XLSX_ICONSET_MAP[Object.keys(XLSX_ICONSET_MAP).find((key) => iconSet.upper.toLowerCase().startsWith(key)) ||
73634
- "dots"];
73703
+ return XLSX_ICONSET_MAP[detectIconsType(iconSet)];
73704
+ }
73705
+ /**
73706
+ * Partial detection based on "upper" point only.
73707
+ * We support any arbitrary icon in the set, while excel doesn't allow
73708
+ * mixing icons from different types.
73709
+ */
73710
+ function detectIconsType(iconSet) {
73711
+ const type = Object.keys(ICON_SETS).find((type) => Object.values(ICON_SETS[type]).includes(iconSet.upper)) || "dots";
73712
+ return type;
73635
73713
  }
73636
73714
  function thresholdAttributes(threshold, position) {
73637
73715
  const type = getExcelThresholdType(threshold.type, position);
@@ -75548,6 +75626,6 @@ exports.tokenColors = tokenColors;
75548
75626
  exports.tokenize = tokenize;
75549
75627
 
75550
75628
 
75551
- __info__.version = "18.1.11";
75552
- __info__.date = "2025-03-12T15:31:44.276Z";
75553
- __info__.hash = "7de2363";
75629
+ __info__.version = "18.1.13";
75630
+ __info__.date = "2025-03-26T12:48:31.680Z";
75631
+ __info__.hash = "45ec54c";