@odoo/o-spreadsheet 18.0.19 → 18.0.21

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.0.19
6
- * @date 2025-03-12T15:33:28.300Z
7
- * @hash d8dea1b
5
+ * @version 18.0.21
6
+ * @date 2025-03-26T12:49:46.872Z
7
+ * @hash c687e1c
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -1112,7 +1112,10 @@
1112
1112
  }
1113
1113
  else if (stringVals.length === 4) {
1114
1114
  const alpha = parseFloat(stringVals.pop() || "1");
1115
- alphaHex = Math.round((alpha || 1) * 255);
1115
+ if (isNaN(alpha)) {
1116
+ throw new Error("invalid alpha value");
1117
+ }
1118
+ alphaHex = Math.round(alpha * 255);
1116
1119
  }
1117
1120
  const vals = stringVals.map((val) => parseInt(val, 10));
1118
1121
  if (alphaHex !== 255) {
@@ -6108,11 +6111,13 @@
6108
6111
  if (!cell || (!cell.isFormula && !cell.content)) {
6109
6112
  return DEFAULT_CELL_HEIGHT;
6110
6113
  }
6111
- const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6112
- const numberOfLines = cell.isFormula
6113
- ? 1
6114
- : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
6115
- const fontSize = computeTextFontSizeInPixels(cell.style);
6114
+ const content = cell.isFormula ? "" : cell.content;
6115
+ return getCellContentHeight(ctx, content, cell.style, colSize);
6116
+ }
6117
+ function getCellContentHeight(ctx, content, style, colSize) {
6118
+ const maxWidth = style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6119
+ const numberOfLines = splitTextToWidth(ctx, content, style, maxWidth).length;
6120
+ const fontSize = computeTextFontSizeInPixels(style);
6116
6121
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
6117
6122
  }
