@odoo/o-spreadsheet 17.3.0-alpha.2 → 17.3.0-alpha.4

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.2
7
- * @date 2024-04-05T14:01:07.060Z
8
- * @hash 8c5a229
6
+ * @version 17.3.0-alpha.4
7
+ * @date 2024-04-15T11:02:51.551Z
8
+ * @hash a32a1df
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -38,6 +38,9 @@
38
38
  const FROZEN_PANE_HEADER_BORDER_COLOR = "#BCBCBC";
39
39
  const FROZEN_PANE_BORDER_COLOR = "#DADFE8";
40
40
  const COMPOSER_ASSISTANT_COLOR = "#9B359B";
41
+ const CHART_WATERFALL_POSITIVE_COLOR = "#006FBE";
42
+ const CHART_WATERFALL_NEGATIVE_COLOR = "#E40000";
43
+ const CHART_WATERFALL_SUBTOTAL_COLOR = "#AAAAAA";
41
44
  // Color picker defaults as upper case HEX to match `toHex`helper
42
45
  const COLOR_PICKER_DEFAULTS = [
43
46
  "#000000",
@@ -189,7 +192,7 @@
189
192
  const DEFAULT_GAUGE_MIDDLE_COLOR = "#f1c232";
190
193
  const DEFAULT_GAUGE_UPPER_COLOR = "#6aa84f";
191
194
  const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
192
- const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6aa84f";
195
+ const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6AA84F";
193
196
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#E06666";
194
197
  const LINE_FILL_TRANSPARENCY = 0.4;
195
198
  // session
@@ -561,7 +564,7 @@
561
564
  if (typeof o1 !== typeof o2)
562
565
  return false;
563
566
  if (typeof o1 !== "object")
564
- return o1 === o2;
567
+ return false;
565
568
  // Objects can have different keys if the values are undefined
566
569
  for (const key in o2) {
567
570
  if (!(key in o1) && o2[key] !== undefined) {
@@ -1885,6 +1888,11 @@
1885
1888
  "REMOVE_CONDITIONAL_FORMAT",
1886
1889
  "CHANGE_CONDITIONAL_FORMAT_PRIORITY",
1887
1890
  ]);
1891
+ const invalidateBordersCommands = new Set([
1892
+ "AUTOFILL_CELL",
1893
+ "SET_BORDER",
1894
+ "SET_ZONE_BORDERS",
1895
+ ]);
1888
1896
  const readonlyAllowedCommands = new Set([
1889
1897
  "START",
1890
1898
  "ACTIVATE_SHEET",
@@ -2116,6 +2124,7 @@
2116
2124
  CommandResult["NoChanges"] = "NoChanges";
2117
2125
  CommandResult["InvalidInputId"] = "InvalidInputId";
2118
2126
  CommandResult["SheetIsHidden"] = "SheetIsHidden";
2127
+ CommandResult["InvalidTableResize"] = "InvalidTableResize";
2119
2128
  })(exports.CommandResult || (exports.CommandResult = {}));
2120
2129
 
2121
2130
  const PLAIN_TEXT_FORMAT = "@"; // see OpenXML spec §18.8.31
@@ -2182,6 +2191,7 @@
2182
2191
  BadExpression: "#BAD_EXPR",
2183
2192
  CircularDependency: "#CYCLE",
2184
2193
  UnknownFunction: "#NAME?",
2194
+ DivisionByZero: "#DIV/0!",
2185
2195
  GenericError: "#ERROR",
2186
2196
  };
2187
2197
  const errorTypes = new Set(Object.values(CellErrorType));
@@ -2220,9 +2230,9 @@
2220
2230
 
2221
2231
  // HELPERS
2222
2232
  const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"];
2223
- function assert(condition, message) {
2233
+ function assert(condition, message, value) {
2224
2234
  if (!condition()) {
2225
- throw new EvaluationError(message);
2235
+ throw new EvaluationError(message, value);
2226
2236
  }
2227
2237
  }
2228
2238
  function inferFormat(data) {
@@ -2300,6 +2310,9 @@
2300
2310
  function assertNumberGreaterThanOrEqualToOne(value) {
2301
2311
  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()));
2302
2312
  }
2313
+ function assertNotZero(value) {
2314
+ assert(() => value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
2315
+ }
2303
2316
  function toString(data) {
2304
2317
  const value = toValue(data);
2305
2318
  switch (typeof value) {
@@ -2509,6 +2522,15 @@
2509
2522
  }
2510
2523
  return generateMatrix(matrix.length, matrix[0].length, (col, row) => fn(matrix[col][row]));
2511
2524
  }
2525
+ function matrixForEach(matrix, fn) {
2526
+ const numberOfCols = matrix.length;
2527
+ const numberOfRows = matrix[0]?.length ?? 0;
2528
+ for (let col = 0; col < numberOfCols; col++) {
2529
+ for (let row = 0; row < numberOfRows; row++) {
2530
+ fn(matrix[col][row]);
2531
+ }
2532
+ }
2533
+ }
2512
2534
  function transposeMatrix(matrix) {
2513
2535
  if (!matrix.length) {
2514
2536
  return [];
@@ -3065,6 +3087,9 @@
3065
3087
  return formattedValue;
3066
3088
  }
3067
3089
  function applyInternalNumberFormat(value, format, locale) {
3090
+ if (value === Infinity) {
3091
+ return "∞" + (format.isPercent ? "%" : "");
3092
+ }
3068
3093
  if (format.isPercent) {
3069
3094
  value = value * 100;
3070
3095
  }
@@ -3408,6 +3433,46 @@
3408
3433
  });
3409
3434
  return convertInternalFormatToFormat(roundedFormat);
3410
3435
  }
3436
+ function humanizeNumber({ value, format }, locale) {
3437
+ const numberFormat = formatLargeNumber({
3438
+ value,
3439
+ format,
3440
+ }, undefined, locale);
3441
+ return formatValue(value, { format: numberFormat, locale });
3442
+ }
3443
+ function formatLargeNumber(arg, unit, locale) {
3444
+ let value = 0;
3445
+ try {
3446
+ value = Math.abs(toNumber(arg?.value, locale));
3447
+ }
3448
+ catch (e) {
3449
+ return "";
3450
+ }
3451
+ const format = arg?.format;
3452
+ if (unit !== undefined) {
3453
+ const postFix = unit?.value;
3454
+ switch (postFix) {
3455
+ case "k":
3456
+ return createLargeNumberFormat(format, 1e3, "k");
3457
+ case "m":
3458
+ return createLargeNumberFormat(format, 1e6, "m");
3459
+ case "b":
3460
+ return createLargeNumberFormat(format, 1e9, "b");
3461
+ default:
3462
+ throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
3463
+ }
3464
+ }
3465
+ if (value < 1e5) {
3466
+ return createLargeNumberFormat(format, 0, "");
3467
+ }
3468
+ else if (value < 1e8) {
3469
+ return createLargeNumberFormat(format, 1e3, "k");
3470
+ }
3471
+ else if (value < 1e11) {
3472
+ return createLargeNumberFormat(format, 1e6, "m");
3473
+ }
3474
+ return createLargeNumberFormat(format, 1e9, "b");
3475
+ }
3411
3476
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
3412
3477
  const internalFormat = parseFormat(format || "#,##0");
3413
3478
  const largeNumberFormat = [];
@@ -4600,14 +4665,19 @@
4600
4665
  const textWidthCache = {};
4601
4666
  function computeTextWidth(context, text, style, fontUnit = "pt") {
4602
4667
  const font = computeTextFont(style, fontUnit);
4668
+ context.save();
4669
+ context.font = font;
4670
+ const width = computeCachedTextWidth(context, text);
4671
+ context.restore();
4672
+ return width;
4673
+ }
4674
+ function computeCachedTextWidth(context, text) {
4675
+ const font = context.font;
4603
4676
  if (!textWidthCache[font]) {
4604
4677
  textWidthCache[font] = {};
4605
4678
  }
4606
4679
  if (textWidthCache[font][text] === undefined) {
4607
- context.save();
4608
- context.font = font;
4609
4680
  const textWidth = context.measureText(text).width;
4610
- context.restore();
4611
4681
  textWidthCache[font][text] = textWidth;
4612
4682
  }
4613
4683
  return textWidthCache[font][text];
@@ -4752,6 +4822,42 @@
4752
4822
  function getContextFontSize(font) {
4753
4823
  return Number(font.match(pxRegex)?.[1]);
4754
4824
  }
4825
+ // Inspired from https://stackoverflow.com/a/10511598
4826
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
4827
+ let width = computeCachedTextWidth(ctx, text);
4828
+ if (width <= maxWidth) {
4829
+ return text;
4830
+ }
4831
+ const ellipsis = "…";
4832
+ const ellipsisWidth = computeCachedTextWidth(ctx, text);
4833
+ if (width <= ellipsisWidth) {
4834
+ return text;
4835
+ }
4836
+ let len = text.length;
4837
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
4838
+ text = text.substring(0, len);
4839
+ width = computeCachedTextWidth(ctx, text);
4840
+ }
4841
+ return text + ellipsis;
4842
+ }
4843
+ function splitTextInTwoLines(text) {
4844
+ let spaces = "";
4845
+ while (text[0] === " ") {
4846
+ spaces += " ";
4847
+ text = text.slice(1);
4848
+ }
4849
+ const length = text.length;
4850
+ const middle = Math.floor(length / 2);
4851
+ const leftSpace = text.substring(0, middle).lastIndexOf(" ");
4852
+ const rightSpace = text.substring(middle).indexOf(" ") + middle;
4853
+ if (leftSpace === -1 && rightSpace === middle - 1) {
4854
+ return [spaces + text, ""];
4855
+ }
4856
+ if (leftSpace > length - rightSpace || rightSpace === middle - 1) {
4857
+ return [spaces + text.slice(0, leftSpace), spaces + text.slice(leftSpace + 1)];
4858
+ }
4859
+ return [spaces + text.slice(0, rightSpace), spaces + text.slice(rightSpace + 1)];
4860
+ }
4755
4861
  function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10 //This value is defined to get a good looking stroke
4756
4862
  ) {
4757
4863
  context.fillText(text, position.x, position.y);
@@ -7395,6 +7501,7 @@
7395
7501
  };
7396
7502
  const ChartTerms = {
7397
7503
  Series: _t("Series"),
7504
+ BackgroundColor: _t("Background color"),
7398
7505
  Errors: {
7399
7506
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7400
7507
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -9058,6 +9165,62 @@ stores.inject(MyMetaStore, storeInstance);
9058
9165
  }
9059
9166
  }
9060
9167
 
9168
+ /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
9169
+ const waterfallLinesPlugin = {
9170
+ id: "waterfallLinesPlugin",
9171
+ beforeDraw(chart, args, options) {
9172
+ if (!options.showConnectorLines) {
9173
+ return;
9174
+ }
9175
+ // Note: private properties are not in the typing of chartJS (and some of the existing types are missing properties)
9176
+ // so we don't type anything in this file
9177
+ const drawData = chart._metasets?.[0]?.data;
9178
+ if (!drawData) {
9179
+ return;
9180
+ }
9181
+ const ctx = chart.ctx;
9182
+ ctx.save();
9183
+ ctx.setLineDash([3, 2]);
9184
+ for (let i = 0; i < drawData.length; i++) {
9185
+ const bar = drawData[i];
9186
+ if (bar.height === 0) {
9187
+ continue;
9188
+ }
9189
+ const nextBar = getNextNonEmptyBar(drawData, i);
9190
+ if (!nextBar) {
9191
+ break;
9192
+ }
9193
+ const rect = getBarElementRect(bar);
9194
+ const nextBarRect = getBarElementRect(nextBar);
9195
+ const rawBarValues = bar.$context.raw;
9196
+ const value = rawBarValues[1] - rawBarValues[0];
9197
+ const lineY = Math.round(value < 0 ? rect.bottom - 1 : rect.top);
9198
+ const lineStart = Math.round(rect.right);
9199
+ const lineEnd = Math.round(nextBarRect.left);
9200
+ ctx.strokeStyle = "#999";
9201
+ ctx.beginPath();
9202
+ ctx.moveTo(lineStart + 1, lineY + 0.5);
9203
+ ctx.lineTo(lineEnd, lineY + 0.5);
9204
+ ctx.stroke();
9205
+ }
9206
+ ctx.restore();
9207
+ },
9208
+ };
9209
+ function getBarElementRect(bar) {
9210
+ const flipped = bar.base < bar.y; // Bar are flipped for negative values in the dataset
9211
+ return {
9212
+ left: bar.x - bar.width / 2,
9213
+ right: bar.x + bar.width / 2,
9214
+ bottom: flipped ? bar.base + bar.height : bar.y + bar.height,
9215
+ top: flipped ? bar.base : bar.y,
9216
+ };
9217
+ }
9218
+ function getNextNonEmptyBar(bars, startIndex) {
9219
+ return bars.find((bar, i) => i > startIndex && bar.height !== 0);
9220
+ }
9221
+
9222
+ // @ts-ignore
9223
+ window.Chart?.register(waterfallLinesPlugin);
9061
9224
  class ChartJsComponent extends owl.Component {
9062
9225
  static template = "o-spreadsheet-ChartJsComponent";
9063
9226
  static props = {
@@ -9102,10 +9265,7 @@ stores.inject(MyMetaStore, storeInstance);
9102
9265
  else {
9103
9266
  this.chart.data.datasets = [];
9104
9267
  }
9105
- this.chart.config.options.plugins.tooltip = chartData.options.plugins.tooltip;
9106
- this.chart.config.options.plugins.legend = chartData.options.plugins.legend;
9107
- this.chart.config.options.scales = chartData.options?.scales;
9108
- // ?
9268
+ this.chart.config.options = chartData.options;
9109
9269
  this.chart.update("active");
9110
9270
  }
9111
9271
  }
@@ -9430,32 +9590,63 @@ stores.inject(MyMetaStore, storeInstance);
9430
9590
  }
9431
9591
  return true;
9432
9592
  }
9433
- // ---------------------------------------------------------------------------
9434
- // Scorecard
9435
- // ---------------------------------------------------------------------------
9436
- function getBaselineText(baseline, keyValue, baselineMode, locale) {
9593
+ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9594
+ const { x, y } = getters.getMainViewportCoordinates();
9595
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9596
+ const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9597
+ const position = {
9598
+ x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9599
+ y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9600
+ }; // Position at the center of the scrollable viewport
9601
+ return position;
9602
+ }
9603
+
9604
+ function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
9437
9605
  if (!baseline) {
9438
9606
  return "";
9439
9607
  }
9440
9608
  else if (baselineMode === "text" ||
9441
9609
  keyValue?.type !== CellValueType.number ||
9442
9610
  baseline.type !== CellValueType.number) {
9611
+ if (humanize) {
9612
+ return humanizeNumber(baseline, locale);
9613
+ }
9443
9614
  return baseline.formattedValue;
9444
9615
  }
9616
+ let { value, format } = baseline;
9617
+ if (baselineMode === "progress") {
9618
+ value = keyValue.value / value;
9619
+ format = "0.0%";
9620
+ }
9445
9621
  else {
9446
- let diff = keyValue.value - baseline.value;
9447
- if (baselineMode === "percentage" && diff !== 0) {
9448
- diff = (diff / baseline.value) * 100;
9622
+ value = Math.abs(keyValue.value - value);
9623
+ if (baselineMode === "percentage" && value !== 0) {
9624
+ value = value / baseline.value;
9449
9625
  }
9450
- if (baselineMode !== "percentage" && baseline.format) {
9451
- return formatValue(diff, { format: baseline.format, locale });
9626
+ if (baselineMode === "percentage") {
9627
+ format = "0.0%";
9452
9628
  }
9453
- const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();
9454
- return baselineMode === "percentage" ? baselineStr + "%" : baselineStr;
9629
+ if (!format) {
9630
+ value = Math.round(value * 100) / 100;
9631
+ }
9632
+ }
9633
+ if (humanize) {
9634
+ return humanizeNumber({ value, format }, locale);
9635
+ }
9636
+ return formatValue(value, { format, locale });
9637
+ }
9638
+ function getKeyValueText(keyValueCell, humanize, locale) {
9639
+ if (!keyValueCell) {
9640
+ return "";
9455
9641
  }
9642
+ if (humanize) {
9643
+ return humanizeNumber(keyValueCell, locale);
9644
+ }
9645
+ return keyValueCell.formattedValue ?? String(keyValueCell.value ?? "");
9456
9646
  }
9457
9647
  function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
9458
9648
  if (baselineMode === "text" ||
9649
+ baselineMode === "progress" ||
9459
9650
  baseline?.type !== CellValueType.number ||
9460
9651
  keyValue?.type !== CellValueType.number) {
9461
9652
  return undefined;
@@ -9484,17 +9675,6 @@ stores.inject(MyMetaStore, storeInstance);
9484
9675
  }
9485
9676
  return "neutral";
9486
9677
  }
