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