@odoo/o-spreadsheet 18.4.12 → 18.4.14

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.4.12
6
- * @date 2025-09-23T12:37:43.708Z
7
- * @hash 0c91305
5
+ * @version 18.4.14
6
+ * @date 2025-10-16T06:39:40.249Z
7
+ * @hash bc55c40
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, useExternalListener, onWillUpdateProps, onWillStart, onWillPatch, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -904,9 +904,7 @@ function removeIndexesFromArray(array, indexes) {
904
904
  return newArray;
905
905
  }
906
906
  function insertItemsAtIndex(array, items, index) {
907
- const newArray = [...array];
908
- newArray.splice(index, 0, ...items);
909
- return newArray;
907
+ return array.slice(0, index).concat(items).concat(array.slice(index));
910
908
  }
911
909
  function replaceItemAtIndex(array, newItem, index) {
912
910
  const newArray = [...array];
@@ -3194,7 +3192,17 @@ function toNumberMatrix(data, argName) {
3194
3192
  return toMatrix(data).map((row) => {
3195
3193
  return row.map((cell) => {
3196
3194
  if (typeof cell.value !== "number") {
3197
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects number values for %s, but got a %s.", argName, typeof cell.value));
3195
+ let message = "";
3196
+ if (typeof cell === "object") {
3197
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
3198
+ }
3199
+ else if (typeof cell === "string") {
3200
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
3201
+ }
3202
+ else if (typeof cell === "boolean") {
3203
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
3204
+ }
3205
+ throw new EvaluationError(message);
3198
3206
  }
3199
3207
  return cell.value;
3200
3208
  });
@@ -4313,7 +4321,7 @@ function tokensToTextInternalFormat(tokens) {
4313
4321
  * Replace in place tokens "mm" and "m" that denote minutes in date format with "MM" to avoid confusion with months.
4314
4322
  *
4315
4323
  * As per OpenXML specification, in date formats if a date token "m" or "mm" is followed by a date token "s" or
4316
- * preceded by a data token "h", then it's not a month but an minute.
4324
+ * preceded by a data token "h", then it's not a month but a minute.
4317
4325
  */
4318
4326
  function convertTokensToMinutesInDateFormat(tokens) {
4319
4327
  const dateParts = tokens.filter((token) => token.type === "DATE_PART");
@@ -4356,6 +4364,9 @@ function internalFormatPartToFormat(internalFormat) {
4356
4364
  case "REPEATED_CHAR":
4357
4365
  format += "*" + token.value;
4358
4366
  break;
4367
+ case "DATE_PART":
4368
+ format += token.value === "MM" ? "mm" : token.value; // Convert "MM" back to "mm" for minutes
4369
+ break;
4359
4370
  default:
4360
4371
  format += token.value;
4361
4372
  }
@@ -9079,7 +9090,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
9079
9090
  pasteCell(origin, target, clipboardOption) {
9080
9091
  const { sheetId, col, row } = target;
9081
9092
  const targetCell = this.getters.getEvaluatedCell(target);
9082
- const originFormat = origin?.format ?? origin.evaluatedCell.format;
9093
+ const originFormat = origin?.format || origin.evaluatedCell.format;
9083
9094
  if (clipboardOption?.pasteOption === "asValue") {
9084
9095
  this.dispatch("UPDATE_CELL", {
9085
9096
  ...target,
@@ -13176,7 +13187,7 @@ const GROWTH = {
13176
13187
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13177
13188
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13178
13189
  }
13179
- return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "the first argument (known_data_y)")), toNumberMatrix(knownDataX, "the second argument (known_data_x)"), toNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b)));
13190
+ return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
13180
13191
  },
13181
13192
  };
13182
13193
  // -----------------------------------------------------------------------------
@@ -13249,7 +13260,7 @@ const LINEST = {
13249
13260
  if (dataY.length === 0 || dataY[0].length === 0) {
13250
13261
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13251
13262
  }
13252
- return fullLinearRegression(toNumberMatrix(dataX, "the first argument (data_y)"), toNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
13263
+ return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
13253
13264
  },
13254
13265
  isExported: true,
13255
13266
  };
@@ -13268,7 +13279,7 @@ const LOGEST = {
13268
13279
  if (dataY.length === 0 || dataY[0].length === 0) {
13269
13280
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13270
13281
  }
13271
- const coeffs = fullLinearRegression(toNumberMatrix(dataX, "the second argument (data_x)"), logM(toNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
13282
+ const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
13272
13283
  for (let i = 0; i < coeffs.length; i++) {
13273
13284
  coeffs[i][0] = Math.exp(coeffs[i][0]);
13274
13285
  }
@@ -13882,7 +13893,7 @@ const TREND = {
13882
13893
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13883
13894
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13884
13895
  }
13885
- return predictLinearValues(toNumberMatrix(knownDataY, "the first argument (known_data_y)"), toNumberMatrix(knownDataX, "the second argument (known_data_x)"), toNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b));
13896
+ return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
13886
13897
  },
13887
13898
  };
13888
13899
  // -----------------------------------------------------------------------------
@@ -21876,6 +21887,10 @@ const chartShowValuesPlugin = {
21876
21887
  }
21877
21888
  const ctx = chart.ctx;
21878
21889
  ctx.save();
21890
+ const { left, top, height, width } = chart.chartArea;
21891
+ ctx.beginPath();
21892
+ ctx.rect(left, top, width, height);
21893
+ ctx.clip();
21879
21894
  ctx.textAlign = "center";
21880
21895
  ctx.textBaseline = "middle";
21881
21896
  ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
@@ -22911,7 +22926,18 @@ class ChartJsComponent extends Component {
22911
22926
  this.chart.update();
22912
22927
  }
22913
22928
  hasChartDataChanged() {
22914
- return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
22929
+ return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
22930
+ }
22931
+ getChartDataInRuntime(runtime) {
22932
+ const data = runtime.chartJsConfig.data;
22933
+ return {
22934
+ labels: data.labels,
22935
+ dataset: data.datasets.map((dataset) => ({
22936
+ data: dataset.data,
22937
+ label: dataset.label,
22938
+ tree: dataset.tree,
22939
+ })),
22940
+ };
22915
22941
  }
22916
22942
  enableAnimationInChartData(chartData) {
22917
22943
  return {
@@ -24533,6 +24559,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
24533
24559
  parser: luxonFormat,
24534
24560
  displayFormats,
24535
24561
  unit: timeUnit ?? false,
24562
+ tooltipFormat: luxonFormat,
24536
24563
  };
24537
24564
  }
24538
24565
  /**
@@ -25613,6 +25640,7 @@ function getLineChartScales(definition, args) {
25613
25640
  };
25614
25641
  Object.assign(scales.x, axis);
25615
25642
  scales.x.ticks.maxTicksLimit = 15;
25643
+ delete scales?.x?.ticks?.callback;
25616
25644
  }
25617
25645
  else if (axisType === "linear") {
25618
25646
  scales.x.type = "linear";
@@ -30661,7 +30689,7 @@ class ChartDashboardMenu extends Component {
30661
30689
  }
30662
30690
  openContextMenu(ev) {
30663
30691
  this.menuState.isOpen = true;
30664
- this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
30692
+ this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
30665
30693
  this.menuState.menuItems = getChartMenuActions(this.props.figureUI.id, () => { }, this.env);
30666
30694
  }
30667
30695
  get fullScreenMenuItem() {
@@ -37463,6 +37491,74 @@ function getPath2D(svgPath) {
37463
37491
  return path2D;
37464
37492
  }
37465
37493
 
37494
+ /**
37495
+ * Get the relative path between two files
37496
+ *
37497
+ * Eg.:
37498
+ * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
37499
+ */
37500
+ function getRelativePath(from, to) {
37501
+ const fromPathParts = from.split("/");
37502
+ const toPathParts = to.split("/");
37503
+ let relPath = "";
37504
+ let startIndex = 0;
37505
+ for (let i = 0; i < fromPathParts.length - 1; i++) {
37506
+ if (fromPathParts[i] === toPathParts[i]) {
37507
+ startIndex++;
37508
+ }
37509
+ else {
37510
+ relPath += "../";
37511
+ }
37512
+ }
37513
+ relPath += toPathParts.slice(startIndex).join("/");
37514
+ return relPath;
37515
+ }
37516
+ /**
37517
+ * Convert an array of element into an object where the objects keys were the elements position in the array.
37518
+ * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
37519
+ *
37520
+ * eg. : ["a", "b"] => {0:"a", 1:"b"}
37521
+ */
37522
+ function arrayToObject(array, indexOffset = 0) {
37523
+ const obj = {};
37524
+ for (let i = 0; i < array.length; i++) {
37525
+ if (array[i]) {
37526
+ obj[i + indexOffset] = array[i];
37527
+ }
37528
+ }
37529
+ return obj;
37530
+ }
37531
+ /**
37532
+ * In xlsx we can have string with unicode characters with the format _x00fa_.
37533
+ * Replace with characters understandable by JS
37534
+ */
37535
+ function fixXlsxUnicode(str) {
37536
+ return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
37537
+ return String.fromCharCode(parseInt(code, 16));
37538
+ });
37539
+ }
37540
+ /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
37541
+ function getSheetDataHeader(sheetData, dimension, index) {
37542
+ if (dimension === "COL") {
37543
+ if (!sheetData.cols[index]) {
37544
+ sheetData.cols[index] = {};
37545
+ }
37546
+ return sheetData.cols[index];
37547
+ }
37548
+ if (!sheetData.rows[index]) {
37549
+ sheetData.rows[index] = {};
37550
+ }
37551
+ return sheetData.rows[index];
37552
+ }
37553
+ /** Prefix the string by "=" if the string looks like a formula */
37554
+ function prefixFormulaWithEqual(formula) {
37555
+ if (formula[0] === "=") {
37556
+ return formula;
37557
+ }
37558
+ const tokens = tokenize(formula);
37559
+ return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
37560
+ }
37561
+
37466
37562
  /**
37467
37563
  * Map of the different types of conversions warnings and their name in error messages
37468
37564
  */
@@ -37985,66 +38081,6 @@ function hexaToInt(hex) {
37985
38081
  */
37986
38082
  const DEFAULT_SYSTEM_COLOR = "FF000000";
37987
38083
 
37988
- /**
37989
- * Get the relative path between two files
37990
- *
37991
- * Eg.:
37992
- * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
37993
- */
37994
- function getRelativePath(from, to) {
37995
- const fromPathParts = from.split("/");
37996
- const toPathParts = to.split("/");
37997
- let relPath = "";
37998
- let startIndex = 0;
37999
- for (let i = 0; i < fromPathParts.length - 1; i++) {
38000
- if (fromPathParts[i] === toPathParts[i]) {
38001
- startIndex++;
38002
- }
38003
- else {
38004
- relPath += "../";
38005
- }
38006
- }
38007
- relPath += toPathParts.slice(startIndex).join("/");
38008
- return relPath;
38009
- }
38010
- /**
38011
- * Convert an array of element into an object where the objects keys were the elements position in the array.
38012
- * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
38013
- *
38014
- * eg. : ["a", "b"] => {0:"a", 1:"b"}
38015
- */
38016
- function arrayToObject(array, indexOffset = 0) {
38017
- const obj = {};
38018
- for (let i = 0; i < array.length; i++) {
38019
- if (array[i]) {
38020
- obj[i + indexOffset] = array[i];
38021
- }
38022
- }
38023
- return obj;
38024
- }
38025
- /**
38026
- * In xlsx we can have string with unicode characters with the format _x00fa_.
38027
- * Replace with characters understandable by JS
38028
- */
38029
- function fixXlsxUnicode(str) {
38030
- return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
38031
- return String.fromCharCode(parseInt(code, 16));
38032
- });
38033
- }
38034
- /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
38035
- function getSheetDataHeader(sheetData, dimension, index) {
38036
- if (dimension === "COL") {
38037
- if (!sheetData.cols[index]) {
38038
- sheetData.cols[index] = {};
38039
- }
38040
- return sheetData.cols[index];
38041
- }
38042
- if (!sheetData.rows[index]) {
38043
- sheetData.rows[index] = {};
38044
- }
38045
- return sheetData.rows[index];
38046
- }
38047
-
38048
38084
  const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
