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