@odoo/o-spreadsheet 17.3.0-alpha.1 → 17.3.0-alpha.3

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.3.0-alpha.1
7
- * @date 2024-03-25T09:43:36.072Z
8
- * @hash 4095c41
6
+ * @version 17.3.0-alpha.3
7
+ * @date 2024-04-10T12:28:23.658Z
8
+ * @hash 80b5056
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -189,7 +189,7 @@
189
189
  const DEFAULT_GAUGE_MIDDLE_COLOR = "#f1c232";
190
190
  const DEFAULT_GAUGE_UPPER_COLOR = "#6aa84f";
191
191
  const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
192
- const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6aa84f";
192
+ const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6AA84F";
193
193
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#E06666";
194
194
  const LINE_FILL_TRANSPARENCY = 0.4;
195
195
  // session
@@ -465,7 +465,7 @@
465
465
  }
466
466
  // Generate new Id if the item didn't exist in the dictionary
467
467
  const ids = Object.keys(itemsDic);
468
- const maxId = ids.length === 0 ? 0 : Math.max(...ids.map((id) => parseInt(id, 10)));
468
+ const maxId = ids.length === 0 ? 0 : largeMax(ids.map((id) => parseInt(id, 10)));
469
469
  itemsDic[maxId + 1] = item;
470
470
  return maxId + 1;
471
471
  }
@@ -483,7 +483,7 @@
483
483
  let timeout = undefined;
484
484
  const debounced = function () {
485
485
  const context = this;
486
- const args = arguments;
486
+ const args = Array.from(arguments);
487
487
  function later() {
488
488
  timeout = undefined;
489
489
  if (!immediate) {
@@ -561,7 +561,7 @@
561
561
  if (typeof o1 !== typeof o2)
562
562
  return false;
563
563
  if (typeof o1 !== "object")
564
- return o1 === o2;
564
+ return false;
565
565
  // Objects can have different keys if the values are undefined
566
566
  for (const key in o2) {
567
567
  if (!(key in o1) && o2[key] !== undefined) {
@@ -685,6 +685,34 @@
685
685
  }
686
686
  return RegExp(searchValue, flags);
687
687
  }
688
+ /**
689
+ * Alternative to Math.max that works with large arrays.
690
+ * Typically useful for arrays bigger than 100k elements.
691
+ */
692
+ function largeMax(array) {
693
+ let len = array.length;
694
+ if (len < 100_000)
695
+ return Math.max(...array);
696
+ let max = -Infinity;
697
+ while (len--) {
698
+ max = array[len] > max ? array[len] : max;
699
+ }
700
+ return max;
701
+ }
702
+ /**
703
+ * Alternative to Math.min that works with large arrays.
704
+ * Typically useful for arrays bigger than 100k elements.
705
+ */
706
+ function largeMin(array) {
707
+ let len = array.length;
708
+ if (len < 100_000)
709
+ return Math.min(...array);
710
+ let min = +Infinity;
711
+ while (len--) {
712
+ min = array[len] < min ? array[len] : min;
713
+ }
714
+ return min;
715
+ }
688
716
 
689
717
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
690
718
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -1857,6 +1885,11 @@
1857
1885
  "REMOVE_CONDITIONAL_FORMAT",
1858
1886
  "CHANGE_CONDITIONAL_FORMAT_PRIORITY",
1859
1887
  ]);
1888
+ const invalidateBordersCommands = new Set([
1889
+ "AUTOFILL_CELL",
1890
+ "SET_BORDER",
1891
+ "SET_ZONE_BORDERS",
1892
+ ]);
1860
1893
  const readonlyAllowedCommands = new Set([
1861
1894
  "START",
1862
1895
  "ACTIVATE_SHEET",
@@ -2087,6 +2120,7 @@
2087
2120
  CommandResult["InvalidCopyPasteSelection"] = "InvalidCopyPasteSelection";
2088
2121
  CommandResult["NoChanges"] = "NoChanges";
2089
2122
  CommandResult["InvalidInputId"] = "InvalidInputId";
2123
+ CommandResult["SheetIsHidden"] = "SheetIsHidden";
2090
2124
  })(exports.CommandResult || (exports.CommandResult = {}));
2091
2125
 
2092
2126
  const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
@@ -2153,6 +2187,7 @@
2153
2187
  BadExpression: "#BAD_EXPR",
2154
2188
  CircularDependency: "#CYCLE",
2155
2189
  UnknownFunction: "#NAME?",
2190
+ DivisionByZero: "#DIV/0!",
2156
2191
  GenericError: "#ERROR",
2157
2192
  };
2158
2193
  const errorTypes = new Set(Object.values(CellErrorType));
@@ -2191,9 +2226,9 @@
2191
2226
 
2192
2227
  // HELPERS
2193
2228
  const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"];
2194
- function assert(condition, message) {
2229
+ function assert(condition, message, value) {
2195
2230
  if (!condition()) {
2196
- throw new EvaluationError(message);
2231
+ throw new EvaluationError(message, value);
2197
2232
  }
2198
2233
  }
2199
2234
  function inferFormat(data) {
@@ -2271,6 +2306,9 @@
2271
2306
  function assertNumberGreaterThanOrEqualToOne(value) {
2272
2307
  assert(() => value >= 1, _t("The function [[FUNCTION_NAME]] expects a number value to be greater than or equal to 1, but receives %s.", value.toString()));
2273
2308
  }
2309
+ function assertNotZero(value) {
2310
+ assert(() => value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
2311
+ }
2274
2312
  function toString(data) {
2275
2313
  const value = toValue(data);
2276
2314
  switch (typeof value) {
@@ -3036,6 +3074,9 @@
3036
3074
  return formattedValue;
3037
3075
  }
3038
3076
  function applyInternalNumberFormat(value, format, locale) {
3077
+ if (value === Infinity) {
3078
+ return "∞" + (format.isPercent ? "%" : "");
3079
+ }
3039
3080
  if (format.isPercent) {
3040
3081
  value = value * 100;
3041
3082
  }
@@ -3379,6 +3420,46 @@
3379
3420
  });
3380
3421
  return convertInternalFormatToFormat(roundedFormat);
3381
3422
  }
3423
+ function humanizeNumber({ value, format }, locale) {
3424
+ const numberFormat = formatLargeNumber({
3425
+ value,
3426
+ format,
3427
+ }, undefined, locale);
3428
+ return formatValue(value, { format: numberFormat, locale });
3429
+ }
3430
+ function formatLargeNumber(arg, unit, locale) {
3431
+ let value = 0;
3432
+ try {
3433
+ value = Math.abs(toNumber(arg?.value, locale));
3434
+ }
3435
+ catch (e) {
3436
+ return "";
3437
+ }
3438
+ const format = arg?.format;
3439
+ if (unit !== undefined) {
3440
+ const postFix = unit?.value;
3441
+ switch (postFix) {
3442
+ case "k":
3443
+ return createLargeNumberFormat(format, 1e3, "k");
3444
+ case "m":
3445
+ return createLargeNumberFormat(format, 1e6, "m");
3446
+ case "b":
3447
+ return createLargeNumberFormat(format, 1e9, "b");
3448
+ default:
3449
+ throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
3450
+ }
3451
+ }
3452
+ if (value < 1e5) {
3453
+ return createLargeNumberFormat(format, 0, "");
3454
+ }
3455
+ else if (value < 1e8) {
3456
+ return createLargeNumberFormat(format, 1e3, "k");
3457
+ }
3458
+ else if (value < 1e11) {
3459
+ return createLargeNumberFormat(format, 1e6, "m");
3460
+ }
3461
+ return createLargeNumberFormat(format, 1e9, "b");
3462
+ }
3382
3463
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
3383
3464
  const internalFormat = parseFormat(format || "#,##0");
3384
3465
  const largeNumberFormat = [];
@@ -4558,8 +4639,9 @@
4558
4639
  * Get the default height of the cell given its style.
4559
4640
  */
4560
4641
  function getDefaultCellHeight(ctx, cell, colSize) {
4561
- if (!cell || !cell.content)
4642
+ if (!cell || (!cell.isFormula && !cell.content)) {
4562
4643
  return DEFAULT_CELL_HEIGHT;
4644
+ }
4563
4645
  const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
4564
4646
  const numberOfLines = cell.isFormula
4565
4647
  ? 1
@@ -4570,14 +4652,19 @@
4570
4652
  const textWidthCache = {};
4571
4653
  function computeTextWidth(context, text, style, fontUnit = "pt") {
4572
4654
  const font = computeTextFont(style, fontUnit);
4655
+ context.save();
4656
+ context.font = font;
4657
+ const width = computeCachedTextWidth(context, text);
4658
+ context.restore();
4659
+ return width;
4660
+ }
4661
+ function computeCachedTextWidth(context, text) {
4662
+ const font = context.font;
4573
4663
  if (!textWidthCache[font]) {
4574
4664
  textWidthCache[font] = {};
4575
4665
  }
4576
4666
  if (textWidthCache[font][text] === undefined) {
4577
- context.save();
4578
- context.font = font;
4579
4667
  const textWidth = context.measureText(text).width;
4580
- context.restore();
4581
4668
  textWidthCache[font][text] = textWidth;
4582
4669
  }
4583
4670
  return textWidthCache[font][text];
@@ -4722,6 +4809,42 @@
4722
4809
  function getContextFontSize(font) {
4723
4810
  return Number(font.match(pxRegex)?.[1]);
4724
4811
  }
4812
+ // Inspired from https://stackoverflow.com/a/10511598
4813
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
4814
+ let width = computeCachedTextWidth(ctx, text);
4815
+ if (width <= maxWidth) {
4816
+ return text;
4817
+ }
4818
+ const ellipsis = "…";
4819
+ const ellipsisWidth = computeCachedTextWidth(ctx, text);
4820
+ if (width <= ellipsisWidth) {
4821
+ return text;
4822
+ }
4823
+ let len = text.length;
4824
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
4825
+ text = text.substring(0, len);
4826
+ width = computeCachedTextWidth(ctx, text);
4827
+ }
4828
+ return text + ellipsis;
4829
+ }
4830
+ function splitTextInTwoLines(text) {
4831
+ let spaces = "";
4832
+ while (text[0] === " ") {
4833
+ spaces += " ";
4834
+ text = text.slice(1);
4835
+ }
4836
+ const length = text.length;
4837
+ const middle = Math.floor(length / 2);
4838
+ const leftSpace = text.substring(0, middle).lastIndexOf(" ");
4839
+ const rightSpace = text.substring(middle).indexOf(" ") + middle;
4840
+ if (leftSpace === -1 && rightSpace === middle - 1) {
4841
+ return [spaces + text, ""];
4842
+ }
4843
+ if (leftSpace > length - rightSpace || rightSpace === middle - 1) {
4844
+ return [spaces + text.slice(0, leftSpace), spaces + text.slice(leftSpace + 1)];
4845
+ }
4846
+ return [spaces + text.slice(0, rightSpace), spaces + text.slice(rightSpace + 1)];
4847
+ }
4725
4848
  function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10 //This value is defined to get a good looking stroke
4726
4849
  ) {
4727
4850
  context.fillText(text, position.x, position.y);
@@ -6993,10 +7116,17 @@
6993
7116
  },
6994
7117
  open(url, env) {
6995
7118
  const sheetId = parseSheetUrl(url);
6996
- env.model.dispatch("ACTIVATE_SHEET", {
7119
+ const result = env.model.dispatch("ACTIVATE_SHEET", {
6997
7120
  sheetIdFrom: env.model.getters.getActiveSheetId(),
6998
7121
  sheetIdTo: sheetId,
6999
7122
  });
7123
+ if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
7124
+ env.notifyUser({
7125
+ type: "warning",
7126
+ sticky: false,
7127
+ text: _t("Cannot open the link because the linked sheet is hidden."),
7128
+ });
7129
+ }
7000
7130
  },
7001
7131
  sequence: 0,
7002
7132
  });
@@ -7112,7 +7242,7 @@
7112
7242
  }
