@odoo/o-spreadsheet 17.0.56 → 17.0.58

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.56
6
- * @date 2025-04-04T08:36:58.258Z
7
- * @hash c484787
5
+ * @version 17.0.58
6
+ * @date 2025-04-18T16:20:26.099Z
7
+ * @hash 88d92de
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -2002,6 +2002,7 @@
2002
2002
  "CLEAR_FORMATTING",
2003
2003
  "SET_BORDER",
2004
2004
  "SET_ZONE_BORDERS",
2005
+ "SET_BORDERS_ON_TARGET",
2005
2006
  /** CHART */
2006
2007
  "CREATE_CHART",
2007
2008
  "UPDATE_CHART",
@@ -4816,7 +4817,7 @@
4816
4817
  if (executed.type === "ADD_COLUMNS_ROWS") {
4817
4818
  return expandZoneOnInsertion(zone, executed.dimension === "COL" ? "left" : "top", executed.base, executed.position, executed.quantity);
4818
4819
  }
4819
- return { ...zone };
4820
+ return zone;
4820
4821
  }
4821
4822
  function transformRangeData(range, executed) {
4822
4823
  const deletedSheet = executed.type === "DELETE_SHEET" && executed.sheetId;
@@ -7617,6 +7618,12 @@
7617
7618
  }
7618
7619
  return null;
7619
7620
  }
7621
+ /**
7622
+ - \p{L} is for any letter (from any language)
7623
+ - \p{N} is for any number
7624
+ - the u flag at the end is for unicode, which enables the `\p{...}` syntax
7625
+ */
7626
+ const unicodeSymbolCharRegexp = /\p{L}|\p{N}|_|\.|!|\$/u;
7620
7627
  const SYMBOL_CHARS = new Set("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.!$");
