@odoo/o-spreadsheet 17.2.3 → 17.2.5

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.3
7
- * @date 2024-04-10T12:28:36.552Z
8
- * @hash ab5e22d
6
+ * @version 17.2.5
7
+ * @date 2024-04-26T07:41:13.193Z
8
+ * @hash a730f5c
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -2508,6 +2508,15 @@
2508
2508
  }
2509
2509
  return generateMatrix(matrix.length, matrix[0].length, (col, row) => fn(matrix[col][row]));
2510
2510
  }
2511
+ function matrixForEach(matrix, fn) {
2512
+ const numberOfCols = matrix.length;
2513
+ const numberOfRows = matrix[0]?.length ?? 0;
2514
+ for (let col = 0; col < numberOfCols; col++) {
2515
+ for (let row = 0; row < numberOfRows; row++) {
2516
+ fn(matrix[col][row]);
2517
+ }
2518
+ }
2519
+ }
2511
2520
  function transposeMatrix(matrix) {
2512
2521
  if (!matrix.length) {
2513
2522
  return [];
@@ -18635,7 +18644,7 @@ stores.inject(MyMetaStore, storeInstance);
18635
18644
  }
18636
18645
  const descr = addMetaInfoFromArg(addDescr);
18637
18646
  validateArguments(descr.args);
18638
- this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr)), name);
18647
+ this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr), name), name);
18639
18648
  super.add(name, descr);
18640
18649
  return this;
18641
18650
  }
@@ -18674,9 +18683,7 @@ stores.inject(MyMetaStore, storeInstance);
18674
18683
  // so we fallback to a generic error
18675
18684
  if (hasStringValue(e) && isEvaluationError(e.value)) {
18676
18685
  if (hasStringMessage(e)) {
18677
- if (e.message?.includes("[[FUNCTION_NAME]]")) {
18678
- e.message = e.message.replace("[[FUNCTION_NAME]]", functionName);
18679
- }
18686
+ replaceFunctionNamePlaceholder(e, functionName);
18680
18687
  }
18681
18688
  return e;
18682
18689
  }
@@ -18691,21 +18698,29 @@ stores.inject(MyMetaStore, storeInstance);
18691
18698
  return (obj?.message !== undefined &&
18692
18699
  typeof obj.message === "string");
18693
18700
  }
18694
- function addResultHandling(compute) {
18695
- return function (...args) {
18701
+ function addResultHandling(compute, functionName) {
18702
+ return function computeWithResultHandling(...args) {
18696
18703
  const result = compute.apply(this, args);
18697
18704
  if (!isMatrix(result)) {
18698
18705
  if (typeof result === "object" && result !== null && "value" in result) {
18706
+ replaceFunctionNamePlaceholder(result, functionName);
18699
18707
  return result;
18700
18708
  }
18701
18709
  return { value: result };
18702
18710
  }
18703
18711
  if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
18712
+ matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
18704
18713
  return result;
18705
18714
  }
18706
18715
  return matrixMap(result, (row) => ({ value: row }));
18707
18716
  };
18708
18717
  }
18718
+ function replaceFunctionNamePlaceholder(fPayload, functionName) {
18719
+ // for performance reasons: change in place and only if needed
18720
+ if (fPayload.message?.includes("[[FUNCTION_NAME]]")) {
18721
+ fPayload.message = fPayload.message.replace("[[FUNCTION_NAME]]", functionName);
18722
+ }
18723
+ }
18709
18724
  const functionRegistry = new FunctionRegistry();
18710
18725
  for (let category of categories) {
18711
18726
  const fns = category.functions;
@@ -19473,7 +19488,7 @@ stores.inject(MyMetaStore, storeInstance);
19473
19488
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
19474
19489
  // tooltipItem.parsed.y can be an object or a number for pie charts
19475
19490
  const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
19476
- const toolTipFormat = !format && yLabel > 1000 ? "#,##" : format;
19491
+ const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
19477
19492
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
19478
19493
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
19479
19494
  },
@@ -19773,7 +19788,10 @@ stores.inject(MyMetaStore, storeInstance);
19773
19788
  if (isNaN(value))
19774
19789
  return value;
19775
19790
  const { locale, format } = localeFormat;
19776
- return formatValue(value, { locale, format: !format && value > 1000 ? "#,##" : format });
19791
+ return formatValue(value, {
19792
+ locale,
19793
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
19794
+ });
19777
19795
  },
