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