7113
7243
  function numberCell(value, format, formattedValue) {
7114
7244
  return {
7115
- value: value || 0,
7245
+ value: value || 0, // necessary to avoid "-0" and NaN values,
7116
7246
  format,
7117
7247
  formattedValue,
7118
7248
  type: CellValueType.number,
@@ -7358,6 +7488,7 @@
7358
7488
  };
7359
7489
  const ChartTerms = {
7360
7490
  Series: _t("Series"),
7491
+ BackgroundColor: _t("Background color"),
7361
7492
  Errors: {
7362
7493
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7363
7494
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -9393,32 +9524,63 @@ stores.inject(MyMetaStore, storeInstance);
9393
9524
  }
9394
9525
  return true;
9395
9526
  }
9396
- // ---------------------------------------------------------------------------
9397
- // Scorecard
9398
- // ---------------------------------------------------------------------------
9399
- function getBaselineText(baseline, keyValue, baselineMode, locale) {
9527
+ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9528
+ const { x, y } = getters.getMainViewportCoordinates();
9529
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9530
+ const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9531
+ const position = {
9532
+ x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9533
+ y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9534
+ }; // Position at the center of the scrollable viewport
9535
+ return position;
9536
+ }
9537
+
9538
+ function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
9400
9539
  if (!baseline) {
9401
9540
  return "";
9402
9541
  }
9403
9542
  else if (baselineMode === "text" ||
9404
9543
  keyValue?.type !== CellValueType.number ||
9405
9544
  baseline.type !== CellValueType.number) {
9545
+ if (humanize) {
9546
+ return humanizeNumber(baseline, locale);
9547
+ }
9406
9548
  return baseline.formattedValue;
9407
9549
  }
9550
+ let { value, format } = baseline;
9551
+ if (baselineMode === "progress") {
9552
+ value = keyValue.value / value;
9553
+ format = "0.0%";
9554
+ }
9408
9555
  else {
9409
- let diff = keyValue.value - baseline.value;
9410
- if (baselineMode === "percentage" && diff !== 0) {
9411
- diff = (diff / baseline.value) * 100;
9556
+ value = Math.abs(keyValue.value - value);
9557
+ if (baselineMode === "percentage" && value !== 0) {
9558
+ value = value / baseline.value;
9559
+ }
9560
+ if (baselineMode === "percentage") {
9561
+ format = "0.0%";
9412
9562
  }
9413
- if (baselineMode !== "percentage" && baseline.format) {
9414
- return formatValue(diff, { format: baseline.format, locale });
9563
+ if (!format) {
9564
+ value = Math.round(value * 100) / 100;
9415
9565
  }
9416
- const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();
9417
- return baselineMode === "percentage" ? baselineStr + "%" : baselineStr;
9418
9566
  }
9567
+ if (humanize) {
9568
+ return humanizeNumber({ value, format }, locale);
9569
+ }
9570
+ return formatValue(value, { format, locale });
9571
+ }
9572
+ function getKeyValueText(keyValueCell, humanize, locale) {
9573
+ if (!keyValueCell) {
9574
+ return "";
9575
+ }
9576
+ if (humanize) {
9577
+ return humanizeNumber(keyValueCell, locale);
9578
+ }
9579
+ return keyValueCell.formattedValue ?? String(keyValueCell.value ?? "");
9419
9580
  }
9420
9581
  function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
9421
9582
  if (baselineMode === "text" ||
9583
+ baselineMode === "progress" ||
9422
9584
  baseline?.type !== CellValueType.number ||
9423
9585
  keyValue?.type !== CellValueType.number) {
9424
9586
  return undefined;
@@ -9447,17 +9609,6 @@ stores.inject(MyMetaStore, storeInstance);
9447
9609
  }
9448
9610
  return "neutral";
9449
9611
  }
9450
- function getChartPositionAtCenterOfViewport(getters, chartSize) {
9451
- const { x, y } = getters.getMainViewportCoordinates();
9452
- const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9453
- const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9454
- const position = {
9455
- x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9456
- y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9457
- }; // Position at the center of the scrollable viewport
9458
- return position;
9459
- }
9460
-
9461
9612
  function checkKeyValue(definition) {
9462
9613
  return definition.keyValue && !rangeReference.test(definition.keyValue)
9463
9614
  ? "InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */
@@ -9475,10 +9626,12 @@ stores.inject(MyMetaStore, storeInstance);
9475
9626
  baseline;
9476
9627
  baselineMode;
9477
9628
  baselineDescr;
9629
+ progressBar = false;
9478
9630
  background;
9479
9631
  baselineColorUp;
9480
9632
  baselineColorDown;
9481
9633
  fontColor;
9634
+ humanize;
9482
9635
  type = "scorecard";
9483
9636
  constructor(definition, sheetId, getters) {
9484
9637
  super(definition, sheetId, getters);
@@ -9489,6 +9642,7 @@ stores.inject(MyMetaStore, storeInstance);
9489
9642
  this.background = definition.background;
9490
9643
  this.baselineColorUp = definition.baselineColorUp;
9491
9644
  this.baselineColorDown = definition.baselineColorDown;
9645
+ this.humanize = definition.humanize ?? false;
9492
9646
  }
9493
9647
  static validateChartDefinition(validator, definition) {
9494
9648
  return validator.checkValidations(definition, checkKeyValue, checkBaseline);
@@ -9558,6 +9712,7 @@ stores.inject(MyMetaStore, storeInstance);
9558
9712
  keyValue: keyValue
9559
9713
  ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
9560
9714
  : undefined,
9715
+ humanize: this.humanize,
9561
9716
  };
9562
9717
  }
9563
9718
  getDefinitionForExcel() {
@@ -9583,7 +9738,7 @@ stores.inject(MyMetaStore, storeInstance);
9583
9738
  if (structure.title) {
9584
9739
  ctx.font = structure.title.style.font;
9585
9740
  ctx.fillStyle = structure.title.style.color;
9586
- ctx.fillText(structure.title.text, structure.title.position.x, structure.title.position.y);
9741
+ ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
9587
9742
  }
9588
9743
  if (structure.baseline) {
9589
9744
  ctx.font = structure.baseline.style.font;
@@ -9610,20 +9765,41 @@ stores.inject(MyMetaStore, storeInstance);
9610
9765
  ctx.restore();
9611
9766
  }
9612
9767
  if (structure.baselineDescr) {
9613
- ctx.font = structure.baselineDescr.style.font;
9614
- ctx.fillStyle = structure.baselineDescr.style.color;
9615
- ctx.fillText(structure.baselineDescr.text, structure.baselineDescr.position.x, structure.baselineDescr.position.y);
9768
+ const descr = structure.baselineDescr[0];
9769
+ ctx.font = descr.style.font;
9770
+ ctx.fillStyle = descr.style.color;
9771
+ for (const description of structure.baselineDescr) {
9772
+ ctx.fillText(clipTextWithEllipsis(ctx, description.text, canvas.width - description.position.x), description.position.x, description.position.y);
9773
+ }
9616
9774
  }
9617
9775
  if (structure.key) {
9618
9776
  ctx.font = structure.key.style.font;
9619
9777
  ctx.fillStyle = structure.key.style.color;
9620
9778
  drawDecoratedText(ctx, structure.key.text, structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
9621
9779
  }
9780
+ if (structure.progressBar) {
9781
+ ctx.fillStyle = structure.progressBar.style.backgroundColor;
9782
+ ctx.beginPath();
9783
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width, structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9784
+ ctx.fill();
9785
+ ctx.fillStyle = structure.progressBar.style.color;
9786
+ ctx.beginPath();
9787
+ if (structure.progressBar.value > 0) {
9788
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width *
9789
+ Math.max(0, Math.min(1.0, structure.progressBar.value)), structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9790
+ }
9791
+ else {
9792
+ const width = structure.progressBar.dimension.width *
9793
+ Math.max(0, Math.min(1.0, -structure.progressBar.value));
9794
+ ctx.roundRect(structure.progressBar.position.x + structure.progressBar.dimension.width - width, structure.progressBar.position.y, width, structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9795
+ }
9796
+ ctx.fill();
9797
+ }
9622
9798
  }
9623
9799
  function createScorecardChartRuntime(chart, getters) {
9624
- let keyValue = "";
9625
9800
  let formattedKeyValue = "";
9626
9801
  let keyValueCell;
9802
+ const locale = getters.getLocale();
9627
9803
  if (chart.keyValue) {
9628
9804
  const keyValuePosition = {
9629
9805
  sheetId: chart.keyValue.sheetId,
@@ -9631,31 +9807,33 @@ stores.inject(MyMetaStore, storeInstance);
9631
9807
  row: chart.keyValue.zone.top,
9632
9808
  };
9633
9809
  keyValueCell = getters.getEvaluatedCell(keyValuePosition);
9634
- keyValue = String(keyValueCell.value ?? "");
9635
- formattedKeyValue = keyValueCell.formattedValue;
9810
+ formattedKeyValue = getKeyValueText(keyValueCell, chart.humanize ?? false, locale);
9636
9811
  }
9637
9812
  let baselineCell;
9638
9813
  const baseline = chart.baseline;
9639
9814
  if (baseline) {
9640
9815
  const baselinePosition = {
9641
- sheetId: chart.baseline.sheetId,
9642
- col: chart.baseline.zone.left,
9643
- row: chart.baseline.zone.top,
9816
+ sheetId: baseline.sheetId,
9817
+ col: baseline.zone.left,
9818
+ row: baseline.zone.top,
9644
9819
  };
9645
9820
  baselineCell = getters.getEvaluatedCell(baselinePosition);
9646
9821
  }
9647
9822
  const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
9648
- const locale = getters.getLocale();
9823
+ const baselineDisplay = getBaselineText(baselineCell, keyValueCell, chart.baselineMode, chart.humanize ?? false, locale);
9824
+ const baselineValue = chart.baselineMode === "progress" && isNumber(baselineDisplay, locale)
9825
+ ? toNumber(baselineDisplay, locale)
9826
+ : 0;
9649
9827
  return {
9650
9828
  title: _t(chart.title),
9651
- keyValue: formattedKeyValue || keyValue,
9652
- baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode, locale),
9829
+ keyValue: formattedKeyValue,
9830
+ baselineDisplay,
9653
9831
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
9654
9832
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
9655
- baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
9833
+ baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr ? _t(chart.baselineDescr) : "",
9656
9834
  fontColor,
9657
9835
  background,
9658
- baselineStyle: chart.baselineMode !== "percentage" && baseline
9836
+ baselineStyle: chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
9659
9837
  ? getters.getCellStyle({
9660
9838
  sheetId: baseline.sheetId,
9661
9839
  col: baseline.zone.left,
@@ -9669,17 +9847,21 @@ stores.inject(MyMetaStore, storeInstance);
9669
9847
  row: chart.keyValue.zone.top,
9670
9848
  })
9671
9849
  : undefined,
9850
+ progressBar: chart.baselineMode === "progress"
9851
+ ? {
9852
+ value: baselineValue,
9853
+ color: baselineValue > 0 ? chart.baselineColorUp : chart.baselineColorDown,
9854
+ }
9855
+ : undefined,
9672
9856
  };
9673
9857
  }
9674
9858
 
9675
9859
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
9676
9860
  const TITLE_FONT_SIZE = 18;
9677
- const BASELINE_BOX_HEIGHT_RATIO = 0.35;
9678
- const KEY_BOX_HEIGHT_RATIO = 0.65;
9679
- /** Baseline description should have a smaller font than the baseline */
9680
- const BASELINE_DESCR_FONT_RATIO = 0.9;
9681
- /* Padding at the border of the chart, in percentage of the chart width */
9682
- const CHART_PADDING_RATIO = 0.02;
9861
+ const KEY_BOX_HEIGHT_RATIO = 0.8;
9862
+ /* Padding at the border of the chart */
9863
+ const CHART_PADDING = 10;
9864
+ const BOTTOM_PADDING_RATIO = 0.05;
9683
9865
  /**
9684
9866
  * Line height (in em)
9685
9867
  * Having a line heigh =1em (=font size) don't work, the font will overflow.
@@ -9719,31 +9901,37 @@ stores.inject(MyMetaStore, storeInstance);
9719
9901
  },
9720
9902
  };
9721
9903
  const style = this.getTextStyles();
9722
- const { height: titleHeight } = this.getTextDimensions(this.title, style.title.font);
9904
+ let titleHeight = 0;
9723
9905
  if (this.title) {
9906
+ ({ height: titleHeight } = this.getFullTextDimensions(this.title, style.title.font));
9724
9907
  structure.title = {
9725
9908
  text: this.title,
9726
9909
  style: style.title,
9727
9910
  position: {
9728
- x: this.chartPadding,
9729
- y: this.chartPadding + titleHeight,
9911
+ x: CHART_PADDING,
9912
+ y: CHART_PADDING / 2 + titleHeight,
9730
9913
  },
9731
9914
  };
9732
9915
  }
9733
9916
  const baselineArrowSize = style.baselineArrow?.size ?? 0;
9734
- const { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9735
- const { width: baselineDescrWidth } = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font);
9917
+ let { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9918
+ if (!this.baseline) {
9919
+ baselineHeight = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).height;
9920
+ }
9921
+ const baselineDescrWidth = style.baselineDescr.isSplit
9922
+ ? Math.max(...splitTextInTwoLines(this.baselineDescr).map((line) => this.getTextDimensions(line, style.baselineDescr.font).width))
9923
+ : this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).width;
9736
9924
  structure.baseline = {
9737
9925
  text: this.baseline,
9738
9926
  style: style.baselineValue,
9739
9927
  position: {
9740
9928
  x: (this.width - baselineWidth - baselineDescrWidth + baselineArrowSize) / 2,
9741
9929
  y: this.keyValue
9742
- ? this.height - 2 * this.chartPadding
9743
- : this.height - (this.height - titleHeight - baselineHeight) / 2 - this.chartPadding,
9930
+ ? this.height * (1 - BOTTOM_PADDING_RATIO * (this.runtime.progressBar ? 1 : 2))
9931
+ : this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING,
9744
9932
  },
9745
9933
  };
9746
- if (style.baselineArrow) {
9934
+ if (style.baselineArrow && !this.runtime.progressBar) {
9747
9935
  structure.baselineArrow = {
9748
9936
  direction: this.baselineArrow,
9749
9937
  style: style.baselineArrow,
@@ -9754,23 +9942,68 @@ stores.inject(MyMetaStore, storeInstance);
9754
9942
  };
9755
9943
  }
9756
9944
  if (this.baselineDescr) {
9757
- structure.baselineDescr = {
9758
- text: this.baselineDescr,
9759
- style: style.baselineDescr,
9945
+ const position = {
9946
+ x: structure.baseline.position.x + baselineWidth,
9947
+ y: structure.baseline.position.y,
9948
+ };
9949
+ if (style.baselineDescr.isSplit) {
9950
+ const description = splitTextInTwoLines(this.baselineDescr);
9951
+ const measure = this.context.measureText(description[1]);
9952
+ const deltaY = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9953
+ structure.baselineDescr = [
9954
+ {
9955
+ text: description[0],
9956
+ style: style.baselineDescr,
9957
+ position: {
9958
+ x: position.x,
9959
+ y: position.y - deltaY,
9960
+ },
9961
+ },
9962
+ {
9963
+ text: description[1],
9964
+ style: style.baselineDescr,
9965
+ position,
9966
+ },
9967
+ ];
9968
+ }
9969
+ else {
9970
+ structure.baselineDescr = [
9971
+ {
9972
+ text: this.baselineDescr,
9973
+ style: style.baselineDescr,
9974
+ position,
9975
+ },
9976
+ ];
9977
+ }
9978
+ }
9979
+ let progressBarHeight = 0;
9980
+ if (this.runtime.progressBar) {
9981
+ progressBarHeight = this.height * 0.05;
9982
+ structure.progressBar = {
9760
9983
  position: {
9761
- x: structure.baseline.position.x + baselineWidth,
9762
- y: structure.baseline.position.y,
9984
+ x: 2 * CHART_PADDING,
9985
+ y: this.height * (1 - 2 * BOTTOM_PADDING_RATIO) - baselineHeight - progressBarHeight,
9986
+ },
9987
+ dimension: {
9988
+ height: progressBarHeight,
9989
+ width: this.width - 4 * CHART_PADDING,
9990
+ },
9991
+ value: this.runtime.progressBar.value,
9992
+ style: {
9993
+ color: this.runtime.progressBar.color,
9994
+ backgroundColor: this.secondaryFontColor,
9763
9995
  },
9764
9996
  };
9765
9997
  }
9766
- const { height: keyHeight, width: keyWidth } = this.getTextDimensions(this.keyValue, style.keyValue.font);
9998
+ const { width: keyWidth, height: keyHeight } = this.getFullTextDimensions(this.keyValue, style.keyValue.font);
9767
9999
  if (this.keyValue) {
9768
10000
  structure.key = {
9769
10001
  text: this.keyValue,
9770
10002
  style: style.keyValue,
9771
10003
  position: {
9772
10004
  x: (this.width - keyWidth) / 2,
9773
- y: (this.height - baselineHeight + titleHeight + keyHeight) / 2 - this.chartPadding,
10005
+ y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
10006
+ (titleHeight + keyHeight / (this.baseline || this.baselineDescr ? 2 : 1.2)) / 2,
9774
10007
  },
9775
10008
  };
9776
10009
  }
@@ -9797,9 +10030,6 @@ stores.inject(MyMetaStore, storeInstance);
9797
10030
  get secondaryFontColor() {
9798
10031
  return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
9799
10032
  }
9800
- get chartPadding() {
9801
- return this.width * CHART_PADDING_RATIO;
9802
- }
9803
10033
  getTextDimensions(text, font) {
9804
10034
  this.context.font = font;
9805
10035
  const measure = this.context.measureText(text);
@@ -9808,16 +10038,44 @@ stores.inject(MyMetaStore, storeInstance);
9808
10038
  height: measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent,
9809
10039
  };
9810
10040
  }
10041
+ getFullTextDimensions(text, font) {
10042
+ this.context.font = font;
10043
+ const measure = this.context.measureText(text);
10044
+ return {
10045
+ width: measure.width,
10046
+ height: measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent,
10047
+ };
10048
+ }
9811
10049
  getTextStyles() {
9812
10050
  // If the widest text overflows horizontally, scale it down, and apply the same scaling factors to all the other fonts.
9813
- const maxLineWidth = this.width * (1 - 2 * CHART_PADDING_RATIO);
9814
- const widestElement = this.getWidestElement();
9815
- const baseFontSize = widestElement.getElementMaxFontSize(this.getDrawableHeight(), this);
9816
- const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.context, this));
9817
- let scalingFactor = fontSizeMatchingWidth / baseFontSize;
10051
+ const maxLineWidth = this.width - 2 * CHART_PADDING;
10052
+ const drawableHeight = this.getDrawableHeight();
9818
10053
  // Fonts sizes in px
9819
- const keyFontSize = new KeyValueElement(this.runtime.keyValueStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
9820
- const baselineFontSize = new BaselineElement(this.runtime.baselineStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
10054
+ const keyValueElement = new KeyValueElement(this.runtime.keyValueStyle);
10055
+ const heightFont = keyValueElement.getElementMaxFontSize(drawableHeight, this);
10056
+ const widthFont = getFontSizeMatchingWidth(maxLineWidth, 600, (fontSize) => keyValueElement.getElementWidth(fontSize, this.context, this));
10057
+ const keyFontSize = Math.min(heightFont, widthFont);
10058
+ let baselineValueFontSize = Math.floor(keyFontSize * 0.5);
10059
+ this.context.font = getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic);
10060
+ const baselineText = this.baselineArrow !== "neutral" ? "A " + this.baseline : this.baseline;
10061
+ const baselineValueWidth = computeCachedTextWidth(this.context, baselineText);
10062
+ const remainingWidth = maxLineWidth - baselineValueWidth;
10063
+ let baselineDescrFontSize = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => computeTextWidth(this.context, this.baselineDescr, { fontSize }));
10064
+ let isBaselineSplit = false;
10065
+ if (baselineDescrFontSize < baselineValueFontSize / 2.5) {
10066
+ isBaselineSplit = true;
10067
+ baselineDescrFontSize = Math.floor(baselineValueFontSize / 2.5);
10068
+ for (const line of splitTextInTwoLines(this.baselineDescr)) {
10069
+ const lineWidth = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => {
10070
+ this.context.font = getDefaultContextFont(fontSize);
10071
+ return this.context.measureText(line).width;
10072
+ });
10073
+ baselineDescrFontSize = Math.min(baselineDescrFontSize, lineWidth);
10074
+ }
10075
+ }
10076
+ if (this.runtime.progressBar) {
10077
+ baselineValueFontSize /= 1.5;
10078
+ }
9821
10079
  return {
9822
10080
  title: {
9823
10081
  font: getDefaultContextFont(TITLE_FONT_SIZE),
@@ -9830,7 +10088,7 @@ stores.inject(MyMetaStore, storeInstance);
9830
10088
  underline: this.runtime.keyValueStyle?.underline,
9831
10089
  },
9832
10090
  baselineValue: {
9833
- font: getDefaultContextFont(baselineFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
10091
+ font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
9834
10092
  strikethrough: this.runtime.baselineStyle?.strikethrough,
9835
10093
  underline: this.runtime.baselineStyle?.underline,
9836
10094
  color: this.runtime.baselineStyle?.textColor ||
@@ -9838,33 +10096,25 @@ stores.inject(MyMetaStore, storeInstance);
9838
10096
  this.secondaryFontColor,
9839
10097
  },
9840
10098
  baselineDescr: {
9841
- font: getDefaultContextFont(baselineFontSize * BASELINE_DESCR_FONT_RATIO),
10099
+ font: getDefaultContextFont(baselineDescrFontSize),
10100
+ isSplit: isBaselineSplit,
9842
10101
  color: this.secondaryFontColor,
9843
10102
  },
9844
- baselineArrow: this.baselineArrow === "neutral"
10103
+ baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar
9845
10104
  ? undefined
9846
10105
  : {
9847
- size: this.keyValue ? 0.8 * baselineFontSize : 0,
10106
+ size: this.keyValue ? 0.8 * baselineValueFontSize : 0,
9848
10107
  color: this.runtime.baselineColor || this.secondaryFontColor,
9849
10108
  },
9850
10109
  };
9851
10110
  }
9852
10111
  /** Get the height of the chart minus all the vertical paddings */
9853
10112
  getDrawableHeight() {
9854
- const verticalPadding = 2 * this.chartPadding;
9855
- let availableHeight = this.height - verticalPadding;
10113
+ const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10114
+ let availableHeight = this.height - 2 * verticalPadding;
9856
10115
  availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
9857
10116
  return availableHeight;
9858
10117
  }
9859
- /** Return the element with he widest text in the chart */
9860
- getWidestElement() {
9861
- const baseline = new BaselineElement(this.runtime.baselineStyle);
9862
- const keyValue = new KeyValueElement(this.runtime.keyValueStyle);
9863
- return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.context, this) >
9864
- keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.context, this)
9865
- ? baseline
9866
- : keyValue;
9867
- }
9868
10118
  }
9869
10119
  class ScorecardScalableElement {
9870
10120
  style;
@@ -9873,29 +10123,7 @@ stores.inject(MyMetaStore, storeInstance);
9873
10123
  }
9874
10124
  measureTextWidth(ctx, text, fontSize) {
9875
10125
  ctx.font = getDefaultContextFont(fontSize, this.style.bold, this.style.italic);
9876
- return ctx.measureText(text).width;
9877
- }
9878
- }
9879
- class BaselineElement extends ScorecardScalableElement {
9880
- getElementWidth(fontSize, ctx, chart) {
9881
- if (!chart.runtime) {
9882
- return 0;
9883
- }
9884
- const baselineStr = chart.baseline;
9885
- // Put mock text to simulate the width of the up/down arrow
9886
- const largeText = chart.baselineArrow !== "neutral" ? "A " + baselineStr : baselineStr;
9887
- let textWidth = this.measureTextWidth(ctx, largeText, fontSize);
9888
- // Baseline descr font size should be smaller than baseline font size
9889
- textWidth += this.measureTextWidth(ctx, chart.baselineDescr, fontSize * BASELINE_DESCR_FONT_RATIO);
9890
- return textWidth;
9891
- }
9892
- getElementMaxFontSize(availableHeight, chart) {
9893
- if (!chart.runtime) {
9894
- return 0;
9895
- }
9896
- const haveBaseline = chart.baseline !== "" || chart.baselineDescr;
9897
- const maxHeight = haveBaseline ? BASELINE_BOX_HEIGHT_RATIO * availableHeight : 0;
9898
- return maxHeight / LINE_HEIGHT;
10126
+ return computeCachedTextWidth(ctx, text);
9899
10127
  }
9900
10128
  }
9901
10129
  class KeyValueElement extends ScorecardScalableElement {
@@ -9956,11 +10184,11 @@ stores.inject(MyMetaStore, storeInstance);
9956
10184
  }
9957
10185
  else {
9958
10186
  const range = this.getters.getRangeFromSheetXC(position.sheetId, rule.criterion.values[0]);
9959
- values = this.getters
10187
+ values = Array.from(new Set(this.getters
9960
10188
  .getRangeValues(range)
9961
10189
  .filter(isNotNull)
9962
10190
  .map((value) => value.toString())
9963
- .filter((val) => val !== "");
10191
+ .filter((val) => val !== "")));
9964
10192
  }
9965
10193
  return values.map((value) => ({ text: value }));
9966
10194
  },
@@ -10825,33 +11053,6 @@ stores.inject(MyMetaStore, storeInstance);
10825
11053
  // -----------------------------------------------------------------------------
10826
11054
  // FORMAT.LARGE.NUMBER
10827
11055
  // -----------------------------------------------------------------------------
10828
- function formatLargeNumber(arg, unit, locale) {
10829
- const value = Math.abs(toNumber(arg?.value, locale));
10830
- const format = arg?.format;
10831
- if (unit !== undefined) {
10832
- const postFix = unit?.value;
10833
- switch (postFix) {
10834
- case "k":
10835
- return createLargeNumberFormat(format, 1e3, "k");
10836
- case "m":
10837
- return createLargeNumberFormat(format, 1e6, "m");
10838
- case "b":
10839
- return createLargeNumberFormat(format, 1e9, "b");
10840
- default:
10841
- throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
10842
- }
10843
- }
10844
- if (value < 1e5) {
10845
- return createLargeNumberFormat(format, 0, "");
10846
- }
10847
- else if (value < 1e8) {
10848
- return createLargeNumberFormat(format, 1e3, "k");
10849
- }
10850
- else if (value < 1e11) {
10851
- return createLargeNumberFormat(format, 1e6, "m");
10852
- }
10853
- return createLargeNumberFormat(format, 1e9, "b");
10854
- }
10855
11056
  const FORMAT_LARGE_NUMBER = {
10856
11057
  description: _t("Apply a large number format"),
10857
11058
  args: [
@@ -11013,7 +11214,7 @@ stores.inject(MyMetaStore, storeInstance);
11013
11214
  compute: function (x, y) {
11014
11215
  const _x = toNumber(x, this.locale);
11015
11216
  const _y = toNumber(y, this.locale);
11016
- assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
11217
+ assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
11017
11218
  return Math.atan2(_y, _x);
11018
11219
  },
11019
11220
  isExported: true,
@@ -11143,7 +11344,7 @@ stores.inject(MyMetaStore, storeInstance);
11143
11344
  returns: ["NUMBER"],
11144
11345
  compute: function (angle) {
11145
11346
  const _angle = toNumber(angle, this.locale);
11146
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11347
+ assertNotZero(_angle);
11147
11348
  return 1 / Math.tan(_angle);
11148
11349
  },
11149
11350
  isExported: true,
@@ -11157,7 +11358,7 @@ stores.inject(MyMetaStore, storeInstance);
11157
11358
  returns: ["NUMBER"],
11158
11359
  compute: function (value) {
11159
11360
  const _value = toNumber(value, this.locale);
11160
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11361
+ assertNotZero(_value);
11161
11362
  return 1 / Math.tanh(_value);
11162
11363
  },
11163
11364
  isExported: true,
@@ -11285,7 +11486,7 @@ stores.inject(MyMetaStore, storeInstance);
11285
11486
  returns: ["NUMBER"],
11286
11487
  compute: function (angle) {
11287
11488
  const _angle = toNumber(angle, this.locale);
11288
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11489
+ assertNotZero(_angle);
11289
11490
  return 1 / Math.sin(_angle);
11290
11491
  },
11291
11492
  isExported: true,
@@ -11299,7 +11500,7 @@ stores.inject(MyMetaStore, storeInstance);
11299
11500
  returns: ["NUMBER"],
11300
11501
  compute: function (value) {
11301
11502
  const _value = toNumber(value, this.locale);
11302
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11503
+ assertNotZero(_value);
11303
11504
  return 1 / Math.sinh(_value);
11304
11505
  },
11305
11506
  isExported: true,
@@ -11498,7 +11699,7 @@ stores.inject(MyMetaStore, storeInstance);
11498
11699
  // MOD
11499
11700
  // -----------------------------------------------------------------------------
11500
11701
  function mod(dividend, divisor) {
11501
- assert(() => divisor !== 0, _t("The divisor must be different from 0."));
11702
+ assert(() => divisor !== 0, _t("The divisor must be different from 0."), CellErrorType.DivisionByZero);
11502
11703
  const modulus = dividend % divisor;
11503
11704
  // -42 % 10 = -2 but we want 8, so need the code below
11504
11705
  if ((modulus > 0 && divisor < 0) || (modulus < 0 && divisor > 0)) {
@@ -12067,7 +12268,7 @@ stores.inject(MyMetaStore, storeInstance);
12067
12268
  count += 1;
12068
12269
  return acc + a;
12069
12270
  }, 0, locale);
12070
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12271
+ assertNotZero(count);
12071
12272
  return sum / count;
12072
12273
  }
12073
12274
  function countNumbers(values, locale) {
@@ -12134,7 +12335,7 @@ stores.inject(MyMetaStore, storeInstance);
12134
12335
  function covariance(dataY, dataX, isSample) {
12135
12336
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
12136
12337
  const count = flatDataY.length;
12137
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12338
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12138
12339
  let sumY = 0;
12139
12340
  let sumX = 0;
12140
12341
  for (let i = 0; i < count; i++) {
@@ -12157,7 +12358,7 @@ stores.inject(MyMetaStore, storeInstance);
12157
12358
  count += 1;
12158
12359
  return acc + a;
12159
12360
  }, 0, locale);
12160
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12361
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12161
12362
  const average = sum / count;
12162
12363
  return (reduceFunction(args, (acc, a) => acc + Math.pow(a - average, 2), 0, locale) /
12163
12364
  (count - (isSample ? 1 : 0)));
@@ -12354,7 +12555,7 @@ stores.inject(MyMetaStore, storeInstance);
12354
12555
  count += 1;
12355
12556
  return acc + a;
12356
12557
  }, 0, this.locale);
12357
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12558
+ assertNotZero(count);
12358
12559
  const average = sum / count;
12359
12560
  return reduceNumbers(values, (acc, a) => acc + Math.abs(average - a), 0, this.locale) / count;
12360
12561
  },
@@ -12426,7 +12627,7 @@ stores.inject(MyMetaStore, storeInstance);
12426
12627
  }
12427
12628
  }
12428
12629
  }
12429
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12630
+ assertNotZero(count);
12430
12631
  return { value: sum / count, format: inferFormat(args[0]) };
12431
12632
  },
12432
12633
  };
@@ -12446,7 +12647,7 @@ stores.inject(MyMetaStore, storeInstance);
12446
12647
  count += 1;
12447
12648
  return acc + a;
12448
12649
  }, 0, this.locale);
12449
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12650
+ assertNotZero(count);
12450
12651
  return {
12451
12652
  value: sum / count,
12452
12653
  format: inferFormat(args[0]),
@@ -12476,7 +12677,7 @@ stores.inject(MyMetaStore, storeInstance);
12476
12677
  sum += value;
12477
12678
  }
12478
12679
  }, this.locale);
12479
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12680
+ assertNotZero(count);
12480
12681
  return sum / count;
12481
12682
  },
12482
12683
  isExported: true,
@@ -12505,7 +12706,7 @@ stores.inject(MyMetaStore, storeInstance);
12505
12706
  sum += value;
