@odoo/o-spreadsheet 18.5.0-alpha.2 → 18.5.0-alpha.3

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.5.0-alpha.2
6
- * @date 2025-07-11T11:13:53.317Z
7
- * @hash 6d42178
5
+ * @version 18.5.0-alpha.3
6
+ * @date 2025-07-28T13:43:05.981Z
7
+ * @hash 53dfee8
8
8
  */
9
9
 
10
10
  'use strict';
@@ -2903,6 +2903,7 @@ const availableConditionalFormatOperators = new Set([
2903
2903
  "isEmpty",
2904
2904
  "isNotEqual",
2905
2905
  "isEqual",
2906
+ "customFormula",
2906
2907
  ]);
2907
2908
 
2908
2909
  const availableDataValidationOperators = new Set([
@@ -5035,11 +5036,11 @@ function isTextFormat(format) {
5035
5036
  }
5036
5037
  }
5037
5038
 
5038
- function evaluateLiteral(literalCell, localeFormat) {
5039
+ function evaluateLiteral(literalCell, localeFormat, position) {
5039
5040
  const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5040
5041
  ? literalCell.content
5041
5042
  : literalCell.parsedValue;
5042
- const functionResult = { value, format: localeFormat.format };
5043
+ const functionResult = { value, format: localeFormat.format, origin: position };
5043
5044
  return createEvaluatedCell(functionResult, localeFormat.locale);
5044
5045
  }
5045
5046
  function parseLiteral(content, locale) {
@@ -5061,10 +5062,11 @@ function parseLiteral(content, locale) {
5061
5062
  }
5062
5063
  return content;
5063
5064
  }
5064
- function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5065
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell, origin) {
5065
5066
  const link = detectLink(functionResult.value);
5066
5067
  if (!link) {
5067
- return _createEvaluatedCell(functionResult, locale, cell);
5068
+ const evaluateCell = _createEvaluatedCell(functionResult, locale, cell);
5069
+ return addOrigin(evaluateCell, functionResult.origin ?? origin);
5068
5070
  }
5069
5071
  const value = parseLiteral(link.label, locale);
5070
5072
  const format = functionResult.format ||
@@ -5075,10 +5077,10 @@ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5075
5077
  value,
5076
5078
  format,
5077
5079
  };
5078
- return {
5080
+ return addOrigin({
5079
5081
  ..._createEvaluatedCell(linkPayload, locale, cell),
5080
5082
  link,
5081
- };
5083
+ }, functionResult.origin ?? origin);
5082
5084
  }
5083
5085
  function _createEvaluatedCell(functionResult, locale, cell) {
5084
5086
  let { value, format, message } = functionResult;
@@ -5168,6 +5170,17 @@ function errorCell(value, message) {
5168
5170
  defaultAlign: "center",
5169
5171
  };
5170
5172
  }
5173
+ function addOrigin(cell, origin) {
5174
+ if (cell.value === null) {
5175
+ // ignore empty cells to allow sharing the same object instance
5176
+ return cell;
5177
+ }
5178
+ if ("origin" in cell) {
5179
+ return cell;
5180
+ }
5181
+ cell.origin = origin;
5182
+ return cell;
5183
+ }
5171
5184
 
5172
5185
  function toCriterionDateNumber(dateValue) {
5173
5186
  const today = DateTime.now();
@@ -7848,6 +7861,7 @@ function changeCFRuleLocale(rule, changeContentLocale) {
7848
7861
  case "isGreaterOrEqualTo":
7849
7862
  case "isLessThan":
7850
7863
  case "isLessOrEqualTo":
7864
+ case "customFormula":
7851
7865
  rule.values = rule.values.map((v) => changeContentLocale(v));
7852
7866
  return rule;
7853
7867
  case "beginsWithText":
@@ -10536,6 +10550,7 @@ const EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS = 100;
10536
10550
  /** The possible values for the XLSX polynomial trendline order are defined by the ST_Order simple type (§21.2.3.29) */
10537
10551
  const MAX_XLSX_POLYNOMIAL_DEGREE = 6;
10538
10552
  const FIRST_NUMFMT_ID = 164;
10553
+ const DEFAULT_DOUGHNUT_CHART_HOLE_SIZE = 50;
10539
10554
  const FORCE_DEFAULT_ARGS_FUNCTIONS = {
10540
10555
  FLOOR: [{ type: "NUMBER", value: 1 }],
10541
10556
  CEILING: [{ type: "NUMBER", value: 1 }],
@@ -19035,13 +19050,19 @@ const COLUMN = {
19035
19050
  if (isEvaluationError(cellReference?.value)) {
19036
19051
  return cellReference;
19037
19052
  }
19038
- const column = cellReference === undefined
19039
- ? this.__originCellPosition?.col
19040
- : toZone(cellReference.value).left;
19041
- if (column === undefined) {
19042
- return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19053
+ if (cellReference === undefined) {
19054
+ if (this.__originCellPosition?.col === undefined) {
19055
+ return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19056
+ }
19057
+ return this.__originCellPosition.col + 1;
19058
+ }
19059
+ const zone = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), cellReference.value).zone;
19060
+ if (zone.left === zone.right) {
19061
+ return zone.left + 1;
19043
19062
  }
19044
- return column + 1;
19063
+ return generateMatrix(zone.right - zone.left + 1, 1, (col, row) => ({
19064
+ value: zone.left + col + 1,
19065
+ }));
19045
19066
  },
19046
19067
  isExported: true,
19047
19068
  };
@@ -19272,13 +19293,19 @@ const ROW = {
19272
19293
  if (isEvaluationError(cellReference?.value)) {
19273
19294
  return cellReference;
19274
19295
  }
19275
- const row = cellReference === undefined
19276
- ? this.__originCellPosition?.row
19277
- : toZone(cellReference.value).top;
19278
- if (row === undefined) {
19279
- return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19296
+ if (cellReference === undefined) {
19297
+ if (this.__originCellPosition?.row === undefined) {
19298
+ return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19299
+ }
19300
+ return this.__originCellPosition.row + 1;
19301
+ }
19302
+ const zone = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), cellReference.value).zone;
19303
+ if (zone.top === zone.bottom) {
19304
+ return zone.top + 1;
19280
19305
  }
19281
- return row + 1;
19306
+ return generateMatrix(1, zone.bottom - zone.top + 1, (col, row) => ({
19307
+ value: zone.top + row + 1,
19308
+ }));
19282
19309
  },
