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