6118
6123
  function getDefaultContextFont(fontSize, bold = false, italic = false) {
@@ -6834,7 +6839,7 @@
6834
6839
  */
6835
6840
  function canonicalizeNumberContent(content, locale) {
6836
6841
  return content.startsWith("=")
6837
- ? canonicalizeFormula$1(content, locale)
6842
+ ? canonicalizeFormula(content, locale)
6838
6843
  : canonicalizeNumberLiteral(content, locale);
6839
6844
  }
6840
6845
  /**
@@ -6849,7 +6854,7 @@
6849
6854
  */
6850
6855
  function canonicalizeContent(content, locale) {
6851
6856
  return content.startsWith("=")
6852
- ? canonicalizeFormula$1(content, locale)
6857
+ ? canonicalizeFormula(content, locale)
6853
6858
  : canonicalizeLiteral(content, locale);
6854
6859
  }
6855
6860
  /**
@@ -6865,15 +6870,21 @@
6865
6870
  ? localizeFormula(content, locale)
6866
6871
  : localizeLiteral(content, locale);
6867
6872
  }
6873
+ /** Change a number string to its canonical form (en_US locale) */
6874
+ function canonicalizeNumberValue(content, locale) {
6875
+ return content.startsWith("=")
6876
+ ? canonicalizeFormula(content, locale)
6877
+ : canonicalizeNumberLiteral(content, locale);
6878
+ }
6868
6879
  /** Change a formula to its canonical form (en_US locale) */
6869
- function canonicalizeFormula$1(formula, locale) {
6870
- return _localizeFormula$1(formula, locale, DEFAULT_LOCALE);
6880
+ function canonicalizeFormula(formula, locale) {
6881
+ return _localizeFormula(formula, locale, DEFAULT_LOCALE);
6871
6882
  }
6872
6883
  /** Change a formula from the canonical form to the given locale */
6873
6884
  function localizeFormula(formula, locale) {
6874
- return _localizeFormula$1(formula, DEFAULT_LOCALE, locale);
6885
+ return _localizeFormula(formula, DEFAULT_LOCALE, locale);
6875
6886
  }
6876
- function _localizeFormula$1(formula, fromLocale, toLocale) {
6887
+ function _localizeFormula(formula, fromLocale, toLocale) {
6877
6888
  if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
6878
6889
  fromLocale.decimalSeparator === toLocale.decimalSeparator) {
6879
6890
  return formula;
@@ -7029,37 +7040,6 @@
7029
7040
  return locale.dateFormat + " " + locale.timeFormat;
7030
7041
  }
7031
7042
 
7032
- /** Change a number string to its canonical form (en_US locale) */
7033
- function canonicalizeNumberValue(content, locale) {
7034
- return content.startsWith("=")
7035
- ? canonicalizeFormula(content, locale)
7036
- : canonicalizeNumberLiteral(content, locale);
7037
- }
7038
- /** Change a formula to its canonical form (en_US locale) */
7039
- function canonicalizeFormula(formula, locale) {
7040
- return _localizeFormula(formula, locale, DEFAULT_LOCALE);
7041
- }
7042
- function _localizeFormula(formula, fromLocale, toLocale) {
7043
- if (fromLocale.formulaArgSeparator === toLocale.formulaArgSeparator &&
7044
- fromLocale.decimalSeparator === toLocale.decimalSeparator) {
7045
- return formula;
7046
- }
7047
- const tokens = tokenize(formula, fromLocale);
7048
- let localizedFormula = "";
7049
- for (const token of tokens) {
7050
- if (token.type === "NUMBER") {
7051
- localizedFormula += token.value.replace(fromLocale.decimalSeparator, toLocale.decimalSeparator);
7052
- }
7053
- else if (token.type === "ARG_SEPARATOR") {
7054
- localizedFormula += toLocale.formulaArgSeparator;
7055
- }
7056
- else {
7057
- localizedFormula += token.value;
7058
- }
7059
- }
7060
- return localizedFormula;
7061
- }
7062
-
7063
7043
  function boolAnd(args) {
7064
7044
  let foundBoolean = false;
7065
7045
  let acc = true;
@@ -8275,13 +8255,6 @@
8275
8255
  this.clearClippedZones(content);
8276
8256
  const selection = target[0];
8277
8257
  this.pasteZone(sheetId, selection.left, selection.top, content.cells, options);
8278
- this.dispatch("MOVE_RANGES", {
8279
- target: content.zones,
8280
- sheetId: content.sheetId,
8281
- targetSheetId: sheetId,
8282
- col: selection.left,
8283
- row: selection.top,
8284
- });
8285
8258
  }
8286
8259
  /**
8287
8260
  * Clear the clipped zones: remove the cells and clear the formatting
@@ -8790,14 +8763,15 @@
8790
8763
  }
8791
8764
  merges.push(mergesInRow);
8792
8765
  }
8793
- return { merges };
8766
+ return { merges, sheetId };
8794
8767
  }
8795
8768
  /**
8796
8769
  * Paste the clipboard content in the given target
8797
8770
  */
8798
8771
  paste(target, content, options) {
8799
8772
  if (options.isCutOperation) {
8800
- return;
8773
+ const copiedMerges = content.merges.flat().filter(isDefined);
8774
+ this.dispatch("REMOVE_MERGE", { sheetId: content.sheetId, target: copiedMerges });
8801
8775
  }
8802
8776
  this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
8803
8777
  }
@@ -8832,6 +8806,27 @@
8832
8806
  }
8833
8807
  }
8834
8808
 
8809
+ class ReferenceClipboardHandler extends AbstractCellClipboardHandler {
8810
+ copy(data) {
8811
+ return {
8812
+ zones: data.clippedZones,
8813
+ sheetId: data.sheetId,
8814
+ };
8815
+ }
8816
+ paste(target, content, options) {
8817
+ if (options.isCutOperation) {
8818
+ const selection = target.zones[0];
8819
+ this.dispatch("MOVE_RANGES", {
8820
+ target: content.zones,
8821
+ sheetId: content.sheetId,
8822
+ targetSheetId: target.sheetId,
8823
+ col: selection.left,
8824
+ row: selection.top,
8825
+ });
8826
+ }
8827
+ }
8828
+ }
8829
+
8835
8830
  class SheetClipboardHandler extends AbstractCellClipboardHandler {
8836
8831
  isPasteAllowed(sheetId, target, content, options) {
8837
8832
  if (!("cells" in content)) {
@@ -8999,7 +8994,8 @@
8999
8994
  .add("merge", MergeClipboardHandler)
9000
8995
  .add("border", BorderClipboardHandler)
9001
8996
  .add("table", TableClipboardHandler)
9002
- .add("conditionalFormat", ConditionalFormatClipboardHandler);
8997
+ .add("conditionalFormat", ConditionalFormatClipboardHandler)
8998
+ .add("references", ReferenceClipboardHandler);
9003
8999
 
9004
9000
  function transformZone(zone, executed) {
9005
9001
  if (executed.type === "REMOVE_COLUMNS_ROWS") {
@@ -10054,9 +10050,9 @@ stores.inject(MyMetaStore, storeInstance);
10054
10050
  /** In XLSX color format (no #) */
10055
10051
  const AUTO_COLOR = "000000";
10056
10052
  const XLSX_ICONSET_MAP = {
10057
- arrow: "3Arrows",
10053
+ arrows: "3Arrows",
10058
10054
  smiley: "3Symbols",
10059
- dot: "3TrafficLights1",
10055
+ dots: "3TrafficLights1",
10060
10056
  };
10061
10057
  const NAMESPACE = {
10062
10058
  styleSheet: "http://schemas.openxmlformats.org/spreadsheetml/2006/main",
@@ -10728,6 +10724,7 @@ stores.inject(MyMetaStore, storeInstance);
10728
10724
  };
10729
10725
  /** Map between legend position in XLSX file and human readable position */
10730
10726
  const DRAWING_LEGEND_POSITION_CONVERSION_MAP = {
10727
+ none: "none",
10731
10728
  b: "bottom",
10732
10729
  t: "top",
10733
10730
  l: "left",
@@ -13484,7 +13481,7 @@ stores.inject(MyMetaStore, storeInstance);
13484
13481
  default: "ffffff",
13485
13482
  }).asString(),
13486
13483
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
13487
- default: "b",
13484
+ default: "none",
13488
13485
  }).asString()],
13489
13486
  stacked: barChartGrouping === "stacked",
13490
13487
  fontColor: "000000",
@@ -13518,7 +13515,7 @@ stores.inject(MyMetaStore, storeInstance);
13518
13515
  default: "ffffff",
13519
13516
  }).asString(),