9487
- function getChartPositionAtCenterOfViewport(getters, chartSize) {
9488
- const { x, y } = getters.getMainViewportCoordinates();
9489
- const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9490
- const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9491
- const position = {
9492
- x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9493
- y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9494
- }; // Position at the center of the scrollable viewport
9495
- return position;
9496
- }
9497
-
9498
9678
  function checkKeyValue(definition) {
9499
9679
  return definition.keyValue && !rangeReference.test(definition.keyValue)
9500
9680
  ? "InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */
@@ -9512,10 +9692,12 @@ stores.inject(MyMetaStore, storeInstance);
9512
9692
  baseline;
9513
9693
  baselineMode;
9514
9694
  baselineDescr;
9695
+ progressBar = false;
9515
9696
  background;
9516
9697
  baselineColorUp;
9517
9698
  baselineColorDown;
9518
9699
  fontColor;
9700
+ humanize;
9519
9701
  type = "scorecard";
9520
9702
  constructor(definition, sheetId, getters) {
9521
9703
  super(definition, sheetId, getters);
@@ -9526,6 +9708,7 @@ stores.inject(MyMetaStore, storeInstance);
9526
9708
  this.background = definition.background;
9527
9709
  this.baselineColorUp = definition.baselineColorUp;
9528
9710
  this.baselineColorDown = definition.baselineColorDown;
9711
+ this.humanize = definition.humanize ?? false;
9529
9712
  }
9530
9713
  static validateChartDefinition(validator, definition) {
9531
9714
  return validator.checkValidations(definition, checkKeyValue, checkBaseline);
@@ -9595,6 +9778,7 @@ stores.inject(MyMetaStore, storeInstance);
9595
9778
  keyValue: keyValue
9596
9779
  ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
9597
9780
  : undefined,
9781
+ humanize: this.humanize,
9598
9782
  };
9599
9783
  }
9600
9784
  getDefinitionForExcel() {
@@ -9620,7 +9804,7 @@ stores.inject(MyMetaStore, storeInstance);
9620
9804
  if (structure.title) {
9621
9805
  ctx.font = structure.title.style.font;
9622
9806
  ctx.fillStyle = structure.title.style.color;
9623
- ctx.fillText(structure.title.text, structure.title.position.x, structure.title.position.y);
9807
+ ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
9624
9808
  }
9625
9809
  if (structure.baseline) {
9626
9810
  ctx.font = structure.baseline.style.font;
@@ -9647,20 +9831,41 @@ stores.inject(MyMetaStore, storeInstance);
9647
9831
  ctx.restore();
9648
9832
  }
9649
9833
  if (structure.baselineDescr) {
9650
- ctx.font = structure.baselineDescr.style.font;
9651
- ctx.fillStyle = structure.baselineDescr.style.color;
9652
- ctx.fillText(structure.baselineDescr.text, structure.baselineDescr.position.x, structure.baselineDescr.position.y);
9834
+ const descr = structure.baselineDescr[0];
9835
+ ctx.font = descr.style.font;
9836
+ ctx.fillStyle = descr.style.color;
9837
+ for (const description of structure.baselineDescr) {
9838
+ ctx.fillText(clipTextWithEllipsis(ctx, description.text, canvas.width - description.position.x), description.position.x, description.position.y);
9839
+ }
9653
9840
  }
9654
9841
  if (structure.key) {
9655
9842
  ctx.font = structure.key.style.font;
9656
9843
  ctx.fillStyle = structure.key.style.color;
9657
9844
  drawDecoratedText(ctx, structure.key.text, structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
9658
9845
  }
9846
+ if (structure.progressBar) {
9847
+ ctx.fillStyle = structure.progressBar.style.backgroundColor;
9848
+ ctx.beginPath();
9849
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width, structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9850
+ ctx.fill();
9851
+ ctx.fillStyle = structure.progressBar.style.color;
9852
+ ctx.beginPath();
9853
+ if (structure.progressBar.value > 0) {
9854
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width *
9855
+ Math.max(0, Math.min(1.0, structure.progressBar.value)), structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9856
+ }
9857
+ else {
9858
+ const width = structure.progressBar.dimension.width *
9859
+ Math.max(0, Math.min(1.0, -structure.progressBar.value));
9860
+ 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);
9861
+ }
9862
+ ctx.fill();
9863
+ }
9659
9864
  }
9660
9865
  function createScorecardChartRuntime(chart, getters) {
9661
- let keyValue = "";
9662
9866
  let formattedKeyValue = "";
9663
9867
  let keyValueCell;
9868
+ const locale = getters.getLocale();
9664
9869
  if (chart.keyValue) {
9665
9870
  const keyValuePosition = {
9666
9871
  sheetId: chart.keyValue.sheetId,
@@ -9668,31 +9873,33 @@ stores.inject(MyMetaStore, storeInstance);
9668
9873
  row: chart.keyValue.zone.top,
9669
9874
  };
9670
9875
  keyValueCell = getters.getEvaluatedCell(keyValuePosition);
9671
- keyValue = String(keyValueCell.value ?? "");
9672
- formattedKeyValue = keyValueCell.formattedValue;
9876
+ formattedKeyValue = getKeyValueText(keyValueCell, chart.humanize ?? false, locale);
9673
9877
  }
9674
9878
  let baselineCell;
9675
9879
  const baseline = chart.baseline;
9676
9880
  if (baseline) {
9677
9881
  const baselinePosition = {
9678
- sheetId: chart.baseline.sheetId,
9679
- col: chart.baseline.zone.left,
9680
- row: chart.baseline.zone.top,
9882
+ sheetId: baseline.sheetId,
9883
+ col: baseline.zone.left,
9884
+ row: baseline.zone.top,
9681
9885
  };
9682
9886
  baselineCell = getters.getEvaluatedCell(baselinePosition);
9683
9887
  }
9684
9888
  const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
9685
- const locale = getters.getLocale();
9889
+ const baselineDisplay = getBaselineText(baselineCell, keyValueCell, chart.baselineMode, chart.humanize ?? false, locale);
9890
+ const baselineValue = chart.baselineMode === "progress" && isNumber(baselineDisplay, locale)
9891
+ ? toNumber(baselineDisplay, locale)
9892
+ : 0;
9686
9893
  return {
9687
9894
  title: _t(chart.title),
9688
- keyValue: formattedKeyValue || keyValue,
9689
- baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode, locale),
9895
+ keyValue: formattedKeyValue,
9896
+ baselineDisplay,
9690
9897
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
9691
9898
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
9692
- baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
9899
+ baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr ? _t(chart.baselineDescr) : "",
9693
9900
  fontColor,
9694
9901
  background,
9695
- baselineStyle: chart.baselineMode !== "percentage" && baseline
9902
+ baselineStyle: chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
9696
9903
  ? getters.getCellStyle({
9697
9904
  sheetId: baseline.sheetId,
9698
9905
  col: baseline.zone.left,
@@ -9706,17 +9913,21 @@ stores.inject(MyMetaStore, storeInstance);
9706
9913
  row: chart.keyValue.zone.top,
9707
9914
  })
9708
9915
  : undefined,
9916
+ progressBar: chart.baselineMode === "progress"
9917
+ ? {
9918
+ value: baselineValue,
9919
+ color: baselineValue > 0 ? chart.baselineColorUp : chart.baselineColorDown,
9920
+ }
9921
+ : undefined,
9709
9922
  };
9710
9923
  }
9711
9924
 
9712
9925
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
9713
9926
  const TITLE_FONT_SIZE = 18;
9714
- const BASELINE_BOX_HEIGHT_RATIO = 0.35;
9715
- const KEY_BOX_HEIGHT_RATIO = 0.65;
9716
- /** Baseline description should have a smaller font than the baseline */
9717
- const BASELINE_DESCR_FONT_RATIO = 0.9;
9718
- /* Padding at the border of the chart, in percentage of the chart width */
9719
- const CHART_PADDING_RATIO = 0.02;
9927
+ const KEY_BOX_HEIGHT_RATIO = 0.8;
9928
+ /* Padding at the border of the chart */
9929
+ const CHART_PADDING = 10;
9930
+ const BOTTOM_PADDING_RATIO = 0.05;
9720
9931
  /**
9721
9932
  * Line height (in em)
9722
9933
  * Having a line heigh =1em (=font size) don't work, the font will overflow.
@@ -9756,31 +9967,37 @@ stores.inject(MyMetaStore, storeInstance);
9756
9967
  },
9757
9968
  };
9758
9969
  const style = this.getTextStyles();
9759
- const { height: titleHeight } = this.getTextDimensions(this.title, style.title.font);
9970
+ let titleHeight = 0;
9760
9971
  if (this.title) {
9972
+ ({ height: titleHeight } = this.getFullTextDimensions(this.title, style.title.font));
9761
9973
  structure.title = {
9762
9974
  text: this.title,
9763
9975
  style: style.title,
9764
9976
  position: {
9765
- x: this.chartPadding,
9766
- y: this.chartPadding + titleHeight,
9977
+ x: CHART_PADDING,
9978
+ y: CHART_PADDING / 2 + titleHeight,
9767
9979
  },
9768
9980
  };
9769
9981
  }
9770
9982
  const baselineArrowSize = style.baselineArrow?.size ?? 0;
9771
- const { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9772
- const { width: baselineDescrWidth } = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font);
9983
+ let { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9984
+ if (!this.baseline) {
9985
+ baselineHeight = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).height;
9986
+ }
9987
+ const baselineDescrWidth = style.baselineDescr.isSplit
9988
+ ? Math.max(...splitTextInTwoLines(this.baselineDescr).map((line) => this.getTextDimensions(line, style.baselineDescr.font).width))
9989
+ : this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).width;
9773
9990
  structure.baseline = {
9774
9991
  text: this.baseline,
9775
9992
  style: style.baselineValue,
9776
9993
  position: {
9777
9994
  x: (this.width - baselineWidth - baselineDescrWidth + baselineArrowSize) / 2,
9778
9995
  y: this.keyValue
9779
- ? this.height - 2 * this.chartPadding
9780
- : this.height - (this.height - titleHeight - baselineHeight) / 2 - this.chartPadding,
9996
+ ? this.height * (1 - BOTTOM_PADDING_RATIO * (this.runtime.progressBar ? 1 : 2))
9997
+ : this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING,
9781
9998
  },
9782
9999
  };
9783
- if (style.baselineArrow) {
10000
+ if (style.baselineArrow && !this.runtime.progressBar) {
9784
10001
  structure.baselineArrow = {
9785
10002
  direction: this.baselineArrow,
9786
10003
  style: style.baselineArrow,
@@ -9791,23 +10008,68 @@ stores.inject(MyMetaStore, storeInstance);
9791
10008
  };
9792
10009
  }
9793
10010
  if (this.baselineDescr) {
9794
- structure.baselineDescr = {
9795
- text: this.baselineDescr,
9796
- style: style.baselineDescr,
10011
+ const position = {
10012
+ x: structure.baseline.position.x + baselineWidth,
10013
+ y: structure.baseline.position.y,
10014
+ };
10015
+ if (style.baselineDescr.isSplit) {
10016
+ const description = splitTextInTwoLines(this.baselineDescr);
10017
+ const measure = this.context.measureText(description[1]);
10018
+ const deltaY = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
10019
+ structure.baselineDescr = [
10020
+ {
10021
+ text: description[0],
10022
+ style: style.baselineDescr,
10023
+ position: {
10024
+ x: position.x,
10025
+ y: position.y - deltaY,
10026
+ },
10027
+ },
10028
+ {
10029
+ text: description[1],
10030
+ style: style.baselineDescr,
10031
+ position,
10032
+ },
10033
+ ];
10034
+ }
10035
+ else {
10036
+ structure.baselineDescr = [
10037
+ {
10038
+ text: this.baselineDescr,
10039
+ style: style.baselineDescr,
10040
+ position,
10041
+ },
10042
+ ];
10043
+ }
10044
+ }
10045
+ let progressBarHeight = 0;
10046
+ if (this.runtime.progressBar) {
10047
+ progressBarHeight = this.height * 0.05;
10048
+ structure.progressBar = {
9797
10049
  position: {
9798
- x: structure.baseline.position.x + baselineWidth,
9799
- y: structure.baseline.position.y,
10050
+ x: 2 * CHART_PADDING,
10051
+ y: this.height * (1 - 2 * BOTTOM_PADDING_RATIO) - baselineHeight - progressBarHeight,
10052
+ },
10053
+ dimension: {
10054
+ height: progressBarHeight,
10055
+ width: this.width - 4 * CHART_PADDING,
10056
+ },
10057
+ value: this.runtime.progressBar.value,
10058
+ style: {
10059
+ color: this.runtime.progressBar.color,
10060
+ backgroundColor: this.secondaryFontColor,
9800
10061
  },
9801
10062
  };
9802
10063
  }
9803
- const { height: keyHeight, width: keyWidth } = this.getTextDimensions(this.keyValue, style.keyValue.font);
10064
+ const { width: keyWidth, height: keyHeight } = this.getFullTextDimensions(this.keyValue, style.keyValue.font);
9804
10065
  if (this.keyValue) {
9805
10066
  structure.key = {
9806
10067
  text: this.keyValue,
9807
10068
  style: style.keyValue,
9808
10069
  position: {
9809
10070
  x: (this.width - keyWidth) / 2,
9810
- y: (this.height - baselineHeight + titleHeight + keyHeight) / 2 - this.chartPadding,
10071
+ y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
10072
+ (titleHeight + keyHeight / (this.baseline || this.baselineDescr ? 2 : 1.2)) / 2,
9811
10073
  },
9812
10074
  };
9813
10075
  }
@@ -9834,9 +10096,6 @@ stores.inject(MyMetaStore, storeInstance);
9834
10096
  get secondaryFontColor() {
9835
10097
  return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
9836
10098
  }
9837
- get chartPadding() {
9838
- return this.width * CHART_PADDING_RATIO;
9839
- }
9840
10099
  getTextDimensions(text, font) {
9841
10100
  this.context.font = font;
9842
10101
  const measure = this.context.measureText(text);
@@ -9845,16 +10104,44 @@ stores.inject(MyMetaStore, storeInstance);
9845
10104
  height: measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent,
9846
10105
  };
9847
10106
  }
10107
+ getFullTextDimensions(text, font) {
10108
+ this.context.font = font;
10109
+ const measure = this.context.measureText(text);
10110
+ return {
10111
+ width: measure.width,
10112
+ height: measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent,
10113
+ };
10114
+ }
9848
10115
  getTextStyles() {
9849
10116
  // If the widest text overflows horizontally, scale it down, and apply the same scaling factors to all the other fonts.
9850
- const maxLineWidth = this.width * (1 - 2 * CHART_PADDING_RATIO);
9851
- const widestElement = this.getWidestElement();
9852
- const baseFontSize = widestElement.getElementMaxFontSize(this.getDrawableHeight(), this);
9853
- const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.context, this));
9854
- let scalingFactor = fontSizeMatchingWidth / baseFontSize;
10117
+ const maxLineWidth = this.width - 2 * CHART_PADDING;
10118
+ const drawableHeight = this.getDrawableHeight();
9855
10119
  // Fonts sizes in px
9856
- const keyFontSize = new KeyValueElement(this.runtime.keyValueStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
9857
- const baselineFontSize = new BaselineElement(this.runtime.baselineStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
10120
+ const keyValueElement = new KeyValueElement(this.runtime.keyValueStyle);
10121
+ const heightFont = keyValueElement.getElementMaxFontSize(drawableHeight, this);
10122
+ const widthFont = getFontSizeMatchingWidth(maxLineWidth, 600, (fontSize) => keyValueElement.getElementWidth(fontSize, this.context, this));
10123
+ const keyFontSize = Math.min(heightFont, widthFont);
10124
+ let baselineValueFontSize = Math.floor(keyFontSize * 0.5);
10125
+ this.context.font = getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic);
10126
+ const baselineText = this.baselineArrow !== "neutral" ? "A " + this.baseline : this.baseline;
10127
+ const baselineValueWidth = computeCachedTextWidth(this.context, baselineText);
10128
+ const remainingWidth = maxLineWidth - baselineValueWidth;
10129
+ let baselineDescrFontSize = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => computeTextWidth(this.context, this.baselineDescr, { fontSize }));
10130
+ let isBaselineSplit = false;
10131
+ if (baselineDescrFontSize < baselineValueFontSize / 2.5) {
10132
+ isBaselineSplit = true;
10133
+ baselineDescrFontSize = Math.floor(baselineValueFontSize / 2.5);
10134
+ for (const line of splitTextInTwoLines(this.baselineDescr)) {
10135
+ const lineWidth = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => {
10136
+ this.context.font = getDefaultContextFont(fontSize);
10137
+ return this.context.measureText(line).width;
10138
+ });
10139
+ baselineDescrFontSize = Math.min(baselineDescrFontSize, lineWidth);
10140
+ }
10141
+ }
10142
+ if (this.runtime.progressBar) {
10143
+ baselineValueFontSize /= 1.5;
10144
+ }
9858
10145
  return {
9859
10146
  title: {
9860
10147
  font: getDefaultContextFont(TITLE_FONT_SIZE),
@@ -9867,7 +10154,7 @@ stores.inject(MyMetaStore, storeInstance);
9867
10154
  underline: this.runtime.keyValueStyle?.underline,
9868
10155
  },
9869
10156
  baselineValue: {
9870
- font: getDefaultContextFont(baselineFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
10157
+ font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
9871
10158
  strikethrough: this.runtime.baselineStyle?.strikethrough,
9872
10159
  underline: this.runtime.baselineStyle?.underline,
9873
10160
  color: this.runtime.baselineStyle?.textColor ||
@@ -9875,33 +10162,25 @@ stores.inject(MyMetaStore, storeInstance);
9875
10162
  this.secondaryFontColor,
9876
10163
  },
9877
10164
  baselineDescr: {
9878
- font: getDefaultContextFont(baselineFontSize * BASELINE_DESCR_FONT_RATIO),
10165
+ font: getDefaultContextFont(baselineDescrFontSize),
10166
+ isSplit: isBaselineSplit,
9879
10167
  color: this.secondaryFontColor,
9880
10168
  },
9881
- baselineArrow: this.baselineArrow === "neutral"
10169
+ baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar
9882
10170
  ? undefined
9883
10171
  : {
9884
- size: this.keyValue ? 0.8 * baselineFontSize : 0,
10172
+ size: this.keyValue ? 0.8 * baselineValueFontSize : 0,
9885
10173
  color: this.runtime.baselineColor || this.secondaryFontColor,
9886
10174
  },
9887
10175
  };
9888
10176
  }
9889
10177
  /** Get the height of the chart minus all the vertical paddings */
