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