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