@odoo/o-spreadsheet 19.0.11 → 19.0.15

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 19.0.11
6
- * @date 2025-11-24T07:46:47.685Z
7
- * @hash f5bdbcc
5
+ * @version 19.0.15
6
+ * @date 2025-12-26T10:19:23.408Z
7
+ * @hash fe625c9
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';
@@ -181,7 +181,6 @@ const invalidateEvaluationCommands = new Set([
181
181
  "REDO",
182
182
  "ADD_MERGE",
183
183
  "REMOVE_MERGE",
184
- "DUPLICATE_SHEET",
185
184
  "UPDATE_LOCALE",
186
185
  "ADD_PIVOT",
187
186
  "UPDATE_PIVOT",
@@ -1894,21 +1893,25 @@ profilesStartingPosition, profiles, zones, toRemove = false) {
1894
1893
  function profilesContainsZone(profilesStartingPosition, profiles, zone) {
1895
1894
  const leftValue = zone.left;
1896
1895
  const rightValue = zone.right;
1897
- const topValue = zone.top;
1898
- const bottomValue = zone.bottom + 1;
1899
1896
  const leftIndex = binaryPredecessorSearch(profilesStartingPosition, leftValue, 0);
1900
- const rightIndex = binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
1901
- if (leftIndex === -1 || rightIndex === -1) {
1902
- return false;
1903
- }
1897
+ const rightIndex = rightValue === undefined
1898
+ ? profilesStartingPosition.length - 1
1899
+ : binaryPredecessorSearch(profilesStartingPosition, rightValue, leftIndex);
1900
+ /**
1901
+ * The `profilesStartingPosition` array always contains at least the value `0` at its first position,
1902
+ * ensuring that applying `binaryPredecessorSearch` will always return a valid index.
1903
+ * Therefore, it is not necessary to check if the result of `binaryPredecessorSearch` equals `-1`.
1904
+ */
1905
+ const topValue = zone.top;
1906
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
1904
1907
  for (let i = leftIndex; i <= rightIndex; i++) {
1905
1908
  const profile = profiles.get(profilesStartingPosition[i]);
1906
- const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, true);
1907
- const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, true);
1909
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0);
1908
1910
  if (topPredIndex === -1 || topPredIndex % 2 !== 0) {
1909
1911
  return false;
1910
1912
  }
1911
- if (topValue < profile[topPredIndex] || bottomValue > profile[bottomSuccIndex]) {
1913
+ const bottomSuccIndex = bottomValue === undefined ? profile.length : binarySuccessorSearch(profile, bottomValue, 0);
1914
+ if (topPredIndex + 1 !== bottomSuccIndex) {
1912
1915
  return false;
1913
1916
  }
1914
1917
  }
@@ -6594,17 +6597,41 @@ function toCriterionDateNumber(dateValue) {
6594
6597
  const today = DateTime.now();
6595
6598
  switch (dateValue) {
6596
6599
  case "today":
6597
- return jsDateToNumber(today);
6598
- case "yesterday":
6599
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
6600
- case "tomorrow":
6601
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
6600
+ return Math.floor(jsDateToNumber(today));
6601
+ case "yesterday": {
6602
+ today.setDate(today.getDate() - 1);
6603
+ return Math.floor(jsDateToNumber(today));
6604
+ }
6605
+ case "tomorrow": {
6606
+ today.setDate(today.getDate() + 1);
6607
+ return Math.floor(jsDateToNumber(today));
6608
+ }
6602
6609
  case "lastWeek":
6603
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
6604
- case "lastMonth":
6605
- return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
6610
+ today.setDate(today.getDate() - 6);
6611
+ return Math.floor(jsDateToNumber(today));
6612
+ case "lastMonth": {
6613
+ const lastMonth = today.getMonth() === 0 ? 11 : today.getMonth() - 1;
6614
+ const dateInLastMonth = new DateTime(today.getFullYear(), lastMonth, 1);
6615
+ if (today.getDate() > getDaysInMonth(dateInLastMonth)) {
6616
+ today.setDate(1);
6617
+ }
6618
+ else {
6619
+ today.setDate(today.getDate() + 1);
6620
+ today.setMonth(today.getMonth() - 1);
6621
+ }
6622
+ return Math.floor(jsDateToNumber(today));
6623
+ }
6606
6624
  case "lastYear":
6607
- return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
6625
+ // Handle leap year case
6626
+ if (today.getMonth() === 1 && today.getDate() === 29) {
6627
+ today.setDate(28);
6628
+ today.setFullYear(today.getFullYear() - 1);
6629
+ }
6630
+ else {
6631
+ today.setDate(today.getDate() + 1);
6632
+ today.setFullYear(today.getFullYear() - 1);
6633
+ }
6634
+ return Math.floor(jsDateToNumber(today));
6608
6635
  }
6609
6636
  }
6610
6637
  /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
@@ -6777,67 +6804,6 @@ function getFullReference(sheetName, xc) {
6777
6804
  return sheetName !== undefined ? `${getCanonicalSymbolName(sheetName)}!${xc}` : xc;
6778
6805
  }
6779
6806
 
6780
- function createDefaultRows(rowNumber) {
6781
- const rows = [];
6782
- for (let i = 0; i < rowNumber; i++) {
6783
- const row = {
6784
- cells: {},
6785
- };
6786
- rows.push(row);
6787
- }
6788
- return rows;
6789
- }
6790
- function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
6791
- return headers.map((header) => {
6792
- if (header >= indexHeaderAdded) {
6793
- return header + numberAdded;
6794
- }
6795
- return header;
6796
- });
6797
- }
6798
- function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
6799
- deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
6800
- return headers
6801
- .map((header) => {
6802
- for (const deletedHeader of deletedHeaders) {
6803
- if (header > deletedHeader) {
6804
- header--;
6805
- }
6806
- else if (header === deletedHeader) {
6807
- return undefined;
6808
- }
6809
- }
6810
- return header;
6811
- })
6812
- .filter(isDefined);
6813
- }
6814
- function getNextSheetName(existingNames, baseName = "Sheet") {
6815
- let i = 1;
6816
- let name = `${baseName}${i}`;
6817
- while (existingNames.includes(name)) {
6818
- name = `${baseName}${i}`;
6819
- i++;
6820
- }
6821
- return name;
6822
- }
6823
- function getDuplicateSheetName(nameToDuplicate, existingNames) {
6824
- let i = 1;
6825
- const baseName = _t("Copy of %s", nameToDuplicate);
6826
- let name = baseName.toString();
6827
- while (existingNames.includes(name)) {
6828
- name = `${baseName} (${i})`;
6829
- i++;
6830
- }
6831
- return name;
6832
- }
6833
- function isSheetNameEqual(name1, name2) {
6834
- if (name1 === undefined || name2 === undefined) {
6835
- return false;
6836
- }
6837
- return (getUnquotedSheetName(name1.trim().toUpperCase()) ===
6838
- getUnquotedSheetName(name2.trim().toUpperCase()));
6839
- }
6840
-
6841
6807
  function createRange(args, getSheetSize) {
6842
6808
  const unboundedZone = args.zone;
6843
6809
  const zone = boundUnboundedZone(unboundedZone, getSheetSize(args.sheetId));
@@ -7085,7 +7051,7 @@ function getApplyRangeChangeRemoveColRow(cmd) {
7085
7051
  elements.sort((a, b) => b - a);
7086
7052
  const groups = groupConsecutive(elements);
7087
7053
  return (range) => {
7088
- if (!isSheetNameEqual(range.sheetId, cmd.sheetId)) {
7054
+ if (range.sheetId !== cmd.sheetId) {
7089
7055
  return { changeType: "NONE" };
7090
7056
  }
7091
7057
  let newRange = range;
@@ -7292,6 +7258,69 @@ function fuzzyLookup(pattern, list, fn) {
7292
7258
  return results.map((r) => r.elem);
7293
7259
  }
7294
7260
 
7261
+ function createDefaultRows(rowNumber) {
7262
+ const rows = [];
7263
+ for (let i = 0; i < rowNumber; i++) {
7264
+ const row = {
7265
+ cells: {},
7266
+ };
7267
+ rows.push(row);
7268
+ }
7269
+ return rows;
7270
+ }
7271
+ function moveHeaderIndexesOnHeaderAddition(indexHeaderAdded, numberAdded, headers) {
7272
+ return headers.map((header) => {
7273
+ if (header >= indexHeaderAdded) {
7274
+ return header + numberAdded;
7275
+ }
7276
+ return header;
7277
+ });
7278
+ }
7279
+ function moveHeaderIndexesOnHeaderDeletion(deletedHeaders, headers) {
7280
+ deletedHeaders = [...deletedHeaders].sort((a, b) => b - a);
7281
+ return headers
7282
+ .map((header) => {
7283
+ for (const deletedHeader of deletedHeaders) {
7284
+ if (header > deletedHeader) {
7285
+ header--;
7286
+ }
7287
+ else if (header === deletedHeader) {
7288
+ return undefined;
7289
+ }
7290
+ }
7291
+ return header;
7292
+ })
7293
+ .filter(isDefined);
7294
+ }
7295
+ function getNextSheetName(existingNames, baseName = "Sheet") {
7296
+ let i = 1;
7297
+ let name = `${baseName}${i}`;
7298
+ while (existingNames.includes(name)) {
7299
+ name = `${baseName}${i}`;
7300
+ i++;
7301
+ }
7302
+ return name;
7303
+ }
7304
+ function getDuplicateSheetName(nameToDuplicate, existingNames) {
7305
+ let i = 1;
7306
+ const baseName = _t("Copy of %s", nameToDuplicate);
7307
+ let name = baseName.toString();
7308
+ while (existingNames.includes(name)) {
7309
+ name = `${baseName} (${i})`;
7310
+ i++;
7311
+ }
7312
+ return name;
7313
+ }
7314
+ const toStandardizedSheetName = memoize(function toStandardizedSheetName(name) {
7315
+ return getUnquotedSheetName(name.trim().toUpperCase());
7316
+ });
7317
+ function isSheetNameEqual(name1, name2) {
7318
+ if (name1 === undefined || name2 === undefined) {
7319
+ return false;
7320
+ }
7321
+ return toStandardizedSheetName(name1) === toStandardizedSheetName(name2);
7322
+ }
7323
+
7295
7324
  function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
7296
7325
  return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
7297
7326
  }
@@ -23877,6 +23906,9 @@ function getTreeMapGroupColors(definition, tree) {
23877
23906
  }));
23878
23907
  }
