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

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.4
6
+ * @date 2025-07-30T11:23:18.805Z
7
+ * @hash 34a4ab3
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';
@@ -2901,6 +2901,7 @@ const availableConditionalFormatOperators = new Set([
2901
2901
  "isEmpty",
2902
2902
  "isNotEqual",
2903
2903
  "isEqual",
2904
+ "customFormula",
2904
2905
  ]);
2905
2906
 
2906
2907
  const availableDataValidationOperators = new Set([
@@ -5033,11 +5034,11 @@ function isTextFormat(format) {
5033
5034
  }
5034
5035
  }
5035
5036
 
5036
- function evaluateLiteral(literalCell, localeFormat) {
5037
+ function evaluateLiteral(literalCell, localeFormat, position) {
5037
5038
  const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5038
5039
  ? literalCell.content
5039
5040
  : literalCell.parsedValue;
5040
- const functionResult = { value, format: localeFormat.format };
5041
+ const functionResult = { value, format: localeFormat.format, origin: position };
5041
5042
  return createEvaluatedCell(functionResult, localeFormat.locale);
5042
5043
  }
5043
5044
  function parseLiteral(content, locale) {
@@ -5059,10 +5060,11 @@ function parseLiteral(content, locale) {
5059
5060
  }
5060
5061
  return content;
5061
5062
  }
5062
- function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5063
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell, origin) {
5063
5064
  const link = detectLink(functionResult.value);
5064
5065
  if (!link) {
5065
- return _createEvaluatedCell(functionResult, locale, cell);
5066
+ const evaluateCell = _createEvaluatedCell(functionResult, locale, cell);
5067
+ return addOrigin(evaluateCell, functionResult.origin ?? origin);
5066
5068
  }
5067
5069
  const value = parseLiteral(link.label, locale);
5068
5070
  const format = functionResult.format ||
@@ -5073,10 +5075,10 @@ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5073
5075
  value,
5074
5076
  format,
5075
5077
  };
5076
- return {
5078
+ return addOrigin({
5077
5079
  ..._createEvaluatedCell(linkPayload, locale, cell),
5078
5080
  link,
5079
- };
5081
+ }, functionResult.origin ?? origin);
5080
5082
  }
5081
5083
  function _createEvaluatedCell(functionResult, locale, cell) {
5082
5084
  let { value, format, message } = functionResult;
@@ -5166,6 +5168,17 @@ function errorCell(value, message) {
5166
5168
  defaultAlign: "center",
5167
5169
  };
5168
5170
  }
5171
+ function addOrigin(cell, origin) {
5172
+ if (cell.value === null) {
5173
+ // ignore empty cells to allow sharing the same object instance
5174
+ return cell;
5175
+ }
5176
+ if ("origin" in cell) {
5177
+ return cell;
5178
+ }
5179
+ cell.origin = origin;
5180
+ return cell;
5181
+ }
5169
5182
 
5170
5183
  function toCriterionDateNumber(dateValue) {
5171
5184
  const today = DateTime.now();
@@ -7846,6 +7859,7 @@ function changeCFRuleLocale(rule, changeContentLocale) {
7846
7859
  case "isGreaterOrEqualTo":
7847
7860
  case "isLessThan":
7848
7861
  case "isLessOrEqualTo":
7862
+ case "customFormula":
7849
7863
  rule.values = rule.values.map((v) => changeContentLocale(v));
7850
7864
  return rule;
7851
7865
  case "beginsWithText":
@@ -8624,12 +8638,12 @@ const AGGREGATOR_NAMES = {
8624
8638
  avg: _t("Average"),
8625
8639
  sum: _t("Sum"),
8626
8640
  };
8627
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8641
+ const DEFAULT_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8628
8642
  const AGGREGATORS_BY_FIELD_TYPE = {
8629
- integer: NUMBER_CHAR_AGGREGATORS,
8630
- char: NUMBER_CHAR_AGGREGATORS,
8643
+ integer: DEFAULT_AGGREGATORS,
8644
+ char: DEFAULT_AGGREGATORS,
8645
+ datetime: DEFAULT_AGGREGATORS,
8631
8646
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8632
- datetime: ["max", "min", "count_distinct", "count"],
8633
8647
  };
8634
8648
  const AGGREGATORS = {};
8635
8649
  for (const type in AGGREGATORS_BY_FIELD_TYPE) {
@@ -10534,6 +10548,7 @@ const EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS = 100;
10534
10548
  /** The possible values for the XLSX polynomial trendline order are defined by the ST_Order simple type (§21.2.3.29) */
10535
10549
  const MAX_XLSX_POLYNOMIAL_DEGREE = 6;
10536
10550
  const FIRST_NUMFMT_ID = 164;
10551
+ const DEFAULT_DOUGHNUT_CHART_HOLE_SIZE = 50;
10537
10552
  const FORCE_DEFAULT_ARGS_FUNCTIONS = {
10538
10553
  FLOOR: [{ type: "NUMBER", value: 1 }],
10539
10554
  CEILING: [{ type: "NUMBER", value: 1 }],
@@ -19033,13 +19048,19 @@ const COLUMN = {
19033
19048
  if (isEvaluationError(cellReference?.value)) {
19034
19049
  return cellReference;
19035
19050
  }
19036
- const column = cellReference === undefined
19037
- ? this.__originCellPosition?.col
19038
- : toZone(cellReference.value).left;
19039
- if (column === undefined) {
19040
- return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19051
+ if (cellReference === undefined) {
19052
+ if (this.__originCellPosition?.col === undefined) {
19053
+ return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19054
+ }
19055
+ return this.__originCellPosition.col + 1;
19056
+ }
19057
+ const zone = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), cellReference.value).zone;
19058
+ if (zone.left === zone.right) {
19059
+ return zone.left + 1;
19041
19060
  }
19042
- return column + 1;
19061
+ return generateMatrix(zone.right - zone.left + 1, 1, (col, row) => ({
19062
+ value: zone.left + col + 1,
19063
+ }));
19043
19064
  },
19044
19065
  isExported: true,
19045
19066
  };
@@ -19270,13 +19291,19 @@ const ROW = {
19270
19291
  if (isEvaluationError(cellReference?.value)) {
19271
19292
  return cellReference;
19272
19293
  }
19273
- const row = cellReference === undefined
19274
- ? this.__originCellPosition?.row
19275
- : toZone(cellReference.value).top;
19276
- if (row === undefined) {
19277
- return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19294
+ if (cellReference === undefined) {
19295
+ if (this.__originCellPosition?.row === undefined) {
19296
+ return new EvaluationError(_t("In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter."));
19297
+ }
19298
+ return this.__originCellPosition.row + 1;
19278
19299
  }
19279
- return row + 1;
19300
+ const zone = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), cellReference.value).zone;
19301
+ if (zone.top === zone.bottom) {
19302
+ return zone.top + 1;
19303
+ }
19304
+ return generateMatrix(1, zone.bottom - zone.top + 1, (col, row) => ({
19305
+ value: zone.top + row + 1,
19306
+ }));
19280
19307
  },
19281
19308
  isExported: true,
19282
19309
  };