12506
12707
  }
12507
12708
  }, this.locale);
12508
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12709
+ assertNotZero(count);
12509
12710
  return sum / count;
12510
12711
  },
12511
12712
  isExported: true,
@@ -17930,7 +18131,7 @@ stores.inject(MyMetaStore, storeInstance);
17930
18131
  returns: ["NUMBER"],
17931
18132
  compute: function (dividend, divisor) {
17932
18133
  const _divisor = toNumber(divisor, this.locale);
17933
- assert(() => _divisor !== 0, _t("The divisor must be different from zero."));
18134
+ assert(() => _divisor !== 0, _t("The divisor must be different from zero."), CellErrorType.DivisionByZero);
17934
18135
  return {
17935
18136
  value: toNumber(dividend, this.locale) / _divisor,
17936
18137
  format: dividend?.format || divisor?.format,
@@ -19407,10 +19608,10 @@ stores.inject(MyMetaStore, storeInstance);
19407
19608
  }
19408
19609
  }
19409
19610
  return {
19410
- labels: Object.keys(labelMap),
19611
+ labels: Array.from(labelSet),
19411
19612
  dataSetsValues: datasets.map((dataset, indexOfDataset) => ({
19412
19613
  ...dataset,
19413
- data: Object.values(labelMap).map((dataOfLabel) => dataOfLabel[indexOfDataset]),
19614
+ data: Array.from(labelSet).map((label) => labelMap[label][indexOfDataset]),
19414
19615
  })),
19415
19616
  };
19416
19617
  }
@@ -19429,8 +19630,8 @@ stores.inject(MyMetaStore, storeInstance);
19429
19630
  function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
19430
19631
  const options = {
19431
19632
  // https://www.chartjs.org/docs/latest/general/responsive.html
19432
- responsive: true,
19433
- maintainAspectRatio: false,
19633
+ responsive: true, // will resize when its container is resized
19634
+ maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
19434
19635
  layout: {
19435
19636
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
19436
19637
  },
@@ -19476,7 +19677,7 @@ stores.inject(MyMetaStore, storeInstance);
19476
19677
  labels: labels.map(truncateLabel),
19477
19678
  datasets: [],
19478
19679
  },
19479
- platform: undefined,
19680
+ platform: undefined, // This key is optional and will be set by chart.js
19480
19681
  plugins: [],
19481
19682
  };
19482
19683
  }
@@ -19753,7 +19954,7 @@ stores.inject(MyMetaStore, storeInstance);
19753
19954
  },
19754
19955
  y: {
19755
19956
  position: chart.verticalAxisPosition,
19756
- beginAtZero: true,
19957
+ beginAtZero: true, // the origin of the y axis is always zero
19757
19958
  ticks: {
19758
19959
  color: fontColor,
19759
19960
  callback: (value) => {
@@ -19804,6 +20005,204 @@ stores.inject(MyMetaStore, storeInstance);
19804
20005
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
19805
20006
  }
19806
20007
 
20008
+ class ComboChart extends AbstractChart {
20009
+ useBothYAxis;
20010
+ dataSets;
20011
+ labelRange;
20012
+ background;
20013
+ verticalAxisPosition;
20014
+ legendPosition;
20015
+ aggregated;
20016
+ dataSetsHaveTitle;
20017
+ type = "combo";
20018
+ constructor(definition, sheetId, getters) {
20019
+ super(definition, sheetId, getters);
20020
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
20021
+ this.labelRange = createRange(getters, sheetId, definition.labelRange);
20022
+ this.background = definition.background;
20023
+ this.verticalAxisPosition = definition.verticalAxisPosition;
20024
+ this.legendPosition = definition.legendPosition;
20025
+ this.aggregated = definition.aggregated;
20026
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
20027
+ this.useBothYAxis = definition.useBothYAxis;
20028
+ }
20029
+ static transformDefinition(definition, executed) {
20030
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
20031
+ }
20032
+ static validateChartDefinition(validator, definition) {
20033
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
20034
+ }
20035
+ getContextCreation() {
20036
+ return {
20037
+ background: this.background,
20038
+ title: this.title,
20039
+ range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
20040
+ auxiliaryRange: this.labelRange
20041
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
20042
+ : undefined,
20043
+ aggregated: this.aggregated,
20044
+ };
20045
+ }
20046
+ getDefinition() {
20047
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
20048
+ }
20049
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
20050
+ return {
20051
+ type: "combo",
20052
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
20053
+ background: this.background,
20054
+ dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
20055
+ legendPosition: this.legendPosition,
20056
+ verticalAxisPosition: this.verticalAxisPosition,
20057
+ labelRange: labelRange
20058
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
20059
+ : undefined,
20060
+ title: this.title,
20061
+ aggregated: this.aggregated,
20062
+ useBothYAxis: this.useBothYAxis,
20063
+ };
20064
+ }
20065
+ getDefinitionForExcel() {
20066
+ // Excel does not support aggregating labels
20067
+ if (this.aggregated) {
20068
+ return undefined;
20069
+ }
20070
+ const dataSets = this.dataSets
20071
+ .map((ds) => toExcelDataset(this.getters, ds))
20072
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
20073
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
20074
+ return {
20075
+ ...this.getDefinition(),
20076
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
20077
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
20078
+ dataSets,
20079
+ labelRange,
20080
+ };
20081
+ }
20082
+ updateRanges(applyChange) {
20083
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
20084
+ if (!isStale) {
20085
+ return this;
20086
+ }
20087
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
20088
+ return new ComboChart(definition, this.sheetId, this.getters);
20089
+ }
20090
+ static getDefinitionFromContextCreation(context) {
20091
+ return {
20092
+ background: context.background,
20093
+ dataSets: context.range ? context.range : [],
20094
+ dataSetsHaveTitle: false,
20095
+ aggregated: context.aggregated,
20096
+ legendPosition: "top",
20097
+ title: context.title || "",
20098
+ verticalAxisPosition: "left",
20099
+ labelRange: context.auxiliaryRange || undefined,
20100
+ type: "combo",
20101
+ useBothYAxis: false,
20102
+ };
20103
+ }
20104
+ copyForSheetId(sheetId) {
20105
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
20106
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
20107
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
20108
+ return new ComboChart(definition, sheetId, this.getters);
20109
+ }
20110
+ copyInSheetId(sheetId) {
20111
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
20112
+ return new ComboChart(definition, sheetId, this.getters);
20113
+ }
20114
+ }
20115
+ function createComboChartRuntime(chart, getters) {
20116
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
20117
+ const locale = getters.getLocale();
20118
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
20119
+ let labels = labelValues.formattedValues;
20120
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
20121
+ if (chart.dataSetsHaveTitle &&
20122
+ dataSetsValues[0] &&
20123
+ labels.length > dataSetsValues[0].data.length) {
20124
+ labels.shift();
20125
+ }
20126
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
20127
+ if (chart.aggregated) {
20128
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
20129
+ }
20130
+ const localeFormat = { format: dataSetFormat, locale };
20131
+ const fontColor = chartFontColor(chart.background);
20132
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
20133
+ const legend = {
20134
+ labels: { color: fontColor },
20135
+ };
20136
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
20137
+ legend.display = false;
20138
+ }
20139
+ else {
20140
+ legend.position = chart.legendPosition;
20141
+ }
20142
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
20143
+ config.options.layout = {
20144
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
20145
+ };
20146
+ config.options.scales = {
20147
+ x: {
20148
+ ticks: {
20149
+ padding: 5,
20150
+ color: fontColor,
20151
+ },
20152
+ },
20153
+ };
20154
+ const verticalAxis = {
20155
+ beginAtZero: true, // the origin of the y axis is always zero
20156
+ ticks: {
20157
+ color: fontColor,
20158
+ callback: (value) => {
20159
+ value = Number(value);
20160
+ if (isNaN(value))
20161
+ return value;
20162
+ const { locale, format } = localeFormat;
20163
+ return formatValue(value, {
20164
+ locale,
20165
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
20166
+ });
20167
+ },
20168
+ },
20169
+ };
20170
+ if (chart.useBothYAxis) {
20171
+ config.options.scales.y = {
20172
+ ...verticalAxis,
20173
+ position: "left",
20174
+ };
20175
+ config.options.scales.y1 = {
20176
+ ...verticalAxis,
20177
+ position: "right",
20178
+ grid: {
20179
+ display: false,
20180
+ },
20181
+ };
20182
+ }
20183
+ else {
20184
+ config.options.scales.y = {
20185
+ ...verticalAxis,
20186
+ position: chart.verticalAxisPosition,
20187
+ };
20188
+ }
20189
+ const colors = new ChartColors();
20190
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
20191
+ const color = colors.next();
20192
+ const dataset = {
20193
+ label,
20194
+ data,
20195
+ borderColor: color,
20196
+ backgroundColor: color,
20197
+ yAxisID: index > 0 && chart.useBothYAxis ? "y1" : "y",
20198
+ type: index === 0 ? "bar" : "line",
20199
+ order: -index,
20200
+ };
20201
+ config.data.datasets.push(dataset);
20202
+ }
20203
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
20204
+ }
20205
+
19807
20206
  function isDataRangeValid(definition) {
19808
20207
  return definition.dataRange && !rangeReference.test(definition.dataRange)
19809
20208
  ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
@@ -20142,7 +20541,7 @@ stores.inject(MyMetaStore, storeInstance);
20142
20541
  return undefined;
20143
20542
  }
20144
20543
  const labelsTimestamps = labelDates.map((date) => date.getTime());
20145
- const period = Math.max(...labelsTimestamps) - Math.min(...labelsTimestamps);
20544
+ const period = largeMax(labelsTimestamps) - largeMin(labelsTimestamps);
20146
20545
  const minUnit = getFormatMinDisplayUnit(format);
20147
20546
  if (UNIT_LENGTH.second >= UNIT_LENGTH[minUnit] && Milliseconds.inSeconds(period) < 180) {
20148
20547
  return "second";
@@ -20274,7 +20673,7 @@ stores.inject(MyMetaStore, storeInstance);
20274
20673
  },
20275
20674
  y: {
20276
20675
  position: chart.verticalAxisPosition,
20277
- beginAtZero: true,
20676
+ beginAtZero: true, // the origin of the y axis is always zero
20278
20677
  ticks: {
20279
20678
  color: fontColor,
20280
20679
  callback: (value) => {
@@ -20360,7 +20759,7 @@ stores.inject(MyMetaStore, storeInstance);
20360
20759
  const dataset = {
20361
20760
  label,
20362
20761
  data,
20363
- tension: 0,
20762
+ tension: 0, // 0 -> render straight lines, which is much faster
20364
20763
  borderColor: color,
20365
20764
  backgroundColor,
20366
20765
  pointBackgroundColor: color,
@@ -20582,7 +20981,7 @@ stores.inject(MyMetaStore, storeInstance);
20582
20981
  ...this.getDefinition(),
20583
20982
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
20584
20983
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
20585
- verticalAxisPosition: "left",
20984
+ verticalAxisPosition: "left", //TODO ExcelChartDefinition should be adapted, but can be done later
20586
20985
  dataSets,
20587
20986
  labelRange,
20588
20987
  };
@@ -20630,7 +21029,7 @@ stores.inject(MyMetaStore, storeInstance);
20630
21029
  }
20631
21030
  function getPieColors(colors, dataSetsValues) {
20632
21031
  const pieColors = [];
20633
- const maxLength = Math.max(...dataSetsValues.map((ds) => ds.data.length));
21032
+ const maxLength = largeMax(dataSetsValues.map((ds) => ds.data.length));
20634
21033
  for (let i = 0; i <= maxLength; i++) {
20635
21034
  pieColors.push(colors.next());
20636
21035
  }
@@ -20777,7 +21176,21 @@ stores.inject(MyMetaStore, storeInstance);
20777
21176
  return new ScatterChart(definition, this.sheetId, this.getters);
20778
21177
  }
20779
21178
  getDefinitionForExcel() {
20780
- return undefined; // TODO
21179
+ // Excel does not support aggregating labels
21180
+ if (this.aggregated) {
21181
+ return undefined;
21182
+ }
21183
+ const dataSets = this.dataSets
21184
+ .map((ds) => toExcelDataset(this.getters, ds))
21185
+ .filter((ds) => ds.range !== "");
21186
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
21187
+ return {
21188
+ ...this.getDefinition(),
21189
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
21190
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
21191
+ dataSets,
21192
+ labelRange,
21193
+ };
20781
21194
  }
20782
21195
  copyForSheetId(sheetId) {
20783
21196
  const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
@@ -20799,7 +21212,7 @@ stores.inject(MyMetaStore, storeInstance);
20799
21212
  configOptions.elements = {
20800
21213
  point: {
20801
21214
  radius: 3,
20802
- hoverRadius: 3,
21215
+ hoverRadius: 3, // chartJS seems bugged, the point starts with the "hoverRadius" value
20803
21216
  hitRadius: 8,
20804
21217
  },
20805
21218
  };
@@ -20839,6 +21252,16 @@ stores.inject(MyMetaStore, storeInstance);
20839
21252
  name: _t("Bar"),
20840
21253
  sequence: 10,
20841
21254
  });
21255
+ chartRegistry.add("combo", {
21256
+ match: (type) => type === "combo",
21257
+ createChart: (definition, sheetId, getters) => new ComboChart(definition, sheetId, getters),
21258
+ getChartRuntime: createComboChartRuntime,
21259
+ validateChartDefinition: (validator, definition) => ComboChart.validateChartDefinition(validator, definition),
21260
+ transformDefinition: (definition, executed) => ComboChart.transformDefinition(definition, executed),
21261
+ getChartDefinitionFromContextCreation: (context) => ComboChart.getDefinitionFromContextCreation(context),
21262
+ name: _t("Combo"),
21263
+ sequence: 15,
21264
+ });
20842
21265
  chartRegistry.add("line", {
20843
21266
  match: (type) => type === "line",
20844
21267
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
@@ -20892,6 +21315,7 @@ stores.inject(MyMetaStore, storeInstance);
20892
21315
  const chartComponentRegistry = new Registry();
20893
21316
  chartComponentRegistry.add("line", ChartJsComponent);
20894
21317
  chartComponentRegistry.add("bar", ChartJsComponent);
21318
+ chartComponentRegistry.add("combo", ChartJsComponent);
20895
21319
  chartComponentRegistry.add("pie", ChartJsComponent);
20896
21320
  chartComponentRegistry.add("gauge", GaugeChartComponent);
20897
21321
  chartComponentRegistry.add("scatter", ChartJsComponent);
@@ -23150,7 +23574,7 @@ stores.inject(MyMetaStore, storeInstance);
23150
23574
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23151
23575
  border: { bottom: { color: colorSet.highlight, style: "thin" } },
23152
23576
  },
23153
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23577
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23154
23578
  firstRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23155
23579
  secondRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
23156
23580
  });
@@ -23168,7 +23592,7 @@ stores.inject(MyMetaStore, storeInstance);
23168
23592
  },
23169
23593
  },
23170
23594
  headerRow: { border: { bottom: { color: colorSet.highlight, style: "medium" } } },
23171
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23595
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23172
23596
  firstRowStripe: { style: { fillColor: colorSet.light } },
23173
23597
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23174
23598
  });
@@ -23187,7 +23611,7 @@ stores.inject(MyMetaStore, storeInstance);
23187
23611
  headerRow: {
23188
23612
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23189
23613
  },
23190
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23614
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23191
23615
  firstRowStripe: { style: { fillColor: colorSet.light } },
23192
23616
  firstColumnStripe: { style: { fillColor: colorSet.light } },
23193
23617
  });
@@ -23223,7 +23647,7 @@ stores.inject(MyMetaStore, storeInstance);
23223
23647
  bottom: { color: "#000000", style: "medium" },
23224
23648
  },
23225
23649
  },
23226
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23650
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23227
23651
  headerRow: {
23228
23652
  style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" },
23229
23653
  border: { bottom: { color: "#000000", style: "medium" } },
@@ -23247,7 +23671,7 @@ stores.inject(MyMetaStore, storeInstance);
23247
23671
  },
23248
23672
  style: { fillColor: colorSet.light },
23249
23673
  },
23250
- totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } },
23674
+ totalRow: { border: { top: { color: colorSet.highlight, style: "medium" } } }, // @compatibility: should be double line
23251
23675
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23252
23676
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
23253
23677
  });
@@ -23278,7 +23702,7 @@ stores.inject(MyMetaStore, storeInstance);
23278
23702
  category: "dark",
23279
23703
  colorName: colorSet.name,
23280
23704
  wholeTable: { style: { fillColor: colorSet.light } },
23281
- totalRow: { border: { top: { color: "#000000", style: "medium" } } },
23705
+ totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
23282
23706
  headerRow: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
23283
23707
  firstRowStripe: { style: { fillColor: colorSet.medium } },
23284
23708
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
@@ -23443,8 +23867,8 @@ stores.inject(MyMetaStore, storeInstance);
23443
23867
  let last;
23444
23868
  const activesRows = env.model.getters.getActiveRows();
23445
23869
  if (activesRows.size !== 0) {
23446
- first = Math.min(...activesRows);
23447
- last = Math.max(...activesRows);
23870
+ first = largeMin([...activesRows]);
23871
+ last = largeMax([...activesRows]);
23448
23872
  }