23879
23908
  function getTreeMapColorScale(tree, coloringOption) {
23909
+ if (tree.length === 0) {
23910
+ return undefined;
23911
+ }
23880
23912
  const treeNodesByLevel = pyramidizeTree(tree);
23881
23913
  const nodes = treeNodesByLevel[treeNodesByLevel.length - 1];
23882
23914
  const minValue = Math.min(...nodes.map((node) => node.value));
@@ -25562,11 +25594,18 @@ function chartToImageUrl(runtime, figure, type) {
25562
25594
  // we have to add the canvas to the DOM otherwise it won't be rendered
25563
25595
  document.body.append(div);
25564
25596
  if ("chartJsConfig" in runtime) {
25597
+ const extensionsLoaded = areChartJSExtensionsLoaded();
25598
+ if (!extensionsLoaded) {
25599
+ registerChartJSExtensions();
25600
+ }
25565
25601
  const config = deepCopy(runtime.chartJsConfig);
25566
25602
  config.plugins = [backgroundColorChartJSPlugin];
25567
25603
  const chart = new window.Chart(canvas, config);
25568
25604
  imageContent = chart.toBase64Image();
25569
25605
  chart.destroy();
25606
+ if (!extensionsLoaded) {
25607
+ unregisterChartJsExtensions();
25608
+ }
25570
25609
  }
25571
25610
  else if (type === "scorecard") {
25572
25611
  const design = getScorecardConfiguration(figure, runtime);
@@ -25596,11 +25635,18 @@ async function chartToImageFile(runtime, figure, type) {
25596
25635
  document.body.append(div);
25597
25636
  let chartBlob = null;
25598
25637
  if ("chartJsConfig" in runtime) {
25638
+ const extensionsLoaded = areChartJSExtensionsLoaded();
25639
+ if (!extensionsLoaded) {
25640
+ registerChartJSExtensions();
25641
+ }
25599
25642
  const config = deepCopy(runtime.chartJsConfig);
25600
25643
  config.plugins = [backgroundColorChartJSPlugin];
25601
25644
  const chart = new window.Chart(canvas, config);
25602
25645
  chartBlob = await new Promise((resolve) => canvas.toBlob(resolve, "image/png"));
25603
25646
  chart.destroy();
25647
+ if (!extensionsLoaded) {
25648
+ unregisterChartJsExtensions();
25649
+ }
25604
25650
  }
25605
25651
  else if (type === "scorecard") {
25606
25652
  const design = getScorecardConfiguration(figure, runtime);
@@ -27726,29 +27772,19 @@ class ZoomableChartStore extends SpreadsheetStore {
27726
27772
  }
27727
27773
  resetAxisLimits(chartId, limits) {
27728
27774
  for (const axisId of ZOOMABLE_AXIS_IDS) {
27729
- if (limits?.[axisId]) {
27730
- if (!this.originalAxisLimits[chartId]?.[axisId]) {
27731
- this.originalAxisLimits[chartId] = {
27732
- ...this.originalAxisLimits[chartId],
27733
- [axisId]: {},
27734
- };
27735
- }
27736
- this.originalAxisLimits[chartId][axisId]["min"] = limits[axisId].min;
27737
- this.originalAxisLimits[chartId][axisId]["max"] = limits[axisId].max;
27775
+ if (limits[axisId]) {
27776
+ this.originalAxisLimits[chartId] = {
27777
+ ...this.originalAxisLimits[chartId],
27778
+ [axisId]: { ...limits[axisId] },
27779
+ };
27738
27780
  }
27739
- else {
27740
- if (this.originalAxisLimits[chartId]?.[axisId]) {
27741
- delete this.originalAxisLimits[chartId][axisId];
27742
- }
27781
+ else if (this.originalAxisLimits[chartId]?.[axisId]) {
27782
+ delete this.originalAxisLimits[chartId][axisId];
27743
27783
  }
27744
27784
  }
27745
27785
  return "noStateChange";
27746
27786
  }
27747
27787
  updateAxisLimits(chartId, limits) {
27748
- if (limits === undefined) {
27749
- delete this.currentAxesLimits[chartId];
27750
- return "noStateChange";
27751
- }
27752
27788
  let { min, max } = limits;
27753
27789
  if (min > max) {
27754
27790
  [min, max] = [max, min];
@@ -27764,26 +27800,14 @@ class ZoomableChartStore extends SpreadsheetStore {
27764
27800
  * for the current trend line axes.
27765
27801
  */
27766
27802
  updateTrendLineConfiguration(chartId) {
27767
- if (!this.originalAxisLimits[chartId]) {
27803
+ if (!this.originalAxisLimits[chartId]?.x || !this.currentAxesLimits[chartId]?.x) {
27768
27804
  return "noStateChange";
27769
27805
  }
27770
27806
  const chartLimits = this.originalAxisLimits[chartId].x;
27771
- if (chartLimits === undefined) {
27772
- return "noStateChange";
27773
- }
27774
27807
  for (const axisId of TREND_LINE_AXES_IDS) {
27775
27808
  if (!this.originalAxisLimits[chartId][axisId]) {
27776
27809
  continue;
27777
27810
  }
27778
- if (!this.currentAxesLimits[chartId]?.[axisId]) {
27779
- this.currentAxesLimits[chartId] = {
27780
- ...this.currentAxesLimits[chartId],
27781
- [axisId]: {},
27782
- };
27783
- }
27784
- if (this.currentAxesLimits[chartId]?.x === undefined) {
27785
- return "noStateChange";
27786
- }
27787
27811
  const realRange = chartLimits.max - chartLimits.min;
27788
27812
  const trendingLimits = this.originalAxisLimits[chartId][axisId];
27789
27813
  const trendingRange = trendingLimits.max - trendingLimits.min;
@@ -27791,8 +27815,10 @@ class ZoomableChartStore extends SpreadsheetStore {
27791
27815
  const intercept = trendingLimits.min - chartLimits.min * slope;
27792
27816
  const newXMin = this.currentAxesLimits[chartId].x.min;
27793
27817
  const newXMax = this.currentAxesLimits[chartId].x.max;
27794
- this.currentAxesLimits[chartId][axisId].min = newXMin * slope + intercept;
27795
- this.currentAxesLimits[chartId][axisId].max = newXMax * slope + intercept;
27818
+ this.currentAxesLimits[chartId][axisId] = {
27819
+ min: newXMin * slope + intercept,
27820
+ max: newXMax * slope + intercept,
27821
+ };
27796
27822
  }
27797
27823
  return "noStateChange";
27798
27824
  }
@@ -27861,8 +27887,9 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27861
27887
  hasLinearScale;
27862
27888
  isBarChart;
27863
27889
  chartId = "";
27864
- datasetBoundaries = { xMin: 0, xMax: 0 };
27890
+ datasetBoundaries = { min: 0, max: 0 };
27865
27891
  removeEventListeners = () => { };
27892
+ isMasterChartAllowed = false;
27866
27893
  setup() {
27867
27894
  this.store = useStore(ZoomableChartStore);
27868
27895
  super.setup();
@@ -27878,12 +27905,19 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27878
27905
  height:${height};
27879
27906
  `;
27880
27907
  }
27908
+ get masterChartContainerStyle() {
27909
+ const runtime = this.env.model.getters.getChartRuntime(this.props.chartId);
27910
+ if (runtime && !runtime.chartJsConfig.data.datasets.some((ds) => ds.data.length > 1)) {
27911
+ return "opacity: 0.3;";
27912
+ }
27913
+ return "";
27914
+ }
27881
27915
  get sliceable() {
27882
27916
  if (this.props.isFullScreen) {
27883
27917
  return true;
27884
27918
  }
27885
27919
  const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
27886
- return ("zoomable" in definition && definition?.zoomable) ?? false;
27920
+ return ("zoomable" in definition && definition.zoomable) ?? false;
27887
27921
  }
27888
27922
  get axisOffset() {
27889
27923
  return !this.hasLinearScale && this.isBarChart ? 0.5 : 0;
@@ -27908,15 +27942,13 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27908
27942
  if (!this.sliceable) {
27909
27943
  return chartData;
27910
27944
  }
27911
- const xAxis = this.store.currentAxesLimits[this.chartId]?.x;
27912
- const xScale = {
27913
- ...chartData.options.scales?.x,
27914
- };
27915
- if (xAxis?.min !== undefined) {
27916
- xScale.min = this.hasLinearScale ? xAxis.min : Math.ceil(xAxis.min) - this.axisOffset;
27917
- }
27918
- if (xAxis?.max !== undefined) {
27919
- xScale.max = this.hasLinearScale ? xAxis.max : Math.floor(xAxis.max) - this.axisOffset;
27945
+ let x = chartData.options.scales.x;
27946
+ const limits = this.store.currentAxesLimits[this.chartId]?.x;
27947
+ if (limits) {
27948
+ x = {
27949
+ ...x,
27950
+ ...this.getStoredBoundaries(),
27951
+ };
27920
27952
  }
27921
27953
  return {
27922
27954
  ...chartData,
@@ -27924,7 +27956,7 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27924
27956
  ...chartData.options,
27925
27957
  scales: {
27926
27958
  ...chartData.options.scales,
27927
- x: xScale,
27959
+ x,
27928
27960
  },
27929
27961
  layout: {
27930
27962
  ...chartData.options.layout,
@@ -27939,9 +27971,19 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27939
27971
  getAxisLimitsFromDataset(chartData) {
27940
27972
  const data = chartData.data.datasets.map((ds) => ds.data).flat();
27941
27973
  const xValues = data.map((d, i) => (typeof d === "object" && d !== null ? d.x : i));
27942
- const xMin = Math.min(...xValues);
27943
- const xMax = Math.max(...xValues);
27944
- return { xMin, xMax };
27974
+ const min = Math.min(...xValues);
27975
+ const max = Math.max(...xValues);
27976
+ return { min, max };
27977
+ }
27978
+ setMasterChartCursor(runtime) {
27979
+ const masterElement = this.masterChartCanvas?.el;
27980
+ if (runtime && !runtime.chartJsConfig.data.datasets.some((ds) => ds.data.length > 1)) {
27981
+ masterElement.style.cursor = "not-allowed";
27982
+ this.isMasterChartAllowed = false;
27983
+ return;
27984
+ }
27985
+ masterElement.style.cursor = "default";
27986
+ this.isMasterChartAllowed = true;
27945
27987
  }
27946
27988
  createChart(chartRuntime) {
27947
27989
  const chartData = chartRuntime.chartJsConfig;
@@ -27953,12 +27995,14 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27953
27995
  chartRuntime.chartJsConfig = updatedData;
27954
27996
  }
27955
27997
  super.createChart(chartRuntime);
27956
- this.hasLinearScale = this.chart?.scales?.x.type === "linear";
27998
+ this.hasLinearScale = this.chart?.scales?.x?.type === "linear";
27957
27999
  if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
28000
+ this.isMasterChartAllowed = false;
27958
28001
  return;
27959
28002
  }
27960
28003
  this.masterChart?.destroy();
27961
- const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
28004
+ const masterChartCtx = (this.masterChartCanvas?.el).getContext("2d");
28005
+ this.setMasterChartCursor(chartRuntime);
27962
28006
  this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
27963
28007
  this.resetAxesLimits();
27964
28008
  if (this.chart?.options) {
@@ -27967,11 +28011,10 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27967
28011
  }
27968
28012
  updateChartJs(chartRuntime) {
27969
28013
  const chartData = chartRuntime.chartJsConfig;
27970
- const newDatasetBoundaries = this.getAxisLimitsFromDataset(chartData);
27971
- if (this.datasetBoundaries.xMin !== newDatasetBoundaries.xMin ||
27972
- this.datasetBoundaries.xMax !== newDatasetBoundaries.xMax) {
28014
+ const { min, max } = this.getAxisLimitsFromDataset(chartData);
28015
+ if (this.datasetBoundaries.min !== min || this.datasetBoundaries.max !== max) {
27973
28016
  this.store.clearAxisLimits(this.chartId);
27974
- this.datasetBoundaries = newDatasetBoundaries;
28017
+ this.datasetBoundaries = { min, max };
27975
28018
  }
27976
28019
  this.isBarChart = chartData?.type === "bar";
27977
28020
  this.chartId = `${chartData.type}-${this.props.chartId}`;
@@ -27980,9 +28023,10 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27980
28023
  chartRuntime.chartJsConfig = updatedData;
27981
28024
  }
27982
28025
  super.updateChartJs(chartRuntime);
27983
- this.hasLinearScale = this.chart?.scales?.x.type === "linear";
28026
+ this.hasLinearScale = this.chart?.scales?.x?.type === "linear";
27984
28027
  if (!this.sliceable || !("masterChartConfig" in chartRuntime)) {
27985
28028
  this.masterChart = undefined;
28029
+ this.isMasterChartAllowed = false;
27986
28030
  }
27987
28031
  else {
27988
28032
  const masterChartConfig = this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]);
@@ -27995,6 +28039,7 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27995
28039
  this.masterChart.config.options = masterChartConfig.options;
27996
28040
  this.masterChart.update();
27997
28041
  }
28042
+ this.setMasterChartCursor(chartRuntime);
27998
28043
  }
27999
28044
  this.resetAxesLimits();
28000
28045
  if (this.chart?.options) {
@@ -28005,18 +28050,15 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28005
28050
  if (!this.chart) {
28006
28051
  return;
28007
28052
  }
28008
- const previousAxisLimits = this.store.originalAxisLimits[this.chartId];
28009
- if (previousAxisLimits?.x?.min === undefined && previousAxisLimits?.x?.max === undefined) {
28053
+ const storedLimits = this.store.originalAxisLimits[this.chartId]?.x;
28054
+ if (!storedLimits) {
28010
28055
  let scales = this.masterChart
28011
28056
  ? this.masterChart.scales
28012
28057
  : this.chart.scales;
28013
- if (!this.hasLinearScale && scales?.x) {
28058
+ if (!this.hasLinearScale && scales.x) {
28014
28059
  scales = {
28015
28060
  ...scales,
28016
- x: {
28017
- min: Math.ceil(scales.x.min) - this.axisOffset,
28018
- max: Math.floor(scales.x.max) + this.axisOffset,
28019
- },
28061
+ x: this.adjustBoundaries(scales.x),
28020
28062
  };
28021
28063
  }
28022
28064
  this.store.resetAxisLimits(this.chartId, scales);
@@ -28031,13 +28073,13 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28031
28073
  }
28032
28074
  updateTrendingLineAxes() {
28033
28075
  this.store.updateTrendLineConfiguration(this.chartId);
28034
- const config = this.store.currentAxesLimits[this.chartId];
28076
+ const limits = this.store.currentAxesLimits[this.chartId];
28035
28077
  for (const axisId of [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID]) {
28036
- if (!this.chart?.config.options?.scales?.[axisId] || !config?.[axisId]) {
28078
+ if (!this.chart?.config?.options?.scales?.[axisId] || !limits?.[axisId]) {
28037
28079
  continue;
28038
28080
  }
28039
- this.chart.config.options.scales[axisId].min = config[axisId].min;
28040
- this.chart.config.options.scales[axisId].max = config[axisId].max;
28081
+ this.chart.config.options.scales[axisId].min = limits[axisId].min;
28082
+ this.chart.config.options.scales[axisId].max = limits[axisId].max;
28041
28083
  }
28042
28084
  }
28043
28085
  get upperBound() {
@@ -28080,29 +28122,71 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28080
28122
  offset +
28081
28123
  ((scale.max + 2 * offset - scale.min) * (position - left)) / (right - left));
28082
28124
  }
28083
- updateAxisLimits(xMin, xMax) {
28125
+ /**
28126
+ * Compute min and max from the store, adjusting them if needed for non linear scales.
28127
+ * Getting the value from the store, we have to ensure that the values are integers for
28128
+ * non linear scales (bar and category). To select a bar in the chart, we have to include
28129
+ * the whole bar, which means that for the i-th bar, the selected min should be <= i and
28130
+ * the selected max should be >= i, so using the Math.floor and Math.ceil functions is
28131
+ * the right way to do it.
28132
+ * Sometimes, we can get a minimal value > the maximal value, which arise when the user
28133
+ * select a very small area in the master chart, and hasn't selected the middle of a bar
28134
+ * or a group of bars (in case of more than one data series).
28135
+ * Assuming we have to select the middle of a bar/a groupe of bars, we will reject the
28136
+ * coming value afterward. In this case, we do not update the chart because it would lead
28137
+ * to an empty chart.
28138
+ */
28139
+ getStoredBoundaries() {
28140
+ let { min, max } = this.store.currentAxesLimits[this.chartId].x;
28084
28141
  if (!this.hasLinearScale) {
28085
- this.chart.config.options.scales.x.min = Math.ceil(xMin);
28086
- this.chart.config.options.scales.x.max = Math.floor(xMax);
28142
+ min = Math.ceil(min);
28143
+ max = Math.floor(max);
28087
28144
  }
28088
- else {
28089
- this.chart.config.options.scales.x.min = xMin;
28090
- this.chart.config.options.scales.x.max = xMax;
28145
+ return { min, max };
28146
+ }
28147
+ /**
28148
+ * Adjust the min and max values of an axis if needed for non linear scales.
28149
+ * Here, after rounding (see docstring of getStoredBoundaries), we adjust the min by
28150
+ * substracting the axis offset, and we add it to the max, because when computing from the
28151
+ * scale, chartJs use integer values as the limits for non linear scales. If we have a min
28152
+ * value of 1, it means we want to start displaying from 0.5, and if we have a max value of
28153
+ * 4, it means we want to display until 4.5.
28154
+ * Here, we don't have to check if min > max because we are computing from the scale, and
28155
+ * chartJs ensures that this won't happen, even after our adjustments.
28156
+ */
28157
+ adjustBoundaries({ min, max }) {
28158
+ if (!this.hasLinearScale) {
28159
+ min = Math.ceil(min) - this.axisOffset;
28160
+ max = Math.floor(max) + this.axisOffset;
28161
+ }
28162
+ return { min, max };
28163
+ }
28164
+ updateAxisLimits(xMin, xMax) {
28165
+ if (xMin === xMax) {
28166
+ return;
28167
+ }
28168
+ if (!this.chart) {
28169
+ return;
28091
28170
  }
28092
28171
  this.store.updateAxisLimits(this.chartId, { min: xMin, max: xMax });
28093
- this.updateTrendingLineAxes();
28172
+ const { min, max } = this.getStoredBoundaries();
28173
+ if (max > min || (this.isBarChart && max === min)) {
28174
+ this.chart.config.options.scales.x.min = min;
28175
+ this.chart.config.options.scales.x.max = max;
28176
+ this.updateTrendingLineAxes();
28177
+ this.chart.update();
28178
+ }
28094
28179
  this.masterChart?.update();
28095
- this.chart?.update();
28096
28180
  }
28097
- onPointerDownInMasterChart(ev) {
28181
+ onMasterChartPointerDown(ev) {
28098
28182
  this.removeEventListeners();
28099
28183
  const position = ev.offsetX;
28100
28184
  if (!this.masterChart?.chartArea || !this.chart?.scales?.x) {
28101
28185
  return;
28102
28186
  }
28103
28187
  const { left, right, top, bottom } = this.masterChart.chartArea;
28104
- const xMax = this.upperBound ?? right;
28105
- const xMin = this.lowerBound ?? left;
28188
+ const upperBound = this.upperBound ?? right;
28189
+ const lowerBound = this.lowerBound ?? left;
28106
28190
  if (position < left - 5 || position > right + 5 || ev.offsetY < top || ev.offsetY > bottom) {
28107
28191
  return;
28108
28192
  }
@@ -28110,8 +28194,10 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28110
28194
  ev.stopPropagation();
28111
28195
  let startingPositionOnChart, windowSize, startX;
28112
28196
  const startingEventPosition = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
28113
- if ((xMin !== left || xMax !== right) && position > xMin + 5 && position < xMax - 5) {
28114
- startingPositionOnChart = ev.offsetX - xMin;
28197
+ if ((lowerBound !== left || upperBound !== right) &&
28198
+ position > lowerBound + 5 &&
28199
+ position < upperBound - 5) {
28200
+ startingPositionOnChart = ev.offsetX - lowerBound;
28115
28201
  this.mode = "moveInMaster";
28116
28202
  const currentLimits = this.store.currentAxesLimits[this.chartId]?.x;
28117
28203
  windowSize =
@@ -28120,31 +28206,29 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28120
28206
  }
28121
28207
  else {
28122
28208
  this.mode = "selectInMaster";
28123
- if (Math.abs(position - xMin) < 5) {
28124
- startingPositionOnChart = xMax;
28209
+ if (Math.abs(position - lowerBound) < 5) {
28210
+ startingPositionOnChart = upperBound;
28125
28211
  }
28126
- else if (Math.abs(position - xMax) < 5) {
28127
- startingPositionOnChart = xMin;
28212
+ else if (Math.abs(position - upperBound) < 5) {
28213
+ startingPositionOnChart = lowerBound;
28128
28214
  }
28129
28215
  else {
28130
28216
  startingPositionOnChart = clip(position, left, right);
28131
28217
  }
28132
28218
  startX = this.computeCoordinate(startingPositionOnChart);
28133
28219
  }
28134
- const originalXMin = this.store.originalAxisLimits[this.chartId].x.min;
28135
- const originalXMax = this.store.originalAxisLimits[this.chartId].x.max;
28220
+ const storedMin = this.store.originalAxisLimits[this.chartId].x.min;
28221
+ const storedMax = this.store.originalAxisLimits[this.chartId].x.max;
28136
28222
  const computeNewAxisLimits = (position) => {
28137
- let xMin, xMax;
28138
- const { left, right } = this.masterChart.chartArea;
28139
28223
  if (this.mode === "moveInMaster") {
28140
- xMin = this.computeCoordinate(position - startingPositionOnChart);
28141
- if (xMin < originalXMin) {
28142
- xMin = originalXMin;
28224
+ let min = this.computeCoordinate(position - startingPositionOnChart);
28225
+ if (min < storedMin) {
28226
+ min = storedMin;
28143
28227
  }
28144
- else if (xMin > originalXMax - windowSize) {
28145
- xMin = originalXMax - windowSize;
28228
+ else if (min > storedMax - windowSize) {
28229
+ min = storedMax - windowSize;
28146
28230
  }
28147
- xMax = xMin + windowSize;
28231
+ return { min, max: min + windowSize };
28148
28232
  }
28149
28233
  else if (this.mode === "selectInMaster") {
28150
28234
  const upperBound = clip(position, left, right);
@@ -28153,54 +28237,52 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28153
28237
  if (startX === undefined || endX === undefined) {
28154
28238
  return {};
28155
28239
  }
28156
- xMin = Math.min(startX, endX);
28157
- xMax = Math.max(startX, endX);
28240
+ return {
28241
+ min: Math.min(startX, endX),
28242
+ max: Math.max(startX, endX),
28243
+ };
28158
28244
  }
28159
28245
  }
28160
- return { min: xMin, max: xMax };
28246
+ return {};
28161
28247
  };
28162
- const onDragFromMasterChart = (ev) => {
28248
+ const onMasterChartDrag = (ev) => {
28163
28249
  const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
28164
28250
  if (Math.abs(position - startingEventPosition) < 5) {
28165
28251
  return;
28166
28252
  }
28167
- const { min: xMin, max: xMax } = computeNewAxisLimits(position);
28168
- if (xMin !== undefined && xMax !== undefined) {
28169
- this.updateAxisLimits(xMin, xMax);
28253
+ const { min, max } = computeNewAxisLimits(position);
28254
+ if (min !== undefined && max !== undefined) {
28255
+ this.updateAxisLimits(min, max);
28170
28256
  }
28171
28257
  };
28172
- const onPointerUpInMasterChart = (ev) => {
28258
+ const onMasterChartPointerUp = (ev) => {
28173
28259
  this.removeEventListeners();
28174
- const position = ev.clientX - (this.masterChartCanvas.el?.getBoundingClientRect().left ?? 0);
28175
- if (Math.abs(position - startingEventPosition) > 5) {
28176
- let { min: xMin, max: xMax } = computeNewAxisLimits(position);
28177
- if (xMin !== undefined && xMax !== undefined) {
28178
- if (!this.hasLinearScale) {
28179
- if (this.mode === "moveInMaster" && windowSize && !this.isBarChart) {
28180
- xMin = Math.round(xMin) - this.axisOffset;
28181
- xMax = xMin + windowSize;
28182
- }
28183
- else {
28184
- xMin = Math.ceil(xMin) - this.axisOffset;
28185
- xMax = Math.floor(xMax) + this.axisOffset;
28186
- }
28187
- }
28188
- this.updateAxisLimits(xMin, xMax);
28260
+ let { min, max } = this.chart.scales.x;
28261
+ if (!this.hasLinearScale) {
28262
+ if (this.mode === "moveInMaster") {
28263
+ min = Math.round(min) - this.axisOffset;
28264
+ max = min + windowSize;
28265
+ }
28266
+ else {
28267
+ ({ min, max } = this.adjustBoundaries({ min, max }));
28189
28268
  }
28190
28269
  }
28270
+ this.updateAxisLimits(min, max);
28191
28271
  this.mode = undefined;
28192
28272
  };
28193
28273
  this.removeEventListeners = () => {
28194
- window.removeEventListener("pointermove", onDragFromMasterChart, true);
28195
- window.removeEventListener("pointerup", onPointerUpInMasterChart, true);
28274
+ window.removeEventListener("pointermove", onMasterChartDrag, true);
28275
+ window.removeEventListener("pointerup", onMasterChartPointerUp, true);
28196
28276
  };
28197
- window.addEventListener("pointermove", onDragFromMasterChart, true);
28198
- window.addEventListener("pointerup", onPointerUpInMasterChart, true);
28277
+ window.addEventListener("pointermove", onMasterChartDrag, true);
28278
+ window.addEventListener("pointerup", onMasterChartPointerUp, true);
28199
28279
  }
28200
- onPointerMoveInMasterChart(ev) {
28201
- const { offsetX: x, offsetY: y } = ev;
28280
+ onMasterChartPointerMove(ev) {
28281
+ const { offsetX: x, offsetY: y, target } = ev;
28282
+ if (!target || !this.isMasterChartAllowed) {
28283
+ return;
28284
+ }
28202
28285
  if (this.mode === undefined) {
28203
- const target = ev.target;
28204
28286
  if (!this.masterChart?.chartArea) {
28205
28287
  target["style"].cursor = "default";
28206
28288
  return;
@@ -28222,14 +28304,14 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28222
28304
  }
28223
28305
  }
28224
28306
  }
28225
- onMouseLeaveMasterChart(ev) {
28307
+ onMasterChartMouseLeave(ev) {
28226
28308
  const target = ev.target;
28227
- if (!target) {
28309
+ if (!target || !this.isMasterChartAllowed) {
28228
28310
  return;
28229
28311
  }
28230
28312
  target["style"].cursor = "default";
28231
28313
  }
28232
- onDoubleClickInMasterChart(ev) {
28314
+ onMasterChartDoubleClick(ev) {
28233
28315
  this.mode = undefined;
28234
28316
  const position = ev.offsetX;
28235
28317
  if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
@@ -28246,33 +28328,25 @@ class ZoomableChartJsComponent extends ChartJsComponent {
28246
28328
  }
28247
28329
  ev.preventDefault();
28248
28330
  ev.stopPropagation();
28249
- let { min: xMin, max: xMax } = this.store.currentAxesLimits[this.chartId]?.x ?? this.chart.scales.x;
28331
+ let { min, max } = this.store.currentAxesLimits[this.chartId]?.x ?? this.chart.scales.x;
28250
28332
  const originalAxisLimits = this.store.originalAxisLimits[this.chartId].x;
28251
28333
  if (!originalAxisLimits) {
28252
28334
  return;
28253
28335
  }
28254
- let originalXMin = originalAxisLimits.min;
28255
- let originalXMax = originalAxisLimits.max;
28256
- if (this.hasLinearScale) {
28257
- originalXMin = Math.ceil(originalXMin) - this.axisOffset;
28258
- originalXMax = Math.floor(originalXMax) + this.axisOffset;
28259
- }
28260
28336
  if (Math.abs(position - lowerBound) < 5) {
28261
- // Reset to original min
28262
- xMin = originalXMin;
28337
+ min = originalAxisLimits.min;
28263
28338
  }
28264
28339
  else if (Math.abs(position - upperBound) < 5) {
28265
- xMax = originalXMax;
28340
+ max = originalAxisLimits.max;
28266
28341
  }
28267
28342
  else if (lowerBound < position && position < upperBound) {
28268
- // Reset to original limits
28269
- xMin = originalXMin;
28270
- xMax = originalXMax;
28343
+ min = originalAxisLimits.min;
28344
+ max = originalAxisLimits.max;
28271
28345
  }
28272
28346
  else {
28273
28347
  return;
28274
28348
  }
28275
- this.updateAxisLimits(xMin, xMax);
28349
+ this.updateAxisLimits(min, max);
28276
28350
  }
28277
28351
  }
28278
28352
 
@@ -31375,7 +31449,7 @@ function escapeQueryNameSpaces(query) {
31375
31449
  return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
31376
31450
  }
31377
31451
 
31378
- function getChartMenuActions(figureId, onFigureDeleted, env) {
31452
+ function getChartMenuActions(figureId, env) {
31379
31453
  const chartId = env.model.getters.getChartIdFromFigureId(figureId);
31380
31454
  if (!chartId) {
31381
31455
  return [];
@@ -31395,11 +31469,11 @@ function getChartMenuActions(figureId, onFigureDeleted, env) {
31395
31469
  getCutMenuItem(figureId, env),
31396
31470
  getCopyAsImageMenuItem(figureId, env),
31397
31471
  getDownloadChartMenuItem(figureId, env),
31398
- getDeleteMenuItem(figureId, onFigureDeleted, env),
31472
+ getDeleteMenuItem(figureId, env),
31399
31473
  ];
31400
31474
  return createActions(menuItemSpecs).filter((action) => env.model.getters.isReadonly() ? action.isReadonlyAllowed : true);
31401
31475
  }
31402
- function getImageMenuActions(figureId, onFigureDeleted, env) {
31476
+ function getImageMenuActions(figureId, env) {
31403
31477
  const menuItemSpecs = [
31404
31478
  getCopyMenuItem(figureId, env, _t("Image copied to clipboard")),
31405
31479
  getCutMenuItem(figureId, env),
@@ -31442,11 +31516,11 @@ function getImageMenuActions(figureId, onFigureDeleted, env) {
31442
31516
  },
31443
31517
  icon: "o-spreadsheet-Icon.DOWNLOAD",
31444
31518
  },
31445
- getDeleteMenuItem(figureId, onFigureDeleted, env),
31519
+ getDeleteMenuItem(figureId, env),
31446
31520
  ];
31447
31521
  return createActions(menuItemSpecs);
31448
31522
  }
31449
- function getCarouselMenuActions(figureId, onFigureDeleted, env) {
31523
+ function getCarouselMenuActions(figureId, env) {
31450
31524
  const isChartSelected = (env) => env.model.getters.getSelectedCarouselItem(figureId)?.type === "chart";
31451
31525
  const menuItemSpecs = [
31452
31526
  {
@@ -31465,7 +31539,7 @@ function getCarouselMenuActions(figureId, onFigureDeleted, env) {
31465
31539
  },
31466
31540
  { ...getCutMenuItem(figureId, env), name: _t("Cut carousel") },
31467
31541
  {
31468
- ...getDeleteMenuItem(figureId, onFigureDeleted, env),
31542
+ ...getDeleteMenuItem(figureId, env),
31469
31543
  name: _t("Delete carousel"),
31470
31544
  separator: true,
31471
31545
  },
@@ -31608,7 +31682,7 @@ function getDownloadChartMenuItem(figureId, env) {
31608
31682
  isReadonlyAllowed: true,
31609
31683
  };
31610
31684
  }
31611
- function getDeleteMenuItem(figureId, onFigureDeleted, env) {
31685
+ function getDeleteMenuItem(figureId, env) {
31612
31686
  return {
31613
31687
  id: "delete",
31614
31688
  name: _t("Delete"),
@@ -31617,7 +31691,6 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
31617
31691
  sheetId: env.model.getters.getActiveSheetId(),
31618
31692
  figureId,
31619
31693
  });
31620
- onFigureDeleted();
31621
31694
  },
31622
31695
  icon: "o-spreadsheet-Icon.TRASH",
31623
31696
  };
@@ -32430,7 +32503,7 @@ class ChartDashboardMenu extends Component {
32430
32503
  this.menuState.isOpen = true;
32431
32504
  this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
32432
32505
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32433
- this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
32506
+ this.menuState.menuItems = getChartMenuActions(figureId, this.env);
32434
32507
  }
32435
32508
  get fullScreenMenuItem() {
32436
32509
  if (!this.props.hasFullScreenButton) {
@@ -32457,7 +32530,6 @@ class CarouselFigure extends Component {
32457
32530
  static template = "o-spreadsheet-CarouselFigure";
32458
32531
  static props = {
32459
32532
  figureUI: Object,
32460
- onFigureDeleted: Function,
32461
32533
  editFigureStyle: { type: Function, optional: true },
32462
32534
  isFullScreen: { type: Boolean, optional: true },
32463
32535
  openContextMenu: { type: Function, optional: true },
@@ -32606,7 +32678,6 @@ class ChartFigure extends Component {
32606
32678
  static template = "o-spreadsheet-ChartFigure";
32607
32679
  static props = {
32608
32680
  figureUI: Object,
32609
- onFigureDeleted: Function,
32610
32681
  editFigureStyle: { type: Function, optional: true },
32611
32682
  isFullScreen: { type: Boolean, optional: true },
32612
32683
  openContextMenu: { type: Function, optional: true },
@@ -32640,7 +32711,6 @@ class ImageFigure extends Component {
32640
32711
  static template = "o-spreadsheet-ImageFigure";
32641
32712
  static props = {
32642
32713
  figureUI: Object,
32643
- onFigureDeleted: Function,
32644
32714
  editFigureStyle: { type: Function, optional: true },
32645
32715
  openContextMenu: { type: Function, optional: true },
32646
32716
  };
@@ -32759,13 +32829,11 @@ class FigureComponent extends Component {
32759
32829
  figureUI: Object,
32760
32830
  style: { type: String, optional: true },
32761
32831
  class: { type: String, optional: true },
32762
- onFigureDeleted: { type: Function, optional: true },
32763
32832
  onMouseDown: { type: Function, optional: true },
32764
32833
  onClickAnchor: { type: Function, optional: true },
32765
32834
  };
32766
32835
  static components = { MenuPopover };
32767
32836
  static defaultProps = {
32768
- onFigureDeleted: () => { },
32769
32837
  onMouseDown: () => { },
32770
32838
  onClickAnchor: () => { },
32771
32839
  };
@@ -32843,9 +32911,6 @@ class FigureComponent extends Component {
32843
32911
  this.props.figureUI.id,
32844
32912
  this.figureRef.el,
32845
32913
  ]);
32846
- onWillUnmount(() => {
32847
- this.props.onFigureDeleted();
32848
- });
32849
32914
  }
32850
32915
  clickAnchor(dirX, dirY, ev) {
32851
32916
  this.props.onClickAnchor(dirX, dirY, ev);
@@ -32869,7 +32934,6 @@ class FigureComponent extends Component {
32869
32934
  sheetId: this.env.model.getters.getActiveSheetId(),
32870
32935
  figureId: this.props.figureUI.id,
32871
32936
  });
32872
- this.props.onFigureDeleted();
32873
32937
  ev.preventDefault();
32874
32938
  ev.stopPropagation();
32875
32939
  break;
@@ -32962,7 +33026,7 @@ class FigureComponent extends Component {
32962
33026
  this.menuState.anchorRect = anchorRect;
32963
33027
  this.menuState.menuItems = figureRegistry
32964
33028
  .get(this.props.figureUI.tag)
32965
- .menuBuilder(this.props.figureUI.id, this.props.onFigureDeleted, this.env);
33029
+ .menuBuilder(this.props.figureUI.id, this.env);
32966
33030
  }
32967
33031
  editWrapperStyle(properties) {
32968
33032
  if (this.figureWrapperRef.el) {
@@ -33334,7 +33398,7 @@ criterionEvaluatorRegistry.add("dateIs", {
33334
33398
  return false;
33335
33399
  }
33336
33400
  if (["lastWeek", "lastMonth", "lastYear"].includes(criterion.dateValue)) {
33337
- const today = jsDateToRoundNumber(DateTime.now());
33401
+ const today = Math.floor(jsDateToNumber(DateTime.now()));
33338
33402
  return isDateBetween(dateValue, today, criterionValue);
33339
33403
  }
33340
33404
  return areDatesSameDay(dateValue, criterionValue);
@@ -34403,7 +34467,8 @@ class Composer extends Component {
34403
34467
  assistantStyle["max-height"] = `${availableSpaceAbove - CLOSE_ICON_RADIUS}px`;
34404
34468
  // render top
34405
34469
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
34406
- assistantStyle.transform = `translate(0, calc(-100% - ${cellHeight + 3}px))`;
34470
+ assistantStyle.top = `-3px`;
34471
+ assistantStyle.transform = `translate(0, -100%)`;
34407
34472
  }
34408
34473
  if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
34409
34474
  // render left
@@ -35194,7 +35259,7 @@ class AbstractComposerStore extends SpreadsheetStore {
35194
35259
  }
35195
35260
  this.selectionStart = start;
35196
35261
  this.selectionEnd = end;
35197
- this.editionMode = "editing";
35262
+ this.stopComposerRangeSelection();
35198
35263
  this.computeFormulaCursorContext();
35199
35264
  this.computeParenthesisRelatedToCursor();
35200
35265
  this.updateAutoCompleteProvider();
@@ -37447,6 +37512,12 @@ class SelectionInput extends Component {
37447
37512
  }
37448
37513
  setup() {
37449
37514
  useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
37515
+ useEffect(() => {
37516
+ // Check the offsetParent to know if the input or an ancestor is `display: none` (eg. when changing side panel tab)
37517
+ if (this.store.hasFocus && this.selectionRef.el?.offsetParent === null) {
37518
+ this.reset();
37519
+ }
37520
+ });
37450
37521
  this.store = useLocalStore(SelectionInputStore, this.props.ranges, this.props.hasSingleRange || false, this.props.colors, this.props.disabledRanges);
37451
37522
  onWillUpdateProps((nextProps) => {
37452
37523
  if (nextProps.ranges.join() !== this.store.selectionInputValues.join()) {
@@ -44191,18 +44262,22 @@ function dropCommands(initialMessages, commandType) {
44191
44262
  return messages;
44192
44263
  }
44193
44264
  function fixChartDefinitions(data, initialMessages) {
44265
+ /**
44266
+ * Revisions created after version 18.5.1 contain the full chart definition in the command
44267
+ * if the data was alreay updated to 18.5.1, then those older revision cannot (by definition) be reaplied
44268
+ * and should not be replayed.
44269
+ * FIXME: every command should be versionned when upgraded to allow finer tuning.
44270
+ */
44271
+ if (!data.version || compareVersions(String(data.version), "18.5.1") >= 0) {
44272
+ return initialMessages;
44273
+ }
44194
44274
  const messages = [];
44195
44275
  const map = {};
44196
44276
  for (const sheet of data.sheets || []) {
44197
44277
  sheet.figures?.forEach((figure) => {
44198
44278
  if (figure.tag === "chart") {
44199
44279
  // chart definition
44200
- if (data.version && compareVersions(String(data.version), "18.5.1") <= 0) {
44201
- map[figure.data.chartId] = figure.data;
44202
- }
44203
- else {
44204
- map[figure.id] = figure.data;
44205
- }
44280
+ map[figure.id] = figure.data;
44206
44281
  }
44207
44282
  });
44208
44283
  }
@@ -45226,7 +45301,7 @@ class FormulaFingerprintStore extends SpreadsheetStore {
45226
45301
  const leftOffset = isLeftUnbounded || left?.colFixed ? 0 : colCellOffset;
45227
45302
  const topOffset = isTopUnbounded || left?.rowFixed ? 0 : rowCellOffset;
45228
45303
  const isRightFixed = (!right && left?.colFixed) || right?.colFixed;
45229
- const isBottomFixed = (!right && left.rowFixed) || right?.rowFixed;
45304
+ const isBottomFixed = (!right && left?.rowFixed) || right?.rowFixed;
45230
45305
  const isRightUnbounded = range.unboundedZone.right === undefined;
45231
45306
  const isBottomUnbounded = range.unboundedZone.bottom === undefined;
45232
45307
  const rightOffset = isRightUnbounded || isRightFixed ? 0 : colCellOffset;
@@ -47785,13 +47860,42 @@ const dateGranularities = [
47785
47860
  pivotRegistry.add("SPREADSHEET", {
47786
47861
  ui: SpreadsheetPivot,
47787
47862
  definition: SpreadsheetPivotRuntimeDefinition,
47788
- externalData: false,
47789
47863
  dateGranularities: [...dateGranularities],
47790
47864
  datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
47791
47865
  isMeasureCandidate: (field) => field.type !== "boolean",
47792
47866
  isGroupable: () => true,
47793
47867
  canHaveCustomGroup: (field) => field.type === "char" && !field.isCustomField,
47868
+ adaptRanges: (getters, definition, applyChange) => {
47869
+ if (definition.type !== "SPREADSHEET" || !definition.dataSet) {
47870
+ return definition;
47871
+ }
47872
+ const { sheetId, zone } = definition.dataSet;
47873
+ const range = getters.getRangeFromZone(sheetId, zone);
47874
+ const adaptedRange = adaptPivotRange(range, applyChange);
47875
+ if (adaptedRange === range) {
47876
+ return definition;
47877
+ }
47878
+ const dataSet = adaptedRange && {
47879
+ sheetId: adaptedRange.sheetId,
47880
+ zone: adaptedRange.zone,
47881
+ };
47882
+ return { ...definition, dataSet };
47883
+ },
47794
47884
  });
47885
+ function adaptPivotRange(range, applyChange) {
47886
+ if (!range) {
47887
+ return undefined;
47888
+ }
47889
+ const change = applyChange(range);
47890
+ switch (change.changeType) {
47891
+ case "NONE":
47892
+ return range;
47893
+ case "REMOVE":
47894
+ return undefined;
47895
+ default:
47896
+ return change.range;
47897
+ }
47898
+ }
47795
47899
 
47796
47900
  const pivotProperties = {
47797
47901
  name: _t("See pivot properties"),
@@ -49062,7 +49166,6 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
49062
49166
  }
49063
49167
  get highlights() {
49064
49168
  const position = this.model.getters.getActivePosition();
49065
- const cell = this.getters.getEvaluatedCell(position);
49066
49169
  const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
49067
49170
  const zone = spreader
49068
49171
  ? this.model.getters.getSpreadZone(spreader, { ignoreSpillError: true })
@@ -49070,10 +49173,11 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
49070
49173
  if (!zone) {
49071
49174
  return [];
49072
49175
  }
49176
+ const isArrayFormulaBlocked = this.model.getters.isArrayFormulaSpillBlocked(spreader ?? position);
49073
49177
  return [
49074
49178
  {
49075
49179
  range: this.model.getters.getRangeFromZone(position.sheetId, zone),
49076
- dashed: cell.value === CellErrorType.SpilledBlocked,
49180
+ dashed: isArrayFormulaBlocked,
49077
49181
  color: "#17A2B8",
49078
49182
  noFill: true,
49079
49183
  thinLine: true,
@@ -50480,9 +50584,7 @@ css /*SCSS*/ `
50480
50584
  */
50481
50585
  class FiguresContainer extends Component {
50482
50586
  static template = "o-spreadsheet-FiguresContainer";
50483
- static props = {
50484
- onFigureDeleted: Function,
50485
- };
50587
+ static props = {};
50486
50588
  static components = { FigureComponent };
50487
50589
  dnd = useState({
50488
50590
  draggedFigure: undefined,
@@ -50692,7 +50794,6 @@ class FiguresContainer extends Component {
50692
50794
  carouselFigureId: this.dnd.overlappingCarousel.id,
50693
50795
  chartFigureId: figureUI.id,
50694
50796
  });
50695
- this.props.onFigureDeleted();
50696
50797
  }
50697
50798
  this.dnd.draggedFigure = undefined;
50698
50799
  this.dnd.horizontalSnap = undefined;
@@ -50918,16 +51019,16 @@ css /* scss */ `
50918
51019
  `;
50919
51020
  class GridAddRowsFooter extends Component {
50920
51021
  static template = "o-spreadsheet-GridAddRowsFooter";
50921
- static props = {
50922
- focusGrid: Function,
50923
- };
51022
+ static props = {};
50924
51023
  static components = { ValidationMessages };
51024
+ DOMFocusableElementStore;
50925
51025
  inputRef = useRef("inputRef");
50926
51026
  state = useState({
50927
51027
  inputValue: "100",
50928
51028
  errorFlag: false,
50929
51029
  });
50930
51030
  setup() {
51031
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
50931
51032
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
50932
51033
  }
50933
51034
  get addRowsPosition() {
@@ -50945,7 +51046,7 @@ class GridAddRowsFooter extends Component {
50945
51046
  }
50946
51047
  onKeydown(ev) {
50947
51048
  if (ev.key.toUpperCase() === "ESCAPE") {
50948
- this.props.focusGrid();
51049
+ this.focusDefaultElement();
50949
51050
  }
50950
51051
  else if (ev.key.toUpperCase() === "ENTER") {
50951
51052
  this.onConfirm();
@@ -50972,7 +51073,7 @@ class GridAddRowsFooter extends Component {
50972
51073
  quantity,
50973
51074
  dimension: "ROW",
50974
51075
  });
50975
- this.props.focusGrid();
51076
+ this.focusDefaultElement();
50976
51077
  // After adding new rows, scroll down to the new last row
50977
51078
  const { scrollX } = this.env.model.getters.getActiveSheetScrollInfo();
50978
51079
  const { end } = this.env.model.getters.getRowDimensions(activeSheetId, rowNumber + quantity - 1);
@@ -50985,7 +51086,12 @@ class GridAddRowsFooter extends Component {
50985
51086
  if (this.inputRef.el !== document.activeElement || ev.target === this.inputRef.el) {
50986
51087
  return;
50987
51088
  }
50988
- this.props.focusGrid();
51089
+ this.focusDefaultElement();
51090
+ }
51091
+ focusDefaultElement() {
51092
+ if (document.activeElement === this.inputRef.el) {
51093
+ this.DOMFocusableElementStore.focus();
51094
+ }
50989
51095
  }
50990
51096
  }
50991
51097
 
@@ -51260,7 +51366,6 @@ class GridOverlay extends Component {
51260
51366
  onCellClicked: { type: Function, optional: true },
51261
51367
  onCellRightClicked: { type: Function, optional: true },
51262
51368
  onGridResized: { type: Function, optional: true },
51263
- onFigureDeleted: { type: Function, optional: true },
51264
51369
  onGridMoved: Function,
51265
51370
  gridOverlayDimensions: String,
51266
51371
  slots: { type: Object, optional: true },
@@ -51275,7 +51380,6 @@ class GridOverlay extends Component {
51275
51380
  onCellClicked: () => { },
51276
51381
  onCellRightClicked: () => { },
51277
51382
  onGridResized: () => { },
51278
- onFigureDeleted: () => { },
51279
51383
  };
51280
51384
  gridOverlay = useRef("gridOverlay");
51281
51385
  cellPopovers;
@@ -53821,7 +53925,9 @@ class FontSizeEditor extends Component {
53821
53925
  inputRef = useRef("inputFontSize");
53822
53926
  rootEditorRef = useRef("FontSizeEditor");
53823
53927
  fontSizeListRef = useRef("fontSizeList");
53928
+ DOMFocusableElementStore;
53824
53929
  setup() {
53930
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
53825
53931
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
53826
53932
  }
53827
53933
  get popoverProps() {
@@ -53875,6 +53981,13 @@ class FontSizeEditor extends Component {
53875
53981
  }
53876
53982
  this.props.onToggle?.();
53877
53983
  }
53984
+ if (ev.key === "Tab") {
53985
+ ev.preventDefault();
53986
+ ev.stopPropagation();
53987
+ this.closeFontList();
53988
+ this.DOMFocusableElementStore.focus();
53989
+ return;
53990
+ }
53878
53991
  }
53879
53992
  }
53880
53993
 
@@ -55210,19 +55323,6 @@ class ChartWithAxisDesignPanel extends Component {
55210
55323
  }
55211
55324
  }
55212
55325
 
55213
- class ChartShowDataMarkers extends Component {
55214
- static template = "o-spreadsheet-ChartShowDataMarkers";
55215
- static components = {
55216
- Checkbox,
55217
- };
55218
- static props = {
55219
- chartId: String,
55220
- definition: Object,
55221
- updateChart: Function,
55222
- canUpdateChart: Function,
55223
- };
55224
- }
55225
-
55226
55326
  class GenericZoomableChartDesignPanel extends ChartWithAxisDesignPanel {
55227
55327
  static template = "o-spreadsheet-GenericZoomableChartDesignPanel";
55228
55328
  static components = {
@@ -55236,6 +55336,26 @@ class GenericZoomableChartDesignPanel extends ChartWithAxisDesignPanel {
55236
55336
  }
55237
55337
  }
55238
55338
 
55339
+ class BarChartDesignPanel extends GenericZoomableChartDesignPanel {
55340
+ static template = "o-spreadsheet-BarChartDesignPanel";
55341
+ get isZoomable() {
55342
+ return !this.props.definition.horizontal;
55343
+ }
55344
+ }
55345
+
55346
+ class ChartShowDataMarkers extends Component {
55347
+ static template = "o-spreadsheet-ChartShowDataMarkers";
55348
+ static components = {
55349
+ Checkbox,
55350
+ };
55351
+ static props = {
55352
+ chartId: String,
55353
+ definition: Object,
55354
+ updateChart: Function,
55355
+ canUpdateChart: Function,
55356
+ };
55357
+ }
55358
+
55239
55359
  class ComboChartDesignPanel extends GenericZoomableChartDesignPanel {
55240
55360
  static template = "o-spreadsheet-ComboChartDesignPanel";
55241
55361
  static components = {
@@ -56164,7 +56284,7 @@ chartSidePanelComponentRegistry
56164
56284
  })
56165
56285
  .add("bar", {
56166
56286
  configuration: BarConfigPanel,
56167
- design: GenericZoomableChartDesignPanel,
56287
+ design: BarChartDesignPanel,
56168
56288
  })
56169
56289
  .add("combo", {
56170
56290
  configuration: GenericChartConfigPanel,
@@ -56525,7 +56645,7 @@ function useHighlights(highlightProvider) {
56525
56645
  }
56526
56646
 
56527
56647
  css /* scss */ `
56528
- .o-cf-preview {
56648
+ .o-spreadsheet .o-cf-preview {
56529
56649
  &.o-cf-cursor-ptr {
56530
56650
  cursor: pointer;
56531
56651
  }
@@ -56533,6 +56653,7 @@ css /* scss */ `
56533
56653
  border-bottom: 1px solid ${GRAY_300};
56534
56654
  height: 80px;
56535
56655
  padding: 10px;
56656
+ box-sizing: border-box;
56536
56657
  position: relative;
56537
56658
  cursor: pointer;
56538
56659
  &:hover,
@@ -56546,7 +56667,6 @@ css /* scss */ `
56546
56667
  .o-cf-preview-icon {
56547
56668
  border: 1px solid ${GRAY_300};
56548
56669
  background-color: #fff;
56549
- position: absolute;
56550
56670
  height: 50px;
56551
56671
  width: 50px;
56552
56672
  .o-icon {
@@ -56555,12 +56675,6 @@ css /* scss */ `
56555
56675
  }
56556
56676
  }
56557
56677
  .o-cf-preview-description {
56558
- left: 65px;
56559
- margin-bottom: auto;
56560
- margin-right: 8px;
56561
- margin-top: auto;
56562
- position: relative;
56563
- width: 142px;
56564
56678
  .o-cf-preview-description-rule {
56565
56679
  margin-bottom: 4px;
56566
56680
  max-height: 2.8em;
@@ -56570,16 +56684,11 @@ css /* scss */ `
56570
56684
  font-size: 12px;
56571
56685
  }
56572
56686
  }
56573
- .o-cf-delete {
56574
- left: 90%;
56575
- top: 39%;
56576
- position: absolute;
56577
- }
56578
56687
  &:not(:hover):not(.o-cf-dragging) .o-cf-drag-handle {
56579
56688
  display: none !important;
56580
56689
  }
56581
56690
  .o-cf-drag-handle {
56582
- left: -8px;
56691
+ left: 2px;
56583
56692
  cursor: move;
56584
56693
  .o-icon {
56585
56694
  width: 6px;
@@ -62384,10 +62493,10 @@ class BordersPlugin extends CorePlugin {
62384
62493
  // existingBorderSideToClear[side] = true means we should clear the border on that
62385
62494
  // side of the existing adjacent zone before adding the new border.
62386
62495
  const existingBorderSideToClear = {
62387
- left: force || !!newBorder?.right,
62388
- right: force || !!newBorder?.left,
62389
- top: force || !!newBorder?.bottom,
62390
- bottom: force || !!newBorder?.top,
62496
+ left: !!newBorder?.right,
62497
+ right: !!newBorder?.left,
62498
+ top: !!newBorder?.bottom,
62499
+ bottom: !!newBorder?.top,
62391
62500
  };
62392
62501
  let editingZone = [zone];
62393
62502
  for (const existingBorder of this.borders[sheetId] ?? []) {
@@ -64256,7 +64365,8 @@ class FigurePlugin extends CorePlugin {
64256
64365
  }
64257
64366
  break;
64258
64367
  case "DUPLICATE_SHEET": {
64259
- for (const figureId in this.figures[cmd.sheetId]) {
64368
+ for (const figure of this.getFigures(cmd.sheetId)) {
64369
+ const figureId = figure.id;
64260
64370
  const fig = this.figures[cmd.sheetId]?.[figureId];
64261
64371
  if (!fig) {
64262
64372
  continue;
@@ -65379,6 +65489,7 @@ function rangeToMerge(mergeId, range) {
65379
65489
  class RangeAdapter {
65380
65490
  getters;
65381
65491
  providers = [];
65492
+ isAdaptingRanges = false;
65382
65493
  constructor(getters) {
65383
65494
  this.getters = getters;
65384
65495
  }
@@ -65410,6 +65521,9 @@ class RangeAdapter {
65410
65521
  }
65411
65522
  beforeHandle(command) { }
65412
65523
  handle(cmd) {
65524
+ if (this.isAdaptingRanges) {
65525
+ throw new Error("Plugins cannot dispatch commands during adaptRanges phase");
65526
+ }
65413
65527
  const rangeAdapter = getRangeAdapter(cmd);
65414
65528
  if (rangeAdapter?.applyChange) {
65415
65529
  this.executeOnAllRanges(rangeAdapter.applyChange, rangeAdapter.sheetId, rangeAdapter.sheetName);
@@ -65432,10 +65546,12 @@ class RangeAdapter {
65432
65546
  };
65433
65547
  }
65434
65548
  executeOnAllRanges(adaptRange, sheetId, sheetName) {
65549
+ this.isAdaptingRanges = true;
65435
65550
  const func = this.verifyRangeRemoved(adaptRange);
65436
65551
  for (const provider of this.providers) {
65437
65552
  provider(func, sheetId, sheetName);
65438
65553
  }
65554
+ this.isAdaptingRanges = false;
65439
65555
  }
65440
65556
  /**
65441
65557
  * Stores the functions bound to each plugin to be able to iterate over all ranges of the application,
@@ -65752,7 +65868,7 @@ class SheetPlugin extends CorePlugin {
65752
65868
  break;
65753
65869
  case "CREATE_SHEET":
65754
65870
  const sheet = this.createSheet(cmd.sheetId, cmd.name || this.getNextSheetName(), cmd.cols || 26, cmd.rows || 100, cmd.position);
65755
- this.history.update("sheetIdsMapName", sheet.name, sheet.id);
65871
+ this.history.update("sheetIdsMapName", toStandardizedSheetName(sheet.name), sheet.id);
65756
65872
  break;
65757
65873
  case "MOVE_SHEET":
65758
65874
  this.moveSheet(cmd.sheetId, cmd.delta);
@@ -65819,7 +65935,7 @@ class SheetPlugin extends CorePlugin {
65819
65935
  // that depends on a sheet not already imported will not be able to be
65820
65936
  // compiled
65821
65937
  for (const sheet of data.sheets) {
65822
- this.sheetIdsMapName[sheet.name] = sheet.id;
65938
+ this.sheetIdsMapName[toStandardizedSheetName(sheet.name)] = sheet.id;
65823
65939
  }
65824
65940
  for (const sheetData of data.sheets) {
65825
65941
  const name = sheetData.name || "Sheet" + (Object.keys(this.sheets).length + 1);
@@ -65909,12 +66025,7 @@ class SheetPlugin extends CorePlugin {
65909
66025
  }
65910
66026
  getSheetIdByName(name) {
65911
66027
  if (name) {
65912
- const unquotedName = getUnquotedSheetName(name);
65913
- for (const key in this.sheetIdsMapName) {
65914
- if (isSheetNameEqual(key, unquotedName)) {
65915
- return this.sheetIdsMapName[key];
65916
- }
65917
- }
66028
+ return this.sheetIdsMapName[toStandardizedSheetName(name)];
65918
66029
  }
65919
66030
  return undefined;
65920
66031
  }
@@ -66214,8 +66325,8 @@ class SheetPlugin extends CorePlugin {
66214
66325
  const oldName = sheet.name;
66215
66326
  this.history.update("sheets", sheet.id, "name", name.trim());
66216
66327
  const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);
66217
- delete sheetIdsMapName[oldName];
66218
- sheetIdsMapName[name] = sheet.id;
66328
+ delete sheetIdsMapName[toStandardizedSheetName(oldName)];
66329
+ sheetIdsMapName[toStandardizedSheetName(name)] = sheet.id;
66219
66330
  this.history.update("sheetIdsMapName", sheetIdsMapName);
66220
66331
  }
66221
66332
  hideSheet(sheetId) {
@@ -66253,7 +66364,7 @@ class SheetPlugin extends CorePlugin {
66253
66364
  });
66254
66365
  }
66255
66366
  const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);
66256
- sheetIdsMapName[newSheet.name] = newSheet.id;
66367
+ sheetIdsMapName[toStandardizedSheetName(newSheet.name)] = newSheet.id;
66257
66368
  this.history.update("sheetIdsMapName", sheetIdsMapName);
66258
66369
  }
66259
66370
  getDuplicateSheetName(sheetName) {
@@ -66270,7 +66381,7 @@ class SheetPlugin extends CorePlugin {
66270
66381
  orderedSheetIds.splice(currentIndex, 1);
66271
66382
  this.history.update("orderedSheetIds", orderedSheetIds);
66272
66383
  const sheetIdsMapName = Object.assign({}, this.sheetIdsMapName);
66273
- delete sheetIdsMapName[name];
66384
+ delete sheetIdsMapName[toStandardizedSheetName(name)];
66274
66385
  this.history.update("sheetIdsMapName", sheetIdsMapName);
66275
66386
  }
66276
66387
  /**
@@ -67590,6 +67701,18 @@ class PivotCorePlugin extends CorePlugin {
67590
67701
  }
67591
67702
  }
67592
67703
  adaptRanges(applyChange) {
67704
+ for (const pivotId in this.pivots) {
67705
+ const definition = deepCopy(this.pivots[pivotId]?.definition);
67706
+ if (!definition) {
67707
+ continue;
67708
+ }
67709
+ const newDefinition = pivotRegistry
67710
+ .get(definition.type)
67711
+ ?.adaptRanges?.(this.getters, definition, applyChange);
67712
+ if (newDefinition && !deepEquals(definition, newDefinition)) {
67713
+ this.history.update("pivots", pivotId, "definition", newDefinition);
67714
+ }
67715
+ }
67593
67716
  for (const sheetId in this.compiledMeasureFormulas) {
67594
67717
  for (const formulaString in this.compiledMeasureFormulas[sheetId]) {
67595
67718
  const compiledFormula = this.compiledMeasureFormulas[sheetId][formulaString];
@@ -67864,20 +67987,6 @@ class SettingsPlugin extends CorePlugin {
67864
67987
  }
67865
67988
  }
67866
67989
 
67867
- function adaptPivotRange(range, applyChange) {
67868
- if (!range) {
67869
- return undefined;
67870
- }
67871
- const change = applyChange(range);
67872
- switch (change.changeType) {
67873
- case "NONE":
67874
- return range;
67875
- case "REMOVE":
67876
- return undefined;
67877
- default:
67878
- return change.range;
67879
- }
67880
- }
67881
67990
  class SpreadsheetPivotCorePlugin extends CorePlugin {
67882
67991
  allowDispatch(cmd) {
67883
67992
  switch (cmd.type) {
@@ -67888,24 +67997,6 @@ class SpreadsheetPivotCorePlugin extends CorePlugin {
67888
67997
  }
67889
67998
  return "Success" /* CommandResult.Success */;
67890
67999
  }
67891
- adaptRanges(applyChange) {
67892
- for (const pivotId of this.getters.getPivotIds()) {
67893
- const definition = this.getters.getPivotCoreDefinition(pivotId);
67894
- if (definition.type !== "SPREADSHEET") {
67895
- continue;
67896
- }
67897
- if (definition.dataSet) {
67898
- const { sheetId, zone } = definition.dataSet;
67899
- const range = this.getters.getRangeFromZone(sheetId, zone);
67900
- const adaptedRange = adaptPivotRange(range, applyChange);
67901
- const dataSet = adaptedRange && {
67902
- sheetId: adaptedRange.sheetId,
67903
- zone: adaptedRange.zone,
67904
- };
67905
- this.dispatch("UPDATE_PIVOT", { pivotId, pivot: { ...definition, dataSet } });
67906
- }
67907
- }
67908
- }
67909
68000
  checkDataSetValidity(definition) {
67910
68001
  if (definition.type === "SPREADSHEET" && definition.dataSet) {
67911
68002
  const { zone, sheetId } = definition.dataSet;
@@ -68904,13 +68995,6 @@ class ZoneSet {
68904
68995
  }
68905
68996
  return result;
68906
68997
  }
68907
- size() {
68908
- let size = 0;
68909
- for (const profile of this.profiles.values()) {
68910
- size += profile.length;
68911
- }
68912
- return size / 2;
68913
- }
68914
68998
  /**
68915
68999
  * iterator of all the zones in the ZoneSet
68916
69000
  */
@@ -68992,13 +69076,6 @@ class RangeSet {
68992
69076
  clear() {
68993
69077
  this.setsBySheetId = {};
68994
69078
  }
68995
- size() {
68996
- let size = 0;
68997
- for (const sheetId in this.setsBySheetId) {
68998
- size += this.setsBySheetId[sheetId].size();
68999
- }
69000
- return size;
69001
- }
69002
69079
  isEmpty() {
69003
69080
  for (const sheetId in this.setsBySheetId) {
69004
69081
  if (!this.setsBySheetId[sheetId].isEmpty()) {
@@ -69502,6 +69579,9 @@ class Evaluator {
69502
69579
  const arrayFormulas = this.spreadingRelations.searchFormulaPositionsSpreadingOn(position.sheetId, positionToZone(position));
69503
69580
  return arrayFormulas.find((position) => !this.blockedArrayFormulas.has(position));
69504
69581
  }
69582
+ isArrayFormulaSpillBlocked(position) {
69583
+ return this.blockedArrayFormulas.has(position);
69584
+ }
69505
69585
  updateDependencies(position) {
69506
69586
  // removing dependencies is slow because it requires
69507
69587
  // to traverse the entire r-tree.
@@ -69513,13 +69593,8 @@ class Evaluator {
69513
69593
  addDependencies(position, dependencies) {
69514
69594
  this.formulaDependencies().addDependencies(position, dependencies);
69515
69595
  for (const range of dependencies) {
69516
- const sheetId = range.sheetId;
69517
- const { left, bottom, right, top } = range.zone;
69518
- for (let col = left; col <= right; col++) {
69519
- for (let row = top; row <= bottom; row++) {
69520
- this.computeAndSave({ sheetId, col, row });
69521
- }
69522
- }
69596
+ // ensure that all ranges are computed
69597
+ this.compilationParams.ensureRange(range, false);
69523
69598
  }
69524
69599
  }
69525
69600
  updateCompilationParameters() {
@@ -69747,6 +69822,10 @@ class Evaluator {
69747
69822
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
69748
69823
  const nbColumns = formulaReturn.length;
69749
69824
  const nbRows = formulaReturn[0].length;
69825
+ if (nbRows === 0) {
69826
+ // empty matrix
69827
+ return createEvaluatedCell({ value: 0 }, this.getters.getLocale(), cellData);
69828
+ }
69750
69829
  const resultZone = {
69751
69830
  top: formulaPosition.row,
69752
69831
  bottom: formulaPosition.row + nbRows - 1,
@@ -70017,6 +70096,7 @@ class EvaluationPlugin extends CoreViewPlugin {
70017
70096
  "getEvaluatedCellsPositions",
70018
70097
  "getSpreadZone",
70019
70098
  "getArrayFormulaSpreadingOn",
70099
+ "isArrayFormulaSpillBlocked",
70020
70100
  "isEmpty",
70021
70101
  ];
70022
70102
  shouldRebuildDependenciesGraph = true;
@@ -70136,6 +70216,9 @@ class EvaluationPlugin extends CoreViewPlugin {
70136
70216
  getArrayFormulaSpreadingOn(position) {
70137
70217
  return this.evaluator.getArrayFormulaSpreadingOn(position);
70138
70218
  }
70219
+ isArrayFormulaSpillBlocked(position) {
70220
+ return this.evaluator.isArrayFormulaSpillBlocked(position);
70221
+ }
70139
70222
  /**
70140
70223
  * Check if a zone only contains empty cells
70141
70224
  */
@@ -72217,9 +72300,7 @@ class PivotUIPlugin extends CoreViewPlugin {
72217
72300
  handle(cmd) {
72218
72301
  if (invalidateEvaluationCommands.has(cmd.type)) {
72219
72302
  for (const pivotId of this.getters.getPivotIds()) {
72220
- if (!pivotRegistry.get(this.getters.getPivotCoreDefinition(pivotId).type).externalData) {
72221
- this.setupPivot(pivotId, { recreate: true });
72222
- }
72303
+ this.setupPivot(pivotId, { recreate: true });
72223
72304
  }
72224
72305
  }
72225
72306
  switch (cmd.type) {
@@ -72441,7 +72522,7 @@ class PivotUIPlugin extends CoreViewPlugin {
72441
72522
  pivot.init({ reload: true });
72442
72523
  }
72443
72524
  setupPivot(pivotId, { recreate } = { recreate: false }) {
72444
- const definition = this.getters.getPivotCoreDefinition(pivotId);
72525
+ const definition = deepCopy(this.getters.getPivotCoreDefinition(pivotId));
72445
72526
  if (!(pivotId in this.pivots)) {
72446
72527
  const Pivot = withPivotPresentationLayer(pivotRegistry.get(definition.type).ui);
72447
72528
  this.pivots[pivotId] = new Pivot(this.custom, { definition, getters: this.getters });
@@ -81948,16 +82029,18 @@ class ClickableCellsStore extends SpreadsheetStore {
81948
82029
  get clickableCells() {
81949
82030
  const cells = [];
81950
82031
  const getters = this.getters;
81951
- const sheetId = getters.getActiveSheetId();
81952
82032
  for (const position of this.getters.getVisibleCellPositions()) {
81953
82033
  const item = this.getClickableItem(position);
81954
82034
  if (!item) {
81955
82035
  continue;
81956
82036
  }
81957
82037
  const title = typeof item.title === "function" ? item.title(position, getters) : item.title;
81958
- const zone = getters.expandZone(sheetId, positionToZone(position));
82038
+ const rect = this.getClickableCellRect(position);
82039
+ if (!rect) {
82040
+ continue;
82041
+ }
81959
82042
  cells.push({
81960
- coordinates: getters.getVisibleRect(zone),
82043
+ coordinates: rect,
81961
82044
  position,
81962
82045
  action: item.execute,
81963
82046
  title: title || "",
@@ -81967,6 +82050,31 @@ class ClickableCellsStore extends SpreadsheetStore {
81967
82050
  }
81968
82051
  return cells;
81969
82052
  }
82053
+ getClickableCellRect(position) {
82054
+ const zone = this.getters.expandZone(position.sheetId, positionToZone(position));
82055
+ const clickableRect = this.getters.getVisibleRect(zone);
82056
+ const icons = this.getters.getCellIcons(position);
82057
+ const iconsAtPosition = {
82058
+ center: icons.find((icon) => icon.horizontalAlign === "center"),
82059
+ left: icons.find((icon) => icon.horizontalAlign === "left"),
82060
+ right: icons.find((icon) => icon.horizontalAlign === "right"),
82061
+ };
82062
+ if (iconsAtPosition.center?.onClick) {
82063
+ return undefined;
82064
+ }
82065
+ if (iconsAtPosition.right?.onClick) {
82066
+ const cellRect = this.getters.getRect(zone);
82067
+ const iconRect = this.getters.getCellIconRect(iconsAtPosition.right, cellRect);
82068
+ clickableRect.width -= iconRect.width + iconsAtPosition.right.margin;
82069
+ }
82070
+ if (iconsAtPosition.left?.onClick) {
82071
+ const cellRect = this.getters.getRect(zone);
82072
+ const iconRect = this.getters.getCellIconRect(iconsAtPosition.left, cellRect);
82073
+ clickableRect.x += iconRect.width + iconsAtPosition.left.margin;
82074
+ clickableRect.width -= iconRect.width + iconsAtPosition.left.margin;
82075
+ }
82076
+ return clickableRect;
82077
+ }
81970
82078
  }
81971
82079
 
81972
82080
  css /* scss */ `
@@ -82724,7 +82832,7 @@ class SmallBottomBar extends Component {
82724
82832
  height: this.focus === "inactive" ? "26px" : "fit-content",
82725
82833
  "max-height": `130px`,
82726
82834
  }),
82727
- showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
82835
+ showAssistant: false, // Hide assistant in small composer as it gets cropped ATM
82728
82836
  placeholder: this.composerStore.placeholder,
82729
82837
  };
82730
82838
  }
@@ -84216,7 +84324,7 @@ class Spreadsheet extends Component {
84216
84324
  document.activeElement?.contains(this.spreadsheetRef.el)) {
84217
84325
  this.focusGrid();
84218
84326
  }
84219
- }, () => [this.env.model.getters.getActiveSheetId()]);
84327
+ });
84220
84328
  useExternalListener(window, "resize", () => this.render(true));
84221
84329
  // For some reason, the wheel event is not properly registered inside templates
84222
84330
  // in Chromium-based browsers based on chromium 125
@@ -88956,6 +89064,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
88956
89064
  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, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
88957
89065
 
88958
89066
 
88959
- __info__.version = "19.0.11";
88960
- __info__.date = "2025-11-24T07:46:47.685Z";
88961
- __info__.hash = "f5bdbcc";
89067
+ __info__.version = "19.0.15";
89068
+ __info__.date = "2025-12-26T10:19:23.408Z";
89069
+ __info__.hash = "fe625c9";