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