@odoo/o-spreadsheet 17.2.4 → 17.2.6

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.4
7
- * @date 2024-04-18T16:41:38.407Z
8
- * @hash 0c66038
6
+ * @version 17.2.6
7
+ * @date 2024-05-07T10:41:20.332Z
8
+ * @hash a4f800c
9
9
  */
10
10
 
11
11
  import { reactive, useEnv, useSubEnv, useState, onWillUnmount, markRaw, toRaw, Component, useRef, onMounted, useEffect, onPatched, useComponent, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv } from '@odoo/owl';
@@ -552,7 +552,7 @@ function getAddHeaderStartIndex(position, base) {
552
552
  /**
553
553
  * Compares two objects.
554
554
  */
555
- function deepEquals(o1, o2) {
555
+ function deepEquals(o1, o2, ignoreFunctions) {
556
556
  if (o1 === o2)
557
557
  return true;
558
558
  if ((o1 && !o2) || (o2 && !o1))
@@ -568,13 +568,16 @@ function deepEquals(o1, o2) {
568
568
  }
569
569
  }
570
570
  for (const key in o1) {
571
- if (typeof o1[key] !== typeof o2[key])
571
+ const typeOfO1Key = typeof o1[key];
572
+ if (typeOfO1Key !== typeof o2[key])
572
573
  return false;
573
- if (typeof o1[key] === "object") {
574
- if (!deepEquals(o1[key], o2[key]))
574
+ if (typeOfO1Key === "object") {
575
+ if (!deepEquals(o1[key], o2[key], ignoreFunctions))
575
576
  return false;
576
577
  }
577
578
  else {
579
+ if (ignoreFunctions && typeOfO1Key === "function")
580
+ return true;
578
581
  if (o1[key] !== o2[key])
579
582
  return false;
580
583
  }
@@ -3682,21 +3685,22 @@ function toZoneWithoutBoundaryChanges(xc) {
3682
3685
  xc = xc.split("!").at(-1);
3683
3686
  }
3684
3687
  if (xc.includes("$")) {
3685
- xc = xc.replace(/\$/g, "");
3688
+ xc = xc.replaceAll("$", "");
3686
3689
  }
3687
- let ranges;
3690
+ let firstRangePart = "";
3691
+ let secondRangePart;
3688
3692
  if (xc.includes(":")) {
3689
- ranges = xc.split(":").map((x) => x.trim());
3693
+ [firstRangePart, secondRangePart] = xc.split(":");
3694
+ firstRangePart = firstRangePart.trim();
3695
+ secondRangePart = secondRangePart.trim();
3690
3696
  }
3691
3697
  else {
3692
- ranges = [xc.trim()];
3698
+ firstRangePart = xc.trim();
3693
3699
  }
3694
3700
  let top, bottom, left, right;
3695
3701
  let fullCol = false;
3696
3702
  let fullRow = false;
3697
3703
  let hasHeader = false;
3698
- const firstRangePart = ranges[0];
3699
- const secondRangePart = ranges[1] && ranges[1];
3700
3704
  if (isColReference(firstRangePart)) {
3701
3705
  left = right = lettersToNumber(firstRangePart);
3702
3706
  top = bottom = 0;
@@ -3713,7 +3717,7 @@ function toZoneWithoutBoundaryChanges(xc) {
3713
3717
  top = bottom = c.row;
3714
3718
  hasHeader = true;
3715
3719
  }
3716
- if (ranges.length === 2) {
3720
+ if (secondRangePart) {
3717
3721
  if (isColReference(secondRangePart)) {
3718
3722
  right = lettersToNumber(secondRangePart);
3719
3723
  fullCol = true;
@@ -8904,8 +8908,7 @@ class ComposerStore extends SpreadsheetStore {
8904
8908
  const exactMatch = proposals?.find((p) => p.text === tokenAtCursor.value);
8905
8909
  // remove tokens that are likely to be other parts of the formula that slipped in the token if it's a string
8906
8910
  const searchTerm = tokenAtCursor.value.replace(/[ ,\(\)]/g, "");
8907
- const initialContent = this.initialContent;
8908
- if (exactMatch && exactMatch.text !== initialContent) {
8911
+ if (exactMatch && this._currentContent !== this.initialContent) {
8909
8912
  // this means the user has chosen a proposal
8910
8913
  return;
8911
8914
  }
@@ -8913,7 +8916,7 @@ class ComposerStore extends SpreadsheetStore {
8913
8916
  proposals &&
8914
8917
  !["ARG_SEPARATOR", "LEFT_PAREN"].includes(tokenAtCursor.type)) {
8915
8918
  const filteredProposals = fuzzyLookup(searchTerm, proposals, (p) => p.fuzzySearchKey || p.text);
8916
- if (!exactMatch) {
8919
+ if (!exactMatch || filteredProposals.length > 1) {
8917
8920
  proposals = filteredProposals;
8918
8921
  }
8919
8922
  }
@@ -9048,6 +9051,7 @@ class ChartJsComponent extends Component {
9048
9051
  };
9049
9052
  canvas = useRef("graphContainer");
9050
9053
  chart;
9054
+ currentRuntime;
9051
9055
  get background() {
9052
9056
  return this.chartRuntime.background;
9053
9057
  }
@@ -9064,9 +9068,18 @@ class ChartJsComponent extends Component {
9064
9068
  setup() {
9065
9069
  onMounted(() => {
9066
9070
  const runtime = this.chartRuntime;
9067
- this.createChart(runtime.chartJsConfig);
9071
+ this.currentRuntime = runtime;
9072
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
9073
+ this.createChart(deepCopy(runtime.chartJsConfig));
9074
+ });
9075
+ onWillUnmount(() => this.chart?.destroy());
9076
+ useEffect(() => {
9077
+ const runtime = this.chartRuntime;
9078
+ if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
9079
+ this.currentRuntime = runtime;
9080
+ this.updateChartJs(deepCopy(runtime));
9081
+ }
9068
9082
  });
9069
- useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
9070
9083
  }
9071
9084
  createChart(chartData) {
9072
9085
  const canvas = this.canvas.el;
@@ -9088,8 +9101,7 @@ class ChartJsComponent extends Component {
9088
9101
  this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
9089
9102
  this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
9090
9103
  this.chart.config.options.scales = chartData.options?.scales;
9091
- // ?
9092
- this.chart.update("active");
9104
+ this.chart.update();
9093
9105
  }
9094
9106
  }
9095
9107
 
@@ -19452,7 +19464,7 @@ function truncateLabel(label) {
19452
19464
  /**
19453
19465
  * Get a default chart js configuration
19454
19466
  */
19455
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
19467
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
19456
19468
  const options = {
19457
19469
  // https://www.chartjs.org/docs/latest/general/responsive.html
19458
19470
  responsive: true,
@@ -19499,7 +19511,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
19499
19511
  type: chart.type,
19500
19512
  options,
19501
19513
  data: {
19502
- labels: labels.map(truncateLabel),
19514
+ labels: truncateLabels ? labels.map(truncateLabel) : labels,
19503
19515
  datasets: [],
19504
19516
  },
19505
19517
  platform: undefined,
@@ -20265,9 +20277,9 @@ function isLuxonTimeAdapterInstalled() {
20265
20277
  }
20266
20278
  return isInstalled;
20267
20279
  }
20268
- function getLineOrScatterConfiguration(chart, labels, localeFormat) {
20280
+ function getLineOrScatterConfiguration(chart, labels, options) {
20269
20281
  const fontColor = chartFontColor(chart.background);
20270
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
20282
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
20271
20283
  const legend = {
20272
20284
  labels: {
20273
20285
  color: fontColor,
@@ -20310,7 +20322,7 @@ function getLineOrScatterConfiguration(chart, labels, localeFormat) {
20310
20322
  value = Number(value);
20311
20323
  if (isNaN(value))
20312
20324
  return value;
20313
- const { locale, format } = localeFormat;
20325
+ const { locale, format } = options;
20314
20326
  return formatValue(value, {
20315
20327
  locale,
20316
20328
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
@@ -20343,9 +20355,10 @@ function createLineOrScatterChartRuntime(chart, getters) {
20343
20355
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
20344
20356
  }
20345
20357
  const locale = getters.getLocale();
20358
+ const truncateLabels = axisType === "category";
20346
20359
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
20347
- const localeFormat = { format: dataSetFormat, locale };
20348
- const config = getLineOrScatterConfiguration(chart, labels, localeFormat);
20360
+ const options = { format: dataSetFormat, locale, truncateLabels };
20361
+ const config = getLineOrScatterConfiguration(chart, labels, options);
20349
20362
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
20350
20363
  if (axisType === "time") {
20351
20364
  const axis = {
@@ -32071,17 +32084,13 @@ class Composer extends Component {
32071
32084
  this.DOMFocusableElementStore.setFocusableElement(el);
32072
32085
  }
32073
32086
  this.contentHelper.updateEl(el);
32074
- this.processTokenAtCursor();
32075
32087
  });
32076
32088
  useEffect(() => {
32077
32089
  this.processContent();
32078
32090
  });
32079
- onPatched(() => {
32080
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
32081
- if (this.composerStore.editionMode === "inactive") {
32082
- this.processTokenAtCursor();
32083
- }
32084
- });
32091
+ useEffect(() => {
32092
+ this.processTokenAtCursor();
32093
+ }, () => [this.composerStore.editionMode !== "inactive"]);
32085
32094
  }
32086
32095
  // ---------------------------------------------------------------------------
32087
32096
  // Handlers
@@ -32903,7 +32912,8 @@ class DataValidationOverlay extends Component {
32903
32912
  get checkBoxCellPositions() {
32904
32913
  return this.env.model.getters
32905
32914
  .getVisibleCellPositions()
32906
- .filter(this.env.model.getters.isCellValidCheckbox);
32915
+ .filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
32916
+ !this.env.model.getters.isFilterHeader(position));
32907
32917
  }
32908
32918
  get listIconsCellPositions() {
32909
32919
  if (this.env.model.getters.isReadonly()) {
@@ -32911,7 +32921,8 @@ class DataValidationOverlay extends Component {
32911
32921
  }
32912
32922
  return this.env.model.getters
32913
32923
  .getVisibleCellPositions()
32914
- .filter(this.env.model.getters.cellHasListDataValidationIcon);
32924
+ .filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
32925
+ !this.env.model.getters.isFilterHeader(position));
32915
32926
  }
32916
32927
  }
32917
32928
 
@@ -40378,7 +40389,7 @@ class BordersPlugin extends CorePlugin {
40378
40389
  this.clearBorders(cmd.sheetId, cmd.target);
40379
40390
  break;
40380
40391
  case "REMOVE_COLUMNS_ROWS":
40381
- for (let el of cmd.elements) {
40392
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
40382
40393
  if (cmd.dimension === "COL") {
40383
40394
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
40384
40395
  }
@@ -45917,6 +45928,353 @@ class CompilationParametersBuilder {
45917
45928
  }
45918
45929
  }
45919
45930
 
45931
+ /**
45932
+ * ####################################################
45933
+ * # INTRODUCTION
45934
+ * ####################################################
45935
+ *
45936
+ * This file contain the function recomputeZones.
45937
+ * This function try to recompute in a performant way
45938
+ * an ensemble of zones possibly overlapping to avoid
45939
+ * overlapping and to reduce the number of zones.
45940
+ *
45941
+ * It also allows to remove some zones from the ensemble.
45942
+ *
45943
+ * In the following example, 2 zones are overlapping.
45944
+ * Applying recomputeZones will return zones without
45945
+ * overlapping:
45946
+ *
45947
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
45948
+ *
45949
+ * A B C D E A B C D E
45950
+ * 1 ___ 1 ___
45951
+ * 2 ___|_ | 2 ___| | |
45952
+ * 3 | |_|_| ---> 3 | | |_|
45953
+ * 4 |_____| 4 |___|_|
45954
+ * 6 6
45955
+ * 7 7
45956
+ *
45957
+ *
45958
+ * In the following example, 2 zones are contiguous.
45959
+ * Applying recomputeZones will return only one zone:
45960
+ *
45961
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
45962
+ *
45963
+ * A B C D E A B C D E
45964
+ * 1 _ ___ 1 _____
45965
+ * 2 | | | ---> 2 | |
45966
+ * 3 |_|___| 3 |_____|
45967
+ * 4 4
45968
+ *
45969
+ *
45970
+ * In the following example, we want to remove a zone
45971
+ * from the ensemble. Applying recomputeZones will
45972
+ * return the ensemble without the zone to remove:
45973
+ *
45974
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
45975
+ * "C4:D4", "E2:E4"]
45976
+ *
45977
+ * A B C D E F A B C D E F
45978
+ * 1 _______ 1 _______
45979
+ * 2 | | ---> 2 | |___| |
45980
+ * 3 | xxx | 3 | |___| |
45981
+ * 4 |_______| 4 |_|___|_|
45982
+ * 5 5
45983
+ *
45984
+ *
45985
+ * The exercise seems simple when we have only 2 zones.
45986
+ * But with n zones and in a performant way, we want to
45987
+ * avoid comparing each zone with all the others.
45988
+ *
45989
+ *
45990
+ * ####################################################
45991
+ * # Methodological approach
45992
+ * ####################################################
45993
+ *
45994
+ * The methodological approach to avoid comparing each
45995
+ * zone with all the others is to use a data structure
45996
+ * that allow to quickly find which zones are
45997
+ * overlapping with any other given zone.
45998
+ *
45999
+ * Here the idea is to profile the zones at the columns level.
46000
+ *
46001
+ * To do that, we propose to use a data structure
46002
+ * composed of 2 parts:
46003
+ * - profilesStartingPosition: a sorted number array
46004
+ * indicating on which columns a new profile begins.
46005
+ * - profiles: a map where the key is a column
46006
+ * position (from profilesStartingPosition) and the
46007
+ * value is a sorted number array representing a
46008
+ * profile.
46009
+ *
46010
+ *
46011
+ * See the following example: here profileStartingPosition
46012
+ * corresponds to [A,C,E,G,K]
46013
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
46014
+ * 1 ' ' ' '
46015
+ * 2 ' ' '_______' here profile correspond
46016
+ * 3 '___' |_______| for A to []
46017
+ * 4 | | for C to [3, 5]
46018
+ * 5 |___| for E to []
46019
+ * 6 for G to [2, 3]
46020
+ * 7 for K to []
46021
+ *
46022
+ *
46023
+ * Now we can easily find which zones are overlapping
46024
+ * with a given zone. Suppose we want to add a new zone
46025
+ * D5:H6 to the ensemble:
46026
+ *
46027
+ * With a binary search of left and right
46028
+ * A B C D E F G H I J K on profilesStartingPosition, we can
46029
+ * 1 ' ' ' ' find the indexes of the profiles on which
46030
+ * 2 ' ' '_______' to apply a modification.
46031
+ * 3 '___' |_______|
46032
+ * 4 | _|_______ Here we will:
46033
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
46034
+ * 6 |_________| - modify the profile in E --> become [4, 6]
46035
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
46036
+ * - add a new profile in I --> become [8, 10]
46037
+ *
46038
+ * See below the result:
46039
+ *
46040
+ * Note the particularity of the profile
46041
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
46042
+ * 1 ' ' ' ' ' '
46043
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
46044
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
46045
+ * 4 | | |___ ___ search of the top and bottom value on the
46046
+ * 5 |_| | | | profile array. Depending on the result index
46047
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
46048
+ * 7 go by pairs, we know if we are in a zone or
46049
+ * not and how operate.
46050
+ */
46051
+ /**
46052
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
46053
+ * This compute is particularly useful because after this function:
46054
+ * - you will find coordinate of a cell only once among all the zones
46055
+ * - the number of zones will be reduced to the minimum
46056
+ */
46057
+ function futureRecomputeZones(zones, zonesToRemove = []) {
46058
+ const profilesStartingPosition = [0];
46059
+ const profiles = new Map([[0, []]]);
46060
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
46061
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
46062
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
46063
+ }
46064
+ function modifyProfiles(// export for testing only
46065
+ profilesStartingPosition, profiles, zones, toRemove = false) {
46066
+ for (const zone of zones) {
46067
+ const leftValue = zone.left;
46068
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
46069
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
46070
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
46071
+ for (let i = leftIndex; i <= rightIndex; i++) {
46072
+ const profile = profiles.get(profilesStartingPosition[i]);
46073
+ modifyProfile(profile, zone, toRemove);
46074
+ }
46075
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
46076
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
46077
+ }
46078
+ }
46079
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
46080
+ if (value === undefined) {
46081
+ // this is only the case when the value correspond to a bottom value that could be undefined
46082
+ return profilesStartingPosition.length - 1;
46083
+ }
46084
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
46085
+ if (value != profilesStartingPosition[predecessorIndex]) {
46086
+ // mean that the value is not ending/starting at the same position as the previous/next profile
46087
+ // --> it's a new profile
46088
+ // --> we need to add it
46089
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
46090
+ // suppose the we want to add the for the left value
46091
+ // following profile following zone: 'C', the predecessor index
46092
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
46093
+ // The next line code will
46094
+ // A B C D A B C D copy the profile of 'B'
46095
+ // 1 '___' 1 '___' to 'C'. In the rest of the
46096
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
46097
+ // 3 |___| 3 |_|_| | function will adapt the waiting
46098
+ // 4 4 |___| 'C' profile [1, 3] to the
46099
+ // correct 'C' profile [1, 4]
46100
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
46101
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
46102
+ }
46103
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
46104
+ }
46105
+ /**
46106
+ * Suppose the following Suppose we want to add We want to have the
46107
+ * profile: the following zone: following profile:
46108
+ *
46109
+ * A B C D E F A B C D E F A B C D E F
46110
+ * 1 '___' 1 ' ' 1 '___'
46111
+ * 2 |___| 2 '___' 2 | |
46112
+ * 3 ' ' 3 | | 3 | |
46113
+ * 4 '___' --> 4 | | --> 4 | |
46114
+ * 6 | | 6 |___| 6 | |
46115
+ * 7 |___| 7 7 |___|
46116
+ * 8 8 8
46117
+ *
46118
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
46119
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
46120
+ * ____ ____ correspond to 6
46121
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
46122
+ * ____ is to know what must be deleted
46123
+ * Note that the 'filled [3, 7] and what must be added to the
46124
+ * zone' are always between existing profile.
46125
+ * an even index and its
46126
+ * next index
46127
+ *
46128
+ */
46129
+ function modifyProfile(profile, zone, toRemove = false) {
46130
+ const topValue = zone.top;
46131
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
46132
+ const newPoints = [];
46133
+ // Case we want to add a zone to the profile:
46134
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
46135
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
46136
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
46137
+ // so we can keep the index of the top position
46138
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
46139
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
46140
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
46141
+ newPoints.push(topValue);
46142
+ }
46143
+ if (bottomValue === undefined) {
46144
+ // The following two code lines will not impact the final result,
46145
+ // but they will impact the intermediate profile.
46146
+ // We keep them for performance reason
46147
+ profile.splice(topPredIndex + 1);
46148
+ profile.push(...newPoints);
46149
+ return;
46150
+ }
46151
+ // Case we want to add a zone to the profile:
46152
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
46153
+ // so we can keep the index of the bottom position.
46154
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
46155
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
46156
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
46157
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
46158
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
46159
+ newPoints.push(bottomValue);
46160
+ }
46161
+ // add the top and bottom value to the profile and
46162
+ // remove all information between the top and bottom index
46163
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
46164
+ }
46165
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
46166
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
46167
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
46168
+ for (let i = end; i > start; i--) {
46169
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
46170
+ profiles.delete(profilesStartingPosition[i]);
46171
+ profilesStartingPosition.splice(i, 1);
46172
+ }
46173
+ }
46174
+ }
46175
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
46176
+ const mergedZone = [];
46177
+ let pendingZones = [];
46178
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
46179
+ const left = profilesStartingPosition[colIndex];
46180
+ const profile = profiles.get(left);
46181
+ if (!profile || profile.length === 0) {
46182
+ mergedZone.push(...pendingZones);
46183
+ pendingZones = [];
46184
+ continue;
46185
+ }
46186
+ let right = profilesStartingPosition[colIndex + 1];
46187
+ if (right !== undefined) {
46188
+ right--;
46189
+ }
46190
+ const nextPendingZones = [];
46191
+ for (let i = 0; i < profile.length; i += 2) {
46192
+ const top = profile[i];
46193
+ let bottom = profile[i + 1];
46194
+ if (bottom !== undefined) {
46195
+ bottom--;
46196
+ }
46197
+ const profileZone = {
46198
+ top,
46199
+ left,
46200
+ bottom,
46201
+ right,
46202
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
46203
+ };
46204
+ let findCorrespondingZone = false;
46205
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
46206
+ const pendingZone = pendingZones[j];
46207
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
46208
+ pendingZone.right = profileZone.right;
46209
+ pendingZones.splice(j, 1);
46210
+ nextPendingZones.push(pendingZone);
46211
+ findCorrespondingZone = true;
46212
+ break;
46213
+ }
46214
+ }
46215
+ if (!findCorrespondingZone) {
46216
+ nextPendingZones.push(profileZone);
46217
+ }
46218
+ }
46219
+ mergedZone.push(...pendingZones);
46220
+ pendingZones = nextPendingZones;
46221
+ }
46222
+ mergedZone.push(...pendingZones);
46223
+ return mergedZone;
46224
+ }
46225
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
46226
+ let end = arr.length - 1;
46227
+ let result = -1;
46228
+ while (start <= end) {
46229
+ const mid = Math.floor((start + end) / 2);
46230
+ if (arr[mid] === val && matchEqual) {
46231
+ return mid;
46232
+ }
46233
+ else if (arr[mid] < val) {
46234
+ result = mid;
46235
+ start = mid + 1;
46236
+ }
46237
+ else {
46238
+ end = mid - 1;
46239
+ }
46240
+ }
46241
+ return result;
46242
+ }
46243
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
46244
+ let end = arr.length - 1;
46245
+ let result = arr.length;
46246
+ while (start <= end) {
46247
+ const mid = Math.floor((start + end) / 2);
46248
+ if (arr[mid] === val && matchEqual) {
46249
+ return mid;
46250
+ }
46251
+ else if (arr[mid] > val) {
46252
+ result = mid;
46253
+ end = mid - 1;
46254
+ }
46255
+ else {
46256
+ start = mid + 1;
46257
+ }
46258
+ }
46259
+ return result;
46260
+ }
46261
+ /**
46262
+ * Compares two arrays.
46263
+ * For performance reasons, this function is to be preferred
46264
+ * to 'deepEquals' in the case we know that the inputs are arrays.
46265
+ */
46266
+ function deepEqualsArray(arr1, arr2) {
46267
+ if (arr1.length !== arr2.length) {
46268
+ return false;
46269
+ }
46270
+ for (let i = 0; i < arr1.length; i++) {
46271
+ if (!deepEquals(arr1[i], arr2[i])) {
46272
+ return false;
46273
+ }
46274
+ }
46275
+ return true;
46276
+ }
46277
+
45920
46278
  class PositionMap {
45921
46279
  map = {};
45922
46280
  set({ sheetId, col, row }, value) {
@@ -46715,7 +47073,7 @@ class FormulaDependencyGraph {
46715
47073
  }
46716
47074
  }
46717
47075
  /**
46718
- * Return the cell and all cells that depend on it,
47076
+ * Return all the cells that depend on the provided ranges,
46719
47077
  * in the correct order they should be evaluated.
46720
47078
  * This is called a topological ordering (excluding cycles)
46721
47079
  */
@@ -46726,11 +47084,19 @@ class FormulaDependencyGraph {
46726
47084
  const range = queue.pop();
46727
47085
  visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
46728
47086
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
47087
+ const nextInQueue = {};
46729
47088
  for (const position of impactedPositions) {
46730
47089
  if (!visited.has(position)) {
46731
- queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
47090
+ if (!nextInQueue[position.sheetId]) {
47091
+ nextInQueue[position.sheetId] = [];
47092
+ }
47093
+ nextInQueue[position.sheetId].push(positionToZone(position));
46732
47094
  }
46733
47095
  }
47096
+ for (const sheetId in nextInQueue) {
47097
+ const zones = futureRecomputeZones(nextInQueue[sheetId]);
47098
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
47099
+ }
46734
47100
  }
46735
47101
  visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
46736
47102
  return visited;
@@ -47069,7 +47435,7 @@ class Evaluator {
47069
47435
  }
47070
47436
  if (!content) {
47071
47437
  // The previous content could have blocked some array formulas
47072
- impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
47438
+ impactedPositions.addMany(this.getArrayFormulasBlockedBy(position));
47073
47439
  }
47074
47440
  }
47075
47441
  return impactedPositions;
@@ -47112,14 +47478,23 @@ class Evaluator {
47112
47478
  positions.fillAllPositions();
47113
47479
  return positions;
47114
47480
  }
47115
- getArrayFormulasBlockedByOrSpreadingOn(position) {
47481
+ /**
47482
+ * Return the position of formulas blocked by the given position
47483
+ * as well as all their dependencies.
47484
+ */
47485
+ getArrayFormulasBlockedBy(position) {
47116
47486
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
47117
47487
  return [];
47118
47488
  }
47119
47489
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
47120
47490
  const positions = this.createEmptyPositionSet();
47121
47491
  positions.addMany(arrayFormulas);
47122
- positions.addMany(this.getCellsDependingOn(arrayFormulas));
47492
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
47493
+ if (arrayFormulaPosition) {
47494
+ // ignore the formula spreading on the position. Keep only the blocked ones
47495
+ positions.delete(arrayFormulaPosition);
47496
+ }
47497
+ positions.addMany(this.getCellsDependingOn(positions));
47123
47498
  return positions;
47124
47499
  }
47125
47500
  nextPositionsToUpdate = new PositionSet({});
@@ -47259,7 +47634,7 @@ class Evaluator {
47259
47634
  }
47260
47635
  this.evaluatedCells.delete(child);
47261
47636
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
47262
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
47637
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
47263
47638
  }
47264
47639
  this.spreadingRelations.removeNode(position);
47265
47640
  }
@@ -47567,7 +47942,7 @@ class EvaluationPlugin extends UIPlugin {
47567
47942
  ? getItemId(newFormat, data.formats)
47568
47943
  : exportedCellData.format;
47569
47944
  let content;
47570
- if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
47945
+ if (isExported && isFormula && formulaCell instanceof FormulaCellWithDependencies) {
47571
47946
  content = formulaCell.contentWithFixedReferences;
47572
47947
  }
47573
47948
  else {
@@ -60197,6 +60572,6 @@ const constants = {
60197
60572
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
60198
60573
 
60199
60574
 
60200
- __info__.version = "17.2.4";
60201
- __info__.date = "2024-04-18T16:41:38.407Z";
60202
- __info__.hash = "0c66038";
60575
+ __info__.version = "17.2.6";
60576
+ __info__.date = "2024-05-07T10:41:20.332Z";
60577
+ __info__.hash = "a4f800c";