23449
23873
  else {
23450
23874
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23472,8 +23896,8 @@ stores.inject(MyMetaStore, storeInstance);
23472
23896
  let last;
23473
23897
  const activeCols = env.model.getters.getActiveCols();
23474
23898
  if (activeCols.size !== 0) {
23475
- first = Math.min(...activeCols);
23476
- last = Math.max(...activeCols);
23899
+ first = largeMin([...activeCols]);
23900
+ last = largeMax([...activeCols]);
23477
23901
  }
23478
23902
  else {
23479
23903
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23501,8 +23925,8 @@ stores.inject(MyMetaStore, storeInstance);
23501
23925
  let last;
23502
23926
  const activesRows = env.model.getters.getActiveRows();
23503
23927
  if (activesRows.size !== 0) {
23504
- first = Math.min(...activesRows);
23505
- last = Math.max(...activesRows);
23928
+ first = largeMin([...activesRows]);
23929
+ last = largeMax([...activesRows]);
23506
23930
  }
23507
23931
  else {
23508
23932
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23543,8 +23967,8 @@ stores.inject(MyMetaStore, storeInstance);
23543
23967
  let last;
23544
23968
  const activeCols = env.model.getters.getActiveCols();
23545
23969
  if (activeCols.size !== 0) {
23546
- first = Math.min(...activeCols);
23547
- last = Math.max(...activeCols);
23970
+ first = largeMin([...activeCols]);
23971
+ last = largeMax([...activeCols]);
23548
23972
  }
23549
23973
  else {
23550
23974
  const zone = env.model.getters.getSelectedZones()[0];
@@ -23585,7 +24009,7 @@ stores.inject(MyMetaStore, storeInstance);
23585
24009
  let row;
23586
24010
  let quantity;
23587
24011
  if (activeRows.size) {
23588
- row = Math.min(...activeRows);
24012
+ row = largeMin([...activeRows]);
23589
24013
  quantity = activeRows.size;
23590
24014
  }
23591
24015
  else {
@@ -23606,7 +24030,7 @@ stores.inject(MyMetaStore, storeInstance);
23606
24030
  let row;
23607
24031
  let quantity;
23608
24032
  if (activeRows.size) {
23609
- row = Math.max(...activeRows);
24033
+ row = largeMax([...activeRows]);
23610
24034
  quantity = activeRows.size;
23611
24035
  }
23612
24036
  else {
@@ -23627,7 +24051,7 @@ stores.inject(MyMetaStore, storeInstance);
23627
24051
  let column;
23628
24052
  let quantity;
23629
24053
  if (activeCols.size) {
23630
- column = Math.min(...activeCols);
24054
+ column = largeMin([...activeCols]);
23631
24055
  quantity = activeCols.size;
23632
24056
  }
23633
24057
  else {
@@ -23648,7 +24072,7 @@ stores.inject(MyMetaStore, storeInstance);
23648
24072
  let column;
23649
24073
  let quantity;
23650
24074
  if (activeCols.size) {
23651
- column = Math.max(...activeCols);
24075
+ column = largeMax([...activeCols]);
23652
24076
  quantity = activeCols.size;
23653
24077
  }
23654
24078
  else {
@@ -27194,13 +27618,30 @@ stores.inject(MyMetaStore, storeInstance);
27194
27618
  }
27195
27619
  }
27196
27620
 
27197
- class ChartColor extends owl.Component {
27198
- static template = "o-spreadsheet.ChartColor";
27199
- static components = { ColorPickerWidget, Section };
27621
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
27622
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
27623
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
27624
+ </svg>
27625
+ `;
27626
+ css /* scss */ `
27627
+ .o-round-color-picker-button {
27628
+ width: 15px;
27629
+ height: 15px;
27630
+ cursor: pointer;
27631
+ border: 1px solid #aaa;
27632
+ background-position: 1px 1px;
27633
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
27634
+ }
27635
+ `;
27636
+ class RoundColorPicker extends owl.Component {
27637
+ static template = "o-spreadsheet.RoundColorPicker";
27638
+ static components = { ColorPickerWidget, Section, ColorPicker };
27200
27639
  static props = {
27201
27640
  currentColor: { type: String, optional: true },
27641
+ title: { type: String, optional: true },
27202
27642
  onColorPicked: Function,
27203
27643
  };
27644
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
27204
27645
  state;
27205
27646
  setup() {
27206
27647
  this.state = owl.useState({ pickerOpened: false });
@@ -27212,6 +27653,19 @@ stores.inject(MyMetaStore, storeInstance);
27212
27653
  togglePicker() {
27213
27654
  this.state.pickerOpened = !this.state.pickerOpened;
27214
27655
  }
27656
+ onColorPicked(color) {
27657
+ this.props.onColorPicked(color);
27658
+ this.state.pickerOpened = false;
27659
+ }
27660
+ get colorPickerAnchorRect() {
27661
+ const button = this.colorPickerButtonRef.el;
27662
+ return getBoundingRectAsPOJO(button);
27663
+ }
27664
+ get buttonStyle() {
27665
+ return cssPropertiesToCss({
27666
+ background: this.props.currentColor,
27667
+ });
27668
+ }
27215
27669
  }
27216
27670
 
27217
27671
  class ChartTitle extends owl.Component {
@@ -27225,7 +27679,7 @@ stores.inject(MyMetaStore, storeInstance);
27225
27679
 
27226
27680
  class LineBarPieDesignPanel extends owl.Component {
27227
27681
  static template = "o-spreadsheet-LineBarPieDesignPanel";
27228
- static components = { ChartColor, ChartTitle, Section };
27682
+ static components = { RoundColorPicker, ChartTitle, Section };
27229
27683
  static props = {
27230
27684
  figureId: String,
27231
27685
  definition: Object,
@@ -27248,12 +27702,31 @@ stores.inject(MyMetaStore, storeInstance);
27248
27702
  [attr]: ev.target.value,
27249
27703
  });
27250
27704
  }
27705
+ get backgroundColorTitle() {
27706
+ return ChartTerms.BackgroundColor;
27707
+ }
27251
27708
  }
27252
27709
 
27253
27710
  class BarChartDesignPanel extends LineBarPieDesignPanel {
27254
27711
  static template = "o-spreadsheet-BarChartDesignPanel";
27255
27712
  }
27256
27713
 
27714
+ class ComboChartConfigPanel extends LineBarPieConfigPanel {
27715
+ static template = "o-spreadsheet-ComboChartConfigPanel";
27716
+ get shouldUseRightAxis() {
27717
+ return _t("Use right axis for line series");
27718
+ }
27719
+ onUpdateUseRightAxis(useBothYAxis) {
27720
+ this.props.updateChart(this.props.figureId, {
27721
+ useBothYAxis,
27722
+ });
27723
+ }
27724
+ }
27725
+
27726
+ class ComboChartDesignPanel extends LineBarPieDesignPanel {
27727
+ static template = "o-spreadsheet-ComboChartDesignPanel";
27728
+ }
27729
+
27257
27730
  class GaugeChartConfigPanel extends owl.Component {
27258
27731
  static template = "o-spreadsheet-GaugeChartConfigPanel";
27259
27732
  static components = { ChartErrorSection, ChartDataSeries };
@@ -27301,6 +27774,10 @@ stores.inject(MyMetaStore, storeInstance);
27301
27774
  line-height: 18px;
27302
27775
  width: 100%;
27303
27776
  }
27777
+ td {
27778
+ box-sizing: border-box;
27779
+ height: 30px;
27780
+ }
27304
27781
  th.o-gauge-color-set-colorPicker {
27305
27782
  width: 8%;
27306
27783
  }
@@ -27323,7 +27800,12 @@ stores.inject(MyMetaStore, storeInstance);
27323
27800
  `;
27324
27801
  class GaugeChartDesignPanel extends owl.Component {
27325
27802
  static template = "o-spreadsheet-GaugeChartDesignPanel";
27326
- static components = { ColorPickerWidget, ChartErrorSection, ChartColor, ChartTitle, Section };
27803
+ static components = {
27804
+ ChartErrorSection,
27805
+ RoundColorPicker,
27806
+ ChartTitle,
27807
+ Section,
27808
+ };
27327
27809
  static props = {
27328
27810
  figureId: String,
27329
27811
  definition: Object,
@@ -27335,9 +27817,6 @@ stores.inject(MyMetaStore, storeInstance);
27335
27817
  sectionRuleDispatchResult: undefined,
27336
27818
  sectionRule: deepCopy(this.props.definition.sectionRule),
27337
27819
  });
27338
- setup() {
27339
- owl.useExternalListener(window, "click", this.closeMenus);
27340
- }
27341
27820
  get title() {
27342
27821
  return _t(this.props.definition.title);
27343
27822
  }
@@ -27378,27 +27857,22 @@ stores.inject(MyMetaStore, storeInstance);
27378
27857
  const sectionRule = deepCopy(this.state.sectionRule);
27379
27858
  sectionRule.colors[target] = color;
27380
27859
  this.updateSectionRule(sectionRule);
27381
- this.closeMenus();
27382
- }
27383
- toggleMenu(menu) {
27384
- const isSelected = this.state.openedMenu === menu;
27385
- this.closeMenus();
27386
- if (!isSelected) {
27387
- this.state.openedMenu = menu;
27388
- }
27389
27860
  }
27390
27861
  updateSectionRule(sectionRule) {
27391
27862
  this.state.sectionRuleDispatchResult = this.props.updateChart(this.props.figureId, {
27392
27863
  sectionRule,
27393
27864
  });
27865
+ if (this.state.sectionRuleDispatchResult.isSuccessful) {
27866
+ this.state.sectionRule = deepCopy(sectionRule);
27867
+ }
27394
27868
  }
27395
27869
  canUpdateSectionRule(sectionRule) {
27396
27870
  this.state.sectionRuleDispatchResult = this.props.canUpdateChart(this.props.figureId, {
27397
27871
  sectionRule,
27398
27872
  });
27399
27873
  }
27400
- closeMenus() {
27401
- this.state.openedMenu = undefined;
27874
+ get backgroundColorTitle() {
27875
+ return ChartTerms.BackgroundColor;
27402
27876
  }
27403
27877
  }
27404
27878
 
@@ -27546,39 +28020,36 @@ stores.inject(MyMetaStore, storeInstance);
27546
28020
 
27547
28021
  class ScorecardChartDesignPanel extends owl.Component {
27548
28022
  static template = "o-spreadsheet-ScorecardChartDesignPanel";
27549
- static components = { ColorPickerWidget, ChartColor, ChartTitle, Section };
28023
+ static components = { RoundColorPicker, ChartTitle, Section, Checkbox };
27550
28024
  static props = {
27551
28025
  figureId: String,
27552
28026
  definition: Object,
27553
28027
  updateChart: Function,
27554
28028
  canUpdateChart: Function,
27555
28029
  };
27556
- state = owl.useState({
27557
- openedColorPicker: undefined,
27558
- });
27559
- setup() {
27560
- owl.useExternalListener(window, "click", this.closeMenus);
27561
- }
27562
28030
  get title() {
27563
28031
  return _t(this.props.definition.title);
27564
28032
  }
28033
+ get colorsSectionTitle() {
28034
+ return this.props.definition.baselineMode === "progress"
28035
+ ? _t("Progress bar colors")
28036
+ : _t("Baseline colors");
28037
+ }
28038
+ get humanizeNumbersLabel() {
28039
+ return _t("Humanize numbers");
28040
+ }
27565
28041
  updateTitle(title) {
27566
28042
  this.props.updateChart(this.props.figureId, { title });
27567
28043
  }
28044
+ updateHumanizeNumbers(humanize) {
28045
+ this.props.updateChart(this.props.figureId, { humanize });
28046
+ }
27568
28047
  translate(term) {
27569
28048
  return _t(term);
27570
28049
  }
27571
28050
  updateBaselineDescr(ev) {
27572
28051
  this.props.updateChart(this.props.figureId, { baselineDescr: ev.target.value });
27573
28052
  }
27574
- toggleColorPicker(colorPickerId) {
27575
- if (this.state.openedColorPicker === colorPickerId) {
27576
- this.state.openedColorPicker = undefined;
27577
- }
27578
- else {
27579
- this.state.openedColorPicker = colorPickerId;
27580
- }
27581
- }
27582
28053
  setColor(color, colorPickerId) {
27583
28054
  switch (colorPickerId) {
27584
28055
  case "backgroundColor":
@@ -27591,10 +28062,9 @@ stores.inject(MyMetaStore, storeInstance);
27591
28062
  this.props.updateChart(this.props.figureId, { baselineColorUp: color });
27592
28063
  break;
27593
28064
  }
27594
- this.closeMenus();
27595
28065
  }
27596
- closeMenus() {
27597
- this.state.openedColorPicker = undefined;
28066
+ get backgroundColorTitle() {
28067
+ return ChartTerms.BackgroundColor;
27598
28068
  }
27599
28069
  }
27600
28070
 
@@ -27611,6 +28081,10 @@ stores.inject(MyMetaStore, storeInstance);
27611
28081
  .add("bar", {
27612
28082
  configuration: BarConfigPanel,
27613
28083
  design: BarChartDesignPanel,
28084
+ })
28085
+ .add("combo", {
28086
+ configuration: ComboChartConfigPanel,
28087
+ design: ComboChartDesignPanel,
27614
28088
  })
27615
28089
  .add("pie", {
27616
28090
  configuration: LineBarPieConfigPanel,
@@ -28539,6 +29013,7 @@ stores.inject(MyMetaStore, storeInstance);
28539
29013
  ColorPickerWidget,
28540
29014
  ConditionalFormatPreviewList,
28541
29015
  Section,
29016
+ RoundColorPicker,
28542
29017
  };
28543
29018
  icons = ICONS;
28544
29019
  cellIsOperators = CellIsOperators;
@@ -30251,7 +30726,11 @@ stores.inject(MyMetaStore, storeInstance);
30251
30726
  const composerStore = useStore(ComposerStore);
30252
30727
  // The feature makes no sense if we are editing a cell, because then the selection isn't active
30253
30728
  // Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
30254
- owl.useEffect(this.props.onCloseSidePanel, () => [composerStore.editionMode]);
30729
+ owl.useEffect((editionMode) => {
30730
+ if (editionMode !== "inactive") {
30731
+ this.props.onCloseSidePanel();
30732
+ }
30733
+ }, () => [composerStore.editionMode]);
30255
30734
  owl.onMounted(() => {
30256
30735
  composerStore.stopEdition();
30257
30736
  });
@@ -31960,6 +32439,9 @@ stores.inject(MyMetaStore, storeInstance);
31960
32439
  this.assistantState.allowCellSelectionBehind = false;
31961
32440
  }, 2000);
31962
32441
  }
32442
+ get formulaArgSeparator() {
32443
+ return this.env.model.getters.getLocale().formulaArgSeparator + " ";
32444
+ }
31963
32445
  }
31964
32446
 
31965
32447
  const functions$2 = functionRegistry.content;
@@ -32036,6 +32518,7 @@ stores.inject(MyMetaStore, storeInstance);
32036
32518
  onComposerCellFocused: { type: Function, optional: true },
32037
32519
  onComposerContentFocused: Function,
32038
32520
  isDefaultFocus: { type: Boolean, optional: true },
32521
+ onInputContextMenu: { type: Function, optional: true },
32039
32522
  };
32040
32523
  static components = { TextValueProvider, FunctionDescriptionProvider };
32041
32524
  static defaultProps = {
@@ -32086,6 +32569,9 @@ stores.inject(MyMetaStore, storeInstance);
32086
32569
  assistantStyle.right = `0px`;
32087
32570
  }
32088
32571
  }
32572
+ else if (this.props.delimitation) {
32573
+ assistantStyle["max-height"] = `${this.props.delimitation.height}px`;
32574
+ }
32089
32575
  return cssPropertiesToCss(assistantStyle);
32090
32576
  }
32091
32577
  // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
@@ -32119,6 +32605,12 @@ stores.inject(MyMetaStore, storeInstance);
32119
32605
  owl.useEffect(() => {
32120
32606
  this.processContent();
32121
32607
  });
32608
+ owl.onPatched(() => {
32609
+ // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
32610
+ if (this.composerStore.editionMode === "inactive") {
32611
+ this.processTokenAtCursor();
32612
+ }
32613
+ });
32122
32614
  }
32123
32615
  // ---------------------------------------------------------------------------
32124
32616
  // Handlers
@@ -32366,6 +32858,11 @@ stores.inject(MyMetaStore, storeInstance);
32366
32858
  }
32367
32859
  }
32368
32860
  }
32861
+ onContextMenu(ev) {
32862
+ if (this.composerStore.editionMode === "inactive") {
32863
+ this.props.onInputContextMenu?.(ev);
32864
+ }
32865
+ }
32369
32866
  // ---------------------------------------------------------------------------
32370
32867
  // Private
32371
32868
  // ---------------------------------------------------------------------------
@@ -32587,6 +33084,7 @@ stores.inject(MyMetaStore, storeInstance);
32587
33084
  static template = "o-spreadsheet-GridComposer";
32588
33085
  static props = {
32589
33086
  gridDims: Object,
33087
+ onInputContextMenu: Function,
32590
33088
  };
32591
33089
  static components = { Composer };
32592
33090
  rect = this.defaultRect;
@@ -32634,6 +33132,7 @@ stores.inject(MyMetaStore, storeInstance);
32634
33132
  isDefaultFocus: true,
32635
33133
  onComposerContentFocused: () => this.composerFocusStore.focusGridComposerContent(),
32636
33134
  onComposerCellFocused: (content) => this.composerFocusStore.focusGridComposerCell(content),
33135
+ onInputContextMenu: this.props.onInputContextMenu,
32637
33136
  };
32638
33137
  }
32639
33138
  get containerStyle() {
@@ -32732,7 +33231,6 @@ stores.inject(MyMetaStore, storeInstance);
32732
33231
  cellPosition: Object,
32733
33232
  horizontalAlign: { type: String, optional: true },
32734
33233
  verticalAlign: { type: String, optional: true },
32735
- offset: { type: Object, optional: true },
32736
33234
  slots: Object,
32737
33235
  };
32738
33236
  get iconStyle() {
@@ -32743,8 +33241,8 @@ stores.inject(MyMetaStore, storeInstance);
32743
33241
  const x = this.getIconHorizontalPosition(rect, cellPosition);
32744
33242
  const y = this.getIconVerticalPosition(rect, cellPosition);
32745
33243
  return cssPropertiesToCss({
32746
- top: `${y + (this.props.offset?.y || 0)}px`,
32747
- left: `${x + (this.props.offset?.x || 0)}px`,
33244
+ top: `${y}px`,
33245
+ left: `${x}px`,
32748
33246
  });
32749
33247
  }
32750
33248
  getIconVerticalPosition(rect, cellPosition) {
@@ -32784,83 +33282,6 @@ stores.inject(MyMetaStore, storeInstance);
32784
33282
  }
32785
33283
  }
32786
33284
 
32787
- css /* scss */ `
32788
- .o-filter-icon {
32789
- color: ${FILTERS_COLOR};
32790
- display: flex;
32791
- align-items: center;
32792
- justify-content: center;
32793
- width: ${GRID_ICON_EDGE_LENGTH}px;
32794
- height: ${GRID_ICON_EDGE_LENGTH}px;
32795
-
32796
- &:hover {
32797
- background: ${FILTERS_COLOR};
32798
- color: #fff;
32799
- }
32800
-
32801
- &.o-high-contrast {
32802
- color: #defade;
32803
- }
32804
- &.o-high-contrast:hover {
32805
- color: ${FILTERS_COLOR};
32806
- background: #fff;
32807
- }
32808
- }
32809
- .o-filter-icon:hover {
32810
- background: ${FILTERS_COLOR};
32811
- color: #fff;
32812
- }
32813
- `;
32814
- class FilterIcon extends owl.Component {
32815
- static template = "o-spreadsheet-FilterIcon";
32816
- static props = {
32817
- cellPosition: Object,
32818
- };
32819
- cellPopovers;
32820
- setup() {
32821
- this.cellPopovers = useStore(CellPopoverStore);
32822
- }
32823
- onClick() {
32824
- const position = this.props.cellPosition;
32825
- const activePopover = this.cellPopovers.persistentCellPopover;
32826
- const { col, row } = position;
32827
- if (activePopover.isOpen &&
32828
- activePopover.col === col &&
32829
- activePopover.row === row &&
32830
- activePopover.type === "FilterMenu") {
32831
- this.cellPopovers.close();
32832
- return;
32833
- }
32834
- this.cellPopovers.open({ col, row }, "FilterMenu");
32835
- }
32836
- get isFilterActive() {
32837
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
32838
- }
32839
- get iconClass() {
32840
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
32841
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
32842
- return luminance < 0.45 ? "o-high-contrast" : "";
32843
- }
32844
- }
32845
-
32846
- class FilterIconsOverlay extends owl.Component {
32847
- static template = "o-spreadsheet-FilterIconsOverlay";
32848
- static props = {
32849
- gridPosition: { type: Object, optional: true },
32850
- };
32851
- static components = {
32852
- GridCellIcon,
32853
- FilterIcon,
32854
- };
32855
- static defaultProps = {
32856
- gridPosition: { x: 0, y: 0 },
32857
- };
32858
- getFilterHeadersPositions() {
32859
- const sheetId = this.env.model.getters.getActiveSheetId();
32860
- return this.env.model.getters.getFilterHeaders(sheetId);
32861
- }
32862
- }
32863
-
32864
33285
  const CHECKBOX_WIDTH = 15;
32865
33286
  const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
32866
33287
  css /* scss */ `
@@ -33528,6 +33949,80 @@ stores.inject(MyMetaStore, storeInstance);
33528
33949
  }
33529
33950
  }
33530
33951
 
33952
+ css /* scss */ `
33953
+ .o-filter-icon {
33954
+ color: ${FILTERS_COLOR};
33955
+ display: flex;
33956
+ align-items: center;
33957
+ justify-content: center;
33958
+ width: ${GRID_ICON_EDGE_LENGTH}px;
33959
+ height: ${GRID_ICON_EDGE_LENGTH}px;
33960
+
33961
+ &:hover {
33962
+ background: ${FILTERS_COLOR};
33963
+ color: #fff;
33964
+ }
33965
+
33966
+ &.o-high-contrast {
33967
+ color: #defade;
33968
+ }
33969
+ &.o-high-contrast:hover {
33970
+ color: ${FILTERS_COLOR};
33971
+ background: #fff;
33972
+ }
33973
+ }
33974
+ .o-filter-icon:hover {
33975
+ background: ${FILTERS_COLOR};
33976
+ color: #fff;
33977
+ }
33978
+ `;
33979
+ class FilterIcon extends owl.Component {
33980
+ static template = "o-spreadsheet-FilterIcon";
33981
+ static props = {
33982
+ cellPosition: Object,
33983
+ };
33984
+ cellPopovers;
33985
+ setup() {
33986
+ this.cellPopovers = useStore(CellPopoverStore);
33987
+ }
33988
+ onClick() {
33989
+ const position = this.props.cellPosition;
33990
+ const activePopover = this.cellPopovers.persistentCellPopover;
33991
+ const { col, row } = position;
33992
+ if (activePopover.isOpen &&
33993
+ activePopover.col === col &&
33994
+ activePopover.row === row &&
33995
+ activePopover.type === "FilterMenu") {
33996
+ this.cellPopovers.close();
33997
+ return;
33998
+ }
33999
+ this.cellPopovers.open({ col, row }, "FilterMenu");
34000
+ }
34001
+ get isFilterActive() {
34002
+ return this.env.model.getters.isFilterActive(this.props.cellPosition);
34003
+ }
34004
+ get iconClass() {
34005
+ const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
34006
+ const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
34007
+ return luminance < 0.45 ? "o-high-contrast" : "";
34008
+ }
34009
+ }
34010
+
34011
+ class FilterIconsOverlay extends owl.Component {
34012
+ static template = "o-spreadsheet-FilterIconsOverlay";
34013
+ static props = {
34014
+ onMouseDown: Function,
34015
+ };
34016
+ static components = {
34017
+ GridCellIcon,
34018
+ FilterIcon,
34019
+ };
34020
+ getFilterHeadersPositions() {
34021
+ const sheetId = this.env.model.getters.getActiveSheetId();
34022
+ return this.env.model.getters.getFilterHeaders(sheetId);
34023
+ }
34024
+ }
34025
+
33531
34026
  css /* scss */ `
