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