@odoo/o-spreadsheet 17.0.19 → 17.0.21

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.19
6
- * @date 2024-04-18T16:41:42.291Z
7
- * @hash 9f3317a
5
+ * @version 17.0.21
6
+ * @date 2024-05-07T10:39:36.750Z
7
+ * @hash 998bea0
8
8
  */
9
9
 
10
10
  'use strict';
@@ -642,7 +642,7 @@ function getAddHeaderStartIndex(position, base) {
642
642
  /**
643
643
  * Compares two objects.
644
644
  */
645
- function deepEquals(o1, o2) {
645
+ function deepEquals(o1, o2, ignoreFunctions) {
646
646
  if (o1 === o2)
647
647
  return true;
648
648
  if ((o1 && !o2) || (o2 && !o1))
@@ -658,13 +658,16 @@ function deepEquals(o1, o2) {
658
658
  }
659
659
  }
660
660
  for (const key in o1) {
661
- if (typeof o1[key] !== typeof o2[key])
661
+ const typeOfO1Key = typeof o1[key];
662
+ if (typeOfO1Key !== typeof o2[key])
662
663
  return false;
663
- if (typeof o1[key] === "object") {
664
- if (!deepEquals(o1[key], o2[key]))
664
+ if (typeOfO1Key === "object") {
665
+ if (!deepEquals(o1[key], o2[key], ignoreFunctions))
665
666
  return false;
666
667
  }
667
668
  else {
669
+ if (ignoreFunctions && typeOfO1Key === "function")
670
+ return true;
668
671
  if (o1[key] !== o2[key])
669
672
  return false;
670
673
  }
@@ -3559,21 +3562,22 @@ function toZoneWithoutBoundaryChanges(xc) {
3559
3562
  xc = xc.split("!").at(-1);
3560
3563
  }
3561
3564
  if (xc.includes("$")) {
3562
- xc = xc.replace(/\$/g, "");
3565
+ xc = xc.replaceAll("$", "");
3563
3566
  }
3564
- let ranges;
3567
+ let firstRangePart = "";
3568
+ let secondRangePart;
3565
3569
  if (xc.includes(":")) {
3566
- ranges = xc.split(":").map((x) => x.trim());
3570
+ [firstRangePart, secondRangePart] = xc.split(":");
3571
+ firstRangePart = firstRangePart.trim();
3572
+ secondRangePart = secondRangePart.trim();
3567
3573
  }
3568
3574
  else {
3569
- ranges = [xc.trim()];
3575
+ firstRangePart = xc.trim();
3570
3576
  }
3571
3577
  let top, bottom, left, right;
3572
3578
  let fullCol = false;
3573
3579
  let fullRow = false;
3574
3580
  let hasHeader = false;
3575
- const firstRangePart = ranges[0];
3576
- const secondRangePart = ranges[1] && ranges[1];
3577
3581
  if (isColReference(firstRangePart)) {
3578
3582
  left = right = lettersToNumber(firstRangePart);
3579
3583
  top = bottom = 0;
@@ -3590,7 +3594,7 @@ function toZoneWithoutBoundaryChanges(xc) {
3590
3594
  top = bottom = c.row;
3591
3595
  hasHeader = true;
3592
3596
  }
3593
- if (ranges.length === 2) {
3597
+ if (secondRangePart) {
3594
3598
  if (isColReference(secondRangePart)) {
3595
3599
  right = lettersToNumber(secondRangePart);
3596
3600
  fullCol = true;
@@ -4759,6 +4763,7 @@ class ChartJsComponent extends owl.Component {
4759
4763
  static template = "o-spreadsheet-ChartJsComponent";
4760
4764
  canvas = owl.useRef("graphContainer");
4761
4765
  chart;
4766
+ currentRuntime;
4762
4767
  get background() {
4763
4768
  return this.chartRuntime.background;
4764
4769
  }
@@ -4775,9 +4780,17 @@ class ChartJsComponent extends owl.Component {
4775
4780
  setup() {
4776
4781
  owl.onMounted(() => {
4777
4782
  const runtime = this.chartRuntime;
4778
- this.createChart(runtime.chartJsConfig);
4783
+ this.currentRuntime = runtime;
4784
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
4785
+ this.createChart(deepCopy(runtime.chartJsConfig));
4786
+ });
4787
+ owl.useEffect(() => {
4788
+ const runtime = this.chartRuntime;
4789
+ if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
4790
+ this.currentRuntime = runtime;
4791
+ this.updateChartJs(deepCopy(runtime));
4792
+ }
4779
4793
  });
4780
- owl.useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
4781
4794
  }
4782
4795
  createChart(chartData) {
4783
4796
  const canvas = this.canvas.el;
@@ -4805,8 +4818,7 @@ class ChartJsComponent extends owl.Component {
4805
4818
  this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
4806
4819
  this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
4807
4820
  this.chart.config.options.scales = chartData.options?.scales;
4808
- // ?
4809
- this.chart.update("active");
4821
+ this.chart.update();
4810
4822
  }
4811
4823
  }
4812
4824
  ChartJsComponent.props = {
@@ -8375,7 +8387,7 @@ function truncateLabel(label) {
8375
8387
  /**
8376
8388
  * Get a default chart js configuration
8377
8389
  */
8378
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
8390
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
8379
8391
  const options = {
8380
8392
  // https://www.chartjs.org/docs/latest/general/responsive.html
8381
8393
  responsive: true,
@@ -8422,7 +8434,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
8422
8434
  type: chart.type,
8423
8435
  options,
8424
8436
  data: {
8425
- labels: labels.map(truncateLabel),
8437
+ labels: truncateLabels ? labels.map(truncateLabel) : labels,
8426
8438
  datasets: [],
8427
8439
  },
8428
8440
  plugins: [],
@@ -9304,9 +9316,9 @@ function canBeLinearChart(labelRange, getters) {
9304
9316
  }
9305
9317
  return true;
9306
9318
  }
9307
- function getLineConfiguration(chart, labels, localeFormat) {
9319
+ function getLineConfiguration(chart, labels, options) {
9308
9320
  const fontColor = chartFontColor(chart.background);
9309
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
9321
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
9310
9322
  const legend = {
9311
9323
  labels: {
9312
9324
  color: fontColor,
@@ -9349,7 +9361,7 @@ function getLineConfiguration(chart, labels, localeFormat) {
9349
9361
  value = Number(value);
9350
9362
  if (isNaN(value))
9351
9363
  return value;
9352
- const { locale, format } = localeFormat;
9364
+ const { locale, format } = options;
9353
9365
  return formatValue(value, {
9354
9366
  locale,
9355
9367
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
@@ -9382,9 +9394,10 @@ function createLineChartRuntime(chart, getters) {
9382
9394
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
9383
9395
  }
9384
9396
  const locale = getters.getLocale();
9397
+ const truncateLabels = axisType === "category";
9385
9398
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
9386
- const localeFormat = { format: dataSetFormat, locale };
9387
- const config = getLineConfiguration(chart, labels, localeFormat);
9399
+ const options = { format: dataSetFormat, locale, truncateLabels };
9400
+ const config = getLineConfiguration(chart, labels, options);
9388
9401
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
9389
9402
  if (axisType === "time") {
9390
9403
  const axis = {
@@ -26668,17 +26681,13 @@ class Composer extends owl.Component {
26668
26681
  this.env.focusableElement.setFocusableElement(el);
26669
26682
  }
26670
26683
  this.contentHelper.updateEl(el);
26671
- this.processTokenAtCursor();
26672
26684
  });
26673
26685
  owl.useEffect(() => {
26674
26686
  this.processContent();
26675
26687
  });
26676
- owl.onPatched(() => {
26677
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
26678
- if (this.env.model.getters.getEditionMode() === "inactive") {
26679
- this.processTokenAtCursor();
26680
- }
26681
- });
26688
+ owl.useEffect(() => {
26689
+ this.processTokenAtCursor();
26690
+ }, () => [this.env.model.getters.getEditionMode() !== "inactive"]);
26682
26691
  }
26683
26692
  // ---------------------------------------------------------------------------
26684
26693
  // Handlers
@@ -27109,6 +27118,7 @@ class Composer extends owl.Component {
27109
27118
  const dataValidationAutocompleteValues = this.env.model.getters.getAutoCompleteDataValidationValues();
27110
27119
  if (!content.startsWith("=") && dataValidationAutocompleteValues.length) {
27111
27120
  this.showDataValidationAutocomplete(dataValidationAutocompleteValues);
27121
+ return;
27112
27122
  }
27113
27123
  if (content.startsWith("=")) {
27114
27124
  const token = this.env.model.getters.getTokenAtCursor();
@@ -27547,7 +27557,8 @@ class DataValidationOverlay extends owl.Component {
27547
27557
  get checkBoxCellPositions() {
27548
27558
  return this.env.model.getters
27549
27559
  .getVisibleCellPositions()
27550
- .filter(this.env.model.getters.isCellValidCheckbox);
27560
+ .filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
27561
+ !this.env.model.getters.isFilterHeader(position));
27551
27562
  }
27552
27563
  get listIconsCellPositions() {
27553
27564
  if (this.env.model.getters.isReadonly()) {
@@ -27555,7 +27566,8 @@ class DataValidationOverlay extends owl.Component {
27555
27566
  }
27556
27567
  return this.env.model.getters
27557
27568
  .getVisibleCellPositions()
27558
- .filter(this.env.model.getters.cellHasListDataValidationIcon);
27569
+ .filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
27570
+ !this.env.model.getters.isFilterHeader(position));
27559
27571
  }
27560
27572
  }
27561
27573
  DataValidationOverlay.props = {};
@@ -34368,7 +34380,7 @@ class BordersPlugin extends CorePlugin {
34368
34380
  this.clearBorders(cmd.sheetId, cmd.target);
34369
34381
  break;
34370
34382
  case "REMOVE_COLUMNS_ROWS":
34371
- for (let el of cmd.elements) {
34383
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
34372
34384
  if (cmd.dimension === "COL") {
34373
34385
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
34374
34386
  }
@@ -40148,6 +40160,353 @@ class CompilationParametersBuilder {
40148
40160
  }
40149
40161
  }
40150
40162
 
40163
+ /**
40164
+ * ####################################################
40165
+ * # INTRODUCTION
40166
+ * ####################################################
40167
+ *
40168
+ * This file contain the function recomputeZones.
40169
+ * This function try to recompute in a performant way
40170
+ * an ensemble of zones possibly overlapping to avoid
40171
+ * overlapping and to reduce the number of zones.
40172
+ *
40173
+ * It also allows to remove some zones from the ensemble.
40174
+ *
40175
+ * In the following example, 2 zones are overlapping.
40176
+ * Applying recomputeZones will return zones without
40177
+ * overlapping:
40178
+ *
40179
+ * ["B3:D4", "D2:E3"] ["B3:C4", "D2:D4", "E2:E3"]
40180
+ *
40181
+ * A B C D E A B C D E
40182
+ * 1 ___ 1 ___
40183
+ * 2 ___|_ | 2 ___| | |
40184
+ * 3 | |_|_| ---> 3 | | |_|
40185
+ * 4 |_____| 4 |___|_|
40186
+ * 6 6
40187
+ * 7 7
40188
+ *
40189
+ *
40190
+ * In the following example, 2 zones are contiguous.
40191
+ * Applying recomputeZones will return only one zone:
40192
+ *
40193
+ * ["B2:B3", "C2:D3"] ["B2:D3"]
40194
+ *
40195
+ * A B C D E A B C D E
40196
+ * 1 _ ___ 1 _____
40197
+ * 2 | | | ---> 2 | |
40198
+ * 3 |_|___| 3 |_____|
40199
+ * 4 4
40200
+ *
40201
+ *
40202
+ * In the following example, we want to remove a zone
40203
+ * from the ensemble. Applying recomputeZones will
40204
+ * return the ensemble without the zone to remove:
40205
+ *
40206
+ * remove ["C3:D3"] ["B2:B4", "C2:D2",
40207
+ * "C4:D4", "E2:E4"]
40208
+ *
40209
+ * A B C D E F A B C D E F
40210
+ * 1 _______ 1 _______
40211
+ * 2 | | ---> 2 | |___| |
40212
+ * 3 | xxx | 3 | |___| |
40213
+ * 4 |_______| 4 |_|___|_|
40214
+ * 5 5
40215
+ *
40216
+ *
40217
+ * The exercise seems simple when we have only 2 zones.
40218
+ * But with n zones and in a performant way, we want to
40219
+ * avoid comparing each zone with all the others.
40220
+ *
40221
+ *
40222
+ * ####################################################
40223
+ * # Methodological approach
40224
+ * ####################################################
40225
+ *
40226
+ * The methodological approach to avoid comparing each
40227
+ * zone with all the others is to use a data structure
40228
+ * that allow to quickly find which zones are
40229
+ * overlapping with any other given zone.
40230
+ *
40231
+ * Here the idea is to profile the zones at the columns level.
40232
+ *
40233
+ * To do that, we propose to use a data structure
40234
+ * composed of 2 parts:
40235
+ * - profilesStartingPosition: a sorted number array
40236
+ * indicating on which columns a new profile begins.
40237
+ * - profiles: a map where the key is a column
40238
+ * position (from profilesStartingPosition) and the
40239
+ * value is a sorted number array representing a
40240
+ * profile.
40241
+ *
40242
+ *
40243
+ * See the following example: here profileStartingPosition
40244
+ * corresponds to [A,C,E,G,K]
40245
+ * A B C D E F G H I J K so with number [0,2,4,6,10]
40246
+ * 1 ' ' ' '
40247
+ * 2 ' ' '_______' here profile correspond
40248
+ * 3 '___' |_______| for A to []
40249
+ * 4 | | for C to [3, 5]
40250
+ * 5 |___| for E to []
40251
+ * 6 for G to [2, 3]
40252
+ * 7 for K to []
40253
+ *
40254
+ *
40255
+ * Now we can easily find which zones are overlapping
40256
+ * with a given zone. Suppose we want to add a new zone
40257
+ * D5:H6 to the ensemble:
40258
+ *
40259
+ * With a binary search of left and right
40260
+ * A B C D E F G H I J K on profilesStartingPosition, we can
40261
+ * 1 ' ' ' ' find the indexes of the profiles on which
40262
+ * 2 ' ' '_______' to apply a modification.
40263
+ * 3 '___' |_______|
40264
+ * 4 | _|_______ Here we will:
40265
+ * 5 |_|_| | - add a new profile in D --> become [3, 6]
40266
+ * 6 |_________| - modify the profile in E --> become [4, 6]
40267
+ * 7 - modify the profile in G --> become [2, 3, 4, 6]
40268
+ * - add a new profile in I --> become [8, 10]
40269
+ *
40270
+ * See below the result:
40271
+ *
40272
+ * Note the particularity of the profile
40273
+ * A B C D E F G H I J K for G: it will correspond to [2, 3, 4, 6]
40274
+ * 1 ' ' ' ' ' '
40275
+ * 2 ' ' ' '___'___' To know how to modify the profile (add a
40276
+ * 3 '_'_' |___|___| zone or remove it) we do a binary
40277
+ * 4 | | |___ ___ search of the top and bottom value on the
40278
+ * 5 |_| | | | profile array. Depending on the result index
40279
+ * 6 |_|___|___| parity (odd or even), because zone boundaries
40280
+ * 7 go by pairs, we know if we are in a zone or
40281
+ * not and how operate.
40282
+ */
40283
+ /**
40284
+ * Recompute the zone without the cells in toRemoveZones and avoid overlapping.
40285
+ * This compute is particularly useful because after this function:
40286
+ * - you will find coordinate of a cell only once among all the zones
40287
+ * - the number of zones will be reduced to the minimum
40288
+ */
40289
+ function futureRecomputeZones(zones, zonesToRemove = []) {
40290
+ const profilesStartingPosition = [0];
40291
+ const profiles = new Map([[0, []]]);
40292
+ modifyProfiles(profilesStartingPosition, profiles, zones, false);
40293
+ modifyProfiles(profilesStartingPosition, profiles, zonesToRemove, true);
40294
+ return constructZonesFromProfiles(profilesStartingPosition, profiles);
40295
+ }
40296
+ function modifyProfiles(// export for testing only
40297
+ profilesStartingPosition, profiles, zones, toRemove = false) {
40298
+ for (const zone of zones) {
40299
+ const leftValue = zone.left;
40300
+ const rightValue = zone.right === undefined ? undefined : zone.right + 1;
40301
+ const leftIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, leftValue, true, 0);
40302
+ const rightIndex = findIndexAndCreateProfile(profilesStartingPosition, profiles, rightValue, false, leftIndex);
40303
+ for (let i = leftIndex; i <= rightIndex; i++) {
40304
+ const profile = profiles.get(profilesStartingPosition[i]);
40305
+ modifyProfile(profile, zone, toRemove);
40306
+ }
40307
+ // maybe this part cost in performance, and maybe it's not necessary (depending on the use case). To be checked
40308
+ removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex);
40309
+ }
40310
+ }
40311
+ function findIndexAndCreateProfile(profilesStartingPosition, profiles, value, searchLeft, startIndex) {
40312
+ if (value === undefined) {
40313
+ // this is only the case when the value correspond to a bottom value that could be undefined
40314
+ return profilesStartingPosition.length - 1;
40315
+ }
40316
+ const predecessorIndex = binaryPredecessorSearch(profilesStartingPosition, value, startIndex);
40317
+ if (value != profilesStartingPosition[predecessorIndex]) {
40318
+ // mean that the value is not ending/starting at the same position as the previous/next profile
40319
+ // --> it's a new profile
40320
+ // --> we need to add it
40321
+ profilesStartingPosition.splice(predecessorIndex + 1, 0, value);
40322
+ // suppose the we want to add the for the left value
40323
+ // following profile following zone: 'C', the predecessor index
40324
+ // for B: [1, 3] "C3:D4" correspond to 'B'.
40325
+ // The next line code will
40326
+ // A B C D A B C D copy the profile of 'B'
40327
+ // 1 '___' 1 '___' to 'C'. In the rest of the
40328
+ // 2 | | ---> 2 | _|_ process the 'modifyProfile'
40329
+ // 3 |___| 3 |_|_| | function will adapt the waiting
40330
+ // 4 4 |___| 'C' profile [1, 3] to the
40331
+ // correct 'C' profile [1, 4]
40332
+ profiles.set(value, [...profiles.get(profilesStartingPosition[predecessorIndex])]);
40333
+ return searchLeft ? predecessorIndex + 1 : predecessorIndex;
40334
+ }
40335
+ return searchLeft ? predecessorIndex : predecessorIndex - 1;
40336
+ }
40337
+ /**
40338
+ * Suppose the following Suppose we want to add We want to have the
40339
+ * profile: the following zone: following profile:
40340
+ *
40341
+ * A B C D E F A B C D E F A B C D E F
40342
+ * 1 '___' 1 ' ' 1 '___'
40343
+ * 2 |___| 2 '___' 2 | |
40344
+ * 3 ' ' 3 | | 3 | |
40345
+ * 4 '___' --> 4 | | --> 4 | |
40346
+ * 6 | | 6 |___| 6 | |
40347
+ * 7 |___| 7 7 |___|
40348
+ * 8 8 8
40349
+ *
40350
+ * the profile for 'C' the top zone correspond Here [2, 3, 5, 8] with [3, 7]
40351
+ * corresponds to: to 3 and the bottom zone would be merged into [2, 8]
40352
+ * ____ ____ correspond to 6
40353
+ * [2, 3, 5, 8] would be the profile: The difficulty of modify profile
40354
+ * ____ is to know what must be deleted
40355
+ * Note that the 'filled [3, 7] and what must be added to the
40356
+ * zone' are always between existing profile.
40357
+ * an even index and its
40358
+ * next index
40359
+ *
40360
+ */
40361
+ function modifyProfile(profile, zone, toRemove = false) {
40362
+ const topValue = zone.top;
40363
+ const bottomValue = zone.bottom === undefined ? undefined : zone.bottom + 1;
40364
+ const newPoints = [];
40365
+ // Case we want to add a zone to the profile:
40366
+ // - If the top predecessor index `topPredIndex` is even, it means the top of the zone is already positioned on a filled zone
40367
+ // so we don't need to add it to the profile. we can keep in reference the index of the predecessor.
40368
+ // - If it is odd, it means the top of the zone must be the beginning of a filled zone.
40369
+ // so we can keep the index of the top position
40370
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40371
+ const topPredIndex = binaryPredecessorSearch(profile, topValue, 0, false);
40372
+ if ((topPredIndex % 2 !== 0 && !toRemove) || (topPredIndex % 2 === 0 && toRemove)) {
40373
+ newPoints.push(topValue);
40374
+ }
40375
+ if (bottomValue === undefined) {
40376
+ // The following two code lines will not impact the final result,
40377
+ // but they will impact the intermediate profile.
40378
+ // We keep them for performance reason
40379
+ profile.splice(topPredIndex + 1);
40380
+ profile.push(...newPoints);
40381
+ return;
40382
+ }
40383
+ // Case we want to add a zone to the profile:
40384
+ // - If the bottom successor index `bottomSuccIndex` is even, it means the bottom of the zone must be the ending of a filled zone
40385
+ // so we can keep the index of the bottom position.
40386
+ // - If it is odd, it means the bottom of the zone is already positioned on a filled zone
40387
+ // so we don't need to add it to the profile. we can keep in reference the index of the successor
40388
+ // Case we want to remove a zone from the profile: it's the opposite of the previous case
40389
+ const bottomSuccIndex = binarySuccessorSearch(profile, bottomValue, 0, false);
40390
+ if ((bottomSuccIndex % 2 === 0 && !toRemove) || (bottomSuccIndex % 2 !== 0 && toRemove)) {
40391
+ newPoints.push(bottomValue);
40392
+ }
40393
+ // add the top and bottom value to the profile and
40394
+ // remove all information between the top and bottom index
40395
+ profile.splice(topPredIndex + 1, bottomSuccIndex - topPredIndex - 1, ...newPoints);
40396
+ }
40397
+ function removeContiguousProfiles(profilesStartingPosition, profiles, leftIndex, rightIndex) {
40398
+ const start = leftIndex - 1 === -1 ? 0 : leftIndex - 1;
40399
+ const end = rightIndex === profilesStartingPosition.length - 1 ? rightIndex : rightIndex + 1;
40400
+ for (let i = end; i > start; i--) {
40401
+ if (deepEqualsArray(profiles.get(profilesStartingPosition[i]), profiles.get(profilesStartingPosition[i - 1]))) {
40402
+ profiles.delete(profilesStartingPosition[i]);
40403
+ profilesStartingPosition.splice(i, 1);
40404
+ }
40405
+ }
40406
+ }
40407
+ function constructZonesFromProfiles(profilesStartingPosition, profiles) {
40408
+ const mergedZone = [];
40409
+ let pendingZones = [];
40410
+ for (let colIndex = 0; colIndex < profilesStartingPosition.length; colIndex++) {
40411
+ const left = profilesStartingPosition[colIndex];
40412
+ const profile = profiles.get(left);
40413
+ if (!profile || profile.length === 0) {
40414
+ mergedZone.push(...pendingZones);
40415
+ pendingZones = [];
40416
+ continue;
40417
+ }
40418
+ let right = profilesStartingPosition[colIndex + 1];
40419
+ if (right !== undefined) {
40420
+ right--;
40421
+ }
40422
+ const nextPendingZones = [];
40423
+ for (let i = 0; i < profile.length; i += 2) {
40424
+ const top = profile[i];
40425
+ let bottom = profile[i + 1];
40426
+ if (bottom !== undefined) {
40427
+ bottom--;
40428
+ }
40429
+ const profileZone = {
40430
+ top,
40431
+ left,
40432
+ bottom,
40433
+ right,
40434
+ hasHeader: (bottom === undefined && top !== 0) || (right === undefined && left !== 0),
40435
+ };
40436
+ let findCorrespondingZone = false;
40437
+ for (let j = pendingZones.length - 1; j >= 0; j--) {
40438
+ const pendingZone = pendingZones[j];
40439
+ if (pendingZone.top === profileZone.top && pendingZone.bottom === profileZone.bottom) {
40440
+ pendingZone.right = profileZone.right;
40441
+ pendingZones.splice(j, 1);
40442
+ nextPendingZones.push(pendingZone);
40443
+ findCorrespondingZone = true;
40444
+ break;
40445
+ }
40446
+ }
40447
+ if (!findCorrespondingZone) {
40448
+ nextPendingZones.push(profileZone);
40449
+ }
40450
+ }
40451
+ mergedZone.push(...pendingZones);
40452
+ pendingZones = nextPendingZones;
40453
+ }
40454
+ mergedZone.push(...pendingZones);
40455
+ return mergedZone;
40456
+ }
40457
+ function binaryPredecessorSearch(arr, val, start = 0, matchEqual = true) {
40458
+ let end = arr.length - 1;
40459
+ let result = -1;
40460
+ while (start <= end) {
40461
+ const mid = Math.floor((start + end) / 2);
40462
+ if (arr[mid] === val && matchEqual) {
40463
+ return mid;
40464
+ }
40465
+ else if (arr[mid] < val) {
40466
+ result = mid;
40467
+ start = mid + 1;
40468
+ }
40469
+ else {
40470
+ end = mid - 1;
40471
+ }
40472
+ }
40473
+ return result;
40474
+ }
40475
+ function binarySuccessorSearch(arr, val, start = 0, matchEqual = true) {
40476
+ let end = arr.length - 1;
40477
+ let result = arr.length;
40478
+ while (start <= end) {
40479
+ const mid = Math.floor((start + end) / 2);
40480
+ if (arr[mid] === val && matchEqual) {
40481
+ return mid;
40482
+ }
40483
+ else if (arr[mid] > val) {
40484
+ result = mid;
40485
+ end = mid - 1;
40486
+ }
40487
+ else {
40488
+ start = mid + 1;
40489
+ }
40490
+ }
40491
+ return result;
40492
+ }
40493
+ /**
40494
+ * Compares two arrays.
40495
+ * For performance reasons, this function is to be preferred
40496
+ * to 'deepEquals' in the case we know that the inputs are arrays.
40497
+ */
40498
+ function deepEqualsArray(arr1, arr2) {
40499
+ if (arr1.length !== arr2.length) {
40500
+ return false;
40501
+ }
40502
+ for (let i = 0; i < arr1.length; i++) {
40503
+ if (!deepEquals(arr1[i], arr2[i])) {
40504
+ return false;
40505
+ }
40506
+ }
40507
+ return true;
40508
+ }
40509
+
40151
40510
  function quickselect(arr, k, left, right, compare) {
40152
40511
  quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare);
40153
40512
  }
@@ -40911,7 +41270,7 @@ class FormulaDependencyGraph {
40911
41270
  }
40912
41271
  }
40913
41272
  /**
40914
- * Return the cell and all cells that depend on it,
41273
+ * Return all the cells that depend on the provided ranges,
40915
41274
  * in the correct order they should be evaluated.
40916
41275
  * This is called a topological ordering (excluding cycles)
40917
41276
  */
@@ -40922,11 +41281,20 @@ class FormulaDependencyGraph {
40922
41281
  const range = queue.pop();
40923
41282
  visited.addMany(this.encoder.encodeBoundingBox(range));
40924
41283
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41284
+ const nextInQueue = {};
40925
41285
  for (const positionId of impactedPositionIds) {
40926
41286
  if (!visited.has(positionId)) {
40927
- queue.push(this.encoder.decodeToBoundingBox(positionId));
41287
+ const { sheetId, zone } = this.encoder.decodeToBoundingBox(positionId);
41288
+ if (!nextInQueue[sheetId]) {
41289
+ nextInQueue[sheetId] = [];
41290
+ }
41291
+ nextInQueue[sheetId].push(zone);
40928
41292
  }
40929
41293
  }
41294
+ for (const sheetId in nextInQueue) {
41295
+ const zones = futureRecomputeZones(nextInQueue[sheetId]);
41296
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
41297
+ }
40930
41298
  }
40931
41299
  visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40932
41300
  return visited;
@@ -41082,7 +41450,7 @@ class Evaluator {
41082
41450
  }
41083
41451
  if (!content) {
41084
41452
  // The previous content could have blocked some array formulas
41085
- impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41453
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedBy(positionId));
41086
41454
  }
41087
41455
  }
41088
41456
  return impactedPositionIds;
@@ -41127,13 +41495,22 @@ class Evaluator {
41127
41495
  }
41128
41496
  return positionIds;
41129
41497
  }
41130
- getArrayFormulasBlockedByOrSpreadingOn(positionId) {
41498
+ /**
41499
+ * Return the position of formulas blocked by the given position
41500
+ * as well as all their dependencies.
41501
+ */
41502
+ getArrayFormulasBlockedBy(positionId) {
41131
41503
  if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
41132
41504
  return [];
41133
41505
  }
41134
41506
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41135
41507
  const cells = new JetSet(arrayFormulas);
41136
- cells.addMany(this.getCellsDependingOn(arrayFormulas));
41508
+ const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
41509
+ if (arrayFormulaPositionId) {
41510
+ // ignore the formula spreading on the position. Keep only the blocked ones
41511
+ cells.delete(arrayFormulaPositionId);
41512
+ }
41513
+ cells.addMany(this.getCellsDependingOn(cells));
41137
41514
  return cells;
41138
41515
  }
41139
41516
  nextPositionsToUpdate = new JetSet();
@@ -41298,7 +41675,7 @@ class Evaluator {
41298
41675
  }
41299
41676
  this.evaluatedCells.delete(child);
41300
41677
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41301
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41678
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
41302
41679
  }
41303
41680
  this.spreadingRelations.removeNode(positionId);
41304
41681
  }
@@ -41657,7 +42034,7 @@ class EvaluationPlugin extends UIPlugin {
41657
42034
  ? getItemId(newFormat, data.formats)
41658
42035
  : exportedCellData.format;
41659
42036
  let content;
41660
- if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
42037
+ if (isExported && isFormula && formulaCell?.compiledFormula.dependencies.length) {
41661
42038
  content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41662
42039
  }
41663
42040
  else {
@@ -57030,6 +57407,6 @@ exports.setTranslationMethod = setTranslationMethod;
57030
57407
  exports.tokenize = tokenize;
57031
57408
 
57032
57409
 
57033
- __info__.version = "17.0.19";
57034
- __info__.date = "2024-04-18T16:41:42.291Z";
57035
- __info__.hash = "9f3317a";
57410
+ __info__.version = "17.0.21";
57411
+ __info__.date = "2024-05-07T10:39:36.750Z";
57412
+ __info__.hash = "998bea0";
@@ -5952,6 +5952,7 @@ declare class ChartJsComponent extends Component<Props$B, SpreadsheetChildEnv> {
5952
5952
  static template: string;
5953
5953
  private canvas;
5954
5954
  private chart?;
5955
+ private currentRuntime;
5955
5956
  get background(): string;
5956
5957
  get canvasStyle(): string;
5957
5958
  get chartRuntime(): ChartJSRuntime;
@@ -7793,7 +7794,9 @@ declare function chartFontColor(backgroundColor: Color | undefined): Color;
7793
7794
  /**
7794
7795
  * Get a default chart js configuration
7795
7796
  */
7796
- declare function getDefaultChartJsRuntime(chart: AbstractChart, labels: string[], fontColor: Color, { format, locale }: LocaleFormat): Required<ChartConfiguration>;
7797
+ declare function getDefaultChartJsRuntime(chart: AbstractChart, labels: string[], fontColor: Color, { format, locale, truncateLabels }: LocaleFormat & {
7798
+ truncateLabels?: boolean;
7799
+ }): Required<ChartConfiguration>;
7797
7800
  /** See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes */
7798
7801
  declare function getFillingMode(index: number): "origin" | number;
7799
7802