7621
7628
  /**
7622
7629
  * A "Symbol" is just basically any word-like element that can appear in a
@@ -7657,7 +7664,8 @@
7657
7664
  };
7658
7665
  }
7659
7666
  }
7660
- while (chars.current && SYMBOL_CHARS.has(chars.current)) {
7667
+ while (chars.current &&
7668
+ (SYMBOL_CHARS.has(chars.current) || chars.current.match(unicodeSymbolCharRegexp))) {
7661
7669
  result += chars.shift();
7662
7670
  }
7663
7671
  if (result.length) {
@@ -34684,6 +34692,15 @@
34684
34692
  case "SET_BORDER":
34685
34693
  this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
34686
34694
  break;
34695
+ case "SET_BORDERS_ON_TARGET":
34696
+ for (const zone of cmd.target) {
34697
+ for (let row = zone.top; row <= zone.bottom; row++) {
34698
+ for (let col = zone.left; col <= zone.right; col++) {
34699
+ this.setBorder(cmd.sheetId, col, row, cmd.border);
34700
+ }
34701
+ }
34702
+ }
34703
+ break;
34687
34704
  case "SET_ZONE_BORDERS":
34688
34705
  if (cmd.border) {
34689
34706
  const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
@@ -36690,6 +36707,353 @@
36690
36707
  }
36691
36708
  }
36692
36709
 
36710
+ /**
36711
+ * ####################################################
36712
+ * # INTRODUCTION
36713
+ * ####################################################
36714
+ *
36715
+ * This file contain the function recomputeZones.
36716
+ * This function try to recompute in a performant way
36717
+ * an ensemble of zones possibly overlapping to avoid
36718
+ * overlapping and to reduce the number of zones.
36719
+ *
36720
+ * It also allows to remove some zones from the ensemble.
36721
+ *
36722
+ * In the following example, 2 zones are overlapping.
36723
+ * Applying recomputeZones will return zones without
36724
+ * overlapping:
36725
+ *
36726
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
36727
+ *
36728
+ * A B C D E A B C D E
36729
+ * 1 ___ 1 ___
36730
+ * 2 ___|_ | 2 ___| | |
36731
+ * 3 | |_|_| ---> 3 | | |_|
36732
+ * 4 |_____| 4 |___|_|
36733
+ * 6 6
36734
+ * 7 7
36735
+ *
36736
+ *
36737
+ * In the following example, 2 zones are contiguous.
36738
+ * Applying recomputeZones will return only one zone:
36739
+ *
36740
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
36741
+ *
36742
+ * A B C D E A B C D E
36743
+ * 1 _ ___ 1 _____
36744
+ * 2 | | | ---> 2 | |
36745
+ * 3 |_|___| 3 |_____|
36746
+ * 4 4
36747
+ *
36748
+ *
36749
+ * In the following example, we want to remove a zone
36750
+ * from the ensemble. Applying recomputeZones will
36751
+ * return the ensemble without the zone to remove:
36752
+ *
36753
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
36754
+ * "C4:D4", "E2:E4"]
36755
+ *
36756
+ * A B C D E F A B C D E F
36757
+ * 1 _______ 1 _______
36758
+ * 2 | | ---> 2 | |___| |
36759
+ * 3 | xxx | 3 | |___| |
36760
+ * 4 |_______| 4 |_|___|_|
36761
+ * 5 5
36762
+ *
36763
+ *
36764
+ * The exercise seems simple when we have only 2 zones.
36765
+ * But with n zones and in a performant way, we want to
36766
+ * avoid comparing each zone with all the others.
36767
+ *
36768
+ *
36769
+ * ####################################################
36770
+ * # Methodological approach
36771
+ * ####################################################
36772
+ *
36773
+ * The methodological approach to avoid comparing each
36774
+ * zone with all the others is to use a data structure
36775
+ * that allow to quickly find which zones are
36776
+ * overlapping with any other given zone.
36777
+ *
36778
+ * Here the idea is to profile the zones at the columns level.
36779
+ *
36780
+ * To do that, we propose to use a data structure
36781
+ * composed of 2 parts:
36782
+ * - profilesStartingPosition: a sorted number array
36783
+ * indicating on which columns a new profile begins.
36784
+ * - profiles: a map where the key is a column
36785
+ * position (from profilesStartingPosition) and the
36786
+ * value is a sorted number array representing a
36787
+ * profile.
36788
+ *
36789
+ *
36790
+ * See the following example: here profileStartingPosition
36791
+ * corresponds to [A,C,E,G,K]
36792
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
36793
+ * 1 ' ' ' '
36794
+ * 2 ' ' '_______' here profile correspond
36795
+ * 3 '___' |_______| for A to []
36796
+ * 4 | | for C to [3, 5]
36797
+ * 5 |___| for E to []
36798
+ * 6 for G to [2, 3]
36799
+ * 7 for K to []
36800
+ *
36801
+ *
36802
+ * Now we can easily find which zones are overlapping
36803
+ * with a given zone. Suppose we want to add a new zone
36804
+ * D5:H6 to the ensemble:
36805
+ *
36806
+ * With a binary search of left and right
36807
+ * A B C D E F G H I J K on profilesStartingPosition, we can
36808
+ * 1 ' ' ' ' find the indexes of the profiles on which
36809
+ * 2 ' ' '_______' to apply a modification.
36810
+ * 3 '___' |_______|
36811
+ * 4 | _|_______ Here we will:
36812
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
36813
+ * 6 |_________| - modify the profile in E --> become [4, 6]
36814
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
36815
+ * - add a new profile in I --> become [8, 10]
36816
+ *
36817
+ * See below the result:
36818
+ *
36819
+ * Note the particularity of the profile
36820
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
36821
+ * 1 ' ' ' ' ' '
36822
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
36823
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
36824
+ * 4 | | |___ ___ search of the top and bottom value on the
36825
+ * 5 |_| | | | profile array. Depending on the result index
36826
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
36827
+ * 7 go by pairs, we know if we are in a zone or
36828
+ * not and how operate.
36829
+ */
36830
+ /**
36831
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
36832
+ * This compute is particularly useful because after this function:
36833
+ * - you will find coordinate of a cell only once among all the zones
36834
+ * - the number of zones will be reduced to the minimum
36835
+ */
36836
+ function futureRecomputeZones(zones, zonesToRemove = []) {
36837
+ const profilesStartingPosition = [0];
36838
+ const profiles = new Map([[0, []]]);
36839
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
36840
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
36841
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
36842
+ }
36843
+ function modifyProfiles(// export for testing only
36844
+ profilesStartingPosition, profiles, zones, toRemove = false) {
36845
+ for (const zone of zones) {
36846
+ const leftValue = zone.left;
36847
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
36848
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
36849
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
36850
+ for (let i = leftIndex; i <= rightIndex; i++) {
36851
+ const profile = profiles.get(profilesStartingPosition[i]);
36852
+ modifyProfile(profile, zone, toRemove);
36853
+ }
36854
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
36855
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
36856
+ }
36857
+ }
36858
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
36859
+ if (value === undefined) {
36860
+ // this is only the case when the value correspond to a bottom value that could be undefined
36861
+ return profilesStartingPosition.length - 1;
36862
+ }
36863
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
36864
+ if (value != profilesStartingPosition[predecessorIndex]) {
36865
+ // mean that the value is not ending/starting at the same position as the previous/next profile
36866
+ // --> it's a new profile
36867
+ // --> we need to add it
36868
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
36869
+ // suppose the we want to add the for the left value
36870
+ // following profile following zone: 'C', the predecessor index
36871
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
36872
+ // The next line code will
36873
+ // A B C D A B C D copy the profile of 'B'
36874
+ // 1 '___' 1 '___' to 'C'. In the rest of the
36875
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
36876
+ // 3 |___| 3 |_|_| | function will adapt the waiting
36877
+ // 4 4 |___| 'C' profile [1, 3] to the
36878
+ // correct 'C' profile [1, 4]
36879
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
36880
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
36881
+ }
36882
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
36883
+ }
36884
+ /**
36885
+ * Suppose the following Suppose we want to add We want to have the
36886
+ * profile: the following zone: following profile:
36887
+ *
36888
+ * A B C D E F A B C D E F A B C D E F
36889
+ * 1 '___' 1 ' ' 1 '___'
36890
+ * 2 |___| 2 '___' 2 | |
36891
+ * 3 ' ' 3 | | 3 | |
36892
+ * 4 '___' --> 4 | | --> 4 | |
36893
+ * 6 | | 6 |___| 6 | |
36894
+ * 7 |___| 7 7 |___|
36895
+ * 8 8 8
36896
+ *
36897
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
36898
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
36899
+ * ____ ____ correspond to 6
36900
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
36901
+ * ____ is to know what must be deleted
36902
+ * Note that the 'filled [3, 7] and what must be added to the
36903
+ * zone' are always between existing profile.
36904
+ * an even index and its
36905
+ * next index
36906
+ *
36907
+ */
36908
+ function modifyProfile(profile, zone, toRemove = false) {
36909
+ const topValue = zone.top;
36910
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
36911
+ const newPoints = [];
36912
+ // Case we want to add a zone to the profile:
36913
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
36914
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
36915
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
36916
+ // so we can keep the index of the top position
36917
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
36918
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
36919
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
36920
+ newPoints.push(topValue);
36921
+ }
36922
+ if (bottomValue === undefined) {
36923
+ // The following two code lines will not impact the final result,
36924
+ // but they will impact the intermediate profile.
36925
+ // We keep them for performance reason
36926
+ profile.splice(topPredIndex + 1);
36927
+ profile.push(...newPoints);
36928
+ return;
36929
+ }
36930
+ // Case we want to add a zone to the profile:
36931
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
36932
+ // so we can keep the index of the bottom position.
36933
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
36934
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
36935
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
36936
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
36937
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
36938
+ newPoints.push(bottomValue);
36939
+ }
36940
+ // add the top and bottom value to the profile and
36941
+ // remove all information between the top and bottom index
36942
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
36943
+ }
36944
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
36945
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
36946
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
36947
+ for (let i = end; i > start; i--) {
36948
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
36949
+ profiles.delete(profilesStartingPosition[i]);
36950
+ profilesStartingPosition.splice(i, 1);
36951
+ }
36952
+ }
36953
+ }
36954
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
36955
+ const mergedZone = [];
36956
+ let pendingZones = [];
36957
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
36958
+ const left = profilesStartingPosition[colIndex];
36959
+ const profile = profiles.get(left);
36960
+ if (!profile || profile.length === 0) {
36961
+ mergedZone.push(...pendingZones);
36962
+ pendingZones = [];
36963
+ continue;
36964
+ }
36965
+ let right = profilesStartingPosition[colIndex + 1];
36966
+ if (right !== undefined) {
36967
+ right--;
36968
+ }
36969
+ const nextPendingZones = [];
36970
+ for (let i = 0; i < profile.length; i += 2) {
36971
+ const top = profile[i];
36972
+ let bottom = profile[i + 1];
36973
+ if (bottom !== undefined) {
36974
+ bottom--;
36975
+ }
36976
+ const profileZone = {
36977
+ top,
36978
+ left,
36979
+ bottom,
36980
+ right,
36981
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
36982
+ };
36983
+ let findCorrespondingZone = false;
36984
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
36985
+ const pendingZone = pendingZones[j];
36986
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
36987
+ pendingZone.right = profileZone.right;
36988
+ pendingZones.splice(j, 1);
36989
+ nextPendingZones.push(pendingZone);
36990
+ findCorrespondingZone = true;
36991
+ break;
36992
+ }
36993
+ }
36994
+ if (!findCorrespondingZone) {
36995
+ nextPendingZones.push(profileZone);
36996
+ }
36997
+ }
36998
+ mergedZone.push(...pendingZones);
36999
+ pendingZones = nextPendingZones;
37000
+ }
37001
+ mergedZone.push(...pendingZones);
37002
+ return mergedZone;
37003
+ }
37004
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
37005
+ let end = arr.length - 1;
37006
+ let result = -1;
37007
+ while (start <= end) {
37008
+ const mid = Math.floor((start + end) / 2);
37009
+ if (arr[mid] === val && matchEqual) {
37010
+ return mid;
37011
+ }
37012
+ else if (arr[mid] < val) {
37013
+ result = mid;
37014
+ start = mid + 1;
37015
+ }
37016
+ else {
37017
+ end = mid - 1;
37018
+ }
37019
+ }
37020
+ return result;
37021
+ }
37022
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
37023
+ let end = arr.length - 1;
37024
+ let result = arr.length;
37025
+ while (start <= end) {
37026
+ const mid = Math.floor((start + end) / 2);
37027
+ if (arr[mid] === val && matchEqual) {
37028
+ return mid;
37029
+ }
37030
+ else if (arr[mid] > val) {
37031
+ result = mid;
37032
+ end = mid - 1;
37033
+ }
37034
+ else {
37035
+ start = mid + 1;
37036
+ }
37037
+ }
37038
+ return result;
37039
+ }
37040
+ /**
37041
+ * Compares two arrays.
37042
+ * For performance reasons, this function is to be preferred
37043
+ * to 'deepEquals' in the case we know that the inputs are arrays.
37044
+ */
37045
+ function deepEqualsArray(arr1, arr2) {
37046
+ if (arr1.length !== arr2.length) {
37047
+ return false;
37048
+ }
37049
+ for (let i = 0; i < arr1.length; i++) {
37050
+ if (!deepEquals(arr1[i], arr2[i])) {
37051
+ return false;
37052
+ }
37053
+ }
37054
+ return true;
37055
+ }
37056
+
36693
37057
  // -----------------------------------------------------------------------------