9890
10178
  getDrawableHeight() {
9891
- const verticalPadding = 2 * this.chartPadding;
9892
- let availableHeight = this.height - verticalPadding;
10179
+ const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10180
+ let availableHeight = this.height - 2 * verticalPadding;
9893
10181
  availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
9894
10182
  return availableHeight;
9895
10183
  }
9896
- /** Return the element with he widest text in the chart */
9897
- getWidestElement() {
9898
- const baseline = new BaselineElement(this.runtime.baselineStyle);
9899
- const keyValue = new KeyValueElement(this.runtime.keyValueStyle);
9900
- return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.context, this) >
9901
- keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.context, this)
9902
- ? baseline
9903
- : keyValue;
9904
- }
9905
10184
  }
9906
10185
  class ScorecardScalableElement {
9907
10186
  style;
@@ -9910,29 +10189,7 @@ stores.inject(MyMetaStore, storeInstance);
9910
10189
  }
9911
10190
  measureTextWidth(ctx, text, fontSize) {
9912
10191
  ctx.font = getDefaultContextFont(fontSize, this.style.bold, this.style.italic);
9913
- return ctx.measureText(text).width;
9914
- }
9915
- }
9916
- class BaselineElement extends ScorecardScalableElement {
9917
- getElementWidth(fontSize, ctx, chart) {
9918
- if (!chart.runtime) {
9919
- return 0;
9920
- }
9921
- const baselineStr = chart.baseline;
9922
- // Put mock text to simulate the width of the up/down arrow
9923
- const largeText = chart.baselineArrow !== "neutral" ? "A " + baselineStr : baselineStr;
9924
- let textWidth = this.measureTextWidth(ctx, largeText, fontSize);
9925
- // Baseline descr font size should be smaller than baseline font size
9926
- textWidth += this.measureTextWidth(ctx, chart.baselineDescr, fontSize * BASELINE_DESCR_FONT_RATIO);
9927
- return textWidth;
9928
- }
9929
- getElementMaxFontSize(availableHeight, chart) {
9930
- if (!chart.runtime) {
9931
- return 0;
9932
- }
9933
- const haveBaseline = chart.baseline !== "" || chart.baselineDescr;
9934
- const maxHeight = haveBaseline ? BASELINE_BOX_HEIGHT_RATIO * availableHeight : 0;
9935
- return maxHeight / LINE_HEIGHT;
10192
+ return computeCachedTextWidth(ctx, text);
9936
10193
  }
9937
10194
  }
9938
10195
  class KeyValueElement extends ScorecardScalableElement {
@@ -10862,33 +11119,6 @@ stores.inject(MyMetaStore, storeInstance);
10862
11119
  // -----------------------------------------------------------------------------
10863
11120
  // FORMAT.LARGE.NUMBER
10864
11121
  // -----------------------------------------------------------------------------
10865
- function formatLargeNumber(arg, unit, locale) {
10866
- const value = Math.abs(toNumber(arg?.value, locale));
10867
- const format = arg?.format;
10868
- if (unit !== undefined) {
10869
- const postFix = unit?.value;
10870
- switch (postFix) {
10871
- case "k":
10872
- return createLargeNumberFormat(format, 1e3, "k");
10873
- case "m":
10874
- return createLargeNumberFormat(format, 1e6, "m");
10875
- case "b":
10876
- return createLargeNumberFormat(format, 1e9, "b");
10877
- default:
10878
- throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
10879
- }
10880
- }
10881
- if (value < 1e5) {
10882
- return createLargeNumberFormat(format, 0, "");
10883
- }
10884
- else if (value < 1e8) {
10885
- return createLargeNumberFormat(format, 1e3, "k");
10886
- }
10887
- else if (value < 1e11) {
10888
- return createLargeNumberFormat(format, 1e6, "m");
10889
- }
10890
- return createLargeNumberFormat(format, 1e9, "b");
10891
- }
10892
11122
  const FORMAT_LARGE_NUMBER = {
10893
11123
  description: _t("Apply a large number format"),
10894
11124
  args: [
@@ -11050,7 +11280,7 @@ stores.inject(MyMetaStore, storeInstance);
11050
11280
  compute: function (x, y) {
11051
11281
  const _x = toNumber(x, this.locale);
11052
11282
  const _y = toNumber(y, this.locale);
11053
- assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
11283
+ assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
11054
11284
  return Math.atan2(_y, _x);
11055
11285
  },
11056
11286
  isExported: true,
@@ -11180,7 +11410,7 @@ stores.inject(MyMetaStore, storeInstance);
11180
11410
  returns: ["NUMBER"],
11181
11411
  compute: function (angle) {
11182
11412
  const _angle = toNumber(angle, this.locale);
11183
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11413
+ assertNotZero(_angle);
11184
11414
  return 1 / Math.tan(_angle);
11185
11415
  },
11186
11416
  isExported: true,
@@ -11194,7 +11424,7 @@ stores.inject(MyMetaStore, storeInstance);
11194
11424
  returns: ["NUMBER"],
11195
11425
  compute: function (value) {
11196
11426
  const _value = toNumber(value, this.locale);
11197
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11427
+ assertNotZero(_value);
11198
11428
  return 1 / Math.tanh(_value);
11199
11429
  },
11200
11430
  isExported: true,
@@ -11322,7 +11552,7 @@ stores.inject(MyMetaStore, storeInstance);
11322
11552
  returns: ["NUMBER"],
11323
11553
  compute: function (angle) {
11324
11554
  const _angle = toNumber(angle, this.locale);
11325
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11555
+ assertNotZero(_angle);
11326
11556
  return 1 / Math.sin(_angle);
11327
11557
  },
11328
11558
  isExported: true,
@@ -11336,7 +11566,7 @@ stores.inject(MyMetaStore, storeInstance);
11336
11566
  returns: ["NUMBER"],
11337
11567
  compute: function (value) {
11338
11568
  const _value = toNumber(value, this.locale);
11339
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11569
+ assertNotZero(_value);
11340
11570
  return 1 / Math.sinh(_value);
11341
11571
  },
11342
11572
  isExported: true,
@@ -11535,7 +11765,7 @@ stores.inject(MyMetaStore, storeInstance);
11535
11765
  // MOD
11536
11766
  // -----------------------------------------------------------------------------
11537
11767
  function mod(dividend, divisor) {
11538
- assert(() => divisor !== 0, _t("The divisor must be different from 0."));
11768
+ assert(() => divisor !== 0, _t("The divisor must be different from 0."), CellErrorType.DivisionByZero);
11539
11769
  const modulus = dividend % divisor;
11540
11770
  // -42 % 10 = -2 but we want 8, so need the code below
11541
11771
  if ((modulus > 0 && divisor < 0) || (modulus < 0 && divisor > 0)) {
@@ -12104,7 +12334,7 @@ stores.inject(MyMetaStore, storeInstance);
12104
12334
  count += 1;
12105
12335
  return acc + a;
12106
12336
  }, 0, locale);
12107
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12337
+ assertNotZero(count);
12108
12338
  return sum / count;
12109
12339
  }
12110
12340
  function countNumbers(values, locale) {
@@ -12171,7 +12401,7 @@ stores.inject(MyMetaStore, storeInstance);
12171
12401
  function covariance(dataY, dataX, isSample) {
12172
12402
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
12173
12403
  const count = flatDataY.length;
12174
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12404
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12175
12405
  let sumY = 0;
12176
12406
  let sumX = 0;
12177
12407
  for (let i = 0; i < count; i++) {
@@ -12194,7 +12424,7 @@ stores.inject(MyMetaStore, storeInstance);
12194
12424
  count += 1;
12195
12425
  return acc + a;
12196
12426
  }, 0, locale);
12197
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12427
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12198
12428
  const average = sum / count;
12199
12429
  return (reduceFunction(args, (acc, a) => acc + Math.pow(a - average, 2), 0, locale) /
12200
12430
  (count - (isSample ? 1 : 0)));
@@ -12391,7 +12621,7 @@ stores.inject(MyMetaStore, storeInstance);
12391
12621
  count += 1;
12392
12622
  return acc + a;
12393
12623
  }, 0, this.locale);
12394
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12624
+ assertNotZero(count);
12395
12625
  const average = sum / count;
12396
12626
  return reduceNumbers(values, (acc, a) => acc + Math.abs(average - a), 0, this.locale) / count;
12397
12627
  },
@@ -12463,7 +12693,7 @@ stores.inject(MyMetaStore, storeInstance);
12463
12693
  }
12464
12694
  }
12465
12695
  }
12466
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12696
+ assertNotZero(count);
12467
12697
  return { value: sum / count, format: inferFormat(args[0]) };
12468
12698
  },
12469
12699
  };
@@ -12483,7 +12713,7 @@ stores.inject(MyMetaStore, storeInstance);
12483
12713
  count += 1;
12484
12714
  return acc + a;
12485
12715
  }, 0, this.locale);
12486
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12716
+ assertNotZero(count);
12487
12717
  return {
12488
12718
  value: sum / count,
12489
12719
  format: inferFormat(args[0]),
@@ -12513,7 +12743,7 @@ stores.inject(MyMetaStore, storeInstance);
12513
12743
  sum += value;
12514
12744
  }
12515
12745
  }, this.locale);
12516
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12746
+ assertNotZero(count);
12517
12747
  return sum / count;
12518
12748
  },
12519
12749
  isExported: true,
@@ -12542,7 +12772,7 @@ stores.inject(MyMetaStore, storeInstance);
12542
12772
  sum += value;
12543
12773
  }
12544
12774
  }, this.locale);
12545
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12775
+ assertNotZero(count);
12546
12776
  return sum / count;
12547
12777
  },
12548
12778
  isExported: true,
@@ -17967,7 +18197,7 @@ stores.inject(MyMetaStore, storeInstance);
17967
18197
  returns: ["NUMBER"],
17968
18198
  compute: function (dividend, divisor) {
17969
18199
  const _divisor = toNumber(divisor, this.locale);
17970
- assert(() => _divisor !== 0, _t("The divisor must be different from zero."));
18200
+ assert(() => _divisor !== 0, _t("The divisor must be different from zero."), CellErrorType.DivisionByZero);
17971
18201
  return {
17972
18202
  value: toNumber(dividend, this.locale) / _divisor,
17973
18203
  format: dividend?.format || divisor?.format,
@@ -18660,7 +18890,7 @@ stores.inject(MyMetaStore, storeInstance);
18660
18890
  }
18661
18891
  const descr = addMetaInfoFromArg(addDescr);
18662
18892
  validateArguments(descr.args);
18663
- this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr)), name);
18893
+ this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr), name), name);
18664
18894
  super.add(name, descr);
18665
18895
  return this;
18666
18896
  }
@@ -18699,9 +18929,7 @@ stores.inject(MyMetaStore, storeInstance);
18699
18929
  // so we fallback to a generic error
18700
18930
  if (hasStringValue(e) && isEvaluationError(e.value)) {
18701
18931
  if (hasStringMessage(e)) {
18702
- if (e.message?.includes("[[FUNCTION_NAME]]")) {
18703
- e.message = e.message.replace("[[FUNCTION_NAME]]", functionName);
18704
- }
18932
+ replaceFunctionNamePlaceholder(e, functionName);
18705
18933
  }
18706
18934
  return e;
18707
18935
  }
@@ -18716,21 +18944,29 @@ stores.inject(MyMetaStore, storeInstance);
18716
18944
  return (obj?.message !== undefined &&
18717
18945
  typeof obj.message === "string");
18718
18946
  }