13520
13517
  legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
13521
- default: "b",
13518
+ default: "none",
13522
13519
  }).asString()],
13523
13520
  stacked: barChartGrouping === "stacked",
13524
13521
  fontColor: "000000",
@@ -16710,7 +16707,8 @@ stores.inject(MyMetaStore, storeInstance);
16710
16707
  }
16711
16708
  }
16712
16709
  else if (dataSets.length === 1) {
16713
- for (let i = 0; i < getData(getters, dataSets[0]).length; i++) {
16710
+ const dataLength = getData(getters, dataSets[0]).length;
16711
+ for (let i = 0; i < dataLength; i++) {
16714
16712
  labels.formattedValues.push("");
16715
16713
  labels.values.push("");
16716
16714
  }
@@ -25956,11 +25954,26 @@ stores.inject(MyMetaStore, storeInstance);
25956
25954
  const _searchFor = toString(searchFor).toLowerCase();
25957
25955
  const _textToSearch = toString(textToSearch).toLowerCase();
25958
25956
  const _startingAt = toNumber(startingAt, this.locale);
25959
- assert(() => _textToSearch !== "", _t("The text_to_search must be non-empty."));
25960
- assert(() => _startingAt >= 1, _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt.toString()));
25957
+ if (_textToSearch === "") {
25958
+ return {
25959
+ value: CellErrorType.GenericError,
25960
+ message: _t("The text_to_search must be non-empty."),
25961
+ };
25962
+ }
25963
+ if (_startingAt < 1) {
25964
+ return {
25965
+ value: CellErrorType.GenericError,
25966
+ message: _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt),
25967
+ };
25968
+ }
25961
25969
  const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);