36694
37058
  // Constants
36695
37059
  // -----------------------------------------------------------------------------
@@ -36859,10 +37223,12 @@
36859
37223
  const replaceIndex = rules.findIndex((c) => c.id === cf.id);
36860
37224
  let currentRanges = [];
36861
37225
  if (replaceIndex > -1) {
36862
- currentRanges = rules[replaceIndex].ranges;
37226
+ currentRanges = rules[replaceIndex].ranges.map(toUnboundedZone);
36863
37227
  }
36864
37228
  currentRanges = currentRanges.concat(toAdd);
36865
- return recomputeZones(currentRanges, toRemove);
37229
+ // Remove the zones first in case the same position is in toAdd and toRemove
37230
+ const withRemovedZones = futureRecomputeZones(currentRanges, toRemove);
37231
+ return futureRecomputeZones([...toAdd, ...withRemovedZones], []);
36866
37232
  }
36867
37233
  // ---------------------------------------------------------------------------
36868
37234
  // Private
@@ -40597,353 +40963,6 @@
40597
40963
  }
40598
40964
  }
40599
40965
 
40600
- /**
40601
- * ####################################################
40602
- * # INTRODUCTION
40603
- * ####################################################
40604
- *
40605
- * This file contain the function recomputeZones.
40606
- * This function try to recompute in a performant way
40607
- * an ensemble of zones possibly overlapping to avoid
40608
- * overlapping and to reduce the number of zones.
40609
- *
40610
- * It also allows to remove some zones from the ensemble.
40611
- *
40612
- * In the following example, 2 zones are overlapping.
40613
- * Applying recomputeZones will return zones without
40614
- * overlapping:
40615
- *
40616
- * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
40617
- *
40618
- * A B C D E A B C D E
40619
- * 1 ___ 1 ___
40620
- * 2 ___|_ | 2 ___| | |
40621
- * 3 | |_|_| ---> 3 | | |_|
40622
- * 4 |_____| 4 |___|_|
40623
- * 6 6
40624
- * 7 7
40625
- *
40626
- *
40627
- * In the following example, 2 zones are contiguous.
40628
- * Applying recomputeZones will return only one zone:
40629
- *
40630
- * ["B2:B3", "C2:D3"] ["B2:D3"]
40631
- *
40632
- * A B C D E A B C D E
40633
- * 1 _ ___ 1 _____
40634
- * 2 | | | ---> 2 | |
40635
- * 3 |_|___| 3 |_____|
40636
- * 4 4
40637
- *
40638
- *
40639
- * In the following example, we want to remove a zone
40640
- * from the ensemble. Applying recomputeZones will
40641
- * return the ensemble without the zone to remove:
40642
- *
40643
- * remove ["C3:D3"] ["B2:B4", "C2:D2",
40644
- * "C4:D4", "E2:E4"]
40645
- *
40646
- * A B C D E F A B C D E F
40647
- * 1 _______ 1 _______
40648
- * 2 | | ---> 2 | |___| |
40649
- * 3 | xxx | 3 | |___| |
40650
- * 4 |_______| 4 |_|___|_|
40651
- * 5 5
40652
- *
40653
- *
40654
- * The exercise seems simple when we have only 2 zones.
40655
- * But with n zones and in a performant way, we want to
40656
- * avoid comparing each zone with all the others.
40657
- *
40658
- *
40659
- * ####################################################
40660
- * # Methodological approach
40661
- * ####################################################
40662
- *
40663
- * The methodological approach to avoid comparing each
40664
- * zone with all the others is to use a data structure
40665
- * that allow to quickly find which zones are
40666
- * overlapping with any other given zone.
40667
- *
40668
- * Here the idea is to profile the zones at the columns level.
40669
- *
40670
- * To do that, we propose to use a data structure
40671
- * composed of 2 parts:
40672
- * - profilesStartingPosition: a sorted number array
40673
- * indicating on which columns a new profile begins.
40674
- * - profiles: a map where the key is a column
40675
- * position (from profilesStartingPosition) and the
40676
- * value is a sorted number array representing a
40677
- * profile.
40678
- *
40679
- *
40680
- * See the following example: here profileStartingPosition
40681
- * corresponds to [A,C,E,G,K]
40682
- * A B C D E F G H I J K so with number [0,2,4,6,10]
40683
- * 1 ' ' ' '
40684
- * 2 ' ' '_______' here profile correspond
40685
- * 3 '___' |_______| for A to []
40686
- * 4 | | for C to [3, 5]
40687
- * 5 |___| for E to []
40688
- * 6 for G to [2, 3]
40689
- * 7 for K to []
40690
- *
40691
- *
40692
- * Now we can easily find which zones are overlapping
40693
- * with a given zone. Suppose we want to add a new zone
40694
- * D5:H6 to the ensemble:
40695
- *
40696
- * With a binary search of left and right
40697
- * A B C D E F G H I J K on profilesStartingPosition, we can
40698
- * 1 ' ' ' ' find the indexes of the profiles on which
40699
- * 2 ' ' '_______' to apply a modification.
40700
- * 3 '___' |_______|
40701
- * 4 | _|_______ Here we will:
40702
- * 5 |_|_| | - add a new profile in D --> become [3, 6]
40703
- * 6 |_________| - modify the profile in E --> become [4, 6]
40704
- * 7 - modify the profile in G --> become [2, 3, 4, 6]
40705
- * - add a new profile in I --> become [8, 10]
40706
- *
40707
- * See below the result:
40708
- *
40709
- * Note the particularity of the profile
40710
- * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
40711
- * 1 ' ' ' ' ' '
40712
- * 2 ' ' ' '___'___' To know how to modify the profile (add a
40713
- * 3 '_'_' |___|___| zone or remove it) we do a binary
40714
- * 4 | | |___ ___ search of the top and bottom value on the
40715
- * 5 |_| | | | profile array. Depending on the result index
40716
- * 6 |_|___|___| parity (odd or even), because zone boundaries
40717
- * 7 go by pairs, we know if we are in a zone or
40718
- * not and how operate.
40719
- */
40720
- /**
40721
- * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
40722
- * This compute is particularly useful because after this function:
40723
- * - you will find coordinate of a cell only once among all the zones
40724
- * - the number of zones will be reduced to the minimum
40725
- */
40726
- function futureRecomputeZones(zones, zonesToRemove = []) {
40727
- const profilesStartingPosition = [0];
40728
- const profiles = new Map([[0, []]]);
40729
- modifyProfiles(profilesStartingPosition, profiles, zones, false);
40730
- modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
40731
- return constructZonesFromProfiles(profilesStartingPosition, profiles);
40732
- }
40733
- function modifyProfiles(// export for testing only
40734
- profilesStartingPosition, profiles, zones, toRemove = false) {
40735
- for (const zone of zones) {
40736
- const leftValue = zone.left;
40737
- const rightValue = zone.right === undefined ? undefined : zone.right + 1;
40738
- const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
40739
- const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
40740
- for (let i = leftIndex; i <= rightIndex; i++) {
40741
- const profile = profiles.get(profilesStartingPosition[i]);
40742
- modifyProfile(profile, zone, toRemove);
40743
- }
40744
- // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
40745
- removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
40746
- }
40747
- }
40748
- function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
40749
- if (value === undefined) {
40750
- // this is only the case when the value correspond to a bottom value that could be undefined
40751
- return profilesStartingPosition.length - 1;
40752
- }
40753
- const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
40754
- if (value != profilesStartingPosition[predecessorIndex]) {
40755
- // mean that the value is not ending/starting at the same position as the previous/next profile
40756
- // --> it's a new profile
40757
- // --> we need to add it
40758
- profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
40759
- // suppose the we want to add the for the left value
40760
- // following profile following zone: 'C', the predecessor index
40761
- // for B: [1, 3] "C3:D4" correspond to 'B'.
40762
- // The next line code will
40763
- // A B C D A B C D copy the profile of 'B'
40764
- // 1 '___' 1 '___' to 'C'. In the rest of the
40765
- // 2 | | ---> 2 | _|_ process the 'modifyProfile'
40766
- // 3 |___| 3 |_|_| | function will adapt the waiting
40767
- // 4 4 |___| 'C' profile [1, 3] to the
40768
- // correct 'C' profile [1, 4]
40769
- profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
40770
- return searchLeft ? predecessorIndex + 1 : predecessorIndex;
40771
- }
40772
- return searchLeft ? predecessorIndex : predecessorIndex - 1;
40773
- }
40774
- /**
40775
- * Suppose the following Suppose we want to add We want to have the
40776
- * profile: the following zone: following profile:
40777
- *
40778
- * A B C D E F A B C D E F A B C D E F
40779
- * 1 '___' 1 ' ' 1 '___'
40780
- * 2 |___| 2 '___' 2 | |
40781
- * 3 ' ' 3 | | 3 | |
40782
- * 4 '___' --> 4 | | --> 4 | |
40783
- * 6 | | 6 |___| 6 | |
40784
- * 7 |___| 7 7 |___|
40785
- * 8 8 8
40786
- *
40787
- * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
40788
- * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
40789
- * ____ ____ correspond to 6
40790
- * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
40791
- * ____ is to know what must be deleted
40792
- * Note that the 'filled [3, 7] and what must be added to the
40793
- * zone' are always between existing profile.
40794
- * an even index and its
40795
- * next index
40796
- *
40797
- */
40798
- function modifyProfile(profile, zone, toRemove = false) {
40799
- const topValue = zone.top;
40800
- const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
40801
- const newPoints = [];
40802
- // Case we want to add a zone to the profile:
40803
- // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
40804
- // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
40805
- // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
40806
- // so we can keep the index of the top position
40807
- // Case we want to remove a zone from the profile: it's the opposite of the previous case
40808
- const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
40809
- if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
40810
- newPoints.push(topValue);
40811
- }
40812
- if (bottomValue === undefined) {
40813
- // The following two code lines will not impact the final result,
40814
- // but they will impact the intermediate profile.
40815
- // We keep them for performance reason
40816
- profile.splice(topPredIndex + 1);
40817
- profile.push(...newPoints);
40818
- return;
40819
- }
40820
- // Case we want to add a zone to the profile:
40821
- // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
40822
- // so we can keep the index of the bottom position.
40823
- // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
40824
- // so we don't need to add it to the profile. we can keep in reference the index of the successor
40825
- // Case we want to remove a zone from the profile: it's the opposite of the previous case
40826
- const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
40827
- if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
40828
- newPoints.push(bottomValue);
40829
- }
40830
- // add the top and bottom value to the profile and
40831
- // remove all information between the top and bottom index
40832
- profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
40833
- }
40834
- function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
40835
- const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
40836
- const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
40837
- for (let i = end; i > start; i--) {
40838
- if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
40839
- profiles.delete(profilesStartingPosition[i]);
40840
- profilesStartingPosition.splice(i, 1);
40841
- }
40842
- }
40843
- }
40844
- function constructZonesFromProfiles(profilesStartingPosition, profiles) {
40845
- const mergedZone = [];
40846
- let pendingZones = [];
40847
- for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
40848
- const left = profilesStartingPosition[colIndex];
40849
- const profile = profiles.get(left);
40850
- if (!profile || profile.length === 0) {
40851
- mergedZone.push(...pendingZones);
40852
- pendingZones = [];
40853
- continue;
40854
- }
40855
- let right = profilesStartingPosition[colIndex + 1];
40856
- if (right !== undefined) {
40857
- right--;
40858
- }
40859
- const nextPendingZones = [];
40860
- for (let i = 0; i < profile.length; i += 2) {
40861
- const top = profile[i];
40862
- let bottom = profile[i + 1];
40863
- if (bottom !== undefined) {
40864
- bottom--;
40865
- }
40866
- const profileZone = {
40867
- top,
40868
- left,
40869
- bottom,
40870
- right,
40871
- hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
40872
- };
40873
- let findCorrespondingZone = false;
40874
- for (let j = pendingZones.length - 1; j >= 0; j--) {
40875
- const pendingZone = pendingZones[j];
40876
- if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
40877
- pendingZone.right = profileZone.right;
40878
- pendingZones.splice(j, 1);
40879
- nextPendingZones.push(pendingZone);
40880
- findCorrespondingZone = true;
40881
- break;
40882
- }
40883
- }
40884
- if (!findCorrespondingZone) {
40885
- nextPendingZones.push(profileZone);
40886
- }
40887
- }
40888
- mergedZone.push(...pendingZones);
40889
- pendingZones = nextPendingZones;
40890
- }
40891
- mergedZone.push(...pendingZones);
40892
- return mergedZone;
40893
- }
40894
- function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
40895
- let end = arr.length - 1;
40896
- let result = -1;
40897
- while (start <= end) {
40898
- const mid = Math.floor((start + end) / 2);
40899
- if (arr[mid] === val && matchEqual) {
40900
- return mid;
40901
- }
40902
- else if (arr[mid] < val) {
40903
- result = mid;
40904
- start = mid + 1;
40905
- }
40906
- else {
40907
- end = mid - 1;
40908
- }
40909
- }
40910
- return result;
40911
- }
40912
- function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
40913
- let end = arr.length - 1;
40914
- let result = arr.length;
40915
- while (start <= end) {
40916
- const mid = Math.floor((start + end) / 2);
40917
- if (arr[mid] === val && matchEqual) {
40918
- return mid;
40919
- }
40920
- else if (arr[mid] > val) {
40921
- result = mid;
40922
- end = mid - 1;
40923
- }
40924
- else {
40925
- start = mid + 1;
40926
- }
40927
- }
40928
- return result;
40929
- }
40930
- /**
40931
- * Compares two arrays.
40932
- * For performance reasons, this function is to be preferred
40933
- * to 'deepEquals' in the case we know that the inputs are arrays.
40934
- */
40935
- function deepEqualsArray(arr1, arr2) {
40936
- if (arr1.length !== arr2.length) {
40937
- return false;
40938
- }
40939
- for (let i = 0; i < arr1.length; i++) {
40940
- if (!deepEquals(arr1[i], arr2[i])) {
40941
- return false;
40942
- }
40943
- }
40944
- return true;
40945
- }
40946
-
40947
40966
  function quickselect(arr, k, left, right, compare) {
40948
40967
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
40949
40968
  }