18719
- function addResultHandling(compute) {
18720
- return function (...args) {
18947
+ function addResultHandling(compute, functionName) {
18948
+ return function computeWithResultHandling(...args) {
18721
18949
  const result = compute.apply(this, args);
18722
18950
  if (!isMatrix(result)) {
18723
18951
  if (typeof result === "object" && result !== null && "value" in result) {
18952
+ replaceFunctionNamePlaceholder(result, functionName);
18724
18953
  return result;
18725
18954
  }
18726
18955
  return { value: result };
18727
18956
  }
18728
18957
  if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
18958
+ matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
18729
18959
  return result;
18730
18960
  }
18731
18961
  return matrixMap(result, (row) => ({ value: row }));
18732
18962
  };
18733
18963
  }
18964
+ function replaceFunctionNamePlaceholder(fPayload, functionName) {
18965
+ // for performance reasons: change in place and only if needed
18966
+ if (fPayload.message?.includes("[[FUNCTION_NAME]]")) {
18967
+ fPayload.message = fPayload.message.replace("[[FUNCTION_NAME]]", functionName);
18968
+ }
18969
+ }
18734
18970
  const functionRegistry = new FunctionRegistry();
18735
18971
  for (let category of categories) {
18736
18972
  const fns = category.functions;
@@ -21012,7 +21248,21 @@ stores.inject(MyMetaStore, storeInstance);
21012
21248
  return new ScatterChart(definition, this.sheetId, this.getters);
21013
21249
  }
21014
21250
  getDefinitionForExcel() {
21015
- return undefined; // TODO
21251
+ // Excel does not support aggregating labels
21252
+ if (this.aggregated) {
21253
+ return undefined;
21254
+ }
21255
+ const dataSets = this.dataSets
21256
+ .map((ds) => toExcelDataset(this.getters, ds))
21257
+ .filter((ds) => ds.range !== "");
21258
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
21259
+ return {
21260
+ ...this.getDefinition(),
21261
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
21262
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
21263
+ dataSets,
21264
+ labelRange,
21265
+ };
21016
21266
  }
21017
21267
  copyForSheetId(sheetId) {
21018
21268
  const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
@@ -21059,6 +21309,263 @@ stores.inject(MyMetaStore, storeInstance);
21059
21309
  return { chartJsConfig, background };
21060
21310
  }
21061
21311
 
21312
+ class WaterfallChart extends AbstractChart {
21313
+ dataSets;
21314
+ labelRange;
21315
+ background;
21316
+ verticalAxisPosition;
21317
+ legendPosition;
21318
+ aggregated;
21319
+ type = "waterfall";
21320
+ dataSetsHaveTitle;
21321
+ showSubTotals;
21322
+ firstValueAsSubtotal;
21323
+ showConnectorLines;
21324
+ positiveValuesColor;
21325
+ negativeValuesColor;
21326
+ subTotalValuesColor;
21327
+ constructor(definition, sheetId, getters) {
21328
+ super(definition, sheetId, getters);
21329
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
21330
+ this.labelRange = createRange(getters, sheetId, definition.labelRange);
21331
+ this.background = definition.background;
21332
+ this.verticalAxisPosition = definition.verticalAxisPosition;
21333
+ this.legendPosition = definition.legendPosition;
21334
+ this.aggregated = definition.aggregated;
21335
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
21336
+ this.showSubTotals = definition.showSubTotals;
21337
+ this.showConnectorLines = definition.showConnectorLines;
21338
+ this.positiveValuesColor = definition.positiveValuesColor;
21339
+ this.negativeValuesColor = definition.negativeValuesColor;
21340
+ this.subTotalValuesColor = definition.subTotalValuesColor;
21341
+ this.firstValueAsSubtotal = definition.firstValueAsSubtotal;
21342
+ }
21343
+ static transformDefinition(definition, executed) {
21344
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
21345
+ }
21346
+ static validateChartDefinition(validator, definition) {
21347
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
21348
+ }
21349
+ static getDefinitionFromContextCreation(context) {
21350
+ return {
21351
+ background: context.background,
21352
+ dataSets: context.range ? context.range : [],
21353
+ dataSetsHaveTitle: false,
21354
+ aggregated: context.aggregated ?? false,
21355
+ legendPosition: "top",
21356
+ title: context.title || "",
21357
+ type: "waterfall",
21358
+ verticalAxisPosition: "left",
21359
+ labelRange: context.auxiliaryRange || undefined,
21360
+ showSubTotals: true,
21361
+ showConnectorLines: true,
21362
+ firstValueAsSubtotal: false,
21363
+ };
21364
+ }
21365
+ getContextCreation() {
21366
+ return {
21367
+ background: this.background,
21368
+ title: this.title,
21369
+ range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
21370
+ auxiliaryRange: this.labelRange
21371
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
21372
+ : undefined,
21373
+ aggregated: this.aggregated,
21374
+ };
21375
+ }
21376
+ copyForSheetId(sheetId) {
21377
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
21378
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
21379
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
21380
+ return new WaterfallChart(definition, sheetId, this.getters);
21381
+ }
21382
+ copyInSheetId(sheetId) {
21383
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
21384
+ return new WaterfallChart(definition, sheetId, this.getters);
21385
+ }
21386
+ getDefinition() {
21387
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
21388
+ }
21389
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
21390
+ return {
21391
+ type: "waterfall",
21392
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
21393
+ background: this.background,
21394
+ dataSets: dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, targetSheetId || this.sheetId)),
21395
+ legendPosition: this.legendPosition,
21396
+ verticalAxisPosition: this.verticalAxisPosition,
21397
+ labelRange: labelRange
21398
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
21399
+ : undefined,
21400
+ title: this.title,
21401
+ aggregated: this.aggregated,
21402
+ showSubTotals: this.showSubTotals,
21403
+ showConnectorLines: this.showConnectorLines,
21404
+ positiveValuesColor: this.positiveValuesColor,
21405
+ negativeValuesColor: this.negativeValuesColor,
21406
+ subTotalValuesColor: this.subTotalValuesColor,
21407
+ firstValueAsSubtotal: this.firstValueAsSubtotal,
21408
+ };
21409
+ }
21410
+ getDefinitionForExcel() {
21411
+ // TODO: implement export excel
21412
+ return undefined;
21413
+ }
21414
+ updateRanges(applyChange) {
21415
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
21416
+ if (!isStale) {
21417
+ return this;
21418
+ }
21419
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
21420
+ return new WaterfallChart(definition, this.sheetId, this.getters);
21421
+ }
21422
+ }
21423
+ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat) {
21424
+ const { locale, format } = localeFormat;
21425
+ const fontColor = chartFontColor(chart.background);
21426
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
21427
+ const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
21428
+ const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
21429
+ const subTotalColor = chart.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
21430
+ const legend = {
21431
+ labels: {
21432
+ generateLabels: () => {
21433
+ const legendValues = [
21434
+ { text: _t("Positive values"), fontColor, fillStyle: positiveColor },
21435
+ { text: _t("Negative values"), fontColor, fillStyle: negativeColor },
21436
+ ];
21437
+ if (chart.showSubTotals || chart.firstValueAsSubtotal) {
21438
+ legendValues.push({
21439
+ text: _t("Subtotals"),
21440
+ fontColor,
21441
+ fillStyle: subTotalColor,
21442
+ });
21443
+ }
21444
+ return legendValues;
21445
+ },
21446
+ },
21447
+ };
21448
+ if (chart.legendPosition === "none") {
21449
+ legend.display = false;
21450
+ }
21451
+ else {
21452
+ legend.position = chart.legendPosition;
21453
+ }
21454
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
21455
+ config.options.layout = {
21456
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
21457
+ };
21458
+ config.options.scales = {
21459
+ x: {
21460
+ ticks: {
21461
+ padding: 5,
21462
+ color: fontColor,
21463
+ },
21464
+ grid: {
21465
+ display: false,
21466
+ },
21467
+ },
21468
+ y: {
21469
+ position: chart.verticalAxisPosition,
21470
+ ticks: {
21471
+ color: fontColor,
21472
+ callback: (value) => {
21473
+ value = Number(value);
21474
+ if (isNaN(value))
21475
+ return value;
21476
+ return formatValue(value, {
21477
+ locale,
21478
+ format: !format && Math.abs(value) > 1000 ? "#,##" : format,
21479
+ });
21480
+ },
21481
+ },
21482
+ grid: {
21483
+ lineWidth: (context) => {
21484
+ return context.tick.value === 0 ? 2 : 1;
21485
+ },
21486
+ },
21487
+ },
21488
+ };
21489
+ config.options.plugins.tooltip = {
21490
+ callbacks: {
21491
+ label: function (tooltipItem) {
21492
+ const [lastValue, currentValue] = tooltipItem.raw;
21493
+ const yLabel = currentValue - lastValue;
21494
+ const dataSeriesIndex = Math.floor(tooltipItem.dataIndex / labels.length);
21495
+ const dataSeriesLabel = dataSeriesLabels[dataSeriesIndex];
21496
+ const toolTipFormat = !format && Math.abs(yLabel) > 1000 ? "#,##" : format;
21497
+ const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
21498
+ return dataSeriesLabel ? `${dataSeriesLabel}: ${yLabelStr}` : yLabelStr;
21499
+ },
21500
+ },
21501
+ };
21502
+ config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
21503
+ return config;
21504
+ }
21505
+ function createWaterfallChartRuntime(chart, getters) {
21506
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
21507
+ let labels = labelValues.formattedValues;
21508
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
21509
+ if (chart.dataSetsHaveTitle &&
21510
+ dataSetsValues[0] &&
21511
+ labels.length > dataSetsValues[0].data.length) {
21512
+ labels.shift();
21513
+ }
21514
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
21515
+ if (chart.aggregated) {
21516
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
21517
+ }
21518
+ if (chart.showSubTotals) {
21519
+ labels.push(_t("Subtotal"));
21520
+ }
21521
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
21522
+ const locale = getters.getLocale();
21523
+ const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
21524
+ const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
21525
+ format: dataSetFormat,
21526
+ locale,
21527
+ });
21528
+ config.type = "bar";
21529
+ const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
21530
+ const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
21531
+ const subTotalColor = chart.subTotalValuesColor || CHART_WATERFALL_SUBTOTAL_COLOR;
21532
+ const backgroundColor = [];
21533
+ const datasetValues = [];
21534
+ const dataset = {
21535
+ label: "",
21536
+ data: datasetValues,
21537
+ backgroundColor,
21538
+ };
21539
+ const labelsWithSubTotals = [];
21540
+ let lastValue = 0;
21541
+ for (const dataSetsValue of dataSetsValues) {
21542
+ for (let i = 0; i < dataSetsValue.data.length; i++) {
21543
+ const data = dataSetsValue.data[i];
21544
+ labelsWithSubTotals.push(labels[i]);
21545
+ if (isNaN(Number(data))) {
21546
+ datasetValues.push([lastValue, lastValue]);
21547
+ backgroundColor.push("");
21548
+ continue;
21549
+ }
21550
+ datasetValues.push([lastValue, data + lastValue]);
21551
+ let color = data >= 0 ? positiveColor : negativeColor;
21552
+ if (i === 0 && dataSetsValue === dataSetsValues[0] && chart.firstValueAsSubtotal) {
21553
+ color = subTotalColor;
21554
+ }
21555
+ backgroundColor.push(color);
21556
+ lastValue += data;
21557
+ }
21558
+ if (chart.showSubTotals) {
21559
+ labelsWithSubTotals.push(_t("Subtotal"));
21560
+ datasetValues.push([0, lastValue]);
21561
+ backgroundColor.push(subTotalColor);
21562
+ }
21563
+ }
21564
+ config.data.datasets.push(dataset);
21565
+ config.data.labels = labelsWithSubTotals.map(truncateLabel);
21566
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
21567
+ }
21568
+
21062
21569
  /**
21063
21570
  * This registry is intended to map a cell content (raw string) to
21064
21571
  * an instance of a cell.
@@ -21068,9 +21575,9 @@ stores.inject(MyMetaStore, storeInstance);
21068
21575
  match: (type) => type === "bar",
21069
21576
  createChart: (definition, sheetId, getters) => new BarChart(definition, sheetId, getters),
21070
21577
  getChartRuntime: createBarChartRuntime,
21071
- validateChartDefinition: (validator, definition) => BarChart.validateChartDefinition(validator, definition),
21072
- transformDefinition: (definition, executed) => BarChart.transformDefinition(definition, executed),
21073
- getChartDefinitionFromContextCreation: (context) => BarChart.getDefinitionFromContextCreation(context),
21578
+ validateChartDefinition: BarChart.validateChartDefinition,
21579
+ transformDefinition: BarChart.transformDefinition,
21580
+ getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
21074
21581
  name: _t("Bar"),
21075
21582
  sequence: 10,
21076
21583
  });
@@ -21078,9 +21585,9 @@ stores.inject(MyMetaStore, storeInstance);
21078
21585
  match: (type) => type === "combo",
21079
21586
  createChart: (definition, sheetId, getters) => new ComboChart(definition, sheetId, getters),
21080
21587
  getChartRuntime: createComboChartRuntime,
21081
- validateChartDefinition: (validator, definition) => ComboChart.validateChartDefinition(validator, definition),
21082
- transformDefinition: (definition, executed) => ComboChart.transformDefinition(definition, executed),
21083
- getChartDefinitionFromContextCreation: (context) => ComboChart.getDefinitionFromContextCreation(context),
21588
+ validateChartDefinition: ComboChart.validateChartDefinition,
21589
+ transformDefinition: ComboChart.transformDefinition,
21590
+ getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
21084
21591
  name: _t("Combo"),
21085
21592
  sequence: 15,
21086
21593
  });
@@ -21088,9 +21595,9 @@ stores.inject(MyMetaStore, storeInstance);
21088
21595
  match: (type) => type === "line",
21089
21596
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
21090
21597
  getChartRuntime: createLineChartRuntime,
21091
- validateChartDefinition: (validator, definition) => LineChart.validateChartDefinition(validator, definition),
21092
- transformDefinition: (definition, executed) => LineChart.transformDefinition(definition, executed),
21093
- getChartDefinitionFromContextCreation: (context) => LineChart.getDefinitionFromContextCreation(context),
21598
+ validateChartDefinition: LineChart.validateChartDefinition,
21599
+ transformDefinition: LineChart.transformDefinition,
21600
+ getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
21094
21601
  name: _t("Line"),
21095
21602
  sequence: 20,
21096
21603
  });
@@ -21098,9 +21605,9 @@ stores.inject(MyMetaStore, storeInstance);
21098
21605
  match: (type) => type === "pie",
21099
21606
  createChart: (definition, sheetId, getters) => new PieChart(definition, sheetId, getters),
21100
21607
  getChartRuntime: createPieChartRuntime,
21101
- validateChartDefinition: (validator, definition) => PieChart.validateChartDefinition(validator, definition),
21102
- transformDefinition: (definition, executed) => PieChart.transformDefinition(definition, executed),
21103
- getChartDefinitionFromContextCreation: (context) => PieChart.getDefinitionFromContextCreation(context),
21608
+ validateChartDefinition: PieChart.validateChartDefinition,
21609
+ transformDefinition: PieChart.transformDefinition,
21610
+ getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
21104
21611
  name: _t("Pie"),
21105
21612
  sequence: 30,
21106
21613
  });
@@ -21108,9 +21615,9 @@ stores.inject(MyMetaStore, storeInstance);
21108
21615
  match: (type) => type === "scorecard",
21109
21616
  createChart: (definition, sheetId, getters) => new ScorecardChart$1(definition, sheetId, getters),
21110
21617
  getChartRuntime: createScorecardChartRuntime,
21111
- validateChartDefinition: (validator, definition) => ScorecardChart$1.validateChartDefinition(validator, definition),
21112
- transformDefinition: (definition, executed) => ScorecardChart$1.transformDefinition(definition, executed),
21113
- getChartDefinitionFromContextCreation: (context) => ScorecardChart$1.getDefinitionFromContextCreation(context),
21618
+ validateChartDefinition: ScorecardChart$1.validateChartDefinition,
21619
+ transformDefinition: ScorecardChart$1.transformDefinition,
21620
+ getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
21114
21621
  name: _t("Scorecard"),
21115
21622
  sequence: 40,
21116
21623
  });
@@ -21118,9 +21625,9 @@ stores.inject(MyMetaStore, storeInstance);
21118
21625
  match: (type) => type === "gauge",
21119
21626
  createChart: (definition, sheetId, getters) => new GaugeChart(definition, sheetId, getters),
21120
21627
  getChartRuntime: createGaugeChartRuntime,
21121
- validateChartDefinition: (validator, definition) => GaugeChart.validateChartDefinition(validator, definition),
21122
- transformDefinition: (definition, executed) => GaugeChart.transformDefinition(definition, executed),
21123
- getChartDefinitionFromContextCreation: (context) => GaugeChart.getDefinitionFromContextCreation(context),
21628
+ validateChartDefinition: GaugeChart.validateChartDefinition,
21629
+ transformDefinition: GaugeChart.transformDefinition,
21630
+ getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
21124
21631
  name: _t("Gauge"),
21125
21632
  sequence: 50,
21126
21633
  });
@@ -21128,12 +21635,22 @@ stores.inject(MyMetaStore, storeInstance);
21128
21635
  match: (type) => type === "scatter",
21129
21636
  createChart: (definition, sheetId, getters) => new ScatterChart(definition, sheetId, getters),
21130
21637
  getChartRuntime: createScatterChartRuntime,
21131
- validateChartDefinition: (validator, definition) => ScatterChart.validateChartDefinition(validator, definition),
21132
- transformDefinition: (definition, executed) => ScatterChart.transformDefinition(definition, executed),
21133
- getChartDefinitionFromContextCreation: (context) => ScatterChart.getDefinitionFromContextCreation(context),
21638
+ validateChartDefinition: ScatterChart.validateChartDefinition,
21639
+ transformDefinition: ScatterChart.transformDefinition,
21640
+ getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
21134
21641
  name: _t("Scatter"),
21135
21642
  sequence: 60,
21136
21643
  });
21644
+ chartRegistry.add("waterfall", {
21645
+ match: (type) => type === "waterfall",
21646
+ createChart: (definition, sheetId, getters) => new WaterfallChart(definition, sheetId, getters),
21647
+ getChartRuntime: createWaterfallChartRuntime,
21648
+ validateChartDefinition: WaterfallChart.validateChartDefinition,
21649
+ transformDefinition: WaterfallChart.transformDefinition,
21650
+ getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
21651
+ name: _t("Waterfall"),
21652
+ sequence: 70,
21653
+ });
21137
21654
  const chartComponentRegistry = new Registry();
21138
21655
  chartComponentRegistry.add("line", ChartJsComponent);
21139
21656
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -21142,6 +21659,7 @@ stores.inject(MyMetaStore, storeInstance);
21142
21659
  chartComponentRegistry.add("gauge", GaugeChartComponent);
21143
21660
  chartComponentRegistry.add("scatter", ChartJsComponent);
21144
21661
  chartComponentRegistry.add("scorecard", ScorecardChart);
21662
+ chartComponentRegistry.add("waterfall", ChartJsComponent);
21145
21663
 
21146
21664
  /**
21147
21665
  * Registry intended to support usual currencies. It is mainly used to create
@@ -23121,6 +23639,7 @@ stores.inject(MyMetaStore, storeInstance);
23121
23639
  this.save();
23122
23640
  }
23123
23641
  ev.stopPropagation();
23642
+ ev.preventDefault();
23124
23643
  break;
23125
23644
  case "Escape":
23126
23645
  this.cancel();
@@ -26774,8 +27293,8 @@ stores.inject(MyMetaStore, storeInstance);
26774
27293
  };
26775
27294
  }
26776
27295
 
26777
- class LineBarPieConfigPanel extends owl.Component {
26778
- static template = "o-spreadsheet-LineBarPieConfigPanel";
27296
+ class GenericChartConfigPanel extends owl.Component {
27297
+ static template = "o-spreadsheet-GenericChartConfigPanel";
26779
27298
  static components = {
26780
27299
  SelectionInput,
26781
27300
  ValidationMessages,
@@ -26892,7 +27411,7 @@ stores.inject(MyMetaStore, storeInstance);
26892
27411
  }
26893
27412
  }
26894
27413
 
26895
- class BarConfigPanel extends LineBarPieConfigPanel {
27414
+ class BarConfigPanel extends GenericChartConfigPanel {
26896
27415
  static template = "o-spreadsheet-BarConfigPanel";
26897
27416
  get stackedLabel() {
26898
27417
  return _t("Stacked barchart");
@@ -27440,13 +27959,30 @@ stores.inject(MyMetaStore, storeInstance);
27440
27959
  }
27441
27960
  }
27442
27961
 
27443
- class ChartColor extends owl.Component {
27444
- static template = "o-spreadsheet.ChartColor";
27445
- static components = { ColorPickerWidget, Section };
27962
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
27963
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
27964
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
27965
+ </svg>
27966
+ `;
27967
+ css /* scss */ `
27968
+ .o-round-color-picker-button {
27969
+ width: 15px;
27970
+ height: 15px;
27971
+ cursor: pointer;
27972
+ border: 1px solid #aaa;
27973
+ background-position: 1px 1px;
27974
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
27975
+ }
27976
+ `;
27977
+ class RoundColorPicker extends owl.Component {
27978
+ static template = "o-spreadsheet.RoundColorPicker";
27979
+ static components = { ColorPickerWidget, Section, ColorPicker };
27446
27980
  static props = {
27447
27981
  currentColor: { type: String, optional: true },
27982
+ title: { type: String, optional: true },
27448
27983
  onColorPicked: Function,
27449
27984
  };
27985
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
27450
27986
  state;
27451
27987
  setup() {
27452
27988
  this.state = owl.useState({ pickerOpened: false });
@@ -27458,6 +27994,19 @@ stores.inject(MyMetaStore, storeInstance);
27458
27994
  togglePicker() {
27459
27995
  this.state.pickerOpened = !this.state.pickerOpened;
27460
27996
  }
27997
+ onColorPicked(color) {
27998
+ this.props.onColorPicked(color);
27999
+ this.state.pickerOpened = false;
28000
+ }
28001
+ get colorPickerAnchorRect() {
28002
+ const button = this.colorPickerButtonRef.el;
28003
+ return getBoundingRectAsPOJO(button);
28004
+ }
28005
+ get buttonStyle() {
28006
+ return cssPropertiesToCss({
28007
+ background: this.props.currentColor,
28008
+ });
28009
+ }
27461
28010
  }
27462
28011
 
27463
28012
  class ChartTitle extends owl.Component {
@@ -27469,9 +28018,9 @@ stores.inject(MyMetaStore, storeInstance);
27469
28018
  }
27470
28019
  }
