@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
  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":
@@ -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;
19299
+ }
19300
+ const zone = this.getters.getRangeFromSheetXC(this.getters.getActiveSheetId(), cellReference.value).zone;
19301
+ if (zone.top === zone.bottom) {
19302
+ return zone.top + 1;
19278
19303
  }
19279
- return row + 1;
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,
@@ -32606,6 +32644,9 @@ class AbstractComposerStore extends SpreadsheetStore {
32606
32644
  get isAutoCompleteDisplayed() {
32607
32645
  return !!this.autoComplete.provider;
32608
32646
  }
32647
+ get canBeToggled() {
32648
+ return this.autoComplete.provider?.canBeToggled ?? true;
32649
+ }
32609
32650
  cycleReferences() {
32610
32651
  const locale = this.getters.getLocale();
32611
32652
  const updated = cycleFixedReference(this.composerSelection, this._currentContent, locale);
@@ -33135,6 +33176,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33135
33176
  proposals,
33136
33177
  selectProposal: provider.selectProposal,
33137
33178
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33179
+ canBeToggled: provider.canBeToggled,
33138
33180
  };
33139
33181
  }
33140
33182
  if (exactMatch && this._currentContent !== this.initialContent) {
@@ -33157,6 +33199,7 @@ class AbstractComposerStore extends SpreadsheetStore {
33157
33199
  proposals,
33158
33200
  selectProposal: provider.selectProposal,
33159
33201
  autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
33202
+ canBeToggled: provider.canBeToggled,
33160
33203
  };
33161
33204
  }
33162
33205
  }
@@ -33727,9 +33770,13 @@ class Composer extends Component {
33727
33770
  }
33728
33771
  }
33729
33772
  closeAssistant() {
33773
+ if (!this.props.composerStore.canBeToggled)
33774
+ return;
33730
33775
  this.assistant.forcedClosed = true;
33731
33776
  }
33732
33777
  openAssistant() {
33778
+ if (!this.props.composerStore.canBeToggled)
33779
+ return;
33733
33780
  this.assistant.forcedClosed = false;
33734
33781
  }
33735
33782
  onWheel(event) {
@@ -33919,7 +33966,7 @@ class Composer extends Component {
33919
33966
  return [...new Set(argsToFocus)];
33920
33967
  }
33921
33968
  autoComplete(value) {
33922
- if (!value || this.assistant.forcedClosed) {
33969
+ if (!value || (this.assistant.forcedClosed && this.props.composerStore.canBeToggled)) {
33923
33970
  return;
33924
33971
  }
33925
33972
  this.props.composerStore.insertAutoCompleteValue(value);
@@ -37497,7 +37544,7 @@ const CF_OPERATOR_TYPE_CONVERSION_MAP = {
37497
37544
  /** Conversion map CF types in XLSX <=> Cf types in o_spreadsheet */
37498
37545
  const CF_TYPE_CONVERSION_MAP = {
37499
37546
  aboveAverage: undefined,
37500
- expression: undefined,
37547
+ expression: "customFormula",
37501
37548
  cellIs: undefined, // exist but isn't an operator in o_spreadsheet
37502
37549
  colorScale: undefined, // exist but isn't an operator in o_spreadsheet
37503
37550
  dataBar: undefined,
@@ -38101,7 +38148,6 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38101
38148
  case "containsErrors":
38102
38149
  case "notContainsErrors":
38103
38150
  case "duplicateValues":
38104
- case "expression":
38105
38151
  case "top10":
38106
38152
  case "uniqueValues":
38107
38153
  case "timePeriod":
@@ -38134,6 +38180,12 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
38134
38180
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
38135
38181
  values.push(rule.text);
38136
38182
  break;
38183
+ case "expression":
38184
+ if (!rule.formula?.length)
38185
+ continue;
38186
+ operator = CF_TYPE_CONVERSION_MAP[rule.type];
38187
+ values.push(`=${rule.formula[0]}`);
38188
+ break;
38137
38189
  case "containsBlanks":
38138
38190
  case "notContainsBlanks":
38139
38191
  operator = CF_TYPE_CONVERSION_MAP[rule.type];
@@ -38363,6 +38415,8 @@ function convertOperator(operator) {
38363
38415
  return "equal";
38364
38416
  case "isNotEqual":
38365
38417
  return "notEqual";
38418
+ case "customFormula":
38419
+ return "";
38366
38420
  }
38367
38421
  }
38368
38422
  // -------------------------------------
@@ -38698,6 +38752,9 @@ function convertChartData(chartData) {
38698
38752
  aggregated: false,
38699
38753
  cumulative: chartData.cumulative || false,
38700
38754
  labelsAsText: false,
38755
+ horizontal: chartData.horizontal,
38756
+ isDoughnut: chartData.isDoughnut,
38757
+ pieHolePercentage: chartData.pieHolePercentage,
38701
38758
  };
38702
38759
  try {
38703
38760
  const ChartClass = chartRegistry.get(chartData.type);
@@ -39974,6 +40031,12 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39974
40031
  const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
39975
40032
  default: "clustered",
39976
40033
  }).asString();
40034
+ const chartDirection = this.extractChildAttr(rootChartElement, "c:barDir", "val", {
40035
+ default: "col",
40036
+ }).asString();
40037
+ const chartHoleSize = this.extractChildAttr(rootChartElement, "c:holeSize", "val", {
40038
+ default: "0",
40039
+ }).asNum();
39977
40040
  return {
39978
40041
  title: { text: chartTitle },
39979
40042
  type: CHART_TYPE_CONVERSION_MAP[chartType],
@@ -39987,6 +40050,9 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
39987
40050
  }).asString()],
39988
40051
  stacked: barChartGrouping === "stacked",
39989
40052
  fontColor: "000000",
40053
+ horizontal: chartDirection === "bar",
40054
+ isDoughnut: chartHoleSize > 0,
40055
+ pieHolePercentage: chartHoleSize,
39990
40056
  };