@@ -43540,25 +43559,6 @@
43540
43559
  case "AUTOFILL_AUTO":
43541
43560
  this.autofillAuto();
43542
43561
  break;
43543
- case "AUTOFILL_CELL":
43544
- this.autoFillMerge(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
43545
- const sheetId = this.getters.getActiveSheetId();
43546
- this.dispatch("UPDATE_CELL", {
43547
- sheetId,
43548
- col: cmd.col,
43549
- row: cmd.row,
43550
- style: cmd.style || null,
43551
- content: cmd.content || "",
43552
- format: cmd.format || "",
43553
- });
43554
- this.dispatch("SET_BORDER", {
43555
- sheetId,
43556
- col: cmd.col,
43557
- row: cmd.row,
43558
- border: cmd.border,
43559
- });
43560
- this.autofillCF(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
43561
- this.autofillDV(cmd.originCol, cmd.originRow, cmd.col, cmd.row);
43562
43562
  }
43563
43563
  }
43564
43564
  // ---------------------------------------------------------------------------
@@ -43582,6 +43582,7 @@
43582
43582
  }
43583
43583
  const source = this.getters.getSelectedZone();
43584
43584
  const target = this.autofillZone;
43585
+ const autofillCellsData = [];
43585
43586
  switch (this.direction) {
43586
43587
  case "down" /* DIRECTION.DOWN */:
43587
43588
  for (let col = source.left; col <= source.right; col++) {
@@ -43591,7 +43592,7 @@
43591
43592
  }
43592
43593
  const generator = this.createGenerator(xcs);
43593
43594
  for (let row = target.top; row <= target.bottom; row++) {
43594
- this.computeNewCell(generator, col, row, apply);
43595
+ autofillCellsData.push(this.computeNewCell(generator, col, row));
43595
43596
  }
43596
43597
  }