27471
28020
 
27472
- class LineBarPieDesignPanel extends owl.Component {
27473
- static template = "o-spreadsheet-LineBarPieDesignPanel";
27474
- static components = { ChartColor, ChartTitle, Section };
28021
+ class GenericChartDesignPanel extends owl.Component {
28022
+ static template = "o-spreadsheet-GenericChartDesignPanel";
28023
+ static components = { RoundColorPicker, ChartTitle, Section };
27475
28024
  static props = {
27476
28025
  figureId: String,
27477
28026
  definition: Object,
@@ -27494,13 +28043,16 @@ stores.inject(MyMetaStore, storeInstance);
27494
28043
  [attr]: ev.target.value,
27495
28044
  });
27496
28045
  }
28046
+ get backgroundColorTitle() {
28047
+ return ChartTerms.BackgroundColor;
28048
+ }
27497
28049
  }
27498
28050
 
27499
- class BarChartDesignPanel extends LineBarPieDesignPanel {
28051
+ class BarChartDesignPanel extends GenericChartDesignPanel {
27500
28052
  static template = "o-spreadsheet-BarChartDesignPanel";
27501
28053
  }
27502
28054
 
27503
- class ComboChartConfigPanel extends LineBarPieConfigPanel {
28055
+ class ComboChartConfigPanel extends GenericChartConfigPanel {
27504
28056
  static template = "o-spreadsheet-ComboChartConfigPanel";
27505
28057
  get shouldUseRightAxis() {
27506
28058
  return _t("Use right axis for line series");
@@ -27512,7 +28064,7 @@ stores.inject(MyMetaStore, storeInstance);
27512
28064
  }
27513
28065
  }
27514
28066
 
27515
- class ComboChartDesignPanel extends LineBarPieDesignPanel {
28067
+ class ComboChartDesignPanel extends GenericChartDesignPanel {
27516
28068
  static template = "o-spreadsheet-ComboChartDesignPanel";
27517
28069
  }
27518
28070
 
@@ -27563,6 +28115,10 @@ stores.inject(MyMetaStore, storeInstance);
27563
28115
  line-height: 18px;
27564
28116
  width: 100%;
27565
28117
  }
28118
+ td {
28119
+ box-sizing: border-box;
28120
+ height: 30px;
28121
+ }
27566
28122
  th.o-gauge-color-set-colorPicker {
27567
28123
  width: 8%;
27568
28124
  }
@@ -27585,7 +28141,12 @@ stores.inject(MyMetaStore, storeInstance);
27585
28141
  `;
27586
28142
  class GaugeChartDesignPanel extends owl.Component {
27587
28143
  static template = "o-spreadsheet-GaugeChartDesignPanel";
27588
- static components = { ColorPickerWidget, ChartErrorSection, ChartColor, ChartTitle, Section };
28144
+ static components = {
28145
+ ChartErrorSection,
28146
+ RoundColorPicker,
28147
+ ChartTitle,
28148
+ Section,
28149
+ };
27589
28150
  static props = {
27590
28151
  figureId: String,
27591
28152
  definition: Object,
@@ -27597,9 +28158,6 @@ stores.inject(MyMetaStore, storeInstance);
27597
28158
  sectionRuleDispatchResult: undefined,
27598
28159
  sectionRule: deepCopy(this.props.definition.sectionRule),
27599
28160
  });
27600
- setup() {
27601
- owl.useExternalListener(window, "click", this.closeMenus);
27602
- }
27603
28161
  get title() {
27604
28162
  return _t(this.props.definition.title);
27605
28163
  }
@@ -27640,31 +28198,26 @@ stores.inject(MyMetaStore, storeInstance);
27640
28198
  const sectionRule = deepCopy(this.state.sectionRule);
27641
28199
  sectionRule.colors[target] = color;
27642
28200
  this.updateSectionRule(sectionRule);
27643
- this.closeMenus();
27644
- }
27645
- toggleMenu(menu) {
27646
- const isSelected = this.state.openedMenu === menu;
27647
- this.closeMenus();
27648
- if (!isSelected) {
27649
- this.state.openedMenu = menu;
27650
- }
27651
28201
  }
27652
28202
  updateSectionRule(sectionRule) {
27653
28203
  this.state.sectionRuleDispatchResult = this.props.updateChart(this.props.figureId, {
27654
28204
  sectionRule,
27655
28205
  });
28206
+ if (this.state.sectionRuleDispatchResult.isSuccessful) {
28207
+ this.state.sectionRule = deepCopy(sectionRule);
28208
+ }
27656
28209
  }
27657
28210
  canUpdateSectionRule(sectionRule) {
27658
28211
  this.state.sectionRuleDispatchResult = this.props.canUpdateChart(this.props.figureId, {
27659
28212
  sectionRule,
27660
28213
  });
27661
28214
  }
27662
- closeMenus() {
27663
- this.state.openedMenu = undefined;
28215
+ get backgroundColorTitle() {
28216
+ return ChartTerms.BackgroundColor;
27664
28217
  }
27665
28218
  }
27666
28219
 
27667
- class LineConfigPanel extends LineBarPieConfigPanel {
28220
+ class LineConfigPanel extends GenericChartConfigPanel {
27668
28221
  static template = "o-spreadsheet-LineConfigPanel";
27669
28222
  get canTreatLabelsAsText() {
27670
28223
  const chart = this.env.model.getters.getChart(this.props.figureId);
@@ -27713,11 +28266,11 @@ stores.inject(MyMetaStore, storeInstance);
27713
28266
  }
27714
28267
  }
27715
28268
 
27716
- class LineChartDesignPanel extends LineBarPieDesignPanel {
28269
+ class LineChartDesignPanel extends GenericChartDesignPanel {
27717
28270
  static template = "o-spreadsheet-LineChartDesignPanel";
27718
28271
  }
27719
28272
 
27720
- class ScatterConfigPanel extends LineBarPieConfigPanel {
28273
+ class ScatterConfigPanel extends GenericChartConfigPanel {
27721
28274
  static template = "o-spreadsheet-ScatterConfigPanel";
27722
28275
  get canTreatLabelsAsText() {
27723
28276
  const chart = this.env.model.getters.getChart(this.props.figureId);
@@ -27808,39 +28361,36 @@ stores.inject(MyMetaStore, storeInstance);
27808
28361
 
27809
28362
  class ScorecardChartDesignPanel extends owl.Component {
27810
28363
  static template = "o-spreadsheet-ScorecardChartDesignPanel";
27811
- static components = { ColorPickerWidget, ChartColor, ChartTitle, Section };
28364
+ static components = { RoundColorPicker, ChartTitle, Section, Checkbox };
27812
28365
  static props = {
27813
28366
  figureId: String,
27814
28367
  definition: Object,
27815
28368
  updateChart: Function,
27816
28369
  canUpdateChart: Function,
27817
28370
  };
27818
- state = owl.useState({
27819
- openedColorPicker: undefined,
27820
- });
27821
- setup() {
27822
- owl.useExternalListener(window, "click", this.closeMenus);
27823
- }
27824
28371
  get title() {
27825
28372
  return _t(this.props.definition.title);
27826
28373
  }
28374
+ get colorsSectionTitle() {
28375
+ return this.props.definition.baselineMode === "progress"
28376
+ ? _t("Progress bar colors")
28377
+ : _t("Baseline colors");
28378
+ }
28379
+ get humanizeNumbersLabel() {
28380
+ return _t("Humanize numbers");
28381
+ }
27827
28382
  updateTitle(title) {
27828
28383
  this.props.updateChart(this.props.figureId, { title });
27829
28384
  }
28385
+ updateHumanizeNumbers(humanize) {
28386
+ this.props.updateChart(this.props.figureId, { humanize });
28387
+ }
27830
28388
  translate(term) {
27831
28389
  return _t(term);
27832
28390
  }
27833
28391
  updateBaselineDescr(ev) {
27834
28392
  this.props.updateChart(this.props.figureId, { baselineDescr: ev.target.value });
27835
28393
  }
27836
- toggleColorPicker(colorPickerId) {
27837
- if (this.state.openedColorPicker === colorPickerId) {
27838
- this.state.openedColorPicker = undefined;
27839
- }
27840
- else {
27841
- this.state.openedColorPicker = colorPickerId;
27842
- }
27843
- }
27844
28394
  setColor(color, colorPickerId) {
27845
28395
  switch (colorPickerId) {
27846
28396
  case "backgroundColor":
@@ -27853,10 +28403,49 @@ stores.inject(MyMetaStore, storeInstance);
27853
28403
  this.props.updateChart(this.props.figureId, { baselineColorUp: color });
27854
28404
  break;
27855
28405
  }
27856
- this.closeMenus();
27857
28406
  }
27858
- closeMenus() {
27859
- this.state.openedColorPicker = undefined;
28407
+ get backgroundColorTitle() {
28408
+ return ChartTerms.BackgroundColor;
28409
+ }
28410
+ }
28411
+
28412
+ class WaterfallChartDesignPanel extends GenericChartDesignPanel {
28413
+ static template = "o-spreadsheet-WaterfallChartDesignPanel";
28414
+ static components = { ...GenericChartDesignPanel.components, Checkbox, RoundColorPicker };
28415
+ state = owl.useState({ pickerOpened: false });
28416
+ setup() {
28417
+ super.setup();
28418
+ owl.useExternalListener(window, "click", this.closePicker);
28419
+ }
28420
+ onUpdateShowSubTotals(showSubTotals) {
28421
+ this.props.updateChart(this.props.figureId, { showSubTotals });
28422
+ }
28423
+ onUpdateShowConnectorLines(showConnectorLines) {
28424
+ this.props.updateChart(this.props.figureId, { showConnectorLines });
28425
+ }
28426
+ onUpdateFirstValueAsSubtotal(firstValueAsSubtotal) {
28427
+ this.props.updateChart(this.props.figureId, { firstValueAsSubtotal });
28428
+ }
28429
+ updateColor(colorName, color) {
28430
+ this.props.updateChart(this.props.figureId, { [colorName]: color });
28431
+ }
28432
+ closePicker() {
28433
+ this.state.pickerOpened = false;
28434
+ }
28435
+ togglePicker() {
28436
+ this.state.pickerOpened = !this.state.pickerOpened;
28437
+ }
28438
+ get positiveValuesColor() {
28439
+ return (this.props.definition.positiveValuesColor ||
28440
+ CHART_WATERFALL_POSITIVE_COLOR);
28441
+ }
28442
+ get negativeValuesColor() {
28443
+ return (this.props.definition.negativeValuesColor ||
28444
+ CHART_WATERFALL_NEGATIVE_COLOR);
28445
+ }
28446
+ get subTotalValuesColor() {
28447
+ return (this.props.definition.subTotalValuesColor ||
28448
+ CHART_WATERFALL_SUBTOTAL_COLOR);
27860
28449
  }
27861
28450
  }
27862
28451
 
@@ -27879,8 +28468,8 @@ stores.inject(MyMetaStore, storeInstance);
27879
28468
  design: ComboChartDesignPanel,
27880
28469
  })
27881
28470
  .add("pie", {
27882
- configuration: LineBarPieConfigPanel,
27883
- design: LineBarPieDesignPanel,
28471
+ configuration: GenericChartConfigPanel,
28472
+ design: GenericChartDesignPanel,
27884
28473
  })
27885
28474
  .add("gauge", {
27886
28475
  configuration: GaugeChartConfigPanel,
@@ -27889,6 +28478,10 @@ stores.inject(MyMetaStore, storeInstance);
27889
28478
  .add("scorecard", {
27890
28479
  configuration: ScorecardChartConfigPanel,
27891
28480
  design: ScorecardChartDesignPanel,
28481
+ })
28482
+ .add("waterfall", {
28483
+ configuration: GenericChartConfigPanel,
28484
+ design: WaterfallChartDesignPanel,
27892
28485
  });
27893
28486
 
27894
28487
  class MainChartPanelStore extends SpreadsheetStore {
@@ -28805,6 +29398,7 @@ stores.inject(MyMetaStore, storeInstance);
28805
29398
  ColorPickerWidget,
28806
29399
  ConditionalFormatPreviewList,
28807
29400
  Section,
29401
+ RoundColorPicker,
28808
29402
  };
28809
29403
  icons = ICONS;
28810
29404
  cellIsOperators = CellIsOperators;
@@ -30625,6 +31219,9 @@ stores.inject(MyMetaStore, storeInstance);
30625
31219
  filteredRange: filteredZone.top > filteredZone.bottom ? undefined : filteredRange,
30626
31220
  };
30627
31221
  }
31222
+ function isStaticTable(table) {
31223
+ return table.type === "static" || table.type === "forceStatic";
31224
+ }
30628
31225
  function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
30629
31226
  return {
30630
31227
  borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
@@ -31159,7 +31756,12 @@ stores.inject(MyMetaStore, storeInstance);
31159
31756
  const extendedZone = this.env.model.getters.getContiguousZone(sheetId, newRange.zone);
31160
31757
  newRange = this.env.model.getters.getRangeFromZone(sheetId, extendedZone);
31161
31758
  }
31162
- const result = this.env.model.dispatch("UPDATE_TABLE", {
31759
+ const newTableZone = newRange.zone;
31760
+ const oldTableZone = this.props.table.range.zone;
31761
+ const cmdToCall = newTableZone.top === oldTableZone.top && newTableZone.left === oldTableZone.left
31762
+ ? "RESIZE_TABLE"
31763
+ : "UPDATE_TABLE";
31764
+ const result = this.env.model.dispatch(cmdToCall, {
31163
31765
  sheetId,
31164
31766
  zone: this.props.table.range.zone,
31165
31767
  newTableRange: newRange.rangeData,
@@ -31648,6 +32250,38 @@ stores.inject(MyMetaStore, storeInstance);
31648
32250
  }
31649
32251
  }
31650
32252
 
32253
+ class ArrayFormulaHighlight extends SpreadsheetStore {
32254
+ highlightStore = this.get(HighlightStore);
32255
+ constructor(get) {
32256
+ super(get);
32257
+ this.highlightStore.register(this);
32258
+ }
32259
+ get highlights() {
32260
+ const zone = this.getHighlightZone();
32261
+ if (!zone) {
32262
+ return [];
32263
+ }
32264
+ const sheetId = this.model.getters.getActiveSheetId();
32265
+ return [
32266
+ {
32267
+ sheetId,
32268
+ zone,
32269
+ color: "#17A2B8",
32270
+ noFill: true,
32271
+ thinLine: true,
32272
+ },
32273
+ ];
32274
+ }
32275
+ getHighlightZone() {
32276
+ const position = this.model.getters.getActivePosition();
32277
+ const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
32278
+ const spreadZone = spreader
32279
+ ? this.model.getters.getSpreadZone(spreader)
32280
+ : this.model.getters.getSpreadZone(position);
32281
+ return spreadZone;
32282
+ }
32283
+ }
32284
+
31651
32285
  // -----------------------------------------------------------------------------
31652
32286
  // Autofill
31653
32287
  // -----------------------------------------------------------------------------
@@ -32309,6 +32943,7 @@ stores.inject(MyMetaStore, storeInstance);
32309
32943
  onComposerCellFocused: { type: Function, optional: true },
32310
32944
  onComposerContentFocused: Function,
32311
32945
  isDefaultFocus: { type: Boolean, optional: true },
32946
+ onInputContextMenu: { type: Function, optional: true },
32312
32947
  };
32313
32948
  static components = { TextValueProvider, FunctionDescriptionProvider };
32314
32949
  static defaultProps = {
@@ -32359,6 +32994,9 @@ stores.inject(MyMetaStore, storeInstance);
32359
32994
  assistantStyle.right = `0px`;
32360
32995
  }
32361
32996
  }
32997
+ else if (this.props.delimitation) {
32998
+ assistantStyle["max-height"] = `${this.props.delimitation.height}px`;
32999
+ }
32362
33000
  return cssPropertiesToCss(assistantStyle);
32363
33001
  }
32364
33002
  // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
@@ -32645,6 +33283,11 @@ stores.inject(MyMetaStore, storeInstance);
32645
33283
  }
32646
33284
  }
32647
33285
  }
33286
+ onContextMenu(ev) {
33287
+ if (this.composerStore.editionMode === "inactive") {
33288
+ this.props.onInputContextMenu?.(ev);
33289
+ }
33290
+ }
32648
33291
  // ---------------------------------------------------------------------------
32649
33292
  // Private
32650
33293
  // ---------------------------------------------------------------------------
@@ -32866,6 +33509,7 @@ stores.inject(MyMetaStore, storeInstance);
32866
33509
  static template = "o-spreadsheet-GridComposer";
32867
33510
  static props = {
32868
33511
  gridDims: Object,
33512
+ onInputContextMenu: Function,
32869
33513
  };
32870
33514
  static components = { Composer };
32871
33515
  rect = this.defaultRect;
@@ -32913,6 +33557,7 @@ stores.inject(MyMetaStore, storeInstance);
32913
33557
  isDefaultFocus: true,
32914
33558
  onComposerContentFocused: () => this.composerFocusStore.focusGridComposerContent(),
32915
33559
  onComposerCellFocused: (content) => this.composerFocusStore.focusGridComposerCell(content),
33560
+ onInputContextMenu: this.props.onInputContextMenu,
32916
33561
  };
32917
33562
  }
32918
33563
  get containerStyle() {
@@ -33011,7 +33656,6 @@ stores.inject(MyMetaStore, storeInstance);
33011
33656
  cellPosition: Object,
33012
33657
  horizontalAlign: { type: String, optional: true },
33013
33658
  verticalAlign: { type: String, optional: true },
33014
- offset: { type: Object, optional: true },
33015
33659
  slots: Object,
33016
33660
  };
33017
33661
  get iconStyle() {
@@ -33022,8 +33666,8 @@ stores.inject(MyMetaStore, storeInstance);
33022
33666
  const x = this.getIconHorizontalPosition(rect, cellPosition);
33023
33667
  const y = this.getIconVerticalPosition(rect, cellPosition);
33024
33668
  return cssPropertiesToCss({
33025
- top: `${y + (this.props.offset?.y || 0)}px`,
33026
- left: `${x + (this.props.offset?.x || 0)}px`,
33669
+ top: `${y}px`,
33670
+ left: `${x}px`,
33027
33671
  });
33028
33672
  }
33029
33673
  getIconVerticalPosition(rect, cellPosition) {
@@ -33063,83 +33707,6 @@ stores.inject(MyMetaStore, storeInstance);
33063
33707
  }
33064
33708
  }
33065
33709
 
33066
- css /* scss */ `
33067
- .o-filter-icon {
33068
- color: ${FILTERS_COLOR};
33069
- display: flex;
33070
- align-items: center;
33071
- justify-content: center;
33072
- width: ${GRID_ICON_EDGE_LENGTH}px;
33073
- height: ${GRID_ICON_EDGE_LENGTH}px;
33074
-
33075
- &:hover {
33076
- background: ${FILTERS_COLOR};
33077
- color: #fff;
33078
- }
33079
-
33080
- &.o-high-contrast {
33081
- color: #defade;
33082
- }
33083
- &.o-high-contrast:hover {
33084
- color: ${FILTERS_COLOR};
33085
- background: #fff;
33086
- }
33087
- }
33088
- .o-filter-icon:hover {
33089
- background: ${FILTERS_COLOR};
33090
- color: #fff;
33091
- }
33092
- `;
33093
- class FilterIcon extends owl.Component {
33094
- static template = "o-spreadsheet-FilterIcon";
33095
- static props = {
33096
- cellPosition: Object,
33097
- };
33098
- cellPopovers;
33099
- setup() {
33100
- this.cellPopovers = useStore(CellPopoverStore);
33101
- }
33102
- onClick() {
33103
- const position = this.props.cellPosition;
33104
- const activePopover = this.cellPopovers.persistentCellPopover;
33105
- const { col, row } = position;
33106
- if (activePopover.isOpen &&
33107
- activePopover.col === col &&
33108
- activePopover.row === row &&
33109
- activePopover.type === "FilterMenu") {
33110
- this.cellPopovers.close();
33111
- return;
33112
- }
33113
- this.cellPopovers.open({ col, row }, "FilterMenu");
33114
- }
33115
- get isFilterActive() {
33116
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
33117
- }
33118
- get iconClass() {
33119
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
33120
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
33121
- return luminance < 0.45 ? "o-high-contrast" : "";
33122
- }
33123
- }
33124
-
33125
- class FilterIconsOverlay extends owl.Component {
33126
- static template = "o-spreadsheet-FilterIconsOverlay";
33127
- static props = {
33128
- gridPosition: { type: Object, optional: true },
33129
- };
33130
- static components = {
33131
- GridCellIcon,
33132
- FilterIcon,
33133
- };
33134
- static defaultProps = {
33135
- gridPosition: { x: 0, y: 0 },
33136
- };
33137
- getFilterHeadersPositions() {
33138
- const sheetId = this.env.model.getters.getActiveSheetId();
33139
- return this.env.model.getters.getFilterHeaders(sheetId);
33140
- }
33141
- }
33142
-
33143
33710
  const CHECKBOX_WIDTH = 15;
33144
33711
  const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
33145
33712
  css /* scss */ `
@@ -33491,6 +34058,10 @@ stores.inject(MyMetaStore, storeInstance);
33491
34058
  height: 0px;
33492
34059
  }
33493
34060
  }
