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