19778
19796
  },
19779
19797
  },
@@ -20294,7 +20312,10 @@ stores.inject(MyMetaStore, storeInstance);
20294
20312
  if (isNaN(value))
20295
20313
  return value;
20296
20314
  const { locale, format } = localeFormat;
20297
- return formatValue(value, { locale, format: !format && value > 1000 ? "#,##" : format });
20315
+ return formatValue(value, {
20316
+ locale,
20317
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
20318
+ });
20298
20319
  },
20299
20320
  },
20300
20321
  },
@@ -20634,7 +20655,7 @@ stores.inject(MyMetaStore, storeInstance);
20634
20655
  const percentage = calculatePercentage(data, dataIndex);
20635
20656
  const xLabel = tooltipItem.label || tooltipItem.dataset.label;
20636
20657
  const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
20637
- const toolTipFormat = !format && yLabel > 1000 ? "#,##" : format;
20658
+ const toolTipFormat = !format && yLabel >= 1000 ? "#,##" : format;
20638
20659
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
20639
20660
  return xLabel ? `${xLabel}: ${yLabelStr} (${percentage}%)` : `${yLabelStr} (${percentage}%)`;
20640
20661
  };
@@ -22886,6 +22907,7 @@ stores.inject(MyMetaStore, storeInstance);
22886
22907
  this.save();
22887
22908
  }
22888
22909
  ev.stopPropagation();
22910
+ ev.preventDefault();
22889
22911
  break;
22890
22912
  case "Escape":
22891
22913
  this.cancel();
@@ -32050,17 +32072,13 @@ stores.inject(MyMetaStore, storeInstance);
32050
32072
  this.DOMFocusableElementStore.setFocusableElement(el);
32051
32073
  }
32052
32074
  this.contentHelper.updateEl(el);
32053
- this.processTokenAtCursor();
32054
32075
  });
32055
32076
  owl.useEffect(() => {
32056
32077
  this.processContent();
32057
32078
  });
32058
- owl.onPatched(() => {
32059
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
32060
- if (this.composerStore.editionMode === "inactive") {
32061
- this.processTokenAtCursor();
32062
- }
32063
- });
32079
+ owl.useEffect(() => {
32080
+ this.processTokenAtCursor();
32081
+ }, () => [this.composerStore.editionMode !== "inactive"]);
32064
32082
  }
32065
32083
  // ---------------------------------------------------------------------------
32066
32084
  // Handlers
@@ -33162,6 +33180,10 @@ stores.inject(MyMetaStore, storeInstance);
33162
33180
  height: 0px;
33163
33181
  }
33164
33182
  }
