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