33532
34027
  .o-grid-add-rows {
33533
34028
  input {
@@ -33741,7 +34236,12 @@ stores.inject(MyMetaStore, storeInstance);
33741
34236
  onGridMoved: Function,
33742
34237
  gridOverlayDimensions: String,
33743
34238
  };
33744
- static components = { FiguresContainer, DataValidationOverlay, GridAddRowsFooter };
34239
+ static components = {
34240
+ FiguresContainer,
34241
+ DataValidationOverlay,
34242
+ GridAddRowsFooter,
34243
+ FilterIconsOverlay,
34244
+ };
33745
34245
  static defaultProps = {
33746
34246
  onCellHovered: () => { },
33747
34247
  onCellDoubleClicked: () => { },
@@ -33786,7 +34286,7 @@ stores.inject(MyMetaStore, storeInstance);
33786
34286
  get isPaintingFormat() {
33787
34287
  return this.env.model.getters.isPaintingFormat();
33788
34288
  }
33789
- onMouseDown(ev) {
34289
+ onMouseDown(ev, modifiers) {
33790
34290
  if (ev.button > 0) {
33791
34291
  // not main button, probably a context menu
33792
34292
  return;
@@ -33795,6 +34295,7 @@ stores.inject(MyMetaStore, storeInstance);
33795
34295
  this.props.onCellClicked(col, row, {
33796
34296
  expandZone: ev.shiftKey,
33797
34297
  addZone: isCtrlKey(ev),
34298
+ closePopover: modifiers?.closePopover ?? true,
33798
34299
  });
33799
34300
  }
33800
34301
  onDoubleClick(ev) {
@@ -34098,11 +34599,6 @@ stores.inject(MyMetaStore, storeInstance);
34098
34599
  height: 10000px;
34099
34600
  background-color: ${SELECTION_BORDER_COLOR};
34100
34601
  }
34101
- .o-unhide-buttons {
34102
- width: fit-content;
34103
- gap: 5px;
34104
- transform: translate(-50%, 0);
34105
- }
34106
34602
  .o-unhide:hover {
34107
34603
  z-index: ${ComponentsImportance.Grid + 1};
34108
34604
  background-color: lightgrey;
@@ -34264,10 +34760,6 @@ stores.inject(MyMetaStore, storeInstance);
34264
34760
  height: 1px;
34265
34761
  background-color: ${SELECTION_BORDER_COLOR};
34266
34762
  }
34267
- .o-unhide-buttons {
34268
- height: fit-content;
34269
- transform: translate(0, -50%);
34270
- }
34271
34763
  .o-unhide:hover {
34272
34764
  z-index: ${ComponentsImportance.Grid + 1};
34273
34765
  background-color: lightgrey;
@@ -35475,7 +35967,7 @@ stores.inject(MyMetaStore, storeInstance);
35475
35967
  onScroll(offset) {
35476
35968
  const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();
35477
35969
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
35478
- offsetX: scrollX,
35970
+ offsetX: scrollX, // offsetX is the same
35479
35971
  offsetY: offset,
35480
35972
  });
35481
35973
  }
@@ -35569,7 +36061,6 @@ stores.inject(MyMetaStore, storeInstance);
35569
36061
  Popover,
35570
36062
  VerticalScrollBar,
35571
36063
  HorizontalScrollBar,
35572
- FilterIconsOverlay,
35573
36064
  };
35574
36065
  HEADER_HEIGHT = HEADER_HEIGHT;
35575
36066
  HEADER_WIDTH = HEADER_WIDTH;
@@ -35763,8 +36254,8 @@ stores.inject(MyMetaStore, storeInstance);
35763
36254
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
35764
36255
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
35765
36256
  "Ctrl+Shift+V": () => PASTE_AS_VALUE_ACTION(this.env),
35766
- "Ctrl+Shift+<": () => this.clearFormatting(),
35767
- "Ctrl+<": () => this.clearFormatting(),
36257
+ "Ctrl+Shift+<": () => this.clearFormatting(), // for qwerty
36258
+ "Ctrl+<": () => this.clearFormatting(), // for azerty
35768
36259
  "Ctrl+Shift+ ": () => {
35769
36260
  this.env.model.selection.selectAll();
35770
36261
  },
@@ -35870,17 +36361,17 @@ stores.inject(MyMetaStore, storeInstance);
35870
36361
  // ---------------------------------------------------------------------------
35871
36362
  // Zone selection with mouse
35872
36363
  // ---------------------------------------------------------------------------
35873
- onCellClicked(col, row, { addZone, expandZone }) {
35874
- if (this.cellPopovers.isOpen) {
36364
+ onCellClicked(col, row, modifiers) {
36365
+ if (modifiers.closePopover && this.cellPopovers.isOpen) {
35875
36366
  this.cellPopovers.close();
35876
36367
  }
35877
36368
  if (this.composerStore.editionMode === "editing") {
35878
36369
  this.composerStore.stopEdition();
35879
36370
  }
35880
- if (expandZone) {
36371
+ if (modifiers.expandZone) {
35881
36372
  this.env.model.selection.setAnchorCorner(col, row);
35882
36373
  }
35883
- else if (addZone) {
36374
+ else if (modifiers.addZone) {
35884
36375
  this.env.model.selection.addCellToSelection(col, row);
35885
36376
  }
35886
36377
  else {
@@ -36200,6 +36691,7 @@ stores.inject(MyMetaStore, storeInstance);
36200
36691
  "surfaceChart",
36201
36692
  "surface3DChart",
36202
36693
  "bubbleChart",
36694
+ "comboChart",
36203
36695
  ];
36204
36696
 
36205
36697
  /** In XLSX color format (no #) */
@@ -36531,10 +37023,10 @@ stores.inject(MyMetaStore, storeInstance);
36531
37023
  const CF_TYPE_CONVERSION_MAP = {
36532
37024
  aboveAverage: undefined,
36533
37025
  expression: undefined,
36534
- cellIs: undefined,
36535
- colorScale: undefined,
37026
+ cellIs: undefined, // exist but isn't an operator in o_spreadsheet
37027
+ colorScale: undefined, // exist but isn't an operator in o_spreadsheet
36536
37028
  dataBar: undefined,
36537
- iconSet: undefined,
37029
+ iconSet: undefined, // exist but isn't an operator in o_spreadsheet
36538
37030
  top10: undefined,
36539
37031
  uniqueValues: undefined,
36540
37032
  duplicateValues: undefined,
@@ -36601,7 +37093,7 @@ stores.inject(MyMetaStore, storeInstance);
36601
37093
  line3DChart: undefined,
36602
37094
  stockChart: undefined,
36603
37095
  radarChart: undefined,
36604
- scatterChart: undefined,
37096
+ scatterChart: "scatter",
36605
37097
  pieChart: "pie",
36606
37098
  pie3DChart: undefined,
36607
37099
  doughnutChart: "pie",
@@ -36611,6 +37103,7 @@ stores.inject(MyMetaStore, storeInstance);
36611
37103
  surfaceChart: undefined,
36612
37104
  surface3DChart: undefined,
36613
37105
  bubbleChart: undefined,
37106
+ comboChart: "combo",
36614
37107
  };
36615
37108
  /** Conversion map for the SUBTOTAL(index, formula) function in xlsx, index <=> actual function*/
36616
37109
  const SUBTOTAL_FUNCTION_CONVERSION_MAP = {
@@ -36769,7 +37262,7 @@ stores.inject(MyMetaStore, storeInstance);
36769
37262
  61: "993366",
36770
37263
  62: "333399",
36771
37264
  63: "333333",
36772
- 64: "000000",
37265
+ 64: "000000", // system foreground
36773
37266
  65: "FFFFFF", // system background
36774
37267
  };
36775
37268
  const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
@@ -37364,29 +37857,12 @@ stores.inject(MyMetaStore, storeInstance);
37364
37857
  return width;
37365
37858
  return Math.round((width / WIDTH_FACTOR) * 100) / 100;
37366
37859
  }
37367
- function convertBorderDescr(descr) {
37368
- if (!descr) {
37369
- return undefined;
37370
- }
37371
- return {
37372
- style: descr.style,
37373
- color: { rgb: descr.color },
37374
- };
37375
- }
37376
37860
  function extractStyle(cell, data) {
37377
37861
  let style = {};
37378
37862
  if (cell.style) {
37379
37863
  style = data.styles[cell.style];
37380
37864
  }
37381
37865
  const format = extractFormat(cell, data);
37382
- const exportedBorder = {};
37383
- if (cell.border) {
37384
- const border = data.borders[cell.border];
37385
- exportedBorder.left = convertBorderDescr(border.left);
37386
- exportedBorder.right = convertBorderDescr(border.right);
37387
- exportedBorder.bottom = convertBorderDescr(border.bottom);
37388
- exportedBorder.top = convertBorderDescr(border.top);
37389
- }
37390
37866
  const styles = {
37391
37867
  font: {
37392
37868
  size: style?.fontSize || DEFAULT_FONT_SIZE,
@@ -37400,7 +37876,7 @@ stores.inject(MyMetaStore, storeInstance);
37400
37876
  }
37401
37877
  : { reservedAttribute: "none" },
37402
37878
  numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
37403
- border: exportedBorder || {},
37879
+ border: cell.border || 0,
37404
37880
  alignment: {
37405
37881
  horizontal: style.align,
37406
37882
  vertical: style.verticalAlign
@@ -37422,15 +37898,12 @@ stores.inject(MyMetaStore, storeInstance);
37422
37898
  return undefined;
37423
37899
  }
37424
37900
  function normalizeStyle(construct, styles) {
37425
- const { id: fontId } = pushElement(styles["font"], construct.fonts);
37426
- const { id: fillId } = pushElement(styles["fill"], construct.fills);
37427
- const { id: borderId } = pushElement(styles["border"], construct.borders);
37428
37901
  // Normalize this
37429
37902
  const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
37430
37903
  const style = {
37431
- fontId,
37432
- fillId,
37433
- borderId,
37904
+ fontId: pushElement(styles.font, construct.fonts),
37905
+ fillId: pushElement(styles.fill, construct.fills),
37906
+ borderId: styles.border,
37434
37907
  numFmtId,
37435
37908
  alignment: {
37436
37909
  vertical: styles.alignment.vertical,
@@ -37438,8 +37911,7 @@ stores.inject(MyMetaStore, storeInstance);
37438
37911
  wrapText: styles.alignment.wrapText,
37439
37912
  },
37440
37913
  };
37441
- const { id } = pushElement(style, construct.styles);
37442
- return id;
37914
+ return pushElement(style, construct.styles);
37443
37915
  }
37444
37916
  function convertFormat(format, numFmtStructure) {
37445
37917
  if (!format) {
@@ -37447,8 +37919,7 @@ stores.inject(MyMetaStore, storeInstance);
37447
37919
  }
37448
37920
  let formatId = XLSX_FORMAT_MAP[format.format];
37449
37921
  if (!formatId) {
37450
- const { id } = pushElement(format, numFmtStructure);
37451
- formatId = id + FIRST_NUMFMT_ID;
37922
+ formatId = pushElement(format, numFmtStructure) + FIRST_NUMFMT_ID;
37452
37923
  }
37453
37924
  return formatId;
37454
37925
  }
@@ -37473,20 +37944,15 @@ stores.inject(MyMetaStore, storeInstance);
37473
37944
  return id;
37474
37945
  }
37475
37946
  function pushElement(property, propertyList) {
37476
- for (let [key, value] of Object.entries(propertyList)) {
37477
- if (JSON.stringify(value) === JSON.stringify(property)) {
37478
- return { id: parseInt(key, 10), list: propertyList };
37947
+ let len = propertyList.length;
37948
+ const operator = typeof property === "object" ? deepEquals : (a, b) => a === b;
37949
+ for (let i = 0; i < len; i++) {
37950
+ if (operator(property, propertyList[i])) {
37951
+ return i;
37479
37952
  }
37480
37953
  }
37481
- let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));
37482
- if (elemId === -1) {
37483
- propertyList.push(property);
37484
- elemId = propertyList.length - 1;
37485
- }
37486
- return {
37487
- id: elemId,
37488
- list: propertyList,
37489
- };
37954
+ propertyList[propertyList.length] = property;
37955
+ return propertyList.length - 1;
37490
37956
  }
37491
37957
  const chartIds = [];
37492
37958
  /**
@@ -37961,7 +38427,7 @@ stores.inject(MyMetaStore, storeInstance);
37961
38427
  function getSheetDims(sheet) {
37962
38428
  const dims = [0, 0];
37963
38429
  for (let row of sheet.rows) {
37964
- dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));
38430
+ dims[0] = Math.max(dims[0], largeMax(row.cells.map((cell) => toCartesian(cell.xc).col)));
37965
38431
  dims[1] = Math.max(dims[1], row.index);
37966
38432
  }
37967
38433
  dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);
@@ -38281,7 +38747,25 @@ stores.inject(MyMetaStore, storeInstance);
38281
38747
  }
38282
38748
  return document;
38283
38749
  }
38284
- function getDefaultXLSXStructure() {
38750
+ function convertBorderDescr(descr) {
38751
+ if (!descr) {
38752
+ return undefined;
38753
+ }
38754
+ return {
38755
+ style: descr.style,
38756
+ color: { rgb: descr.color },
38757
+ };
38758
+ }
38759
+ function getDefaultXLSXStructure(data) {
38760
+ const xlsxBorders = Object.values(data.borders).map((border) => {
38761
+ return {
38762
+ left: convertBorderDescr(border.left),
38763
+ right: convertBorderDescr(border.right),
38764
+ bottom: convertBorderDescr(border.bottom),
38765
+ top: convertBorderDescr(border.top),
38766
+ };
38767
+ });
38768
+ const borders = [{}, ...xlsxBorders];
38285
38769
  return {
38286
38770
  relsFiles: [],
38287
38771
  sharedStrings: [],
@@ -38304,7 +38788,7 @@ stores.inject(MyMetaStore, storeInstance);
38304
38788
  },
38305
38789
  ],
38306
38790
  fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
38307
- borders: [{}],
38791
+ borders,
38308
38792
  numFmts: [],
38309
38793
  dxfs: [],
38310
38794
  };
@@ -38827,6 +39311,9 @@ stores.inject(MyMetaStore, storeInstance);
38827
39311
  if (!CHART_TYPE_CONVERSION_MAP[chartType]) {
38828
39312
  throw new Error(`Unsupported chart type ${chartType}`);
38829
39313
  }
39314
+ if (CHART_TYPE_CONVERSION_MAP[chartType] === "combo") {
39315
+ return this.extractComboChart(rootChartElement);
39316
+ }
38830
39317
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
38831
39318
  const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
38832
39319
  return textElement.textContent || "";
@@ -38837,8 +39324,8 @@ stores.inject(MyMetaStore, storeInstance);
38837
39324
  return {
38838
39325
  title: chartTitle,
38839
39326
  type: CHART_TYPE_CONVERSION_MAP[chartType],
38840
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
38841
- labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
39327
+ dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
39328
+ labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
38842
39329
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
38843
39330
  default: "ffffff",
38844
39331
  }).asString(),
@@ -38855,7 +39342,41 @@ stores.inject(MyMetaStore, storeInstance);
38855
39342
  };
38856
39343
  })[0];
38857
39344
  }
38858
- extractChartDatasets(chartElement) {
39345
+ extractComboChart(chartElement) {
39346
+ // Title can be separated into multiple xml elements (for styling and such), we only import the text
39347
+ const chartTitle = this.mapOnElements({ parent: chartElement, query: "c:title a:t" }, (textElement) => {
39348
+ return textElement.textContent || "";
39349
+ }).join("");
39350
+ const barChartGrouping = this.extractChildAttr(chartElement, "c:grouping", "val", {
39351
+ default: "clustered",
39352
+ }).asString();
39353
+ return {
39354
+ title: chartTitle,
39355
+ type: "combo",
39356
+ dataSets: [
39357
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
39358
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
39359
+ ],
39360
+ labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
39361
+ backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
39362
+ default: "ffffff",
39363
+ }).asString(),
39364
+ verticalAxisPosition: this.extractChildAttr(chartElement, "c:valAx > c:axPos", "val", {
39365
+ default: "l",
39366
+ }).asString() === "r"
39367
+ ? "right"
39368
+ : "left",
39369
+ legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(chartElement, "c:legendPos", "val", {
39370
+ default: "b",
39371
+ }).asString()],
39372
+ stacked: barChartGrouping === "stacked",
39373
+ fontColor: "000000",
39374
+ };
39375
+ }
39376
+ extractChartDatasets(chartElement, chartType) {
39377
+ if (chartType === "scatterChart") {
39378
+ return this.extractScatterChartDatasets(chartElement);
39379
+ }
38859
39380
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
38860
39381
  return {
38861
39382
  label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
@@ -38863,6 +39384,14 @@ stores.inject(MyMetaStore, storeInstance);
38863
39384
  };
38864
39385
  });
38865
39386
  }
39387
+ extractScatterChartDatasets(chartElement) {
39388
+ return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
39389
+ return {
39390
+ label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
39391
+ range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
39392
+ };
39393
+ });
39394
+ }
38866
39395
  /**
38867
39396
  * The chart type in the XML isn't explicitly defined, but there is an XML element that define the
38868
39397
  * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.
@@ -38872,12 +39401,21 @@ stores.inject(MyMetaStore, storeInstance);
38872
39401
  if (!plotAreaElement) {
38873
39402
  throw new Error("Missing plot area in the chart definition.");
38874
39403
  }
39404
+ let globalTag = undefined;
38875
39405
  for (let child of plotAreaElement.children) {
38876
39406
  const tag = removeTagEscapedNamespaces(child.tagName);
38877
39407
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
38878
- return tag;
39408
+ if (!globalTag) {
39409
+ globalTag = tag;
39410
+ }
39411
+ else if (globalTag !== tag) {
39412
+ globalTag = "comboChart";
39413
+ }
38879
39414
  }
38880
39415
  }
39416
+ if (globalTag) {
39417
+ return globalTag;
39418
+ }
38881
39419
  throw new Error("Unknown chart type");
38882
39420
  }
38883
39421
  }
@@ -40247,12 +40785,14 @@ stores.inject(MyMetaStore, storeInstance);
40247
40785
  static getters = [];
40248
40786
  history;
40249
40787
  dispatch;
40250
- constructor(stateObserver, dispatch) {
40788
+ canDispatch;
40789
+ constructor(stateObserver, dispatch, canDispatch) {
40251
40790
  this.history = Object.assign(Object.create(stateObserver), {
40252
40791
  update: stateObserver.addChange.bind(stateObserver, this),
40253
40792
  selectCell: () => { },
40254
40793
  });
40255
40794
  this.dispatch = dispatch;
40795
+ this.canDispatch = canDispatch;
40256
40796
  }
40257
40797
  /**
40258
40798
  * Export for excel should be available for all plugins, even for the UI.
@@ -40331,8 +40871,8 @@ stores.inject(MyMetaStore, storeInstance);
40331
40871
  class CorePlugin extends BasePlugin {
40332
40872
  getters;
40333
40873
  uuidGenerator;
40334
- constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
40335
- super(stateObserver, dispatch);
40874
+ constructor({ getters, stateObserver, range, dispatch, canDispatch, uuidGenerator, }) {
40875
+ super(stateObserver, dispatch, canDispatch);
40336
40876
  range.addRangeProvider(this.adaptRanges.bind(this));
40337
40877
  this.getters = getters;
40338
40878
  this.uuidGenerator = uuidGenerator;
@@ -42468,6 +43008,9 @@ stores.inject(MyMetaStore, storeInstance);
42468
43008
  if (newRule.criterion.type === "isBoolean") {
42469
43009
  this.setCenterStyleToBooleanCells(newRule);
42470
43010
  }
43011
+ else if (newRule.criterion.type === "isValueInList") {
43012
+ newRule.criterion.values = Array.from(new Set(newRule.criterion.values));
43013
+ }
42471
43014
  const adaptedRules = this.removeRangesFromRules(sheetId, newRule.ranges, rules);
42472
43015
  const ruleIndex = adaptedRules.findIndex((rule) => rule.id === newRule.id);
42473
43016
  if (ruleIndex !== -1) {
@@ -42864,7 +43407,7 @@ stores.inject(MyMetaStore, storeInstance);
42864
43407
  if (hiddenElements.size >= elements) {
42865
43408
  return "TooManyHiddenElements" /* CommandResult.TooManyHiddenElements */;
42866
43409
  }
42867
- else if (Math.min(...cmd.elements) < 0 || Math.max(...cmd.elements) > elements) {
43410
+ else if (largeMin(cmd.elements) < 0 || largeMax(cmd.elements) > elements) {
42868
43411
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
42869
43412
  }
42870
43413
  else {
@@ -43193,7 +43736,6 @@ stores.inject(MyMetaStore, storeInstance);
43193
43736
  "isInSameMerge",
43194
43737
  "isMergeHidden",
43195
43738
  "getMainCellPosition",
43196
- "getBottomLeftCell",
43197
43739
  "expandZone",
43198
43740
  "doesIntersectMerge",
43199
43741
  "doesColumnsHaveCommonMerges",
@@ -43375,13 +43917,6 @@ stores.inject(MyMetaStore, storeInstance);
43375
43917
  const mergeTopLeftPos = this.getMerge(position).topLeft;
43376
43918
  return { sheetId: position.sheetId, col: mergeTopLeftPos.col, row: mergeTopLeftPos.row };
43377
43919
  }
43378
- getBottomLeftCell(position) {
43379
- if (!this.isInMerge(position)) {
43380
- return position;
43381
- }
43382
- const { bottom, left } = this.getMerge(position);
43383
- return { sheetId: position.sheetId, col: left, row: bottom };
43384
- }
43385
43920
  isMergeHidden(sheetId, merge) {
43386
43921
  const hiddenColsGroups = this.getters.getHiddenColsGroups(sheetId);
43387
43922
  const hiddenRowsGroups = this.getters.getHiddenRowsGroups(sheetId);
@@ -43682,8 +44217,8 @@ stores.inject(MyMetaStore, storeInstance);
43682
44217
  let newRange = range;
43683
44218
  let changeType = "NONE";
43684
44219
  for (let group of groups) {
43685
- const min = Math.min(...group);
43686
- const max = Math.max(...group);
44220
+ const min = largeMin(group);
44221
+ const max = largeMax(group);
43687
44222
  if (range.zone[start] <= min && min <= range.zone[end]) {
43688
44223
  const toRemove = Math.min(range.zone[end], max) - min + 1;
43689
44224
  changeType = "RESIZE";
@@ -44065,7 +44600,6 @@ stores.inject(MyMetaStore, storeInstance);
44065
44600
  "getSheetIds",
44066
44601
  "getVisibleSheetIds",
44067
44602
  "isSheetVisible",
44068
- "getEvaluationSheets",
44069
44603
  "doesHeaderExist",
44070
44604
  "doesHeadersExist",
44071
44605
  "getCell",
@@ -44132,8 +44666,8 @@ stores.inject(MyMetaStore, storeInstance);
44132
44666
  }
44133
44667
  return "Success" /* CommandResult.Success */;
44134
44668
  case "REMOVE_COLUMNS_ROWS": {
44135
- const min = Math.min(...cmd.elements);
44136
- const max = Math.max(...cmd.elements);
44669
+ const min = largeMin(cmd.elements);
44670
+ const max = largeMax(cmd.elements);
44137
44671
  if (min < 0 || !this.doesHeaderExist(cmd.sheetId, cmd.dimension, max)) {
44138
44672
  return "InvalidHeaderIndex" /* CommandResult.InvalidHeaderIndex */;
44139
44673
  }
@@ -44324,9 +44858,6 @@ stores.inject(MyMetaStore, storeInstance);
44324
44858
  getVisibleSheetIds() {
44325
44859
  return this.orderedSheetIds.filter(this.isSheetVisible.bind(this));
44326
44860
  }
44327
- getEvaluationSheets() {
44328
- return this.sheets;
44329
- }
44330
44861
  doesHeaderExist(sheetId, dimension, index) {
44331
44862
  return dimension === "COL"
44332
44863
  ? index >= 0 && index < this.getNumberCols(sheetId)
@@ -44335,13 +44866,6 @@ stores.inject(MyMetaStore, storeInstance);
44335
44866
  doesHeadersExist(sheetId, dimension, headerIndexes) {
44336
44867
  return headerIndexes.every((index) => this.doesHeaderExist(sheetId, dimension, index));
44337
44868
  }
44338
- getRow(sheetId, index) {
44339
- const row = this.getSheet(sheetId).rows[index];
44340
- if (!row) {
44341
- throw new Error(`Row ${row} not found.`);
44342
- }
44343
- return row;
44344
- }
44345
44869
  getCell({ sheetId, col, row }) {
44346
44870
  const sheet = this.tryGetSheet(sheetId);
44347
44871
  const cellId = sheet?.rows[row]?.cells[col];
@@ -45845,8 +46369,8 @@ stores.inject(MyMetaStore, storeInstance);
45845
46369
  getters;
45846
46370
  ui;
45847
46371
  selection;
45848
- constructor({ getters, stateObserver, dispatch, uiActions, selection }) {
45849
- super(stateObserver, dispatch);
46372
+ constructor({ getters, stateObserver, dispatch, canDispatch, uiActions, selection, }) {
46373
+ super(stateObserver, dispatch, canDispatch);
45850
46374
  this.getters = getters;
45851
46375
  this.ui = uiActions;
45852
46376
  this.selection = selection;
@@ -45910,12 +46434,6 @@ stores.inject(MyMetaStore, storeInstance);
45910
46434
  : _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
45911
46435
  }
45912
46436
  const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
45913
- return this.readCell(position);
45914
- }
45915
- readCell(position) {
45916
- if (!this.getters.tryGetSheet(position.sheetId)) {
45917
- throw new EvaluationError(_t("Invalid sheet name"));
45918
- }
45919
46437
  return this.computeCell(position);
45920
46438
  }
45921
46439
  /**
@@ -45951,7 +46469,7 @@ stores.inject(MyMetaStore, storeInstance);
45951
46469
  matrix[colIndex] = new Array(height);
45952
46470
  for (let row = _zone.top; row <= _zone.bottom; row++) {
45953
46471
  const rowIndex = row - _zone.top;
45954
- matrix[colIndex][rowIndex] = this.readCell({ sheetId, col, row });
46472
+ matrix[colIndex][rowIndex] = this.computeCell({ sheetId, col, row });
45955
46473
  }
45956
46474
  }
45957
46475
  this.rangeCache[cacheKey] = matrix;
@@ -47059,15 +47577,15 @@ stores.inject(MyMetaStore, storeInstance);
47059
47577
  getEvaluatedCell(position) {
47060
47578
  return this.evaluatedCells.get(position) || EMPTY_CELL;
47061
47579
  }
47062
- getSpreadPositionsOf(position) {
47580
+ getSpreadZone(position) {
47063
47581
  if (!this.spreadingRelations.isArrayFormula(position)) {
47064
- return [];
47582
+ return undefined;
47065
47583
  }
47066
47584
  if (this.evaluatedCells.get(position)?.type === CellValueType.error) {
47067
- return [position];
47585
+ return positionToZone(position);
47068
47586
  }
47069
47587
  const spreadPositions = Array.from(this.spreadingRelations.getArrayResultPositions(position));
47070
- return [position, ...spreadPositions];
47588
+ return union(positionToZone(position), unionPositionsToZone(spreadPositions));
47071
47589
  }
47072
47590
  getEvaluatedPositions() {
47073
47591
  return this.evaluatedCells.keys();
@@ -47132,7 +47650,9 @@ stores.inject(MyMetaStore, storeInstance);
47132
47650
  this.blockedArrayFormulas = this.createEmptyPositionSet();
47133
47651
  this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
47134
47652
  this.formulaDependencies = lazy(() => {
47135
- const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position).map((range) => ({
47653
+ const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
47654
+ .filter((range) => !range.invalidSheetName && !range.invalidXc)
47655
+ .map((range) => ({
47136
47656
  data: position,
47137
47657
  boundingBox: {
47138
47658
  zone: range.zone,
@@ -47472,7 +47992,7 @@ stores.inject(MyMetaStore, storeInstance);
47472
47992
  "getEvaluatedCell",
47473
47993
  "getEvaluatedCells",
47474
47994
  "getEvaluatedCellsInZone",
47475
- "getSpreadPositionsOf",
47995
+ "getSpreadZone",
47476
47996
  "getArrayFormulaSpreadingOn",
47477
47997
  "isEmpty",
47478
47998
  ];
@@ -47578,8 +48098,11 @@ stores.inject(MyMetaStore, storeInstance);
47578
48098
  getEvaluatedCellsInZone(sheetId, zone) {
47579
48099
  return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
47580
48100
  }
47581
- getSpreadPositionsOf(position) {
47582
- return this.evaluator.getSpreadPositionsOf(position);
48101
+ /**
48102
+ * Return the spread zone the position is part of, if any
48103
+ */
48104
+ getSpreadZone(position) {
48105
+ return this.evaluator.getSpreadZone(position);
47583
48106
  }
47584
48107
  getArrayFormulaSpreadingOn(position) {
47585
48108
  return this.evaluator.getArrayFormulaSpreadingOn(position);
@@ -47619,7 +48142,7 @@ stores.inject(MyMetaStore, storeInstance);
47619
48142
  ? getItemId(newFormat, data.formats)
47620
48143
  : exportedCellData.format;
47621
48144
  let content;
47622
- if (formulaCell instanceof FormulaCellWithDependencies) {
48145
+ if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
47623
48146
  content = formulaCell.contentWithFixedReferences;
47624
48147
  }
47625
48148
  else {
@@ -47667,17 +48190,17 @@ stores.inject(MyMetaStore, storeInstance);
47667
48190
  */
47668
48191
  function sortWithClusters(colorsToSort) {
47669
48192
  const clusters = [
47670
- { leadColor: rgba(255, 0, 0), colors: [] },
47671
- { leadColor: rgba(255, 128, 0), colors: [] },
47672
- { leadColor: rgba(128, 128, 0), colors: [] },
47673
- { leadColor: rgba(128, 255, 0), colors: [] },
47674
- { leadColor: rgba(0, 255, 0), colors: [] },
47675
- { leadColor: rgba(0, 255, 128), colors: [] },
47676
- { leadColor: rgba(0, 255, 255), colors: [] },
47677
- { leadColor: rgba(0, 127, 255), colors: [] },
47678
- { leadColor: rgba(0, 0, 255), colors: [] },
47679
- { leadColor: rgba(127, 0, 255), colors: [] },
47680
- { leadColor: rgba(128, 0, 128), colors: [] },
48193
+ { leadColor: rgba(255, 0, 0), colors: [] }, // red
48194
+ { leadColor: rgba(255, 128, 0), colors: [] }, // orange
48195
+ { leadColor: rgba(128, 128, 0), colors: [] }, // yellow
48196
+ { leadColor: rgba(128, 255, 0), colors: [] }, // chartreuse
48197
+ { leadColor: rgba(0, 255, 0), colors: [] }, // green
48198
+ { leadColor: rgba(0, 255, 128), colors: [] }, // spring green
48199
+ { leadColor: rgba(0, 255, 255), colors: [] }, // cyan
48200
+ { leadColor: rgba(0, 127, 255), colors: [] }, // azure
48201
+ { leadColor: rgba(0, 0, 255), colors: [] }, // blue
48202
+ { leadColor: rgba(127, 0, 255), colors: [] }, // violet
48203
+ { leadColor: rgba(128, 0, 128), colors: [] }, // magenta
47681
48204
  { leadColor: rgba(255, 0, 128), colors: [] }, // rose
47682
48205
  ];
47683
48206
  for (const color of colorsToSort.map(colorToRGBA)) {
@@ -48068,13 +48591,13 @@ stores.inject(MyMetaStore, storeInstance);
48068
48591
  .map((cell) => cell.value);
48069
48592
  switch (threshold.type) {
48070
48593
  case "value":
48071
- const result = functionName === "max" ? Math.max(...rangeValues) : Math.min(...rangeValues);
48594
+ const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
48072
48595
  return result;
48073
48596
  case "number":
48074
48597
  return Number(threshold.value);
48075
48598
  case "percentage":
48076
- const min = Math.min(...rangeValues);
48077
- const max = Math.max(...rangeValues);
48599
+ const min = largeMin(rangeValues);
48600
+ const max = largeMax(rangeValues);
48078
48601
  const delta = max - min;
48079
48602
  return min + (delta * Number(threshold.value)) / 100;
48080
48603
  case "percentile":
@@ -48541,8 +49064,8 @@ stores.inject(MyMetaStore, storeInstance);
48541
49064
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
48542
49065
  return true;
48543
49066
  }
48544
- const spreadPositions = this.getters.getSpreadPositionsOf(parentSpreadingCell);
48545
- return deepEquals(unionZone, unionPositionsToZone(spreadPositions));
49067
+ const zone = this.getters.getSpreadZone(parentSpreadingCell);
49068
+ return deepEquals(unionZone, zone);
48546
49069
  }
48547
49070
  coreTableToTable(sheetId, table) {
48548
49071
  if (table.type !== "dynamic") {
@@ -48550,8 +49073,7 @@ stores.inject(MyMetaStore, storeInstance);
48550
49073
  }
48551
49074
  const tableZone = table.range.zone;
48552
49075
  const tablePosition = { sheetId, col: tableZone.left, row: tableZone.top };
48553
- const spreadPositions = this.getters.getSpreadPositionsOf(tablePosition);
48554
- const zone = spreadPositions.length ? unionPositionsToZone(spreadPositions) : table.range.zone;
49076
+ const zone = this.getters.getSpreadZone(tablePosition) ?? table.range.zone;
48555
49077
  const range = this.getters.getRangeFromZone(sheetId, zone);
48556
49078
  const filters = this.getDynamicTableFilters(sheetId, table, zone);
48557
49079
  return { id: table.id, range, filters, config: table.config };
@@ -49001,8 +49523,7 @@ stores.inject(MyMetaStore, storeInstance);
49001
49523
  let row = zone.bottom;
49002
49524
  if (col > 0) {
49003
49525
  let leftPosition = { sheetId, col: col - 1, row };
49004
- while (this.getters.getCorrespondingFormulaCell(leftPosition) ||
49005
- this.getters.getCell(leftPosition)?.content) {
49526
+ while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty) {
49006
49527
  row += 1;
49007
49528
  leftPosition = { sheetId, col: col - 1, row };
49008
49529
  }
@@ -49011,8 +49532,7 @@ stores.inject(MyMetaStore, storeInstance);
49011
49532
  col = zone.right;
49012
49533
  if (col <= this.getters.getNumberCols(sheetId)) {
49013
49534
  let rightPosition = { sheetId, col: col + 1, row };
49014
- while (this.getters.getCorrespondingFormulaCell(rightPosition) ||
49015
- this.getters.getCell(rightPosition)?.content) {
49535
+ while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty) {
49016
49536
  row += 1;
49017
49537
  rightPosition = { sheetId, col: col + 1, row };
49018
49538
  }
@@ -49322,13 +49842,13 @@ stores.inject(MyMetaStore, storeInstance);
49322
49842
  const cells = this.getters.getEvaluatedCellsInZone(sheet.id, zone);
49323
49843
  const cellPositions = range(end, -1, -1);
49324
49844
  const invalidCells = cellPositions.filter((position) => cells[position] && !cells[position].isAutoSummable);
49325
- const maxValidPosition = Math.max(...invalidCells);
49845
+ const maxValidPosition = largeMax(invalidCells);
49326
49846
  const numberSequences = groupConsecutive(cellPositions.filter((position) => this.isNumber(cells[position])));
49327
49847
  const firstSequence = numberSequences[0] || [];
49328
- if (Math.max(...firstSequence) < maxValidPosition) {
49848
+ if (largeMax(firstSequence) < maxValidPosition) {
49329
49849
  return Infinity;
49330
49850
  }
49331
- return Math.min(...firstSequence);
49851
+ return largeMin(firstSequence);
49332
49852
  }
49333
49853
  shouldFindData(sheetId, zone) {
49334
49854
  return this.getters.isEmpty(sheetId, zone) || this.getters.isSingleCellOrMerge(sheetId, zone);
@@ -50903,8 +51423,6 @@ stores.inject(MyMetaStore, storeInstance);
50903
51423
  static getters = [
50904
51424
  "doesCellHaveGridIcon",
50905
51425
  "getCellWidth",
50906
- "getCellComputedBorder",
50907
- "getCellComputedStyle",
50908
51426
  "getTextWidth",
50909
51427
  "getCellText",
50910
51428
  "getCellMultiLineText",
@@ -50948,7 +51466,7 @@ stores.inject(MyMetaStore, storeInstance);
50948
51466
  // Getters
50949
51467
  // ---------------------------------------------------------------------------
50950
51468
  getCellWidth(position) {
50951
- const style = this.getCellComputedStyle(position);
51469
+ const style = this.getters.getCellComputedStyle(position);
50952
51470
  let contentWidth = 0;
50953
51471
  const content = this.getters.getEvaluatedCell(position).formattedValue;
50954
51472
  if (content) {
@@ -51041,35 +51559,12 @@ stores.inject(MyMetaStore, storeInstance);
51041
51559
  */
51042
51560
  isCellEmpty(position) {
51043
51561
  const mainPosition = this.getters.getMainCellPosition(position);
51044
- return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
51045
- this.getters.getCell(mainPosition)?.content);
51046
- }
51047
- getCellComputedBorder(position) {
51048
- const cellBorder = this.getters.getCellBorder(position) || {};
51049
- const cellTableBorder = this.getters.getCellTableBorder(position) || {};
51050
- // Use removeFalsyAttributes to avoid overwriting borders with undefined values
51051
- const border = { ...cellTableBorder, ...removeFalsyAttributes(cellBorder) };
51052
- return isObjectEmptyRecursive(border) ? null : border;
51053
- }
51054
- getCellComputedStyle(position) {
51055
- const cell = this.getters.getCell(position);
51056
- const cfStyle = this.getters.getCellConditionalFormatStyle(position);
51057
- const tableStyle = this.getters.getCellTableStyle(position);
51058
- const computedStyle = {
51059
- ...removeFalsyAttributes(tableStyle),
51060
- ...removeFalsyAttributes(cell?.style),
51061
- ...removeFalsyAttributes(cfStyle),
51062
- };
51063
- const evaluatedCell = this.getters.getEvaluatedCell(position);
51064
- if (evaluatedCell.link && !computedStyle.textColor) {
51065
- computedStyle.textColor = LINK_COLOR;
51066
- }
51067
- return computedStyle;
51562
+ return this.getters.getEvaluatedCell(mainPosition).type === CellValueType.empty;
51068
51563
  }
51069
51564
  getColMaxWidth(sheetId, index) {
51070
51565
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
51071
51566
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
51072
- return Math.max(0, ...sizes);
51567
+ return Math.max(0, largeMax(sizes));
51073
51568
  }
51074
51569
  /**
51075
51570
  * Check that any "sheetId" in the command matches an existing
@@ -51098,6 +51593,241 @@ stores.inject(MyMetaStore, storeInstance);
51098
51593
  }
51099
51594
  }
51100
51595
 
51596
+ class TableStylePlugin extends UIPlugin {
51597
+ static getters = ["getCellTableStyle", "getCellTableBorder"];
51598
+ tableStyles = {};
51599
+ handle(cmd) {
51600
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51601
+ (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51602
+ cmd.type === "EVALUATE_CELLS") {
51603
+ this.tableStyles = {};
51604
+ return;
51605
+ }
51606
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51607
+ delete this.tableStyles[cmd.sheetId];
51608
+ return;
51609
+ }
51610
+ }
51611
+ finalize() {
51612
+ for (const sheetId of this.getters.getSheetIds()) {
51613
+ if (!this.tableStyles[sheetId]) {
51614
+ this.tableStyles[sheetId] = {};
51615
+ }
51616
+ for (const table of this.getters.getTables(sheetId)) {
51617
+ if (!this.tableStyles[sheetId][table.id]) {
51618
+ this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51619
+ }
51620
+ }
51621
+ }
51622
+ }
51623
+ getCellTableStyle(position) {
51624
+ const table = this.getters.getTable(position);
51625
+ if (!table) {
51626
+ return undefined;
51627
+ }
51628
+ return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51629
+ }
51630
+ getCellTableBorder(position) {
51631
+ const table = this.getters.getTable(position);
51632
+ if (!table) {
51633
+ return undefined;
51634
+ }
51635
+ return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51636
+ }
51637
+ computeTableStyle(sheetId, table) {
51638
+ return lazy(() => {
51639
+ const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51640
+ const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51641
+ // Return the style with sheet coordinates instead of tables coordinates
51642
+ const mapping = this.getTableMapping(sheetId, table);
51643
+ const absoluteTableStyle = { borders: {}, styles: {} };
51644
+ for (let col = 0; col < numberOfCols; col++) {
51645
+ const colInSheet = mapping.colMapping[col];
51646
+ absoluteTableStyle.borders[colInSheet] = {};
51647
+ absoluteTableStyle.styles[colInSheet] = {};
51648
+ for (let row = 0; row < numberOfRows; row++) {
51649
+ const rowInSheet = mapping.rowMapping[row];
51650
+ absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51651
+ absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51652
+ }
51653
+ }
51654
+ return absoluteTableStyle;
51655
+ });
51656
+ }
51657
+ /**
51658
+ * Get the actual table config that will be used to compute the table style. It is different from
51659
+ * the config of the table because of hidden rows and columns in the sheet. For example remove the
51660
+ * hidden rows from config.numberOfHeaders.
51661
+ */
51662
+ getTableRuntimeConfig(sheetId, table) {
51663
+ const tableZone = table.range.zone;
51664
+ const config = { ...table.config };
51665
+ let numberOfCols = tableZone.right - tableZone.left + 1;
51666
+ let numberOfRows = tableZone.bottom - tableZone.top + 1;
51667
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51668
+ if (!this.getters.isRowHidden(sheetId, row)) {
51669
+ continue;
51670
+ }
51671
+ numberOfRows--;
51672
+ if (row - tableZone.top < table.config.numberOfHeaders) {
51673
+ config.numberOfHeaders--;
51674
+ if (config.numberOfHeaders < 0) {
51675
+ config.numberOfHeaders = 0;
51676
+ }
51677
+ }
51678
+ if (row === tableZone.bottom) {
51679
+ config.totalRow = false;
51680
+ }
51681
+ }
51682
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51683
+ if (!this.getters.isColHidden(sheetId, col)) {
51684
+ continue;
51685
+ }
51686
+ numberOfCols--;
51687
+ if (col === tableZone.left) {
51688
+ config.firstColumn = false;
51689
+ }
51690
+ if (col === tableZone.right) {
51691
+ config.lastColumn = false;
51692
+ }
51693
+ }
51694
+ return {
51695
+ config,
51696
+ numberOfCols,
51697
+ numberOfRows,
51698
+ };
51699
+ }
51700
+ /**
51701
+ * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51702
+ */
51703
+ getTableMapping(sheetId, table) {
51704
+ const colMapping = {};
51705
+ const rowMapping = {};
51706
+ let colOffset = 0;
51707
+ let rowOffset = 0;
51708
+ const tableZone = table.range.zone;
51709
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
51710
+ if (this.getters.isColHidden(sheetId, col)) {
51711
+ continue;
51712
+ }
51713
+ colMapping[colOffset] = col;
51714
+ colOffset++;
51715
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51716
+ if (this.getters.isRowHidden(sheetId, row)) {
51717
+ continue;
51718
+ }
51719
+ rowMapping[rowOffset] = row;
51720
+ rowOffset++;
51721
+ }
51722
+ }
51723
+ return {
51724
+ colMapping,
51725
+ rowMapping,
51726
+ };
51727
+ }
51728
+ }
51729
+ const invalidateTableStyleCommands = [
51730
+ "HIDE_COLUMNS_ROWS",
51731
+ "UNHIDE_COLUMNS_ROWS",
51732
+ "UNFOLD_HEADER_GROUP",
51733
+ "FOLD_HEADER_GROUP",
51734
+ "FOLD_ALL_HEADER_GROUPS",
51735
+ "UNFOLD_ALL_HEADER_GROUPS",
51736
+ "CREATE_TABLE",
51737
+ "UPDATE_TABLE",
51738
+ "UPDATE_FILTER",
51739
+ "REMOVE_TABLE",
51740
+ ];
51741
+ const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
51742
+ function doesCommandInvalidatesTableStyle(cmd) {
51743
+ return invalidateTableStyleCommandsSet.has(cmd.type);
51744
+ }
51745
+
51746
+ class CellComputedStylePlugin extends UIPlugin {
51747
+ static getters = ["getCellComputedBorder", "getCellComputedStyle"];
51748
+ styles = {};
51749
+ borders = {};
51750
+ handle(cmd) {
51751
+ if (invalidateEvaluationCommands.has(cmd.type) ||
51752
+ cmd.type === "UPDATE_CELL" ||
51753
+ cmd.type === "EVALUATE_CELLS") {
51754
+ this.styles = {};
51755
+ this.borders = {};
51756
+ return;
51757
+ }
51758
+ if (doesCommandInvalidatesTableStyle(cmd)) {
51759
+ delete this.styles[cmd.sheetId];
51760
+ delete this.borders[cmd.sheetId];
51761
+ return;
51762
+ }
51763
+ if (invalidateCFEvaluationCommands.has(cmd.type)) {
51764
+ this.styles = {};
51765
+ return;
51766
+ }
51767
+ if (invalidateBordersCommands.has(cmd.type)) {
51768
+ this.borders = {};
51769
+ return;
51770
+ }
51771
+ }
51772
+ getCellComputedBorder(position) {
51773
+ const { sheetId, row, col } = position;
51774
+ if (this.borders[sheetId]?.[row]?.[col] !== undefined) {
51775
+ return this.borders[sheetId][row][col];
51776
+ }
51777
+ if (!this.borders[sheetId]) {
51778
+ this.borders[sheetId] = {};
51779
+ }
51780
+ if (!this.borders[sheetId][row]) {
51781
+ this.borders[sheetId][row] = {};
51782
+ }
51783
+ if (!this.borders[sheetId][row][col]) {
51784
+ this.borders[sheetId][row][col] = this.computeCellBorder(position);
51785
+ }
51786
+ return this.borders[sheetId][row][col];
51787
+ }
51788
+ getCellComputedStyle(position) {
51789
+ const { sheetId, row, col } = position;
51790
+ if (this.styles[sheetId]?.[row]?.[col] !== undefined) {
51791
+ return this.styles[sheetId][row][col];
51792
+ }
51793
+ if (!this.styles[sheetId]) {
51794
+ this.styles[sheetId] = {};
51795
+ }
51796
+ if (!this.styles[sheetId][row]) {
51797
+ this.styles[sheetId][row] = {};
51798
+ }
51799
+ if (!this.styles[sheetId][row][col]) {
51800
+ this.styles[sheetId][row][col] = this.computeCellStyle(position);
51801
+ }
51802
+ return this.styles[sheetId][row][col];
51803
+ }
51804
+ computeCellBorder(position) {
51805
+ const cellBorder = this.getters.getCellBorder(position) || {};
51806
+ const cellTableBorder = this.getters.getCellTableBorder(position) || {};
51807
+ // Use removeFalsyAttributes to avoid overwriting borders with undefined values
51808
+ const border = {
51809
+ ...removeFalsyAttributes(cellTableBorder),
51810
+ ...removeFalsyAttributes(cellBorder),
51811
+ };
51812
+ return isObjectEmptyRecursive(border) ? null : border;
51813
+ }
51814
+ computeCellStyle(position) {
51815
+ const cell = this.getters.getCell(position);
51816
+ const cfStyle = this.getters.getCellConditionalFormatStyle(position);
51817
+ const tableStyle = this.getters.getCellTableStyle(position);
51818
+ const computedStyle = {
51819
+ ...removeFalsyAttributes(tableStyle),
51820
+ ...removeFalsyAttributes(cell?.style),
51821
+ ...removeFalsyAttributes(cfStyle),
51822
+ };
51823
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
51824
+ if (evaluatedCell.link && !computedStyle.textColor) {
51825
+ computedStyle.textColor = LINK_COLOR;
51826
+ }
51827
+ return computedStyle;
51828
+ }
51829
+ }
51830
+
51101
51831
  const genericRepeatsTransforms = [
51102
51832
  repeatSheetDependantCommand,
51103
51833
  repeatTargetDependantCommand,
@@ -51675,148 +52405,6 @@ stores.inject(MyMetaStore, storeInstance);
51675
52405
  }
51676
52406
  }
51677
52407
 
51678
- class TableStylePlugin extends UIPlugin {
51679
- static getters = ["getCellTableStyle", "getCellTableBorder"];
51680
- tableStyles = {};
51681
- handle(cmd) {
51682
- if (invalidateEvaluationCommands.has(cmd.type) ||
51683
- (cmd.type === "UPDATE_CELL" && "content" in cmd) ||
51684
- cmd.type === "EVALUATE_CELLS") {
51685
- this.tableStyles = {};
51686
- return;
51687
- }
51688
- switch (cmd.type) {
51689
- case "HIDE_COLUMNS_ROWS":
51690
- case "UNHIDE_COLUMNS_ROWS":
51691
- case "UNFOLD_HEADER_GROUP":
51692
- case "FOLD_HEADER_GROUP":
51693
- case "FOLD_ALL_HEADER_GROUPS":
51694
- case "UNFOLD_ALL_HEADER_GROUPS":
51695
- case "UPDATE_TABLE":
51696
- case "UPDATE_FILTER":
51697
- delete this.tableStyles[cmd.sheetId];
51698
- break;
51699
- }
51700
- }
51701
- finalize() {
51702
- for (const sheetId of this.getters.getSheetIds()) {
51703
- if (!this.tableStyles[sheetId]) {
51704
- this.tableStyles[sheetId] = {};
51705
- }
51706
- for (const table of this.getters.getTables(sheetId)) {
51707
- if (!this.tableStyles[sheetId][table.id]) {
51708
- this.tableStyles[sheetId][table.id] = this.computeTableStyle(sheetId, table);
51709
- }
51710
- }
51711
- }
51712
- }
51713
- getCellTableStyle(position) {
51714
- const table = this.getters.getTable(position);
51715
- if (!table) {
51716
- return undefined;
51717
- }
51718
- return this.tableStyles[position.sheetId][table.id]().styles[position.col]?.[position.row];
51719
- }
51720
- getCellTableBorder(position) {
51721
- const table = this.getters.getTable(position);
51722
- if (!table) {
51723
- return undefined;
51724
- }
51725
- return this.tableStyles[position.sheetId][table.id]().borders[position.col]?.[position.row];
51726
- }
51727
- computeTableStyle(sheetId, table) {
51728
- return lazy(() => {
51729
- const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
51730
- const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
51731
- // Return the style with sheet coordinates instead of tables coordinates
51732
- const mapping = this.getTableMapping(sheetId, table);
51733
- const absoluteTableStyle = { borders: {}, styles: {} };
51734
- for (let col = 0; col < numberOfCols; col++) {
51735
- const colInSheet = mapping.colMapping[col];
51736
- absoluteTableStyle.borders[colInSheet] = {};
51737
- absoluteTableStyle.styles[colInSheet] = {};
51738
- for (let row = 0; row < numberOfRows; row++) {
51739
- const rowInSheet = mapping.rowMapping[row];
51740
- absoluteTableStyle.borders[colInSheet][rowInSheet] = relativeTableStyle.borders[col][row];
51741
- absoluteTableStyle.styles[colInSheet][rowInSheet] = relativeTableStyle.styles[col][row];
51742
- }
51743
- }
51744
- return absoluteTableStyle;
51745
- });
51746
- }
51747
- /**
51748
- * Get the actual table config that will be used to compute the table style. It is different from
51749
- * the config of the table because of hidden rows and columns in the sheet. For example remove the
51750
- * hidden rows from config.numberOfHeaders.
51751
- */
51752
- getTableRuntimeConfig(sheetId, table) {
51753
- const tableZone = table.range.zone;
51754
- const config = { ...table.config };
51755
- let numberOfCols = tableZone.right - tableZone.left + 1;
51756
- let numberOfRows = tableZone.bottom - tableZone.top + 1;
51757
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51758
- if (!this.getters.isRowHidden(sheetId, row)) {
51759
- continue;
51760
- }
51761
- numberOfRows--;
51762
- if (row - tableZone.top < table.config.numberOfHeaders) {
51763
- config.numberOfHeaders--;
51764
- if (config.numberOfHeaders < 0) {
51765
- config.numberOfHeaders = 0;
51766
- }
51767
- }
51768
- if (row === tableZone.bottom) {
51769
- config.totalRow = false;
51770
- }
51771
- }
51772
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51773
- if (!this.getters.isColHidden(sheetId, col)) {
51774
- continue;
51775
- }
51776
- numberOfCols--;
51777
- if (col === tableZone.left) {
51778
- config.firstColumn = false;
51779
- }
51780
- if (col === tableZone.right) {
51781
- config.lastColumn = false;
51782
- }
51783
- }
51784
- return {
51785
- config,
51786
- numberOfCols,
51787
- numberOfRows,
51788
- };
51789
- }
51790
- /**
51791
- * Get a mapping: relative col/row position in the table <=> col/row in the sheet
51792
- */
51793
- getTableMapping(sheetId, table) {
51794
- const colMapping = {};
51795
- const rowMapping = {};
51796
- let colOffset = 0;
51797
- let rowOffset = 0;
51798
- const tableZone = table.range.zone;
51799
- for (let col = tableZone.left; col <= tableZone.right; col++) {
51800
- if (this.getters.isColHidden(sheetId, col)) {
51801
- continue;
51802
- }
51803
- colMapping[colOffset] = col;
51804
- colOffset++;
51805
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
51806
- if (this.getters.isRowHidden(sheetId, row)) {
51807
- continue;
51808
- }
51809
- rowMapping[rowOffset] = row;
51810
- rowOffset++;
51811
- }
51812
- }
51813
- return {
51814
- colMapping,
51815
- rowMapping,
51816
- };
51817
- }
51818
- }
51819
-
51820
52408
  /**
51821
52409
  * Clipboard Plugin
51822
52410
  *
@@ -52526,38 +53114,6 @@ stores.inject(MyMetaStore, storeInstance);
52526
53114
  }
52527
53115
  }
52528
53116
 
52529
- const selectionStatisticFunctions = [
52530
- {
52531
- name: _t("Sum"),
52532
- types: [CellValueType.number],
52533
- compute: (values, locale) => sum([[values]], locale),
52534
- },
52535
- {
52536
- name: _t("Avg"),
52537
- types: [CellValueType.number],
52538
- compute: (values, locale) => average([[values]], locale),
52539
- },
52540
- {
52541
- name: _t("Min"),
52542
- types: [CellValueType.number],
52543
- compute: (values, locale) => min([[values]], locale),
52544
- },
52545
- {
52546
- name: _t("Max"),
52547
- types: [CellValueType.number],
52548
- compute: (values, locale) => max([[values]], locale),
52549
- },
52550
- {
52551
- name: _t("Count"),
52552
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52553
- compute: (values) => countAny([[values]]),
52554
- },
52555
- {
52556
- name: _t("Count Numbers"),
52557
- types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
52558
- compute: (values, locale) => countNumbers([[values]], locale),
52559
- },
52560
- ];
52561
53117
  /**
52562
53118
  * SelectionPlugin
52563
53119
  */
@@ -52573,8 +53129,6 @@ stores.inject(MyMetaStore, storeInstance);
52573
53129
  "getSelectedZones",
52574
53130
  "getSelectedZone",
52575
53131
  "getSelectedCells",
52576
- "getStatisticFnResults",
52577
- "getAggregate",
52578
53132
  "getSelectedFigureId",
52579
53133
  "getSelection",
52580
53134
  "getActivePosition",
@@ -52609,7 +53163,10 @@ stores.inject(MyMetaStore, storeInstance);
52609
53163
  switch (cmd.type) {
52610
53164
  case "ACTIVATE_SHEET":
52611
53165
  try {
52612
- this.getters.getSheet(cmd.sheetIdTo);
53166
+ const sheet = this.getters.getSheet(cmd.sheetIdTo);
53167
+ if (!sheet.isVisible) {
53168
+ return "SheetIsHidden" /* CommandResult.SheetIsHidden */;
53169
+ }
52613
53170
  break;
52614
53171
  }
52615
53172
  catch (error) {
@@ -52858,52 +53415,6 @@ stores.inject(MyMetaStore, storeInstance);
52858
53415
  : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });
52859
53416
  }
52860
53417
  }
52861
- getStatisticFnResults() {
52862
- const sheetId = this.getters.getActiveSheetId();
52863
- const cells = new Set();
52864
- for (const zone of this.gridSelection.zones) {
52865
- for (const { col, row } of positions(zone)) {
52866
- if (this.getters.isRowHidden(sheetId, row) || this.getters.isColHidden(sheetId, col)) {
52867
- continue; // Skip hidden cells
52868
- }
52869
- const evaluatedCell = this.getters.getEvaluatedCell({ sheetId, col, row });
52870
- if (evaluatedCell.type !== CellValueType.empty) {
52871
- cells.add(evaluatedCell);
52872
- }
52873
- }
52874
- }
52875
- const locale = this.getters.getLocale();
52876
- let statisticFnResults = {};
52877
- for (let fn of selectionStatisticFunctions) {
52878
- // We don't want to display statistical information when there is no interest:
52879
- // We set the statistical result to undefined if the data handled by the selection
52880
- // does not match the data handled by the function.
52881
- // Ex: if there are only texts in the selection, we prefer that the SUM result
52882
- // be displayed as undefined rather than 0.
52883
- let fnResult = undefined;
52884
- const evaluatedCells = [...cells].filter((c) => fn.types.includes(c.type));
52885
- if (evaluatedCells.length) {
52886
- fnResult = fn.compute(evaluatedCells, locale);
52887
- }
52888
- statisticFnResults[fn.name] = fnResult;
52889
- }
52890
- return statisticFnResults;
52891
- }
52892
- getAggregate() {
52893
- let aggregate = 0;
52894
- let n = 0;
52895
- const sheetId = this.getters.getActiveSheetId();
52896
- const cellPositions = this.gridSelection.zones.map(positions).flat();
52897
- for (const { col, row } of cellPositions) {
52898
- const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
52899
- if (cell.type === CellValueType.number) {
52900
- n++;
52901
- aggregate += cell.value;
52902
- }
52903
- }
52904
- const locale = this.getters.getLocale();
52905
- return n < 2 ? null : formatValue(aggregate, { locale });
52906
- }
52907
53418
  isSelected(zone) {
52908
53419
  return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
52909
53420
  }
@@ -52945,9 +53456,6 @@ stores.inject(MyMetaStore, storeInstance);
52945
53456
  // Other
52946
53457
  // ---------------------------------------------------------------------------
52947
53458
  activateSheet(sheetIdFrom, sheetIdTo) {
52948
- if (!this.getters.isSheetVisible(sheetIdTo)) {
52949
- this.dispatch("SHOW_SHEET", { sheetId: sheetIdTo });
52950
- }
52951
53459
  this.setActiveSheet(sheetIdTo);
52952
53460
  this.sheetsData[sheetIdFrom] = {
52953
53461
  gridSelection: deepCopy(this.gridSelection),
@@ -53954,7 +54462,7 @@ stores.inject(MyMetaStore, storeInstance);
53954
54462
  * column of the current viewport
53955
54463
  */
53956
54464
  getColDimensionsInViewport(sheetId, col) {
53957
- const left = Math.min(...this.getters.getSheetViewVisibleCols());
54465
+ const left = largeMin(this.getters.getSheetViewVisibleCols());
53958
54466
  const start = this.getters.getColRowOffsetInViewport("COL", left, col);
53959
54467
  const size = this.getters.getColSize(sheetId, col);
53960
54468
  const isColHidden = this.getters.isColHidden(sheetId, col);
@@ -53969,7 +54477,7 @@ stores.inject(MyMetaStore, storeInstance);
53969
54477
  * of the current viewport
53970
54478
  */
53971
54479
  getRowDimensionsInViewport(sheetId, row) {
53972
- const top = Math.min(...this.getters.getSheetViewVisibleRows());
54480
+ const top = largeMin(this.getters.getSheetViewVisibleRows());
53973
54481
  const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
53974
54482
  const size = this.getters.getRowSize(sheetId, row);
53975
54483
  const isRowHidden = this.getters.isRowHidden(sheetId, row);
@@ -54321,6 +54829,7 @@ stores.inject(MyMetaStore, storeInstance);
54321
54829
  .add("evaluation_filter", FilterEvaluationPlugin)
54322
54830
  .add("header_visibility_ui", HeaderVisibilityUIPlugin)
54323
54831
  .add("table_style", TableStylePlugin)
54832
+ .add("cell_computed_style", CellComputedStylePlugin)
54324
54833
  .add("header_positions", HeaderPositionsUIPlugin)
54325
54834
  .add("viewport", SheetViewPlugin)
54326
54835
  .add("clipboard", ClipboardPlugin);
@@ -54381,6 +54890,38 @@ stores.inject(MyMetaStore, storeInstance);
54381
54890
  }
54382
54891
  }
54383
54892
 
54893
+ class ArrayFormulaHighlight extends SpreadsheetStore {
54894
+ highlightStore = this.get(HighlightStore);
54895
+ constructor(get) {
54896
+ super(get);
54897
+ this.highlightStore.register(this);
54898
+ }
54899
+ get highlights() {
54900
+ const zone = this.getHighlightZone();
54901
+ if (!zone) {
54902
+ return [];
54903
+ }
54904
+ const sheetId = this.model.getters.getActiveSheetId();
54905
+ return [
54906
+ {
54907
+ sheetId,
54908
+ zone,
54909
+ color: "#17A2B8",
54910
+ noFill: true,
54911
+ thinLine: true,
54912
+ },
54913
+ ];
54914
+ }
54915
+ getHighlightZone() {
54916
+ const position = this.model.getters.getActivePosition();
54917
+ const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
54918
+ const spreadZone = spreader
54919
+ ? this.model.getters.getSpreadZone(spreader)
54920
+ : this.model.getters.getSpreadZone(position);
54921
+ return spreadZone;
54922
+ }
54923
+ }
54924
+
54384
54925
  const RIPPLE_KEY_FRAMES = [
54385
54926
  { transform: "scale(0)" },
54386
54927
  { transform: "scale(0.8)", offset: 0.33 },
@@ -54683,12 +55224,14 @@ stores.inject(MyMetaStore, storeInstance);
54683
55224
  this.editionState = "initializing";
54684
55225
  }
54685
55226
  stopEdition() {
54686
- if (!this.state.isEditing)
55227
+ const input = this.sheetNameRef.el;
55228
+ if (!this.state.isEditing || !input)
54687
55229
  return;
54688
55230
  this.state.isEditing = false;
54689
55231
  this.editionState = "initializing";
54690
- this.sheetNameRef.el?.blur();
55232
+ input.blur();
54691
55233
  const inputValue = this.getInputContent() || "";
55234
+ input.innerText = inputValue;
54692
55235
  interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
54693
55236
  }
54694
55237
  cancelEdition() {
@@ -54732,6 +55275,115 @@ stores.inject(MyMetaStore, storeInstance);
54732
55275
  }
54733
55276
  }
54734
55277
 
55278
+ const selectionStatisticFunctions = [
55279
+ {
55280
+ name: _t("Sum"),
55281
+ types: [CellValueType.number],
55282
+ compute: (values, locale) => sum([[values]], locale),
55283
+ },
55284
+ {
55285
+ name: _t("Avg"),
55286
+ types: [CellValueType.number],
55287
+ compute: (values, locale) => average([[values]], locale),
55288
+ },
55289
+ {
55290
+ name: _t("Min"),
55291
+ types: [CellValueType.number],
55292
+ compute: (values, locale) => min([[values]], locale),
55293
+ },
55294
+ {
55295
+ name: _t("Max"),
55296
+ types: [CellValueType.number],
55297
+ compute: (values, locale) => max([[values]], locale),
55298
+ },
55299
+ {
55300
+ name: _t("Count"),
55301
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55302
+ compute: (values) => countAny([[values]]),
55303
+ },
55304
+ {
55305
+ name: _t("Count Numbers"),
55306
+ types: [CellValueType.number, CellValueType.text, CellValueType.boolean, CellValueType.error],
55307
+ compute: (values, locale) => countNumbers([[values]], locale),
55308
+ },
55309
+ ];
55310
+ class AggregateStatisticsStore extends SpreadsheetStore {
55311
+ statisticFnResults = this._computeStatisticFnResults();
55312
+ isDirty = false;
55313
+ constructor(get) {
55314
+ super(get);
55315
+ this.model.selection.observe(this, {
55316
+ handleEvent: this.handleEvent.bind(this),
55317
+ });
55318
+ }
55319
+ handle(cmd) {
55320
+ if (invalidateEvaluationCommands.has(cmd.type) ||
55321
+ (cmd.type === "UPDATE_CELL" && "content" in cmd)) {
55322
+ this.isDirty = true;
55323
+ }
55324
+ switch (cmd.type) {
55325
+ case "HIDE_COLUMNS_ROWS":
55326
+ case "UNHIDE_COLUMNS_ROWS":
55327
+ case "GROUP_HEADERS":
55328
+ case "UNGROUP_HEADERS":
55329
+ case "ACTIVATE_SHEET":
55330
+ case "ACTIVATE_NEXT_SHEET":
55331
+ case "ACTIVATE_PREVIOUS_SHEET":
55332
+ case "EVALUATE_CELLS":
55333
+ case "UNDO":
55334
+ case "REDO":
55335
+ this.isDirty = true;
55336
+ }
55337
+ }
55338
+ finalize() {
55339
+ if (this.isDirty) {
55340
+ this.isDirty = false;
55341
+ this.statisticFnResults = this._computeStatisticFnResults();
55342
+ }
55343
+ }
55344
+ handleEvent() {
55345
+ if (this.getters.isGridSelectionActive()) {
55346
+ this.statisticFnResults = this._computeStatisticFnResults();
55347
+ }
55348
+ }
55349
+ _computeStatisticFnResults() {
55350
+ const getters = this.getters;
55351
+ const sheetId = getters.getActiveSheetId();
55352
+ const cells = new Set();
55353
+ const zones = getters.getSelectedZones();
55354
+ for (const zone of zones) {
55355
+ for (let col = zone.left; col <= zone.right; col++) {
55356
+ for (let row = zone.top; row <= zone.bottom; row++) {
55357
+ if (getters.isRowHidden(sheetId, row) || getters.isColHidden(sheetId, col)) {
55358
+ continue; // Skip hidden cells
55359
+ }
55360
+ const evaluatedCell = getters.getEvaluatedCell({ sheetId, col, row });
55361
+ if (evaluatedCell.type !== CellValueType.empty) {
55362
+ cells.add(evaluatedCell);
55363
+ }
55364
+ }
55365
+ }
55366
+ }
55367
+ const locale = getters.getLocale();
55368
+ let statisticFnResults = {};
55369
+ const cellsArray = [...cells];
55370
+ for (let fn of selectionStatisticFunctions) {
55371
+ // We don't want to display statistical information when there is no interest:
55372
+ // We set the statistical result to undefined if the data handled by the selection
55373
+ // does not match the data handled by the function.
55374
+ // Ex: if there are only texts in the selection, we prefer that the SUM result
55375
+ // be displayed as undefined rather than 0.
55376
+ let fnResult = undefined;
55377
+ const evaluatedCells = cellsArray.filter((c) => fn.types.includes(c.type));
55378
+ if (evaluatedCells.length) {
55379
+ fnResult = fn.compute(evaluatedCells, locale);
55380
+ }
55381
+ statisticFnResults[fn.name] = fnResult;
55382
+ }
55383
+ return statisticFnResults;
55384
+ }
55385
+ }
55386
+
54735
55387
  // -----------------------------------------------------------------------------
54736
55388
  // SpreadSheet
54737
55389
  // -----------------------------------------------------------------------------
@@ -54747,40 +55399,38 @@ stores.inject(MyMetaStore, storeInstance);
54747
55399
  }
54748
55400
  `;
54749
55401
  class BottomBarStatistic extends owl.Component {
54750
- static template = "o-spreadsheet-BottomBarStatisic";
55402
+ static template = "o-spreadsheet-BottomBarStatistic";
54751
55403
  static props = {
54752
55404
  openContextMenu: Function,
54753
55405
  closeContextMenu: Function,
54754
55406
  };
54755
55407
  static components = { Ripple };
54756
55408
  selectedStatisticFn = "";
54757
- statisticFnResults = {};
55409
+ store;
54758
55410
  setup() {
54759
- this.statisticFnResults = this.env.model.getters.getStatisticFnResults();
55411
+ this.store = useStore(AggregateStatisticsStore);
54760
55412
  owl.onWillUpdateProps(() => {
54761
- const newStatisticFnResults = this.env.model.getters.getStatisticFnResults();
54762
- if (!deepEquals(newStatisticFnResults, this.statisticFnResults)) {
55413
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54763
55414
  this.props.closeContextMenu();
54764
55415
  }
54765
- this.statisticFnResults = newStatisticFnResults;
54766
55416
  });
54767
55417
  }
54768
55418
  getSelectedStatistic() {
54769
55419
  // don't display button if no function has a result
54770
- if (Object.values(this.statisticFnResults).every((result) => result === undefined)) {
55420
+ if (Object.values(this.store.statisticFnResults).every((result) => result === undefined)) {
54771
55421
  return undefined;
54772
55422
  }
54773
55423
  if (this.selectedStatisticFn === "") {
54774
- this.selectedStatisticFn = Object.keys(this.statisticFnResults)[0];
55424
+ this.selectedStatisticFn = Object.keys(this.store.statisticFnResults)[0];
54775
55425
  }
54776
- return this.getComposedFnName(this.selectedStatisticFn, this.statisticFnResults[this.selectedStatisticFn]);
55426
+ return this.getComposedFnName(this.selectedStatisticFn);
54777
55427
  }
54778
55428
  listSelectionStatistics(ev) {
54779
55429
  const registry = new MenuItemRegistry();
54780
55430
  let i = 0;
54781
- for (let [fnName, fnValue] of Object.entries(this.statisticFnResults)) {
55431
+ for (let [fnName] of Object.entries(this.store.statisticFnResults)) {
54782
55432
  registry.add(fnName, {
54783
- name: this.getComposedFnName(fnName, fnValue),
55433
+ name: () => this.getComposedFnName(fnName),
54784
55434
  sequence: i,
54785
55435
  isReadonlyAllowed: true,
54786
55436
  execute: () => {
@@ -54793,8 +55443,9 @@ stores.inject(MyMetaStore, storeInstance);
54793
55443
  const { top, left, width } = target.getBoundingClientRect();
54794
55444
  this.props.openContextMenu(left + width, top, registry);
54795
55445
  }
54796
- getComposedFnName(fnName, fnValue) {
55446
+ getComposedFnName(fnName) {
54797
55447
  const locale = this.env.model.getters.getLocale();
55448
+ const fnValue = this.store.statisticFnResults[fnName];
54798
55449
  return fnName + ": " + (fnValue !== undefined ? formatValue(fnValue, { locale }) : "__");
54799
55450
  }
54800
55451
  }
@@ -54903,10 +55554,14 @@ stores.inject(MyMetaStore, storeInstance);
54903
55554
  name: sheet.name,
54904
55555
  sequence: i,
54905
55556
  isReadonlyAllowed: true,
54906
- textColor: sheet.isVisible ? undefined : "grey",
55557
+ textColor: sheet.isVisible ? undefined : "#808080",
54907
55558
  execute: (env) => {
55559
+ if (!this.env.model.getters.isSheetVisible(sheetId)) {
55560
+ this.env.model.dispatch("SHOW_SHEET", { sheetId });
55561
+ }
54908
55562
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: from, sheetIdTo: sheetId });
54909
55563
  },
55564
+ isEnabled: (env) => (env.model.getters.isReadonly() ? sheet.isVisible : true),
54910
55565
  });
54911
55566
  i++;
54912
55567
  }
@@ -54979,7 +55634,7 @@ stores.inject(MyMetaStore, storeInstance);
54979
55634
  this.sheetListRef.el.scrollTo({ top: 0, left: scroll, behavior: "smooth" });
54980
55635
  }
54981
55636
  onSheetMouseDown(sheetId, event) {
54982
- if (event.button !== 0)
55637
+ if (event.button !== 0 || this.env.model.getters.isReadonly())
54983
55638
  return;
54984
55639
  this.closeMenu();
54985
55640
  const visibleSheets = this.getVisibleSheets();
@@ -55015,7 +55670,7 @@ stores.inject(MyMetaStore, storeInstance);
55015
55670
  .map((sheetEl) => sheetEl.getBoundingClientRect())
55016
55671
  .map((rect) => ({
55017
55672
  x: rect.x,
55018
- width: rect.width - 1,
55673
+ width: rect.width - 1, // -1 to compensate negative margin
55019
55674
  y: rect.y,
55020
55675
  height: rect.height,
55021
55676
  }));
@@ -55053,7 +55708,6 @@ stores.inject(MyMetaStore, storeInstance);
55053
55708
  Popover,
55054
55709
  VerticalScrollBar,
55055
55710
  HorizontalScrollBar,
55056
- FilterIconsOverlay,
55057
55711
  };
55058
55712
  cellPopovers;
55059
55713
  onMouseWheel;
@@ -55242,7 +55896,7 @@ stores.inject(MyMetaStore, storeInstance);
55242
55896
  }
55243
55897
  return cssPropertiesToCss({
55244
55898
  top: `${groupBox.headerRect.height / 2}px`,
55245
- left: `calc(50% - 1px)`,
55899
+ left: `calc(50% - 1px)`, // -1px: we want the border to be on the center
55246
55900
  width: `30%`,
55247
55901
  height: `calc(100% - ${groupBox.headerRect.height / 2}px)`,
55248
55902
  "border-left": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
@@ -55294,7 +55948,7 @@ stores.inject(MyMetaStore, storeInstance);
55294
55948
  return "";
55295
55949
  }
55296
55950
  return cssPropertiesToCss({
55297
- top: `calc(50% - 1px)`,
55951
+ top: `calc(50% - 1px)`, // -1px: we want the border to be on the center
55298
55952
  left: `${groupBox.headerRect.width / 2}px`,
55299
55953
  width: `calc(100% - ${groupBox.headerRect.width / 2}px)`,
55300
55954
  height: `30%`,
@@ -55940,6 +56594,13 @@ stores.inject(MyMetaStore, storeInstance);
55940
56594
  "border-color": SELECTION_BORDER_COLOR,
55941
56595
  });
55942
56596
  }
56597
+ get delimitation() {
56598
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
56599
+ return {
56600
+ width,
56601
+ height,
56602
+ };
56603
+ }
55943
56604
  onFocus(selection) {
55944
56605
  this.composerFocusStore.focusTopBarComposer(selection);
55945
56606
  }
@@ -56385,7 +57046,7 @@ stores.inject(MyMetaStore, storeInstance);
56385
57046
  }
56386
57047
  .o-disabled {
56387
57048
  opacity: 0.4;
56388
- pointer: default;
57049
+ cursor: default;
56389
57050
  pointer-events: none;
56390
57051
  }
56391
57052
 
@@ -56551,7 +57212,7 @@ stores.inject(MyMetaStore, storeInstance);
56551
57212
 
56552
57213
  .o-number-input {
56553
57214
  /* Remove number input arrows */
56554
- -moz-appearance: textfield;
57215
+ appearance: textfield;
56555
57216
  &::-webkit-outer-spin-button,
56556
57217
  &::-webkit-inner-spin-button {
56557
57218
  -webkit-appearance: none;
@@ -56594,6 +57255,7 @@ stores.inject(MyMetaStore, storeInstance);
56594
57255
  this.notificationStore = useStore(NotificationStore);
56595
57256
  this.composerFocusStore = useStore(ComposerFocusStore);
56596
57257
  this.sidePanel = useStore(SidePanelStore);
57258
+ useStore(ArrayFormulaHighlight);
56597
57259
  this.keyDownMapping = {
56598
57260
  "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
56599
57261
  "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
@@ -56696,7 +57358,7 @@ stores.inject(MyMetaStore, storeInstance);
56696
57358
  const gridColSize = GROUP_LAYER_WIDTH * this.rowLayers.length;
56697
57359
  const gridRowSize = GROUP_LAYER_WIDTH * this.colLayers.length;
56698
57360
  return cssPropertiesToCss({
56699
- "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`,
57361
+ "grid-template-columns": `${gridColSize ? gridColSize + 2 : 0}px auto`, // +2: margins
56700
57362
  "grid-template-rows": `${gridRowSize ? gridRowSize + 2 : 0}px auto`,
56701
57363
  });
56702
57364
  }
@@ -57428,14 +58090,6 @@ stores.inject(MyMetaStore, storeInstance);
57428
58090
  this.revertBefore(operationId);
57429
58091
  this.tree.drop(operationId);
57430
58092
  }
57431
- getRevertedExecution() {
57432
- const data = [];
57433
- const operations = this.tree.revertedExecution(this.HEAD_BRANCH);
57434
- for (const { operation } of operations) {
57435
- data.push(operation.data);
57436
- }
57437
- return data;
57438
- }
57439
58093
  /**
57440
58094
  * Revert the state as it was *before* the given operation was executed.
57441
58095
  */
@@ -58224,9 +58878,15 @@ stores.inject(MyMetaStore, storeInstance);
58224
58878
  case "bar":
58225
58879
  plot = addBarChart(chart.data);
58226
58880
  break;
58881
+ case "combo":
58882
+ plot = addComboChart(chart.data);
58883
+ break;
58227
58884
  case "line":
58228
58885
  plot = addLineChart(chart.data);
58229
58886
  break;
58887
+ case "scatter":
58888
+ plot = addScatterChart(chart.data);
58889
+ break;
58230
58890
  case "pie":
58231
58891
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
58232
58892
  break;
@@ -58386,6 +59046,79 @@ stores.inject(MyMetaStore, storeInstance);
58386
59046
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58387
59047
  `;
58388
59048
  }
59049
+ function addComboChart(chart) {
59050
+ // gapWitdh and overlap that define the space between clusters (in %) and the overlap between datasets (from -100: completely scattered to 100, completely overlapped)
59051
+ // see gapWidth : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_gapWidth_topic_ID0EFVEQB.html#topic_ID0EFVEQB
59052
+ // see overlap : https://c-rex.net/projects/samples/ooxml/e1/Part4/OOXML_P4_DOCX_overlap_topic_ID0ELYQQB.html#topic_ID0ELYQQB
59053
+ //
59054
+ // overlap and gapWitdh seems to be by default at -20 and 20 in chart.js.
59055
+ // See https://www.chartjs.org/docs/latest/charts/bar.html and https://www.chartjs.org/docs/latest/charts/bar.html#barpercentage-vs-categorypercentage
59056
+ const colors = new ChartColors();
59057
+ const dataSetsNodes = [];
59058
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
59059
+ const color = toXlsxHexColor(colors.next());
59060
+ const dataShapeProperty = shapeProperty({
59061
+ backgroundColor: color,
59062
+ line: { color },
59063
+ });
59064
+ dataSetsNodes.push(dsIndex === "0"
59065
+ ? escapeXml /*xml*/ `
59066
+ <c:ser>
59067
+ <c:idx val="${dsIndex}"/>
59068
+ <c:order val="${dsIndex}"/>
59069
+ ${dataset.label ? escapeXml /*xml*/ `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
59070
+ ${dataShapeProperty}
59071
+ ${chart.labelRange ? escapeXml /*xml*/ `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
59072
+ <c:val> <!-- x-coordinate values -->
59073
+ ${numberRef(dataset.range)}
59074
+ </c:val>
59075
+ </c:ser>
59076
+ `
59077
+ : escapeXml /*xml*/ `
59078
+ <c:ser>
59079
+ <c:idx val="${dsIndex}"/>
59080
+ <c:order val="${dsIndex}"/>
59081
+ <c:smooth val="0"/>
59082
+ <c:marker>
59083
+ <c:symbol val="circle" />
59084
+ <c:size val="5"/>
59085
+ </c:marker>
59086
+ ${dataset.label ? escapeXml `<c:tx>${stringRef(dataset.label)}</c:tx>` : ""}
59087
+ ${dataShapeProperty}
59088
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
59089
+ <c:val> <!-- x-coordinate values -->
59090
+ ${numberRef(dataset.range)}
59091
+ </c:val>
59092
+ </c:ser>
59093
+ `);
59094
+ }
59095
+ // Excel does not support this feature
59096
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
59097
+ const overlap = chart.stacked ? 100 : -20;
59098
+ return escapeXml /*xml*/ `
59099
+ <c:barChart>
59100
+ <c:barDir val="col"/>
59101
+ <c:grouping val="clustered"/>
59102
+ <c:overlap val="${overlap}"/>
59103
+ <c:gapWidth val="70"/>
59104
+ <!-- each data marker in the series does not have a different color -->
59105
+ <c:varyColors val="0"/>
59106
+ ${dataSetsNodes[0]}
59107
+ <c:axId val="${catAxId}" />
59108
+ <c:axId val="${valAxId}" />
59109
+ </c:barChart>
59110
+ <c:lineChart>
59111
+ <c:grouping val="standard"/>
59112
+ <!-- each data marker in the series does not have a different color -->
59113
+ <c:varyColors val="0"/>
59114
+ ${joinXmlNodes(dataSetsNodes.slice(1))}
59115
+ <c:axId val="${catAxId}" />
59116
+ <c:axId val="${valAxId}" />
59117
+ </c:lineChart>
59118
+ ${addAx("b", "c:catAx", catAxId, valAxId, { fontColor: chart.fontColor })}
59119
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
59120
+ `;
59121
+ }
58389
59122
  function addLineChart(chart) {
58390
59123
  const colors = new ChartColors();
58391
59124
  const dataSetsNodes = [];
@@ -58431,9 +59164,55 @@ stores.inject(MyMetaStore, storeInstance);
58431
59164
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58432
59165
  `;
58433
59166
  }
59167
+ function addScatterChart(chart) {
59168
+ const colors = new ChartColors();
59169
+ const dataSetsNodes = [];
59170
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
59171
+ dataSetsNodes.push(escapeXml /*xml*/ `
59172
+ <c:ser>
59173
+ <c:idx val="${dsIndex}"/>
59174
+ <c:order val="${dsIndex}"/>
59175
+ <c:smooth val="0"/>
59176
+ <c:spPr>
59177
+ <a:ln w="19050" cap="rnd">
59178
+ <a:noFill/>
59179
+ <a:round/>
59180
+ </a:ln>
59181
+ <a:effectLst/>
59182
+ </c:spPr>
59183
+ <c:marker>
59184
+ <c:symbol val="circle" />
59185
+ <c:size val="5"/>
59186
+ ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
59187
+ </c:marker>
59188
+ ${chart.labelRange
59189
+ ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
59190
+ ${numberRef(chart.labelRange)}
59191
+ </c:xVal>`
59192
+ : ""}
59193
+ <c:yVal> <!-- y-coordinate values -->
59194
+ ${numberRef(dataset.range)}
59195
+ </c:yVal>
59196
+ </c:ser>
59197
+ `);
59198
+ }
59199
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
59200
+ return escapeXml /*xml*/ `
59201
+ <c:scatterChart>
59202
+ <!-- each data marker in the series does not have a different color -->
59203
+ <c:varyColors val="0"/>
59204
+ <c:scatterStyle val="lineMarker"/>
59205
+ ${joinXmlNodes(dataSetsNodes)}
59206
+ <c:axId val="${catAxId}" />
59207
+ <c:axId val="${valAxId}" />
59208
+ </c:scatterChart>
59209
+ ${addAx("b", "c:valAx", catAxId, valAxId, { fontColor: chart.fontColor })}
59210
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
59211
+ `;
59212
+ }
58434
59213
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
58435
59214
  const colors = new ChartColors();
58436
- const maxLength = Math.max(...chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
59215
+ const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
58437
59216
  const doughnutColors = range(0, maxLength).map(() => toXlsxHexColor(colors.next()));
58438
59217
  const dataSetsNodes = [];
58439
59218
  for (const [dsIndex, dataset] of Object.entries(chart.dataSets).reverse()) {
@@ -58571,8 +59350,7 @@ stores.inject(MyMetaStore, storeInstance);
58571
59350
  attrs.push(["t", "b"]);
58572
59351
  }
58573
59352
  else if (forceString || !isNumber(value, DEFAULT_LOCALE)) {
58574
- const { id } = pushElement(content, sharedStrings);
58575
- value = id.toString();
59353
+ value = pushElement(content, sharedStrings);
58576
59354
  attrs.push(["t", "s"]);
58577
59355
  }
58578
59356
  return { attrs, node: escapeXml /*xml*/ `<v>${value}</v>` };
@@ -58697,8 +59475,7 @@ stores.inject(MyMetaStore, storeInstance);
58697
59475
  if (rule.style.fillColor) {
58698
59476
  dxf.fill = { fgColor: { rgb: rule.style.fillColor } };
58699
59477
  }
58700
- const { id } = pushElement(dxf, dxfs);
58701
- ruleAttributes.push(["dxfId", id]);
59478
+ ruleAttributes.push(["dxfId", pushElement(dxf, dxfs)]);
58702
59479
  return escapeXml /*xml*/ `
58703
59480
  <conditionalFormatting sqref="${cf.ranges.join(" ")}">
58704
59481
  <cfRule ${formatAttributes(ruleAttributes)}>
@@ -59311,7 +60088,7 @@ stores.inject(MyMetaStore, storeInstance);
59311
60088
  const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
59312
60089
  const colName = sheetData.cells[colHeaderXc]?.content || `col${i}`;
59313
60090
  const colAttributes = [
59314
- ["id", i + 1],
60091
+ ["id", i + 1], // id cannot be 0
59315
60092
  ["name", colName],
59316
60093
  ];
59317
60094
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
@@ -59527,8 +60304,10 @@ stores.inject(MyMetaStore, storeInstance);
59527
60304
  * https://www.ecma-international.org/publications-and-standards/standards/ecma-376/
59528
60305
  */
59529
60306
  function getXLSX(data) {
60307
+ data = fixLengthySheetNames(data);
60308
+ data = purgeSingleRowTables(data);
59530
60309
  const files = [];
59531
- const construct = getDefaultXLSXStructure();
60310
+ const construct = getDefaultXLSXStructure(data);
59532
60311
  files.push(createWorkbook(data, construct));
59533
60312
  files.push(...createWorksheets(data, construct));
59534
60313
  files.push(createStylesSheet(construct));
@@ -59774,6 +60553,50 @@ stores.inject(MyMetaStore, storeInstance);
59774
60553
  `;
59775
60554
  return createXMLFile(parseXML(xml), "_rels/.rels");
59776
60555
  }
60556
+ /**
60557
+ * Excel sheet names are maximum 31 characters while o-spreadsheet do not have this limit.
60558
+ * This method converts the sheet names to be within the 31 characters limit.
60559
+ * The cells/charts referencing this sheet will be updated accordingly.
60560
+ */
60561
+ function fixLengthySheetNames(data) {
60562
+ const nameMapping = {};
60563
+ const newNames = new Set();
60564
+ for (const sheet of data.sheets) {
60565
+ let newName = sheet.name.slice(0, 31);
60566
+ let i = 1;
60567
+ while (newNames.has(newName)) {
60568
+ newName = newName.slice(0, 31 - String(i).length) + i++;
60569
+ }
60570
+ newNames.add(newName);
60571
+ if (newName !== sheet.name) {
60572
+ nameMapping[sheet.name] = newName;
60573
+ sheet.name = newName;
60574
+ }
60575
+ }
60576
+ if (!Object.keys(nameMapping).length) {
60577
+ return data;
60578
+ }
60579
+ const sheetWithNewNames = Object.keys(nameMapping).sort((a, b) => b.length - a.length);
60580
+ let stringifiedData = JSON.stringify(data);
60581
+ for (const sheetName of sheetWithNewNames) {
60582
+ const regex = new RegExp(`'?${escapeRegExp(sheetName)}'?!`, "g");
60583
+ stringifiedData = stringifiedData.replaceAll(regex, (match) => {
60584
+ const newName = nameMapping[sheetName];
60585
+ return match.replace(sheetName, newName);
60586
+ });
60587
+ }
60588
+ return JSON.parse(stringifiedData);
60589
+ }
60590
+ /** Excel files do not support tables with a single row the defined range
60591
+ * Since those tables are not really useful (no filtering/limited styling)
60592
+ * This function filters out all tables with a single row.
60593
+ */
60594
+ function purgeSingleRowTables(data) {
60595
+ for (const sheet of data.sheets) {
60596
+ sheet.tables = sheet.tables.filter((table) => zoneToDimension(toZone(table.range)).numberOfRows > 1);
60597
+ }
60598
+ return data;
60599
+ }
59777
60600
 
59778
60601
  var Status;
59779
60602
  (function (Status) {
@@ -60027,6 +60850,7 @@ stores.inject(MyMetaStore, storeInstance);
60027
60850
  stateObserver: this.state,
60028
60851
  range: this.range,
60029
60852
  dispatch: this.dispatchFromCorePlugin,
60853
+ canDispatch: this.canDispatch,
60030
60854
  uuidGenerator: this.uuidGenerator,
60031
60855
  custom: this.config.custom,
60032
60856
  external: this.config.external,
@@ -60037,6 +60861,7 @@ stores.inject(MyMetaStore, storeInstance);
60037
60861
  getters: this.getters,
60038
60862
  stateObserver: this.state,
60039
60863
  dispatch: this.dispatch,
60864
+ canDispatch: this.canDispatch,
60040
60865
  selection: this.selection,
60041
60866
  moveClient: this.session.move.bind(this.session),
60042
60867
  custom: this.config.custom,
@@ -60360,7 +61185,7 @@ stores.inject(MyMetaStore, storeInstance);
60360
61185
  const components = {
60361
61186
  Checkbox,
60362
61187
  Section,
60363
- ChartColor,
61188
+ RoundColorPicker,
60364
61189
  ChartDataSeries,
60365
61190
  ChartErrorSection,
60366
61191
  ChartLabelRange,
@@ -60463,9 +61288,9 @@ stores.inject(MyMetaStore, storeInstance);
60463
61288
  exports.tokenize = tokenize;
60464
61289
 
60465
61290
 
60466
- __info__.version = "17.3.0-alpha.1";
60467
- __info__.date = "2024-03-25T09:43:36.072Z";
60468
- __info__.hash = "4095c41";
61291
+ __info__.version = "17.3.0-alpha.3";
61292
+ __info__.date = "2024-04-10T12:28:23.658Z";
61293
+ __info__.hash = "80b5056";
60469
61294
 
60470
61295
 
60471
61296
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);