33183
+ .o-figure-container {
33184
+ -webkit-user-select: none; // safari
33185
+ user-select: none;
33186
+ }
33165
33187
  `;
33166
33188
  /**
33167
33189
  * Each figure ⭐ is positioned inside a container `div` placed and sized
@@ -40353,7 +40375,7 @@ stores.inject(MyMetaStore, storeInstance);
40353
40375
  this.clearBorders(cmd.sheetId, cmd.target);
40354
40376
  break;
40355
40377
  case "REMOVE_COLUMNS_ROWS":
40356
- for (let el of cmd.elements) {
40378
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
40357
40379
  if (cmd.dimension === "COL") {
40358
40380
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
40359
40381
  }
@@ -45892,6 +45914,353 @@ stores.inject(MyMetaStore, storeInstance);
45892
45914
  }
45893
45915
  }
45894
45916
 
45917
+ /**
45918
+ * ####################################################
45919
+ * # INTRODUCTION
45920
+ * ####################################################
45921
+ *
45922
+ * This file contain the function recomputeZones.
45923
+ * This function try to recompute in a performant way
45924
+ * an ensemble of zones possibly overlapping to avoid
45925
+ * overlapping and to reduce the number of zones.
45926
+ *
45927
+ * It also allows to remove some zones from the ensemble.
45928
+ *
45929
+ * In the following example, 2 zones are overlapping.
45930
+ * Applying recomputeZones will return zones without
45931
+ * overlapping:
45932
+ *
45933
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
45934
+ *
45935
+ * A B C D E A B C D E
45936
+ * 1 ___ 1 ___
45937
+ * 2 ___|_ | 2 ___| | |
45938
+ * 3 | |_|_| ---> 3 | | |_|
45939
+ * 4 |_____| 4 |___|_|
45940
+ * 6 6
45941
+ * 7 7
45942
+ *
45943
+ *
45944
+ * In the following example, 2 zones are contiguous.
45945
+ * Applying recomputeZones will return only one zone:
45946
+ *
45947
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
45948
+ *
45949
+ * A B C D E A B C D E
45950
+ * 1 _ ___ 1 _____
45951
+ * 2 | | | ---> 2 | |
45952
+ * 3 |_|___| 3 |_____|
45953
+ * 4 4
45954
+ *
45955
+ *
45956
+ * In the following example, we want to remove a zone
45957
+ * from the ensemble. Applying recomputeZones will
45958
+ * return the ensemble without the zone to remove:
45959
+ *
45960
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
45961
+ * "C4:D4", "E2:E4"]
45962
+ *
45963
+ * A B C D E F A B C D E F
45964
+ * 1 _______ 1 _______
45965
+ * 2 | | ---> 2 | |___| |
45966
+ * 3 | xxx | 3 | |___| |
45967
+ * 4 |_______| 4 |_|___|_|
45968
+ * 5 5
45969
+ *
45970
+ *
45971
+ * The exercise seems simple when we have only 2 zones.
45972
+ * But with n zones and in a performant way, we want to
45973
+ * avoid comparing each zone with all the others.
45974
+ *
45975
+ *
45976
+ * ####################################################
45977
+ * # Methodological approach
45978
+ * ####################################################
45979
+ *
45980
+ * The methodological approach to avoid comparing each
45981
+ * zone with all the others is to use a data structure
45982
+ * that allow to quickly find which zones are
45983
+ * overlapping with any other given zone.
45984
+ *
45985
+ * Here the idea is to profile the zones at the columns level.
45986
+ *
45987
+ * To do that, we propose to use a data structure
45988
+ * composed of 2 parts:
45989
+ * - profilesStartingPosition: a sorted number array
45990
+ * indicating on which columns a new profile begins.
45991
+ * - profiles: a map where the key is a column
45992
+ * position (from profilesStartingPosition) and the
45993
+ * value is a sorted number array representing a
45994
+ * profile.
45995
+ *
45996
+ *
45997
+ * See the following example: here profileStartingPosition
45998
+ * corresponds to [A,C,E,G,K]
45999
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
46000
+ * 1 ' ' ' '
46001
+ * 2 ' ' '_______' here profile correspond
46002
+ * 3 '___' |_______| for A to []
46003
+ * 4 | | for C to [3, 5]
46004
+ * 5 |___| for E to []
46005
+ * 6 for G to [2, 3]
46006
+ * 7 for K to []
46007
+ *
46008
+ *
46009
+ * Now we can easily find which zones are overlapping
46010
+ * with a given zone. Suppose we want to add a new zone
46011
+ * D5:H6 to the ensemble:
46012
+ *
46013
+ * With a binary search of left and right
46014
+ * A B C D E F G H I J K on profilesStartingPosition, we can
46015
+ * 1 ' ' ' ' find the indexes of the profiles on which
46016
+ * 2 ' ' '_______' to apply a modification.
46017
+ * 3 '___' |_______|
46018
+ * 4 | _|_______ Here we will:
46019
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
46020
+ * 6 |_________| - modify the profile in E --> become [4, 6]
46021
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
46022
+ * - add a new profile in I --> become [8, 10]
46023
+ *
46024
+ * See below the result:
46025
+ *
46026
+ * Note the particularity of the profile
46027
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
46028
+ * 1 ' ' ' ' ' '
46029
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
46030
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
46031
+ * 4 | | |___ ___ search of the top and bottom value on the
46032
+ * 5 |_| | | | profile array. Depending on the result index
46033
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
46034
+ * 7 go by pairs, we know if we are in a zone or
46035
+ * not and how operate.
46036
+ */
46037
+ /**
46038
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
46039
+ * This compute is particularly useful because after this function:
46040
+ * - you will find coordinate of a cell only once among all the zones
46041
+ * - the number of zones will be reduced to the minimum
46042
+ */
46043
+ function futureRecomputeZones(zones, zonesToRemove = []) {
46044
+ const profilesStartingPosition = [0];
46045
+ const profiles = new Map([[0, []]]);
46046
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
46047
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
46048
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
46049
+ }
46050
+ function modifyProfiles(// export for testing only
46051
+ profilesStartingPosition, profiles, zones, toRemove = false) {
46052
+ for (const zone of zones) {
46053
+ const leftValue = zone.left;
46054
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
46055
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
46056
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
46057
+ for (let i = leftIndex; i <= rightIndex; i++) {
46058
+ const profile = profiles.get(profilesStartingPosition[i]);
46059
+ modifyProfile(profile, zone, toRemove);
46060
+ }
46061
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
46062
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
46063
+ }
46064
+ }
46065
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
46066
+ if (value === undefined) {
46067
+ // this is only the case when the value correspond to a bottom value that could be undefined
46068
+ return profilesStartingPosition.length - 1;
46069
+ }
46070
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
46071
+ if (value != profilesStartingPosition[predecessorIndex]) {
46072
+ // mean that the value is not ending/starting at the same position as the previous/next profile
46073
+ // --> it's a new profile
46074
+ // --> we need to add it
46075
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
46076
+ // suppose the we want to add the for the left value
46077
+ // following profile following zone: 'C', the predecessor index
46078
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
46079
+ // The next line code will
46080
+ // A B C D A B C D copy the profile of 'B'
46081
+ // 1 '___' 1 '___' to 'C'. In the rest of the
46082
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
46083
+ // 3 |___| 3 |_|_| | function will adapt the waiting
46084
+ // 4 4 |___| 'C' profile [1, 3] to the
46085
+ // correct 'C' profile [1, 4]
46086
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
46087
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
46088
+ }
46089
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
46090
+ }
46091
+ /**
46092
+ * Suppose the following Suppose we want to add We want to have the
46093
+ * profile: the following zone: following profile:
46094
+ *
46095
+ * A B C D E F A B C D E F A B C D E F
46096
+ * 1 '___' 1 ' ' 1 '___'
46097
+ * 2 |___| 2 '___' 2 | |
46098
+ * 3 ' ' 3 | | 3 | |
46099
+ * 4 '___' --> 4 | | --> 4 | |
46100
+ * 6 | | 6 |___| 6 | |
46101
+ * 7 |___| 7 7 |___|
46102
+ * 8 8 8
46103
+ *
46104
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
46105
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
46106
+ * ____ ____ correspond to 6
46107
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
46108
+ * ____ is to know what must be deleted
46109
+ * Note that the 'filled [3, 7] and what must be added to the
46110
+ * zone' are always between existing profile.
46111
+ * an even index and its
46112
+ * next index
46113
+ *
46114
+ */
46115
+ function modifyProfile(profile, zone, toRemove = false) {
46116
+ const topValue = zone.top;
46117
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
46118
+ const newPoints = [];
46119
+ // Case we want to add a zone to the profile:
46120
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
46121
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
46122
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
46123
+ // so we can keep the index of the top position
46124
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
46125
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
46126
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
46127
+ newPoints.push(topValue);
46128
+ }
46129
+ if (bottomValue === undefined) {
46130
+ // The following two code lines will not impact the final result,
46131
+ // but they will impact the intermediate profile.
46132
+ // We keep them for performance reason
46133
+ profile.splice(topPredIndex + 1);
46134
+ profile.push(...newPoints);
46135
+ return;
46136
+ }
46137
+ // Case we want to add a zone to the profile:
46138
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
46139
+ // so we can keep the index of the bottom position.
46140
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
46141
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
46142
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
46143
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
46144
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
46145
+ newPoints.push(bottomValue);
46146
+ }
46147
+ // add the top and bottom value to the profile and
46148
+ // remove all information between the top and bottom index
46149
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
46150
+ }
46151
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
46152
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
46153
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
46154
+ for (let i = end; i > start; i--) {
46155
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
46156
+ profiles.delete(profilesStartingPosition[i]);
46157
+ profilesStartingPosition.splice(i, 1);
46158
+ }
46159
+ }
46160
+ }
46161
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
46162
+ const mergedZone = [];
46163
+ let pendingZones = [];
46164
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
46165
+ const left = profilesStartingPosition[colIndex];
46166
+ const profile = profiles.get(left);
46167
+ if (!profile || profile.length === 0) {
46168
+ mergedZone.push(...pendingZones);
46169
+ pendingZones = [];
46170
+ continue;
46171
+ }
46172
+ let right = profilesStartingPosition[colIndex + 1];
46173
+ if (right !== undefined) {
46174
+ right--;
46175
+ }
46176
+ const nextPendingZones = [];
46177
+ for (let i = 0; i < profile.length; i += 2) {
46178
+ const top = profile[i];
46179
+ let bottom = profile[i + 1];
46180
+ if (bottom !== undefined) {
46181
+ bottom--;
46182
+ }
46183
+ const profileZone = {
46184
+ top,
46185
+ left,
46186
+ bottom,
46187
+ right,
46188
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
46189
+ };
46190
+ let findCorrespondingZone = false;
46191
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
46192
+ const pendingZone = pendingZones[j];
46193
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
46194
+ pendingZone.right = profileZone.right;
46195
+ pendingZones.splice(j, 1);
46196
+ nextPendingZones.push(pendingZone);
46197
+ findCorrespondingZone = true;
46198
+ break;
46199
+ }
46200
+ }
46201
+ if (!findCorrespondingZone) {
46202
+ nextPendingZones.push(profileZone);
46203
+ }
46204
+ }
46205
+ mergedZone.push(...pendingZones);
46206
+ pendingZones = nextPendingZones;
46207
+ }
46208
+ mergedZone.push(...pendingZones);
46209
+ return mergedZone;
46210
+ }
46211
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
46212
+ let end = arr.length - 1;
46213
+ let result = -1;
46214
+ while (start <= end) {
46215
+ const mid = Math.floor((start + end) / 2);
46216
+ if (arr[mid] === val && matchEqual) {
46217
+ return mid;
46218
+ }
46219
+ else if (arr[mid] < val) {
46220
+ result = mid;
46221
+ start = mid + 1;
46222
+ }
46223
+ else {
46224
+ end = mid - 1;
46225
+ }
46226
+ }
46227
+ return result;
46228
+ }
46229
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
46230
+ let end = arr.length - 1;
46231
+ let result = arr.length;
46232
+ while (start <= end) {
46233
+ const mid = Math.floor((start + end) / 2);
46234
+ if (arr[mid] === val && matchEqual) {
46235
+ return mid;
46236
+ }
46237
+ else if (arr[mid] > val) {
46238
+ result = mid;
46239
+ end = mid - 1;
46240
+ }
46241
+ else {
46242
+ start = mid + 1;
46243
+ }
46244
+ }
46245
+ return result;
46246
+ }
46247
+ /**
46248
+ * Compares two arrays.
46249
+ * For performance reasons, this function is to be preferred
46250
+ * to 'deepEquals' in the case we know that the inputs are arrays.
46251
+ */
46252
+ function deepEqualsArray(arr1, arr2) {
46253
+ if (arr1.length !== arr2.length) {
46254
+ return false;
46255
+ }
46256
+ for (let i = 0; i < arr1.length; i++) {
46257
+ if (!deepEquals(arr1[i], arr2[i])) {
46258
+ return false;
46259
+ }
46260
+ }
46261
+ return true;
46262
+ }
46263
+
45895
46264
  class PositionMap {
45896
46265
  map = {};
45897
46266
  set({ sheetId, col, row }, value) {
@@ -46690,7 +47059,7 @@ stores.inject(MyMetaStore, storeInstance);
46690
47059
  }
46691
47060
  }
46692
47061
  /**
46693
- * Return the cell and all cells that depend on it,
47062
+ * Return all the cells that depend on the provided ranges,
46694
47063
  * in the correct order they should be evaluated.
46695
47064
  * This is called a topological ordering (excluding cycles)
46696
47065
  */
@@ -46701,11 +47070,19 @@ stores.inject(MyMetaStore, storeInstance);
46701
47070
  const range = queue.pop();
46702
47071
  visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
46703
47072
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
47073
+ const nextInQueue = {};
46704
47074
  for (const position of impactedPositions) {
46705
47075
  if (!visited.has(position)) {
46706
- queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
47076
+ if (!nextInQueue[position.sheetId]) {
47077
+ nextInQueue[position.sheetId] = [];
47078
+ }
47079
+ nextInQueue[position.sheetId].push(positionToZone(position));
46707
47080
  }
46708
47081
  }
47082
+ for (const sheetId in nextInQueue) {
47083
+ const zones = futureRecomputeZones(nextInQueue[sheetId]);
47084
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
47085
+ }
46709
47086
  }
46710
47087
  visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
46711
47088
  return visited;
@@ -47044,7 +47421,7 @@ stores.inject(MyMetaStore, storeInstance);
47044
47421
  }
47045
47422
  if (!content) {
47046
47423
  // The previous content could have blocked some array formulas
47047
- impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
47424
+ impactedPositions.addMany(this.getArrayFormulasBlockedBy(position));
47048
47425
  }
47049
47426
  }
47050
47427
  return impactedPositions;
@@ -47087,14 +47464,23 @@ stores.inject(MyMetaStore, storeInstance);
47087
47464
  positions.fillAllPositions();
47088
47465
  return positions;
47089
47466
  }
47090
- getArrayFormulasBlockedByOrSpreadingOn(position) {
47467
+ /**
47468
+ * Return the position of formulas blocked by the given position
47469
+ * as well as all their dependencies.
47470
+ */
47471
+ getArrayFormulasBlockedBy(position) {
47091
47472
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
47092
47473
  return [];
47093
47474
  }
47094
47475
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
47095
47476
  const positions = this.createEmptyPositionSet();
47096
47477
  positions.addMany(arrayFormulas);
47097
- positions.addMany(this.getCellsDependingOn(arrayFormulas));
47478
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
47479
+ if (arrayFormulaPosition) {
47480
+ // ignore the formula spreading on the position. Keep only the blocked ones
47481
+ positions.delete(arrayFormulaPosition);
47482
+ }
47483
+ positions.addMany(this.getCellsDependingOn(positions));
47098
47484
  return positions;
47099
47485
  }
47100
47486
  nextPositionsToUpdate = new PositionSet({});
@@ -47234,7 +47620,7 @@ stores.inject(MyMetaStore, storeInstance);
47234
47620
  }
47235
47621
  this.evaluatedCells.delete(child);
47236
47622
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
47237
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
47623
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
47238
47624
  }
47239
47625
  this.spreadingRelations.removeNode(position);
47240
47626
  }
@@ -52085,7 +52471,7 @@ stores.inject(MyMetaStore, storeInstance);
52085
52471
  "isFilterActive",
52086
52472
  ];
52087
52473
  filterValues = {};
52088
- hiddenRows = new Set();
52474
+ hiddenRows = {};
52089
52475
  isEvaluationDirty = false;
52090
52476
  allowDispatch(cmd) {
52091
52477
  switch (cmd.type) {
@@ -52129,11 +52515,11 @@ stores.inject(MyMetaStore, storeInstance);
52129
52515
  case "UNFOLD_HEADER_GROUP":
52130
52516
  case "FOLD_ALL_HEADER_GROUPS":
52131
52517
  case "UNFOLD_ALL_HEADER_GROUPS":
52132
- this.updateHiddenRows();
52518
+ this.updateHiddenRows(cmd.sheetId);
52133
52519
  break;
52134
52520
  case "UPDATE_FILTER":
52135
52521
  this.updateFilter(cmd);
52136
- this.updateHiddenRows();
52522
+ this.updateHiddenRows(cmd.sheetId);
52137
52523
  break;
52138
52524
  case "DUPLICATE_SHEET":
52139
52525
  const filterValues = {};
@@ -52153,15 +52539,14 @@ stores.inject(MyMetaStore, storeInstance);
52153
52539
  }
52154
52540
  finalize() {
52155
52541
  if (this.isEvaluationDirty) {
52156
- this.updateHiddenRows();
52542
+ for (const sheetId of this.getters.getSheetIds()) {
52543
+ this.updateHiddenRows(sheetId);
52544
+ }
52157
52545
  this.isEvaluationDirty = false;
52158
52546
  }
52159
52547
  }
52160
52548
  isRowFiltered(sheetId, row) {
52161
- if (sheetId !== this.getters.getActiveSheetId()) {
52162
- return false;
52163
- }
52164
- return this.hiddenRows.has(row);
52549
+ return !!this.hiddenRows[sheetId]?.has(row);
52165
52550
  }
52166
52551
  getFilterHiddenValues(position) {
52167
52552
  const id = this.getters.getFilterId(position);
@@ -52188,8 +52573,7 @@ stores.inject(MyMetaStore, storeInstance);
52188
52573
  this.filterValues[sheetId] = {};
52189
52574
  this.filterValues[sheetId][id] = hiddenValues;
52190
52575
  }
52191
- updateHiddenRows() {
52192
- const sheetId = this.getters.getActiveSheetId();
52576
+ updateHiddenRows(sheetId) {
52193
52577
  const filters = this.getters
52194
52578
  .getFilters(sheetId)
52195
52579
  .sort((filter1, filter2) => filter1.rangeWithHeaders.zone.top - filter2.rangeWithHeaders.zone.top);
@@ -52211,7 +52595,7 @@ stores.inject(MyMetaStore, storeInstance);
52211
52595
  }
52212
52596
  }
52213
52597
  }
52214
- this.hiddenRows = hiddenRows;
52598
+ this.hiddenRows[sheetId] = hiddenRows;
52215
52599
  }
52216
52600
  getCellValueAsString(sheetId, col, row) {
52217
52601
  const value = this.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue;
@@ -53330,13 +53714,14 @@ stores.inject(MyMetaStore, storeInstance);
53330
53714
  }
53331
53715
  }
53332
53716
  handleEvent(event) {
53717
+ const sheetId = this.getters.getActiveSheetId();
53333
53718
  if (event.options.scrollIntoView) {
53334
53719
  let { col, row } = findCellInNewZone(event.previousAnchor.zone, event.anchor.zone);
53335
53720
  if (event.mode === "updateAnchor") {
53336
53721
  const oldZone = event.previousAnchor.zone;
53337
53722
  const newZone = event.anchor.zone;
53338
53723
  // altering a zone should not move the viewport in a dimension that wasn't changed
53339
- const { top, bottom, left, right } = this.getters.getActiveMainViewport();
53724
+ const { top, bottom, left, right } = this.getMainInternalViewport(sheetId);
53340
53725
  if (oldZone.left === newZone.left && oldZone.right === newZone.right) {
53341
53726
  col = left > col || col > right ? left : col;
53342
53727
  }
@@ -53344,7 +53729,6 @@ stores.inject(MyMetaStore, storeInstance);
53344
53729
  row = top > row || row > bottom ? top : row;
53345
53730
  }
53346
53731
  }
53347
- const sheetId = this.getters.getActiveSheetId();
53348
53732
  col = Math.min(col, this.getters.getNumberCols(sheetId) - 1);
53349
53733
  row = Math.min(row, this.getters.getNumberRows(sheetId) - 1);
53350
53734
  if (!this.sheetsWithDirtyViewports.has(sheetId)) {
@@ -53381,16 +53765,16 @@ stores.inject(MyMetaStore, storeInstance);
53381
53765
  this.setSheetViewOffset(cmd.offsetX, cmd.offsetY);
53382
53766
  break;
53383
53767
  case "SHIFT_VIEWPORT_DOWN":
53384
- const { top } = this.getActiveMainViewport();
53385
53768
  const sheetId = this.getters.getActiveSheetId();
53386
- const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).start + this.sheetViewHeight);
53387
- this.shiftVertically(shiftedOffsetY);
53769
+ const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
53770
+ const topRowDims = this.getters.getRowDimensions(sheetId, top);
53771
+ this.shiftVertically(topRowDims.start + viewportHeight - offsetCorrectionY);
53388
53772
  break;
53389
53773
  case "SHIFT_VIEWPORT_UP": {
53390
- const { top } = this.getActiveMainViewport();
53391
53774
  const sheetId = this.getters.getActiveSheetId();
53392
- const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).end - this.sheetViewHeight);
53393
- this.shiftVertically(shiftedOffsetY);
53775
+ const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
53776
+ const topRowDims = this.getters.getRowDimensions(sheetId, top);
53777
+ this.shiftVertically(topRowDims.end - offsetCorrectionY - viewportHeight);
53394
53778
  break;
53395
53779
  }
53396
53780
  case "REMOVE_TABLE":
@@ -53813,17 +54197,6 @@ stores.inject(MyMetaStore, storeInstance);
53813
54197
  const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset();
53814
54198
  Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(clip(offsetX, 0, maxOffsetX), clip(offsetY, 0, maxOffsetY)));
53815
54199
  }
53816
- /**
53817
- * Clip the vertical offset within the allowed range.
53818
- * Not above the sheet, nor below the sheet.
53819
- */
53820
- clipOffsetY(offsetY) {
53821
- const { height } = this.getMainViewportRect();
53822
- const maxOffset = height - this.sheetViewHeight;
53823
- offsetY = Math.min(offsetY, maxOffset);
53824
- offsetY = Math.max(offsetY, 0);
53825
- return offsetY;
53826
- }
53827
54200
  getViewportOffset(sheetId) {
53828
54201
  return {
53829
54202
  x: this.viewports[sheetId]?.bottomRight.offsetScrollbarX || 0,
@@ -53879,12 +54252,15 @@ stores.inject(MyMetaStore, storeInstance);
53879
54252
  * viewport top.
53880
54253
  */
53881
54254
  shiftVertically(offset) {
53882
- const { top } = this.getActiveMainViewport();
54255
+ const sheetId = this.getters.getActiveSheetId();
54256
+ const { top } = this.getMainInternalViewport(sheetId);
53883
54257
  const { scrollX } = this.getActiveSheetScrollInfo();
53884
54258
  this.setSheetViewOffset(scrollX, offset);
53885
54259
  const { anchor } = this.getters.getSelection();
53886
- const deltaRow = this.getActiveMainViewport().top - top;
53887
- this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);
54260
+ if (anchor.cell.row >= this.getters.getPaneDivisions(sheetId).ySplit) {
54261
+ const deltaRow = this.getMainInternalViewport(sheetId).top - top;
54262
+ this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);
54263
+ }
53888
54264
  }
53889
54265
  getVisibleFigures() {
53890
54266
  const sheetId = this.getters.getActiveSheetId();
@@ -54803,7 +55179,6 @@ stores.inject(MyMetaStore, storeInstance);
54803
55179
  cursor: pointer;
54804
55180
  }
54805
55181
  `;