@@ -22853,6 +22880,16 @@ class AbstractChart {
22853
22880
  static getDefinitionFromContextCreation(context) {
22854
22881
  throw new Error("This method should be implemented by sub class");
22855
22882
  }
22883
+ getCommonDataSetAttributesForExcel(labelRange, dataSets, shouldRemoveFirstLabel) {
22884
+ const excelDataSets = dataSets
22885
+ .map((ds) => toExcelDataset(this.getters, ds))
22886
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
22887
+ const excelLabelRange = toExcelLabelRange(this.getters, labelRange, shouldRemoveFirstLabel);
22888
+ return {
22889
+ dataSets: excelDataSets,
22890
+ labelRange: excelLabelRange,
22891
+ };
22892
+ }
22856
22893
  }
22857
22894
 
22858
22895
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
@@ -26335,6 +26372,7 @@ class BarChart extends AbstractChart {
26335
26372
  labelRange: context.auxiliaryRange || undefined,
26336
26373
  axesDesign: context.axesDesign,
26337
26374
  showValues: context.showValues,
26375
+ horizontal: context.horizontal,
26338
26376
  };
26339
26377
  }
26340
26378
  getContextCreation() {
@@ -26392,10 +26430,7 @@ class BarChart extends AbstractChart {
26392
26430
  };
26393
26431
  }