34061
+ .o-figure-container {
34062
+ -webkit-user-select: none; // safari
34063
+ user-select: none;
34064
+ }
33494
34065
  `;
33495
34066
  /**
33496
34067
  * Each figure ⭐ is positioned inside a container `div` placed and sized
@@ -33807,6 +34378,80 @@ stores.inject(MyMetaStore, storeInstance);
33807
34378
  }
33808
34379
  }
33809
34380
 
34381
+ css /* scss */ `
34382
+ .o-filter-icon {
34383
+ color: ${FILTERS_COLOR};
34384
+ display: flex;
34385
+ align-items: center;
34386
+ justify-content: center;
34387
+ width: ${GRID_ICON_EDGE_LENGTH}px;
34388
+ height: ${GRID_ICON_EDGE_LENGTH}px;
34389
+
34390
+ &:hover {
34391
+ background: ${FILTERS_COLOR};
34392
+ color: #fff;
34393
+ }
34394
+
34395
+ &.o-high-contrast {
34396
+ color: #defade;
34397
+ }
34398
+ &.o-high-contrast:hover {
34399
+ color: ${FILTERS_COLOR};
34400
+ background: #fff;
34401
+ }
34402
+ }
34403
+ .o-filter-icon:hover {
34404
+ background: ${FILTERS_COLOR};
34405
+ color: #fff;
34406
+ }
34407
+ `;
34408
+ class FilterIcon extends owl.Component {
34409
+ static template = "o-spreadsheet-FilterIcon";
34410
+ static props = {
34411
+ cellPosition: Object,
34412
+ };
34413
+ cellPopovers;
34414
+ setup() {
34415
+ this.cellPopovers = useStore(CellPopoverStore);
34416
+ }
34417
+ onClick() {
34418
+ const position = this.props.cellPosition;
34419
+ const activePopover = this.cellPopovers.persistentCellPopover;
34420
+ const { col, row } = position;
34421
+ if (activePopover.isOpen &&
34422
+ activePopover.col === col &&
34423
+ activePopover.row === row &&
34424
+ activePopover.type === "FilterMenu") {
34425
+ this.cellPopovers.close();
34426
+ return;
34427
+ }
34428
+ this.cellPopovers.open({ col, row }, "FilterMenu");
34429
+ }
34430
+ get isFilterActive() {
34431
+ return this.env.model.getters.isFilterActive(this.props.cellPosition);
34432
+ }
34433
+ get iconClass() {
34434
+ const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
34435
+ const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
34436
+ return luminance < 0.45 ? "o-high-contrast" : "";
34437
+ }
34438
+ }
34439
+
34440
+ class FilterIconsOverlay extends owl.Component {
34441
+ static template = "o-spreadsheet-FilterIconsOverlay";
34442
+ static props = {
34443
+ onMouseDown: Function,
34444
+ };
34445
+ static components = {
34446
+ GridCellIcon,
34447
+ FilterIcon,
34448
+ };
34449
+ getFilterHeadersPositions() {
34450
+ const sheetId = this.env.model.getters.getActiveSheetId();
34451
+ return this.env.model.getters.getFilterHeaders(sheetId);
34452
+ }
34453
+ }
34454
+
33810
34455
  css /* scss */ `