25962
- assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch));
25963
- return result + 1;
25970
+ if (result === -1) {
25971
+ return {
25972
+ value: CellErrorType.GenericError,
25973
+ message: _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch),
25974
+ };
25975
+ }
25976
+ return { value: result + 1 };
25964
25977
  },
25965
25978
  isExported: true,
25966
25979
  };
@@ -27810,11 +27823,14 @@ stores.inject(MyMetaStore, storeInstance);
27810
27823
  }
27811
27824
  }
27812
27825
  function compileTokensOrThrow(tokens) {
27813
- const { dependencies, constantValues, symbols } = formulaArguments(tokens);
27814
- const cacheKey = compilationCacheKey(tokens, dependencies, constantValues);
27826
+ const { dependencies, literalValues, symbols } = formulaArguments(tokens);
27827
+ const cacheKey = compilationCacheKey(tokens);
27815
27828
  if (!functionCache[cacheKey]) {
27816
27829
  const ast = parseTokens([...tokens]);
27817
27830
  const scope = new Scope();
27831
+ let stringCount = 0;
27832
+ let numberCount = 0;
27833
+ let dependencyCount = 0;
27818
27834
  if (ast.type === "BIN_OPERATION" && ast.value === ":") {
27819
27835
  throw new BadExpressionError(_t("Invalid formula"));
27820
27836
  }
@@ -27887,16 +27903,15 @@ stores.inject(MyMetaStore, storeInstance);
27887
27903
  case "BOOLEAN":
27888
27904
  return code.return(`{ value: ${ast.value} }`);
27889
27905
  case "NUMBER":
27890
- return code.return(`{ value: this.constantValues.numbers[${constantValues.numbers.indexOf(ast.value)}] }`);
27906
+ return code.return(`this.literalValues.numbers[${numberCount++}]`);
27891
27907
  case "STRING":
27892
- return code.return(`{ value: this.constantValues.strings[${constantValues.strings.indexOf(ast.value)}] }`);
27908
+ return code.return(`this.literalValues.strings[${stringCount++}]`);
27893
27909
  case "REFERENCE":
27894
- const referenceIndex = dependencies.indexOf(ast.value);
27895
27910
  if ((!isMeta && ast.value.includes(":")) || hasRange) {
27896
- return code.return(`range(deps[${referenceIndex}])`);
27911
+ return code.return(`range(deps[${dependencyCount++}])`);
27897
27912
  }
27898
27913
  else {
27899
- return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"})`);
27914
+ return code.return(`ref(deps[${dependencyCount++}], ${isMeta ? "true" : "false"})`);
27900
27915
  }
27901
27916
  case "FUNCALL":
27902
27917
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
@@ -27928,7 +27943,7 @@ stores.inject(MyMetaStore, storeInstance);
27928
27943
  const compiledFormula = {
27929
27944
  execute: functionCache[cacheKey],
27930
27945
  dependencies,
27931
- constantValues,
27946
+ literalValues,
27932
27947
  symbols,
27933
27948
  tokens,
27934
27949
  isBadExpression: false,
@@ -27940,33 +27955,31 @@ stores.inject(MyMetaStore, storeInstance);
27940
27955
  * References, numbers and strings are replaced with placeholders because
27941
27956
  * the compiled formula does not depend on their actual value.
27942
27957
  * Both `=A1+1+"2"` and `=A2+2+"3"` are compiled to the exact same function.
27943
- *
27944
27958
  * Spaces are also ignored to compute the cache key.
27945
27959
  *
27946
- * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|0|+|1|+SUM(|N0|,|N0|,|S0|)`
27960
+ * A formula `=A1+A2+SUM(2, 2, "2")` have the cache key `=|C|+|C|+SUM(|N|,|N|,|S|)`
27947
27961
  */
27948
- function compilationCacheKey(tokens, dependencies, constantValues, symbols) {
27962
+ function compilationCacheKey(tokens) {
27949
27963
  let cacheKey = "";
27950
27964
  for (const token of tokens) {
27951
27965
  switch (token.type) {
27952
27966
  case "STRING":
27953
- const value = removeStringQuotes(token.value);
27954
- cacheKey += `|S${constantValues.strings.indexOf(value)}|`;
27967
+ cacheKey += "|S|";
27955
27968
  break;
27956
27969
  case "NUMBER":
27957
- cacheKey += `|N${constantValues.numbers.indexOf(parseNumber(token.value, DEFAULT_LOCALE))}|`;
27970
+ cacheKey += "|N|";
27958
27971
  break;
27959
27972
  case "REFERENCE":
27960
27973
  case "INVALID_REFERENCE":
27961
27974
  if (token.value.includes(":")) {
27962
- cacheKey += `R|${dependencies.indexOf(token.value)}|`;
27975
+ cacheKey += "|R|";
27963
27976
  }
27964
27977
  else {
27965
- cacheKey += `C|${dependencies.indexOf(token.value)}|`;
27978
+ cacheKey += "|C|";
27966
27979
  }
27967
27980
  break;
27968
27981
  case "SPACE":
27969
- cacheKey += "";
27982
+ // ignore spaces
27970
27983
  break;
27971
27984
  default:
27972
27985
  cacheKey += token.value;
@@ -27979,7 +27992,7 @@ stores.inject(MyMetaStore, storeInstance);
27979
27992
  * Return formula arguments which are references, strings and numbers.
27980
27993
  */
27981
27994
  function formulaArguments(tokens) {
27982
- const constantValues = {
27995
+ const literalValues = {
27983
27996
  numbers: [],
27984
27997
  strings: [],
27985
27998
  };
@@ -27993,15 +28006,11 @@ stores.inject(MyMetaStore, storeInstance);
27993
28006
  break;
27994
28007
  case "STRING":
27995
28008
  const value = removeStringQuotes(token.value);
27996
- if (!constantValues.strings.includes(value)) {
27997
- constantValues.strings.push(value);
27998
- }
28009
+ literalValues.strings.push({ value });
27999
28010
  break;
28000
28011
  case "NUMBER": {
28001
28012
  const value = parseNumber(token.value, DEFAULT_LOCALE);
28002
- if (!constantValues.numbers.includes(value)) {
28003
- constantValues.numbers.push(value);
28004
- }
28013
+ literalValues.numbers.push({ value });
28005
28014
  break;
28006
28015
  }
28007
28016
  case "SYMBOL": {
@@ -28012,7 +28021,7 @@ stores.inject(MyMetaStore, storeInstance);
28012
28021
  }
28013
28022
  return {
28014
28023
  dependencies,
28015
- constantValues,
28024
+ literalValues,
28016
28025
  symbols,
28017
28026
  };
28018
28027
  }
@@ -28436,7 +28445,7 @@ stores.inject(MyMetaStore, storeInstance);
28436
28445
  text,
28437
28446
  description: usedLabel,
28438
28447
  htmlContent: [{ value: text, color }],
28439
- fuzzySearchKey: value + usedLabel,
28448
+ fuzzySearchKey: text + usedLabel,
28440
28449
  };
28441
28450
  });
28442
28451
  },
@@ -38839,8 +38848,8 @@ stores.inject(MyMetaStore, storeInstance);
38839
38848
  this.updateRangeColor();
38840
38849
  }
38841
38850
  cancelEdition() {
38842
- this.cancelEditionAndActivateSheet();
38843
38851
  this.resetContent();
38852
+ this.cancelEditionAndActivateSheet();
38844
38853
  }
38845
38854
  setCurrentContent(content, selection) {
38846
38855
  if (selection && !this.isSelectionValid(content.length, selection.start, selection.end)) {
@@ -38856,8 +38865,8 @@ stores.inject(MyMetaStore, storeInstance);
38856
38865
  switch (cmd.type) {
38857
38866
  case "SELECT_FIGURE":
38858
38867
  if (cmd.id) {
38859
- this.cancelEditionAndActivateSheet();
38860
38868
  this.resetContent();
38869
+ this.cancelEditionAndActivateSheet();
38861
38870
  }
38862
38871
  break;
38863
38872
  case "START_CHANGE_HIGHLIGHT":
@@ -44752,6 +44761,7 @@ stores.inject(MyMetaStore, storeInstance);
44752
44761
  columns: {},
44753
44762
  });
44754
44763
  setup() {
44764
+ this.updateColumns();
44755
44765
  owl.onWillUpdateProps(() => this.updateColumns());
44756
44766
  }
44757
44767
  toggleHasHeader() {
@@ -46540,8 +46550,8 @@ stores.inject(MyMetaStore, storeInstance);
46540
46550
  const sheetIdExists = !!this.getters.tryGetSheet(this.sheetId);
46541
46551
  if (!sheetIdExists && this.editionMode !== "inactive") {
46542
46552
  this.sheetId = this.getters.getActiveSheetId();
46543
- this.cancelEditionAndActivateSheet();
46544
46553
  this.resetContent();
46554
+ this.cancelEditionAndActivateSheet();
46545
46555
  this.notificationStore.raiseError(CELL_DELETED_MESSAGE);
46546
46556
  }
46547
46557
  break;
@@ -62897,12 +62907,7 @@ stores.inject(MyMetaStore, storeInstance);
62897
62907
  }
62898
62908
  break;
62899
62909
  case "AUTORESIZE_ROWS":
62900
- this.dispatch("RESIZE_COLUMNS_ROWS", {
62901
- elements: cmd.rows,
62902
- dimension: "ROW",
62903
- size: null,
62904
- sheetId: cmd.sheetId,
62905
- });
62910
+ this.autoResizeRows(cmd.sheetId, cmd.rows);
62906
62911
  break;
62907
62912
  }
62908
62913
  }
@@ -63067,6 +63072,48 @@ stores.inject(MyMetaStore, storeInstance);
63067
63072
  }
63068
63073
  return "Success" /* CommandResult.Success */;
63069
63074
  }
63075
+ autoResizeRows(sheetId, rows) {
63076
+ const rowSizes = [];
63077
+ for (const row of rows) {
63078
+ let evaluatedRowSize = 0;
63079
+ for (const cellId of this.getters.getRowCells(sheetId, row)) {
63080
+ const cell = this.getters.getCellById(cellId);
63081
+ if (!cell) {
63082
+ continue;
63083
+ }
63084
+ const position = this.getters.getCellPosition(cell.id);
63085
+ const colSize = this.getters.getColSize(sheetId, position.col);
63086
+ if (cell.isFormula) {
63087
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
63088
+ const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
63089
+ if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
63090
+ evaluatedRowSize = evaluatedSize;
63091
+ }
63092
+ }
63093
+ else {
63094
+ const content = cell.content;
63095
+ const dynamicRowSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
63096
+ // Only keep the size of evaluated cells if it's bigger than the dynamic row size
63097
+ if (dynamicRowSize >= evaluatedRowSize && dynamicRowSize > DEFAULT_CELL_HEIGHT) {
63098
+ evaluatedRowSize = 0;
63099
+ }
63100
+ }
63101
+ }
63102
+ rowSizes.push(evaluatedRowSize || null);
63103
+ }
63104
+ const groupedSizes = new Map(rowSizes.map((size) => [size, []]));
63105
+ for (let i = 0; i < rowSizes.length; i++) {
63106
+ groupedSizes.get(rowSizes[i])?.push(rows[i]);
63107
+ }
63108
+ for (const [size, rows] of groupedSizes) {
63109
+ this.dispatch("RESIZE_COLUMNS_ROWS", {
63110
+ elements: rows,
63111
+ dimension: "ROW",
63112
+ size,
63113
+ sheetId,
63114
+ });
63115
+ }
63116
+ }
63070
63117
  }
63071
63118
 
63072
63119
  class TableComputedStylePlugin extends UIPlugin {
@@ -68242,7 +68289,9 @@ stores.inject(MyMetaStore, storeInstance);
68242
68289
  border: 1px solid;
68243
68290
  font-family: ${DEFAULT_FONT};
68244
68291
 
68245
- .o-composer:empty:not(:focus):not(.active)::before {
68292
+ /* In readonly we always show the fx icon if the composer is empty, not matter the focus */
68293
+ .o-composer:empty:not(:focus):not(.active)::before,
68294
+ &.o-topbar-composer-readonly .o-composer:empty::before {
68246
68295
  content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
68247
68296
  position: relative;
68248
68297
  top: 20%;
@@ -71603,10 +71652,14 @@ stores.inject(MyMetaStore, storeInstance);
71603
71652
  continue;
71604
71653
  }
71605
71654
  const cfValueObjectNodes = cfValueObject.map((attrs) => escapeXml /*xml*/ `<cfvo ${formatAttributes(attrs)} />`);
71655
+ const iconSetAttrs = [["iconSet", getIconSet(rule.icons)]];
71656
+ if (isIconSetReversed(rule.icons)) {
71657
+ iconSetAttrs.push(["reverse", "1"]);
71658
+ }
71606
71659
  conditionalFormats.push(escapeXml /*xml*/ `
71607
71660
  <conditionalFormatting sqref="${range}">
71608
71661
  <cfRule ${formatAttributes(ruleAttributes)}>
71609
- <iconSet iconSet="${getIconSet(rule.icons)}">
71662
+ <iconSet ${formatAttributes(iconSetAttrs)}>
71610
71663
  ${joinXmlNodes(cfValueObjectNodes)}
71611
71664
  </iconSet>
71612
71665
  </cfRule>
@@ -71624,9 +71677,21 @@ stores.inject(MyMetaStore, storeInstance);
71624
71677
  ["stopIfTrue", cf.stopIfTrue ? 1 : 0],
71625
71678
  ];
71626
71679
  }
71680
+ function isIconSetReversed(iconSet) {
71681
+ const defaultIconSet = ICON_SETS[detectIconsType(iconSet)];
71682
+ return iconSet.upper === defaultIconSet.bad && iconSet.lower === defaultIconSet.good;
71683
+ }
71627
71684
  function getIconSet(iconSet) {
71628
- return XLSX_ICONSET_MAP[Object.keys(XLSX_ICONSET_MAP).find((key) => iconSet.upper.toLowerCase().startsWith(key)) ||
71629
- "dots"];
71685
+ return XLSX_ICONSET_MAP[detectIconsType(iconSet)];
71686
+ }
71687
+ /**
71688
+ * Partial detection based on "upper" point only.
71689
+ * We support any arbitrary icon in the set, while excel doesn't allow
71690
+ * mixing icons from different types.
71691
+ */
71692
+ function detectIconsType(iconSet) {
71693
+ const type = Object.keys(ICON_SETS).find((type) => Object.values(ICON_SETS[type]).includes(iconSet.upper)) || "dots";
71694
+ return type;
71630
71695
  }
71631
71696
  function thresholdAttributes(threshold, position) {
71632
71697
  const type = getExcelThresholdType(threshold.type, position);
@@ -73550,9 +73615,9 @@ stores.inject(MyMetaStore, storeInstance);
73550
73615
  exports.tokenize = tokenize;
73551
73616
 
73552
73617
 
73553
- __info__.version = "18.0.19";
73554
- __info__.date = "2025-03-12T15:33:28.300Z";
73555
- __info__.hash = "d8dea1b";
73618
+ __info__.version = "18.0.21";
73619
+ __info__.date = "2025-03-26T12:49:46.872Z";
73620
+ __info__.hash = "c687e1c";
73556
73621
 
73557
73622
 
73558
73623
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);