43597
43598
  break;
@@ -43603,7 +43604,7 @@
43603
43604
  }
43604
43605
  const generator = this.createGenerator(xcs);
43605
43606
  for (let row = target.bottom; row >= target.top; row--) {
43606
- this.computeNewCell(generator, col, row, apply);
43607
+ autofillCellsData.push(this.computeNewCell(generator, col, row));
43607
43608
  }
43608
43609
  }
43609
43610
  break;
@@ -43615,7 +43616,7 @@
43615
43616
  }
43616
43617
  const generator = this.createGenerator(xcs);
43617
43618
  for (let col = target.right; col >= target.left; col--) {
43618
- this.computeNewCell(generator, col, row, apply);
43619
+ autofillCellsData.push(this.computeNewCell(generator, col, row));
43619
43620
  }
43620
43621
  }
43621
43622
  break;
@@ -43627,12 +43628,26 @@
43627
43628
  }
43628
43629
  const generator = this.createGenerator(xcs);
43629
43630
  for (let col = target.left; col <= target.right; col++) {
43630
- this.computeNewCell(generator, col, row, apply);
43631
+ autofillCellsData.push(this.computeNewCell(generator, col, row));
43631
43632
  }
43632
43633
  }
43633
43634
  break;
43634
43635
  }
43635
43636
  if (apply) {
43637
+ const bordersZones = {};
43638
+ const cfNewRanges = {};
43639
+ const dvNewZones = {};
43640
+ const sheetId = this.getters.getActiveSheetId();
43641
+ for (const data of autofillCellsData) {
43642
+ this.collectBordersData(data, bordersZones);
43643
+ this.autofillMerge(sheetId, data);
43644
+ this.autofillCell(sheetId, data);
43645
+ this.collectConditionalFormatsData(sheetId, data, cfNewRanges);
43646
+ this.collectDataValidationsData(sheetId, data, dvNewZones);
43647
+ }
43648
+ this.autofillBorders(sheetId, bordersZones);
43649
+ this.autofillConditionalFormats(sheetId, cfNewRanges);
43650
+ this.autofillDataValidations(sheetId, dvNewZones);
43636
43651
  this.autofillZone = undefined;
43637
43652
  this.selection.resizeAnchorZone(this.direction, this.steps);
43638
43653
  this.lastCellSelected = {};
@@ -43641,6 +43656,95 @@
43641
43656
  this.tooltip = undefined;
43642
43657
  }
43643
43658
  }