39991
40057
  })[0];
39992
40058
  }
@@ -50977,6 +51043,9 @@ class PieChartDesignPanel extends Component {
50977
51043
  pieHolePercentage,
50978
51044
  });
50979
51045
  }
51046
+ get defaultHoleSize() {
51047
+ return DEFAULT_DOUGHNUT_CHART_HOLE_SIZE;
51048
+ }
50980
51049
  }
50981
51050
 
50982
51051
  class RadarChartDesignPanel extends Component {
@@ -55700,9 +55769,15 @@ class SpreadsheetPivot {
55700
55769
  return domain.reduce((current, acc) => this.filterDataEntriesFromDomainNode(current, acc), dataEntries);
55701
55770
  }
55702
55771
  filterDataEntriesFromDomainNode(dataEntries, domain) {
55703
- const { field, value } = domain;
55772
+ const { field, value, type } = domain;
55704
55773
  const { nameWithGranularity } = this.getDimension(field);
55705
- return dataEntries.filter((entry) => entry[nameWithGranularity]?.value === value);
55774
+ return dataEntries.filter((entry) => {
55775
+ const cellValue = entry[nameWithGranularity]?.value;
55776
+ if (type === "char") {
55777
+ return String(cellValue) === String(value);
55778
+ }
55779
+ return cellValue === value;
55780
+ });
55706
55781
  }
55707
55782
  getDimension(nameWithGranularity) {
55708
55783
  return this.definition.getDimension(nameWithGranularity);
@@ -65654,7 +65729,7 @@ class SpreadingRelation {
65654
65729
  const EMPTY_ARRAY = [];
65655
65730
 
65656
65731
  const MAX_ITERATION = 30;
65657
- const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell(new CircularDependencyError()));
65732
+ const ERROR_CYCLE_CELL = Object.freeze(createEvaluatedCell({ ...new CircularDependencyError(), origin: undefined }));
65658
65733
  const EMPTY_CELL = Object.freeze(createEvaluatedCell({ value: null }));
65659
65734
  class Evaluator {
65660
65735
  context;
@@ -65888,11 +65963,12 @@ class Evaluator {
65888
65963
  this.cellsBeingComputed.add(cellId);
65889
65964
  return cell.isFormula
65890
65965
  ? this.computeFormulaCell(position, cell)
65891
- : evaluateLiteral(cell, localeFormat);
65966
+ : evaluateLiteral(cell, localeFormat, position);
65892
65967
  }
65893
65968
  catch (e) {
65894
65969
  e.value = e?.value || CellErrorType.GenericError;
65895
65970
  e.message = e?.message || implementationErrorMessage;
65971
+ e.origin = position;
65896
65972
  return createEvaluatedCell(e);
65897
65973
  }
65898
65974
  finally {
@@ -65909,7 +65985,7 @@ class Evaluator {
65909
65985
  computeFormulaCell(formulaPosition, cellData) {
65910
65986
  const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, formulaPosition.sheetId, this.buildSafeGetSymbolValue(), formulaPosition);
65911
65987
  if (!isMatrix(formulaReturn)) {
65912
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData);
65988
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData, formulaPosition);
65913
65989
  if (evaluatedCell.type === CellValueType.error) {
65914
65990
  evaluatedCell.errorOriginPosition = formulaReturn.errorOriginPosition ?? formulaPosition;
65915
65991
  }
@@ -65985,7 +66061,7 @@ class Evaluator {
65985
66061
  const spreadValues = (i, j) => {
65986
66062
  const position = { sheetId, col: i + col, row: j + row };
65987
66063
  const cell = this.getters.getCell(position);
65988
- const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
66064
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell, position);
65989
66065
  if (evaluatedCell.type === CellValueType.error) {
65990
66066
  evaluatedCell.errorOriginPosition = matrixResult[i][j].errorOriginPosition ?? position;
65991
66067
  }
@@ -66658,7 +66734,7 @@ class EvaluationChartPlugin extends CoreViewPlugin {
66658
66734
  continue;
66659
66735
  }
66660
66736
  const figureId = figure.id;
66661
- const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel();
66737
+ const figureData = this.getters.getChart(figureId)?.getDefinitionForExcel(this.getters);
66662
66738
  if (figureData) {
66663
66739
  figures.push({
66664
66740
  ...figure,
@@ -67571,6 +67647,23 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67571
67647
  static getters = ["getRowSize", "getHeaderSize", "getMaxAnchorOffset"];
67572
67648
  tallestCellInRow = {};
67573
67649
  ctx = document.createElement("canvas").getContext("2d");
67650
+ beforeHandle(cmd) {
67651
+ switch (cmd.type) {
67652
+ // Ensure rows are updated before "UPDATE_CELL" is dispatched from cell plugin.
67653
+ // "UPDATE_CELL" uses the Sheet core plugin to access row data.
67654
+ // If "ADD_COLUMNS_ROWS" has not been processed yet by header_sizes_ui,
67655
+ // size updates may apply to incorrect (pre-insert) rows.
67656
+ case "ADD_COLUMNS_ROWS":
67657
+ if (cmd.dimension === "COL") {
67658
+ return;
67659
+ }
67660
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
67661
+ const newCells = Array(cmd.quantity).fill(undefined);
67662
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
67663
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
67664
+ break;
67665
+ }
67666
+ }
67574
67667
  handle(cmd) {
67575
67668
  switch (cmd.type) {
67576
67669
  case "START":
@@ -67600,16 +67693,6 @@ class HeaderSizeUIPlugin extends CoreViewPlugin {
67600
67693
  this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
67601
67694
  break;
67602
67695
  }
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
67696
  case "RESIZE_COLUMNS_ROWS":
67614
67697
  {
67615
67698
  const sheetId = cmd.sheetId;
@@ -74158,6 +74241,14 @@ class GridSelectionPlugin extends UIPlugin {
74158
74241
  const isBasedBefore = cmd.base < start;
74159
74242
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
74160
74243
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
74244
+ const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74245
+ const originalSize = Object.fromEntries(toRemove.map((element) => {
74246
+ const size = isCol
74247
+ ? this.getters.getColSize(cmd.sheetId, element)
74248
+ : this.getters.getUserRowSize(cmd.sheetId, element);
74249
+ const isDefaultCol = isCol && size === DEFAULT_CELL_WIDTH;
74250
+ return [element, isDefaultCol ? undefined : size];
74251
+ }));
74161
74252
  const target = [
74162
74253
  {
74163
74254
  left: isCol ? start + deltaCol : 0,
@@ -74188,13 +74279,12 @@ class GridSelectionPlugin extends UIPlugin {
74188
74279
  const col = selection.left;
74189
74280
  const row = selection.top;
74190
74281
  this.setSelectionMixin({ zone: selection, cell: { col, row } }, [selection]);
74191
- const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
74192
74282
  let currentIndex = isBasedBefore ? cmd.base : cmd.base + 1;
74193
74283
  const resizingGroups = {};
74194
74284
  for (const element of toRemove) {
74195
- const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
74285
+ const size = originalSize[element];
74196
74286
  const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
74197
- if (size !== currentSize) {
74287
+ if (size && size !== currentSize) {
74198
74288
  resizingGroups[size] ??= [];
74199
74289
  resizingGroups[size].push(currentIndex);
74200
74290
  currentIndex += 1;
@@ -75620,6 +75710,7 @@ const coreViewsPluginRegistry = new Registry()
75620
75710
 
75621
75711
  autoCompleteProviders.add("dataValidation", {
75622
75712
  displayAllOnInitialContent: true,
75713
+ canBeToggled: false,
75623
75714
  getProposals(tokenAtCursor, content) {
75624
75715
  if (isFormula(content)) {
75625
75716
  return [];
@@ -77185,14 +77276,12 @@ class BottomBarSheet extends Component {
77185
77276
  this.editionState = "initializing";
77186
77277
  }
77187
77278
  stopEdition() {
77188
- const input = this.sheetNameRef.el;
77189
- if (!this.state.isEditing || !input)
77279
+ if (!this.state.isEditing || !this.sheetNameRef.el)
77190
77280
  return;
77191
77281
  this.state.isEditing = false;
77192
77282
  this.editionState = "initializing";
77193
- input.blur();
77283
+ this.sheetNameRef.el.blur();
77194
77284
  const inputValue = this.getInputContent() || "";
77195
- input.innerText = inputValue;
77196
77285
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
77197
77286
  }
77198
77287
  cancelEdition() {
@@ -81610,6 +81699,9 @@ function createChart(chart, chartSheetIndex, data) {
81610
81699
  case "combo":
81611
81700
  plot = addComboChart(chart.data);
81612
81701
  break;
81702
+ case "pyramid":
81703
+ plot = addPyramidChart(chart.data);
81704
+ break;
81613
81705
  case "line":
81614
81706
  plot = addLineChart(chart.data);
81615
81707
  break;
@@ -81617,12 +81709,12 @@ function createChart(chart, chartSheetIndex, data) {
81617
81709
  plot = addScatterChart(chart.data);
81618
81710
  break;
81619
81711
  case "pie":
81620
- plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
81712
+ plot = addDoughnutChart(chart.data, chartSheetIndex, data);
81621
81713
  break;
81622
81714
  case "radar":
81623
81715
  plot = addRadarChart(chart.data);
81624
81716
  }
81625
- let position = "t";
81717
+ let position = "none";
81626
81718
  switch (chart.data.legendPosition) {
81627
81719
  case "bottom":
81628
81720
  position = "b";
@@ -81652,7 +81744,7 @@ function createChart(chart, chartSheetIndex, data) {
81652
81744
  ${plot}
81653
81745
  ${shapeProperty({ backgroundColor: chart.data.backgroundColor })}
81654
81746
  </c:plotArea>
81655
- ${addLegend(position, fontColor)}
81747
+ ${position !== "none" ? addLegend(position, fontColor) : ""}
81656
81748
  </c:chart>
81657
81749
  </c:chartSpace>
81658
81750
  `;
@@ -81810,6 +81902,7 @@ function addBarChart(chart) {
81810
81902
  //
81811
81903
  // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
81812
81904
  // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
81905
+ const chartDirection = chart.horizontal ? "bar" : "col";
81813
81906
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
81814
81907
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
81815
81908
  const leftDataSetsNodes = [];
@@ -81846,7 +81939,7 @@ function addBarChart(chart) {
81846
81939
  ${leftDataSetsNodes.length
81847
81940
  ? escapeXml /*xml*/ `
81848
81941
  <c:barChart>
81849
- <c:barDir val="col"/>
81942
+ <c:barDir val="${chartDirection}"/>
81850
81943
  <c:grouping val="${grouping}"/>
81851
81944
  <c:overlap val="${overlap}"/>
81852
81945
  <c:gapWidth val="70"/>
@@ -81856,8 +81949,12 @@ function addBarChart(chart) {
81856
81949
  <c:axId val="${catAxId}" />
81857
81950
  <c:axId val="${valAxId}" />
81858
81951
  </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)}
81952
+ ${chartDirection === "col"
81953
+ ? addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)
81954
+ : addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, undefined, "maxMin")}
81955
+ ${chartDirection === "col"
81956
+ ? addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)
81957
+ : addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, undefined, undefined, "max")}
81861
81958
  `
81862
81959
  : ""}
81863
81960
  ${rightDataSetsNodes.length
@@ -81983,7 +82080,7 @@ function addComboChart(chart) {
81983
82080
  : ""}
81984
82081
  ${!useRightAxisForBarSerie || leftDataSetsNodes.length
81985
82082
  ? escapeXml /*xml*/ `
81986
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
82083
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, 0)}
81987
82084
  ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
81988
82085
  `
81989
82086
  : ""}
@@ -81995,6 +82092,94 @@ function addComboChart(chart) {
81995
82092
  : ""}
81996
82093
  `;
81997
82094
  }
82095
+ function addPyramidChart(chart) {
82096
+ const dataSets = chart.dataSets;
82097
+ const dataSetsColors = dataSets.map((ds) => ds.backgroundColor ?? "");
82098
+ const colors = new ColorGenerator(dataSets.length, dataSetsColors);
82099
+ const leftDataSet = dataSets[0];
82100
+ const rightDataSet = dataSets[1];
82101
+ const firstColor = toXlsxHexColor(colors.next());
82102
+ const secondColor = toXlsxHexColor(colors.next());
82103
+ const { maxValue, majorUnit } = getPyramidChartHorizontalAxisConfig(chart.maxValue);
82104
+ const labelRangeEl = chart.labelRange
82105
+ ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>`
82106
+ : "";
82107
+ const leftBarDataSetNode = escapeXml /*xml*/ `
82108
+ <c:ser>
82109
+ <c:idx val="0"/>
82110
+ <c:order val="0"/>
82111
+ <c:invertIfNegative val="0" />
82112
+ ${extractDataSetLabel(leftDataSet.label)}
82113
+ ${shapeProperty({
82114
+ backgroundColor: firstColor,
82115
+ line: { color: firstColor },
82116
+ })}
82117
+ ${labelRangeEl}
82118
+ <!-- x-coordinate values -->
82119
+ <c:val>
82120
+ ${numberRef(leftDataSet.range)}
82121
+ </c:val>
82122
+ </c:ser>
82123
+ `;
82124
+ const rightBarDataSetNode = escapeXml /*xml*/ `
82125
+ <c:ser>
82126
+ <c:idx val="1"/>
82127
+ <c:order val="1"/>
82128
+ <c:invertIfNegative val="0" />
82129
+ ${extractDataSetLabel(rightDataSet.label)}
82130
+ ${shapeProperty({
82131
+ backgroundColor: secondColor,
82132
+ line: { color: secondColor },
82133
+ })}
82134
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""}
82135
+ <!-- x-coordinate values -->
82136
+ <c:val>
82137
+ ${numberRef(rightDataSet.range)}
82138
+ </c:val>
82139
+ </c:ser>
82140
+ `;
82141
+ return escapeXml /*xml*/ `
82142
+ <c:barChart>
82143
+ <c:barDir val="bar"/>
82144
+ <c:grouping val="clustered"/>
82145
+ <c:varyColors val="0" />
82146
+ ${leftBarDataSetNode}
82147
+ <c:gapWidth val="50" />
82148
+ <c:axId val="${catAxId}" />
82149
+ <c:axId val="${valAxId}" />
82150
+ </c:barChart>
82151
+ <c:barChart>
82152
+ <c:barDir val="bar"/>
82153
+ <c:grouping val="clustered"/>
82154
+ <c:varyColors val="0" />
82155
+ ${rightBarDataSetNode}
82156
+ <c:gapWidth val="50" />
82157
+ <c:axId val="${secondaryCatAxId}" />
82158
+ <c:axId val="${secondaryValAxId}" />
82159
+ </c:barChart>
82160
+ ${addAx("r", "c:catAx", catAxId, valAxId, chart.axesDesign?.y?.title, chart.fontColor, 0, "maxMin", "autoZero", "high")}
82161
+ ${addAx("b", "c:valAx", valAxId, catAxId, chart.axesDesign?.x?.title, chart.fontColor, 0, "maxMin", "max", "nextTo", maxValue, majorUnit, "#0;#0")}
82162
+ ${addAx("t", "c:valAx", secondaryValAxId, secondaryCatAxId, undefined, chart.fontColor, 1)}
82163
+ ${addAx("l", "c:catAx", secondaryCatAxId, secondaryValAxId, undefined, chart.fontColor, 1, "maxMin")}
82164
+ `;
82165
+ }
82166
+ function getPyramidChartHorizontalAxisConfig(maxValue) {
82167
+ const adjustMaxToDivisibleBy = (value, divisor) => {
82168
+ let adjusted = Math.ceil(value);
82169
+ while (adjusted % divisor !== 0) {
82170
+ adjusted++;
82171
+ }
82172
+ return adjusted;
82173
+ };
82174
+ const tickCount = 4;
82175
+ const interval = tickCount - 1;
82176
+ const adjustedMax = adjustMaxToDivisibleBy(maxValue, interval);
82177
+ const majorUnit = adjustedMax / interval;
82178
+ return {
82179
+ maxValue: adjustedMax,
82180
+ majorUnit,
82181
+ };
82182
+ }
81998
82183
  function addLineChart(chart) {
81999
82184
  const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
82000
82185
  const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
@@ -82187,7 +82372,7 @@ function addRadarChart(chart) {
82187
82372
  `}
82188
82373
  `;
82189
82374
  }
82190
- function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
82375
+ function addDoughnutChart(chart, chartSheetIndex, data) {
82191
82376
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
82192
82377
  const colors = new ColorGenerator(maxLength);
82193
82378
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
@@ -82225,7 +82410,7 @@ function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSiz
82225
82410
  return escapeXml /*xml*/ `
82226
82411
  <c:doughnutChart>
82227
82412
  <c:varyColors val="1" />
82228
- <c:holeSize val="${holeSize}" />
82413
+ <c:holeSize val="${chart.pieHolePercentage ?? (chart.isDoughnut ? DEFAULT_DOUGHNUT_CHART_HOLE_SIZE : 0)}" />
82229
82414
  ${insertDataLabels()}
82230
82415
  ${joinXmlNodes(dataSetsNodes)}
82231
82416
  </c:doughnutChart>
@@ -82244,25 +82429,35 @@ function insertDataLabels({ showLeaderLines } = { showLeaderLines: false }) {
82244
82429
  </dLbls>
82245
82430
  `;
82246
82431
  }
82247
- function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0) {
82432
+ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, deleteAxis = 0, orientation = "minMax", crossPosition, tickLabelPosition = "nextTo", maxValue, majorUnit, format = "General") {
82248
82433
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
82249
82434
  // I.e. x-axis, will reference y-axis and vice-versa.
82250
82435
  const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
82251
82436
  const fontSize = title?.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE;
82437
+ const crossBetweenEl = axisName === "c:valAx" ? escapeXml `<c:crossBetween val="between" />` : "";
82438
+ const maxValueEl = maxValue ? escapeXml `<c:max val="${maxValue}" />` : "";
82439
+ const minValueEl = maxValue ? escapeXml `<c:min val="${-maxValue}" />` : "";
82440
+ const majorUnitEl = majorUnit ? escapeXml `<c:majorUnit val="${majorUnit}" />` : "";
82252
82441
  return escapeXml /*xml*/ `
82253
82442
  <${axisName}>
82254
82443
  <c:axId val="${axId}"/>
82255
82444
  <c:crossAx val="${crossAxId}"/> <!-- reference to the other axe of the chart -->
82256
- <c:crosses val="${position === "b" || position === "l" ? "min" : "max"}"/>
82445
+ <c:crosses val="${crossPosition || (position === "b" || position === "l" ? "min" : "max")}"/>
82446
+ <c:auto val="1"/>
82447
+ ${crossBetweenEl}
82257
82448
  <c:delete val="${deleteAxis}"/> <!-- by default, axis are not displayed -->
82258
82449
  <c:scaling>
82259
- <c:orientation val="minMax" />
82450
+ <c:orientation val="${orientation}" />
82451
+ ${maxValueEl}
82452
+ ${minValueEl}
82260
82453
  </c:scaling>
82454
+ ${majorUnitEl}
82261
82455
  <c:axPos val="${position}" />
82456
+ <c:tickLblPos val="${tickLabelPosition}" />
82262
82457
  ${insertMajorGridLines()}
82263
82458
  <c:majorTickMark val="out" />
82264
82459
  <c:minorTickMark val="none" />
82265
- <c:numFmt formatCode="General" sourceLinked="1" />
82460
+ <c:numFmt formatCode="${format}" sourceLinked="${format === "General" ? "1" : "0"}" />
82266
82461
  <c:title>
82267
82462
  ${insertText(title?.text ?? "", color, fontSize, title)}
82268
82463
  </c:title>
@@ -82457,7 +82652,10 @@ function addConditionalFormatting(dxfs, conditionalFormats) {
82457
82652
  function addCellIsRule(cf, rule, dxfs) {
82458
82653
  const ruleAttributes = commonCfAttributes(cf);
82459
82654
  const operator = convertOperator(rule.operator);
82460
- ruleAttributes.push(...cellRuleTypeAttributes(rule), ["operator", operator]);
82655
+ ruleAttributes.push(...cellRuleTypeAttributes(rule));
82656
+ if (operator.length) {
82657
+ ruleAttributes.push(["operator", operator]);
82658
+ }
82461
82659
  const formulas = cellRuleFormula(cf.ranges, rule).map((formula) => escapeXml /*xml*/ `<formula>${formula}</formula>`);
82462
82660
  const dxf = {
82463
82661
  font: {
@@ -82503,6 +82701,8 @@ function cellRuleFormula(ranges, rule) {
82503
82701
  case "isLessThan":
82504
82702
  case "isLessOrEqualTo":
82505
82703
  return [values[0]];
82704
+ case "customFormula":
82705
+ return values[0].startsWith("=") ? [values[0].slice(1)] : [values[0]];
82506
82706
  case "isBetween":
82507
82707
  case "isNotBetween":
82508
82708
  return [values[0], values[1]];
@@ -82531,6 +82731,8 @@ function cellRuleTypeAttributes(rule) {
82531
82731
  case "isBetween":
82532
82732
  case "isNotBetween":
82533
82733
  return [["type", "cellIs"]];
82734
+ case "customFormula":
82735
+ return [["type", "expression"]];
82534
82736
  }
82535
82737
  }
82536
82738
  function addDataBarRule(cf, rule) {
@@ -84583,6 +84785,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
84583
84785
  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
84786
 
84585
84787
 
84586
- __info__.version = "18.5.0-alpha.2";
84587
- __info__.date = "2025-07-11T11:13:53.317Z";
84588
- __info__.hash = "6d42178";
84788
+ __info__.version = "18.5.0-alpha.3";
84789
+ __info__.date = "2025-07-28T13:43:05.981Z";
84790
+ __info__.hash = "53dfee8";