54806
- let tKey = 1;
54807
55182
  class SpreadsheetDashboard extends owl.Component {
54808
55183
  static template = "o-spreadsheet-SpreadsheetDashboard";
54809
55184
  static props = {};
@@ -54882,13 +55257,9 @@ stores.inject(MyMetaStore, storeInstance);
54882
55257
  coordinates: rect,
54883
55258
  position: { col, row },
54884
55259
  action,
54885
- // we can't rely on position only because a row or a column could
54886
- // be inserted at any time.
54887
- tKey: `${tKey}-${col}-${row}`,
54888
55260
  });
54889
55261
  }
54890
55262
  }
54891
- tKey++;
54892
55263
  return cells;
54893
55264
  }
54894
55265
  getClickableAction(position) {
@@ -60228,9 +60599,9 @@ stores.inject(MyMetaStore, storeInstance);
60228
60599
  exports.tokenize = tokenize;
60229
60600
 
60230
60601
 
60231
- __info__.version = "17.2.3";
60232
- __info__.date = "2024-04-10T12:28:36.552Z";
60233
- __info__.hash = "ab5e22d";
60602
+ __info__.version = "17.2.5";
60603
+ __info__.date = "2024-04-26T07:41:13.193Z";
60604
+ __info__.hash = "a730f5c";
60234
60605
 
60235
60606
 
60236
60607
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);