26394
26432
  getDefinitionForExcel() {
26395
- const dataSets = this.dataSets
26396
- .map((ds) => toExcelDataset(this.getters, ds))
26397
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
26398
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26433
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26399
26434
  const definition = this.getDefinition();
26400
26435
  return {
26401
26436
  ...definition,
@@ -26983,10 +27018,7 @@ class ComboChart extends AbstractChart {
26983
27018
  if (this.aggregated) {
26984
27019
  return undefined;
26985
27020
  }
26986
- const dataSets = this.dataSets
26987
- .map((ds) => toExcelDataset(this.getters, ds))
26988
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
26989
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27021
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
26990
27022
  const definition = this.getDefinition();
26991
27023
  return {
26992
27024
  ...definition,
@@ -27725,10 +27757,7 @@ class LineChart extends AbstractChart {
27725
27757
  return new LineChart(definition, this.sheetId, this.getters);
27726
27758
  }
27727
27759
  getDefinitionForExcel() {
27728
- const dataSets = this.dataSets
27729
- .map((ds) => toExcelDataset(this.getters, ds))
27730
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
27731
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27760
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27732
27761
  const definition = this.getDefinition();
27733
27762
  return {
27734
27763
  ...definition,
@@ -27816,7 +27845,8 @@ class PieChart extends AbstractChart {
27816
27845
  type: "pie",
27817
27846
  labelRange: context.auxiliaryRange || undefined,
27818
27847
  aggregated: context.aggregated ?? false,
27819
- isDoughnut: false,
27848
+ isDoughnut: context.isDoughnut,
27849
+ pieHolePercentage: context.pieHolePercentage,
27820
27850
  showValues: context.showValues,
27821
27851
  };
27822
27852
  }
@@ -27864,10 +27894,7 @@ class PieChart extends AbstractChart {
27864
27894
  return new PieChart(definition, sheetId, this.getters);
27865
27895
  }
27866
27896
  getDefinitionForExcel() {
27867
- const dataSets = this.dataSets
27868
- .map((ds) => toExcelDataset(this.getters, ds))
27869
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
27870
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27897
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
27871
27898
  return {
27872
27899
  ...this.getDefinition(),
27873
27900
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
@@ -28012,8 +28039,22 @@ class PyramidChart extends AbstractChart {
28012
28039
  showValues: this.showValues,
28013
28040
  };
28014
28041
  }
28015
- getDefinitionForExcel() {
28016
- return undefined;
28042
+ getDefinitionForExcel(getters) {
28043
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28044
+ const definition = this.getDefinition();
28045
+ const chartData = getPyramidChartData(definition, this.dataSets, this.labelRange, getters);
28046
+ const { dataSetsValues } = chartData;
28047
+ const maxValue = Math.max(...dataSetsValues.map((dataSet) => Math.max(...dataSet.data.map(Math.abs))));
28048
+ return {
28049
+ ...definition,
28050
+ horizontal: true,
28051
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
28052
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
28053
+ dataSets,
28054
+ labelRange,
28055
+ verticalAxis: getDefinedAxis(definition),
28056
+ maxValue,
28057
+ };
28017
28058
  }
28018
28059
  updateRanges(applyChange) {
28019
28060
  const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
@@ -28156,10 +28197,7 @@ class RadarChart extends AbstractChart {
28156
28197
  if (this.aggregated) {
28157
28198
  return undefined;
28158
28199
  }
28159
- const dataSets = this.dataSets
28160
- .map((ds) => toExcelDataset(this.getters, ds))
28161
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
28162
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28200
+ const { dataSets, labelRange } = this.getCommonDataSetAttributesForExcel(this.labelRange, this.dataSets, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
28163
28201
  const definition = this.getDefinition();
28164
28202
  return {
28165
28203
  ...definition,
@@ -30477,28 +30515,17 @@ class MenuPopover extends Component {
30477
30515
  }
30478
30516
  }
30479
30517
 
30480
- class ChartDashboardMenu extends Component {
30481
- static template = "spreadsheet.ChartDashboardMenu";
30482
- static components = { MenuPopover };
30483
- static props = { figureUI: Object };
30518
+ class ChartDashboardMenuStore extends SpreadsheetStore {
30519
+ chartId;
30520
+ mutators = ["reset"];
30484
30521
  originalChartDefinition;
30485
- fullScreenFigureStore;
30486
- menuState = useState({ isOpen: false, anchorRect: null, menuItems: [] });
30487
- setup() {
30488
- super.setup();
30489
- this.fullScreenFigureStore = useStore(FullScreenChartStore);
30490
- this.originalChartDefinition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
30491
- onWillUpdateProps(({ figureUI }) => {
30492
- if (figureUI.id !== this.props.figureUI.id) {
30493
- this.originalChartDefinition = this.env.model.getters.getChartDefinition(figureUI.id);
30494
- }
30495
- });
30496
- }
30497
- getMenuItems() {
30498
- return [this.fullScreenMenuItem, ...this.changeChartTypeMenuItems].filter(isDefined);
30522
+ constructor(get, chartId) {
30523
+ super(get);
30524
+ this.chartId = chartId;
30525
+ this.originalChartDefinition = this.getters.getChartDefinition(this.chartId);
30499
30526
  }
30500
30527
  get changeChartTypeMenuItems() {
30501
- const definition = this.env.model.getters.getChartDefinition(this.props.figureUI.id);
30528
+ const definition = this.getters.getChartDefinition(this.chartId);
30502
30529
  if (!["line", "bar", "pie"].includes(definition.type)) {
30503
30530
  return [];
30504
30531
  }
@@ -30507,27 +30534,19 @@ class ChartDashboardMenu extends Component {
30507
30534
  return {
30508
30535
  id: item.chartType,
30509
30536
  label: item.displayName,
30510
- onClick: () => this.onTypeChange(item.chartType),
30511
- isSelected: item.chartType === this.selectedChartType,
30537
+ onClick: () => this.updateType(item.chartType),
30538
+ isSelected: item.chartType === this.getters.getChartDefinition(this.chartId).type,
30512
30539
  iconClass: this.getIconClasses(item.chartType),
30513
30540
  };
30514
30541
  });
30515
30542
  }
30516
- getIconClasses(type) {
30517
- if (type.includes("bar")) {
30518
- return "fa fa-bar-chart";
30519
- }
30520
- if (type.includes("line")) {
30521
- return "fa fa-line-chart";
30522
- }
30523
- if (type.includes("pie")) {
30524
- return "fa fa-pie-chart";
30525
- }
30526
- return "";
30543
+ reset(chartId) {
30544
+ this.chartId = chartId;
30545
+ this.originalChartDefinition = this.getters.getChartDefinition(chartId);
30527
30546
  }
30528
- onTypeChange(type) {
30529
- const figureId = this.props.figureUI.id;
30530
- const currentDefinition = this.env.model.getters.getChartDefinition(figureId);
30547
+ updateType(type) {
30548
+ const figureId = this.chartId;
30549
+ const currentDefinition = this.getters.getChartDefinition(figureId);
30531
30550
  if (currentDefinition.type === type) {
30532
30551
  return;
30533
30552
  }
@@ -30538,7 +30557,7 @@ class ChartDashboardMenu extends Component {
30538
30557
  else {
30539
30558
  const newChartInfo = chartSubtypeRegistry.get(type);
30540
30559
  const ChartClass = chartRegistry.get(newChartInfo.chartType);
30541
- const chartCreationContext = this.env.model.getters.getContextCreationChart(figureId);
30560
+ const chartCreationContext = this.getters.getContextCreationChart(figureId);
30542
30561
  if (!chartCreationContext)
30543
30562
  return;
30544
30563
  definition = {
@@ -30546,14 +30565,45 @@ class ChartDashboardMenu extends Component {
30546
30565
  ...newChartInfo.subtypeDefinition,
30547
30566
  };
30548
30567
  }
30549
- this.env.model.dispatch("UPDATE_CHART", {
30568
+ this.model.dispatch("UPDATE_CHART", {
30550
30569
  definition,
30551
30570
  figureId,
30552
- sheetId: this.env.model.getters.getActiveSheetId(),
30571
+ sheetId: this.getters.getActiveSheetId(),
30553
30572
  });
30554
30573
  }
30555
- get selectedChartType() {
30556
- return this.env.model.getters.getChartDefinition(this.props.figureUI.id).type;
30574
+ getIconClasses(type) {
30575
+ if (type.includes("bar")) {
30576
+ return "fa fa-bar-chart";
30577
+ }
30578
+ if (type.includes("line")) {
30579
+ return "fa fa-line-chart";
30580
+ }
30581
+ if (type.includes("pie")) {
30582
+ return "fa fa-pie-chart";
30583
+ }
30584
+ return "";
30585
+ }
30586
+ }
30587
+
30588
+ class ChartDashboardMenu extends Component {
30589
+ static template = "o-spreadsheet-ChartDashboardMenu";
30590
+ static components = { MenuPopover };
30591
+ static props = { figureUI: Object };
30592
+ fullScreenFigureStore;
30593
+ store;
30594
+ menuState = useState({ isOpen: false, anchorRect: null, menuItems: [] });
30595
+ setup() {
30596
+ super.setup();
30597
+ this.store = useLocalStore(ChartDashboardMenuStore, this.props.figureUI.id);
30598
+ this.fullScreenFigureStore = useStore(FullScreenChartStore);
30599
+ onWillUpdateProps(({ figureUI }) => {
30600
+ if (figureUI.id !== this.props.figureUI.id) {
30601
+ this.store.reset(figureUI.id);
30602
+ }
30603
+ });
30604
+ }
30605
+ getMenuItems() {
30606
+ return [this.fullScreenMenuItem, ...this.store.changeChartTypeMenuItems].filter(isDefined);
30557
30607
  }
30558
30608
  get backgroundColor() {
30559
30609
  const color = this.env.model.getters.getChartDefinition(this.props.figureUI.id).background;
@@ -31695,7 +31745,7 @@ criterionEvaluatorRegistry.add("isValueInRange", {
31695
31745
  }
31696
31746
  const criterionValues = getters.getDataValidationRangeValues(sheetId, criterion);
31697
31747
  return criterionValues
31698
- .map((value) => value.toLowerCase())
31748
+ .map((value) => value.value.toLowerCase())
31699
31749
  .includes(value.toString().toLowerCase());
31700
31750
  },
31701
31751
  getErrorString: (criterion) => _t("The value must be a value in the range %s", String(criterion.values[0])),
@@ -32606,6 +32656,9 @@ class AbstractComposerStore extends SpreadsheetStore {
32606
32656
  get isAutoCompleteDisplayed() {
32607
32657
  return !!this.autoComplete.provider;
32608
32658
  }
32659
+ get canBeToggled() {
32660
+ return this.autoComplete.provider?.canBeToggled ?? true;
32661
+ }
32609
32662
  cycleReferences() {
32610
32663
  const locale = this.getters.getLocale();
32611
32664
  const updated = cycleFixedReference(this.composerSelection, this._currentContent, locale);
@@ -33135,6 +33188,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33135
33188
  proposals,
33136
33189
  selectProposal: provider.selectProposal,
33137
33190
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33191
+ canBeToggled: provider.canBeToggled,
33138
33192
  };
33139
33193
  }
33140
33194
  if (exactMatch && this._currentContent !== this.initialContent) {
@@ -33157,6 +33211,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33157
33211
  proposals,
33158
33212
  selectProposal: provider.selectProposal,
33159
33213
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33214
+ canBeToggled: provider.canBeToggled,
33160
33215
  };
33161
33216
  }
33162
33217
  }
@@ -33727,9 +33782,13 @@ class Composer extends Component {
33727
33782
  }
33728
33783
  }
33729
33784
  closeAssistant() {
33785
+ if (!this.props.composerStore.canBeToggled)
33786
+ return;
33730
33787
  this.assistant.forcedClosed = true;
33731
33788
  }
33732
33789
  openAssistant() {
33790
+ if (!this.props.composerStore.canBeToggled)
33791
+ return;
33733
33792
  this.assistant.forcedClosed = false;
33734
33793
  }
33735
33794
  onWheel(event) {
@@ -33919,7 +33978,7 @@ class Composer extends Component {
33919
33978
  return [...new Set(argsToFocus)];
33920
33979
  }
33921
33980
  autoComplete(value) {
33922
- if (!value || this.assistant.forcedClosed) {
33981
+ if (!value || (this.assistant.forcedClosed && this.props.composerStore.canBeToggled)) {
33923
33982
  return;
33924
33983
  }
33925
33984
  this.props.composerStore.insertAutoCompleteValue(value);
@@ -37497,7 +37556,7 @@ const CF_OPERATOR_TYPE_CONVERSION_MAP = {
37497
37556
  /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
37498
37557
  const CF_TYPE_CONVERSION_MAP = {
37499
37558
  aboveAverage: undefined,
37500
- expression: undefined,
37559
+ expression: "customFormula",
37501
37560
  cellIs: undefined, // exist but isn't an operator in o_spreadsheet
37502
37561
  colorScale: undefined, // exist but isn't an operator in o_spreadsheet
37503
37562
  dataBar: undefined,
@@ -38101,7 +38160,6 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38101
38160
  case "containsErrors":
38102
38161
  case "notContainsErrors":
38103
38162
  case "duplicateValues":
38104
- case "expression":
38105
38163
  case "top10":
38106
38164
  case "uniqueValues":
38107
38165
  case "timePeriod":
@@ -38134,6 +38192,12 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38134
38192
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
38135
38193
  values.push(rule.text);
38136
38194
  break;
38195
+ case "expression":
38196
+ if (!rule.formula?.length)
38197
+ continue;
38198
+ operator = CF_TYPE_CONVERSION_MAP[rule.type];
38199
+ values.push(`=${rule.formula[0]}`);
38200
+ break;
38137
38201
  case "containsBlanks":
38138
38202
  case "notContainsBlanks":
38139
38203
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
@@ -38363,6 +38427,8 @@ function convertOperator(operator) {
38363
38427
  return "equal";
38364
38428
  case "isNotEqual":
38365
38429
  return "notEqual";
38430
+ case "customFormula":
38431
+ return "";
38366
38432
  }
38367
38433
  }
38368
38434
  // -------------------------------------
@@ -38698,6 +38764,9 @@ function convertChartData(chartData) {
38698
38764
  aggregated: false,
38699
38765
  cumulative: chartData.cumulative || false,
38700
38766
  labelsAsText: false,
38767
+ horizontal: chartData.horizontal,
38768
+ isDoughnut: chartData.isDoughnut,
38769
+ pieHolePercentage: chartData.pieHolePercentage,
38701
38770
  };
38702
38771
  try {
38703
38772
  const ChartClass = chartRegistry.get(chartData.type);
@@ -39974,6 +40043,12 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39974
40043
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
39975
40044
  default: "clustered",
39976
40045
  }).asString();
40046
+ const chartDirection = this.extractChildAttr(rootChartElement, "c:barDir", "val", {
40047
+ default: "col",
40048
+ }).asString();
40049
+ const chartHoleSize = this.extractChildAttr(rootChartElement, "c:holeSize", "val", {
40050
+ default: "0",
40051
+ }).asNum();
39977
40052
  return {
39978
40053
  title: { text: chartTitle },
39979
40054
  type: CHART_TYPE_CONVERSION_MAP[chartType],
@@ -39987,6 +40062,9 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39987
40062
  }).asString()],
39988
40063
  stacked: barChartGrouping === "stacked",
39989
40064
  fontColor: "000000",
40065
+ horizontal: chartDirection === "bar",
40066
+ isDoughnut: chartHoleSize > 0,
40067
+ pieHolePercentage: chartHoleSize,
39990
40068
  };
39991
40069
  })[0];
39992
40070
  }
@@ -50977,6 +51055,9 @@ class PieChartDesignPanel extends Component {
50977
51055
  pieHolePercentage,
50978
51056
  });
50979
51057
  }
51058
+ get defaultHoleSize() {
51059
+ return DEFAULT_DOUGHNUT_CHART_HOLE_SIZE;
51060
+ }
50980
51061
  }
50981
51062
 
50982
51063
  class RadarChartDesignPanel extends Component {
@@ -52041,7 +52122,8 @@ class ConditionalFormattingEditor extends Component {
52041
52122
  static props = {
52042
52123
  editedCf: Object,
52043
52124
  onCancel: Function,
52044
- onSave: Function,
52125
+ onExit: Function,
52126
+ isNewCf: Boolean,
52045
52127
  };
52046
52128
  static components = {
52047
52129
  SelectionInput,
@@ -52066,6 +52148,7 @@ class ConditionalFormattingEditor extends Component {
52066
52148
  currentCFType: this.props.editedCf.rule.type,
52067
52149
  ranges: this.props.editedCf.ranges,
52068
52150
  rules: this.getDefaultRules(),
52151
+ hasEditedCf: this.props.isNewCf,
52069
52152
  });
52070
52153
  switch (this.props.editedCf.rule.type) {
52071
52154
  case "CellIsRule":
@@ -52117,6 +52200,9 @@ class ConditionalFormattingEditor extends Component {
52117
52200
  ranges: ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
52118
52201
  sheetId,
52119
52202
  });
52203
+ if (result.isSuccessful) {
52204
+ this.state.hasEditedCf = true;
52205
+ }
52120
52206
  const reasons = result.reasons.filter((r) => r !== "NoChanges" /* CommandResult.NoChanges */);
52121
52207
  if (!newCf.suppressErrors) {
52122
52208
  this.state.errors = reasons;
@@ -52138,7 +52224,15 @@ class ConditionalFormattingEditor extends Component {
52138
52224
  onSave() {
52139
52225
  const result = this.updateConditionalFormat({});
52140
52226
  if (result.length === 0) {
52141
- this.props.onSave();
52227
+ this.props.onExit();
52228
+ }
52229
+ }
52230
+ onCancel() {
52231
+ if (this.state.hasEditedCf) {
52232
+ this.props.onCancel();
52233
+ }
52234
+ else {
52235
+ this.props.onExit();
52142
52236
  }
52143
52237
  }
52144
52238
  getDefaultRules() {
@@ -55700,9 +55794,15 @@ class SpreadsheetPivot {
55700
55794
  return domain.reduce((current, acc) => this.filterDataEntriesFromDomainNode(current, acc), dataEntries);
55701
55795
  }
55702
55796
  filterDataEntriesFromDomainNode(dataEntries, domain) {
55703
- const { field, value } = domain;
55797
+ const { field, value, type } = domain;
55704
55798
  const { nameWithGranularity } = this.getDimension(field);
55705
- return dataEntries.filter((entry) => entry[nameWithGranularity]?.value === value);
55799
+ return dataEntries.filter((entry) => {
55800
+ const cellValue = entry[nameWithGranularity]?.value;
55801
+ if (type === "char") {
55802
+ return String(cellValue) === String(value);
55803
+ }
55804
+ return cellValue === value;
55805
+ });
55706
55806
  }
55707
55807
  getDimension(nameWithGranularity) {
55708
55808
  return this.definition.getDimension(nameWithGranularity);
@@ -65654,7 +65754,7 @@ class SpreadingRelation {
65654
65754
  const EMPTY_ARRAY = [];
65655
65755
 
65656
65756
  const MAX_ITERATION = 30;
65657
- const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
65757
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell({ ...new CircularDependencyError(), origin: undefined }));
65658
65758
  const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
65659
65759
  class Evaluator {
65660
65760
  context;
@@ -65888,11 +65988,12 @@ class Evaluator {
65888
65988
  this.cellsBeingComputed.add(cellId);
65889
65989
  return cell.isFormula
65890
65990
  ? this.computeFormulaCell(position, cell)
65891
- : evaluateLiteral(cell, localeFormat);
65991
+ : evaluateLiteral(cell, localeFormat, position);
65892
65992
  }
65893
65993
  catch (e) {
65894
65994
  e.value = e?.value || CellErrorType.GenericError;
65895
65995
  e.message = e?.message || implementationErrorMessage;
65996
+ e.origin = position;
65896
65997
  return createEvaluatedCell(e);
65897
65998
  }
65898
65999
  finally {
@@ -65909,7 +66010,7 @@ class Evaluator {
65909
66010
  computeFormulaCell(formulaPosition, cellData) {
65910
66011
  const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, formulaPosition.sheetId, this.buildSafeGetSymbolValue(), formulaPosition);
65911
66012
  if (!isMatrix(formulaReturn)) {
65912
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData);
66013
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData, formulaPosition);
65913
66014
  if (evaluatedCell.type === CellValueType.error) {
65914
66015
  evaluatedCell.errorOriginPosition = formulaReturn.errorOriginPosition ?? formulaPosition;
65915
66016
  }
@@ -65985,7 +66086,7 @@ class Evaluator {
65985
66086
  const spreadValues = (i, j) => {
65986
66087
  const position = { sheetId, col: i + col, row: j + row };
65987
66088
  const cell = this.getters.getCell(position);
65988
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
66089
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell, position);
65989
66090
  if (evaluatedCell.type === CellValueType.error) {
65990
66091
  evaluatedCell.errorOriginPosition = matrixResult[i][j].errorOriginPosition ?? position;
65991
66092
  }
@@ -66658,7 +66759,7 @@ class EvaluationChartPlugin extends CoreViewPlugin {
66658
66759
  continue;
66659
66760
  }
66660
66761
  const figureId = figure.id;
66661
- const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel();
66762
+ const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel(this.getters);
66662
66763
  if (figureData) {
66663
66764
  figures.push({
66664
66765
  ...figure,
@@ -67027,8 +67128,16 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
67027
67128
  }
67028
67129
  getDataValidationRangeValues(sheetId, criterion) {
67029
67130
  const range = this.getters.getRangeFromSheetXC(sheetId, String(criterion.values[0]));
67030
- const criterionValues = this.getters.getRangeValues(range);
67031
- return criterionValues.map((value) => value?.toString()).filter(isDefined);
67131
+ const values = [];
67132
+ const labelsSet = new Set();
67133
+ for (const p of positions(range.zone)) {
67134
+ const cell = this.getters.getEvaluatedCell({ ...p, sheetId: range.sheetId });
67135
+ if (cell.formattedValue && !labelsSet.has(cell.formattedValue)) {
67136
+ labelsSet.add(cell.formattedValue);
67137
+ values.push({ label: cell.formattedValue, value: cell.value?.toString() || "" });
67138
+ }
67139
+ }
67140
+ return values;
67032
67141
  }
67033
67142
  isCellValidCheckbox(cellPosition) {
67034
67143
  if (!this.getters.isMainCellPosition(cellPosition)) {
@@ -67571,6 +67680,23 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67571
67680
  static getters = ["getRowSize", "getHeaderSize", "getMaxAnchorOffset"];
67572
67681
  tallestCellInRow = {};
67573
67682
  ctx = document.createElement("canvas").getContext("2d");
67683
+ beforeHandle(cmd) {
67684
+ switch (cmd.type) {
67685
+ // Ensure rows are updated before "UPDATE_CELL" is dispatched from cell plugin.
67686
+ // "UPDATE_CELL" uses the Sheet core plugin to access row data.
67687
+ // If "ADD_COLUMNS_ROWS" has not been processed yet by header_sizes_ui,
67688
+ // size updates may apply to incorrect (pre-insert) rows.
67689
+ case "ADD_COLUMNS_ROWS":
67690
+ if (cmd.dimension === "COL") {
67691
+ return;
67692
+ }
67693
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
67694
+ const newCells = Array(cmd.quantity).fill(undefined);
67695
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
67696
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
67697
+ break;
67698
+ }
67699
+ }
67574
67700
  handle(cmd) {
67575
67701
  switch (cmd.type) {
67576
67702
  case "START":
@@ -67600,16 +67726,6 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67600
67726
  this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
67601
67727
  break;
67602
67728
  }
67603
- case "ADD_COLUMNS_ROWS": {
67604
- if (cmd.dimension === "COL") {
67605
- return;
67606
- }
67607
- const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
67608
- const newCells = Array(cmd.quantity).fill(undefined);
67609
- const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
67610
- this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
67611
- break;
67612
- }
67613
67729
  case "RESIZE_COLUMNS_ROWS":
67614
67730
  {
67615
67731
  const sheetId = cmd.sheetId;
@@ -74158,6 +74274,14 @@ class GridSelectionPlugin extends UIPlugin {
74158
74274
  const isBasedBefore = cmd.base < start;
74159
74275
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
74160
74276
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
74277
+ const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74278
+ const originalSize = Object.fromEntries(toRemove.map((element) => {
74279
+ const size = isCol
74280
+ ? this.getters.getColSize(cmd.sheetId, element)
74281
+ : this.getters.getUserRowSize(cmd.sheetId, element);
74282
+ const isDefaultCol = isCol && size === DEFAULT_CELL_WIDTH;
74283
+ return [element, isDefaultCol ? undefined : size];
74284
+ }));
74161
74285
  const target = [
74162
74286
  {
74163
74287
  left: isCol ? start + deltaCol : 0,
@@ -74188,13 +74312,12 @@ class GridSelectionPlugin extends UIPlugin {
74188
74312
  const col = selection.left;
74189
74313
  const row = selection.top;
74190
74314
  this.setSelectionMixin({ zone: selection, cell: { col, row } }, [selection]);
74191
- const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74192
74315
  let currentIndex = isBasedBefore ? cmd.base : cmd.base + 1;
74193
74316
  const resizingGroups = {};
74194
74317
  for (const element of toRemove) {
74195
- const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
74318
+ const size = originalSize[element];
74196
74319
  const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
74197
- if (size !== currentSize) {
74320
+ if (size && size !== currentSize) {
74198
74321
  resizingGroups[size] ??= [];
74199
74322
  resizingGroups[size].push(currentIndex);
74200
74323
  currentIndex += 1;
@@ -75620,6 +75743,7 @@ const coreViewsPluginRegistry = new Registry()
75620
75743
 
75621
75744
  autoCompleteProviders.add("dataValidation", {
75622
75745
  displayAllOnInitialContent: true,
75746
+ canBeToggled: false,
75623
75747
  getProposals(tokenAtCursor, content) {
75624
75748
  if (isFormula(content)) {
75625
75749
  return [];
@@ -75635,25 +75759,30 @@ autoCompleteProviders.add("dataValidation", {
75635
75759
  }
75636
75760
  const sheetId = this.composer.currentEditedCell.sheetId;
75637
75761
  const values = rule.criterion.type === "isValueInRange"
75638
- ? Array.from(new Set(this.getters.getDataValidationRangeValues(sheetId, rule.criterion)))
75639
- : rule.criterion.values;
75762
+ ? this.getters.getDataValidationRangeValues(sheetId, rule.criterion)
75763
+ : rule.criterion.values.map((value) => ({ label: value, value }));
75640
75764
  const isChip = rule.criterion.displayStyle === "chip";
75641
75765
  if (!isChip) {
75642
- return values.map((value) => ({ text: value }));
75766
+ return values.map((value) => ({
75767
+ text: value.value,
75768
+ fuzzySearchKey: value.label,
75769
+ htmlContent: [{ value: value.label }],
75770
+ }));
75643
75771
  }
75644
75772
  const colors = rule.criterion.colors;
75645
75773
  return values.map((value) => {
75646
- const color = colors?.[value];
75774
+ const color = colors?.[value.value];
75647
75775
  return {
75648
- text: value,
75776
+ text: value.value,
75649
75777
  htmlContent: [
75650
75778
  {
75651
- value,
75779
+ value: value.label,
75652
75780
  color: color ? chipTextColor(color) : undefined,
75653
75781
  backgroundColor: color || GRAY_200,
75654
75782
  classes: ["badge rounded-pill fs-6 fw-normal w-100 mt-1 text-start"],
75655
75783
  },
75656
75784
  ],
75785
+ fuzzySearchKey: value.label,
75657
75786
  };
75658
75787
  });
75659
75788
  },
@@ -77185,14 +77314,12 @@ class BottomBarSheet extends Component {
77185
77314
  this.editionState = "initializing";
77186
77315
  }
77187
77316
  stopEdition() {
77188
- const input = this.sheetNameRef.el;
77189
- if (!this.state.isEditing || !input)
77317
+ if (!this.state.isEditing || !this.sheetNameRef.el)
77190
77318
  return;
77191
77319
  this.state.isEditing = false;
77192
77320
  this.editionState = "initializing";
77193
- input.blur();
77321
+ this.sheetNameRef.el.blur();
77194
77322
  const inputValue = this.getInputContent() || "";
77195
- input.innerText = inputValue;
77196
77323
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
77197
77324
  }
77198
77325
  cancelEdition() {
@@ -81610,6 +81737,9 @@ function createChart(chart, chartSheetIndex, data) {
81610
81737
  case "combo":
81611
81738
  plot = addComboChart(chart.data);
81612
81739
  break;
81740
+ case "pyramid":
81741
+ plot = addPyramidChart(chart.data);
81742
+ break;
81613
81743
  case "line":
81614
81744
  plot = addLineChart(chart.data);
81615
81745
  break;
@@ -81617,12 +81747,12 @@ function createChart(chart, chartSheetIndex, data) {
81617
81747
  plot = addScatterChart(chart.data);
81618
81748
  break;
81619
81749
  case "pie":
81620
- plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
81750
+ plot = addDoughnutChart(chart.data, chartSheetIndex, data);
81621
81751
  break;
81622
81752
  case "radar":
81623
81753
  plot = addRadarChart(chart.data);
81624
81754
  }
81625
- let position = "t";
81755
+ let position = "none";
81626
81756
  switch (chart.data.legendPosition) {
81627
81757
  case "bottom":
81628
81758
  position = "b";
@@ -81652,7 +81782,7 @@ function createChart(chart, chartSheetIndex, data) {
81652
81782
  ${plot}
81653
81783
  ${shapeProperty({ backgroundColor: chart.data.backgroundColor })}
81654
81784
  </c:plotArea>
81655
- ${addLegend(position, fontColor)}
81785
+ ${position !== "none" ? addLegend(position, fontColor) : ""}
81656
81786
  </c:chart>
81657
81787
  </c:chartSpace>
81658
81788
  `;
@@ -81810,6 +81940,7 @@ function addBarChart(chart) {
81810
81940
  //
81811
81941
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
81812
81942
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
81943
+ const chartDirection = chart.horizontal ? "bar" : "col";
81813
81944
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
81814
81945
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
81815
81946
  const leftDataSetsNodes = [];
@@ -81846,7 +81977,7 @@ function addBarChart(chart) {
81846
81977
  ${leftDataSetsNodes.length
81847
81978
  ? escapeXml /*xml*/ `
81848
81979
  <c:barChart>
81849
- <c:barDir val="col"/>
81980
+ <c:barDir val="${chartDirection}"/>
81850
81981
  <c:grouping val="${grouping}"/>
81851
81982
  <c:overlap val="${overlap}"/>
81852
81983
  <c:gapWidth val="70"/>
@@ -81856,8 +81987,12 @@ function addBarChart(chart) {
81856
81987
  <c:axId val="${catAxId}" />
81857
81988
  <c:axId val="${valAxId}" />
81858
81989
  </c:barChart>
81859
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
81860
- ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
81990
+ ${chartDirection === "col"
81991
+ ? addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)
81992
+ : addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, undefined, "maxMin")}
81993
+ ${chartDirection === "col"
81994
+ ? addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)
81995
+ : addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, undefined, undefined, "max")}
81861
81996
  `
81862
81997
  : ""}
81863
81998
  ${rightDataSetsNodes.length
@@ -81983,7 +82118,7 @@ function addComboChart(chart) {
81983
82118
  : ""}
81984
82119
  ${!useRightAxisForBarSerie || leftDataSetsNodes.length
81985
82120
  ? escapeXml /*xml*/ `
81986
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
82121
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, 0)}
81987
82122
  ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
81988
82123
  `
81989
82124
  : ""}
@@ -81995,6 +82130,94 @@ function addComboChart(chart) {
81995
82130
  : ""}
81996
82131
  `;
81997
82132
  }
82133
+ function addPyramidChart(chart) {
82134
+ const dataSets = chart.dataSets;
82135
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
82136
+ const colors = new ColorGenerator(dataSets.length, dataSetsColors);
82137
+ const leftDataSet = dataSets[0];
82138
+ const rightDataSet = dataSets[1];
82139
+ const firstColor = toXlsxHexColor(colors.next());
82140
+ const secondColor = toXlsxHexColor(colors.next());
82141
+ const { maxValue, majorUnit } = getPyramidChartHorizontalAxisConfig(chart.maxValue);
82142
+ const labelRangeEl = chart.labelRange
82143
+ ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>`
82144
+ : "";
82145
+ const leftBarDataSetNode = escapeXml /*xml*/ `
82146
+ <c:ser>
82147
+ <c:idx val="0"/>
82148
+ <c:order val="0"/>
82149
+ <c:invertIfNegative val="0" />
82150
+ ${extractDataSetLabel(leftDataSet.label)}
82151
+ ${shapeProperty({
82152
+ backgroundColor: firstColor,
82153
+ line: { color: firstColor },
82154
+ })}
82155
+ ${labelRangeEl}
82156
+ <!-- x-coordinate values -->
82157
+ <c:val>
82158
+ ${numberRef(leftDataSet.range)}
82159
+ </c:val>
82160
+ </c:ser>
82161
+ `;
82162
+ const rightBarDataSetNode = escapeXml /*xml*/ `
82163
+ <c:ser>
82164
+ <c:idx val="1"/>
82165
+ <c:order val="1"/>
82166
+ <c:invertIfNegative val="0" />
82167
+ ${extractDataSetLabel(rightDataSet.label)}
82168
+ ${shapeProperty({
82169
+ backgroundColor: secondColor,
82170
+ line: { color: secondColor },
82171
+ })}
82172
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
82173
+ <!-- x-coordinate values -->
82174
+ <c:val>
82175
+ ${numberRef(rightDataSet.range)}
82176
+ </c:val>
82177
+ </c:ser>
82178
+ `;
82179
+ return escapeXml /*xml*/ `
82180
+ <c:barChart>
82181
+ <c:barDir val="bar"/>
82182
+ <c:grouping val="clustered"/>
82183
+ <c:varyColors val="0" />
82184
+ ${leftBarDataSetNode}
82185
+ <c:gapWidth val="50" />
82186
+ <c:axId val="${catAxId}" />
82187
+ <c:axId val="${valAxId}" />
82188
+ </c:barChart>
82189
+ <c:barChart>
82190
+ <c:barDir val="bar"/>
82191
+ <c:grouping val="clustered"/>
82192
+ <c:varyColors val="0" />
82193
+ ${rightBarDataSetNode}
82194
+ <c:gapWidth val="50" />
82195
+ <c:axId val="${secondaryCatAxId}" />
82196
+ <c:axId val="${secondaryValAxId}" />
82197
+ </c:barChart>
82198
+ ${addAx("r", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, 0, "maxMin", "autoZero", "high")}
82199
+ ${addAx("b", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, 0, "maxMin", "max", "nextTo", maxValue, majorUnit, "#0;#0")}
82200
+ ${addAx("t", "c:valAx", secondaryValAxId, secondaryCatAxId, undefined, chart.fontColor, 1)}
82201
+ ${addAx("l", "c:catAx", secondaryCatAxId, secondaryValAxId, undefined, chart.fontColor, 1, "maxMin")}
82202
+ `;
82203
+ }
82204
+ function getPyramidChartHorizontalAxisConfig(maxValue) {
82205
+ const adjustMaxToDivisibleBy = (value, divisor) => {
82206
+ let adjusted = Math.ceil(value);
82207
+ while (adjusted % divisor !== 0) {
82208
+ adjusted++;
82209
+ }
82210
+ return adjusted;
82211
+ };
82212
+ const tickCount = 4;
82213
+ const interval = tickCount - 1;
82214
+ const adjustedMax = adjustMaxToDivisibleBy(maxValue, interval);
82215
+ const majorUnit = adjustedMax / interval;
82216
+ return {
82217
+ maxValue: adjustedMax,
82218
+ majorUnit,
82219
+ };
82220
+ }
81998
82221
  function addLineChart(chart) {
81999
82222
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
82000
82223
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
@@ -82187,7 +82410,7 @@ function addRadarChart(chart) {
82187
82410
  `}
82188
82411
  `;
82189
82412
  }
82190
- function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
82413
+ function addDoughnutChart(chart, chartSheetIndex, data) {
82191
82414
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
82192
82415
  const colors = new ColorGenerator(maxLength);
82193
82416
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
@@ -82225,7 +82448,7 @@ function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSiz
82225
82448
  return escapeXml /*xml*/ `
82226
82449
  <c:doughnutChart>
82227
82450
  <c:varyColors val="1" />
82228
- <c:holeSize val="${holeSize}" />
82451
+ <c:holeSize val="${chart.pieHolePercentage ?? (chart.isDoughnut ? DEFAULT_DOUGHNUT_CHART_HOLE_SIZE : 0)}" />
82229
82452
  ${insertDataLabels()}
82230
82453
  ${joinXmlNodes(dataSetsNodes)}
82231
82454
  </c:doughnutChart>
@@ -82244,25 +82467,35 @@ function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {
82244
82467
  </dLbls>
82245
82468
  `;
82246
82469
  }
82247
- function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
82470
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0, orientation = "minMax", crossPosition, tickLabelPosition = "nextTo", maxValue, majorUnit, format = "General") {
82248
82471
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
82249
82472
  // I.e. x-axis, will reference y-axis and vice-versa.
82250
82473
  const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
82251
82474
  const fontSize = title?.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE;
82475
+ const crossBetweenEl = axisName === "c:valAx" ? escapeXml `<c:crossBetween val="between" />` : "";
82476
+ const maxValueEl = maxValue ? escapeXml `<c:max val="${maxValue}" />` : "";
82477
+ const minValueEl = maxValue ? escapeXml `<c:min val="${-maxValue}" />` : "";
82478
+ const majorUnitEl = majorUnit ? escapeXml `<c:majorUnit val="${majorUnit}" />` : "";
82252
82479
  return escapeXml /*xml*/ `
82253
82480
  <${axisName}>
82254
82481
  <c:axId val="${axId}"/>
82255
82482
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
82256
- <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
82483
+ <c:crosses val="${crossPosition || (position === "b" || position === "l" ? "min" : "max")}"/>
82484
+ <c:auto val="1"/>
82485
+ ${crossBetweenEl}
82257
82486
  <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
82258
82487
  <c:scaling>
82259
- <c:orientation val="minMax" />
82488
+ <c:orientation val="${orientation}" />
82489
+ ${maxValueEl}
82490
+ ${minValueEl}
82260
82491
  </c:scaling>
82492
+ ${majorUnitEl}
82261
82493
  <c:axPos val="${position}" />
82494
+ <c:tickLblPos val="${tickLabelPosition}" />
82262
82495
  ${insertMajorGridLines()}
82263
82496
  <c:majorTickMark val="out" />
82264
82497
  <c:minorTickMark val="none" />
82265
- <c:numFmt formatCode="General" sourceLinked="1" />
82498
+ <c:numFmt formatCode="${format}" sourceLinked="${format === "General" ? "1" : "0"}" />
82266
82499
  <c:title>
82267
82500
  ${insertText(title?.text ?? "", color, fontSize, title)}
82268
82501
  </c:title>
@@ -82457,7 +82690,10 @@ function addConditionalFormatting(dxfs, conditionalFormats) {
82457
82690
  function addCellIsRule(cf, rule, dxfs) {
82458
82691
  const ruleAttributes = commonCfAttributes(cf);
82459
82692
  const operator = convertOperator(rule.operator);
82460
- ruleAttributes.push(...cellRuleTypeAttributes(rule), ["operator", operator]);
82693
+ ruleAttributes.push(...cellRuleTypeAttributes(rule));
82694
+ if (operator.length) {
82695
+ ruleAttributes.push(["operator", operator]);
82696
+ }
82461
82697
  const formulas = cellRuleFormula(cf.ranges, rule).map((formula) => escapeXml /*xml*/ `<formula>${formula}</formula>`);
82462
82698
  const dxf = {
82463
82699
  font: {
@@ -82503,6 +82739,8 @@ function cellRuleFormula(ranges, rule) {
82503
82739
  case "isLessThan":
82504
82740
  case "isLessOrEqualTo":
82505
82741
  return [values[0]];
82742
+ case "customFormula":
82743
+ return values[0].startsWith("=") ? [values[0].slice(1)] : [values[0]];
82506
82744
  case "isBetween":
82507
82745
  case "isNotBetween":
82508
82746
  return [values[0], values[1]];
@@ -82531,6 +82769,8 @@ function cellRuleTypeAttributes(rule) {
82531
82769
  case "isBetween":
82532
82770
  case "isNotBetween":
82533
82771
  return [["type", "cellIs"]];
82772
+ case "customFormula":
82773
+ return [["type", "expression"]];
82534
82774
  }
82535
82775
  }
82536
82776
  function addDataBarRule(cf, rule) {
@@ -84516,6 +84756,8 @@ const components = {
84516
84756
  WaterfallChartDesignPanel,
84517
84757
  ComboChartDesignPanel,
84518
84758
  FunnelChartDesignPanel,
84759
+ SunburstChartDesignPanel,
84760
+ TreeMapChartDesignPanel,
84519
84761
  ChartTypePicker,
84520
84762
  FigureComponent,
84521
84763
  MenuPopover,
@@ -84545,6 +84787,7 @@ const hooks = {
84545
84787
  };
84546
84788
  const stores = {
84547
84789
  useStoreProvider,
84790
+ ChartDashboardMenuStore,
84548
84791
  DependencyContainer,
84549
84792
  CellPopoverStore,
84550
84793
  ComposerFocusStore,
@@ -84583,6 +84826,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
84583
84826
  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 };
84584
84827
 
84585
84828
 
84586
- __info__.version = "18.5.0-alpha.2";
84587
- __info__.date = "2025-07-11T11:13:53.317Z";
84588
- __info__.hash = "6d42178";
84829
+ __info__.version = "18.5.0-alpha.4";
84830
+ __info__.date = "2025-07-30T11:23:18.805Z";
84831
+ __info__.hash = "34a4ab3";