38049
38085
  /**
38050
38086
  * Convert excel format to o_spreadsheet format
@@ -38254,9 +38290,9 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38254
38290
  if (!rule.operator || !rule.formula || rule.formula.length === 0)
38255
38291
  continue;
38256
38292
  operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
38257
- values.push(prefixFormula(rule.formula[0]));
38293
+ values.push(prefixFormulaWithEqual(rule.formula[0]));
38258
38294
  if (rule.formula.length === 2) {
38259
- values.push(prefixFormula(rule.formula[1]));
38295
+ values.push(prefixFormulaWithEqual(rule.formula[1]));
38260
38296
  }
38261
38297
  break;
38262
38298
  }
@@ -38414,11 +38450,6 @@ function convertIcons(xlsxIconSet, index) {
38414
38450
  ? ICON_SETS[iconSet].neutral
38415
38451
  : ICON_SETS[iconSet].good;
38416
38452
  }
38417
- /** Prefix the string by "=" if the string looks like a formula */
38418
- function prefixFormula(formula) {
38419
- const tokens = tokenize(formula);
38420
- return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
38421
- }
38422
38453
  // ---------------------------------------------------------------------------
38423
38454
  // Warnings
38424
38455
  // ---------------------------------------------------------------------------
@@ -38890,7 +38921,7 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
38890
38921
  dvRules.push(decimalRule);
