@odoo/o-spreadsheet 17.0.19 → 17.0.21

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 17.0.19
6
- * @date 2024-04-18T16:41:42.291Z
7
- * @hash 9f3317a
5
+ * @version 17.0.21
6
+ * @date 2024-05-07T10:39:36.750Z
7
+ * @hash 998bea0
8
8
  */
9
9
 
10
10
  import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
@@ -640,7 +640,7 @@ function getAddHeaderStartIndex(position, base) {
640
640
  /**
641
641
  * Compares two objects.
642
642
  */
643
- function deepEquals(o1, o2) {
643
+ function deepEquals(o1, o2, ignoreFunctions) {
644
644
  if (o1 === o2)
645
645
  return true;
646
646
  if ((o1 && !o2) || (o2 && !o1))
@@ -656,13 +656,16 @@ function deepEquals(o1, o2) {
656
656
  }
657
657
  }
658
658
  for (const key in o1) {
659
- if (typeof o1[key] !== typeof o2[key])
659
+ const typeOfO1Key = typeof o1[key];
660
+ if (typeOfO1Key !== typeof o2[key])
660
661
  return false;
661
- if (typeof o1[key] === "object") {
662
- if (!deepEquals(o1[key], o2[key]))
662
+ if (typeOfO1Key === "object") {
663
+ if (!deepEquals(o1[key], o2[key], ignoreFunctions))
663
664
  return false;
664
665
  }
665
666
  else {
667
+ if (ignoreFunctions && typeOfO1Key === "function")
668
+ return true;
666
669
  if (o1[key] !== o2[key])
667
670
  return false;
668
671
  }
@@ -3557,21 +3560,22 @@ function toZoneWithoutBoundaryChanges(xc) {
3557
3560
  xc = xc.split("!").at(-1);
3558
3561
  }
3559
3562
  if (xc.includes("$")) {
3560
- xc = xc.replace(/\$/g, "");
3563
+ xc = xc.replaceAll("$", "");
3561
3564
  }
3562
- let ranges;
3565
+ let firstRangePart = "";
3566
+ let secondRangePart;
3563
3567
  if (xc.includes(":")) {
3564
- ranges = xc.split(":").map((x) => x.trim());
3568
+ [firstRangePart, secondRangePart] = xc.split(":");
3569
+ firstRangePart = firstRangePart.trim();
3570
+ secondRangePart = secondRangePart.trim();
3565
3571
  }
3566
3572
  else {
3567
- ranges = [xc.trim()];
3573
+ firstRangePart = xc.trim();
3568
3574
  }
3569
3575
  let top, bottom, left, right;
3570
3576
  let fullCol = false;
3571
3577
  let fullRow = false;
3572
3578
  let hasHeader = false;
3573
- const firstRangePart = ranges[0];
3574
- const secondRangePart = ranges[1] && ranges[1];
3575
3579
  if (isColReference(firstRangePart)) {
3576
3580
  left = right = lettersToNumber(firstRangePart);
3577
3581
  top = bottom = 0;
@@ -3588,7 +3592,7 @@ function toZoneWithoutBoundaryChanges(xc) {
3588
3592
  top = bottom = c.row;
3589
3593
  hasHeader = true;
3590
3594
  }
3591
- if (ranges.length === 2) {
3595
+ if (secondRangePart) {
3592
3596
  if (isColReference(secondRangePart)) {
3593
3597
  right = lettersToNumber(secondRangePart);
3594
3598
  fullCol = true;
@@ -4757,6 +4761,7 @@ class ChartJsComponent extends Component {
4757
4761
  static template = "o-spreadsheet-ChartJsComponent";
4758
4762
  canvas = useRef("graphContainer");
4759
4763
  chart;
4764
+ currentRuntime;
4760
4765
  get background() {
4761
4766
  return this.chartRuntime.background;
4762
4767
  }
@@ -4773,9 +4778,17 @@ class ChartJsComponent extends Component {
4773
4778
  setup() {
4774
4779
  onMounted(() => {
4775
4780
  const runtime = this.chartRuntime;
4776
- this.createChart(runtime.chartJsConfig);
4781
+ this.currentRuntime = runtime;
4782
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
4783
+ this.createChart(deepCopy(runtime.chartJsConfig));
4784
+ });
4785
+ useEffect(() => {
4786
+ const runtime = this.chartRuntime;
4787
+ if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
4788
+ this.currentRuntime = runtime;
4789
+ this.updateChartJs(deepCopy(runtime));
4790
+ }
4777
4791
  });
4778
- useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
4779
4792
  }
4780
4793
  createChart(chartData) {
4781
4794
  const canvas = this.canvas.el;
@@ -4803,8 +4816,7 @@ class ChartJsComponent extends Component {
4803
4816
  this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
4804
4817
  this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
4805
4818
  this.chart.config.options.scales = chartData.options?.scales;
4806
- // ?
4807
- this.chart.update("active");
4819
+ this.chart.update();
4808
4820
  }
4809
4821
  }
4810
4822
  ChartJsComponent.props = {
@@ -8373,7 +8385,7 @@ function truncateLabel(label) {
8373
8385
  /**
8374
8386
  * Get a default chart js configuration
8375
8387
  */
8376
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
8388
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
8377
8389
  const options = {
8378
8390
  // https://www.chartjs.org/docs/latest/general/responsive.html
8379
8391
  responsive: true,
@@ -8420,7 +8432,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
8420
8432
  type: chart.type,
8421
8433
  options,
8422
8434
  data: {
8423
- labels: labels.map(truncateLabel),
8435
+ labels: truncateLabels ? labels.map(truncateLabel) : labels,
8424
8436
  datasets: [],
8425
8437
  },
8426
8438
  plugins: [],
@@ -9302,9 +9314,9 @@ function canBeLinearChart(labelRange, getters) {
9302
9314
  }
9303
9315
  return true;
9304
9316
  }
9305
- function getLineConfiguration(chart, labels, localeFormat) {
9317
+ function getLineConfiguration(chart, labels, options) {
9306
9318
  const fontColor = chartFontColor(chart.background);
9307
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
9319
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
9308
9320
  const legend = {
9309
9321
  labels: {
9310
9322
  color: fontColor,
@@ -9347,7 +9359,7 @@ function getLineConfiguration(chart, labels, localeFormat) {
9347
9359
  value = Number(value);
9348
9360
  if (isNaN(value))
9349
9361
  return value;
9350
- const { locale, format } = localeFormat;
9362
+ const { locale, format } = options;
9351
9363
  return formatValue(value, {
9352
9364
  locale,
9353
9365
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
@@ -9380,9 +9392,10 @@ function createLineChartRuntime(chart, getters) {
9380
9392
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
9381
9393
  }
9382
9394
  const locale = getters.getLocale();
9395
+ const truncateLabels = axisType === "category";
9383
9396
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
9384
- const localeFormat = { format: dataSetFormat, locale };
9385
- const config = getLineConfiguration(chart, labels, localeFormat);
9397
+ const options = { format: dataSetFormat, locale, truncateLabels };
9398
+ const config = getLineConfiguration(chart, labels, options);
9386
9399
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
9387
9400
  if (axisType === "time") {
9388
9401
  const axis = {
@@ -26666,17 +26679,13 @@ class Composer extends Component {
26666
26679
  this.env.focusableElement.setFocusableElement(el);
26667
26680
  }
26668
26681
  this.contentHelper.updateEl(el);
26669
- this.processTokenAtCursor();
26670
26682
  });
26671
26683
  useEffect(() => {
26672
26684
  this.processContent();
26673
26685
  });
26674
- onPatched(() => {
26675
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
26676
- if (this.env.model.getters.getEditionMode() === "inactive") {
26677
- this.processTokenAtCursor();
26678
- }
26679
- });
26686
+ useEffect(() => {
26687
+ this.processTokenAtCursor();
26688
+ }, () => [this.env.model.getters.getEditionMode() !== "inactive"]);
26680
26689
  }
26681
26690
  // ---------------------------------------------------------------------------
26682
26691
  // Handlers
@@ -27107,6 +27116,7 @@ class Composer extends Component {
27107
27116
  const dataValidationAutocompleteValues = this.env.model.getters.getAutoCompleteDataValidationValues();
27108
27117
  if (!content.startsWith("=") && dataValidationAutocompleteValues.length) {
27109
27118
  this.showDataValidationAutocomplete(dataValidationAutocompleteValues);
27119
+ return;
27110
27120
  }
27111
27121
  if (content.startsWith("=")) {
27112
27122
  const token = this.env.model.getters.getTokenAtCursor();
@@ -27545,7 +27555,8 @@ class DataValidationOverlay extends Component {
27545
27555
  get checkBoxCellPositions() {
27546
27556
  return this.env.model.getters
27547
27557
  .getVisibleCellPositions()
27548
- .filter(this.env.model.getters.isCellValidCheckbox);
27558
+ .filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
27559
+ !this.env.model.getters.isFilterHeader(position));
27549
27560
  }
27550
27561
  get listIconsCellPositions() {
27551
27562
  if (this.env.model.getters.isReadonly()) {
@@ -27553,7 +27564,8 @@ class DataValidationOverlay extends Component {
27553
27564
  }
27554
27565
  return this.env.model.getters
27555
27566
  .getVisibleCellPositions()
27556
- .filter(this.env.model.getters.cellHasListDataValidationIcon);
27567
+ .filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
27568
+ !this.env.model.getters.isFilterHeader(position));
27557
27569
  }
27558
27570
  }
27559
27571
  DataValidationOverlay.props = {};
@@ -34366,7 +34378,7 @@ class BordersPlugin extends CorePlugin {
34366
34378
  this.clearBorders(cmd.sheetId, cmd.target);
34367
34379
  break;
34368
34380
  case "REMOVE_COLUMNS_ROWS":
34369
- for (let el of cmd.elements) {
34381
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
34370
34382
  if (cmd.dimension === "COL") {
34371
34383
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
34372
34384
  }
@@ -40146,6 +40158,353 @@ class CompilationParametersBuilder {
40146
40158
  }
40147
40159
  }
40148
40160
 
40161
+ /**
40162
+ * ####################################################
40163
+ * # INTRODUCTION
40164
+ * ####################################################
40165
+ *
40166
+ * This file contain the function recomputeZones.
40167
+ * This function try to recompute in a performant way
40168
+ * an ensemble of zones possibly overlapping to avoid
40169
+ * overlapping and to reduce the number of zones.
40170
+ *
40171
+ * It also allows to remove some zones from the ensemble.
40172
+ *
40173
+ * In the following example, 2 zones are overlapping.
40174
+ * Applying recomputeZones will return zones without
40175
+ * overlapping:
40176
+ *
40177
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
40178
+ *
40179
+ * A B C D E A B C D E
40180
+ * 1 ___ 1 ___
40181
+ * 2 ___|_ | 2 ___| | |
40182
+ * 3 | |_|_| ---> 3 | | |_|
40183
+ * 4 |_____| 4 |___|_|
40184
+ * 6 6
40185
+ * 7 7
40186
+ *
40187
+ *
40188
+ * In the following example, 2 zones are contiguous.
40189
+ * Applying recomputeZones will return only one zone:
40190
+ *
40191
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
40192
+ *
40193
+ * A B C D E A B C D E
40194
+ * 1 _ ___ 1 _____
40195
+ * 2 | | | ---> 2 | |
40196
+ * 3 |_|___| 3 |_____|
40197
+ * 4 4
40198
+ *
40199
+ *
40200
+ * In the following example, we want to remove a zone
40201
+ * from the ensemble. Applying recomputeZones will
40202
+ * return the ensemble without the zone to remove:
40203
+ *
40204
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
40205
+ * "C4:D4", "E2:E4"]
40206
+ *
40207
+ * A B C D E F A B C D E F
40208
+ * 1 _______ 1 _______
40209
+ * 2 | | ---> 2 | |___| |
40210
+ * 3 | xxx | 3 | |___| |
40211
+ * 4 |_______| 4 |_|___|_|
40212
+ * 5 5
40213
+ *
40214
+ *
40215
+ * The exercise seems simple when we have only 2 zones.
40216
+ * But with n zones and in a performant way, we want to
40217
+ * avoid comparing each zone with all the others.
40218
+ *
40219
+ *
40220
+ * ####################################################
40221
+ * # Methodological approach
40222
+ * ####################################################
40223
+ *
40224
+ * The methodological approach to avoid comparing each
40225
+ * zone with all the others is to use a data structure
40226
+ * that allow to quickly find which zones are
40227
+ * overlapping with any other given zone.
40228
+ *
40229
+ * Here the idea is to profile the zones at the columns level.
40230
+ *
40231
+ * To do that, we propose to use a data structure
40232
+ * composed of 2 parts:
40233
+ * - profilesStartingPosition: a sorted number array
40234
+ * indicating on which columns a new profile begins.
40235
+ * - profiles: a map where the key is a column
40236
+ * position (from profilesStartingPosition) and the
40237
+ * value is a sorted number array representing a
40238
+ * profile.
40239
+ *
40240
+ *
40241
+ * See the following example: here profileStartingPosition
40242
+ * corresponds to [A,C,E,G,K]
40243
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
40244
+ * 1 ' ' ' '
40245
+ * 2 ' ' '_______' here profile correspond
40246
+ * 3 '___' |_______| for A to []
40247
+ * 4 | | for C to [3, 5]
40248
+ * 5 |___| for E to []
40249
+ * 6 for G to [2, 3]
40250
+ * 7 for K to []
40251
+ *
40252
+ *
40253
+ * Now we can easily find which zones are overlapping
40254
+ * with a given zone. Suppose we want to add a new zone
40255
+ * D5:H6 to the ensemble:
40256
+ *
40257
+ * With a binary search of left and right
40258
+ * A B C D E F G H I J K on profilesStartingPosition, we can
40259
+ * 1 ' ' ' ' find the indexes of the profiles on which
40260
+ * 2 ' ' '_______' to apply a modification.
40261
+ * 3 '___' |_______|
40262
+ * 4 | _|_______ Here we will:
40263
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
40264
+ * 6 |_________| - modify the profile in E --> become [4, 6]
40265
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
40266
+ * - add a new profile in I --> become [8, 10]
40267
+ *
40268
+ * See below the result:
40269
+ *
40270
+ * Note the particularity of the profile
40271
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
40272
+ * 1 ' ' ' ' ' '
40273
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
40274
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
40275
+ * 4 | | |___ ___ search of the top and bottom value on the
40276
+ * 5 |_| | | | profile array. Depending on the result index
40277
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
40278
+ * 7 go by pairs, we know if we are in a zone or
40279
+ * not and how operate.
40280
+ */
40281
+ /**
40282
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
40283
+ * This compute is particularly useful because after this function:
40284
+ * - you will find coordinate of a cell only once among all the zones
40285
+ * - the number of zones will be reduced to the minimum
40286
+ */
40287
+ function futureRecomputeZones(zones, zonesToRemove = []) {
40288
+ const profilesStartingPosition = [0];
40289
+ const profiles = new Map([[0, []]]);
40290
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
40291
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
40292
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
40293
+ }
40294
+ function modifyProfiles(// export for testing only
40295
+ profilesStartingPosition, profiles, zones, toRemove = false) {
40296
+ for (const zone of zones) {
40297
+ const leftValue = zone.left;
40298
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
40299
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
40300
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
40301
+ for (let i = leftIndex; i <= rightIndex; i++) {
40302
+ const profile = profiles.get(profilesStartingPosition[i]);
40303
+ modifyProfile(profile, zone, toRemove);
40304
+ }
40305
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
40306
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
40307
+ }
40308
+ }
40309
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
40310
+ if (value === undefined) {
40311
+ // this is only the case when the value correspond to a bottom value that could be undefined
40312
+ return profilesStartingPosition.length - 1;
40313
+ }
40314
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
40315
+ if (value != profilesStartingPosition[predecessorIndex]) {
40316
+ // mean that the value is not ending/starting at the same position as the previous/next profile
40317
+ // --> it's a new profile
40318
+ // --> we need to add it
40319
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
40320
+ // suppose the we want to add the for the left value
40321
+ // following profile following zone: 'C', the predecessor index
40322
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
40323
+ // The next line code will
40324
+ // A B C D A B C D copy the profile of 'B'
40325
+ // 1 '___' 1 '___' to 'C'. In the rest of the
40326
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
40327
+ // 3 |___| 3 |_|_| | function will adapt the waiting
40328
+ // 4 4 |___| 'C' profile [1, 3] to the
40329
+ // correct 'C' profile [1, 4]
40330
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
40331
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
40332
+ }
40333
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
40334
+ }
40335
+ /**
40336
+ * Suppose the following Suppose we want to add We want to have the
40337
+ * profile: the following zone: following profile:
40338
+ *
40339
+ * A B C D E F A B C D E F A B C D E F
40340
+ * 1 '___' 1 ' ' 1 '___'
40341
+ * 2 |___| 2 '___' 2 | |
40342
+ * 3 ' ' 3 | | 3 | |
40343
+ * 4 '___' --> 4 | | --> 4 | |
40344
+ * 6 | | 6 |___| 6 | |
40345
+ * 7 |___| 7 7 |___|
40346
+ * 8 8 8
40347
+ *
40348
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
40349
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
40350
+ * ____ ____ correspond to 6
40351
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
40352
+ * ____ is to know what must be deleted
40353
+ * Note that the 'filled [3, 7] and what must be added to the
40354
+ * zone' are always between existing profile.
40355
+ * an even index and its
40356
+ * next index
40357
+ *
40358
+ */
40359
+ function modifyProfile(profile, zone, toRemove = false) {
40360
+ const topValue = zone.top;
40361
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
40362
+ const newPoints = [];
40363
+ // Case we want to add a zone to the profile:
40364
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
40365
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
40366
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
40367
+ // so we can keep the index of the top position
40368
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40369
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
40370
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
40371
+ newPoints.push(topValue);
40372
+ }
40373
+ if (bottomValue === undefined) {
40374
+ // The following two code lines will not impact the final result,
40375
+ // but they will impact the intermediate profile.
40376
+ // We keep them for performance reason
40377
+ profile.splice(topPredIndex + 1);
40378
+ profile.push(...newPoints);
40379
+ return;
40380
+ }
40381
+ // Case we want to add a zone to the profile:
40382
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
40383
+ // so we can keep the index of the bottom position.
40384
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
40385
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
40386
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40387
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
40388
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
40389
+ newPoints.push(bottomValue);
40390
+ }
40391
+ // add the top and bottom value to the profile and
40392
+ // remove all information between the top and bottom index
40393
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
40394
+ }
40395
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
40396
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
40397
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
40398
+ for (let i = end; i > start; i--) {
40399
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
40400
+ profiles.delete(profilesStartingPosition[i]);
40401
+ profilesStartingPosition.splice(i, 1);
40402
+ }
40403
+ }
40404
+ }
40405
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
40406
+ const mergedZone = [];
40407
+ let pendingZones = [];
40408
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
40409
+ const left = profilesStartingPosition[colIndex];
40410
+ const profile = profiles.get(left);
40411
+ if (!profile || profile.length === 0) {
40412
+ mergedZone.push(...pendingZones);
40413
+ pendingZones = [];
40414
+ continue;
40415
+ }
40416
+ let right = profilesStartingPosition[colIndex + 1];
40417
+ if (right !== undefined) {
40418
+ right--;
40419
+ }
40420
+ const nextPendingZones = [];
40421
+ for (let i = 0; i < profile.length; i += 2) {
40422
+ const top = profile[i];
40423
+ let bottom = profile[i + 1];
40424
+ if (bottom !== undefined) {
40425
+ bottom--;
40426
+ }
40427
+ const profileZone = {
40428
+ top,
40429
+ left,
40430
+ bottom,
40431
+ right,
40432
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
40433
+ };
40434
+ let findCorrespondingZone = false;
40435
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
40436
+ const pendingZone = pendingZones[j];
40437
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
40438
+ pendingZone.right = profileZone.right;
40439
+ pendingZones.splice(j, 1);
40440
+ nextPendingZones.push(pendingZone);
40441
+ findCorrespondingZone = true;
40442
+ break;
40443
+ }
40444
+ }
40445
+ if (!findCorrespondingZone) {
40446
+ nextPendingZones.push(profileZone);
40447
+ }
40448
+ }
40449
+ mergedZone.push(...pendingZones);
40450
+ pendingZones = nextPendingZones;
40451
+ }
40452
+ mergedZone.push(...pendingZones);
40453
+ return mergedZone;
40454
+ }
40455
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
40456
+ let end = arr.length - 1;
40457
+ let result = -1;
40458
+ while (start <= end) {
40459
+ const mid = Math.floor((start + end) / 2);
40460
+ if (arr[mid] === val && matchEqual) {
40461
+ return mid;
40462
+ }
40463
+ else if (arr[mid] < val) {
40464
+ result = mid;
40465
+ start = mid + 1;
40466
+ }
40467
+ else {
40468
+ end = mid - 1;
40469
+ }
40470
+ }
40471
+ return result;
40472
+ }
40473
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
40474
+ let end = arr.length - 1;
40475
+ let result = arr.length;
40476
+ while (start <= end) {
40477
+ const mid = Math.floor((start + end) / 2);
40478
+ if (arr[mid] === val && matchEqual) {
40479
+ return mid;
40480
+ }
40481
+ else if (arr[mid] > val) {
40482
+ result = mid;
40483
+ end = mid - 1;
40484
+ }
40485
+ else {
40486
+ start = mid + 1;
40487
+ }
40488
+ }
40489
+ return result;
40490
+ }
40491
+ /**
40492
+ * Compares two arrays.
40493
+ * For performance reasons, this function is to be preferred
40494
+ * to 'deepEquals' in the case we know that the inputs are arrays.
40495
+ */
40496
+ function deepEqualsArray(arr1, arr2) {
40497
+ if (arr1.length !== arr2.length) {
40498
+ return false;
40499
+ }
40500
+ for (let i = 0; i < arr1.length; i++) {
40501
+ if (!deepEquals(arr1[i], arr2[i])) {
40502
+ return false;
40503
+ }
40504
+ }
40505
+ return true;
40506
+ }
40507
+
40149
40508
  function quickselect(arr, k, left, right, compare) {
40150
40509
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
40151
40510
  }
@@ -40909,7 +41268,7 @@ class FormulaDependencyGraph {
40909
41268
  }
40910
41269
  }
40911
41270
  /**
40912
- * Return the cell and all cells that depend on it,
41271
+ * Return all the cells that depend on the provided ranges,
40913
41272
  * in the correct order they should be evaluated.
40914
41273
  * This is called a topological ordering (excluding cycles)
40915
41274
  */
@@ -40920,11 +41279,20 @@ class FormulaDependencyGraph {
40920
41279
  const range = queue.pop();
40921
41280
  visited.addMany(this.encoder.encodeBoundingBox(range));
40922
41281
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41282
+ const nextInQueue = {};
40923
41283
  for (const positionId of impactedPositionIds) {
40924
41284
  if (!visited.has(positionId)) {
40925
- queue.push(this.encoder.decodeToBoundingBox(positionId));
41285
+ const { sheetId, zone } = this.encoder.decodeToBoundingBox(positionId);
41286
+ if (!nextInQueue[sheetId]) {
41287
+ nextInQueue[sheetId] = [];
41288
+ }
41289
+ nextInQueue[sheetId].push(zone);
40926
41290
  }
40927
41291
  }
41292
+ for (const sheetId in nextInQueue) {
41293
+ const zones = futureRecomputeZones(nextInQueue[sheetId]);
41294
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
41295
+ }
40928
41296
  }
40929
41297
  visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40930
41298
  return visited;
@@ -41080,7 +41448,7 @@ class Evaluator {
41080
41448
  }
41081
41449
  if (!content) {
41082
41450
  // The previous content could have blocked some array formulas
41083
- impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41451
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedBy(positionId));
41084
41452
  }
41085
41453
  }
41086
41454
  return impactedPositionIds;
@@ -41125,13 +41493,22 @@ class Evaluator {
41125
41493
  }
41126
41494
  return positionIds;
41127
41495
  }
41128
- getArrayFormulasBlockedByOrSpreadingOn(positionId) {
41496
+ /**
41497
+ * Return the position of formulas blocked by the given position
41498
+ * as well as all their dependencies.
41499
+ */
41500
+ getArrayFormulasBlockedBy(positionId) {
41129
41501
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
41130
41502
  return [];
41131
41503
  }
41132
41504
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41133
41505
  const cells = new JetSet(arrayFormulas);
41134
- cells.addMany(this.getCellsDependingOn(arrayFormulas));
41506
+ const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
41507
+ if (arrayFormulaPositionId) {
41508
+ // ignore the formula spreading on the position. Keep only the blocked ones
41509
+ cells.delete(arrayFormulaPositionId);
41510
+ }
41511
+ cells.addMany(this.getCellsDependingOn(cells));
41135
41512
  return cells;
41136
41513
  }
41137
41514
  nextPositionsToUpdate = new JetSet();
@@ -41296,7 +41673,7 @@ class Evaluator {
41296
41673
  }
41297
41674
  this.evaluatedCells.delete(child);
41298
41675
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41299
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41676
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
41300
41677
  }
41301
41678
  this.spreadingRelations.removeNode(positionId);
41302
41679
  }
@@ -41655,7 +42032,7 @@ class EvaluationPlugin extends UIPlugin {
41655
42032
  ? getItemId(newFormat, data.formats)
41656
42033
  : exportedCellData.format;
41657
42034
  let content;
41658
- if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
42035
+ if (isExported && isFormula && formulaCell?.compiledFormula.dependencies.length) {
41659
42036
  content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41660
42037
  }
41661
42038
  else {
@@ -56993,6 +57370,6 @@ const constants = {
56993
57370
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
56994
57371
 
56995
57372
 
56996
- __info__.version = "17.0.19";
56997
- __info__.date = "2024-04-18T16:41:42.291Z";
56998
- __info__.hash = "9f3317a";
57373
+ __info__.version = "17.0.21";
57374
+ __info__.date = "2024-05-07T10:39:36.750Z";
57375
+ __info__.hash = "998bea0";