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