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