@odoo/o-spreadsheet 17.1.12 → 17.1.14

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,12 +2,12 @@
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.1.12
6
- * @date 2024-04-18T16:45:30.109Z
7
- * @hash 3e90776
5
+ * @version 17.1.14
6
+ * @date 2024-05-07T10:41:25.690Z
7
+ * @hash 622c47c
8
8
  */
9
9
 
10
- import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
10
+ import { Component, useRef, onMounted, onWillUnmount, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
11
11
 
12
12
  const CANVAS_SHIFT = 0.5;
13
13
  // Colors
@@ -541,7 +541,7 @@ function getAddHeaderStartIndex(position, base) {
541
541
  /**
542
542
  * Compares two objects.
543
543
  */
544
- function deepEquals(o1, o2) {
544
+ function deepEquals(o1, o2, ignoreFunctions) {
545
545
  if (o1 === o2)
546
546
  return true;
547
547
  if ((o1 && !o2) || (o2 && !o1))
@@ -557,13 +557,16 @@ function deepEquals(o1, o2) {
557
557
  }
558
558
  }
559
559
  for (const key in o1) {
560
- if (typeof o1[key] !== typeof o2[key])
560
+ const typeOfO1Key = typeof o1[key];
561
+ if (typeOfO1Key !== typeof o2[key])
561
562
  return false;
562
- if (typeof o1[key] === "object") {
563
- if (!deepEquals(o1[key], o2[key]))
563
+ if (typeOfO1Key === "object") {
564
+ if (!deepEquals(o1[key], o2[key], ignoreFunctions))
564
565
  return false;
565
566
  }
566
567
  else {
568
+ if (ignoreFunctions && typeOfO1Key === "function")
569
+ return true;
567
570
  if (o1[key] !== o2[key])
568
571
  return false;
569
572
  }
@@ -3623,21 +3626,22 @@ function toZoneWithoutBoundaryChanges(xc) {
3623
3626
  xc = xc.split("!").at(-1);
3624
3627
  }
3625
3628
  if (xc.includes("$")) {
3626
- xc = xc.replace(/\$/g, "");
3629
+ xc = xc.replaceAll("$", "");
3627
3630
  }
3628
- let ranges;
3631
+ let firstRangePart = "";
3632
+ let secondRangePart;
3629
3633
  if (xc.includes(":")) {
3630
- ranges = xc.split(":").map((x) => x.trim());
3634
+ [firstRangePart, secondRangePart] = xc.split(":");
3635
+ firstRangePart = firstRangePart.trim();
3636
+ secondRangePart = secondRangePart.trim();
3631
3637
  }
3632
3638
  else {
3633
- ranges = [xc.trim()];
3639
+ firstRangePart = xc.trim();
3634
3640
  }
3635
3641
  let top, bottom, left, right;
3636
3642
  let fullCol = false;
3637
3643
  let fullRow = false;
3638
3644
  let hasHeader = false;
3639
- const firstRangePart = ranges[0];
3640
- const secondRangePart = ranges[1] && ranges[1];
3641
3645
  if (isColReference(firstRangePart)) {
3642
3646
  left = right = lettersToNumber(firstRangePart);
3643
3647
  top = bottom = 0;
@@ -3654,7 +3658,7 @@ function toZoneWithoutBoundaryChanges(xc) {
3654
3658
  top = bottom = c.row;
3655
3659
  hasHeader = true;
3656
3660
  }
3657
- if (ranges.length === 2) {
3661
+ if (secondRangePart) {
3658
3662
  if (isColReference(secondRangePart)) {
3659
3663
  right = lettersToNumber(secondRangePart);
3660
3664
  fullCol = true;
@@ -4828,6 +4832,7 @@ class ChartJsComponent extends Component {
4828
4832
  };
4829
4833
  canvas = useRef("graphContainer");
4830
4834
  chart;
4835
+ currentRuntime;
4831
4836
  get background() {
4832
4837
  return this.chartRuntime.background;
4833
4838
  }
@@ -4844,9 +4849,18 @@ class ChartJsComponent extends Component {
4844
4849
  setup() {
4845
4850
  onMounted(() => {
4846
4851
  const runtime = this.chartRuntime;
4847
- this.createChart(runtime.chartJsConfig);
4852
+ this.currentRuntime = runtime;
4853
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
4854
+ this.createChart(deepCopy(runtime.chartJsConfig));
4855
+ });
4856
+ onWillUnmount(() => this.chart?.destroy());
4857
+ useEffect(() => {
4858
+ const runtime = this.chartRuntime;
4859
+ if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
4860
+ this.currentRuntime = runtime;
4861
+ this.updateChartJs(deepCopy(runtime));
4862
+ }
4848
4863
  });
4849
- useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
4850
4864
  }
4851
4865
  createChart(chartData) {
4852
4866
  const canvas = this.canvas.el;
@@ -4874,8 +4888,7 @@ class ChartJsComponent extends Component {
4874
4888
  this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
4875
4889
  this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
4876
4890
  this.chart.config.options.scales = chartData.options?.scales;
4877
- // ?
4878
- this.chart.update("active");
4891
+ this.chart.update();
4879
4892
  }
4880
4893
  }
4881
4894
 
@@ -8438,7 +8451,7 @@ function truncateLabel(label) {
8438
8451
  /**
8439
8452
  * Get a default chart js configuration
8440
8453
  */
8441
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
8454
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
8442
8455
  const options = {
8443
8456
  // https://www.chartjs.org/docs/latest/general/responsive.html
8444
8457
  responsive: true,
@@ -8485,7 +8498,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
8485
8498
  type: chart.type,
8486
8499
  options,
8487
8500
  data: {
8488
- labels: labels.map(truncateLabel),
8501
+ labels: truncateLabels ? labels.map(truncateLabel) : labels,
8489
8502
  datasets: [],
8490
8503
  },
8491
8504
  platform: undefined,
@@ -9368,9 +9381,9 @@ function canBeLinearChart(labelRange, getters) {
9368
9381
  }
9369
9382
  return true;
9370
9383
  }
9371
- function getLineConfiguration(chart, labels, localeFormat) {
9384
+ function getLineConfiguration(chart, labels, options) {
9372
9385
  const fontColor = chartFontColor(chart.background);
9373
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
9386
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
9374
9387
  const legend = {
9375
9388
  labels: {
9376
9389
  color: fontColor,
@@ -9413,7 +9426,7 @@ function getLineConfiguration(chart, labels, localeFormat) {
9413
9426
  value = Number(value);
9414
9427
  if (isNaN(value))
9415
9428
  return value;
9416
- const { locale, format } = localeFormat;
9429
+ const { locale, format } = options;
9417
9430
  return formatValue(value, {
9418
9431
  locale,
9419
9432
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
@@ -9446,9 +9459,10 @@ function createLineChartRuntime(chart, getters) {
9446
9459
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
9447
9460
  }
9448
9461
  const locale = getters.getLocale();
9462
+ const truncateLabels = axisType === "category";
9449
9463
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
9450
- const localeFormat = { format: dataSetFormat, locale };
9451
- const config = getLineConfiguration(chart, labels, localeFormat);
9464
+ const options = { format: dataSetFormat, locale, truncateLabels };
9465
+ const config = getLineConfiguration(chart, labels, options);
9452
9466
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
9453
9467
  if (axisType === "time") {
9454
9468
  const axis = {
@@ -27178,17 +27192,13 @@ class Composer extends Component {
27178
27192
  this.env.focusableElement.setFocusableElement(el);
27179
27193
  }
27180
27194
  this.contentHelper.updateEl(el);
27181
- this.processTokenAtCursor();
27182
27195
  });
27183
27196
  useEffect(() => {
27184
27197
  this.processContent();
27185
27198
  });
27186
- onPatched(() => {
27187
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
27188
- if (this.env.model.getters.getEditionMode() === "inactive") {
27189
- this.processTokenAtCursor();
27190
- }
27191
- });
27199
+ useEffect(() => {
27200
+ this.processTokenAtCursor();
27201
+ }, () => [this.env.model.getters.getEditionMode() !== "inactive"]);
27192
27202
  }
27193
27203
  // ---------------------------------------------------------------------------
27194
27204
  // Handlers
@@ -27626,6 +27636,7 @@ class Composer extends Component {
27626
27636
  const dataValidationAutocompleteValues = this.env.model.getters.getAutoCompleteDataValidationValues();
27627
27637
  if (!content.startsWith("=") && dataValidationAutocompleteValues.length) {
27628
27638
  this.showDataValidationAutocomplete(dataValidationAutocompleteValues);
27639
+ return;
27629
27640
  }
27630
27641
  if (content.startsWith("=")) {
27631
27642
  const token = this.env.model.getters.getTokenAtCursor();
@@ -28055,7 +28066,8 @@ class DataValidationOverlay extends Component {
28055
28066
  get checkBoxCellPositions() {
28056
28067
  return this.env.model.getters
28057
28068
  .getVisibleCellPositions()
28058
- .filter(this.env.model.getters.isCellValidCheckbox);
28069
+ .filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
28070
+ !this.env.model.getters.isFilterHeader(position));
28059
28071
  }
28060
28072
  get listIconsCellPositions() {
28061
28073
  if (this.env.model.getters.isReadonly()) {
@@ -28063,7 +28075,8 @@ class DataValidationOverlay extends Component {
28063
28075
  }
28064
28076
  return this.env.model.getters
28065
28077
  .getVisibleCellPositions()
28066
- .filter(this.env.model.getters.cellHasListDataValidationIcon);
28078
+ .filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
28079
+ !this.env.model.getters.isFilterHeader(position));
28067
28080
  }
28068
28081
  }
28069
28082
 
@@ -34868,7 +34881,7 @@ class BordersPlugin extends CorePlugin {
34868
34881
  this.clearBorders(cmd.sheetId, cmd.target);
34869
34882
  break;
34870
34883
  case "REMOVE_COLUMNS_ROWS":
34871
- for (let el of cmd.elements) {
34884
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
34872
34885
  if (cmd.dimension === "COL") {
34873
34886
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
34874
34887
  }
@@ -40624,6 +40637,353 @@ class CompilationParametersBuilder {
40624
40637
  }
40625
40638
  }
40626
40639
 
40640
+ /**
40641
+ * ####################################################
40642
+ * # INTRODUCTION
40643
+ * ####################################################
40644
+ *
40645
+ * This file contain the function recomputeZones.
40646
+ * This function try to recompute in a performant way
40647
+ * an ensemble of zones possibly overlapping to avoid
40648
+ * overlapping and to reduce the number of zones.
40649
+ *
40650
+ * It also allows to remove some zones from the ensemble.
40651
+ *
40652
+ * In the following example, 2 zones are overlapping.
40653
+ * Applying recomputeZones will return zones without
40654
+ * overlapping:
40655
+ *
40656
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
40657
+ *
40658
+ * A B C D E A B C D E
40659
+ * 1 ___ 1 ___
40660
+ * 2 ___|_ | 2 ___| | |
40661
+ * 3 | |_|_| ---> 3 | | |_|
40662
+ * 4 |_____| 4 |___|_|
40663
+ * 6 6
40664
+ * 7 7
40665
+ *
40666
+ *
40667
+ * In the following example, 2 zones are contiguous.
40668
+ * Applying recomputeZones will return only one zone:
40669
+ *
40670
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
40671
+ *
40672
+ * A B C D E A B C D E
40673
+ * 1 _ ___ 1 _____
40674
+ * 2 | | | ---> 2 | |
40675
+ * 3 |_|___| 3 |_____|
40676
+ * 4 4
40677
+ *
40678
+ *
40679
+ * In the following example, we want to remove a zone
40680
+ * from the ensemble. Applying recomputeZones will
40681
+ * return the ensemble without the zone to remove:
40682
+ *
40683
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
40684
+ * "C4:D4", "E2:E4"]
40685
+ *
40686
+ * A B C D E F A B C D E F
40687
+ * 1 _______ 1 _______
40688
+ * 2 | | ---> 2 | |___| |
40689
+ * 3 | xxx | 3 | |___| |
40690
+ * 4 |_______| 4 |_|___|_|
40691
+ * 5 5
40692
+ *
40693
+ *
40694
+ * The exercise seems simple when we have only 2 zones.
40695
+ * But with n zones and in a performant way, we want to
40696
+ * avoid comparing each zone with all the others.
40697
+ *
40698
+ *
40699
+ * ####################################################
40700
+ * # Methodological approach
40701
+ * ####################################################
40702
+ *
40703
+ * The methodological approach to avoid comparing each
40704
+ * zone with all the others is to use a data structure
40705
+ * that allow to quickly find which zones are
40706
+ * overlapping with any other given zone.
40707
+ *
40708
+ * Here the idea is to profile the zones at the columns level.
40709
+ *
40710
+ * To do that, we propose to use a data structure
40711
+ * composed of 2 parts:
40712
+ * - profilesStartingPosition: a sorted number array
40713
+ * indicating on which columns a new profile begins.
40714
+ * - profiles: a map where the key is a column
40715
+ * position (from profilesStartingPosition) and the
40716
+ * value is a sorted number array representing a
40717
+ * profile.
40718
+ *
40719
+ *
40720
+ * See the following example: here profileStartingPosition
40721
+ * corresponds to [A,C,E,G,K]
40722
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
40723
+ * 1 ' ' ' '
40724
+ * 2 ' ' '_______' here profile correspond
40725
+ * 3 '___' |_______| for A to []
40726
+ * 4 | | for C to [3, 5]
40727
+ * 5 |___| for E to []
40728
+ * 6 for G to [2, 3]
40729
+ * 7 for K to []
40730
+ *
40731
+ *
40732
+ * Now we can easily find which zones are overlapping
40733
+ * with a given zone. Suppose we want to add a new zone
40734
+ * D5:H6 to the ensemble:
40735
+ *
40736
+ * With a binary search of left and right
40737
+ * A B C D E F G H I J K on profilesStartingPosition, we can
40738
+ * 1 ' ' ' ' find the indexes of the profiles on which
40739
+ * 2 ' ' '_______' to apply a modification.
40740
+ * 3 '___' |_______|
40741
+ * 4 | _|_______ Here we will:
40742
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
40743
+ * 6 |_________| - modify the profile in E --> become [4, 6]
40744
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
40745
+ * - add a new profile in I --> become [8, 10]
40746
+ *
40747
+ * See below the result:
40748
+ *
40749
+ * Note the particularity of the profile
40750
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
40751
+ * 1 ' ' ' ' ' '
40752
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
40753
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
40754
+ * 4 | | |___ ___ search of the top and bottom value on the
40755
+ * 5 |_| | | | profile array. Depending on the result index
40756
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
40757
+ * 7 go by pairs, we know if we are in a zone or
40758
+ * not and how operate.
40759
+ */
40760
+ /**
40761
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
40762
+ * This compute is particularly useful because after this function:
40763
+ * - you will find coordinate of a cell only once among all the zones
40764
+ * - the number of zones will be reduced to the minimum
40765
+ */
40766
+ function futureRecomputeZones(zones, zonesToRemove = []) {
40767
+ const profilesStartingPosition = [0];
40768
+ const profiles = new Map([[0, []]]);
40769
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
40770
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
40771
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
40772
+ }
40773
+ function modifyProfiles(// export for testing only
40774
+ profilesStartingPosition, profiles, zones, toRemove = false) {
40775
+ for (const zone of zones) {
40776
+ const leftValue = zone.left;
40777
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
40778
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
40779
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
40780
+ for (let i = leftIndex; i <= rightIndex; i++) {
40781
+ const profile = profiles.get(profilesStartingPosition[i]);
40782
+ modifyProfile(profile, zone, toRemove);
40783
+ }
40784
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
40785
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
40786
+ }
40787
+ }
40788
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
40789
+ if (value === undefined) {
40790
+ // this is only the case when the value correspond to a bottom value that could be undefined
40791
+ return profilesStartingPosition.length - 1;
40792
+ }
40793
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
40794
+ if (value != profilesStartingPosition[predecessorIndex]) {
40795
+ // mean that the value is not ending/starting at the same position as the previous/next profile
40796
+ // --> it's a new profile
40797
+ // --> we need to add it
40798
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
40799
+ // suppose the we want to add the for the left value
40800
+ // following profile following zone: 'C', the predecessor index
40801
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
40802
+ // The next line code will
40803
+ // A B C D A B C D copy the profile of 'B'
40804
+ // 1 '___' 1 '___' to 'C'. In the rest of the
40805
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
40806
+ // 3 |___| 3 |_|_| | function will adapt the waiting
40807
+ // 4 4 |___| 'C' profile [1, 3] to the
40808
+ // correct 'C' profile [1, 4]
40809
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
40810
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
40811
+ }
40812
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
40813
+ }
40814
+ /**
40815
+ * Suppose the following Suppose we want to add We want to have the
40816
+ * profile: the following zone: following profile:
40817
+ *
40818
+ * A B C D E F A B C D E F A B C D E F
40819
+ * 1 '___' 1 ' ' 1 '___'
40820
+ * 2 |___| 2 '___' 2 | |
40821
+ * 3 ' ' 3 | | 3 | |
40822
+ * 4 '___' --> 4 | | --> 4 | |
40823
+ * 6 | | 6 |___| 6 | |
40824
+ * 7 |___| 7 7 |___|
40825
+ * 8 8 8
40826
+ *
40827
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
40828
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
40829
+ * ____ ____ correspond to 6
40830
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
40831
+ * ____ is to know what must be deleted
40832
+ * Note that the 'filled [3, 7] and what must be added to the
40833
+ * zone' are always between existing profile.
40834
+ * an even index and its
40835
+ * next index
40836
+ *
40837
+ */
40838
+ function modifyProfile(profile, zone, toRemove = false) {
40839
+ const topValue = zone.top;
40840
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
40841
+ const newPoints = [];
40842
+ // Case we want to add a zone to the profile:
40843
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
40844
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
40845
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
40846
+ // so we can keep the index of the top position
40847
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40848
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
40849
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
40850
+ newPoints.push(topValue);
40851
+ }
40852
+ if (bottomValue === undefined) {
40853
+ // The following two code lines will not impact the final result,
40854
+ // but they will impact the intermediate profile.
40855
+ // We keep them for performance reason
40856
+ profile.splice(topPredIndex + 1);
40857
+ profile.push(...newPoints);
40858
+ return;
40859
+ }
40860
+ // Case we want to add a zone to the profile:
40861
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
40862
+ // so we can keep the index of the bottom position.
40863
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
40864
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
40865
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40866
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
40867
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
40868
+ newPoints.push(bottomValue);
40869
+ }
40870
+ // add the top and bottom value to the profile and
40871
+ // remove all information between the top and bottom index
40872
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
40873
+ }
40874
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
40875
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
40876
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
40877
+ for (let i = end; i > start; i--) {
40878
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
40879
+ profiles.delete(profilesStartingPosition[i]);
40880
+ profilesStartingPosition.splice(i, 1);
40881
+ }
40882
+ }
40883
+ }
40884
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
40885
+ const mergedZone = [];
40886
+ let pendingZones = [];
40887
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
40888
+ const left = profilesStartingPosition[colIndex];
40889
+ const profile = profiles.get(left);
40890
+ if (!profile || profile.length === 0) {
40891
+ mergedZone.push(...pendingZones);
40892
+ pendingZones = [];
40893
+ continue;
40894
+ }
40895
+ let right = profilesStartingPosition[colIndex + 1];
40896
+ if (right !== undefined) {
40897
+ right--;
40898
+ }
40899
+ const nextPendingZones = [];
40900
+ for (let i = 0; i < profile.length; i += 2) {
40901
+ const top = profile[i];
40902
+ let bottom = profile[i + 1];
40903
+ if (bottom !== undefined) {
40904
+ bottom--;
40905
+ }
40906
+ const profileZone = {
40907
+ top,
40908
+ left,
40909
+ bottom,
40910
+ right,
40911
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
40912
+ };
40913
+ let findCorrespondingZone = false;
40914
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
40915
+ const pendingZone = pendingZones[j];
40916
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
40917
+ pendingZone.right = profileZone.right;
40918
+ pendingZones.splice(j, 1);
40919
+ nextPendingZones.push(pendingZone);
40920
+ findCorrespondingZone = true;
40921
+ break;
40922
+ }
40923
+ }
40924
+ if (!findCorrespondingZone) {
40925
+ nextPendingZones.push(profileZone);
40926
+ }
40927
+ }
40928
+ mergedZone.push(...pendingZones);
40929
+ pendingZones = nextPendingZones;
40930
+ }
40931
+ mergedZone.push(...pendingZones);
40932
+ return mergedZone;
40933
+ }
40934
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
40935
+ let end = arr.length - 1;
40936
+ let result = -1;
40937
+ while (start <= end) {
40938
+ const mid = Math.floor((start + end) / 2);
40939
+ if (arr[mid] === val && matchEqual) {
40940
+ return mid;
40941
+ }
40942
+ else if (arr[mid] < val) {
40943
+ result = mid;
40944
+ start = mid + 1;
40945
+ }
40946
+ else {
40947
+ end = mid - 1;
40948
+ }
40949
+ }
40950
+ return result;
40951
+ }
40952
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
40953
+ let end = arr.length - 1;
40954
+ let result = arr.length;
40955
+ while (start <= end) {
40956
+ const mid = Math.floor((start + end) / 2);
40957
+ if (arr[mid] === val && matchEqual) {
40958
+ return mid;
40959
+ }
40960
+ else if (arr[mid] > val) {
40961
+ result = mid;
40962
+ end = mid - 1;
40963
+ }
40964
+ else {
40965
+ start = mid + 1;
40966
+ }
40967
+ }
40968
+ return result;
40969
+ }
40970
+ /**
40971
+ * Compares two arrays.
40972
+ * For performance reasons, this function is to be preferred
40973
+ * to 'deepEquals' in the case we know that the inputs are arrays.
40974
+ */
40975
+ function deepEqualsArray(arr1, arr2) {
40976
+ if (arr1.length !== arr2.length) {
40977
+ return false;
40978
+ }
40979
+ for (let i = 0; i < arr1.length; i++) {
40980
+ if (!deepEquals(arr1[i], arr2[i])) {
40981
+ return false;
40982
+ }
40983
+ }
40984
+ return true;
40985
+ }
40986
+
40627
40987
  function quickselect(arr, k, left, right, compare) {
40628
40988
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
40629
40989
  }
@@ -41387,7 +41747,7 @@ class FormulaDependencyGraph {
41387
41747
  }
41388
41748
  }
41389
41749
  /**
41390
- * Return the cell and all cells that depend on it,
41750
+ * Return all the cells that depend on the provided ranges,
41391
41751
  * in the correct order they should be evaluated.
41392
41752
  * This is called a topological ordering (excluding cycles)
41393
41753
  */
@@ -41398,11 +41758,20 @@ class FormulaDependencyGraph {
41398
41758
  const range = queue.pop();
41399
41759
  visited.addMany(this.encoder.encodeBoundingBox(range));
41400
41760
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41761
+ const nextInQueue = {};
41401
41762
  for (const positionId of impactedPositionIds) {
41402
41763
  if (!visited.has(positionId)) {
41403
- queue.push(this.encoder.decodeToBoundingBox(positionId));
41764
+ const { sheetId, zone } = this.encoder.decodeToBoundingBox(positionId);
41765
+ if (!nextInQueue[sheetId]) {
41766
+ nextInQueue[sheetId] = [];
41767
+ }
41768
+ nextInQueue[sheetId].push(zone);
41404
41769
  }
41405
41770
  }
41771
+ for (const sheetId in nextInQueue) {
41772
+ const zones = futureRecomputeZones(nextInQueue[sheetId]);
41773
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
41774
+ }
41406
41775
  }
41407
41776
  visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41408
41777
  return visited;
@@ -41560,7 +41929,7 @@ class Evaluator {
41560
41929
  }
41561
41930
  if (!content) {
41562
41931
  // The previous content could have blocked some array formulas
41563
- impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41932
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedBy(positionId));
41564
41933
  }
41565
41934
  }
41566
41935
  return impactedPositionIds;
@@ -41608,13 +41977,22 @@ class Evaluator {
41608
41977
  }
41609
41978
  return positionIds;
41610
41979
  }
41611
- getArrayFormulasBlockedByOrSpreadingOn(positionId) {
41980
+ /**
41981
+ * Return the position of formulas blocked by the given position
41982
+ * as well as all their dependencies.
41983
+ */
41984
+ getArrayFormulasBlockedBy(positionId) {
41612
41985
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
41613
41986
  return [];
41614
41987
  }
41615
41988
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41616
41989
  const cells = new JetSet(arrayFormulas);
41617
- cells.addMany(this.getCellsDependingOn(arrayFormulas));
41990
+ const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
41991
+ if (arrayFormulaPositionId) {
41992
+ // ignore the formula spreading on the position. Keep only the blocked ones
41993
+ cells.delete(arrayFormulaPositionId);
41994
+ }
41995
+ cells.addMany(this.getCellsDependingOn(cells));
41618
41996
  return cells;
41619
41997
  }
41620
41998
  nextPositionsToUpdate = new JetSet();
@@ -41768,7 +42146,7 @@ class Evaluator {
41768
42146
  }
41769
42147
  this.evaluatedCells.delete(child);
41770
42148
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41771
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
42149
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
41772
42150
  }
41773
42151
  this.spreadingRelations.removeNode(positionId);
41774
42152
  }
@@ -42140,7 +42518,7 @@ class EvaluationPlugin extends UIPlugin {
42140
42518
  ? getItemId(newFormat, data.formats)
42141
42519
  : exportedCellData.format;
42142
42520
  let content;
42143
- if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
42521
+ if (isExported && isFormula && formulaCell instanceof FormulaCellWithDependencies) {
42144
42522
  content = formulaCell.contentWithFixedReferences;
42145
42523
  }
42146
42524
  else {
@@ -57318,6 +57696,6 @@ const constants = {
57318
57696
  export { AbstractChart, CellErrorType, 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 };
57319
57697
 
57320
57698
 
57321
- __info__.version = "17.1.12";
57322
- __info__.date = "2024-04-18T16:45:30.109Z";
57323
- __info__.hash = "3e90776";
57699
+ __info__.version = "17.1.14";
57700
+ __info__.date = "2024-05-07T10:41:25.690Z";
57701
+ __info__.hash = "622c47c";