@odoo/o-spreadsheet 17.3.0-alpha.5 → 17.3.0-alpha.7

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.5
7
- * @date 2024-04-18T14:38:57.166Z
8
- * @hash cff0ef9
6
+ * @version 17.3.0-alpha.7
7
+ * @date 2024-05-07T10:42:47.288Z
8
+ * @hash 853c266
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';
@@ -566,7 +566,7 @@ function getAddHeaderStartIndex(position, base) {
566
566
  /**
567
567
  * Compares two objects.
568
568
  */
569
- function deepEquals(o1, o2) {
569
+ function deepEquals(o1, o2, ignoreFunctions) {
570
570
  if (o1 === o2)
571
571
  return true;
572
572
  if ((o1 && !o2) || (o2 && !o1))
@@ -582,13 +582,16 @@ function deepEquals(o1, o2) {
582
582
  }
583
583
  }
584
584
  for (const key in o1) {
585
- if (typeof o1[key] !== typeof o2[key])
585
+ const typeOfO1Key = typeof o1[key];
586
+ if (typeOfO1Key !== typeof o2[key])
586
587
  return false;
587
- if (typeof o1[key] === "object") {
588
- if (!deepEquals(o1[key], o2[key]))
588
+ if (typeOfO1Key === "object") {
589
+ if (!deepEquals(o1[key], o2[key], ignoreFunctions))
589
590
  return false;
590
591
  }
591
592
  else {
593
+ if (ignoreFunctions && typeOfO1Key === "function")
594
+ return true;
592
595
  if (o1[key] !== o2[key])
593
596
  return false;
594
597
  }
@@ -1989,6 +1992,8 @@ const coreTypes = new Set([
1989
1992
  "CREATE_TABLE",
1990
1993
  "REMOVE_TABLE",
1991
1994
  "UPDATE_TABLE",
1995
+ "CREATE_TABLE_STYLE",
1996
+ "REMOVE_TABLE_STYLE",
1992
1997
  /** IMAGE */
1993
1998
  "CREATE_IMAGE",
1994
1999
  /** HEADER GROUP */
@@ -2135,6 +2140,7 @@ var CommandResult;
2135
2140
  CommandResult["TableNotFound"] = "TableNotFound";
2136
2141
  CommandResult["TableOverlap"] = "TableOverlap";
2137
2142
  CommandResult["InvalidTableConfig"] = "InvalidTableConfig";
2143
+ CommandResult["InvalidTableStyle"] = "InvalidTableStyle";
2138
2144
  CommandResult["FilterNotFound"] = "FilterNotFound";
2139
2145
  CommandResult["MergeInTable"] = "MergeInTable";
2140
2146
  CommandResult["NonContinuousTargets"] = "NonContinuousTargets";
@@ -2236,6 +2242,7 @@ const CellErrorType = {
2236
2242
  CircularDependency: "#CYCLE",
2237
2243
  UnknownFunction: "#NAME?",
2238
2244
  DivisionByZero: "#DIV/0!",
2245
+ SpilledBlocked: "#SPILL!",
2239
2246
  GenericError: "#ERROR",
2240
2247
  };
2241
2248
  const errorTypes = new Set(Object.values(CellErrorType));
@@ -2271,6 +2278,11 @@ class UnknownFunctionError extends EvaluationError {
2271
2278
  super(message, CellErrorType.UnknownFunction);
2272
2279
  }
2273
2280
  }
2281
+ class SplillBlockedError extends EvaluationError {
2282
+ constructor(message = _t("Spill range is not empty")) {
2283
+ super(message, CellErrorType.SpilledBlocked);
2284
+ }
2285
+ }
2274
2286
 
2275
2287
  // HELPERS
2276
2288
  const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"];
@@ -4129,21 +4141,22 @@ function toZoneWithoutBoundaryChanges(xc) {
4129
4141
  xc = xc.split("!").at(-1);
4130
4142
  }
4131
4143
  if (xc.includes("$")) {
4132
- xc = xc.replace(/\$/g, "");
4144
+ xc = xc.replaceAll("$", "");
4133
4145
  }
4134
- let ranges;
4146
+ let firstRangePart = "";
4147
+ let secondRangePart;
4135
4148
  if (xc.includes(":")) {
4136
- ranges = xc.split(":").map((x) => x.trim());
4149
+ [firstRangePart, secondRangePart] = xc.split(":");
4150
+ firstRangePart = firstRangePart.trim();
4151
+ secondRangePart = secondRangePart.trim();
4137
4152
  }
4138
4153
  else {
4139
- ranges = [xc.trim()];
4154
+ firstRangePart = xc.trim();
4140
4155
  }
4141
4156
  let top, bottom, left, right;
4142
4157
  let fullCol = false;
4143
4158
  let fullRow = false;
4144
4159
  let hasHeader = false;
4145
- const firstRangePart = ranges[0];
4146
- const secondRangePart = ranges[1] && ranges[1];
4147
4160
  if (isColReference(firstRangePart)) {
4148
4161
  left = right = lettersToNumber(firstRangePart);
4149
4162
  top = bottom = 0;
@@ -4160,7 +4173,7 @@ function toZoneWithoutBoundaryChanges(xc) {
4160
4173
  top = bottom = c.row;
4161
4174
  hasHeader = true;
4162
4175
  }
4163
- if (ranges.length === 2) {
4176
+ if (secondRangePart) {
4164
4177
  if (isColReference(secondRangePart)) {
4165
4178
  right = lettersToNumber(secondRangePart);
4166
4179
  fullCol = true;
@@ -8653,6 +8666,9 @@ function drawHighlight(renderingContext, highlight, rect) {
8653
8666
  const color = highlight.color || HIGHLIGHT_COLOR;
8654
8667
  const { ctx } = renderingContext;
8655
8668
  if (!highlight.noBorder) {
8669
+ if (highlight.dashed) {
8670
+ ctx.setLineDash([5, 3]);
8671
+ }
8656
8672
  ctx.strokeStyle = color;
8657
8673
  if (highlight.thinLine) {
8658
8674
  ctx.lineWidth = 1;
@@ -9346,8 +9362,7 @@ class ComposerStore extends SpreadsheetStore {
9346
9362
  const exactMatch = proposals?.find((p) => p.text === tokenAtCursor.value);
9347
9363
  // remove tokens that are likely to be other parts of the formula that slipped in the token if it's a string
9348
9364
  const searchTerm = tokenAtCursor.value.replace(/[ ,\(\)]/g, "");
9349
- const initialContent = this.initialContent;
9350
- if (exactMatch && exactMatch.text !== initialContent) {
9365
+ if (exactMatch && this._currentContent !== this.initialContent) {
9351
9366
  // this means the user has chosen a proposal
9352
9367
  return;
9353
9368
  }
@@ -9355,7 +9370,7 @@ class ComposerStore extends SpreadsheetStore {
9355
9370
  proposals &&
9356
9371
  !["ARG_SEPARATOR", "LEFT_PAREN"].includes(tokenAtCursor.type)) {
9357
9372
  const filteredProposals = fuzzyLookup(searchTerm, proposals, (p) => p.fuzzySearchKey || p.text);
9358
- if (!exactMatch) {
9373
+ if (!exactMatch || filteredProposals.length > 1) {
9359
9374
  proposals = filteredProposals;
9360
9375
  }
9361
9376
  }
@@ -9546,6 +9561,7 @@ class ChartJsComponent extends Component {
9546
9561
  };
9547
9562
  canvas = useRef("graphContainer");
9548
9563
  chart;
9564
+ currentRuntime;
9549
9565
  get background() {
9550
9566
  return this.chartRuntime.background;
9551
9567
  }
@@ -9562,9 +9578,18 @@ class ChartJsComponent extends Component {
9562
9578
  setup() {
9563
9579
  onMounted(() => {
9564
9580
  const runtime = this.chartRuntime;
9565
- this.createChart(runtime.chartJsConfig);
9581
+ this.currentRuntime = runtime;
9582
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
9583
+ this.createChart(deepCopy(runtime.chartJsConfig));
9584
+ });
9585
+ onWillUnmount(() => this.chart?.destroy());
9586
+ useEffect(() => {
9587
+ const runtime = this.chartRuntime;
9588
+ if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
9589
+ this.currentRuntime = runtime;
9590
+ this.updateChartJs(deepCopy(runtime));
9591
+ }
9566
9592
  });
9567
- useEffect(() => this.updateChartJs(this.chartRuntime), () => [this.chartRuntime]);
9568
9593
  }
9569
9594
  createChart(chartData) {
9570
9595
  const canvas = this.canvas.el;
@@ -9584,7 +9609,7 @@ class ChartJsComponent extends Component {
9584
9609
  this.chart.data.datasets = [];
9585
9610
  }
9586
9611
  this.chart.config.options = chartData.options;
9587
- this.chart.update("active");
9612
+ this.chart.update();
9588
9613
  }
9589
9614
  }
9590
9615
 
@@ -10073,8 +10098,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10073
10098
  }
10074
10099
  getContextCreation() {
10075
10100
  return {
10076
- background: this.background,
10077
- title: this.title,
10101
+ ...this,
10078
10102
  range: this.keyValue ? [this.getters.getRangeString(this.keyValue, this.sheetId)] : undefined,
10079
10103
  auxiliaryRange: this.baseline
10080
10104
  ? this.getters.getRangeString(this.baseline, this.sheetId)
@@ -18138,6 +18162,9 @@ const COLUMN = {
18138
18162
  ],
18139
18163
  returns: ["NUMBER"],
18140
18164
  compute: function (cellReference) {
18165
+ if (isEvaluationError(cellReference?.value)) {
18166
+ throw cellReference;
18167
+ }
18141
18168
  const _cellReference = cellReference === undefined ? this.__originCellXC?.() : cellReference.value;
18142
18169
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18143
18170
  const zone = toZone(_cellReference);
@@ -18153,6 +18180,9 @@ const COLUMNS = {
18153
18180
  args: [arg("range (meta)", _t("The range whose column count will be returned."))],
18154
18181
  returns: ["NUMBER"],
18155
18182
  compute: function (range) {
18183
+ if (isEvaluationError(range?.value)) {
18184
+ throw range;
18185
+ }
18156
18186
  const zone = toZone(range.value);
18157
18187
  return zone.right - zone.left + 1;
18158
18188
  },
@@ -18375,6 +18405,9 @@ const ROW = {
18375
18405
  ],
18376
18406
  returns: ["NUMBER"],
18377
18407
  compute: function (cellReference) {
18408
+ if (isEvaluationError(cellReference?.value)) {
18409
+ throw cellReference;
18410
+ }
18378
18411
  const _cellReference = cellReference === undefined ? this.__originCellXC?.() : cellReference.value;
18379
18412
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18380
18413
  const zone = toZone(_cellReference);
@@ -18390,6 +18423,9 @@ const ROWS = {
18390
18423
  args: [arg("range (meta)", _t("The range whose row count will be returned."))],
18391
18424
  returns: ["NUMBER"],
18392
18425
  compute: function (range) {
18426
+ if (isEvaluationError(range?.value)) {
18427
+ throw range;
18428
+ }
18393
18429
  const zone = toZone(range.value);
18394
18430
  return zone.bottom - zone.top + 1;
18395
18431
  },
@@ -20342,17 +20378,13 @@ class Composer extends Component {
20342
20378
  this.DOMFocusableElementStore.setFocusableElement(el);
20343
20379
  }
20344
20380
  this.contentHelper.updateEl(el);
20345
- this.processTokenAtCursor();
20346
20381
  });
20347
20382
  useEffect(() => {
20348
20383
  this.processContent();
20349
20384
  });
20350
- onPatched(() => {
20351
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
20352
- if (this.composerStore.editionMode === "inactive") {
20353
- this.processTokenAtCursor();
20354
- }
20355
- });
20385
+ useEffect(() => {
20386
+ this.processTokenAtCursor();
20387
+ }, () => [this.composerStore.editionMode !== "inactive"]);
20356
20388
  }
20357
20389
  // ---------------------------------------------------------------------------
20358
20390
  // Handlers
@@ -20954,13 +20986,10 @@ function compileTokens(tokens) {
20954
20986
  const isRangeOnly = argTypes.every((t) => isRangeType(t));
20955
20987
  if (isRangeOnly) {
20956
20988
  if (!isRangeInput(currentArg)) {
20957
- throw new BadExpressionError(_t("Function %s expects the parameter %s to be reference to a cell or range, not a %s.", functionName, (i + 1).toString(), currentArg.type.toLowerCase()));
20989
+ throw new BadExpressionError(_t("Function %(function_name)s expects the parameter %(arg_index)s to be a reference to a cell or a range.", { function_name: functionName, arg_index: i + 1 }));
20958
20990
  }
20959
20991
  }
20960
- compiledArgs.push(compileAST(currentArg, isMeta, hasRange, {
20961
- functionName,
20962
- paramIndex: i + 1,
20963
- }));
20992
+ compiledArgs.push(compileAST(currentArg, isMeta, hasRange));
20964
20993
  }
20965
20994
  return compiledArgs;
20966
20995
  }
@@ -20976,7 +21005,7 @@ function compileTokens(tokens) {
20976
21005
  * function needs to receive as argument the coordinates of a cell rather
20977
21006
  * than its value. For this we have meta arguments.
20978
21007
  */
20979
- function compileAST(ast, isMeta = false, hasRange = false, referenceVerification = {}) {
21008
+ function compileAST(ast, isMeta = false, hasRange = false) {
20980
21009
  const code = new FunctionCodeBuilder(scope);
20981
21010
  if (ast.type !== "REFERENCE" && !(ast.type === "BIN_OPERATION" && ast.value === ":")) {
20982
21011
  if (isMeta) {
@@ -20995,11 +21024,11 @@ function compileTokens(tokens) {
20995
21024
  return code.return(`{ value: this.constantValues.strings[${constantValues.strings.indexOf(ast.value)}] }`);
20996
21025
  case "REFERENCE":
20997
21026
  const referenceIndex = dependencies.indexOf(ast.value);
20998
- if (hasRange) {
21027
+ if ((!isMeta && ast.value.includes(":")) || hasRange) {
20999
21028
  return code.return(`range(deps[${referenceIndex}])`);
21000
21029
  }
21001
21030
  else {
21002
- return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"}, "${referenceVerification.functionName || OPERATOR_MAP["="]}", ${referenceVerification.paramIndex})`);
21031
+ return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"})`);
21003
21032
  }
21004
21033
  case "FUNCALL":
21005
21034
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
@@ -21008,20 +21037,14 @@ function compileTokens(tokens) {
21008
21037
  return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
21009
21038
  case "UNARY_OPERATION": {
21010
21039
  const fnName = UNARY_OPERATOR_MAP[ast.value];
21011
- const operand = compileAST(ast.operand, false, false, {
21012
- functionName: fnName,
21013
- }).assignResultToVariable();
21040
+ const operand = compileAST(ast.operand, false, false).assignResultToVariable();
21014
21041
  code.append(operand);
21015
21042
  return code.return(`ctx['${fnName}'](${operand.returnExpression})`);
21016
21043
  }
21017
21044
  case "BIN_OPERATION": {
21018
21045
  const fnName = OPERATOR_MAP[ast.value];
21019
- const left = compileAST(ast.left, false, false, {
21020
- functionName: fnName,
21021
- }).assignResultToVariable();
21022
- const right = compileAST(ast.right, false, false, {
21023
- functionName: fnName,
21024
- }).assignResultToVariable();
21046
+ const left = compileAST(ast.left, false, false).assignResultToVariable();
21047
+ const right = compileAST(ast.right, false, false).assignResultToVariable();
21025
21048
  code.append(left);
21026
21049
  code.append(right);
21027
21050
  return code.return(`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
@@ -21059,7 +21082,10 @@ function compilationCacheKey(tokens, dependencies, constantValues) {
21059
21082
  return `|N${constantValues.numbers.indexOf(parseNumber(token.value, DEFAULT_LOCALE))}|`;
21060
21083
  case "REFERENCE":
21061
21084
  case "INVALID_REFERENCE":
21062
- return `|${dependencies.indexOf(token.value)}|`;
21085
+ if (token.value.includes(":")) {
21086
+ return `R|${dependencies.indexOf(token.value)}|`;
21087
+ }
21088
+ return `C|${dependencies.indexOf(token.value)}|`;
21063
21089
  case "SPACE":
21064
21090
  return "";
21065
21091
  default:
@@ -21194,6 +21220,7 @@ const AGGREGATORS_BY_FIELD_TYPE = {
21194
21220
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
21195
21221
  char: ["count_distinct", "count"],
21196
21222
  many2one: ["count_distinct", "count"],
21223
+ reference: ["count_distinct", "count"],
21197
21224
  };
21198
21225
  const AGGREGATORS = {};
21199
21226
  for (const type in AGGREGATORS_BY_FIELD_TYPE) {
@@ -22203,7 +22230,7 @@ function truncateLabel(label) {
22203
22230
  /**
22204
22231
  * Get a default chart js configuration
22205
22232
  */
22206
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale }) {
22233
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22207
22234
  const options = {
22208
22235
  // https://www.chartjs.org/docs/latest/general/responsive.html
22209
22236
  responsive: true, // will resize when its container is resized
@@ -22250,7 +22277,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale })
22250
22277
  type: chart.type,
22251
22278
  options,
22252
22279
  data: {
22253
- labels: labels.map(truncateLabel),
22280
+ labels: truncateLabels ? labels.map(truncateLabel) : labels,
22254
22281
  datasets: [],
22255
22282
  },
22256
22283
  platform: undefined, // This key is optional and will be set by chart.js
@@ -22430,25 +22457,23 @@ class BarChart extends AbstractChart {
22430
22457
  return {
22431
22458
  background: context.background,
22432
22459
  dataSets: context.range ? context.range : [],
22433
- dataSetsHaveTitle: false,
22434
- stacked: false,
22460
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22461
+ stacked: context.stacked ?? false,
22435
22462
  aggregated: context.aggregated ?? false,
22436
- legendPosition: "top",
22463
+ legendPosition: context.legendPosition ?? "top",
22437
22464
  title: context.title || "",
22438
22465
  type: "bar",
22439
- verticalAxisPosition: "left",
22466
+ verticalAxisPosition: context.verticalAxisPosition ?? "left",
22440
22467
  labelRange: context.auxiliaryRange || undefined,
22441
22468
  };
22442
22469
  }
22443
22470
  getContextCreation() {
22444
22471
  return {
22445
- background: this.background,
22446
- title: this.title,
22472
+ ...this,
22447
22473
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
22448
22474
  auxiliaryRange: this.labelRange
22449
22475
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22450
22476
  : undefined,
22451
- aggregated: this.aggregated,
22452
22477
  };
22453
22478
  }
22454
22479
  copyForSheetId(sheetId) {
@@ -22613,13 +22638,11 @@ class ComboChart extends AbstractChart {
22613
22638
  }
22614
22639
  getContextCreation() {
22615
22640
  return {
22616
- background: this.background,
22617
- title: this.title,
22641
+ ...this,
22618
22642
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
22619
22643
  auxiliaryRange: this.labelRange
22620
22644
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
22621
22645
  : undefined,
22622
- aggregated: this.aggregated,
22623
22646
  };
22624
22647
  }
22625
22648
  getDefinition() {
@@ -22669,12 +22692,12 @@ class ComboChart extends AbstractChart {
22669
22692
  static getDefinitionFromContextCreation(context) {
22670
22693
  return {
22671
22694
  background: context.background,
22672
- dataSets: context.range ? context.range : [],
22673
- dataSetsHaveTitle: false,
22695
+ dataSets: context.range ?? [],
22696
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
22674
22697
  aggregated: context.aggregated,
22675
- legendPosition: "top",
22698
+ legendPosition: context.legendPosition ?? "top",
22676
22699
  title: context.title || "",
22677
- verticalAxisPosition: "left",
22700
+ verticalAxisPosition: context.verticalAxisPosition ?? "left",
22678
22701
  labelRange: context.auxiliaryRange || undefined,
22679
22702
  type: "combo",
22680
22703
  useBothYAxis: false,
@@ -22925,8 +22948,7 @@ class GaugeChart extends AbstractChart {
22925
22948
  }
22926
22949
  getContextCreation() {
22927
22950
  return {
22928
- background: this.background,
22929
- title: this.title,
22951
+ ...this,
22930
22952
  range: this.dataRange
22931
22953
  ? [this.getters.getRangeString(this.dataRange, this.sheetId)]
22932
22954
  : undefined,
@@ -23214,9 +23236,9 @@ function isLuxonTimeAdapterInstalled() {
23214
23236
  }
23215
23237
  return isInstalled;
23216
23238
  }
23217
- function getLineOrScatterConfiguration(chart, labels, localeFormat) {
23239
+ function getLineOrScatterConfiguration(chart, labels, options) {
23218
23240
  const fontColor = chartFontColor(chart.background);
23219
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23241
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
23220
23242
  const legend = {
23221
23243
  labels: {
23222
23244
  color: fontColor,
@@ -23259,7 +23281,7 @@ function getLineOrScatterConfiguration(chart, labels, localeFormat) {
23259
23281
  value = Number(value);
23260
23282
  if (isNaN(value))
23261
23283
  return value;
23262
- const { locale, format } = localeFormat;
23284
+ const { locale, format } = options;
23263
23285
  return formatValue(value, {
23264
23286
  locale,
23265
23287
  format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
@@ -23292,9 +23314,10 @@ function createLineOrScatterChartRuntime(chart, getters) {
23292
23314
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
23293
23315
  }
23294
23316
  const locale = getters.getLocale();
23317
+ const truncateLabels = axisType === "category";
23295
23318
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
23296
- const localeFormat = { format: dataSetFormat, locale };
23297
- const config = getLineOrScatterConfiguration(chart, labels, localeFormat);
23319
+ const options = { format: dataSetFormat, locale, truncateLabels };
23320
+ const config = getLineOrScatterConfiguration(chart, labels, options);
23298
23321
  const labelFormat = getChartLabelFormat(getters, chart.labelRange);
23299
23322
  if (axisType === "time") {
23300
23323
  const axis = {
@@ -23394,16 +23417,16 @@ class LineChart extends AbstractChart {
23394
23417
  return {
23395
23418
  background: context.background,
23396
23419
  dataSets: context.range ? context.range : [],
23397
- dataSetsHaveTitle: false,
23398
- labelsAsText: false,
23399
- legendPosition: "top",
23420
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23421
+ labelsAsText: context.labelsAsText ?? false,
23422
+ legendPosition: context.legendPosition ?? "top",
23400
23423
  title: context.title || "",
23401
23424
  type: "line",
23402
- verticalAxisPosition: "left",
23425
+ verticalAxisPosition: context.verticalAxisPosition ?? "left",
23403
23426
  labelRange: context.auxiliaryRange || undefined,
23404
- stacked: false,
23427
+ stacked: context.stacked ?? false,
23405
23428
  aggregated: context.aggregated ?? false,
23406
- cumulative: false,
23429
+ cumulative: context.cumulative ?? false,
23407
23430
  };
23408
23431
  }
23409
23432
  getDefinition() {
@@ -23429,13 +23452,11 @@ class LineChart extends AbstractChart {
23429
23452
  }
23430
23453
  getContextCreation() {
23431
23454
  return {
23432
- background: this.background,
23433
- title: this.title,
23455
+ ...this,
23434
23456
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23435
23457
  auxiliaryRange: this.labelRange
23436
23458
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23437
23459
  : undefined,
23438
- aggregated: this.aggregated,
23439
23460
  };
23440
23461
  }
23441
23462
  updateRanges(applyChange) {
@@ -23505,8 +23526,8 @@ class PieChart extends AbstractChart {
23505
23526
  return {
23506
23527
  background: context.background,
23507
23528
  dataSets: context.range ? context.range : [],
23508
- dataSetsHaveTitle: false,
23509
- legendPosition: "top",
23529
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23530
+ legendPosition: context.legendPosition ?? "top",
23510
23531
  title: context.title || "",
23511
23532
  type: "pie",
23512
23533
  labelRange: context.auxiliaryRange || undefined,
@@ -23518,13 +23539,11 @@ class PieChart extends AbstractChart {
23518
23539
  }
23519
23540
  getContextCreation() {
23520
23541
  return {
23521
- background: this.background,
23522
- title: this.title,
23542
+ ...this,
23523
23543
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23524
23544
  auxiliaryRange: this.labelRange
23525
23545
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23526
23546
  : undefined,
23527
- aggregated: this.aggregated,
23528
23547
  };
23529
23548
  }
23530
23549
  getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
@@ -23709,12 +23728,12 @@ class ScatterChart extends AbstractChart {
23709
23728
  return {
23710
23729
  background: context.background,
23711
23730
  dataSets: context.range ? context.range : [],
23712
- dataSetsHaveTitle: false,
23713
- labelsAsText: false,
23714
- legendPosition: "top",
23731
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23732
+ labelsAsText: context.labelsAsText ?? false,
23733
+ legendPosition: context.legendPosition ?? "top",
23715
23734
  title: context.title || "",
23716
23735
  type: "scatter",
23717
- verticalAxisPosition: "left",
23736
+ verticalAxisPosition: context.verticalAxisPosition ?? "left",
23718
23737
  labelRange: context.auxiliaryRange || undefined,
23719
23738
  aggregated: context.aggregated ?? false,
23720
23739
  };
@@ -23740,13 +23759,11 @@ class ScatterChart extends AbstractChart {
23740
23759
  }
23741
23760
  getContextCreation() {
23742
23761
  return {
23743
- background: this.background,
23744
- title: this.title,
23762
+ ...this,
23745
23763
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23746
23764
  auxiliaryRange: this.labelRange
23747
23765
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23748
23766
  : undefined,
23749
- aggregated: this.aggregated,
23750
23767
  };
23751
23768
  }
23752
23769
  updateRanges(applyChange) {
@@ -23860,27 +23877,25 @@ class WaterfallChart extends AbstractChart {
23860
23877
  return {
23861
23878
  background: context.background,
23862
23879
  dataSets: context.range ? context.range : [],
23863
- dataSetsHaveTitle: false,
23880
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23864
23881
  aggregated: context.aggregated ?? false,
23865
- legendPosition: "top",
23882
+ legendPosition: context.legendPosition ?? "top",
23866
23883
  title: context.title || "",
23867
23884
  type: "waterfall",
23868
- verticalAxisPosition: "left",
23885
+ verticalAxisPosition: context.verticalAxisPosition ?? "left",
23869
23886
  labelRange: context.auxiliaryRange || undefined,
23870
- showSubTotals: true,
23871
- showConnectorLines: true,
23872
- firstValueAsSubtotal: false,
23887
+ showSubTotals: context.showSubTotals ?? false,
23888
+ showConnectorLines: context.showConnectorLines ?? true,
23889
+ firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
23873
23890
  };
23874
23891
  }
23875
23892
  getContextCreation() {
23876
23893
  return {
23877
- background: this.background,
23878
- title: this.title,
23894
+ ...this,
23879
23895
  range: this.dataSets.map((ds) => this.getters.getRangeString(ds.dataRange, this.sheetId)),
23880
23896
  auxiliaryRange: this.labelRange
23881
23897
  ? this.getters.getRangeString(this.labelRange, this.sheetId)
23882
23898
  : undefined,
23883
- aggregated: this.aggregated,
23884
23899
  };
23885
23900
  }
23886
23901
  copyForSheetId(sheetId) {
@@ -24361,7 +24376,7 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
24361
24376
  });
24362
24377
  onFigureDeleted();
24363
24378
  },
24364
- icon: "o-spreadsheet-Icon.DELETE",
24379
+ icon: "o-spreadsheet-Icon.TRASH",
24365
24380
  };
24366
24381
  }
24367
24382
 
@@ -24376,7 +24391,8 @@ const inverseCommandRegistry = new Registry()
24376
24391
  .add("CREATE_FIGURE", inverseCreateFigure)
24377
24392
  .add("CREATE_CHART", inverseCreateChart)
24378
24393
  .add("HIDE_COLUMNS_ROWS", inverseHideColumnsRows)
24379
- .add("UNHIDE_COLUMNS_ROWS", inverseUnhideColumnsRows);
24394
+ .add("UNHIDE_COLUMNS_ROWS", inverseUnhideColumnsRows)
24395
+ .add("CREATE_TABLE_STYLE", inverseCreateTableStyle);
24380
24396
  for (const cmd of coreTypes.values()) {
24381
24397
  if (!inverseCommandRegistry.contains(cmd)) {
24382
24398
  inverseCommandRegistry.add(cmd, identity);
@@ -24461,6 +24477,9 @@ function inverseUnhideColumnsRows(cmd) {
24461
24477
  },
24462
24478
  ];
24463
24479
  }
24480
+ function inverseCreateTableStyle(cmd) {
24481
+ return [{ type: "REMOVE_TABLE_STYLE", tableStyleId: cmd.tableStyleId }];
24482
+ }
24464
24483
 
24465
24484
  /**
24466
24485
  * The class Registry is extended in order to add the function addChild
@@ -25058,7 +25077,7 @@ const CSS = css /* scss */ `
25058
25077
 
25059
25078
  .o-search-icon {
25060
25079
  right: 5px;
25061
- top: 4px;
25080
+ top: 3px;
25062
25081
  opacity: 0.4;
25063
25082
 
25064
25083
  svg {
@@ -25145,8 +25164,12 @@ class FilterMenu extends Component {
25145
25164
  });
25146
25165
  this.state.values = this.getFilterHiddenValues(this.props.filterPosition);
25147
25166
  }
25148
- get isReadonly() {
25149
- return this.env.model.getters.isReadonly();
25167
+ get isSortable() {
25168
+ if (!this.table) {
25169
+ return false;
25170
+ }
25171
+ const coreTable = this.env.model.getters.getCoreTableMatchingTopLeft(this.table.range.sheetId, this.table.range.zone);
25172
+ return !this.env.model.getters.isReadonly() && coreTable?.type !== "dynamic";
25150
25173
  }
25151
25174
  getFilterHiddenValues(position) {
25152
25175
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -26116,10 +26139,10 @@ function getSmartChartDefinition(zone, getters) {
26116
26139
  }
26117
26140
 
26118
26141
  const TABLE_STYLE_CATEGORIES = {
26119
- none: _t("None"),
26120
26142
  light: _t("Light"),
26121
26143
  medium: _t("Medium"),
26122
26144
  dark: _t("Dark"),
26145
+ custom: _t("Custom"),
26123
26146
  };
26124
26147
  const DEFAULT_TABLE_CONFIG = {
26125
26148
  hasFilters: true,
@@ -26132,7 +26155,7 @@ const DEFAULT_TABLE_CONFIG = {
26132
26155
  automaticAutofill: true,
26133
26156
  styleId: "TableStyleMedium2",
26134
26157
  };
26135
- function generateColorSet(name, highlightColor) {
26158
+ function generateTableColorSet(name, highlightColor) {
26136
26159
  return {
26137
26160
  coloredText: darkenColor(highlightColor, 0.3),
26138
26161
  light: lightenColor(highlightColor, 0.8),
@@ -26153,10 +26176,10 @@ const COLOR_SETS = {
26153
26176
  mediumBorder: "#000000",
26154
26177
  highlight: "#000000",
26155
26178
  },
26156
- lightBlue: generateColorSet(_t("Light blue"), "#346B90"),
26157
- red: generateColorSet(_t("Red"), "#C53628"),
26158
- lightGreen: generateColorSet(_t("Light green"), "#748747"),
26159
- purple: generateColorSet(_t("Purple"), "#6C4E65"),
26179
+ lightBlue: generateTableColorSet(_t("Light blue"), "#346B90"),
26180
+ red: generateTableColorSet(_t("Red"), "#C53628"),
26181
+ lightGreen: generateTableColorSet(_t("Light green"), "#748747"),
26182
+ purple: generateTableColorSet(_t("Purple"), "#6C4E65"),
26160
26183
  gray: {
26161
26184
  name: _t("Gray"),
26162
26185
  coloredText: "#666666",
@@ -26166,7 +26189,7 @@ const COLOR_SETS = {
26166
26189
  mediumBorder: "#D0D0D0",
26167
26190
  highlight: "#A9A9A9",
26168
26191
  },
26169
- orange: generateColorSet(_t("Orange"), "#C37034"),
26192
+ orange: generateTableColorSet(_t("Orange"), "#C37034"),
26170
26193
  };
26171
26194
  const DARK_COLOR_SETS = {
26172
26195
  black: COLOR_SETS.black,
@@ -26174,9 +26197,10 @@ const DARK_COLOR_SETS = {
26174
26197
  purpleGreen: { ...COLOR_SETS.lightGreen, highlight: COLOR_SETS.purple.highlight },
26175
26198
  redBlue: { ...COLOR_SETS.lightBlue, highlight: COLOR_SETS.red.highlight },
26176
26199
  };
26177
- const lightTemplateColoredText = (colorSet) => ({
26200
+ const lightColoredText = (colorSet) => ({
26178
26201
  category: "light",
26179
- colorName: colorSet.name,
26202
+ templateName: "lightColoredText",
26203
+ primaryColor: colorSet.highlight,
26180
26204
  wholeTable: {
26181
26205
  style: { textColor: colorSet.coloredText },
26182
26206
  border: {
@@ -26188,9 +26212,10 @@ const lightTemplateColoredText = (colorSet) => ({
26188
26212
  totalRow: { border: { top: { color: colorSet.highlight, style: "thin" } } },
26189
26213
  firstRowStripe: { style: { fillColor: colorSet.light } },
26190
26214
  });
26191
- const lightTemplateWithHeader = (colorSet) => ({
26215
+ const lightWithHeader = (colorSet) => ({
26192
26216
  category: "light",
26193
- colorName: colorSet.name,
26217
+ templateName: "lightWithHeader",
26218
+ primaryColor: colorSet.highlight,
26194
26219
  wholeTable: {
26195
26220
  border: {
26196
26221
  top: { color: colorSet.highlight, style: "thin" },
@@ -26207,9 +26232,10 @@ const lightTemplateWithHeader = (colorSet) => ({
26207
26232
  firstRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
26208
26233
  secondRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
26209
26234
  });
26210
- const lightTemplateAllBorders = (colorSet) => ({
26235
+ const lightAllBorders = (colorSet) => ({
26211
26236
  category: "light",
26212
- colorName: colorSet.name,
26237
+ templateName: "lightAllBorders",
26238
+ primaryColor: colorSet.highlight,
26213
26239
  wholeTable: {
26214
26240
  border: {
26215
26241
  top: { color: colorSet.highlight, style: "thin" },
@@ -26225,9 +26251,10 @@ const lightTemplateAllBorders = (colorSet) => ({
26225
26251
  firstRowStripe: { style: { fillColor: colorSet.light } },
26226
26252
  firstColumnStripe: { style: { fillColor: colorSet.light } },
26227
26253
  });
26228
- const mediumTemplateBandedBorders = (colorSet) => ({
26254
+ const mediumBandedBorders = (colorSet) => ({
26229
26255
  category: "medium",
26230
- colorName: colorSet.name,
26256
+ templateName: "mediumBandedBorders",
26257
+ primaryColor: colorSet.highlight,
26231
26258
  wholeTable: {
26232
26259
  border: {
26233
26260
  top: { color: colorSet.mediumBorder, style: "thin" },
@@ -26244,9 +26271,10 @@ const mediumTemplateBandedBorders = (colorSet) => ({
26244
26271
  firstRowStripe: { style: { fillColor: colorSet.light } },
26245
26272
  firstColumnStripe: { style: { fillColor: colorSet.light } },
26246
26273
  });
26247
- const mediumTemplateWhiteBorders = (colorSet) => ({
26274
+ const mediumWhiteBorders = (colorSet) => ({
26248
26275
  category: "medium",
26249
- colorName: colorSet.name,
26276
+ templateName: "mediumWhiteBorders",
26277
+ primaryColor: colorSet.highlight,
26250
26278
  wholeTable: {
26251
26279
  border: {
26252
26280
  horizontal: { color: "#FFFFFF", style: "thin" },
@@ -26267,9 +26295,10 @@ const mediumTemplateWhiteBorders = (colorSet) => ({
26267
26295
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26268
26296
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26269
26297
  });
26270
- const mediumTemplateMinimalBorders = (colorSet) => ({
26298
+ const mediumMinimalBorders = (colorSet) => ({
26271
26299
  category: "medium",
26272
- colorName: colorSet.name,
26300
+ templateName: "mediumMinimalBorders",
26301
+ primaryColor: colorSet.highlight,
26273
26302
  wholeTable: {
26274
26303
  border: {
26275
26304
  top: { color: "#000000", style: "medium" },
@@ -26286,9 +26315,10 @@ const mediumTemplateMinimalBorders = (colorSet) => ({
26286
26315
  firstRowStripe: { style: { fillColor: COLOR_SETS.black.light } },
26287
26316
  firstColumnStripe: { style: { fillColor: COLOR_SETS.black.light } },
26288
26317
  });
26289
- const mediumTemplateAllBorders = (colorSet) => ({
26318
+ const mediumAllBorders = (colorSet) => ({
26290
26319
  category: "medium",
26291
- colorName: colorSet.name,
26320
+ templateName: "mediumAllBorders",
26321
+ primaryColor: colorSet.highlight,
26292
26322
  wholeTable: {
26293
26323
  border: {
26294
26324
  top: { color: colorSet.mediumBorder, style: "thin" },
@@ -26304,9 +26334,10 @@ const mediumTemplateAllBorders = (colorSet) => ({
26304
26334
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26305
26335
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26306
26336
  });
26307
- const darkTemplate = (colorSet) => ({
26337
+ const dark = (colorSet) => ({
26308
26338
  category: "dark",
26309
- colorName: colorSet.name,
26339
+ templateName: "dark",
26340
+ primaryColor: colorSet.highlight,
26310
26341
  wholeTable: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
26311
26342
  totalRow: {
26312
26343
  style: { fillColor: colorSet.dark, textColor: "#FFFFFF" },
@@ -26327,18 +26358,19 @@ const darkTemplate = (colorSet) => ({
26327
26358
  firstRowStripe: { style: { fillColor: colorSet.dark } },
26328
26359
  firstColumnStripe: { style: { fillColor: colorSet.dark } },
26329
26360
  });
26330
- const darkTemplateNoBorders = (colorSet) => ({
26361
+ const darkNoBorders = (colorSet) => ({
26331
26362
  category: "dark",
26332
- colorName: colorSet.name,
26363
+ templateName: "darkNoBorders",
26364
+ primaryColor: colorSet.highlight,
26333
26365
  wholeTable: { style: { fillColor: colorSet.light } },
26334
26366
  totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
26335
26367
  headerRow: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
26336
26368
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26337
26369
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26338
26370
  });
26339
- const darkTemplateInBlack = darkTemplate(COLOR_SETS.black);
26371
+ const darkTemplateInBlack = dark(COLOR_SETS.black);
26340
26372
  darkTemplateInBlack.wholeTable.style.fillColor = "#737373";
26341
- const mediumMinimalBordersInBlack = mediumTemplateMinimalBorders(COLOR_SETS.black);
26373
+ const mediumMinimalBordersInBlack = mediumMinimalBorders(COLOR_SETS.black);
26342
26374
  mediumMinimalBordersInBlack.wholeTable.border = {
26343
26375
  ...mediumMinimalBordersInBlack.wholeTable.border,
26344
26376
  left: { color: "#000000", style: "thin" },
@@ -26346,69 +26378,92 @@ mediumMinimalBordersInBlack.wholeTable.border = {
26346
26378
  horizontal: { color: "#000000", style: "thin" },
26347
26379
  vertical: { color: "#000000", style: "thin" },
26348
26380
  };
26381
+ function buildPreset(name, template, colorSet) {
26382
+ return { ...template(colorSet), displayName: `${colorSet.name}, ${name}` };
26383
+ }
26349
26384
  const TABLE_PRESETS = {
26350
- None: { category: "none", colorName: "" },
26351
- TableStyleLight1: lightTemplateColoredText(COLOR_SETS.black),
26352
- TableStyleLight2: lightTemplateColoredText(COLOR_SETS.lightBlue),
26353
- TableStyleLight3: lightTemplateColoredText(COLOR_SETS.red),
26354
- TableStyleLight4: lightTemplateColoredText(COLOR_SETS.lightGreen),
26355
- TableStyleLight5: lightTemplateColoredText(COLOR_SETS.purple),
26356
- TableStyleLight6: lightTemplateColoredText(COLOR_SETS.gray),
26357
- TableStyleLight7: lightTemplateColoredText(COLOR_SETS.orange),
26358
- TableStyleLight8: lightTemplateWithHeader(COLOR_SETS.black),
26359
- TableStyleLight9: lightTemplateWithHeader(COLOR_SETS.lightBlue),
26360
- TableStyleLight10: lightTemplateWithHeader(COLOR_SETS.red),
26361
- TableStyleLight11: lightTemplateWithHeader(COLOR_SETS.lightGreen),
26362
- TableStyleLight12: lightTemplateWithHeader(COLOR_SETS.purple),
26363
- TableStyleLight13: lightTemplateWithHeader(COLOR_SETS.gray),
26364
- TableStyleLight14: lightTemplateWithHeader(COLOR_SETS.orange),
26365
- TableStyleLight15: lightTemplateAllBorders(COLOR_SETS.black),
26366
- TableStyleLight16: lightTemplateAllBorders(COLOR_SETS.lightBlue),
26367
- TableStyleLight17: lightTemplateAllBorders(COLOR_SETS.red),
26368
- TableStyleLight18: lightTemplateAllBorders(COLOR_SETS.lightGreen),
26369
- TableStyleLight19: lightTemplateAllBorders(COLOR_SETS.purple),
26370
- TableStyleLight20: lightTemplateAllBorders(COLOR_SETS.gray),
26371
- TableStyleLight21: lightTemplateAllBorders(COLOR_SETS.orange),
26372
- TableStyleMedium1: mediumTemplateBandedBorders(COLOR_SETS.black),
26373
- TableStyleMedium2: mediumTemplateBandedBorders(COLOR_SETS.lightBlue),
26374
- TableStyleMedium3: mediumTemplateBandedBorders(COLOR_SETS.red),
26375
- TableStyleMedium4: mediumTemplateBandedBorders(COLOR_SETS.lightGreen),
26376
- TableStyleMedium5: mediumTemplateBandedBorders(COLOR_SETS.purple),
26377
- TableStyleMedium6: mediumTemplateBandedBorders(COLOR_SETS.gray),
26378
- TableStyleMedium7: mediumTemplateBandedBorders(COLOR_SETS.orange),
26379
- TableStyleMedium8: mediumTemplateWhiteBorders(COLOR_SETS.black),
26380
- TableStyleMedium9: mediumTemplateWhiteBorders(COLOR_SETS.lightBlue),
26381
- TableStyleMedium10: mediumTemplateWhiteBorders(COLOR_SETS.red),
26382
- TableStyleMedium11: mediumTemplateWhiteBorders(COLOR_SETS.lightGreen),
26383
- TableStyleMedium12: mediumTemplateWhiteBorders(COLOR_SETS.purple),
26384
- TableStyleMedium13: mediumTemplateWhiteBorders(COLOR_SETS.gray),
26385
- TableStyleMedium14: mediumTemplateWhiteBorders(COLOR_SETS.orange),
26386
- TableStyleMedium15: mediumMinimalBordersInBlack,
26387
- TableStyleMedium16: mediumTemplateMinimalBorders(COLOR_SETS.lightBlue),
26388
- TableStyleMedium17: mediumTemplateMinimalBorders(COLOR_SETS.red),
26389
- TableStyleMedium18: mediumTemplateMinimalBorders(COLOR_SETS.lightGreen),
26390
- TableStyleMedium19: mediumTemplateMinimalBorders(COLOR_SETS.purple),
26391
- TableStyleMedium20: mediumTemplateMinimalBorders(COLOR_SETS.gray),
26392
- TableStyleMedium21: mediumTemplateMinimalBorders(COLOR_SETS.orange),
26393
- TableStyleMedium22: mediumTemplateAllBorders(COLOR_SETS.black),
26394
- TableStyleMedium23: mediumTemplateAllBorders(COLOR_SETS.lightBlue),
26395
- TableStyleMedium24: mediumTemplateAllBorders(COLOR_SETS.red),
26396
- TableStyleMedium25: mediumTemplateAllBorders(COLOR_SETS.lightGreen),
26397
- TableStyleMedium26: mediumTemplateAllBorders(COLOR_SETS.purple),
26398
- TableStyleMedium27: mediumTemplateAllBorders(COLOR_SETS.gray),
26399
- TableStyleMedium28: mediumTemplateAllBorders(COLOR_SETS.orange),
26400
- TableStyleDark1: darkTemplateInBlack,
26401
- TableStyleDark2: darkTemplate(COLOR_SETS.lightBlue),
26402
- TableStyleDark3: darkTemplate(COLOR_SETS.red),
26403
- TableStyleDark4: darkTemplate(COLOR_SETS.lightGreen),
26404
- TableStyleDark5: darkTemplate(COLOR_SETS.purple),
26405
- TableStyleDark6: darkTemplate(COLOR_SETS.gray),
26406
- TableStyleDark7: darkTemplate(COLOR_SETS.orange),
26407
- TableStyleDark8: darkTemplateNoBorders(DARK_COLOR_SETS.black),
26408
- TableStyleDark9: darkTemplateNoBorders(DARK_COLOR_SETS.redBlue),
26409
- TableStyleDark10: darkTemplateNoBorders(DARK_COLOR_SETS.purpleGreen),
26410
- TableStyleDark11: darkTemplateNoBorders(DARK_COLOR_SETS.orangeBlue),
26411
- };
26385
+ None: { category: "light", templateName: "none", primaryColor: "", displayName: "none" },
26386
+ TableStyleLight1: buildPreset("TableStyleLight1", lightColoredText, COLOR_SETS.black),
26387
+ TableStyleLight2: buildPreset("TableStyleLight2", lightColoredText, COLOR_SETS.lightBlue),
26388
+ TableStyleLight3: buildPreset("TableStyleLight3", lightColoredText, COLOR_SETS.red),
26389
+ TableStyleLight4: buildPreset("TableStyleLight4", lightColoredText, COLOR_SETS.lightGreen),
26390
+ TableStyleLight5: buildPreset("TableStyleLight5", lightColoredText, COLOR_SETS.purple),
26391
+ TableStyleLight6: buildPreset("TableStyleLight6", lightColoredText, COLOR_SETS.gray),
26392
+ TableStyleLight7: buildPreset("TableStyleLight7", lightColoredText, COLOR_SETS.orange),
26393
+ TableStyleLight8: buildPreset("TableStyleLight8", lightWithHeader, COLOR_SETS.black),
26394
+ TableStyleLight9: buildPreset("TableStyleLight9", lightWithHeader, COLOR_SETS.lightBlue),
26395
+ TableStyleLight10: buildPreset("TableStyleLight10", lightWithHeader, COLOR_SETS.red),
26396
+ TableStyleLight11: buildPreset("TableStyleLight11", lightWithHeader, COLOR_SETS.lightGreen),
26397
+ TableStyleLight12: buildPreset("TableStyleLight12", lightWithHeader, COLOR_SETS.purple),
26398
+ TableStyleLight13: buildPreset("TableStyleLight13", lightWithHeader, COLOR_SETS.gray),
26399
+ TableStyleLight14: buildPreset("TableStyleLight14", lightWithHeader, COLOR_SETS.orange),
26400
+ TableStyleLight15: buildPreset("TableStyleLight15", lightAllBorders, COLOR_SETS.black),
26401
+ TableStyleLight16: buildPreset("TableStyleLight16", lightAllBorders, COLOR_SETS.lightBlue),
26402
+ TableStyleLight17: buildPreset("TableStyleLight17", lightAllBorders, COLOR_SETS.red),
26403
+ TableStyleLight18: buildPreset("TableStyleLight18", lightAllBorders, COLOR_SETS.lightGreen),
26404
+ TableStyleLight19: buildPreset("TableStyleLight19", lightAllBorders, COLOR_SETS.purple),
26405
+ TableStyleLight20: buildPreset("TableStyleLight20", lightAllBorders, COLOR_SETS.gray),
26406
+ TableStyleLight21: buildPreset("TableStyleLight21", lightAllBorders, COLOR_SETS.orange),
26407
+ TableStyleMedium1: buildPreset("TableStyleMedium1", mediumBandedBorders, COLOR_SETS.black),
26408
+ TableStyleMedium2: buildPreset("TableStyleMedium2", mediumBandedBorders, COLOR_SETS.lightBlue),
26409
+ TableStyleMedium3: buildPreset("TableStyleMedium3", mediumBandedBorders, COLOR_SETS.red),
26410
+ TableStyleMedium4: buildPreset("TableStyleMedium4", mediumBandedBorders, COLOR_SETS.lightGreen),
26411
+ TableStyleMedium5: buildPreset("TableStyleMedium5", mediumBandedBorders, COLOR_SETS.purple),
26412
+ TableStyleMedium6: buildPreset("TableStyleMedium6", mediumBandedBorders, COLOR_SETS.gray),
26413
+ TableStyleMedium7: buildPreset("TableStyleMedium7", mediumBandedBorders, COLOR_SETS.orange),
26414
+ TableStyleMedium8: buildPreset("TableStyleMedium8", mediumWhiteBorders, COLOR_SETS.black),
26415
+ TableStyleMedium9: buildPreset("TableStyleMedium9", mediumWhiteBorders, COLOR_SETS.lightBlue),
26416
+ TableStyleMedium10: buildPreset("TableStyleMedium10", mediumWhiteBorders, COLOR_SETS.red),
26417
+ TableStyleMedium11: buildPreset("TableStyleMedium11", mediumWhiteBorders, COLOR_SETS.lightGreen),
26418
+ TableStyleMedium12: buildPreset("TableStyleMedium12", mediumWhiteBorders, COLOR_SETS.purple),
26419
+ TableStyleMedium13: buildPreset("TableStyleMedium13", mediumWhiteBorders, COLOR_SETS.gray),
26420
+ TableStyleMedium14: buildPreset("TableStyleMedium14", mediumWhiteBorders, COLOR_SETS.orange),
26421
+ TableStyleMedium15: { ...mediumMinimalBordersInBlack, displayName: "Black, TableStyleMedium15" },
26422
+ TableStyleMedium16: buildPreset("TableStyleMedium16", mediumMinimalBorders, COLOR_SETS.lightBlue),
26423
+ TableStyleMedium17: buildPreset("TableStyleMedium17", mediumMinimalBorders, COLOR_SETS.red),
26424
+ TableStyleMedium18: buildPreset("TableStyleMedium18", mediumMinimalBorders, COLOR_SETS.lightGreen),
26425
+ TableStyleMedium19: buildPreset("TableStyleMedium19", mediumMinimalBorders, COLOR_SETS.purple),
26426
+ TableStyleMedium20: buildPreset("TableStyleMedium20", mediumMinimalBorders, COLOR_SETS.gray),
26427
+ TableStyleMedium21: buildPreset("TableStyleMedium21", mediumMinimalBorders, COLOR_SETS.orange),
26428
+ TableStyleMedium22: buildPreset("TableStyleMedium22", mediumAllBorders, COLOR_SETS.black),
26429
+ TableStyleMedium23: buildPreset("TableStyleMedium23", mediumAllBorders, COLOR_SETS.lightBlue),
26430
+ TableStyleMedium24: buildPreset("TableStyleMedium24", mediumAllBorders, COLOR_SETS.red),
26431
+ TableStyleMedium25: buildPreset("TableStyleMedium25", mediumAllBorders, COLOR_SETS.lightGreen),
26432
+ TableStyleMedium26: buildPreset("TableStyleMedium26", mediumAllBorders, COLOR_SETS.purple),
26433
+ TableStyleMedium27: buildPreset("TableStyleMedium27", mediumAllBorders, COLOR_SETS.gray),
26434
+ TableStyleMedium28: buildPreset("TableStyleMedium28", mediumAllBorders, COLOR_SETS.orange),
26435
+ TableStyleDark1: { ...darkTemplateInBlack, displayName: "Black, TableStyleDark1" },
26436
+ TableStyleDark2: buildPreset("TableStyleDark2", dark, COLOR_SETS.lightBlue),
26437
+ TableStyleDark3: buildPreset("TableStyleDark3", dark, COLOR_SETS.red),
26438
+ TableStyleDark4: buildPreset("TableStyleDark4", dark, COLOR_SETS.lightGreen),
26439
+ TableStyleDark5: buildPreset("TableStyleDark5", dark, COLOR_SETS.purple),
26440
+ TableStyleDark6: buildPreset("TableStyleDark6", dark, COLOR_SETS.gray),
26441
+ TableStyleDark7: buildPreset("TableStyleDark7", dark, COLOR_SETS.orange),
26442
+ TableStyleDark8: buildPreset("TableStyleDark8", darkNoBorders, DARK_COLOR_SETS.black),
26443
+ TableStyleDark9: buildPreset("TableStyleDark9", darkNoBorders, DARK_COLOR_SETS.redBlue),
26444
+ TableStyleDark10: buildPreset("TableStyleDark10", darkNoBorders, DARK_COLOR_SETS.purpleGreen),
26445
+ TableStyleDark11: buildPreset("TableStyleDark11", darkNoBorders, DARK_COLOR_SETS.orangeBlue),
26446
+ };
26447
+ const TABLE_STYLES_TEMPLATES = {
26448
+ none: () => ({ category: "none", templateName: "none", primaryColor: "", name: "none" }),
26449
+ lightColoredText: lightColoredText,
26450
+ lightAllBorders: lightAllBorders,
26451
+ mediumAllBorders: mediumAllBorders,
26452
+ lightWithHeader: lightWithHeader,
26453
+ mediumBandedBorders: mediumBandedBorders,
26454
+ mediumMinimalBorders: mediumMinimalBorders,
26455
+ darkNoBorders: darkNoBorders,
26456
+ mediumWhiteBorders: mediumWhiteBorders,
26457
+ dark: dark,
26458
+ };
26459
+ function buildTableStyle(name, templateName, primaryColor) {
26460
+ const colorSet = generateTableColorSet("", primaryColor);
26461
+ return {
26462
+ ...TABLE_STYLES_TEMPLATES[templateName](colorSet),
26463
+ category: "custom",
26464
+ displayName: name,
26465
+ };
26466
+ }
26412
26467
 
26413
26468
  /**
26414
26469
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
@@ -26971,7 +27026,7 @@ const findAndReplace = {
26971
27026
  execute: (env) => {
26972
27027
  env.openSidePanel("FindAndReplace", {});
26973
27028
  },
26974
- icon: "o-spreadsheet-Icon.FIND_AND_REPLACE",
27029
+ icon: "o-spreadsheet-Icon.SEARCH",
26975
27030
  };
26976
27031
  const deleteValues = {
26977
27032
  name: _t("Delete values"),
@@ -27471,18 +27526,18 @@ cellMenuRegistry
27471
27526
  .add("delete_row", {
27472
27527
  ...deleteRow,
27473
27528
  sequence: 110,
27474
- icon: "o-spreadsheet-Icon.DELETE",
27529
+ icon: "o-spreadsheet-Icon.TRASH",
27475
27530
  })
27476
27531
  .add("delete_column", {
27477
27532
  ...deleteCol,
27478
27533
  sequence: 120,
27479
- icon: "o-spreadsheet-Icon.DELETE",
27534
+ icon: "o-spreadsheet-Icon.TRASH",
27480
27535
  })
27481
27536
  .add("delete_cell", {
27482
27537
  ...deleteCells,
27483
27538
  sequence: 130,
27484
27539
  separator: true,
27485
- icon: "o-spreadsheet-Icon.DELETE",
27540
+ icon: "o-spreadsheet-Icon.TRASH",
27486
27541
  })
27487
27542
  .addChild("delete_cell_up", ["delete_cell"], {
27488
27543
  ...deleteCellShiftUp,
@@ -27598,6 +27653,7 @@ function createFormatActionSpec({ name, format, descriptionValue, }) {
27598
27653
  }),
27599
27654
  execute: (env) => setFormatter(env, formatCallback(env)),
27600
27655
  isActive: (env) => isFormatSelected(env, formatCallback(env)),
27656
+ format,
27601
27657
  };
27602
27658
  }
27603
27659
  const formatNumberAutomatic = {
@@ -27950,7 +28006,9 @@ function getWrapModeIcon(env) {
27950
28006
 
27951
28007
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
27952
28008
  __proto__: null,
28009
+ EXAMPLE_DATE: EXAMPLE_DATE,
27953
28010
  clearFormat: clearFormat,
28011
+ createFormatActionSpec: createFormatActionSpec,
27954
28012
  decraseDecimalPlaces: decraseDecimalPlaces,
27955
28013
  fillColor: fillColor,
27956
28014
  formatAlignment: formatAlignment,
@@ -28318,7 +28376,7 @@ colMenuRegistry
28318
28376
  .add("delete_column", {
28319
28377
  ...deleteCols,
28320
28378
  sequence: 90,
28321
- icon: "o-spreadsheet-Icon.DELETE",
28379
+ icon: "o-spreadsheet-Icon.TRASH",
28322
28380
  })
28323
28381
  .add("clear_column", {
28324
28382
  ...clearCols,
@@ -28361,64 +28419,119 @@ colMenuRegistry
28361
28419
  isVisible: (env) => canUngroupHeaders(env, "COL"),
28362
28420
  });
28363
28421
 
28364
- const numberFormatMenuRegistry = new MenuItemRegistry();
28422
+ const numberFormatMenuRegistry = new Registry();
28365
28423
  numberFormatMenuRegistry
28366
28424
  .add("format_number_automatic", {
28367
28425
  ...formatNumberAutomatic,
28426
+ id: "format_number_automatic",
28368
28427
  sequence: 10,
28369
28428
  })
28370
28429
  .add("format_number_plain_text", {
28371
28430
  ...formatNumberPlainText,
28431
+ id: "format_number_plain_text",
28372
28432
  sequence: 15,
28373
28433
  separator: true,
28374
28434
  })
28375
28435
  .add("format_number_number", {
28376
28436
  ...formatNumberNumber,
28437
+ id: "format_number_number",
28377
28438
  sequence: 20,
28378
28439
  })
28379
28440
  .add("format_number_percent", {
28380
28441
  ...formatNumberPercent,
28442
+ id: "format_number_percent",
28381
28443
  sequence: 30,
28382
28444
  separator: true,
28383
28445
  })
28384
28446
  .add("format_number_currency", {
28385
28447
  ...formatNumberCurrency,
28448
+ id: "format_number_currency",
28386
28449
  sequence: 40,
28387
28450
  })
28388
28451
  .add("format_number_currency_rounded", {
28389
28452
  ...formatNumberCurrencyRounded,
28453
+ id: "format_number_currency_rounded",
28390
28454
  sequence: 50,
28391
28455
  })
28392
28456
  .add("format_custom_currency", {
28393
28457
  ...formatCustomCurrency,
28458
+ id: "format_custom_currency",
28394
28459
  sequence: 60,
28395
28460
  separator: true,
28396
28461
  })
28397
28462
  .add("format_number_date", {
28398
28463
  ...formatNumberDate,
28464
+ id: "format_number_date",
28399
28465
  sequence: 70,
28400
28466
  })
28401
28467
  .add("format_number_time", {
28402
28468
  ...formatNumberTime,
28469
+ id: "format_number_time",
28403
28470
  sequence: 80,
28404
28471
  })
28405
28472
  .add("format_number_date_time", {
28406
28473
  ...formatNumberDateTime,
28474
+ id: "format_number_date_time",
28407
28475
  sequence: 90,
28408
28476
  })
28409
28477
  .add("format_number_duration", {
28410
28478
  ...formatNumberDuration,
28479
+ id: "format_number_duration",
28411
28480
  sequence: 100,
28412
28481
  separator: true,
28413
28482
  })
28414
28483
  .add("more_formats", {
28415
28484
  ...moreFormats,
28416
- sequence: 110,
28485
+ id: "more_formats",
28486
+ sequence: 120,
28487
+ });
28488
+ function getCustomNumberFormats(env) {
28489
+ const defaultFormats = new Set(numberFormatMenuRegistry
28490
+ .getAll()
28491
+ .map((f) => (typeof f.format === "function" ? f.format(env) : f.format)));
28492
+ const customFormats = new Map();
28493
+ for (const sheetId of env.model.getters.getSheetIds()) {
28494
+ const cells = env.model.getters.getEvaluatedCells(sheetId);
28495
+ for (const cellId in cells) {
28496
+ const cell = cells[cellId];
28497
+ if (cell.format && !customFormats.has(cell.format) && !defaultFormats.has(cell.format)) {
28498
+ const formatType = getNumberFormatType(cell.format);
28499
+ if (formatType === "date" || formatType === "currency") {
28500
+ customFormats.set(cell.format, createFormatActionSpec({
28501
+ descriptionValue: formatType === "currency" ? 1000 : EXAMPLE_DATE,
28502
+ format: cell.format,
28503
+ name: cell.format,
28504
+ }));
28505
+ }
28506
+ }
28507
+ }
28508
+ }
28509
+ return [...customFormats.values()];
28510
+ }
28511
+ const getNumberFormatType = memoize((format) => {
28512
+ if (isDateTimeFormat(format)) {
28513
+ return "date";
28514
+ }
28515
+ else if (format.includes("[$")) {
28516
+ return "currency";
28517
+ }
28518
+ return "number";
28417
28519
  });
28418
28520
  const formatNumberMenuItemSpec = {
28419
28521
  name: _t("More formats"),
28420
28522
  icon: "o-spreadsheet-Icon.NUMBER_FORMATS",
28421
- children: [() => numberFormatMenuRegistry.getAll()],
28523
+ children: [
28524
+ (env) => {
28525
+ const customFormats = getCustomNumberFormats(env).map((action) => ({
28526
+ ...action,
28527
+ sequence: 110,
28528
+ }));
28529
+ if (customFormats.length > 0) {
28530
+ customFormats[customFormats.length - 1].separator = true;
28531
+ }
28532
+ return createActions([...numberFormatMenuRegistry.getAll(), ...customFormats]);
28533
+ },
28534
+ ],
28422
28535
  };
28423
28536
 
28424
28537
  const rowMenuRegistry = new MenuItemRegistry();
@@ -28459,7 +28572,7 @@ rowMenuRegistry
28459
28572
  .add("delete_row", {
28460
28573
  ...deleteRows,
28461
28574
  sequence: 70,
28462
- icon: "o-spreadsheet-Icon.DELETE",
28575
+ icon: "o-spreadsheet-Icon.TRASH",
28463
28576
  })
28464
28577
  .add("clear_row", {
28465
28578
  ...clearRows,
@@ -28610,7 +28723,7 @@ topbarMenuRegistry
28610
28723
  })
28611
28724
  .addChild("delete", ["edit"], {
28612
28725
  name: _t("Delete"),
28613
- icon: "o-spreadsheet-Icon.DELETE",
28726
+ icon: "o-spreadsheet-Icon.TRASH",
28614
28727
  sequence: 70,
28615
28728
  })
28616
28729
  .addChild("edit_delete_cell_values", ["edit", "delete"], {
@@ -30814,9 +30927,26 @@ chartSidePanelComponentRegistry
30814
30927
 
30815
30928
  class MainChartPanelStore extends SpreadsheetStore {
30816
30929
  panel = "configuration";
30930
+ creationContext = {};
30817
30931
  activatePanel(panel) {
30818
30932
  this.panel = panel;
30819
30933
  }
30934
+ changeChartType(figureId, type) {
30935
+ this.creationContext = {
30936
+ ...this.creationContext,
30937
+ ...this.getters.getContextCreationChart(figureId),
30938
+ };
30939
+ const sheetId = this.getters.getFigureSheetId(figureId);
30940
+ if (!sheetId) {
30941
+ return;
30942
+ }
30943
+ const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
30944
+ this.model.dispatch("UPDATE_CHART", {
30945
+ definition,
30946
+ id: figureId,
30947
+ sheetId,
30948
+ });
30949
+ }
30820
30950
  }
30821
30951
 
30822
30952
  css /* scss */ `
@@ -30886,16 +31016,7 @@ class ChartPanel extends Component {
30886
31016
  if (!this.figureId) {
30887
31017
  return;
30888
31018
  }
30889
- const context = this.env.model.getters.getContextCreationChart(this.figureId);
30890
- if (!context) {
30891
- throw new Error("Chart not defined.");
30892
- }
30893
- const definition = getChartDefinitionFromContextCreation(context, type);
30894
- this.env.model.dispatch("UPDATE_CHART", {
30895
- definition,
30896
- id: this.figureId,
30897
- sheetId: this.env.model.getters.getFigureSheetId(this.figureId),
30898
- });
31019
+ this.store.changeChartType(this.figureId, type);
30899
31020
  }
30900
31021
  get chartPanel() {
30901
31022
  if (!this.figureId) {
@@ -30922,6 +31043,14 @@ class ChartPanel extends Component {
30922
31043
  css /* scss */ `
30923
31044
  .o-spreadsheet {
30924
31045
  .o-icon {
31046
+ display: flex;
31047
+ align-items: center;
31048
+ justify-content: center;
31049
+ width: ${ICON_EDGE_LENGTH}px;
31050
+ height: ${ICON_EDGE_LENGTH}px;
31051
+ font-size: ${ICON_EDGE_LENGTH}px;
31052
+ vertical-align: middle;
31053
+
30925
31054
  .small-text {
30926
31055
  font: bold 9px sans-serif;
30927
31056
  }
@@ -30929,6 +31058,9 @@ css /* scss */ `
30929
31058
  font: bold 16px sans-serif;
30930
31059
  }
30931
31060
  }
31061
+ .fa-small {
31062
+ font-size: 14px;
31063
+ }
30932
31064
  }
30933
31065
  `;
30934
31066
  // -----------------------------------------------------------------------------
@@ -33552,15 +33684,14 @@ function createFilter(id, range, config, createRange) {
33552
33684
  function isStaticTable(table) {
33553
33685
  return table.type === "static" || table.type === "forceStatic";
33554
33686
  }
33555
- function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
33687
+ function getComputedTableStyle(tableConfig, style, numberOfCols, numberOfRows) {
33556
33688
  return {
33557
- borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
33558
- styles: getAllTableStyles(tableConfig, numberOfCols, numberOfRows),
33689
+ borders: getAllTableBorders(tableConfig, style, numberOfCols, numberOfRows),
33690
+ styles: getAllTableStyles(tableConfig, style, numberOfCols, numberOfRows),
33559
33691
  };
33560
33692
  }
33561
- function getAllTableBorders(tableConfig, nOfCols, nOfRows) {
33693
+ function getAllTableBorders(tableConfig, style, nOfCols, nOfRows) {
33562
33694
  const borders = generateMatrix(nOfCols, nOfRows, () => ({}));
33563
- const style = TABLE_PRESETS[tableConfig.styleId];
33564
33695
  for (const tableElement of TABLE_ELEMENTS_BY_PRIORITY) {
33565
33696
  const styleBorder = style[tableElement]?.border;
33566
33697
  if (!styleBorder)
@@ -33628,9 +33759,8 @@ function setBorderDescr(computedBorders, dir, borderDescr, col, row, numberOfCol
33628
33759
  return;
33629
33760
  }
33630
33761
  }
33631
- function getAllTableStyles(tableConfig, numberOfCols, numberOfRows) {
33762
+ function getAllTableStyles(tableConfig, style, numberOfCols, numberOfRows) {
33632
33763
  const styles = generateMatrix(numberOfCols, numberOfRows, () => ({}));
33633
- const style = TABLE_PRESETS[tableConfig.styleId];
33634
33764
  for (const tableElement of TABLE_ELEMENTS_BY_PRIORITY) {
33635
33765
  const tableElStyle = style[tableElement];
33636
33766
  const bold = isTableElementInBold(tableElement);
@@ -33724,8 +33854,25 @@ function getTableElementZones(el, tableConfig, numberOfCols, numberOfRows) {
33724
33854
  }
33725
33855
  return zones;
33726
33856
  }
33727
- function getTableStyleName(styleId, tableStyle) {
33728
- return tableStyle.colorName ? `${tableStyle.colorName}, ${styleId}` : styleId;
33857
+
33858
+ function createTableStyleContextMenuActions(env, styleId) {
33859
+ if (!env.model.getters.isTableStyleEditable(styleId)) {
33860
+ return [];
33861
+ }
33862
+ return createActions([
33863
+ {
33864
+ id: "editTableStyle",
33865
+ name: _t("Edit table style"),
33866
+ execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
33867
+ icon: "o-spreadsheet-Icon.EDIT",
33868
+ },
33869
+ {
33870
+ id: "deleteTableStyle",
33871
+ name: _t("Delete table style"),
33872
+ execute: (env) => env.model.dispatch("REMOVE_TABLE_STYLE", { tableStyleId: styleId }),
33873
+ icon: "o-spreadsheet-Icon.TRASH",
33874
+ },
33875
+ ]);
33729
33876
  }
33730
33877
 
33731
33878
  function drawPreviewTable(ctx, tableStyle, colWidth, rowHeight) {
@@ -33808,55 +33955,120 @@ function drawTexts(ctx, tableStyle, colWidth, rowHeight) {
33808
33955
  ctx.restore();
33809
33956
  }
33810
33957
 
33958
+ css /* scss */ `
33959
+ .o-table-style-list-item {
33960
+ border: 1px solid transparent;
33961
+ &.selected {
33962
+ border: 1px solid #007eff;
33963
+ background: #f5f5f5;
33964
+ }
33965
+
33966
+ &:hover {
33967
+ background: #ddd;
33968
+ .o-table-style-edit-button {
33969
+ display: block !important;
33970
+ right: 0;
33971
+ top: 0;
33972
+ background: #fff;
33973
+ cursor: pointer;
33974
+ border: 1px solid #ddd;
33975
+ padding: 1px 1px 1px 2px;
33976
+ .o-icon {
33977
+ font-size: 12px;
33978
+ width: 12px;
33979
+ height: 12px;
33980
+ }
33981
+ }
33982
+ }
33983
+ }
33984
+ `;
33811
33985
  class TableStylePreview extends Component {
33812
33986
  static template = "o-spreadsheet-TableStylePreview";
33813
- static props = { tableConfig: Object };
33987
+ static components = { Menu };
33988
+ static props = {
33989
+ tableConfig: Object,
33990
+ tableStyle: Object,
33991
+ class: String,
33992
+ styleId: { type: String, optional: true },
33993
+ selected: { type: Boolean, optional: true },
33994
+ onClick: { type: Function, optional: true },
33995
+ };
33814
33996
  canvasRef = useRef("canvas");
33997
+ menu = useState({ isOpen: false, position: null, menuItems: [] });
33815
33998
  setup() {
33816
33999
  onWillUpdateProps((nextProps) => {
33817
- if (!deepEquals(this.props.tableConfig, nextProps.tableConfig)) {
33818
- this.drawTable(nextProps.tableConfig);
34000
+ if (!deepEquals(this.props.tableConfig, nextProps.tableConfig) ||
34001
+ !deepEquals(this.props.tableStyle, nextProps.tableStyle)) {
34002
+ this.drawTable(nextProps);
33819
34003
  }
33820
34004
  });
33821
- onMounted(() => this.drawTable(this.props.tableConfig));
34005
+ onMounted(() => this.drawTable(this.props));
33822
34006
  }
33823
- drawTable(tableConfig) {
34007
+ drawTable(props) {
33824
34008
  const ctx = this.canvasRef.el.getContext("2d");
33825
34009
  const { width, height } = this.canvasRef.el.getBoundingClientRect();
33826
34010
  this.canvasRef.el.width = width;
33827
34011
  this.canvasRef.el.height = height;
33828
- const tableStyle = getComputedTableStyle(tableConfig, 5, 5);
33829
- drawPreviewTable(ctx, tableStyle, (width - 1) / 5, (height - 1) / 5);
34012
+ const computedStyle = getComputedTableStyle(props.tableConfig, props.tableStyle, 5, 5);
34013
+ drawPreviewTable(ctx, computedStyle, (width - 1) / 5, (height - 1) / 5);
34014
+ }
34015
+ onContextMenu(event) {
34016
+ if (!this.props.styleId) {
34017
+ return;
34018
+ }
34019
+ this.menu.menuItems = createTableStyleContextMenuActions(this.env, this.props.styleId);
34020
+ this.menu.isOpen = true;
34021
+ this.menu.position = { x: event.clientX, y: event.clientY };
34022
+ }
34023
+ closeMenu() {
34024
+ this.menu.isOpen = false;
34025
+ this.menu.position = null;
34026
+ this.menu.menuItems = [];
34027
+ }
34028
+ get styleName() {
34029
+ if (!this.props.styleId) {
34030
+ return "";
34031
+ }
34032
+ return this.env.model.getters.getTableStyle(this.props.styleId).displayName;
34033
+ }
34034
+ get isStyleEditable() {
34035
+ if (!this.props.styleId) {
34036
+ return false;
34037
+ }
34038
+ return this.env.model.getters.isTableStyleEditable(this.props.styleId);
34039
+ }
34040
+ editTableStyle() {
34041
+ this.env.openSidePanel("TableStyleEditorPanel", { styleId: this.props.styleId });
33830
34042
  }
33831
34043
  }
33832
34044
 
33833
34045
  css /* scss */ `
33834
34046
  .o-table-style-popover {
33835
34047
  /** 7 tables preview + padding by line */
33836
- max-width: calc((66px + 4px * 2) * 7);
34048
+ width: calc((66px + 4px * 2) * 7);
33837
34049
  background: #fff;
33838
34050
  font-size: 14px;
33839
- .o-table-style-list-item {
33840
- padding: 4px;
33841
- &.selected {
33842
- padding: 3px;
33843
- }
34051
+ user-select: none;
33844
34052
 
33845
- .o-table-style-popover-preview {
33846
- width: 66px;
33847
- height: 51px;
33848
- }
34053
+ .form-check-input {
34054
+ font-size: 12px;
33849
34055
  }
33850
- }
33851
34056
 
33852
- .o-table-style-list-item {
33853
- &.selected {
33854
- border: 1px solid #007eff;
33855
- background: #f5f5f5;
34057
+ .o-table-style-list-item {
34058
+ padding: 3px;
33856
34059
  }
33857
34060
 
33858
- &:hover {
33859
- background: #ddd;
34061
+ .o-table-style-popover-preview {
34062
+ width: 66px;
34063
+ height: 51px;
34064
+ }
34065
+
34066
+ .o-new-table-style {
34067
+ font-size: 36px;
34068
+ color: #666;
34069
+ &:hover {
34070
+ background: #f5f5f5;
34071
+ }
33860
34072
  }
33861
34073
  }
33862
34074
  `;
@@ -33870,9 +34082,10 @@ class TableStylesPopover extends Component {
33870
34082
  onStylePicked: Function,
33871
34083
  selectedStyleId: { type: String, optional: true },
33872
34084
  };
33873
- stylePresets = TABLE_PRESETS;
33874
34085
  categories = TABLE_STYLE_CATEGORIES;
33875
34086
  tableStyleListRef = useRef("tableStyleList");
34087
+ state = useState({ selectedCategory: this.initialSelectedCategory });
34088
+ menu = useState({ isOpen: false, position: null, menuItems: [] });
33876
34089
  setup() {
33877
34090
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
33878
34091
  }
@@ -33882,14 +34095,20 @@ class TableStylesPopover extends Component {
33882
34095
  ev.hasClosedTableStylesPopover = true;
33883
34096
  }
33884
34097
  }
33885
- getPresetsByCategory(category) {
33886
- return Object.keys(this.stylePresets).filter((key) => this.stylePresets[key].category === category);
34098
+ get displayedStyles() {
34099
+ const styles = this.env.model.getters.getTableStyles();
34100
+ return Object.keys(styles).filter((styleId) => styles[styleId].category === this.state.selectedCategory);
33887
34101
  }
33888
- getTableConfig(styleId) {
33889
- return { ...this.props.tableConfig, styleId: styleId };
34102
+ get initialSelectedCategory() {
34103
+ return this.props.selectedStyleId
34104
+ ? this.env.model.getters.getTableStyle(this.props.selectedStyleId).category
34105
+ : "medium";
33890
34106
  }
33891
- getStyleName(styleId) {
33892
- return getTableStyleName(styleId, TABLE_PRESETS[styleId]);
34107
+ newTableStyle() {
34108
+ this.props.closePopover();
34109
+ this.env.openSidePanel("TableStyleEditorPanel", {
34110
+ onStylePicked: this.props.onStylePicked,
34111
+ });
33893
34112
  }
33894
34113
  }
33895
34114
 
@@ -33909,13 +34128,9 @@ css /* scss */ `
33909
34128
  }
33910
34129
 
33911
34130
  .o-table-style-list-item {
33912
- padding: 4px;
34131
+ padding: 3px;
33913
34132
  margin: 2px 1px;
33914
34133
 
33915
- &.selected {
33916
- padding: 3px;
33917
- }
33918
-
33919
34134
  .o-table-style-picker-preview {
33920
34135
  width: 61px;
33921
34136
  height: 46px;
@@ -33929,7 +34144,9 @@ class TableStylePicker extends Component {
33929
34144
  static props = { table: Object };
33930
34145
  state = useState({ popoverProps: undefined });
33931
34146
  getDisplayedTableStyles() {
33932
- const styles = Object.keys(TABLE_PRESETS);
34147
+ const allStyles = this.env.model.getters.getTableStyles();
34148
+ const selectedStyleCategory = allStyles[this.props.table.config.styleId].category;
34149
+ const styles = Object.keys(allStyles).filter((key) => allStyles[key].category === selectedStyleCategory);
33933
34150
  const selectedStyleIndex = styles.indexOf(this.props.table.config.styleId);
33934
34151
  if (selectedStyleIndex === -1) {
33935
34152
  return styles.slice(0, 4);
@@ -33937,9 +34154,6 @@ class TableStylePicker extends Component {
33937
34154
  const index = Math.floor(selectedStyleIndex / 4) * 4;
33938
34155
  return styles.slice(index, index + 4);
33939
34156
  }
33940
- getTableConfig(styleId) {
33941
- return { ...this.props.table.config, styleId: styleId };
33942
- }
33943
34157
  onStylePicked(styleId) {
33944
34158
  const sheetId = this.env.model.getters.getActiveSheetId();
33945
34159
  this.env.model.dispatch("UPDATE_TABLE", {
@@ -33965,9 +34179,6 @@ class TableStylePicker extends Component {
33965
34179
  closePopover() {
33966
34180
  this.state.popoverProps = undefined;
33967
34181
  }
33968
- getStyleName(styleId) {
33969
- return getTableStyleName(styleId, TABLE_PRESETS[styleId]);
33970
- }
33971
34182
  }
33972
34183
 
33973
34184
  css /* scss */ `
@@ -34156,6 +34367,104 @@ class TablePanel extends Component {
34156
34367
  }
34157
34368
  }
34158
34369
 
34370
+ css /* scss */ `
34371
+ .o-table-style-editor-panel {
34372
+ .o-table-style-list-item {
34373
+ margin: 1px 3px;
34374
+ padding: 3px 6px;
34375
+
34376
+ .o-table-style-edit-template-preview {
34377
+ width: 81px;
34378
+ height: 61px;
34379
+ }
34380
+ }
34381
+
34382
+ .o-sidePanelButtons .o-delete:hover:enabled {
34383
+ color: #ffffff;
34384
+ background: #d94b4b;
34385
+ }
34386
+ }
34387
+ `;
34388
+ class TableStyleEditorPanel extends Component {
34389
+ static template = "o-spreadsheet-TableStyleEditorPanel";
34390
+ static components = { Section, RoundColorPicker, TableStylePreview };
34391
+ static props = {
34392
+ onCloseSidePanel: Function,
34393
+ onStylePicked: { type: Function, optional: true },
34394
+ styleId: { type: String, optional: true },
34395
+ };
34396
+ state = useState(this.getInitialState());
34397
+ setup() {
34398
+ useExternalListener(window, "click", () => (this.state.pickerOpened = false));
34399
+ }
34400
+ getInitialState() {
34401
+ const editedStyle = this.props.styleId
34402
+ ? this.env.model.getters.getTableStyle(this.props.styleId)
34403
+ : null;
34404
+ return {
34405
+ pickerOpened: false,
34406
+ primaryColor: editedStyle?.primaryColor || "#3C78D8",
34407
+ selectedTemplateName: editedStyle?.templateName || "lightColoredText",
34408
+ styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
34409
+ };
34410
+ }
34411
+ togglePicker() {
34412
+ this.state.pickerOpened = !this.state.pickerOpened;
34413
+ }
34414
+ onColorPicked(color) {
34415
+ this.state.primaryColor = color;
34416
+ this.state.pickerOpened = false;
34417
+ }
34418
+ onTemplatePicked(templateName) {
34419
+ this.state.selectedTemplateName = templateName;
34420
+ }
34421
+ onConfirm() {
34422
+ const tableStyleId = this.props.styleId || this.env.model.uuidGenerator.uuidv4();
34423
+ this.env.model.dispatch("CREATE_TABLE_STYLE", {
34424
+ tableStyleId,
34425
+ tableStyleName: this.state.styleName,
34426
+ templateName: this.state.selectedTemplateName,
34427
+ primaryColor: this.state.primaryColor,
34428
+ });
34429
+ this.props.onStylePicked?.(tableStyleId);
34430
+ this.props.onCloseSidePanel();
34431
+ }
34432
+ onCancel() {
34433
+ this.props.onCloseSidePanel();
34434
+ }
34435
+ onDelete() {
34436
+ if (!this.props.styleId) {
34437
+ return;
34438
+ }
34439
+ this.env.model.dispatch("REMOVE_TABLE_STYLE", { tableStyleId: this.props.styleId });
34440
+ this.props.onCloseSidePanel();
34441
+ }
34442
+ get colorPreviewStyle() {
34443
+ return cssPropertiesToCss({ background: this.state.primaryColor });
34444
+ }
34445
+ get tableTemplates() {
34446
+ return Object.keys(TABLE_STYLES_TEMPLATES).filter((templateName) => templateName !== "none");
34447
+ }
34448
+ get previewTableConfig() {
34449
+ return {
34450
+ bandedColumns: false,
34451
+ bandedRows: true,
34452
+ firstColumn: false,
34453
+ lastColumn: false,
34454
+ numberOfHeaders: 1,
34455
+ totalRow: true,
34456
+ hasFilters: true,
34457
+ styleId: "",
34458
+ };
34459
+ }
34460
+ get selectedStyle() {
34461
+ return this.computeTableStyle(this.state.selectedTemplateName);
34462
+ }
34463
+ computeTableStyle(templateName) {
34464
+ return buildTableStyle(this.state.styleName, templateName, this.state.primaryColor);
34465
+ }
34466
+ }
34467
+
34159
34468
  const sidePanelRegistry = new Registry();
34160
34469
 
34161
34470
  //------------------------------------------------------------------------------
@@ -34220,6 +34529,17 @@ sidePanelRegistry.add("TableSidePanel", {
34220
34529
  return { isOpen: true, props: { table: coreTable }, key: table.id };
34221
34530
  },
34222
34531
  });
34532
+ sidePanelRegistry.add("TableStyleEditorPanel", {
34533
+ title: _t("Create custom table style"),
34534
+ Body: TableStyleEditorPanel,
34535
+ computeState: (getters, initialProps) => {
34536
+ return {
34537
+ isOpen: true,
34538
+ props: { ...initialProps },
34539
+ key: initialProps.styleId ?? "new",
34540
+ };
34541
+ },
34542
+ });
34223
34543
 
34224
34544
  class TopBarComponentRegistry extends Registry {
34225
34545
  mapping = {};
@@ -34577,29 +34897,27 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
34577
34897
  this.highlightStore.register(this);
34578
34898
  }
34579
34899
  get highlights() {
34580
- const zone = this.getHighlightZone();
34900
+ let zone;
34901
+ const position = this.model.getters.getActivePosition();
34902
+ const cell = this.getters.getEvaluatedCell(position);
34903
+ const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
34904
+ zone = spreader
34905
+ ? this.model.getters.getSpreadZone(spreader, { ignoreSpillError: true })
34906
+ : this.model.getters.getSpreadZone(position, { ignoreSpillError: true });
34581
34907
  if (!zone) {
34582
34908
  return [];
34583
34909
  }
34584
- const sheetId = this.model.getters.getActiveSheetId();
34585
34910
  return [
34586
34911
  {
34587
- sheetId,
34912
+ sheetId: position.sheetId,
34588
34913
  zone,
34914
+ dashed: cell.value === CellErrorType.SpilledBlocked,
34589
34915
  color: "#17A2B8",
34590
34916
  noFill: true,
34591
34917
  thinLine: true,
34592
34918
  },
34593
34919
  ];
34594
34920
  }
34595
- getHighlightZone() {
34596
- const position = this.model.getters.getActivePosition();
34597
- const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
34598
- const spreadZone = spreader
34599
- ? this.model.getters.getSpreadZone(spreader)
34600
- : this.model.getters.getSpreadZone(position);
34601
- return spreadZone;
34602
- }
34603
34921
  }
34604
34922
 
34605
34923
  // -----------------------------------------------------------------------------
@@ -35055,7 +35373,8 @@ class DataValidationOverlay extends Component {
35055
35373
  get checkBoxCellPositions() {
35056
35374
  return this.env.model.getters
35057
35375
  .getVisibleCellPositions()
35058
- .filter(this.env.model.getters.isCellValidCheckbox);
35376
+ .filter((position) => this.env.model.getters.isCellValidCheckbox(position) &&
35377
+ !this.env.model.getters.isFilterHeader(position));
35059
35378
  }
35060
35379
  get listIconsCellPositions() {
35061
35380
  if (this.env.model.getters.isReadonly()) {
@@ -35063,7 +35382,8 @@ class DataValidationOverlay extends Component {
35063
35382
  }
35064
35383
  return this.env.model.getters
35065
35384
  .getVisibleCellPositions()
35066
- .filter(this.env.model.getters.cellHasListDataValidationIcon);
35385
+ .filter((position) => this.env.model.getters.cellHasListDataValidationIcon(position) &&
35386
+ !this.env.model.getters.isFilterHeader(position));
35067
35387
  }
35068
35388
  }
35069
35389
 
@@ -36305,6 +36625,9 @@ css /* scss */ `
36305
36625
  height: 10000px;
36306
36626
  background-color: ${SELECTION_BORDER_COLOR};
36307
36627
  }
36628
+ .o-unhide {
36629
+ color: ${ICONS_COLOR};
36630
+ }
36308
36631
  .o-unhide:hover {
36309
36632
  z-index: ${ComponentsImportance.Grid + 1};
36310
36633
  background-color: lightgrey;
@@ -36466,6 +36789,9 @@ css /* scss */ `
36466
36789
  height: 1px;
36467
36790
  background-color: ${SELECTION_BORDER_COLOR};
36468
36791
  }
36792
+ .o-unhide {
36793
+ color: ${ICONS_COLOR};
36794
+ }
36469
36795
  .o-unhide:hover {
36470
36796
  z-index: ${ComponentsImportance.Grid + 1};
36471
36797
  background-color: lightgrey;
@@ -39608,6 +39934,13 @@ function hexaToInt(hex) {
39608
39934
  }
39609
39935
  return parseInt(hex.replace("#", ""), 16);
39610
39936
  }
39937
+ /**
39938
+ * When defining style (fontColor, borderColor for instance)
39939
+ * Excel will specify rgb="FF000000"
39940
+ * In that case, We should not consider this value as user-defined but
39941
+ * rather like an instruction: "Use your system default"
39942
+ */
39943
+ const DEFAULT_SYSTEM_COLOR = "FF000000";
39611
39944
 
39612
39945
  /**
39613
39946
  * Get the relative path between two files
@@ -39646,18 +39979,6 @@ function arrayToObject(array, indexOffset = 0) {
39646
39979
  }
39647
39980
  return obj;
39648
39981
  }
39649
- /**
39650
- * Convert an object whose keys are numbers to an array were the element index was their key in the object.
39651
- *
39652
- * eg. : {0:"a", 2:"b"} => ["a", undefined, "b"]
39653
- */
39654
- function objectToArray(obj) {
39655
- const arr = [];
39656
- for (let key of Object.keys(obj).map(Number)) {
39657
- arr[key] = obj[key];
39658
- }
39659
- return arr;
39660
- }
39661
39982
  /**
39662
39983
  * In xlsx we can have string with unicode characters with the format _x00fa_.
39663
39984
  * Replace with characters understandable by JS
@@ -40126,7 +40447,7 @@ function extractStyle(cell, data) {
40126
40447
  vertical: style.verticalAlign
40127
40448
  ? V_ALIGNMENT_EXPORT_CONVERSION_MAP[style.verticalAlign]
40128
40449
  : undefined,
40129
- wrapText: style.wrapping === "wrap",
40450
+ wrapText: style.wrapping === "wrap" || undefined,
40130
40451
  },
40131
40452
  };
40132
40453
  styles.font["strike"] = !!style?.strikethrough || undefined;
@@ -40730,128 +41051,54 @@ function getHeader(sheet, dim, index) {
40730
41051
  : sheet.rows.find((row) => row.index === index);
40731
41052
  }
40732
41053
 
40733
- const TABLE_HEADER_STYLE = {
40734
- fillColor: "#000000",
40735
- textColor: "#ffffff",
40736
- bold: true,
40737
- };
40738
- const TABLE_HIGHLIGHTED_CELL_STYLE = {
40739
- bold: true,
40740
- };
40741
- const TABLE_BORDER_STYLE = { style: "thin", color: "#000000FF" };
40742
41054
  /**
40743
- * Convert the imported XLSX tables.
40744
- *
40745
- * We will create a Table if the imported table have filters, then apply a style in all the cells of the table
40746
- * and convert the table-specific formula references into standard references.
41055
+ * Convert the imported XLSX tables and pivots convert the table-specific formula references into standard references.
40747
41056
  *
40748
41057
  * Change the converted data in-place.
40749
41058
  */
40750
41059
  function convertTables(convertedData, xlsxData) {
40751
41060
  for (const xlsxSheet of xlsxData.sheets) {
41061
+ const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
41062
+ if (!sheet)
41063
+ continue;
41064
+ if (!sheet.tables)
41065
+ sheet.tables = [];
40752
41066
  for (const table of xlsxSheet.tables) {
40753
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
40754
- if (!sheet || !table.autoFilter)
40755
- continue;
40756
- if (!sheet.tables)
40757
- sheet.tables = [];
40758
- sheet.tables.push({ range: table.ref });
41067
+ sheet.tables.push({ range: table.ref, config: convertTableConfig(table) });
41068
+ }
41069
+ for (const pivotTable of xlsxSheet.pivotTables) {
41070
+ sheet.tables.push({
41071
+ range: pivotTable.location.ref,
41072
+ config: convertPivotTableConfig(pivotTable),
41073
+ });
40759
41074
  }
40760
41075
  }
40761
- applyTableStyle(convertedData, xlsxData);
40762
41076
  convertTableFormulaReferences(convertedData.sheets, xlsxData.sheets);
40763
41077
  }
40764
- /**
40765
- * Apply a style to all the cells that are in a table, and add the created styles in the converted data.
40766
- *
40767
- * In XLSXs, the style of the cells of a table are not directly in the sheet, but rather deduced from the style of
40768
- * the table that is defined in the table's XML file. The style of the table is a string referencing a standard style
40769
- * defined in the OpenXML specifications. As there are 80+ different styles, we won't implement every one of them but
40770
- * we will just define a style that will be used for all the imported tables.
40771
- */
40772
- function applyTableStyle(convertedData, xlsxData) {
40773
- const styles = objectToArray(convertedData.styles);
40774
- const borders = objectToArray(convertedData.borders);
40775
- for (let xlsxSheet of xlsxData.sheets) {
40776
- for (let table of xlsxSheet.tables) {
40777
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
40778
- if (!sheet)
40779
- continue;
40780
- const tableZone = toZone(table.ref);
40781
- // Table style
40782
- for (let i = 0; i < table.headerRowCount; i++) {
40783
- applyStyleToZone(TABLE_HEADER_STYLE, { ...tableZone, bottom: tableZone.top + i }, sheet.cells, styles);
40784
- }
40785
- for (let i = 0; i < table.totalsRowCount; i++) {
40786
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, top: tableZone.bottom - i }, sheet.cells, styles);
40787
- }
40788
- if (table.style?.showFirstColumn) {
40789
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, right: tableZone.left }, sheet.cells, styles);
40790
- }
40791
- if (table.style?.showLastColumn) {
40792
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, left: tableZone.right }, sheet.cells, styles);
40793
- }
40794
- // Table borders
40795
- // Borders at : table outline + col(/row) if showColumnStripes(/showRowStripes) + border above totalRow
40796
- for (let col = tableZone.left; col <= tableZone.right; col++) {
40797
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
40798
- const xc = toXC(col, row);
40799
- const cell = sheet.cells[xc];
40800
- const border = {
40801
- left: col === tableZone.left || table.style?.showColumnStripes
40802
- ? TABLE_BORDER_STYLE
40803
- : undefined,
40804
- right: col === tableZone.right ? TABLE_BORDER_STYLE : undefined,
40805
- top: row === tableZone.top ||
40806
- table.style?.showRowStripes ||
40807
- row > tableZone.bottom - table.totalsRowCount
40808
- ? TABLE_BORDER_STYLE
40809
- : undefined,
40810
- bottom: row === tableZone.bottom ? TABLE_BORDER_STYLE : undefined,
40811
- };
40812
- const newBorder = cell?.border ? { ...borders[cell.border], ...border } : border;
40813
- let borderIndex = borders.findIndex((border) => deepEquals(border, newBorder));
40814
- if (borderIndex === -1) {
40815
- borderIndex = borders.length;
40816
- borders.push(newBorder);
40817
- }
40818
- if (cell) {
40819
- cell.border = borderIndex;
40820
- }
40821
- else {
40822
- sheet.cells[xc] = { border: borderIndex };
40823
- }
40824
- }
40825
- }
40826
- }
40827
- }
40828
- convertedData.styles = arrayToObject(styles);
40829
- convertedData.borders = arrayToObject(borders);
41078
+ function convertTableConfig(table) {
41079
+ const styleId = table.style?.name || "";
41080
+ return {
41081
+ hasFilters: table.autoFilter !== undefined,
41082
+ numberOfHeaders: table.headerRowCount,
41083
+ totalRow: table.totalsRowCount > 0,
41084
+ firstColumn: table.style?.showFirstColumn || false,
41085
+ lastColumn: table.style?.showLastColumn || false,
41086
+ bandedRows: table.style?.showRowStripes || false,
41087
+ bandedColumns: table.style?.showColumnStripes || false,
41088
+ styleId: TABLE_PRESETS[styleId] ? styleId : DEFAULT_TABLE_CONFIG.styleId,
41089
+ };
40830
41090
  }
40831
- /**
40832
- * Apply a style to all the cells in the zone. The applied style WILL NOT overwrite values in existing style of the cell.
40833
- *
40834
- * If a style that was not in the styles array was applied, push it into the style array.
40835
- */
40836
- function applyStyleToZone(appliedStyle, zone, cells, styles) {
40837
- for (let col = zone.left; col <= zone.right; col++) {
40838
- for (let row = zone.top; row <= zone.bottom; row++) {
40839
- const xc = toXC(col, row);
40840
- const cell = cells[xc];
40841
- const newStyle = cell?.style ? { ...styles[cell.style], ...appliedStyle } : appliedStyle;
40842
- let styleIndex = styles.findIndex((style) => deepEquals(style, newStyle));
40843
- if (styleIndex === -1) {
40844
- styleIndex = styles.length;
40845
- styles.push(newStyle);
40846
- }
40847
- if (cell) {
40848
- cell.style = styleIndex;
40849
- }
40850
- else {
40851
- cells[xc] = { style: styleIndex };
40852
- }
40853
- }
40854
- }
41091
+ function convertPivotTableConfig(pivotTable) {
41092
+ return {
41093
+ hasFilters: false,
41094
+ numberOfHeaders: pivotTable.location.firstDataRow,
41095
+ totalRow: pivotTable.rowGrandTotals,
41096
+ firstColumn: true,
41097
+ lastColumn: pivotTable.style?.showLastColumn || false,
41098
+ bandedRows: pivotTable.style?.showRowStripes || false,
41099
+ bandedColumns: pivotTable.style?.showColStripes || false,
41100
+ styleId: DEFAULT_TABLE_CONFIG.styleId,
41101
+ };
40855
41102
  }
40856
41103
  /**
40857
41104
  * In all the sheets, replace the table-only references in the formula cells with standard references.
@@ -41020,7 +41267,7 @@ function getDefaultXLSXStructure(data) {
41020
41267
  fillId: 0,
41021
41268
  numFmtId: 0,
41022
41269
  borderId: 0,
41023
- alignment: { vertical: "bottom" },
41270
+ alignment: {},
41024
41271
  },
41025
41272
  ],
41026
41273
  fonts: [
@@ -41028,7 +41275,7 @@ function getDefaultXLSXStructure(data) {
41028
41275
  size: DEFAULT_FONT_SIZE,
41029
41276
  family: 2,
41030
41277
  color: { rgb: "000000" },
41031
- name: "Calibri",
41278
+ name: "Arial",
41032
41279
  },
41033
41280
  ],
41034
41281
  fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
@@ -41320,9 +41567,10 @@ class XlsxBaseExtractor {
41320
41567
  }
41321
41568
  else {
41322
41569
  rgb = this.extractAttr(colorElement, "rgb")?.asString();
41570
+ rgb = rgb === DEFAULT_SYSTEM_COLOR ? undefined : rgb;
41323
41571
  }
41324
41572
  const color = {
41325
- rgb,
41573
+ rgb: rgb || defaultColor,
41326
41574
  auto: this.extractAttr(colorElement, "auto")?.asBool(),
41327
41575
  indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
41328
41576
  tint: this.extractAttr(colorElement, "tint")?.asNum(),
@@ -41759,20 +42007,48 @@ class XlsxPivotExtractor extends XlsxBaseExtractor {
41759
42007
  // pivotTableDefinition elements.
41760
42008
  { query: ":root", parent: this.rootFile.file.xml }, (pivotElement) => {
41761
42009
  return {
41762
- displayName: this.extractAttr(pivotElement, "name", { required: true }).asString(),
41763
- id: this.extractAttr(pivotElement, "name", { required: true }).asString(),
41764
- ref: this.extractChildAttr(pivotElement, "location", "ref", {
42010
+ name: this.extractAttr(pivotElement, "name", { required: true }).asString(),
42011
+ rowGrandTotals: this.extractAttr(pivotElement, "rowGrandTotals", {
42012
+ default: true,
42013
+ }).asBool(),
42014
+ location: this.extractPivotLocation(pivotElement),
42015
+ style: this.extractPivotStyleInfo(pivotElement),
42016
+ };
42017
+ })[0];
42018
+ }
42019
+ extractPivotLocation(pivotElement) {
42020
+ return this.mapOnElements({ query: "location", parent: pivotElement }, (pivotStyleElement) => {
42021
+ return {
42022
+ ref: this.extractAttr(pivotStyleElement, "ref", { required: true }).asString(),
42023
+ firstHeaderRow: this.extractAttr(pivotStyleElement, "firstHeaderRow", {
41765
42024
  required: true,
41766
- }).asString(),
41767
- headerRowCount: this.extractChildAttr(pivotElement, "location", "firstDataRow", {
41768
- default: 0,
41769
42025
  }).asNum(),
41770
- totalsRowCount: 1,
41771
- cols: [],
41772
- style: {
41773
- showFirstColumn: true,
41774
- showRowStripes: true,
41775
- },
42026
+ firstDataRow: this.extractAttr(pivotStyleElement, "firstDataRow", {
42027
+ required: true,
42028
+ }).asNum(),
42029
+ firstDataCol: this.extractAttr(pivotStyleElement, "firstDataCol", {
42030
+ required: true,
42031
+ }).asNum(),
42032
+ };
42033
+ })[0];
42034
+ }
42035
+ extractPivotStyleInfo(pivotElement) {
42036
+ return this.mapOnElements({ query: "pivotTableStyleInfo", parent: pivotElement }, (pivotStyleElement) => {
42037
+ return {
42038
+ name: this.extractAttr(pivotStyleElement, "name", { required: true }).asString(),
42039
+ showRowHeaders: this.extractAttr(pivotStyleElement, "showRowHeaders", {
42040
+ required: true,
42041
+ }).asBool(),
42042
+ showColHeaders: this.extractAttr(pivotStyleElement, "showColHeaders", {
42043
+ required: true,
42044
+ }).asBool(),
42045
+ showRowStripes: this.extractAttr(pivotStyleElement, "showRowStripes", {
42046
+ required: true,
42047
+ }).asBool(),
42048
+ showColStripes: this.extractAttr(pivotStyleElement, "showColStripes", {
42049
+ required: true,
42050
+ }).asBool(),
42051
+ showLastColumn: this.extractAttr(pivotStyleElement, "showLastColumn")?.asBool(),
41776
42052
  };
41777
42053
  })[0];
41778
42054
  }
@@ -41869,7 +42145,8 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
41869
42145
  cfs: this.extractConditionalFormats(),
41870
42146
  figures: this.extractFigures(sheetElement),
41871
42147
  hyperlinks: this.extractHyperLinks(sheetElement),
41872
- tables: [...this.extractTables(sheetElement), ...this.extractPivotTables()],
42148
+ tables: this.extractTables(sheetElement),
42149
+ pivotTables: this.extractPivotTables(),
41873
42150
  isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
41874
42151
  };
41875
42152
  })[0];
@@ -42479,6 +42756,8 @@ function load(data, verboseImport) {
42479
42756
  if (!data) {
42480
42757
  return createEmptyWorkbookData();
42481
42758
  }
42759
+ console.group("Loading data");
42760
+ const start = performance.now();
42482
42761
  if (data["[Content_Types].xml"]) {
42483
42762
  const reader = new XlsxReader(data);
42484
42763
  data = reader.convertXlsx();
@@ -42491,17 +42770,22 @@ function load(data, verboseImport) {
42491
42770
  // apply migrations, if needed
42492
42771
  if ("version" in data) {
42493
42772
  if (data.version < CURRENT_VERSION) {
42773
+ console.info("Migrating data from version", data.version);
42494
42774
  data = migrate(data);
42495
42775
  }
42496
42776
  }
42497
42777
  data = repairData(data);
42778
+ console.info("Data loaded in", performance.now() - start, "ms");
42779
+ console.groupEnd();
42498
42780
  return data;
42499
42781
  }
42500
42782
  function migrate(data) {
42783
+ const start = performance.now();
42501
42784
  const index = MIGRATIONS.findIndex((m) => m.from === data.version);
42502
42785
  for (let i = index; i < MIGRATIONS.length; i++) {
42503
42786
  data = MIGRATIONS[i].applyMigration(data);
42504
42787
  }
42788
+ console.info("Data migrated in", performance.now() - start, "ms");
42505
42789
  return data;
42506
42790
  }
42507
42791
  const MIGRATIONS = [
@@ -43013,6 +43297,7 @@ function createEmptyWorkbookData(sheetName = "Sheet1") {
43013
43297
  settings: { locale: DEFAULT_LOCALE },
43014
43298
  pivots: {},
43015
43299
  pivotNextId: 1,
43300
+ customTableStyles: {},
43016
43301
  };
43017
43302
  return data;
43018
43303
  }
@@ -43221,7 +43506,7 @@ class BordersPlugin extends CorePlugin {
43221
43506
  this.clearBorders(cmd.sheetId, cmd.target);
43222
43507
  break;
43223
43508
  case "REMOVE_COLUMNS_ROWS":
43224
- for (let el of cmd.elements) {
43509
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
43225
43510
  if (cmd.dimension === "COL") {
43226
43511
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
43227
43512
  }
@@ -47489,10 +47774,9 @@ class TablePlugin extends CorePlugin {
47489
47774
  for (const tableId in tables) {
47490
47775
  const table = tables[tableId];
47491
47776
  if (table && cmd.target.some((zone) => isZoneInside(table.range.zone, zone))) {
47492
- delete tables[tableId];
47777
+ this.dispatch("REMOVE_TABLE", { sheetId: cmd.sheetId, target: [table.range.zone] });
47493
47778
  }
47494
47779
  }
47495
- this.history.update("tables", cmd.sheetId, tables);
47496
47780
  break;
47497
47781
  }
47498
47782
  }
@@ -47589,9 +47873,6 @@ class TablePlugin extends CorePlugin {
47589
47873
  if (config.numberOfHeaders !== undefined && config.numberOfHeaders < 0) {
47590
47874
  return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47591
47875
  }
47592
- if (config.styleId && !TABLE_PRESETS[config.styleId]) {
47593
- return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47594
- }
47595
47876
  if (config.hasFilters && config.numberOfHeaders === 0) {
47596
47877
  return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47597
47878
  }
@@ -47809,7 +48090,12 @@ class TablePlugin extends CorePlugin {
47809
48090
  }
47810
48091
  }
47811
48092
  exportForExcel(data) {
47812
- this.export(data);
48093
+ for (const sheet of data.sheets) {
48094
+ for (const table of this.getCoreTables(sheet.id)) {
48095
+ const range = zoneToXc(table.range.zone);
48096
+ sheet.tables.push({ range, filters: [], config: table.config });
48097
+ }
48098
+ }
47813
48099
  }
47814
48100
  }
47815
48101
 
@@ -48680,6 +48966,108 @@ class SettingsPlugin extends CorePlugin {
48680
48966
  }
48681
48967
  }
48682
48968
 
48969
+ class TableStylePlugin extends CorePlugin {
48970
+ static getters = [
48971
+ "getNewCustomTableStyleName",
48972
+ "getTableStyle",
48973
+ "getTableStyles",
48974
+ "isTableStyleEditable",
48975
+ ];
48976
+ styles = {};
48977
+ allowDispatch(cmd) {
48978
+ switch (cmd.type) {
48979
+ case "CREATE_TABLE":
48980
+ case "UPDATE_TABLE":
48981
+ if (cmd.config?.styleId && !this.styles[cmd.config.styleId]) {
48982
+ return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
48983
+ }
48984
+ break;
48985
+ case "CREATE_TABLE_STYLE":
48986
+ if (!TABLE_STYLES_TEMPLATES[cmd.templateName]) {
48987
+ return "InvalidTableStyle" /* CommandResult.InvalidTableStyle */;
48988
+ }
48989
+ try {
48990
+ toHex(cmd.primaryColor);
48991
+ }
48992
+ catch (e) {
48993
+ return "InvalidTableStyle" /* CommandResult.InvalidTableStyle */;
48994
+ }
48995
+ break;
48996
+ }
48997
+ return "Success" /* CommandResult.Success */;
48998
+ }
48999
+ handle(cmd) {
49000
+ switch (cmd.type) {
49001
+ case "CREATE_TABLE_STYLE":
49002
+ const style = buildTableStyle(cmd.tableStyleName, cmd.templateName, cmd.primaryColor);
49003
+ this.history.update("styles", cmd.tableStyleId, style);
49004
+ break;
49005
+ case "REMOVE_TABLE_STYLE":
49006
+ const styles = { ...this.styles };
49007
+ delete styles[cmd.tableStyleId];
49008
+ this.history.update("styles", styles);
49009
+ for (const sheetId of this.getters.getSheetIds()) {
49010
+ for (const table of this.getters.getCoreTables(sheetId)) {
49011
+ if (table.config.styleId === cmd.tableStyleId) {
49012
+ this.dispatch("UPDATE_TABLE", {
49013
+ sheetId,
49014
+ zone: table.range.zone,
49015
+ config: { styleId: DEFAULT_TABLE_CONFIG.styleId },
49016
+ });
49017
+ }
49018
+ }
49019
+ }
49020
+ break;
49021
+ }
49022
+ }
49023
+ getTableStyle(styleId) {
49024
+ if (!this.styles[styleId]) {
49025
+ throw new Error(`Table style ${styleId} does not exist`);
49026
+ }
49027
+ return this.styles[styleId];
49028
+ }
49029
+ getTableStyles() {
49030
+ return this.styles;
49031
+ }
49032
+ getNewCustomTableStyleName() {
49033
+ let name = _t("Custom Table Style");
49034
+ const styleNames = new Set(Object.values(this.styles).map((style) => style.displayName));
49035
+ if (!styleNames.has(name)) {
49036
+ return name;
49037
+ }
49038
+ let i = 2;
49039
+ while (styleNames.has(`${name} ${i}`)) {
49040
+ i++;
49041
+ }
49042
+ return `${name} ${i}`;
49043
+ }
49044
+ isTableStyleEditable(styleId) {
49045
+ return !TABLE_PRESETS[styleId];
49046
+ }
49047
+ import(data) {
49048
+ for (const presetStyleId in TABLE_PRESETS) {
49049
+ this.styles[presetStyleId] = TABLE_PRESETS[presetStyleId];
49050
+ }
49051
+ for (const styleId in data.customTableStyles) {
49052
+ const styleData = data.customTableStyles[styleId];
49053
+ this.styles[styleId] = buildTableStyle(styleData.displayName, styleData.templateName, styleData.primaryColor);
49054
+ }
49055
+ }
49056
+ export(data) {
49057
+ const exportedStyles = {};
49058
+ for (const styleId in this.styles) {
49059
+ if (!TABLE_PRESETS[styleId]) {
49060
+ exportedStyles[styleId] = {
49061
+ displayName: this.styles[styleId].displayName,
49062
+ templateName: this.styles[styleId].templateName,
49063
+ primaryColor: this.styles[styleId].primaryColor,
49064
+ };
49065
+ }
49066
+ }
49067
+ data.customTableStyles = exportedStyles;
49068
+ }
49069
+ }
49070
+
48683
49071
  /**
48684
49072
  * UI plugins handle any transient data required to display a spreadsheet.
48685
49073
  * They can draw on the grid canvas.
@@ -48740,19 +49128,17 @@ class CompilationParametersBuilder {
48740
49128
  * function for which this parameter is used, we just return the string of the parameter.
48741
49129
  * The `compute` of the formula's function must process it completely
48742
49130
  */
48743
- refFn(range, isMeta, functionName, paramNumber) {
48744
- this.assertRangeValid(range);
49131
+ refFn(range, isMeta) {
49132
+ const rangeError = this.getRangeError(range);
49133
+ if (rangeError) {
49134
+ return rangeError;
49135
+ }
48745
49136
  if (isMeta) {
48746
49137
  // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
48747
49138
  const sheetName = this.getters.getSheetName(range.sheetId);
48748
49139
  return { value: getFullReference(sheetName, zoneToXc(range.zone)) };
48749
49140
  }
48750
- // if the formula definition could have accepted a range, we would pass through the _range function and not here
48751
- if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {
48752
- throw new EvaluationError(paramNumber
48753
- ? _t("Function %s expects the parameter %s to be a single value or a single cell reference, not a range.", functionName.toString(), paramNumber.toString())
48754
- : _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
48755
- }
49141
+ // the compiler guarantees only single cell ranges reach this part of the code
48756
49142
  const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
48757
49143
  return this.computeCell(position);
48758
49144
  }
@@ -48765,7 +49151,10 @@ class CompilationParametersBuilder {
48765
49151
  * that are actually present in the grid.
48766
49152
  */
48767
49153
  range(range) {
48768
- this.assertRangeValid(range);
49154
+ const rangeError = this.getRangeError(range);
49155
+ if (rangeError) {
49156
+ return [[rangeError]];
49157
+ }
48769
49158
  const sheetId = range.sheetId;
48770
49159
  const zone = range.zone;
48771
49160
  // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
@@ -48795,13 +49184,14 @@ class CompilationParametersBuilder {
48795
49184
  this.rangeCache[cacheKey] = matrix;
48796
49185
  return matrix;
48797
49186
  }
48798
- assertRangeValid(range) {
49187
+ getRangeError(range) {
48799
49188
  if (!isZoneValid(range.zone)) {
48800
- throw new InvalidReferenceError();
49189
+ return new InvalidReferenceError();
48801
49190
  }
48802
49191
  if (range.invalidSheetName) {
48803
- throw new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
49192
+ return new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
48804
49193
  }
49194
+ return undefined;
48805
49195
  }
48806
49196
  }
48807
49197
 
@@ -49603,7 +49993,7 @@ class FormulaDependencyGraph {
49603
49993
  }
49604
49994
  }
49605
49995
  /**
49606
- * Return the cell and all cells that depend on it,
49996
+ * Return all the cells that depend on the provided ranges,
49607
49997
  * in the correct order they should be evaluated.
49608
49998
  * This is called a topological ordering (excluding cycles)
49609
49999
  */
@@ -49614,11 +50004,19 @@ class FormulaDependencyGraph {
49614
50004
  const range = queue.pop();
49615
50005
  visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
49616
50006
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
50007
+ const nextInQueue = {};
49617
50008
  for (const position of impactedPositions) {
49618
50009
  if (!visited.has(position)) {
49619
- queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
50010
+ if (!nextInQueue[position.sheetId]) {
50011
+ nextInQueue[position.sheetId] = [];
50012
+ }
50013
+ nextInQueue[position.sheetId].push(positionToZone(position));
49620
50014
  }
49621
50015
  }
50016
+ for (const sheetId in nextInQueue) {
50017
+ const zones = recomputeZones(nextInQueue[sheetId], []);
50018
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
50019
+ }
49622
50020
  }
49623
50021
  visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
49624
50022
  return visited;
@@ -49897,11 +50295,13 @@ class Evaluator {
49897
50295
  getEvaluatedCell(position) {
49898
50296
  return this.evaluatedCells.get(position) || EMPTY_CELL;
49899
50297
  }
49900
- getSpreadZone(position) {
50298
+ getSpreadZone(position, options = { ignoreSpillError: false }) {
49901
50299
  if (!this.spreadingRelations.isArrayFormula(position)) {
49902
50300
  return undefined;
49903
50301
  }
49904
- if (this.evaluatedCells.get(position)?.type === CellValueType.error) {
50302
+ const evaluatedCell = this.evaluatedCells.get(position);
50303
+ if (evaluatedCell?.type === CellValueType.error &&
50304
+ !(options.ignoreSpillError && evaluatedCell?.value === CellErrorType.SpilledBlocked)) {
49905
50305
  return positionToZone(position);
49906
50306
  }
49907
50307
  const spreadPositions = Array.from(this.spreadingRelations.getArrayResultPositions(position));
@@ -49942,6 +50342,7 @@ class Evaluator {
49942
50342
  return new PositionSet(sheetSizes);
49943
50343
  }
49944
50344
  evaluateCells(positions) {
50345
+ const start = performance.now();
49945
50346
  const cellsToCompute = this.createEmptyPositionSet();
49946
50347
  cellsToCompute.addMany(positions);
49947
50348
  const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
@@ -49949,6 +50350,7 @@ class Evaluator {
49949
50350
  cellsToCompute.addMany(arrayFormulasPositions);
49950
50351
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
49951
50352
  this.evaluate(cellsToCompute);
50353
+ console.info("evaluate Cells", performance.now() - start, "ms");
49952
50354
  }
49953
50355
  getArrayFormulasImpactedByChangesOf(positions) {
49954
50356
  const impactedPositions = this.createEmptyPositionSet();
@@ -49961,7 +50363,7 @@ class Evaluator {
49961
50363
  }
49962
50364
  if (!content) {
49963
50365
  // The previous content could have blocked some array formulas
49964
- impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
50366
+ impactedPositions.addMany(this.getArrayFormulasBlockedBy(position));
49965
50367
  }
49966
50368
  }
49967
50369
  return impactedPositions;
@@ -49983,8 +50385,10 @@ class Evaluator {
49983
50385
  });
49984
50386
  }
49985
50387
  evaluateAllCells() {
50388
+ const start = performance.now();
49986
50389
  this.evaluatedCells = new PositionMap();
49987
50390
  this.evaluate(this.getAllCells());
50391
+ console.info("evaluate all cells", performance.now() - start, "ms");
49988
50392
  }
49989
50393
  evaluateFormula(sheetId, formulaString) {
49990
50394
  const compiledFormula = compile(formulaString);
@@ -50004,14 +50408,23 @@ class Evaluator {
50004
50408
  positions.fillAllPositions();
50005
50409
  return positions;
50006
50410
  }
50007
- getArrayFormulasBlockedByOrSpreadingOn(position) {
50411
+ /**
50412
+ * Return the position of formulas blocked by the given position
50413
+ * as well as all their dependencies.
50414
+ */
50415
+ getArrayFormulasBlockedBy(position) {
50008
50416
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
50009
50417
  return [];
50010
50418
  }
50011
50419
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
50012
50420
  const positions = this.createEmptyPositionSet();
50013
50421
  positions.addMany(arrayFormulas);
50014
- positions.addMany(this.getCellsDependingOn(arrayFormulas));
50422
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
50423
+ if (arrayFormulaPosition) {
50424
+ // ignore the formula spreading on the position. Keep only the blocked ones
50425
+ positions.delete(arrayFormulaPosition);
50426
+ }
50427
+ positions.addMany(this.getCellsDependingOn(positions));
50015
50428
  return positions;
50016
50429
  }
50017
50430
  nextPositionsToUpdate = new PositionSet({});
@@ -50101,12 +50514,12 @@ class Evaluator {
50101
50514
  return;
50102
50515
  }
50103
50516
  if (enoughCols) {
50104
- throw new EvaluationError(_t("Result couldn't be automatically expanded. Please insert more rows."));
50517
+ throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more rows."));
50105
50518
  }
50106
50519
  if (enoughRows) {
50107
- throw new EvaluationError(_t("Result couldn't be automatically expanded. Please insert more columns."));
50520
+ throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more columns."));
50108
50521
  }
50109
- throw new EvaluationError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
50522
+ throw new SplillBlockedError(_t("Result couldn't be automatically expanded. Please insert more columns and rows."));
50110
50523
  }
50111
50524
  updateSpreadRelation({ sheetId, col, row, }) {
50112
50525
  const arrayFormulaPosition = { sheetId, col, row };
@@ -50123,7 +50536,7 @@ class Evaluator {
50123
50536
  if (rawCell?.content ||
50124
50537
  this.getters.getEvaluatedCell(position).type !== CellValueType.empty) {
50125
50538
  this.blockedArrayFormulas.add(formulaPosition);
50126
- throw new EvaluationError(_t("Array result was not expanded because it would overwrite data in %s.", toXC(position.col, position.row)));
50539
+ throw new SplillBlockedError(_t("Array result was not expanded because it would overwrite data in %s.", toXC(position.col, position.row)));
50127
50540
  }
50128
50541
  this.blockedArrayFormulas.delete(formulaPosition);
50129
50542
  };
@@ -50152,7 +50565,7 @@ class Evaluator {
50152
50565
  }
50153
50566
  this.evaluatedCells.delete(child);
50154
50567
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
50155
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
50568
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
50156
50569
  }
50157
50570
  }
50158
50571
  // ----------------------------------------------------------
@@ -50421,8 +50834,8 @@ class EvaluationPlugin extends UIPlugin {
50421
50834
  /**
50422
50835
  * Return the spread zone the position is part of, if any
50423
50836
  */
50424
- getSpreadZone(position) {
50425
- return this.evaluator.getSpreadZone(position);
50837
+ getSpreadZone(position, options = { ignoreSpillError: false }) {
50838
+ return this.evaluator.getSpreadZone(position, options);
50426
50839
  }
50427
50840
  getArrayFormulaSpreadingOn(position) {
50428
50841
  return this.evaluator.getArrayFormulaSpreadingOn(position);
@@ -50462,7 +50875,7 @@ class EvaluationPlugin extends UIPlugin {
50462
50875
  ? getItemId(newFormat, data.formats)
50463
50876
  : exportedCellData.format;
50464
50877
  let content;
50465
- if (isFormula && formulaCell instanceof FormulaCellWithDependencies) {
50878
+ if (isExported && isFormula && formulaCell instanceof FormulaCellWithDependencies) {
50466
50879
  content = formulaCell.contentWithFixedReferences;
50467
50880
  }
50468
50881
  else {
@@ -50657,7 +51070,7 @@ class CustomColorsPlugin extends UIPlugin {
50657
51070
  const tables = this.getters.getTables(sheetId);
50658
51071
  return tables.flatMap((table) => {
50659
51072
  const config = table.config;
50660
- const style = TABLE_PRESETS[config.styleId];
51073
+ const style = this.getters.getTableStyle(config.styleId);
50661
51074
  return [
50662
51075
  this.getTableStyleElementColors(style.wholeTable),
50663
51076
  config.numberOfHeaders > 0 ? this.getTableStyleElementColors(style.headerRow) : [],
@@ -52599,6 +53012,7 @@ otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["FREEZE_COLUMNS", "FREEZE_ROWS
52599
53012
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["FREEZE_COLUMNS", "FREEZE_ROWS"], freezeTransformation);
52600
53013
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["UPDATE_TABLE"], updateTableTransformation);
52601
53014
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["UPDATE_TABLE"], updateTableTransformation);
53015
+ otRegistry.addTransformation("REMOVE_TABLE_STYLE", ["CREATE_TABLE", "UPDATE_TABLE"], removeTableStyleTransform);
52602
53016
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["GROUP_HEADERS", "UNGROUP_HEADERS", "FOLD_HEADER_GROUP", "UNFOLD_HEADER_GROUP"], groupHeadersTransformation);
52603
53017
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["GROUP_HEADERS", "UNGROUP_HEADERS", "FOLD_HEADER_GROUP", "UNFOLD_HEADER_GROUP"], groupHeadersTransformation);
52604
53018
  otRegistry.addTransformation("REMOVE_PIVOT", ["RENAME_PIVOT", "DUPLICATE_PIVOT", "INSERT_PIVOT", "UPDATE_PIVOT"], pivotTransformation);
@@ -52695,6 +53109,15 @@ function updateTableTransformation(toTransform, executed) {
52695
53109
  : undefined;
52696
53110
  return { ...toTransform, newTableRange, zone: newCmdZone };
52697
53111
  }
53112
+ function removeTableStyleTransform(toTransform, executed) {
53113
+ if (toTransform.config?.styleId !== executed.tableStyleId) {
53114
+ return toTransform;
53115
+ }
53116
+ return {
53117
+ ...toTransform,
53118
+ config: { ...toTransform.config, styleId: DEFAULT_TABLE_CONFIG.styleId },
53119
+ };
53120
+ }
52698
53121
  /**
52699
53122
  * Transform ADD_COLUMNS_ROWS command if some headers were added/removed
52700
53123
  */
@@ -53077,11 +53500,14 @@ class Session extends EventBus {
53077
53500
  this.transportService.onNewMessage(this.clientId, this.onMessageReceived.bind(this));
53078
53501
  }
53079
53502
  loadInitialMessages(messages) {
53503
+ const start = performance.now();
53504
+ const numberOfCommands = messages.reduce((acc, message) => acc + (message.type === "REMOTE_REVISION" ? message.commands.length : 1), 0);
53080
53505
  this.isReplayingInitialRevisions = true;
53081
53506
  for (const message of messages) {
53082
53507
  this.onMessageReceived(message);
53083
53508
  }
53084
53509
  this.isReplayingInitialRevisions = false;
53510
+ console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
53085
53511
  }
53086
53512
  /**
53087
53513
  * Notify the server that the user client left the collaborative session
@@ -54154,7 +54580,7 @@ class SheetUIPlugin extends UIPlugin {
54154
54580
  }
54155
54581
  }
54156
54582
 
54157
- class TableStylePlugin extends UIPlugin {
54583
+ class TableComputedStylePlugin extends UIPlugin {
54158
54584
  static getters = ["getCellTableStyle", "getCellTableBorder"];
54159
54585
  tableStyles = {};
54160
54586
  handle(cmd) {
@@ -54165,7 +54591,12 @@ class TableStylePlugin extends UIPlugin {
54165
54591
  return;
54166
54592
  }
54167
54593
  if (doesCommandInvalidatesTableStyle(cmd)) {
54168
- delete this.tableStyles[cmd.sheetId];
54594
+ if ("sheetId" in cmd) {
54595
+ delete this.tableStyles[cmd.sheetId];
54596
+ }
54597
+ else {
54598
+ this.tableStyles = {};
54599
+ }
54169
54600
  return;
54170
54601
  }
54171
54602
  }
@@ -54198,7 +54629,8 @@ class TableStylePlugin extends UIPlugin {
54198
54629
  computeTableStyle(sheetId, table) {
54199
54630
  return lazy(() => {
54200
54631
  const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
54201
- const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
54632
+ const style = this.getters.getTableStyle(table.config.styleId);
54633
+ const relativeTableStyle = getComputedTableStyle(config, style, numberOfCols, numberOfRows);
54202
54634
  // Return the style with sheet coordinates instead of tables coordinates
54203
54635
  const mapping = this.getTableMapping(sheetId, table);
54204
54636
  const absoluteTableStyle = { borders: {}, styles: {} };
@@ -54299,6 +54731,8 @@ const invalidateTableStyleCommands = [
54299
54731
  "UPDATE_FILTER",
54300
54732
  "REMOVE_TABLE",
54301
54733
  "RESIZE_TABLE",
54734
+ "CREATE_TABLE_STYLE",
54735
+ "REMOVE_TABLE_STYLE",
54302
54736
  ];
54303
54737
  const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
54304
54738
  function doesCommandInvalidatesTableStyle(cmd) {
@@ -54318,8 +54752,14 @@ class CellComputedStylePlugin extends UIPlugin {
54318
54752
  return;
54319
54753
  }
54320
54754
  if (doesCommandInvalidatesTableStyle(cmd)) {
54321
- delete this.styles[cmd.sheetId];
54322
- delete this.borders[cmd.sheetId];
54755
+ if ("sheetId" in cmd) {
54756
+ delete this.styles[cmd.sheetId];
54757
+ delete this.borders[cmd.sheetId];
54758
+ }
54759
+ else {
54760
+ this.styles = {};
54761
+ this.borders = {};
54762
+ }
54323
54763
  return;
54324
54764
  }
54325
54765
  if (invalidateCFEvaluationCommands.has(cmd.type)) {
@@ -55668,9 +56108,7 @@ class FilterEvaluationPlugin extends UIPlugin {
55668
56108
  };
55669
56109
  const filteredValues = this.getFilterHiddenValues(position);
55670
56110
  const filter = this.getters.getFilter(position);
55671
- if (!filter)
55672
- continue;
55673
- const valuesInFilterZone = filter.filteredRange
56111
+ const valuesInFilterZone = filter?.filteredRange
55674
56112
  ? positions(filter.filteredRange.zone).map((position) => this.getters.getEvaluatedCell({ sheetId, ...position }).formattedValue)
55675
56113
  : [];
55676
56114
  if (filteredValues.length) {
@@ -55683,17 +56121,12 @@ class FilterEvaluationPlugin extends UIPlugin {
55683
56121
  displayBlanks: !filteredValues.includes("") && valuesInFilterZone.some((val) => !val),
55684
56122
  });
55685
56123
  }
55686
- // In xlsx, filter header should ALWAYS be a string and should be unique in the table
55687
- const headerPosition = {
55688
- col: filter.col,
55689
- row: filter.rangeWithHeaders.zone.top,
55690
- sheetId,
55691
- };
55692
- const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;
56124
+ // In xlsx, column header should ALWAYS be a string and should be unique in the table
56125
+ const headerString = this.getters.getEvaluatedCell(position).formattedValue;
55693
56126
  const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
55694
56127
  headerNames.push(headerName);
55695
- sheetData.cells[toXC(headerPosition.col, headerPosition.row)] = {
55696
- ...sheetData.cells[toXC(headerPosition.col, headerPosition.row)],
56128
+ sheetData.cells[toXC(position.col, position.row)] = {
56129
+ ...sheetData.cells[toXC(position.col, position.row)],
55697
56130
  content: headerName,
55698
56131
  value: headerName,
55699
56132
  isFormula: false,
@@ -55740,7 +56173,6 @@ class GridSelectionPlugin extends UIPlugin {
55740
56173
  "getSelection",
55741
56174
  "getActivePosition",
55742
56175
  "getSheetPosition",
55743
- "isSelected",
55744
56176
  "isSingleColSelected",
55745
56177
  "getElementsFromSelection",
55746
56178
  "tryGetActiveSheetId",
@@ -56022,9 +56454,6 @@ class GridSelectionPlugin extends UIPlugin {
56022
56454
  : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });
56023
56455
  }
56024
56456
  }
56025
- isSelected(zone) {
56026
- return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
56027
- }
56028
56457
  isSingleColSelected() {
56029
56458
  const selection = this.getters.getSelectedZones();
56030
56459
  if (selection.length !== 1 || selection[0].left !== selection[0].right) {
@@ -57408,7 +57837,8 @@ const corePluginRegistry = new Registry()
57408
57837
  .add("figures", FigurePlugin)
57409
57838
  .add("chart", ChartPlugin)
57410
57839
  .add("image", ImagePlugin)
57411
- .add("pivot_core", PivotCorePlugin);
57840
+ .add("pivot_core", PivotCorePlugin)
57841
+ .add("tableStyle", TableStylePlugin);
57412
57842
  // Plugins which handle a specific feature, without handling any core commands
57413
57843
  const featurePluginRegistry = new Registry()
57414
57844
  .add("ui_sheet", SheetUIPlugin)
@@ -57429,8 +57859,8 @@ const statefulUIPluginRegistry = new Registry()
57429
57859
  .add("selection", GridSelectionPlugin)
57430
57860
  .add("evaluation_filter", FilterEvaluationPlugin)
57431
57861
  .add("header_visibility_ui", HeaderVisibilityUIPlugin)
57432
- .add("table_style", TableStylePlugin)
57433
57862
  .add("cell_computed_style", CellComputedStylePlugin)
57863
+ .add("table_computed_style", TableComputedStylePlugin)
57434
57864
  .add("header_positions", HeaderPositionsUIPlugin)
57435
57865
  .add("viewport", SheetViewPlugin)
57436
57866
  .add("clipboard", ClipboardPlugin);
@@ -58248,6 +58678,7 @@ css /* scss */ `
58248
58678
  .o-icon {
58249
58679
  height: 18px;
58250
58680
  width: 18px;
58681
+ font-size: 18px;
58251
58682
  }
58252
58683
  }
58253
58684
  }
@@ -59789,6 +60220,8 @@ css /* scss */ `
59789
60220
  display: grid;
59790
60221
  grid-template-columns: auto 350px;
59791
60222
  color: #333;
60223
+ font-size: 14px;
60224
+
59792
60225
  input {
59793
60226
  background-color: white;
59794
60227
  }
@@ -59866,12 +60299,6 @@ css /* scss */ `
59866
60299
  grid-column: 1 / 3;
59867
60300
  }
59868
60301
 
59869
- .o-icon {
59870
- width: ${ICON_EDGE_LENGTH}px;
59871
- height: ${ICON_EDGE_LENGTH}px;
59872
- vertical-align: middle;
59873
- }
59874
-
59875
60302
  .o-cf-icon {
59876
60303
  width: ${CF_ICON_EDGE_LENGTH}px;
59877
60304
  height: ${CF_ICON_EDGE_LENGTH}px;
@@ -62774,20 +63201,14 @@ function addCellWiseConditionalFormatting(dxfs // cell-wise CF
62774
63201
  `;
62775
63202
  }
62776
63203
 
62777
- const TABLE_DEFAULT_ATTRS = [
62778
- ["name", "TableStyleLight8"],
62779
- ["showFirstColumn", "0"],
62780
- ["showLastColumn", "0"],
62781
- ["showRowStripes", "0"],
62782
- ["showColumnStripes", "0"],
62783
- ];
62784
- const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(TABLE_DEFAULT_ATTRS)}/>`;
62785
63204
  function createTable(table, tableId, sheetData) {
62786
63205
  const tableAttributes = [
62787
63206
  ["id", tableId],
62788
63207
  ["name", `Table${tableId}`],
62789
63208
  ["displayName", `Table${tableId}`],
62790
63209
  ["ref", table.range],
63210
+ ["headerRowCount", table.config.numberOfHeaders],
63211
+ ["totalsRowCount", table.config.totalRow ? 1 : 0],
62791
63212
  ["xmlns", NAMESPACE.table],
62792
63213
  ["xmlns:xr", NAMESPACE.revision],
62793
63214
  ["xmlns:xr3", NAMESPACE.revision3],
@@ -62795,9 +63216,9 @@ function createTable(table, tableId, sheetData) {
62795
63216
  ];
62796
63217
  const xml = escapeXml /*xml*/ `
62797
63218
  <table ${formatAttributes(tableAttributes)}>
62798
- ${addAutoFilter(table)}
63219
+ ${table.config.hasFilters ? addAutoFilter(table) : ""}
62799
63220
  ${addTableColumns(table, sheetData)}
62800
- ${TABLE_DEFAULT_STYLE}
63221
+ ${addTableStyle(table)}
62801
63222
  </table>
62802
63223
  `;
62803
63224
  return parseXML(xml);
@@ -62849,6 +63270,16 @@ function addTableColumns(table, sheetData) {
62849
63270
  </tableColumns>
62850
63271
  `;
62851
63272
  }
63273
+ function addTableStyle(table) {
63274
+ const tableStyleAttrs = [
63275
+ ["name", table.config.styleId],
63276
+ ["showFirstColumn", table.config.firstColumn ? 1 : 0],
63277
+ ["showLastColumn", table.config.lastColumn ? 1 : 0],
63278
+ ["showRowStripes", table.config.bandedRows ? 1 : 0],
63279
+ ["showColumnStripes", table.config.bandedColumns ? 1 : 0],
63280
+ ];
63281
+ return escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(tableStyleAttrs)}/>`;
63282
+ }
62852
63283
 
62853
63284
  function addColumns(cols) {
62854
63285
  if (!Object.values(cols).length) {
@@ -62905,7 +63336,10 @@ function addRows(construct, data, sheet) {
62905
63336
  const attributes = [["r", xc]];
62906
63337
  // style
62907
63338
  const id = normalizeStyle(construct, extractStyle(cell, data));
62908
- attributes.push(["s", id]);
63339
+ // don't add style if default
63340
+ if (id) {
63341
+ attributes.push(["s", id]);
63342
+ }
62909
63343
  let additionalAttrs = [];
62910
63344
  let cellNode = escapeXml ``;
62911
63345
  // Either formula or static value inside the cell
@@ -63403,6 +63837,8 @@ class Model extends EventBus {
63403
63837
  uiHandlers = [];
63404
63838
  coreHandlers = [];
63405
63839
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
63840
+ const start = performance.now();
63841
+ console.group("Model creation");
63406
63842
  super();
63407
63843
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
63408
63844
  const workbookData = load(data, verboseImport);
@@ -63472,12 +63908,17 @@ class Model extends EventBus {
63472
63908
  this.setupSessionEvents();
63473
63909
  this.joinSession();
63474
63910
  if (config.snapshotRequested) {
63911
+ const startSnapshot = performance.now();
63912
+ console.info("Snapshot requested");
63475
63913
  this.session.snapshot(this.exportData());
63476
63914
  this.garbageCollectExternalResources();
63915
+ console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
63477
63916
  }
63478
63917
  // mark all models as "raw", so they will not be turned into reactive objects
63479
63918
  // by owl, since we do not rely on reactivity
63480
63919
  markRaw(this);
63920
+ console.info("Model created in", performance.now() - start, "ms");
63921
+ console.groupEnd();
63481
63922
  }
63482
63923
  joinSession() {
63483
63924
  this.session.join(this.config.client);
@@ -63692,11 +64133,16 @@ class Model extends EventBus {
63692
64133
  }
63693
64134
  this.status = 1 /* Status.Running */;
63694
64135
  const { changes, commands } = this.state.recordChanges(() => {
64136
+ const start = performance.now();
63695
64137
  if (isCoreCommand(command)) {
63696
64138
  this.state.addCommand(command);
63697
64139
  }
63698
64140
  this.dispatchToHandlers(this.handlers, command);
63699
64141
  this.finalize();
64142
+ const time = performance.now() - start;
64143
+ if (time > 5) {
64144
+ console.info(type, time, "ms");
64145
+ }
63700
64146
  });
63701
64147
  this.session.save(command, commands, changes);
63702
64148
  this.status = 0 /* Status.Ready */;
@@ -64102,6 +64548,6 @@ const constants = {
64102
64548
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, 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 };
64103
64549
 
64104
64550
 
64105
- __info__.version = "17.3.0-alpha.5";
64106
- __info__.date = "2024-04-18T14:38:57.166Z";
64107
- __info__.hash = "cff0ef9";
64551
+ __info__.version = "17.3.0-alpha.7";
64552
+ __info__.date = "2024-05-07T10:42:47.288Z";
64553
+ __info__.hash = "853c266";