43659
+ collectBordersData(data, bordersPositions) {
43660
+ const key = JSON.stringify(data.border);
43661
+ if (!(key in bordersPositions)) {
43662
+ bordersPositions[key] = [];
43663
+ }
43664
+ bordersPositions[key].push(positionToZone({ col: data.col, row: data.row }));
43665
+ }
43666
+ collectConditionalFormatsData(sheetId, data, cfNewRanges) {
43667
+ const { originCol, originRow, col, row } = data;
43668
+ const cfsAtOrigin = this.getters.getRulesByCell(sheetId, originCol, originRow);
43669
+ const xc = toXC(col, row);
43670
+ for (const cf of cfsAtOrigin) {
43671
+ if (!(cf.id in cfNewRanges)) {
43672
+ cfNewRanges[cf.id] = [];
43673
+ }
43674
+ cfNewRanges[cf.id].push(xc);
43675
+ }
43676
+ }
43677
+ collectDataValidationsData(sheetId, data, dvNewZones) {
43678
+ const { originCol, originRow, col, row } = data;
43679
+ const cellPosition = { sheetId, col: originCol, row: originRow };
43680
+ const dvsAtOrigin = this.getters.getValidationRuleForCell(cellPosition);
43681
+ if (!dvsAtOrigin) {
43682
+ return;
43683
+ }
43684
+ if (!(dvsAtOrigin.id in dvNewZones)) {
43685
+ dvNewZones[dvsAtOrigin.id] = [];
43686
+ }
43687
+ dvNewZones[dvsAtOrigin.id].push(positionToZone({ col, row }));
43688
+ }
43689
+ autofillCell(sheetId, data) {
43690
+ this.dispatch("UPDATE_CELL", {
43691
+ sheetId,
43692
+ col: data.col,
43693
+ row: data.row,
43694
+ content: data.content || "",
43695
+ style: data.style || null,
43696
+ format: data.format || "",
43697
+ });
43698
+ // Still usefull in odoo ATM to autofill field sync
43699
+ this.dispatch("AUTOFILL_CELL", data);
43700
+ }
43701
+ autofillBorders(sheetId, bordersPositions) {
43702
+ for (const stringifiedBorder in bordersPositions) {
43703
+ const border = stringifiedBorder === "undefined" ? undefined : JSON.parse(stringifiedBorder);
43704
+ this.dispatch("SET_BORDERS_ON_TARGET", {
43705
+ sheetId,
43706
+ border,
43707
+ target: futureRecomputeZones(bordersPositions[stringifiedBorder]),
43708
+ });
43709
+ }
43710
+ }
43711
+ autofillConditionalFormats(sheetId, cfNewRanges) {
43712
+ for (const cfId in cfNewRanges) {
43713
+ const changes = cfNewRanges[cfId];
43714
+ const cf = this.getters.getConditionalFormats(sheetId).find((cf) => cf.id === cfId);
43715
+ if (!cf) {
43716
+ continue;
43717
+ }
43718
+ const newCfZones = this.getters.getAdaptedCfRanges(sheetId, cf, changes.map(toZone), []);
43719
+ if (newCfZones) {
43720
+ this.dispatch("ADD_CONDITIONAL_FORMAT", {
43721
+ cf: {
43722
+ id: cf.id,
43723
+ rule: cf.rule,
43724
+ stopIfTrue: cf.stopIfTrue,
43725
+ },
43726
+ ranges: newCfZones.map((xc) => this.getters.getRangeDataFromZone(sheetId, xc)),
43727
+ sheetId,
43728
+ });
43729
+ }
43730
+ }
43731
+ }
43732
+ autofillDataValidations(sheetId, dvNewZones) {
43733
+ for (const dvId in dvNewZones) {
43734
+ const changes = dvNewZones[dvId];
43735
+ const dvOrigin = this.getters.getDataValidationRule(sheetId, dvId);
43736
+ if (!dvOrigin) {
43737
+ continue;
43738
+ }
43739
+ const dvRangesXcs = dvOrigin.ranges.map((range) => range.zone);
43740
+ const newDvRanges = futureRecomputeZones(dvRangesXcs.concat(changes), []);
43741
+ this.dispatch("ADD_DATA_VALIDATION_RULE", {
43742
+ rule: dvOrigin,
43743
+ ranges: newDvRanges.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
43744
+ sheetId,
43745
+ });
43746
+ }
43747
+ }
43644
43748
  /**
43645
43749
  * Select a cell which becomes the last cell of the autofillZone
43646
43750
  */
@@ -43707,22 +43811,20 @@
43707
43811
  /**
43708
43812
  * Generate the next cell
43709
43813
  */