38891
38922
  break;
38892
38923
  case "list":
38893
- const listRule = convertListrule(dvId++, dv);
38924
+ const listRule = convertListRule(dvId++, dv);
38894
38925
  dvRules.push(listRule);
38895
38926
  break;
38896
38927
  case "date":
@@ -38910,9 +38941,9 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
38910
38941
  return dvRules;
38911
38942
  }
38912
38943
  function convertDecimalRule(id, dv) {
38913
- const values = [dv.formula1.toString()];
38944
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
38914
38945
  if (dv.formula2) {
38915
- values.push(dv.formula2.toString());
38946
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
38916
38947
  }
38917
38948
  return {
38918
38949
  id: id.toString(),
@@ -38924,7 +38955,7 @@ function convertDecimalRule(id, dv) {
38924
38955
  },
38925
38956
  };
38926
38957
  }
38927
- function convertListrule(id, dv) {
38958
+ function convertListRule(id, dv) {
38928
38959
  const formula1 = dv.formula1.toString();
38929
38960
  const isRangeRule = rangeReference.test(formula1);
38930
38961
  return {
@@ -38940,9 +38971,9 @@ function convertListrule(id, dv) {
38940
38971
  }
38941
38972
  function convertDateRule(id, dv) {
38942
38973
  let criterion;
38943
- const values = [dv.formula1.toString()];
38974
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
38944
38975
  if (dv.formula2) {
38945
- values.push(dv.formula2.toString());
38976
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
38946
38977
  criterion = {
38947
38978
  type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
38948
38979
  values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
@@ -38969,7 +39000,7 @@ function convertCustomRule(id, dv) {
38969
39000
  isBlocking: dv.errorStyle !== "warning",
38970
39001
  criterion: {
38971
39002
  type: "customFormula",
38972
- values: [`=${dv.formula1.toString()}`],
39003
+ values: [prefixFormulaWithEqual(dv.formula1.toString())],
38973
39004
  },
38974
39005
  };
38975
39006
  }
@@ -52860,12 +52891,13 @@ class DataValidationEditor extends Component {
52860
52891
  onCloseSidePanel: { type: Function, optional: true },
52861
52892
  };
52862
52893
  state = useState({ rule: this.defaultDataValidationRule, errors: [] });
52894
+ editingSheetId;
52863
52895
  setup() {
52896
+ this.editingSheetId = this.env.model.getters.getActiveSheetId();
52864
52897
  if (this.props.rule) {
52865
- const sheetId = this.env.model.getters.getActiveSheetId();
52866
52898
  this.state.rule = {
52867
52899
  ...this.props.rule,
52868
- ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, sheetId)),
52900
+ ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
52869
52901
  };
52870
52902
  this.state.rule.criterion.type = this.props.rule.criterion.type;
52871
52903
  }
@@ -52899,7 +52931,6 @@ class DataValidationEditor extends Component {
52899
52931
  const locale = this.env.model.getters.getLocale();
52900
52932
  const criterion = rule.criterion;
52901
52933
  const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
52902
- const sheetId = this.env.model.getters.getActiveSheetId();
52903
52934
  const values = criterion.values
52904
52935
  .slice(0, criterionEvaluator.numberOfValues(criterion))
52905
52936
  .map((value) => value?.trim())
@@ -52907,8 +52938,8 @@ class DataValidationEditor extends Component {
52907
52938
  .map((value) => canonicalizeContent(value, locale));
52908
52939
  rule.criterion = { ...criterion, values };
52909
52940
  return {
52910
- sheetId,
52911
- ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
52941
+ sheetId: this.editingSheetId,
52942
+ ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
52912
52943
  rule,
52913
52944
  };
52914
52945
  }
@@ -53435,6 +53466,7 @@ css /* scss */ `
53435
53466
  .o-button {
53436
53467
  height: 19px;
53437
53468
  width: 19px;
53469
+ box-sizing: content-box;
53438
53470
  .o-icon {
53439
53471
  height: 14px;
53440
53472
  width: 14px;
@@ -54244,7 +54276,7 @@ class PivotMeasureEditor extends Component {
54244
54276
  return undefined;
54245
54277
  }
54246
54278
  get isCalculatedMeasureInvalid() {
54247
- return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
54279
+ return compile(this.props.measure.computedBy?.formula ?? "").isBadExpression;
54248
54280
  }
54249
54281
  }
54250
54282
 
@@ -61256,11 +61288,11 @@ class HeaderSizePlugin extends CorePlugin {
61256
61288
  break;
61257
61289
  }
61258
61290
  case "ADD_COLUMNS_ROWS": {
61259
- const sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
61291
+ const sizes = this.sizes[cmd.sheetId][cmd.dimension];
61260
61292
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
61261
61293
  const baseSize = sizes[cmd.base];
61262
- sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
61263
- this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
61294
+ const newSizes = insertItemsAtIndex(sizes, Array(cmd.quantity).fill(baseSize), addIndex);
61295
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, newSizes);
61264
61296
  break;
61265
61297
  }
61266
61298
  case "RESIZE_COLUMNS_ROWS":
@@ -61411,9 +61443,8 @@ class HeaderVisibilityPlugin extends CorePlugin {
61411
61443
  break;
61412
61444
  }
61413
61445
  case "ADD_COLUMNS_ROWS": {
61414
- const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];
61415
61446
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
61416
- hiddenHeaders.splice(addIndex, 0, ...Array(cmd.quantity).fill(false));
61447
+ const hiddenHeaders = insertItemsAtIndex([...this.hiddenHeaders[cmd.sheetId][cmd.dimension]], Array(cmd.quantity).fill(false), addIndex);
61417
61448
  this.history.update("hiddenHeaders", cmd.sheetId, cmd.dimension, hiddenHeaders);
61418
61449
  break;
61419
61450
  }
@@ -65448,12 +65479,12 @@ class SpreadsheetRTree {
65448
65479
  this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
65449
65480
  }
65450
65481
  rtreeItemComparer(left, right) {
65451
- return (left.data === right.data &&
65452
- left.boundingBox.sheetId === right.boundingBox.sheetId &&
65482
+ return (left.boundingBox.sheetId === right.boundingBox.sheetId &&
65453
65483
  left.boundingBox?.zone.left === right.boundingBox.zone.left &&
65454
65484
  left.boundingBox?.zone.top === right.boundingBox.zone.top &&
65455
65485
  left.boundingBox?.zone.right === right.boundingBox.zone.right &&
65456
- left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
65486
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom &&
65487
+ deepEquals(left.data, right.data));
65457
65488
  }
65458
65489
  }
65459
65490
  /**
@@ -65526,7 +65557,7 @@ class FormulaDependencyGraph {
65526
65557
  * in the correct order they should be evaluated.
65527
65558
  * This is called a topological ordering (excluding cycles)
65528
65559
  */
65529
- getCellsDependingOn(ranges) {
65560
+ getCellsDependingOn(ranges, ignore) {
65530
65561
  const visited = this.createEmptyPositionSet();
65531
65562
  const queue = Array.from(ranges).reverse();
65532
65563
  while (queue.length > 0) {
@@ -65541,7 +65572,7 @@ class FormulaDependencyGraph {
65541
65572
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
65542
65573
  const nextInQueue = {};
65543
65574
  for (const position of impactedPositions) {
65544
- if (!visited.has(position)) {
65575
+ if (!visited.has(position) && !ignore.has(position)) {
65545
65576
  if (!nextInQueue[position.sheetId]) {
65546
65577
  nextInQueue[position.sheetId] = [];
65547
65578
  }
@@ -66098,7 +66129,7 @@ class Evaluator {
66098
66129
  }
66099
66130
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
66100
66131
  // the result matrix is split in 2 zones to exclude the array formula position
66101
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
66132
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
66102
66133
  invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
66103
66134
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
66104
66135
  }
@@ -66216,7 +66247,7 @@ class Evaluator {
66216
66247
  for (const sheetId in zonesBySheetIds) {
66217
66248
  ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
66218
66249
  }
66219
- return this.formulaDependencies().getCellsDependingOn(ranges);
66250
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
66220
66251
  }
66221
66252
  }
66222
66253
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -67689,7 +67720,8 @@ class DynamicTablesPlugin extends CoreViewPlugin {
67689
67720
  const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
67690
67721
  const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
67691
67722
  if (!parentSpreadingCell) {
67692
- return false;
67723
+ const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
67724
+ return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
67693
67725
  }
67694
67726
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
67695
67727
  return true;
@@ -78590,6 +78622,7 @@ class RibbonMenu extends Component {
78590
78622
  static components = { Menu };
78591
78623
  rootItems = topbarMenuRegistry.getMenuItems();
78592
78624
  menuRef = useRef("menu");
78625
+ containerRef = useRef("container");
78593
78626
  state = useState({
78594
78627
  menuItems: this.rootItems,
78595
78628
  title: _t("Menu Bar"),
@@ -78597,6 +78630,7 @@ class RibbonMenu extends Component {
78597
78630
  });
78598
78631
  setup() {
78599
78632
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
78633
+ onMounted(this.updateShadows);
78600
78634
  }
78601
78635
  onExternalClick(ev) {
78602
78636
  if (!this.menuRef.el?.contains(ev.target)) {
@@ -78609,6 +78643,7 @@ class RibbonMenu extends Component {
78609
78643
  this.state.parentState = { ...this.state };
78610
78644
  this.state.menuItems = children;
78611
78645
  this.state.title = menu.name(this.env);
78646
+ this.containerRef.el?.scrollTo({ top: 0 });
78612
78647
  }
78613
78648
  else {
78614
78649
  this.state.menuItems = this.rootItems;
@@ -78630,6 +78665,19 @@ class RibbonMenu extends Component {
78630
78665
  height: `${this.props.height}px`,
78631
78666
  });
78632
78667
  }
78668
+ updateShadows() {
78669
+ if (!this.containerRef.el) {
78670
+ return;
78671
+ }
78672
+ this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
78673
+ const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
78674
+ if (this.containerRef.el.scrollTop < maxScroll - 1) {
78675
+ this.containerRef.el.classList.add("scroll-bottom");
78676
+ }
78677
+ if (this.containerRef.el.scrollTop > 0) {
78678
+ this.containerRef.el.classList.add("scroll-top");
78679
+ }
78680
+ }
78633
78681
  onClickBack() {
78634
78682
  if (!this.state.parentState) {
78635
78683
  this.props.onClose();
@@ -78638,6 +78686,7 @@ class RibbonMenu extends Component {
78638
78686
  this.state.menuItems = this.state.parentState.menuItems;
78639
78687
  this.state.title = this.state.parentState.title;
78640
78688
  this.state.parentState = this.state.parentState.parentState;
78689
+ this.containerRef.el?.scrollTo({ top: 0 });
78641
78690
  }
78642
78691
  get backTitle() {
78643
78692
  return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
@@ -78693,6 +78742,11 @@ class SmallBottomBar extends Component {
78693
78742
  ? this.composerFocusStore.focusMode
78694
78743
  : "inactive";
78695
78744
  }
78745
+ get showFxIcon() {
78746
+ return (this.focus === "inactive" &&
78747
+ !this.composerStore.currentContent &&
78748
+ !this.composerStore.placeholder);
78749
+ }
78696
78750
  get rect() {
78697
78751
  return this.composerRef.el
78698
78752
  ? getBoundingRectAsPOJO(this.composerRef.el)
@@ -78717,6 +78771,7 @@ class SmallBottomBar extends Component {
78717
78771
  "max-height": `130px`,
78718
78772
  }),
78719
78773
  showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
78774
+ placeholder: this.composerStore.placeholder,
78720
78775
  };
78721
78776
  }
78722
78777
  get symbols() {
@@ -78735,12 +78790,6 @@ class SmallBottomBar extends Component {
78735
78790
  }
78736
78791
 
78737
78792
  const COMPOSER_MAX_HEIGHT = 100;
78738
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
78739
- const FX_SVG = /*xml*/ `
78740
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
78741
- <path d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='#BDBDBD'/>
78742
- </svg>
78743
- `;
78744
78793
  css /* scss */ `
78745
78794
  .o-topbar-composer-container {
78746
78795
  height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
@@ -78752,14 +78801,6 @@ css /* scss */ `
78752
78801
  margin-bottom: -1px;
78753
78802
  border: 1px solid;
78754
78803
  font-family: ${DEFAULT_FONT};
78755
-
78756
- /* In readonly we always show the fx icon if the composer is empty, not matter the focus */
78757
- .o-composer:empty:not(:focus):not(.active)::before,
78758
- &.o-topbar-composer-readonly .o-composer:empty::before {
78759
- content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
78760
- position: relative;
78761
- top: 20%;
78762
- }
78763
78804
  }
78764
78805
 
78765
78806
  .user-select-text {
@@ -78792,6 +78833,11 @@ class TopBarComposer extends Component {
78792
78833
  ? this.composerFocusStore.focusMode
78793
78834
  : "inactive";
78794
78835
  }
78836
+ get showFxIcon() {
78837
+ return (this.focus === "inactive" &&
78838
+ !this.composerStore.currentContent &&
78839
+ !this.composerStore.placeholder);
78840
+ }
78795
78841
  get composerStyle() {
78796
78842
  const style = {
78797
78843
  padding: "5px 0px 5px 8px",
@@ -84809,6 +84855,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
84809
84855
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
84810
84856
 
84811
84857
 
84812
- __info__.version = "18.4.12";
84813
- __info__.date = "2025-09-23T12:37:43.708Z";
84814
- __info__.hash = "0c91305";
84858
+ __info__.version = "18.4.14";
84859
+ __info__.date = "2025-10-16T06:39:40.249Z";
84860
+ __info__.hash = "bc55c40";