19283
19310
  isExported: true,
19284
19311
  };
@@ -22855,6 +22882,16 @@ class AbstractChart {
22855
22882
  static getDefinitionFromContextCreation(context) {
22856
22883
  throw new Error("This method should be implemented by sub class");
22857
22884
  }
22885
+ getCommonDataSetAttributesForExcel(labelRange, dataSets, shouldRemoveFirstLabel) {
22886
+ const excelDataSets = dataSets
22887
+ .map((ds) => toExcelDataset(this.getters, ds))
22888
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22889
+ const excelLabelRange = toExcelLabelRange(this.getters, labelRange, shouldRemoveFirstLabel);
22890
+ return {
22891
+ dataSets: excelDataSets,
22892
+ labelRange: excelLabelRange,
22893
+ };
22894
+ }
22858
22895
  }
22859
22896
 
22860
22897
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
@@ -26337,6 +26374,7 @@ class BarChart extends AbstractChart {
26337
26374
  labelRange: context.auxiliaryRange || undefined,
26338
26375
  axesDesign: context.axesDesign,
26339
26376
  showValues: context.showValues,
26377
+ horizontal: context.horizontal,
26340
26378
  };
26341
26379
  }
26342
26380
  getContextCreation() {
@@ -26394,10 +26432,7 @@ class BarChart extends AbstractChart {
26394
26432
  };
26395
26433
  }