43710
- computeNewCell(generator, col, row, apply) {
43814
+ computeNewCell(generator, col, row) {
43711
43815
  const { cellData, tooltip, origin } = generator.next();
43712
43816
  const { content, style, border, format } = cellData;
43713
43817
  this.tooltip = tooltip;
43714
- if (apply) {
43715
- this.dispatch("AUTOFILL_CELL", {
43716
- originCol: origin.col,
43717
- originRow: origin.row,
43718
- col,
43719
- row,
43720
- content,
43721
- style,
43722
- border,
43723
- format,
43724
- });
43725
- }
43818
+ return {
43819
+ originCol: origin.col,
43820
+ originRow: origin.row,
43821
+ col,
43822
+ row,
43823
+ content,
43824
+ style,
43825
+ border,
43826
+ format,
43827
+ };
43726
43828
  }
43727
43829
  /**
43728
43830
  * Get the rule associated to the current cell
@@ -43790,8 +43892,8 @@
43790
43892
  ? position[first].value
43791
43893
  : position[second].value;
43792
43894
  }
43793
- autoFillMerge(originCol, originRow, col, row) {
43794
- const sheetId = this.getters.getActiveSheetId();
43895
+ autofillMerge(sheetId, data) {
43896
+ const { originCol, originRow, col, row } = data;
43795
43897
  const position = { sheetId, col, row };
43796
43898
  const originPosition = { sheetId, col: originCol, row: originRow };
43797
43899
  if (this.getters.isInMerge(position) && !this.getters.isInMerge(originPosition)) {
@@ -43818,35 +43920,6 @@
43818
43920
  });
43819
43921
  }
43820
43922
  }
43821
- autofillCF(originCol, originRow, col, row) {
43822
- const sheetId = this.getters.getActiveSheetId();
43823
- const cfOrigin = this.getters.getRulesByCell(sheetId, originCol, originRow);
43824
- for (const cf of cfOrigin) {
43825
- const newCfRanges = this.getters.getAdaptedCfRanges(sheetId, cf, [toXC(col, row)], []);
43826
- if (newCfRanges) {
43827
- this.dispatch("ADD_CONDITIONAL_FORMAT", {
43828
- cf: deepCopy(cf),
43829
- ranges: newCfRanges.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
43830
- sheetId,
43831
- });
43832
- }
43833
- }
43834
- }
43835
- autofillDV(originCol, originRow, col, row) {
43836
- const sheetId = this.getters.getActiveSheetId();
43837
- const cellPosition = { sheetId, col: originCol, row: originRow };
43838
- const dvOrigin = this.getters.getValidationRuleForCell(cellPosition);
43839
- if (!dvOrigin) {
43840
- return;
43841
- }
43842
- const dvRangesXcs = dvOrigin.ranges.map((range) => this.getters.getRangeString(range, sheetId));
43843
- const newDvRanges = recomputeZones(dvRangesXcs.concat(toXC(col, row)), []);
43844
- this.dispatch("ADD_DATA_VALIDATION_RULE", {
43845
- rule: dvOrigin,
43846
- ranges: newDvRanges.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
43847
- sheetId,
43848
- });
43849
- }
43850
43923
  // ---------------------------------------------------------------------------
43851
43924
  // Grid rendering
43852
43925
  // ---------------------------------------------------------------------------
@@ -44344,10 +44417,8 @@
44344
44417
  }
44345
44418
  const target = [];
44346
44419
  for (const zone1 of toTransform.target) {
44347
- for (const zone2 of executed.target) {
44348
- if (!overlap(zone1, zone2)) {
44349
- target.push({ ...zone1 });
44350
- }
44420
+ if (executed.target.every((zone2) => !overlap(zone1, zone2))) {
44421
+ target.push(zone1);
44351
44422
  }
44352
44423
  }
44353
44424
  if (target.length) {
@@ -45233,6 +45304,9 @@
45233
45304
  copiedTables;
45234
45305
  zones;
45235
45306
  uuidGenerator = new UuidGenerator();
45307
+ queuedBordersToAdd = {};
45308
+ queuedCFChanges = {};
45309
+ queuedDVChanges = {};
45236
45310
  constructor(zones, operation, getters, dispatch, selection) {
45237
45311
  super(operation, getters, dispatch, selection);
45238
45312
  if (!zones.length) {
@@ -45342,6 +45416,8 @@
45342
45416
  * Paste the clipboard content in the given target
45343
45417
  */
45344
45418
  paste(target, options) {
45419
+ this.queuedCFChanges = {};
45420
+ this.queuedDVChanges = {};
45345
45421
  if (this.operation === "COPY") {
45346
45422
  this.pasteFromCopy(target, options);
45347
45423
  }
@@ -45354,6 +45430,7 @@
45354
45430
  if (options?.selectTarget) {
45355
45431
  this.selectPastedZone(width, height, isCutOperation, target);
45356
45432
  }
45433
+ this.executeQueuedChanges(this.getters.getActiveSheetId());
45357
45434
  }
45358
45435
  pasteFromCopy(target, options) {
45359
45436
  if (target.length === 1) {
@@ -45528,7 +45605,11 @@
45528
45605
  left: targetBorders?.left || originBorders?.left,
45529
45606
  right: targetBorders?.right || originBorders?.right,
45530
45607
  };
45531
- this.dispatch("SET_BORDER", { sheetId, col, row, border });
45608
+ const borderKey = JSON.stringify(border);
45609
+ if (!this.queuedBordersToAdd[borderKey]) {
45610
+ this.queuedBordersToAdd[borderKey] = [];
45611
+ }
45612
+ this.queuedBordersToAdd[borderKey].push(positionToZone(target));
45532
45613
  if (clipboardOption?.pasteOption === "onlyFormat") {
45533
45614
  this.dispatch("UPDATE_CELL", {
45534
45615
  ...target,
@@ -45674,7 +45755,8 @@
45674
45755
  }
45675
45756
  }
45676
45757
  pasteCf(origin, target) {
45677
- const xc = toXC(target.col, target.row);
45758
+ const zone = positionToZone(target);
45759
+ const originZone = positionToZone(origin);
45678
45760
  for (let rule of this.getters.getConditionalFormats(origin.sheetId)) {
45679
45761
  for (let range of rule.ranges) {
45680
45762
  if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
@@ -45682,15 +45764,15 @@
45682
45764
  const toRemoveRange = [];
45683
45765
  if (this.operation === "CUT") {
45684
45766
  //remove from current rule
45685
- toRemoveRange.push(toXC(origin.col, origin.row));
45767
+ toRemoveRange.push(originZone);
45686
45768
  }
45687
45769
  if (origin.sheetId === target.sheetId) {
45688
- this.adaptCFRules(origin.sheetId, cf, [xc], toRemoveRange);
45770
+ this.adaptCFRules(origin.sheetId, cf, [zone], toRemoveRange);
45689
45771
  }
45690
45772
  else {
45691
45773
  this.adaptCFRules(origin.sheetId, cf, [], toRemoveRange);
45692
45774
  const cfToCopyTo = this.getCFToCopyTo(target.sheetId, cf);
45693
- this.adaptCFRules(target.sheetId, cfToCopyTo, [xc], []);
45775
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
45694
45776
  }
45695
45777
  }
45696
45778
  }
@@ -45700,79 +45782,130 @@
45700
45782
  * Add or remove cells to a given conditional formatting rule.
45701
45783
  */
45702
45784
  adaptCFRules(sheetId, cf, toAdd, toRemove) {
45703
- const newRangesXC = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
45704
- if (!newRangesXC) {
45705
- return;
45785
+ if (!this.queuedCFChanges[sheetId]) {
45786
+ this.queuedCFChanges[sheetId] = [];
45706
45787
  }
45707
- if (newRangesXC.length === 0) {
45708
- this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
45709
- return;
45788
+ const queuedChange = this.queuedCFChanges[sheetId].find((queued) => queued.cf.id === cf.id);
45789
+ if (!queuedChange) {
45790
+ this.queuedCFChanges[sheetId].push({ toAdd, toRemove, cf });
45791
+ }
45792
+ else {
45793
+ queuedChange.toAdd.push(...toAdd);
45794
+ queuedChange.toRemove.push(...toRemove);
45710
45795
  }
45711
- this.dispatch("ADD_CONDITIONAL_FORMAT", {
45712
- cf: {
45713
- id: cf.id,
45714
- rule: cf.rule,
45715
- stopIfTrue: cf.stopIfTrue,
45716
- },
45717
- ranges: newRangesXC.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
45718
- sheetId,
45719
- });
45720
45796
  }
45721
45797
  getCFToCopyTo(targetSheetId, originCF) {
45722
- const cfInTarget = this.getters
45798
+ let targetCF = this.getters
45723
45799
  .getConditionalFormats(targetSheetId)
45724
45800
  .find((cf) => cf.stopIfTrue === originCF.stopIfTrue && deepEquals(cf.rule, originCF.rule));
45725
- return cfInTarget
45726
- ? cfInTarget
45727
- : { ...originCF, id: this.uuidGenerator.smallUuid(), ranges: [] };
45801
+ const queuedCfs = this.queuedCFChanges[targetSheetId];
45802
+ if (!targetCF && queuedCfs) {
45803
+ targetCF = queuedCfs.find((queued) => queued.cf.stopIfTrue === originCF.stopIfTrue && deepEquals(queued.cf.rule, originCF.rule))?.cf;
45804
+ }
45805
+ return targetCF ? targetCF : { ...originCF, id: this.uuidGenerator.smallUuid(), ranges: [] };
45728
45806
  }
45729
45807
  pasteDataValidation(origin, target) {
45730
45808
  const rule = this.getters.getValidationRuleForCell(origin);
45809
+ const originZone = positionToZone(origin);
45731
45810
  if (!rule) {
45732
45811
  return;
45733
45812
  }
45734
- const xc = toXC(target.col, target.row);
45813
+ const zone = positionToZone(target);
45735
45814
  for (const range of rule.ranges) {
45736
45815
  if (isInside(origin.col, origin.row, range.zone)) {
45737
45816
  const toRemoveRange = [];
45738
45817
  if (this.operation === "CUT") {
45739
- toRemoveRange.push(toXC(origin.col, origin.row));
45818
+ toRemoveRange.push(originZone);
45740
45819
  }
45741
45820
  if (origin.sheetId === target.sheetId) {
45742
- this.adaptDataValidationRule(origin.sheetId, rule, [xc], toRemoveRange);
45821
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
45822
+ this.adaptDataValidationRule(origin.sheetId, copyToRule, [zone], toRemoveRange);
45743
45823
  }
45744
45824
  else {
45745
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveRange);
45746
- let copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
45747
- this.adaptDataValidationRule(target.sheetId, copyToRule, [xc], []);
45825
+ const originRule = this.getters.getValidationRuleForCell(origin);
45826
+ if (originRule) {
45827
+ this.adaptDataValidationRule(origin.sheetId, originRule, [], toRemoveRange);
45828
+ }
45829
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
45830
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
45748
45831
  }
45749
45832
  }
45750
45833
  }
45751
45834
  }
45752
45835
  getDataValidationRuleToCopyTo(targetSheetId, originRule) {
45753
- const ruleInTargetSheet = this.getters
45836
+ let targetRule = this.getters
45754
45837
  .getDataValidationRules(targetSheetId)
45755
45838
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
45756
45839
  originRule.isBlocking === rule.isBlocking);
45757
- return ruleInTargetSheet
45758
- ? ruleInTargetSheet
45759
- : { ...originRule, id: this.uuidGenerator.smallUuid(), ranges: [] };
45840
+ const queuedRules = this.queuedDVChanges[targetSheetId];
45841
+ if (!targetRule && queuedRules) {
45842
+ targetRule = queuedRules.find((queued) => deepEquals(originRule.criterion, queued.rule.criterion) &&
45843
+ originRule.isBlocking === queued.rule.isBlocking)?.rule;
45844
+ }
45845
+ return targetRule || { ...originRule, id: this.uuidGenerator.smallUuid(), ranges: [] };
45760
45846
  }
45761
45847
  /**
45762
45848
  * Add or remove XCs to a given data validation rule.
45763
45849
  */
45764
45850
  adaptDataValidationRule(sheetId, rule, toAdd, toRemove) {
45765
- const dvRangesXcs = rule.ranges.map((range) => this.getters.getRangeString(range, sheetId));
45766
- const newRangesXC = recomputeZones([...dvRangesXcs, ...toAdd], toRemove);
45767
- if (newRangesXC.length === 0) {
45768
- this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: rule.id });
45769
- return;
45851
+ if (!this.queuedDVChanges[sheetId]) {
45852
+ this.queuedDVChanges[sheetId] = [];
45770
45853
  }
45771
- this.dispatch("ADD_DATA_VALIDATION_RULE", {
45772
- rule,
45773
- ranges: newRangesXC.map((xc) => this.getters.getRangeDataFromXc(sheetId, xc)),
45774
- sheetId,
45775
- });
45854
+ const queuedChange = this.queuedDVChanges[sheetId].find((queued) => queued.rule.id === rule.id);
45855
+ if (!queuedChange) {
45856
+ this.queuedDVChanges[sheetId].push({ toAdd, toRemove, rule: deepCopy(rule) });
45857
+ }
45858
+ else {
45859
+ queuedChange.toAdd.push(...toAdd);
45860
+ queuedChange.toRemove.push(...toRemove);
45861
+ }
45862
+ }
45863
+ executeQueuedChanges(pasteSheetTarget) {
45864
+ for (const sheetId in this.queuedCFChanges) {
45865
+ for (const { toAdd, toRemove, cf } of this.queuedCFChanges[sheetId]) {
45866
+ const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
45867
+ if (!newRangesXc) {
45868
+ continue;
45869
+ }
45870
+ if (newRangesXc.length === 0) {
45871
+ this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
45872
+ continue;
45873
+ }
45874
+ this.dispatch("ADD_CONDITIONAL_FORMAT", {
45875
+ cf: {
45876
+ id: cf.id,
45877
+ rule: cf.rule,
45878
+ stopIfTrue: cf.stopIfTrue,
45879
+ },
45880
+ ranges: newRangesXc.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
45881
+ sheetId,
45882
+ });
45883
+ }
45884
+ }
45885
+ for (const sheetId in this.queuedDVChanges) {
45886
+ for (const { toAdd, toRemove, rule: dv } of this.queuedDVChanges[sheetId]) {
45887
+ // Remove the zones first in case the same position is in toAdd and toRemove
45888
+ const dvZones = dv.ranges.map((range) => range.zone);
45889
+ const withRemovedZones = futureRecomputeZones(dvZones, toRemove);
45890
+ const newDvZones = futureRecomputeZones([...withRemovedZones, ...toAdd], []);
45891
+ if (newDvZones.length === 0) {
45892
+ this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: dv.id });
45893
+ continue;
45894
+ }
45895
+ this.dispatch("ADD_DATA_VALIDATION_RULE", {
45896
+ rule: { id: dv.id, criterion: dv.criterion, isBlocking: dv.isBlocking },
45897
+ ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
45898
+ sheetId,
45899
+ });
45900
+ }
45901
+ }
45902
+ for (const borderKey in this.queuedBordersToAdd) {
45903
+ const zones = this.queuedBordersToAdd[borderKey];
45904
+ const border = JSON.parse(borderKey);
45905
+ const target = futureRecomputeZones(zones, []);
45906
+ this.dispatch("SET_BORDERS_ON_TARGET", { sheetId: pasteSheetTarget, target, border });
45907
+ }
45908
+ this.queuedBordersToAdd = {};
45776
45909
  }
45777
45910
  }
45778
45911
 
@@ -58120,9 +58253,9 @@
58120
58253
  exports.tokenize = tokenize;
58121
58254
 
58122
58255
 
58123
- __info__.version = "17.0.56";
58124
- __info__.date = "2025-04-04T08:36:58.258Z";
58125
- __info__.hash = "c484787";
58256
+ __info__.version = "17.0.58";
58257
+ __info__.date = "2025-04-18T16:20:26.099Z";
58258
+ __info__.hash = "88d92de";
58126
58259
 
58127
58260
 
58128
58261
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);