33811
34456
  .o-grid-add-rows {
33812
34457
  input {
@@ -34020,7 +34665,12 @@ stores.inject(MyMetaStore, storeInstance);
34020
34665
  onGridMoved: Function,
34021
34666
  gridOverlayDimensions: String,
34022
34667
  };
34023
- static components = { FiguresContainer, DataValidationOverlay, GridAddRowsFooter };
34668
+ static components = {
34669
+ FiguresContainer,
34670
+ DataValidationOverlay,
34671
+ GridAddRowsFooter,
34672
+ FilterIconsOverlay,
34673
+ };
34024
34674
  static defaultProps = {
34025
34675
  onCellHovered: () => { },
34026
34676
  onCellDoubleClicked: () => { },
@@ -34065,7 +34715,7 @@ stores.inject(MyMetaStore, storeInstance);
34065
34715
  get isPaintingFormat() {
34066
34716
  return this.env.model.getters.isPaintingFormat();
34067
34717
  }
34068
- onMouseDown(ev) {
34718
+ onMouseDown(ev, modifiers) {
34069
34719
  if (ev.button > 0) {
34070
34720
  // not main button, probably a context menu
34071
34721
  return;
@@ -34074,6 +34724,7 @@ stores.inject(MyMetaStore, storeInstance);
34074
34724
  this.props.onCellClicked(col, row, {
34075
34725
  expandZone: ev.shiftKey,
34076
34726
  addZone: isCtrlKey(ev),
34727
+ closePopover: modifiers?.closePopover ?? true,
34077
34728
  });
34078
34729
  }
34079
34730
  onDoubleClick(ev) {
@@ -35812,6 +36463,77 @@ stores.inject(MyMetaStore, storeInstance);
35812
36463
  }
35813
36464
  }
35814
36465
 
36466
+ const SIZE = 3;
36467
+ const COLOR = "#777";
36468
+ css /* scss */ `
36469
+ .o-table-resizer {
36470
+ width: ${SIZE}px;
36471
+ height: ${SIZE}px;
36472
+ border-bottom: ${SIZE}px solid ${COLOR};
36473
+ border-right: ${SIZE}px solid ${COLOR};
36474
+ cursor: nwse-resize;
36475
+ }
36476
+ `;
36477
+ class TableResizer extends owl.Component {
36478
+ static template = "o-spreadsheet-TableResizer";
36479
+ static props = { table: Object };
36480
+ state = owl.useState({ highlightZone: undefined });
36481
+ setup() {
36482
+ useHighlights(this);
36483
+ }
36484
+ get containerStyle() {
36485
+ const tableZone = this.props.table.range.zone;
36486
+ const bottomRight = { ...tableZone, left: tableZone.right, top: tableZone.bottom };
36487
+ const rect = this.env.model.getters.getVisibleRect(bottomRight);
36488
+ if (rect.height === 0 || rect.width === 0) {
36489
+ return cssPropertiesToCss({ display: "none" });
36490
+ }
36491
+ return cssPropertiesToCss({
36492
+ top: `${rect.y + rect.height - SIZE * 2}px`,
36493
+ left: `${rect.x + rect.width - SIZE * 2}px`,
36494
+ });
36495
+ }
36496
+ onMouseDown(ev) {
36497
+ const tableZone = this.props.table.range.zone;
36498
+ const topLeft = { col: tableZone.left, row: tableZone.top };
36499
+ document.body.style.cursor = "nwse-resize";
36500
+ const onMouseUp = () => {
36501
+ document.body.style.cursor = "";
36502
+ const newTableZone = this.state.highlightZone;
36503
+ if (!newTableZone)
36504
+ return;
36505
+ const sheetId = this.props.table.range.sheetId;
36506
+ this.env.model.dispatch("RESIZE_TABLE", {
36507
+ sheetId,
36508
+ zone: this.props.table.range.zone,
36509
+ newTableRange: this.env.model.getters.getRangeDataFromZone(sheetId, newTableZone),
36510
+ });
36511
+ this.state.highlightZone = undefined;
36512
+ };
36513
+ const onMouseMove = (col, row, ev) => {
36514
+ this.state.highlightZone = {
36515
+ left: topLeft.col,
36516
+ top: topLeft.row,
36517
+ right: Math.max(col, topLeft.col),
36518
+ bottom: Math.max(row, topLeft.row),
36519
+ };
36520
+ };
36521
+ dragAndDropBeyondTheViewport(this.env, onMouseMove, onMouseUp);
36522
+ }
36523
+ get highlights() {
36524
+ if (!this.state.highlightZone)
36525
+ return [];
36526
+ return [
36527
+ {
36528
+ zone: this.state.highlightZone,
36529
+ sheetId: this.props.table.range.sheetId,
36530
+ color: COLOR,
36531
+ noFill: true,
36532
+ },
36533
+ ];
36534
+ }
36535
+ }
36536
+
35815
36537
  const registries$1 = {
35816
36538
  ROW: rowMenuRegistry,
35817
36539
  COL: colMenuRegistry,
@@ -35839,7 +36561,7 @@ stores.inject(MyMetaStore, storeInstance);
35839
36561
  Popover,
35840
36562
  VerticalScrollBar,
35841
36563
  HorizontalScrollBar,
35842
- FilterIconsOverlay,
36564
+ TableResizer,
35843
36565
  };
35844
36566
  HEADER_HEIGHT = HEADER_HEIGHT;
35845
36567
  HEADER_WIDTH = HEADER_WIDTH;
@@ -35868,6 +36590,7 @@ stores.inject(MyMetaStore, storeInstance);
35868
36590
  this.composerFocusStore = useStore(ComposerFocusStore);
35869
36591
  this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
35870
36592
  this.sidePanel = useStore(SidePanelStore);
36593
+ useStore(ArrayFormulaHighlight);
35871
36594
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
35872
36595
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
35873
36596
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
@@ -36140,17 +36863,17 @@ stores.inject(MyMetaStore, storeInstance);
36140
36863
  // ---------------------------------------------------------------------------
36141
36864
  // Zone selection with mouse
36142
36865
  // ---------------------------------------------------------------------------
36143
- onCellClicked(col, row, { addZone, expandZone }) {
36144
- if (this.cellPopovers.isOpen) {
36866
+ onCellClicked(col, row, modifiers) {
36867
+ if (modifiers.closePopover && this.cellPopovers.isOpen) {
36145
36868
  this.cellPopovers.close();
36146
36869
  }
36147
36870
  if (this.composerStore.editionMode === "editing") {
36148
36871
  this.composerStore.stopEdition();
36149
36872
  }
36150
- if (expandZone) {
36873
+ if (modifiers.expandZone) {
36151
36874
  this.env.model.selection.setAnchorCorner(col, row);
36152
36875
  }
36153
- else if (addZone) {
36876
+ else if (modifiers.addZone) {
36154
36877
  this.env.model.selection.addCellToSelection(col, row);
36155
36878
  }
36156
36879
  else {
@@ -36436,6 +37159,10 @@ stores.inject(MyMetaStore, storeInstance);
36436
37159
  onComposerContentFocused() {
36437
37160
  this.composerFocusStore.focusGridComposerContent();
36438
37161
  }
37162
+ get staticTables() {
37163
+ const sheetId = this.env.model.getters.getActiveSheetId();
37164
+ return this.env.model.getters.getCoreTables(sheetId).filter(isStaticTable);
37165
+ }
36439
37166
  }
36440
37167
 
36441
37168
  /**
@@ -36872,7 +37599,7 @@ stores.inject(MyMetaStore, storeInstance);
36872
37599
  line3DChart: undefined,
36873
37600
  stockChart: undefined,
36874
37601
  radarChart: undefined,
36875
- scatterChart: undefined,
37602
+ scatterChart: "scatter",
36876
37603
  pieChart: "pie",
36877
37604
  pie3DChart: undefined,
36878
37605
  doughnutChart: "pie",
@@ -37636,29 +38363,12 @@ stores.inject(MyMetaStore, storeInstance);
37636
38363
  return width;
37637
38364
  return Math.round((width / WIDTH_FACTOR) * 100) / 100;
37638
38365
  }
37639
- function convertBorderDescr(descr) {
37640
- if (!descr) {
37641
- return undefined;
37642
- }
37643
- return {
37644
- style: descr.style,
37645
- color: { rgb: descr.color },
37646
- };
37647
- }
37648
38366
  function extractStyle(cell, data) {
37649
38367
  let style = {};
37650
38368
  if (cell.style) {
37651
38369
  style = data.styles[cell.style];
37652
38370
  }
37653
38371
  const format = extractFormat(cell, data);
37654
- const exportedBorder = {};
37655
- if (cell.border) {
37656
- const border = data.borders[cell.border];
37657
- exportedBorder.left = convertBorderDescr(border.left);
37658
- exportedBorder.right = convertBorderDescr(border.right);
37659
- exportedBorder.bottom = convertBorderDescr(border.bottom);
37660
- exportedBorder.top = convertBorderDescr(border.top);
37661
- }
37662
38372
  const styles = {
37663
38373
  font: {
37664
38374
  size: style?.fontSize || DEFAULT_FONT_SIZE,
@@ -37672,7 +38382,7 @@ stores.inject(MyMetaStore, storeInstance);
37672
38382
  }
37673
38383
  : { reservedAttribute: "none" },
37674
38384
  numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
37675
- border: exportedBorder || {},
38385
+ border: cell.border || 0,
37676
38386
  alignment: {
37677
38387
  horizontal: style.align,
37678
38388
  vertical: style.verticalAlign
@@ -37694,15 +38404,12 @@ stores.inject(MyMetaStore, storeInstance);
37694
38404
  return undefined;
37695
38405
  }
37696
38406
  function normalizeStyle(construct, styles) {
37697
- const { id: fontId } = pushElement(styles["font"], construct.fonts);
37698
- const { id: fillId } = pushElement(styles["fill"], construct.fills);
37699
- const { id: borderId } = pushElement(styles["border"], construct.borders);
37700
38407
  // Normalize this
37701
38408
  const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
37702
38409
  const style = {
37703
- fontId,
37704
- fillId,
37705
- borderId,
38410
+ fontId: pushElement(styles.font, construct.fonts),
38411
+ fillId: pushElement(styles.fill, construct.fills),
38412
+ borderId: styles.border,
37706
38413
  numFmtId,
37707
38414
  alignment: {
37708
38415
  vertical: styles.alignment.vertical,
@@ -37710,8 +38417,7 @@ stores.inject(MyMetaStore, storeInstance);
37710
38417
  wrapText: styles.alignment.wrapText,
37711
38418
  },
37712
38419
  };
37713
- const { id } = pushElement(style, construct.styles);
37714
- return id;
38420
+ return pushElement(style, construct.styles);
37715
38421
  }
37716
38422
  function convertFormat(format, numFmtStructure) {
37717
38423
  if (!format) {
@@ -37719,8 +38425,7 @@ stores.inject(MyMetaStore, storeInstance);
37719
38425
  }
37720
38426
  let formatId = XLSX_FORMAT_MAP[format.format];
37721
38427
  if (!formatId) {
37722
- const { id } = pushElement(format, numFmtStructure);
37723
- formatId = id + FIRST_NUMFMT_ID;
38428
+ formatId = pushElement(format, numFmtStructure) + FIRST_NUMFMT_ID;
37724
38429
  }
37725
38430
  return formatId;
37726
38431
  }
@@ -37745,20 +38450,15 @@ stores.inject(MyMetaStore, storeInstance);
37745
38450
  return id;
37746
38451
  }
37747
38452
  function pushElement(property, propertyList) {
37748
- for (let [key, value] of Object.entries(propertyList)) {
37749
- if (JSON.stringify(value) === JSON.stringify(property)) {
37750
- return { id: parseInt(key, 10), list: propertyList };
38453
+ let len = propertyList.length;
38454
+ const operator = typeof property === "object" ? deepEquals : (a, b) => a === b;
38455
+ for (let i = 0; i < len; i++) {
38456
+ if (operator(property, propertyList[i])) {
38457
+ return i;
37751
38458
  }
37752
38459
  }
37753
- let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));
37754
- if (elemId === -1) {
37755
- propertyList.push(property);
37756
- elemId = propertyList.length - 1;
37757
- }
37758
- return {
37759
- id: elemId,
37760
- list: propertyList,
37761
- };
38460
+ propertyList[propertyList.length] = property;
38461
+ return propertyList.length - 1;
37762
38462
  }
37763
38463
  const chartIds = [];
37764
38464
  /**
@@ -38553,7 +39253,25 @@ stores.inject(MyMetaStore, storeInstance);
38553
39253
  }
38554
39254
  return document;
38555
39255
  }
38556
- function getDefaultXLSXStructure() {
39256
+ function convertBorderDescr(descr) {
39257
+ if (!descr) {
39258
+ return undefined;
39259
+ }
39260
+ return {
39261
+ style: descr.style,
39262
+ color: { rgb: descr.color },
39263
+ };
39264
+ }
39265
+ function getDefaultXLSXStructure(data) {
39266
+ const xlsxBorders = Object.values(data.borders).map((border) => {
39267
+ return {
39268
+ left: convertBorderDescr(border.left),
39269
+ right: convertBorderDescr(border.right),
39270
+ bottom: convertBorderDescr(border.bottom),
39271
+ top: convertBorderDescr(border.top),
39272
+ };
39273
+ });
39274
+ const borders = [{}, ...xlsxBorders];
38557
39275
  return {
38558
39276
  relsFiles: [],
38559
39277
  sharedStrings: [],
@@ -38576,7 +39294,7 @@ stores.inject(MyMetaStore, storeInstance);
38576
39294
  },
38577
39295
  ],
38578
39296
  fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
38579
- borders: [{}],
39297
+ borders,
38580
39298
  numFmts: [],
38581
39299
  dxfs: [],
38582
39300
  };
@@ -39112,8 +39830,8 @@ stores.inject(MyMetaStore, storeInstance);
39112
39830
  return {
39113
39831
  title: chartTitle,
39114
39832
  type: CHART_TYPE_CONVERSION_MAP[chartType],
39115
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
39116
- labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
39833
+ dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
39834
+ labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
39117
39835
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
39118
39836
  default: "ffffff",
39119
39837
  }).asString(),
@@ -39142,8 +39860,8 @@ stores.inject(MyMetaStore, storeInstance);
39142
39860
  title: chartTitle,
39143
39861
  type: "combo",
39144
39862
  dataSets: [
39145
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`)),
39146
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`)),
39863
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
39864
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
39147
39865
  ],
39148
39866
  labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
39149
39867
  backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
@@ -39161,7 +39879,10 @@ stores.inject(MyMetaStore, storeInstance);
39161
39879
  fontColor: "000000",
39162
39880
  };
39163
39881
  }
39164
- extractChartDatasets(chartElement) {
39882
+ extractChartDatasets(chartElement, chartType) {
39883
+ if (chartType === "scatterChart") {
39884
+ return this.extractScatterChartDatasets(chartElement);
39885
+ }
39165
39886
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
39166
39887
  return {
39167
39888
  label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
@@ -39169,6 +39890,14 @@ stores.inject(MyMetaStore, storeInstance);
39169
39890
  };
39170
39891
  });
39171
39892
  }
39893
+ extractScatterChartDatasets(chartElement) {
39894
+ return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
39895
+ return {
39896
+ label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
39897
+ range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
39898
+ };
39899
+ });
39900
+ }
39172
39901
  /**
39173
39902
  * The chart type in the XML isn't explicitly defined, but there is an XML element that define the
39174
39903
  * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.
@@ -40562,12 +41291,14 @@ stores.inject(MyMetaStore, storeInstance);
40562
41291
  static getters = [];
40563
41292
  history;
40564
41293
  dispatch;
40565
- constructor(stateObserver, dispatch) {
41294
+ canDispatch;
41295
+ constructor(stateObserver, dispatch, canDispatch) {
40566
41296
  this.history = Object.assign(Object.create(stateObserver), {
40567
41297
  update: stateObserver.addChange.bind(stateObserver, this),
40568
41298
  selectCell: () => { },
40569
41299
  });
40570
41300
  this.dispatch = dispatch;
41301
+ this.canDispatch = canDispatch;
40571
41302
  }
40572
41303
  /**
40573
41304
  * Export for excel should be available for all plugins, even for the UI.
@@ -40646,8 +41377,8 @@ stores.inject(MyMetaStore, storeInstance);
40646
41377
  class CorePlugin extends BasePlugin {
40647
41378
  getters;
40648
41379
  uuidGenerator;
40649
- constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
40650
- super(stateObserver, dispatch);
41380
+ constructor({ getters, stateObserver, range, dispatch, canDispatch, uuidGenerator, }) {
41381
+ super(stateObserver, dispatch, canDispatch);
40651
41382
  range.addRangeProvider(this.adaptRanges.bind(this));
40652
41383
  this.getters = getters;
40653
41384
  this.uuidGenerator = uuidGenerator;
@@ -45225,7 +45956,7 @@ stores.inject(MyMetaStore, storeInstance);
45225
45956
  }
45226
45957
 
45227
45958
  class TablePlugin extends CorePlugin {
45228
- static getters = ["getCoreTable", "getCoreTables"];
45959
+ static getters = ["getCoreTable", "getCoreTables", "getCoreTableMatchingTopLeft"];
45229
45960
  tables = {};
45230
45961
  adaptRanges(applyChange, sheetId) {
45231
45962
  const sheetIds = sheetId ? [sheetId] : this.getters.getSheetIds();
@@ -45246,7 +45977,7 @@ stores.inject(MyMetaStore, storeInstance);
45246
45977
  ? "TableOverlap" /* CommandResult.TableOverlap */
45247
45978
  : "Success" /* CommandResult.Success */, (cmd) => this.checkTableConfigUpdateIsValid(cmd.config));
45248
45979
  case "UPDATE_TABLE":
45249
- const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
45980
+ const updatedTable = this.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
45250
45981
  if (!updatedTable) {
45251
45982
  return "TableNotFound" /* CommandResult.TableNotFound */;
45252
45983
  }
@@ -45402,10 +46133,9 @@ stores.inject(MyMetaStore, storeInstance);
45402
46133
  }
45403
46134
  return direction;
45404
46135
  }
45405
- getTableFromZone(sheetId, zone) {
46136
+ getCoreTableMatchingTopLeft(sheetId, zone) {
45406
46137
  for (const table of this.getCoreTables(sheetId)) {
45407
46138
  const tableZone = table.range.zone;
45408
- // Only check top left to match dynamic tables
45409
46139
  if (tableZone.left === zone.left && tableZone.top === zone.top) {
45410
46140
  return table;
45411
46141
  }
@@ -45421,7 +46151,7 @@ stores.inject(MyMetaStore, storeInstance);
45421
46151
  if (zoneIsInSheet !== "Success" /* CommandResult.Success */) {
45422
46152
  return zoneIsInSheet;
45423
46153
  }
45424
- const updatedTable = this.getTableFromZone(cmd.sheetId, cmd.zone);
46154
+ const updatedTable = this.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
45425
46155
  if (!updatedTable) {
45426
46156
  return "TableNotFound" /* CommandResult.TableNotFound */;
45427
46157
  }
@@ -45471,7 +46201,7 @@ stores.inject(MyMetaStore, storeInstance);
45471
46201
  };
45472
46202
  }
45473
46203
  updateTable(cmd) {
45474
- const table = this.getTableFromZone(cmd.sheetId, cmd.zone);
46204
+ const table = this.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
45475
46205
  if (!table) {
45476
46206
  return;
45477
46207
  }
@@ -46144,8 +46874,8 @@ stores.inject(MyMetaStore, storeInstance);
46144
46874
  getters;
46145
46875
  ui;
46146
46876
  selection;
46147
- constructor({ getters, stateObserver, dispatch, uiActions, selection }) {
46148
- super(stateObserver, dispatch);
46877
+ constructor({ getters, stateObserver, dispatch, canDispatch, uiActions, selection, }) {
46878
+ super(stateObserver, dispatch, canDispatch);
46149
46879
  this.getters = getters;
46150
46880
  this.ui = uiActions;
46151
46881
  this.selection = selection;
@@ -47425,7 +48155,9 @@ stores.inject(MyMetaStore, storeInstance);
47425
48155
  this.blockedArrayFormulas = this.createEmptyPositionSet();
47426
48156
  this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
47427
48157
  this.formulaDependencies = lazy(() => {
47428
- const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position).map((range) => ({
48158
+ const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
48159
+ .filter((range) => !range.invalidSheetName && !range.invalidXc)
48160
+ .map((range) => ({
47429
48161
  data: position,
47430
48162
  boundingBox: {
47431
48163
  zone: range.zone,
@@ -51509,6 +52241,8 @@ stores.inject(MyMetaStore, storeInstance);
51509
52241
  "CREATE_TABLE",
51510
52242
  "UPDATE_TABLE",
51511
52243
  "UPDATE_FILTER",
52244
+ "REMOVE_TABLE",
52245
+ "RESIZE_TABLE",
51512
52246
  ];
51513
52247
  const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
51514
52248
  function doesCommandInvalidatesTableStyle(cmd) {
@@ -51536,6 +52270,10 @@ stores.inject(MyMetaStore, storeInstance);
51536
52270
  this.styles = {};
51537
52271
  return;
51538
52272
  }
52273
+ if (invalidateBordersCommands.has(cmd.type)) {
52274
+ this.borders = {};
52275
+ return;
52276
+ }
51539
52277
  }
51540
52278
  getCellComputedBorder(position) {
51541
52279
  const { sheetId, row, col } = position;
@@ -52139,7 +52877,10 @@ stores.inject(MyMetaStore, storeInstance);
52139
52877
  const { col, row } = cmd;
52140
52878
  const tableContentZone = getTableContentZone(table.range.zone, table.config);
52141
52879
  if (tableContentZone && isInside(col, row, tableContentZone)) {
52142
- this.autofillTableZone(cmd, tableContentZone);
52880
+ const top = cmd.autofillRowStart ?? tableContentZone.top;
52881
+ const bottom = cmd.autofillRowEnd ?? tableContentZone.bottom;
52882
+ const autofillZone = { ...tableContentZone, top, bottom };
52883
+ this.autofillTableZone(cmd, autofillZone);
52143
52884
  }
52144
52885
  break;
52145
52886
  }
@@ -52173,6 +52914,50 @@ stores.inject(MyMetaStore, storeInstance);
52173
52914
  }
52174
52915
  }
52175
52916
 
52917
+ class TableResizeUI extends UIPlugin {
52918
+ allowDispatch(cmd) {
52919
+ switch (cmd.type) {
52920
+ case "RESIZE_TABLE":
52921
+ const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
52922
+ if (!table) {
52923
+ return "TableNotFound" /* CommandResult.TableNotFound */;
52924
+ }
52925
+ const oldTableZone = table.range.zone;
52926
+ const newTableZone = this.getters.getRangeFromRangeData(cmd.newTableRange).zone;
52927
+ if (newTableZone.top !== oldTableZone.top || newTableZone.left !== oldTableZone.left) {
52928
+ return "InvalidTableResize" /* CommandResult.InvalidTableResize */;
52929
+ }
52930
+ return this.canDispatch("UPDATE_TABLE", { ...cmd }).reasons;
52931
+ }
52932
+ return "Success" /* CommandResult.Success */;
52933
+ }
52934
+ handle(cmd) {
52935
+ switch (cmd.type) {
52936
+ case "RESIZE_TABLE": {
52937
+ const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
52938
+ this.dispatch("UPDATE_TABLE", { ...cmd });
52939
+ if (!table || !table.config.automaticAutofill)
52940
+ return;
52941
+ const oldTableZone = table.range.zone;
52942
+ const newTableZone = this.getters.getRangeFromRangeData(cmd.newTableRange).zone;
52943
+ if (newTableZone.bottom >= oldTableZone.bottom) {
52944
+ for (let col = newTableZone.left; col <= newTableZone.right; col++) {
52945
+ const autofillSource = { col, row: oldTableZone.bottom, sheetId: cmd.sheetId };
52946
+ if (this.getters.getCell(autofillSource)?.content.startsWith("=")) {
52947
+ this.dispatch("AUTOFILL_TABLE_COLUMN", {
52948
+ ...autofillSource,
52949
+ autofillRowStart: oldTableZone.bottom,
52950
+ autofillRowEnd: newTableZone.bottom,
52951
+ });
52952
+ }
52953
+ }
52954
+ break;
52955
+ }
52956
+ }
52957
+ }
52958
+ }
52959
+ }
52960
+
52176
52961
  /**
52177
52962
  * Clipboard Plugin
52178
52963
  *
@@ -53847,13 +54632,14 @@ stores.inject(MyMetaStore, storeInstance);
53847
54632
  }
53848
54633
  }
53849
54634
  handleEvent(event) {
54635
+ const sheetId = this.getters.getActiveSheetId();
53850
54636
  if (event.options.scrollIntoView) {
53851
54637
  let { col, row } = findCellInNewZone(event.previousAnchor.zone, event.anchor.zone);
53852
54638
  if (event.mode === "updateAnchor") {
53853
54639
  const oldZone = event.previousAnchor.zone;
53854
54640
  const newZone = event.anchor.zone;
53855
54641
  // altering a zone should not move the viewport in a dimension that wasn't changed
53856
- const { top, bottom, left, right } = this.getters.getActiveMainViewport();
54642
+ const { top, bottom, left, right } = this.getMainInternalViewport(sheetId);
53857
54643
  if (oldZone.left === newZone.left && oldZone.right === newZone.right) {
53858
54644
  col = left > col || col > right ? left : col;
53859
54645
  }
@@ -53861,7 +54647,6 @@ stores.inject(MyMetaStore, storeInstance);
53861
54647
  row = top > row || row > bottom ? top : row;
53862
54648
  }
53863
54649
  }
53864
- const sheetId = this.getters.getActiveSheetId();
53865
54650
  col = Math.min(col, this.getters.getNumberCols(sheetId) - 1);
53866
54651
  row = Math.min(row, this.getters.getNumberRows(sheetId) - 1);
53867
54652
  if (!this.sheetsWithDirtyViewports.has(sheetId)) {
@@ -53898,16 +54683,16 @@ stores.inject(MyMetaStore, storeInstance);
53898
54683
  this.setSheetViewOffset(cmd.offsetX, cmd.offsetY);
53899
54684
  break;
53900
54685
  case "SHIFT_VIEWPORT_DOWN":
53901
- const { top } = this.getActiveMainViewport();
53902
54686
  const sheetId = this.getters.getActiveSheetId();
53903
- const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).start + this.sheetViewHeight);
53904
- this.shiftVertically(shiftedOffsetY);
54687
+ const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
54688
+ const topRowDims = this.getters.getRowDimensions(sheetId, top);
54689
+ this.shiftVertically(topRowDims.start + viewportHeight - offsetCorrectionY);
53905
54690
  break;
53906
54691
  case "SHIFT_VIEWPORT_UP": {
53907
- const { top } = this.getActiveMainViewport();
53908
54692
  const sheetId = this.getters.getActiveSheetId();
53909
- const shiftedOffsetY = this.clipOffsetY(this.getters.getRowDimensions(sheetId, top).end - this.sheetViewHeight);
53910
- this.shiftVertically(shiftedOffsetY);
54693
+ const { top, viewportHeight, offsetCorrectionY } = this.getMainInternalViewport(sheetId);
54694
+ const topRowDims = this.getters.getRowDimensions(sheetId, top);
54695
+ this.shiftVertically(topRowDims.end - offsetCorrectionY - viewportHeight);
53911
54696
  break;
53912
54697
  }
53913
54698
  case "REMOVE_TABLE":
@@ -54330,17 +55115,6 @@ stores.inject(MyMetaStore, storeInstance);
54330
55115
  const { maxOffsetX, maxOffsetY } = this.getMaximumSheetOffset();
54331
55116
  Object.values(this.getSubViewports(sheetId)).forEach((viewport) => viewport.setViewportOffset(clip(offsetX, 0, maxOffsetX), clip(offsetY, 0, maxOffsetY)));
54332
55117
  }
54333
- /**
54334
- * Clip the vertical offset within the allowed range.
54335
- * Not above the sheet, nor below the sheet.
54336
- */
54337
- clipOffsetY(offsetY) {
54338
- const { height } = this.getMainViewportRect();
54339
- const maxOffset = height - this.sheetViewHeight;
54340
- offsetY = Math.min(offsetY, maxOffset);
54341
- offsetY = Math.max(offsetY, 0);
54342
- return offsetY;
54343
- }
54344
55118
  getViewportOffset(sheetId) {
54345
55119
  return {
54346
55120
  x: this.viewports[sheetId]?.bottomRight.offsetScrollbarX || 0,
@@ -54396,12 +55170,15 @@ stores.inject(MyMetaStore, storeInstance);
54396
55170
  * viewport top.
54397
55171
  */
54398
55172
  shiftVertically(offset) {
54399
- const { top } = this.getActiveMainViewport();
55173
+ const sheetId = this.getters.getActiveSheetId();
55174
+ const { top } = this.getMainInternalViewport(sheetId);
54400
55175
  const { scrollX } = this.getActiveSheetScrollInfo();
54401
55176
  this.setSheetViewOffset(scrollX, offset);
54402
55177
  const { anchor } = this.getters.getSelection();
54403
- const deltaRow = this.getActiveMainViewport().top - top;
54404
- this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);
55178
+ if (anchor.cell.row >= this.getters.getPaneDivisions(sheetId).ySplit) {
55179
+ const deltaRow = this.getMainInternalViewport(sheetId).top - top;
55180
+ this.selection.selectCell(anchor.cell.col, anchor.cell.row + deltaRow);
55181
+ }
54405
55182
  }
54406
55183
  getVisibleFigures() {
54407
55184
  const sheetId = this.getters.getActiveSheetId();
@@ -54590,7 +55367,8 @@ stores.inject(MyMetaStore, storeInstance);
54590
55367
  .add("collaborative", CollaborativePlugin)
54591
55368
  .add("history", HistoryPlugin)
54592
55369
  .add("data_cleanup", DataCleanupPlugin)
54593
- .add("table_autofill", TableAutofillPlugin);
55370
+ .add("table_autofill", TableAutofillPlugin)
55371
+ .add("table_ui_resize", TableResizeUI);
54594
55372
  // Plugins which have a state, but which should not be shared in collaborative
54595
55373
  const statefulUIPluginRegistry = new Registry()
54596
55374
  .add("selection", GridSelectionPlugin)
@@ -54658,38 +55436,6 @@ stores.inject(MyMetaStore, storeInstance);
54658
55436
  }
54659
55437
  }
54660
55438
 
54661
- class ArrayFormulaHighlight extends SpreadsheetStore {
54662
- highlightStore = this.get(HighlightStore);
54663
- constructor(get) {
54664
- super(get);
54665
- this.highlightStore.register(this);
54666
- }
54667
- get highlights() {
54668
- const zone = this.getHighlightZone();
54669
- if (!zone) {
54670
- return [];
54671
- }
54672
- const sheetId = this.model.getters.getActiveSheetId();
54673
- return [
54674
- {
54675
- sheetId,
54676
- zone,
54677
- color: "#17A2B8",
54678
- noFill: true,
54679
- thinLine: true,
54680
- },
54681
- ];
54682
- }
54683
- getHighlightZone() {
54684
- const position = this.model.getters.getActivePosition();
54685
- const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
54686
- const spreadZone = spreader
54687
- ? this.model.getters.getSpreadZone(spreader)
54688
- : this.model.getters.getSpreadZone(position);
54689
- return spreadZone;
54690
- }
54691
- }
54692
-
54693
55439
  const RIPPLE_KEY_FRAMES = [
54694
55440
  { transform: "scale(0)" },
54695
55441
  { transform: "scale(0.8)", offset: 0.33 },
@@ -55476,7 +56222,6 @@ stores.inject(MyMetaStore, storeInstance);
55476
56222
  Popover,
55477
56223
  VerticalScrollBar,
55478
56224
  HorizontalScrollBar,
55479
- FilterIconsOverlay,
55480
56225
  };
55481
56226
  cellPopovers;
55482
56227
  onMouseWheel;
@@ -56363,6 +57108,13 @@ stores.inject(MyMetaStore, storeInstance);
56363
57108
  "border-color": SELECTION_BORDER_COLOR,
56364
57109
  });
56365
57110
  }
57111
+ get delimitation() {
57112
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
57113
+ return {
57114
+ width,
57115
+ height,
57116
+ };
57117
+ }
56366
57118
  onFocus(selection) {
56367
57119
  this.composerFocusStore.focusTopBarComposer(selection);
56368
57120
  }
@@ -57017,7 +57769,6 @@ stores.inject(MyMetaStore, storeInstance);
57017
57769
  this.notificationStore = useStore(NotificationStore);
57018
57770
  this.composerFocusStore = useStore(ComposerFocusStore);
57019
57771
  this.sidePanel = useStore(SidePanelStore);
57020
- useStore(ArrayFormulaHighlight);
57021
57772
  this.keyDownMapping = {
57022
57773
  "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
57023
57774
  "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
@@ -58646,6 +59397,9 @@ stores.inject(MyMetaStore, storeInstance);
58646
59397
  case "line":
58647
59398
  plot = addLineChart(chart.data);
58648
59399
  break;
59400
+ case "scatter":
59401
+ plot = addScatterChart(chart.data);
59402
+ break;
58649
59403
  case "pie":
58650
59404
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
58651
59405
  break;
@@ -58923,6 +59677,52 @@ stores.inject(MyMetaStore, storeInstance);
58923
59677
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58924
59678
  `;
58925
59679
  }
59680
+ function addScatterChart(chart) {
59681
+ const colors = new ChartColors();
59682
+ const dataSetsNodes = [];
59683
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
59684
+ dataSetsNodes.push(escapeXml /*xml*/ `
59685
+ <c:ser>
59686
+ <c:idx val="${dsIndex}"/>
59687
+ <c:order val="${dsIndex}"/>
59688
+ <c:smooth val="0"/>
59689
+ <c:spPr>
59690
+ <a:ln w="19050" cap="rnd">
59691
+ <a:noFill/>
59692
+ <a:round/>
59693
+ </a:ln>
59694
+ <a:effectLst/>
59695
+ </c:spPr>
59696
+ <c:marker>
59697
+ <c:symbol val="circle" />
59698
+ <c:size val="5"/>
59699
+ ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
59700
+ </c:marker>
59701
+ ${chart.labelRange
59702
+ ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
59703
+ ${numberRef(chart.labelRange)}
59704
+ </c:xVal>`
59705
+ : ""}
59706
+ <c:yVal> <!-- y-coordinate values -->
59707
+ ${numberRef(dataset.range)}
59708
+ </c:yVal>
59709
+ </c:ser>
59710
+ `);
59711
+ }
59712
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
59713
+ return escapeXml /*xml*/ `
59714
+ <c:scatterChart>
59715
+ <!-- each data marker in the series does not have a different color -->
59716
+ <c:varyColors val="0"/>
59717
+ <c:scatterStyle val="lineMarker"/>
59718
+ ${joinXmlNodes(dataSetsNodes)}
59719
+ <c:axId val="${catAxId}" />
59720
+ <c:axId val="${valAxId}" />
59721
+ </c:scatterChart>
59722
+ ${addAx("b", "c:valAx", catAxId, valAxId, { fontColor: chart.fontColor })}
59723
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
59724
+ `;
59725
+ }
58926
59726
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
58927
59727
  const colors = new ChartColors();
58928
59728
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
@@ -59063,8 +59863,7 @@ stores.inject(MyMetaStore, storeInstance);
59063
59863
  attrs.push(["t", "b"]);
59064
59864
  }
59065
59865
  else if (forceString || !isNumber(value, DEFAULT_LOCALE)) {
59066
- const { id } = pushElement(content, sharedStrings);
59067
- value = id.toString();
59866
+ value = pushElement(content, sharedStrings);
59068
59867
  attrs.push(["t", "s"]);
59069
59868
  }
59070
59869
  return { attrs, node: escapeXml /*xml*/ `<v>${value}</v>` };
@@ -59189,8 +59988,7 @@ stores.inject(MyMetaStore, storeInstance);
59189
59988
  if (rule.style.fillColor) {
59190
59989
  dxf.fill = { fgColor: { rgb: rule.style.fillColor } };
59191
59990
  }
59192
- const { id } = pushElement(dxf, dxfs);
59193
- ruleAttributes.push(["dxfId", id]);
59991
+ ruleAttributes.push(["dxfId", pushElement(dxf, dxfs)]);
59194
59992
  return escapeXml /*xml*/ `
59195
59993
  <conditionalFormatting sqref="${cf.ranges.join(" ")}">
59196
59994
  <cfRule ${formatAttributes(ruleAttributes)}>
@@ -60020,8 +60818,9 @@ stores.inject(MyMetaStore, storeInstance);
60020
60818
  */
60021
60819
  function getXLSX(data) {
60022
60820
  data = fixLengthySheetNames(data);
60821
+ data = purgeSingleRowTables(data);
60023
60822
  const files = [];
60024
- const construct = getDefaultXLSXStructure();
60823
+ const construct = getDefaultXLSXStructure(data);
60025
60824
  files.push(createWorkbook(data, construct));
60026
60825
  files.push(...createWorksheets(data, construct));
60027
60826
  files.push(createStylesSheet(construct));
@@ -60301,6 +61100,16 @@ stores.inject(MyMetaStore, storeInstance);
60301
61100
  }
60302
61101
  return JSON.parse(stringifiedData);
60303
61102
  }
61103
+ /** Excel files do not support tables with a single row the defined range
61104
+ * Since those tables are not really useful (no filtering/limited styling)
61105
+ * This function filters out all tables with a single row.
61106
+ */
61107
+ function purgeSingleRowTables(data) {
61108
+ for (const sheet of data.sheets) {
61109
+ sheet.tables = sheet.tables.filter((table) => zoneToDimension(toZone(table.range)).numberOfRows > 1);
61110
+ }
61111
+ return data;
61112
+ }
60304
61113
 
60305
61114
  var Status;
60306
61115
  (function (Status) {
@@ -60554,6 +61363,7 @@ stores.inject(MyMetaStore, storeInstance);
60554
61363
  stateObserver: this.state,
60555
61364
  range: this.range,
60556
61365
  dispatch: this.dispatchFromCorePlugin,
61366
+ canDispatch: this.canDispatch,
60557
61367
  uuidGenerator: this.uuidGenerator,
60558
61368
  custom: this.config.custom,
60559
61369
  external: this.config.external,
@@ -60564,6 +61374,7 @@ stores.inject(MyMetaStore, storeInstance);
60564
61374
  getters: this.getters,
60565
61375
  stateObserver: this.state,
60566
61376
  dispatch: this.dispatch,
61377
+ canDispatch: this.canDispatch,
60567
61378
  selection: this.selection,
60568
61379
  moveClient: this.session.move.bind(this.session),
60569
61380
  custom: this.config.custom,
@@ -60887,7 +61698,7 @@ stores.inject(MyMetaStore, storeInstance);
60887
61698
  const components = {
60888
61699
  Checkbox,
60889
61700
  Section,
60890
- ChartColor,
61701
+ RoundColorPicker,
60891
61702
  ChartDataSeries,
60892
61703
  ChartErrorSection,
60893
61704
  ChartLabelRange,
@@ -60899,9 +61710,9 @@ stores.inject(MyMetaStore, storeInstance);
60899
61710
  GridOverlay,
60900
61711
  ScorecardChart,
60901
61712
  LineConfigPanel,
60902
- LineBarPieDesignPanel,
61713
+ GenericChartDesignPanel,
60903
61714
  BarConfigPanel,
60904
- LineBarPieConfigPanel,
61715
+ GenericChartConfigPanel,
60905
61716
  GaugeChartConfigPanel,
60906
61717
  GaugeChartDesignPanel,
60907
61718
  ScorecardChartConfigPanel,
@@ -60990,9 +61801,9 @@ stores.inject(MyMetaStore, storeInstance);
60990
61801
  exports.tokenize = tokenize;
60991
61802
 
60992
61803
 
60993
- __info__.version = "17.3.0-alpha.2";
60994
- __info__.date = "2024-04-05T14:01:07.060Z";
60995
- __info__.hash = "8c5a229";
61804
+ __info__.version = "17.3.0-alpha.4";
61805
+ __info__.date = "2024-04-15T11:02:51.551Z";
61806
+ __info__.hash = "a32a1df";
60996
61807
 
60997
61808
 
60998
61809
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);