26396
26434
  getDefinitionForExcel() {
26397
- const dataSets = this.dataSets
26398
- .map((ds) => toExcelDataset(this.getters, ds))
26399
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
26400
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26435
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26401
26436
  const definition = this.getDefinition();
26402
26437
  return {
26403
26438
  ...definition,
@@ -26985,10 +27020,7 @@ class ComboChart extends AbstractChart {
26985
27020
  if (this.aggregated) {
26986
27021
  return undefined;
26987
27022
  }
26988
- const dataSets = this.dataSets
26989
- .map((ds) => toExcelDataset(this.getters, ds))
26990
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
26991
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27023
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26992
27024
  const definition = this.getDefinition();
26993
27025
  return {
26994
27026
  ...definition,
@@ -27727,10 +27759,7 @@ class LineChart extends AbstractChart {
27727
27759
  return new LineChart(definition, this.sheetId, this.getters);
27728
27760
  }
27729
27761
  getDefinitionForExcel() {
27730
- const dataSets = this.dataSets
27731
- .map((ds) => toExcelDataset(this.getters, ds))
27732
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
27733
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27762
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27734
27763
  const definition = this.getDefinition();
27735
27764
  return {
27736
27765
  ...definition,
@@ -27818,7 +27847,8 @@ class PieChart extends AbstractChart {
27818
27847
  type: "pie",
27819
27848
  labelRange: context.auxiliaryRange || undefined,
27820
27849
  aggregated: context.aggregated ?? false,
27821
- isDoughnut: false,
27850
+ isDoughnut: context.isDoughnut,
27851
+ pieHolePercentage: context.pieHolePercentage,
27822
27852
  showValues: context.showValues,
27823
27853
  };
27824
27854
  }
@@ -27866,10 +27896,7 @@ class PieChart extends AbstractChart {
27866
27896
  return new PieChart(definition, sheetId, this.getters);
27867
27897
  }
27868
27898
  getDefinitionForExcel() {
27869
- const dataSets = this.dataSets
27870
- .map((ds) => toExcelDataset(this.getters, ds))
27871
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
27872
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27899
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27873
27900
  return {
27874
27901
  ...this.getDefinition(),
27875
27902
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
@@ -28014,8 +28041,22 @@ class PyramidChart extends AbstractChart {
28014
28041
  showValues: this.showValues,
28015
28042
  };
28016
28043
  }
28017
- getDefinitionForExcel() {
28018
- return undefined;
28044
+ getDefinitionForExcel(getters) {
28045
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28046
+ const definition = this.getDefinition();
28047
+ const chartData = getPyramidChartData(definition, this.dataSets, this.labelRange, getters);
28048
+ const { dataSetsValues } = chartData;
28049
+ const maxValue = Math.max(...dataSetsValues.map((dataSet) => Math.max(...dataSet.data.map(Math.abs))));
28050
+ return {
28051
+ ...definition,
28052
+ horizontal: true,
28053
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
28054
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
28055
+ dataSets,
28056
+ labelRange,
28057
+ verticalAxis: getDefinedAxis(definition),
28058
+ maxValue,
28059
+ };
28019
28060
  }
28020
28061
  updateRanges(applyChange) {
28021
28062
  const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
@@ -28158,10 +28199,7 @@ class RadarChart extends AbstractChart {
28158
28199
  if (this.aggregated) {
28159
28200
  return undefined;
28160
28201
  }
28161
- const dataSets = this.dataSets
28162
- .map((ds) => toExcelDataset(this.getters, ds))
28163
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
28164
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28202
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28165
28203
  const definition = this.getDefinition();
28166
28204
  return {
28167
28205
  ...definition,
@@ -32608,6 +32646,9 @@ class AbstractComposerStore extends SpreadsheetStore {
32608
32646
  get isAutoCompleteDisplayed() {
32609
32647
  return !!this.autoComplete.provider;
32610
32648
  }
32649
+ get canBeToggled() {
32650
+ return this.autoComplete.provider?.canBeToggled ?? true;
32651
+ }
32611
32652
  cycleReferences() {
32612
32653
  const locale = this.getters.getLocale();
32613
32654
  const updated = cycleFixedReference(this.composerSelection, this._currentContent, locale);
@@ -33137,6 +33178,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33137
33178
  proposals,
33138
33179
  selectProposal: provider.selectProposal,
33139
33180
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33181
+ canBeToggled: provider.canBeToggled,
33140
33182
  };
33141
33183
  }
33142
33184
  if (exactMatch && this._currentContent !== this.initialContent) {
@@ -33159,6 +33201,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33159
33201
  proposals,
33160
33202
  selectProposal: provider.selectProposal,
33161
33203
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33204
+ canBeToggled: provider.canBeToggled,
33162
33205
  };
33163
33206
  }
33164
33207
  }
@@ -33729,9 +33772,13 @@ class Composer extends owl.Component {
33729
33772
  }
33730
33773
  }
33731
33774
  closeAssistant() {
33775
+ if (!this.props.composerStore.canBeToggled)
33776
+ return;
33732
33777
  this.assistant.forcedClosed = true;
33733
33778
  }
33734
33779
  openAssistant() {
33780
+ if (!this.props.composerStore.canBeToggled)
33781
+ return;
33735
33782
  this.assistant.forcedClosed = false;
33736
33783
  }
33737
33784
  onWheel(event) {
@@ -33921,7 +33968,7 @@ class Composer extends owl.Component {
33921
33968
  return [...new Set(argsToFocus)];
33922
33969
  }
33923
33970
  autoComplete(value) {
33924
- if (!value || this.assistant.forcedClosed) {
33971
+ if (!value || (this.assistant.forcedClosed && this.props.composerStore.canBeToggled)) {
33925
33972
  return;
33926
33973
  }
33927
33974
  this.props.composerStore.insertAutoCompleteValue(value);
@@ -37499,7 +37546,7 @@ const CF_OPERATOR_TYPE_CONVERSION_MAP = {
37499
37546
  /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
37500
37547
  const CF_TYPE_CONVERSION_MAP = {
37501
37548
  aboveAverage: undefined,
37502
- expression: undefined,
37549
+ expression: "customFormula",
37503
37550
  cellIs: undefined, // exist but isn't an operator in o_spreadsheet
37504
37551
  colorScale: undefined, // exist but isn't an operator in o_spreadsheet
37505
37552
  dataBar: undefined,
@@ -38103,7 +38150,6 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38103
38150
  case "containsErrors":
38104
38151
  case "notContainsErrors":
38105
38152
  case "duplicateValues":
38106
- case "expression":
38107
38153
  case "top10":
38108
38154
  case "uniqueValues":
38109
38155
  case "timePeriod":
@@ -38136,6 +38182,12 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38136
38182
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
38137
38183
  values.push(rule.text);
38138
38184
  break;
38185
+ case "expression":
38186
+ if (!rule.formula?.length)
38187
+ continue;
38188
+ operator = CF_TYPE_CONVERSION_MAP[rule.type];
38189
+ values.push(`=${rule.formula[0]}`);
38190
+ break;
38139
38191
  case "containsBlanks":
38140
38192
  case "notContainsBlanks":
38141
38193
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
@@ -38365,6 +38417,8 @@ function convertOperator(operator) {
38365
38417
  return "equal";
38366
38418
  case "isNotEqual":
38367
38419
  return "notEqual";
38420
+ case "customFormula":
38421
+ return "";
38368
38422
  }
38369
38423
  }
38370
38424
  // -------------------------------------
@@ -38700,6 +38754,9 @@ function convertChartData(chartData) {
38700
38754
  aggregated: false,
38701
38755
  cumulative: chartData.cumulative || false,
38702
38756
  labelsAsText: false,
38757
+ horizontal: chartData.horizontal,
38758
+ isDoughnut: chartData.isDoughnut,
38759
+ pieHolePercentage: chartData.pieHolePercentage,
38703
38760
  };
38704
38761
  try {
38705
38762
  const ChartClass = chartRegistry.get(chartData.type);
@@ -39976,6 +40033,12 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39976
40033
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
39977
40034
  default: "clustered",
39978
40035
  }).asString();
40036
+ const chartDirection = this.extractChildAttr(rootChartElement, "c:barDir", "val", {
40037
+ default: "col",
40038
+ }).asString();
40039
+ const chartHoleSize = this.extractChildAttr(rootChartElement, "c:holeSize", "val", {
40040
+ default: "0",
40041
+ }).asNum();
39979
40042
  return {
39980
40043
  title: { text: chartTitle },
39981
40044
  type: CHART_TYPE_CONVERSION_MAP[chartType],
@@ -39989,6 +40052,9 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39989
40052
  }).asString()],
39990
40053
  stacked: barChartGrouping === "stacked",
39991
40054
  fontColor: "000000",
40055
+ horizontal: chartDirection === "bar",
40056
+ isDoughnut: chartHoleSize > 0,
40057
+ pieHolePercentage: chartHoleSize,
39992
40058
  };
39993
40059
  })[0];
39994
40060
  }
@@ -50979,6 +51045,9 @@ class PieChartDesignPanel extends owl.Component {
50979
51045
  pieHolePercentage,
50980
51046
  });
50981
51047
  }
51048
+ get defaultHoleSize() {
51049
+ return DEFAULT_DOUGHNUT_CHART_HOLE_SIZE;
51050
+ }
50982
51051
  }
50983
51052
 
50984
51053
  class RadarChartDesignPanel extends owl.Component {
@@ -55702,9 +55771,15 @@ class SpreadsheetPivot {
55702
55771
  return domain.reduce((current, acc) => this.filterDataEntriesFromDomainNode(current, acc), dataEntries);
55703
55772
  }
55704
55773
  filterDataEntriesFromDomainNode(dataEntries, domain) {
55705
- const { field, value } = domain;
55774
+ const { field, value, type } = domain;
55706
55775
  const { nameWithGranularity } = this.getDimension(field);
55707
- return dataEntries.filter((entry) => entry[nameWithGranularity]?.value === value);
55776
+ return dataEntries.filter((entry) => {
55777
+ const cellValue = entry[nameWithGranularity]?.value;
55778
+ if (type === "char") {
55779
+ return String(cellValue) === String(value);
55780
+ }
55781
+ return cellValue === value;
55782
+ });
55708
55783
  }
55709
55784
  getDimension(nameWithGranularity) {
55710
55785
  return this.definition.getDimension(nameWithGranularity);
@@ -65656,7 +65731,7 @@ class SpreadingRelation {
65656
65731
  const EMPTY_ARRAY = [];
65657
65732
 
65658
65733
  const MAX_ITERATION = 30;
65659
- const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
65734
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell({ ...new CircularDependencyError(), origin: undefined }));
65660
65735
  const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
65661
65736
  class Evaluator {
65662
65737
  context;
@@ -65890,11 +65965,12 @@ class Evaluator {
65890
65965
  this.cellsBeingComputed.add(cellId);
65891
65966
  return cell.isFormula
65892
65967
  ? this.computeFormulaCell(position, cell)
65893
- : evaluateLiteral(cell, localeFormat);
65968
+ : evaluateLiteral(cell, localeFormat, position);
65894
65969
  }
65895
65970
  catch (e) {
65896
65971
  e.value = e?.value || CellErrorType.GenericError;
65897
65972
  e.message = e?.message || implementationErrorMessage;
65973
+ e.origin = position;
65898
65974
  return createEvaluatedCell(e);
65899
65975
  }
65900
65976
  finally {
@@ -65911,7 +65987,7 @@ class Evaluator {
65911
65987
  computeFormulaCell(formulaPosition, cellData) {
65912
65988
  const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, formulaPosition.sheetId, this.buildSafeGetSymbolValue(), formulaPosition);
65913
65989
  if (!isMatrix(formulaReturn)) {
65914
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData);
65990
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData, formulaPosition);
65915
65991
  if (evaluatedCell.type === CellValueType.error) {
65916
65992
  evaluatedCell.errorOriginPosition = formulaReturn.errorOriginPosition ?? formulaPosition;
65917
65993
  }
@@ -65987,7 +66063,7 @@ class Evaluator {
65987
66063
  const spreadValues = (i, j) => {
65988
66064
  const position = { sheetId, col: i + col, row: j + row };
65989
66065
  const cell = this.getters.getCell(position);
65990
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
66066
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell, position);
65991
66067
  if (evaluatedCell.type === CellValueType.error) {
65992
66068
  evaluatedCell.errorOriginPosition = matrixResult[i][j].errorOriginPosition ?? position;
65993
66069
  }
@@ -66660,7 +66736,7 @@ class EvaluationChartPlugin extends CoreViewPlugin {
66660
66736
  continue;
66661
66737
  }
66662
66738
  const figureId = figure.id;
66663
- const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel();
66739
+ const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel(this.getters);
66664
66740
  if (figureData) {
66665
66741
  figures.push({
66666
66742
  ...figure,
@@ -67573,6 +67649,23 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67573
67649
  static getters = ["getRowSize", "getHeaderSize", "getMaxAnchorOffset"];
67574
67650
  tallestCellInRow = {};
67575
67651
  ctx = document.createElement("canvas").getContext("2d");
67652
+ beforeHandle(cmd) {
67653
+ switch (cmd.type) {
67654
+ // Ensure rows are updated before "UPDATE_CELL" is dispatched from cell plugin.
67655
+ // "UPDATE_CELL" uses the Sheet core plugin to access row data.
67656
+ // If "ADD_COLUMNS_ROWS" has not been processed yet by header_sizes_ui,
67657
+ // size updates may apply to incorrect (pre-insert) rows.
67658
+ case "ADD_COLUMNS_ROWS":
67659
+ if (cmd.dimension === "COL") {
67660
+ return;
67661
+ }
67662
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
67663
+ const newCells = Array(cmd.quantity).fill(undefined);
67664
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
67665
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
67666
+ break;
67667
+ }
67668
+ }
67576
67669
  handle(cmd) {
67577
67670
  switch (cmd.type) {
67578
67671
  case "START":
@@ -67602,16 +67695,6 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67602
67695
  this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
67603
67696
  break;
67604
67697
  }
67605
- case "ADD_COLUMNS_ROWS": {
67606
- if (cmd.dimension === "COL") {
67607
- return;
67608
- }
67609
- const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
67610
- const newCells = Array(cmd.quantity).fill(undefined);
67611
- const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
67612
- this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
67613
- break;
67614
- }
67615
67698
  case "RESIZE_COLUMNS_ROWS":
67616
67699
  {
67617
67700
  const sheetId = cmd.sheetId;
@@ -74160,6 +74243,14 @@ class GridSelectionPlugin extends UIPlugin {
74160
74243
  const isBasedBefore = cmd.base < start;
74161
74244
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
74162
74245
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
74246
+ const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74247
+ const originalSize = Object.fromEntries(toRemove.map((element) => {
74248
+ const size = isCol
74249
+ ? this.getters.getColSize(cmd.sheetId, element)
74250
+ : this.getters.getUserRowSize(cmd.sheetId, element);
74251
+ const isDefaultCol = isCol && size === DEFAULT_CELL_WIDTH;
74252
+ return [element, isDefaultCol ? undefined : size];
74253
+ }));
74163
74254
  const target = [
74164
74255
  {
74165
74256
  left: isCol ? start + deltaCol : 0,
@@ -74190,13 +74281,12 @@ class GridSelectionPlugin extends UIPlugin {
74190
74281
  const col = selection.left;
74191
74282
  const row = selection.top;
74192
74283
  this.setSelectionMixin({ zone: selection, cell: { col, row } }, [selection]);
74193
- const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74194
74284
  let currentIndex = isBasedBefore ? cmd.base : cmd.base + 1;
74195
74285
  const resizingGroups = {};
74196
74286
  for (const element of toRemove) {
74197
- const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
74287
+ const size = originalSize[element];
74198
74288
  const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
74199
- if (size !== currentSize) {
74289
+ if (size && size !== currentSize) {
74200
74290
  resizingGroups[size] ??= [];
74201
74291
  resizingGroups[size].push(currentIndex);
74202
74292
  currentIndex += 1;
@@ -75622,6 +75712,7 @@ const coreViewsPluginRegistry = new Registry()
75622
75712
 
75623
75713
  autoCompleteProviders.add("dataValidation", {
75624
75714
  displayAllOnInitialContent: true,
75715
+ canBeToggled: false,
75625
75716
  getProposals(tokenAtCursor, content) {
75626
75717
  if (isFormula(content)) {
75627
75718
  return [];
@@ -77187,14 +77278,12 @@ class BottomBarSheet extends owl.Component {
77187
77278
  this.editionState = "initializing";
77188
77279
  }
77189
77280
  stopEdition() {
77190
- const input = this.sheetNameRef.el;
77191
- if (!this.state.isEditing || !input)
77281
+ if (!this.state.isEditing || !this.sheetNameRef.el)
77192
77282
  return;
77193
77283
  this.state.isEditing = false;
77194
77284
  this.editionState = "initializing";
77195
- input.blur();
77285
+ this.sheetNameRef.el.blur();
77196
77286
  const inputValue = this.getInputContent() || "";
77197
- input.innerText = inputValue;
77198
77287
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
77199
77288
  }
77200
77289
  cancelEdition() {
@@ -81612,6 +81701,9 @@ function createChart(chart, chartSheetIndex, data) {
81612
81701
  case "combo":
81613
81702
  plot = addComboChart(chart.data);
81614
81703
  break;
81704
+ case "pyramid":
81705
+ plot = addPyramidChart(chart.data);
81706
+ break;
81615
81707
  case "line":
81616
81708
  plot = addLineChart(chart.data);
81617
81709
  break;
@@ -81619,12 +81711,12 @@ function createChart(chart, chartSheetIndex, data) {
81619
81711
  plot = addScatterChart(chart.data);
81620
81712
  break;
81621
81713
  case "pie":
81622
- plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
81714
+ plot = addDoughnutChart(chart.data, chartSheetIndex, data);
81623
81715
  break;
81624
81716
  case "radar":
81625
81717
  plot = addRadarChart(chart.data);
81626
81718
  }
81627
- let position = "t";
81719
+ let position = "none";
81628
81720
  switch (chart.data.legendPosition) {
81629
81721
  case "bottom":
81630
81722
  position = "b";
@@ -81654,7 +81746,7 @@ function createChart(chart, chartSheetIndex, data) {
81654
81746
  ${plot}
81655
81747
  ${shapeProperty({ backgroundColor: chart.data.backgroundColor })}
81656
81748
  </c:plotArea>
81657
- ${addLegend(position, fontColor)}
81749
+ ${position !== "none" ? addLegend(position, fontColor) : ""}
81658
81750
  </c:chart>
81659
81751
  </c:chartSpace>
81660
81752
  `;
@@ -81812,6 +81904,7 @@ function addBarChart(chart) {
81812
81904
  //
81813
81905
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
81814
81906
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
81907
+ const chartDirection = chart.horizontal ? "bar" : "col";
81815
81908
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
81816
81909
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
81817
81910
  const leftDataSetsNodes = [];
@@ -81848,7 +81941,7 @@ function addBarChart(chart) {
81848
81941
  ${leftDataSetsNodes.length
81849
81942
  ? escapeXml /*xml*/ `
81850
81943
  <c:barChart>
81851
- <c:barDir val="col"/>
81944
+ <c:barDir val="${chartDirection}"/>
81852
81945
  <c:grouping val="${grouping}"/>
81853
81946
  <c:overlap val="${overlap}"/>
81854
81947
  <c:gapWidth val="70"/>
@@ -81858,8 +81951,12 @@ function addBarChart(chart) {
81858
81951
  <c:axId val="${catAxId}" />
81859
81952
  <c:axId val="${valAxId}" />
81860
81953
  </c:barChart>
81861
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
81862
- ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
81954
+ ${chartDirection === "col"
81955
+ ? addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)
81956
+ : addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, undefined, "maxMin")}
81957
+ ${chartDirection === "col"
81958
+ ? addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)
81959
+ : addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, undefined, undefined, "max")}
81863
81960
  `
81864
81961
  : ""}
81865
81962
  ${rightDataSetsNodes.length
@@ -81985,7 +82082,7 @@ function addComboChart(chart) {
81985
82082
  : ""}
81986
82083
  ${!useRightAxisForBarSerie || leftDataSetsNodes.length
81987
82084
  ? escapeXml /*xml*/ `
81988
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
82085
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, 0)}
81989
82086
  ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
81990
82087
  `
81991
82088
  : ""}
@@ -81997,6 +82094,94 @@ function addComboChart(chart) {
81997
82094
  : ""}
81998
82095
  `;
81999
82096
  }
82097
+ function addPyramidChart(chart) {
82098
+ const dataSets = chart.dataSets;
82099
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
82100
+ const colors = new ColorGenerator(dataSets.length, dataSetsColors);
82101
+ const leftDataSet = dataSets[0];
82102
+ const rightDataSet = dataSets[1];
82103
+ const firstColor = toXlsxHexColor(colors.next());
82104
+ const secondColor = toXlsxHexColor(colors.next());
82105
+ const { maxValue, majorUnit } = getPyramidChartHorizontalAxisConfig(chart.maxValue);
82106
+ const labelRangeEl = chart.labelRange
82107
+ ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>`
82108
+ : "";
82109
+ const leftBarDataSetNode = escapeXml /*xml*/ `
82110
+ <c:ser>
82111
+ <c:idx val="0"/>
82112
+ <c:order val="0"/>
82113
+ <c:invertIfNegative val="0" />
82114
+ ${extractDataSetLabel(leftDataSet.label)}
82115
+ ${shapeProperty({
82116
+ backgroundColor: firstColor,
82117
+ line: { color: firstColor },
82118
+ })}
82119
+ ${labelRangeEl}
82120
+ <!-- x-coordinate values -->
82121
+ <c:val>
82122
+ ${numberRef(leftDataSet.range)}
82123
+ </c:val>
82124
+ </c:ser>
82125
+ `;
82126
+ const rightBarDataSetNode = escapeXml /*xml*/ `
82127
+ <c:ser>
82128
+ <c:idx val="1"/>
82129
+ <c:order val="1"/>
82130
+ <c:invertIfNegative val="0" />
82131
+ ${extractDataSetLabel(rightDataSet.label)}
82132
+ ${shapeProperty({
82133
+ backgroundColor: secondColor,
82134
+ line: { color: secondColor },
82135
+ })}
82136
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
82137
+ <!-- x-coordinate values -->
82138
+ <c:val>
82139
+ ${numberRef(rightDataSet.range)}
82140
+ </c:val>
82141
+ </c:ser>
82142
+ `;
82143
+ return escapeXml /*xml*/ `
82144
+ <c:barChart>
82145
+ <c:barDir val="bar"/>
82146
+ <c:grouping val="clustered"/>
82147
+ <c:varyColors val="0" />
82148
+ ${leftBarDataSetNode}
82149
+ <c:gapWidth val="50" />
82150
+ <c:axId val="${catAxId}" />
82151
+ <c:axId val="${valAxId}" />
82152
+ </c:barChart>
82153
+ <c:barChart>
82154
+ <c:barDir val="bar"/>
82155
+ <c:grouping val="clustered"/>
82156
+ <c:varyColors val="0" />
82157
+ ${rightBarDataSetNode}
82158
+ <c:gapWidth val="50" />
82159
+ <c:axId val="${secondaryCatAxId}" />
82160
+ <c:axId val="${secondaryValAxId}" />
82161
+ </c:barChart>
82162
+ ${addAx("r", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, 0, "maxMin", "autoZero", "high")}
82163
+ ${addAx("b", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, 0, "maxMin", "max", "nextTo", maxValue, majorUnit, "#0;#0")}
82164
+ ${addAx("t", "c:valAx", secondaryValAxId, secondaryCatAxId, undefined, chart.fontColor, 1)}
82165
+ ${addAx("l", "c:catAx", secondaryCatAxId, secondaryValAxId, undefined, chart.fontColor, 1, "maxMin")}
82166
+ `;
82167
+ }
82168
+ function getPyramidChartHorizontalAxisConfig(maxValue) {
82169
+ const adjustMaxToDivisibleBy = (value, divisor) => {
82170
+ let adjusted = Math.ceil(value);
82171
+ while (adjusted % divisor !== 0) {
82172
+ adjusted++;
82173
+ }
82174
+ return adjusted;
82175
+ };
82176
+ const tickCount = 4;
82177
+ const interval = tickCount - 1;
82178
+ const adjustedMax = adjustMaxToDivisibleBy(maxValue, interval);
82179
+ const majorUnit = adjustedMax / interval;
82180
+ return {
82181
+ maxValue: adjustedMax,
82182
+ majorUnit,
82183
+ };
82184
+ }
82000
82185
  function addLineChart(chart) {
82001
82186
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
82002
82187
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
@@ -82189,7 +82374,7 @@ function addRadarChart(chart) {
82189
82374
  `}
82190
82375
  `;
82191
82376
  }
82192
- function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
82377
+ function addDoughnutChart(chart, chartSheetIndex, data) {
82193
82378
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
82194
82379
  const colors = new ColorGenerator(maxLength);
82195
82380
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
@@ -82227,7 +82412,7 @@ function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSiz
82227
82412
  return escapeXml /*xml*/ `
82228
82413
  <c:doughnutChart>
82229
82414
  <c:varyColors val="1" />
82230
- <c:holeSize val="${holeSize}" />
82415
+ <c:holeSize val="${chart.pieHolePercentage ?? (chart.isDoughnut ? DEFAULT_DOUGHNUT_CHART_HOLE_SIZE : 0)}" />
82231
82416
  ${insertDataLabels()}
82232
82417
  ${joinXmlNodes(dataSetsNodes)}
82233
82418
  </c:doughnutChart>
@@ -82246,25 +82431,35 @@ function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {
82246
82431
  </dLbls>
82247
82432
  `;
82248
82433
  }
82249
- function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
82434
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0, orientation = "minMax", crossPosition, tickLabelPosition = "nextTo", maxValue, majorUnit, format = "General") {
82250
82435
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
82251
82436
  // I.e. x-axis, will reference y-axis and vice-versa.
82252
82437
  const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
82253
82438
  const fontSize = title?.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE;
82439
+ const crossBetweenEl = axisName === "c:valAx" ? escapeXml `<c:crossBetween val="between" />` : "";
82440
+ const maxValueEl = maxValue ? escapeXml `<c:max val="${maxValue}" />` : "";
82441
+ const minValueEl = maxValue ? escapeXml `<c:min val="${-maxValue}" />` : "";
82442
+ const majorUnitEl = majorUnit ? escapeXml `<c:majorUnit val="${majorUnit}" />` : "";
82254
82443
  return escapeXml /*xml*/ `
82255
82444
  <${axisName}>
82256
82445
  <c:axId val="${axId}"/>
82257
82446
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
82258
- <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
82447
+ <c:crosses val="${crossPosition || (position === "b" || position === "l" ? "min" : "max")}"/>
82448
+ <c:auto val="1"/>
82449
+ ${crossBetweenEl}
82259
82450
  <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
82260
82451
  <c:scaling>
82261
- <c:orientation val="minMax" />
82452
+ <c:orientation val="${orientation}" />
82453
+ ${maxValueEl}
82454
+ ${minValueEl}
82262
82455
  </c:scaling>
82456
+ ${majorUnitEl}
82263
82457
  <c:axPos val="${position}" />
82458
+ <c:tickLblPos val="${tickLabelPosition}" />
82264
82459
  ${insertMajorGridLines()}
82265
82460
  <c:majorTickMark val="out" />
82266
82461
  <c:minorTickMark val="none" />
82267
- <c:numFmt formatCode="General" sourceLinked="1" />
82462
+ <c:numFmt formatCode="${format}" sourceLinked="${format === "General" ? "1" : "0"}" />
82268
82463
  <c:title>
82269
82464
  ${insertText(title?.text ?? "", color, fontSize, title)}
82270
82465
  </c:title>
@@ -82459,7 +82654,10 @@ function addConditionalFormatting(dxfs, conditionalFormats) {
82459
82654
  function addCellIsRule(cf, rule, dxfs) {
82460
82655
  const ruleAttributes = commonCfAttributes(cf);
82461
82656
  const operator = convertOperator(rule.operator);
82462
- ruleAttributes.push(...cellRuleTypeAttributes(rule), ["operator", operator]);
82657
+ ruleAttributes.push(...cellRuleTypeAttributes(rule));
82658
+ if (operator.length) {
82659
+ ruleAttributes.push(["operator", operator]);
82660
+ }
82463
82661
  const formulas = cellRuleFormula(cf.ranges, rule).map((formula) => escapeXml /*xml*/ `<formula>${formula}</formula>`);
82464
82662
  const dxf = {
82465
82663
  font: {
@@ -82505,6 +82703,8 @@ function cellRuleFormula(ranges, rule) {
82505
82703
  case "isLessThan":
82506
82704
  case "isLessOrEqualTo":
82507
82705
  return [values[0]];
82706
+ case "customFormula":
82707
+ return values[0].startsWith("=") ? [values[0].slice(1)] : [values[0]];
82508
82708
  case "isBetween":
82509
82709
  case "isNotBetween":
82510
82710
  return [values[0], values[1]];
@@ -82533,6 +82733,8 @@ function cellRuleTypeAttributes(rule) {
82533
82733
  case "isBetween":
82534
82734
  case "isNotBetween":
82535
82735
  return [["type", "cellIs"]];
82736
+ case "customFormula":
82737
+ return [["type", "expression"]];
82536
82738
  }
82537
82739
  }
82538
82740
  function addDataBarRule(cf, rule) {
@@ -84633,6 +84835,6 @@ exports.tokenColors = tokenColors;
84633
84835
  exports.tokenize = tokenize;
84634
84836
 
84635
84837
 
84636
- __info__.version = "18.5.0-alpha.2";
84637
- __info__.date = "2025-07-11T11:13:53.317Z";
84638
- __info__.hash = "6d42178";
84838
+ __info__.version = "18.5.0-alpha.3";
84839
+ __info__.date = "2025-07-28T13:43:05.981Z";
84840
+ __info__.hash = "53dfee8";