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

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.6
7
+ * @date 2024-04-26T07:39:53.611Z
8
+ * @hash f58a0d5
9
9
  */
10
10
 
11
11
  'use strict';
@@ -1991,6 +1991,8 @@ const coreTypes = new Set([
1991
1991
  "CREATE_TABLE",
1992
1992
  "REMOVE_TABLE",
1993
1993
  "UPDATE_TABLE",
1994
+ "CREATE_TABLE_STYLE",
1995
+ "REMOVE_TABLE_STYLE",
1994
1996
  /** IMAGE */
1995
1997
  "CREATE_IMAGE",
1996
1998
  /** HEADER GROUP */
@@ -2137,6 +2139,7 @@ exports.CommandResult = void 0;
2137
2139
  CommandResult["TableNotFound"] = "TableNotFound";
2138
2140
  CommandResult["TableOverlap"] = "TableOverlap";
2139
2141
  CommandResult["InvalidTableConfig"] = "InvalidTableConfig";
2142
+ CommandResult["InvalidTableStyle"] = "InvalidTableStyle";
2140
2143
  CommandResult["FilterNotFound"] = "FilterNotFound";
2141
2144
  CommandResult["MergeInTable"] = "MergeInTable";
2142
2145
  CommandResult["NonContinuousTargets"] = "NonContinuousTargets";
@@ -18140,6 +18143,9 @@ const COLUMN = {
18140
18143
  ],
18141
18144
  returns: ["NUMBER"],
18142
18145
  compute: function (cellReference) {
18146
+ if (isEvaluationError(cellReference?.value)) {
18147
+ throw cellReference;
18148
+ }
18143
18149
  const _cellReference = cellReference === undefined ? this.__originCellXC?.() : cellReference.value;
18144
18150
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18145
18151
  const zone = toZone(_cellReference);
@@ -18155,6 +18161,9 @@ const COLUMNS = {
18155
18161
  args: [arg("range (meta)", _t("The range whose column count will be returned."))],
18156
18162
  returns: ["NUMBER"],
18157
18163
  compute: function (range) {
18164
+ if (isEvaluationError(range?.value)) {
18165
+ throw range;
18166
+ }
18158
18167
  const zone = toZone(range.value);
18159
18168
  return zone.right - zone.left + 1;
18160
18169
  },
@@ -18377,6 +18386,9 @@ const ROW = {
18377
18386
  ],
18378
18387
  returns: ["NUMBER"],
18379
18388
  compute: function (cellReference) {
18389
+ if (isEvaluationError(cellReference?.value)) {
18390
+ throw cellReference;
18391
+ }
18380
18392
  const _cellReference = cellReference === undefined ? this.__originCellXC?.() : cellReference.value;
18381
18393
  assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
18382
18394
  const zone = toZone(_cellReference);
@@ -18392,6 +18404,9 @@ const ROWS = {
18392
18404
  args: [arg("range (meta)", _t("The range whose row count will be returned."))],
18393
18405
  returns: ["NUMBER"],
18394
18406
  compute: function (range) {
18407
+ if (isEvaluationError(range?.value)) {
18408
+ throw range;
18409
+ }
18395
18410
  const zone = toZone(range.value);
18396
18411
  return zone.bottom - zone.top + 1;
18397
18412
  },
@@ -20344,17 +20359,13 @@ class Composer extends owl.Component {
20344
20359
  this.DOMFocusableElementStore.setFocusableElement(el);
20345
20360
  }
20346
20361
  this.contentHelper.updateEl(el);
20347
- this.processTokenAtCursor();
20348
20362
  });
20349
20363
  owl.useEffect(() => {
20350
20364
  this.processContent();
20351
20365
  });
20352
- owl.onPatched(() => {
20353
- // Required because typing '=SUM' and double-clicking another cell leaves ShowProvider/ShowDescription true
20354
- if (this.composerStore.editionMode === "inactive") {
20355
- this.processTokenAtCursor();
20356
- }
20357
- });
20366
+ owl.useEffect(() => {
20367
+ this.processTokenAtCursor();
20368
+ }, () => [this.composerStore.editionMode !== "inactive"]);
20358
20369
  }
20359
20370
  // ---------------------------------------------------------------------------
20360
20371
  // Handlers
@@ -20956,13 +20967,10 @@ function compileTokens(tokens) {
20956
20967
  const isRangeOnly = argTypes.every((t) => isRangeType(t));
20957
20968
  if (isRangeOnly) {
20958
20969
  if (!isRangeInput(currentArg)) {
20959
- throw new BadExpressionError(_t("Function %s expects the parameter %s to be reference to a cell or range, not a %s.", functionName, (i + 1).toString(), currentArg.type.toLowerCase()));
20970
+ throw new BadExpressionError(_t("Function %(function_name)s expects the parameter %(arg_index)s to be a reference to a cell or a range.", { function_name: functionName, arg_index: i + 1 }));
20960
20971
  }
20961
20972
  }
20962
- compiledArgs.push(compileAST(currentArg, isMeta, hasRange, {
20963
- functionName,
20964
- paramIndex: i + 1,
20965
- }));
20973
+ compiledArgs.push(compileAST(currentArg, isMeta, hasRange));
20966
20974
  }
20967
20975
  return compiledArgs;
20968
20976
  }
@@ -20978,7 +20986,7 @@ function compileTokens(tokens) {
20978
20986
  * function needs to receive as argument the coordinates of a cell rather
20979
20987
  * than its value. For this we have meta arguments.
20980
20988
  */
20981
- function compileAST(ast, isMeta = false, hasRange = false, referenceVerification = {}) {
20989
+ function compileAST(ast, isMeta = false, hasRange = false) {
20982
20990
  const code = new FunctionCodeBuilder(scope);
20983
20991
  if (ast.type !== "REFERENCE" && !(ast.type === "BIN_OPERATION" && ast.value === ":")) {
20984
20992
  if (isMeta) {
@@ -20997,11 +21005,11 @@ function compileTokens(tokens) {
20997
21005
  return code.return(`{ value: this.constantValues.strings[${constantValues.strings.indexOf(ast.value)}] }`);
20998
21006
  case "REFERENCE":
20999
21007
  const referenceIndex = dependencies.indexOf(ast.value);
21000
- if (hasRange) {
21008
+ if ((!isMeta && ast.value.includes(":")) || hasRange) {
21001
21009
  return code.return(`range(deps[${referenceIndex}])`);
21002
21010
  }
21003
21011
  else {
21004
- return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"}, "${referenceVerification.functionName || OPERATOR_MAP["="]}", ${referenceVerification.paramIndex})`);
21012
+ return code.return(`ref(deps[${referenceIndex}], ${isMeta ? "true" : "false"})`);
21005
21013
  }
21006
21014
  case "FUNCALL":
21007
21015
  const args = compileFunctionArgs(ast).map((arg) => arg.assignResultToVariable());
@@ -21010,20 +21018,14 @@ function compileTokens(tokens) {
21010
21018
  return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
21011
21019
  case "UNARY_OPERATION": {
21012
21020
  const fnName = UNARY_OPERATOR_MAP[ast.value];
21013
- const operand = compileAST(ast.operand, false, false, {
21014
- functionName: fnName,
21015
- }).assignResultToVariable();
21021
+ const operand = compileAST(ast.operand, false, false).assignResultToVariable();
21016
21022
  code.append(operand);
21017
21023
  return code.return(`ctx['${fnName}'](${operand.returnExpression})`);
21018
21024
  }
21019
21025
  case "BIN_OPERATION": {
21020
21026
  const fnName = OPERATOR_MAP[ast.value];
21021
- const left = compileAST(ast.left, false, false, {
21022
- functionName: fnName,
21023
- }).assignResultToVariable();
21024
- const right = compileAST(ast.right, false, false, {
21025
- functionName: fnName,
21026
- }).assignResultToVariable();
21027
+ const left = compileAST(ast.left, false, false).assignResultToVariable();
21028
+ const right = compileAST(ast.right, false, false).assignResultToVariable();
21027
21029
  code.append(left);
21028
21030
  code.append(right);
21029
21031
  return code.return(`ctx['${fnName}'](${left.returnExpression}, ${right.returnExpression})`);
@@ -21061,7 +21063,10 @@ function compilationCacheKey(tokens, dependencies, constantValues) {
21061
21063
  return `|N${constantValues.numbers.indexOf(parseNumber(token.value, DEFAULT_LOCALE))}|`;
21062
21064
  case "REFERENCE":
21063
21065
  case "INVALID_REFERENCE":
21064
- return `|${dependencies.indexOf(token.value)}|`;
21066
+ if (token.value.includes(":")) {
21067
+ return `R|${dependencies.indexOf(token.value)}|`;
21068
+ }
21069
+ return `C|${dependencies.indexOf(token.value)}|`;
21065
21070
  case "SPACE":
21066
21071
  return "";
21067
21072
  default:
@@ -21196,6 +21201,7 @@ const AGGREGATORS_BY_FIELD_TYPE = {
21196
21201
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
21197
21202
  char: ["count_distinct", "count"],
21198
21203
  many2one: ["count_distinct", "count"],
21204
+ reference: ["count_distinct", "count"],
21199
21205
  };
21200
21206
  const AGGREGATORS = {};
21201
21207
  for (const type in AGGREGATORS_BY_FIELD_TYPE) {
@@ -24363,7 +24369,7 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
24363
24369
  });
24364
24370
  onFigureDeleted();
24365
24371
  },
24366
- icon: "o-spreadsheet-Icon.DELETE",
24372
+ icon: "o-spreadsheet-Icon.TRASH",
24367
24373
  };
24368
24374
  }
24369
24375
 
@@ -24378,7 +24384,8 @@ const inverseCommandRegistry = new Registry()
24378
24384
  .add("CREATE_FIGURE", inverseCreateFigure)
24379
24385
  .add("CREATE_CHART", inverseCreateChart)
24380
24386
  .add("HIDE_COLUMNS_ROWS", inverseHideColumnsRows)
24381
- .add("UNHIDE_COLUMNS_ROWS", inverseUnhideColumnsRows);
24387
+ .add("UNHIDE_COLUMNS_ROWS", inverseUnhideColumnsRows)
24388
+ .add("CREATE_TABLE_STYLE", inverseCreateTableStyle);
24382
24389
  for (const cmd of coreTypes.values()) {
24383
24390
  if (!inverseCommandRegistry.contains(cmd)) {
24384
24391
  inverseCommandRegistry.add(cmd, identity);
@@ -24463,6 +24470,9 @@ function inverseUnhideColumnsRows(cmd) {
24463
24470
  },
24464
24471
  ];
24465
24472
  }
24473
+ function inverseCreateTableStyle(cmd) {
24474
+ return [{ type: "REMOVE_TABLE_STYLE", tableStyleId: cmd.tableStyleId }];
24475
+ }
24466
24476
 
24467
24477
  /**
24468
24478
  * The class Registry is extended in order to add the function addChild
@@ -25060,7 +25070,7 @@ const CSS = css /* scss */ `
25060
25070
 
25061
25071
  .o-search-icon {
25062
25072
  right: 5px;
25063
- top: 4px;
25073
+ top: 3px;
25064
25074
  opacity: 0.4;
25065
25075
 
25066
25076
  svg {
@@ -25147,8 +25157,12 @@ class FilterMenu extends owl.Component {
25147
25157
  });
25148
25158
  this.state.values = this.getFilterHiddenValues(this.props.filterPosition);
25149
25159
  }
25150
- get isReadonly() {
25151
- return this.env.model.getters.isReadonly();
25160
+ get isSortable() {
25161
+ if (!this.table) {
25162
+ return false;
25163
+ }
25164
+ const coreTable = this.env.model.getters.getCoreTableMatchingTopLeft(this.table.range.sheetId, this.table.range.zone);
25165
+ return !this.env.model.getters.isReadonly() && coreTable?.type !== "dynamic";
25152
25166
  }
25153
25167
  getFilterHiddenValues(position) {
25154
25168
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -26118,10 +26132,10 @@ function getSmartChartDefinition(zone, getters) {
26118
26132
  }
26119
26133
 
26120
26134
  const TABLE_STYLE_CATEGORIES = {
26121
- none: _t("None"),
26122
26135
  light: _t("Light"),
26123
26136
  medium: _t("Medium"),
26124
26137
  dark: _t("Dark"),
26138
+ custom: _t("Custom"),
26125
26139
  };
26126
26140
  const DEFAULT_TABLE_CONFIG = {
26127
26141
  hasFilters: true,
@@ -26134,7 +26148,7 @@ const DEFAULT_TABLE_CONFIG = {
26134
26148
  automaticAutofill: true,
26135
26149
  styleId: "TableStyleMedium2",
26136
26150
  };
26137
- function generateColorSet(name, highlightColor) {
26151
+ function generateTableColorSet(name, highlightColor) {
26138
26152
  return {
26139
26153
  coloredText: darkenColor(highlightColor, 0.3),
26140
26154
  light: lightenColor(highlightColor, 0.8),
@@ -26155,10 +26169,10 @@ const COLOR_SETS = {
26155
26169
  mediumBorder: "#000000",
26156
26170
  highlight: "#000000",
26157
26171
  },
26158
- lightBlue: generateColorSet(_t("Light blue"), "#346B90"),
26159
- red: generateColorSet(_t("Red"), "#C53628"),
26160
- lightGreen: generateColorSet(_t("Light green"), "#748747"),
26161
- purple: generateColorSet(_t("Purple"), "#6C4E65"),
26172
+ lightBlue: generateTableColorSet(_t("Light blue"), "#346B90"),
26173
+ red: generateTableColorSet(_t("Red"), "#C53628"),
26174
+ lightGreen: generateTableColorSet(_t("Light green"), "#748747"),
26175
+ purple: generateTableColorSet(_t("Purple"), "#6C4E65"),
26162
26176
  gray: {
26163
26177
  name: _t("Gray"),
26164
26178
  coloredText: "#666666",
@@ -26168,7 +26182,7 @@ const COLOR_SETS = {
26168
26182
  mediumBorder: "#D0D0D0",
26169
26183
  highlight: "#A9A9A9",
26170
26184
  },
26171
- orange: generateColorSet(_t("Orange"), "#C37034"),
26185
+ orange: generateTableColorSet(_t("Orange"), "#C37034"),
26172
26186
  };
26173
26187
  const DARK_COLOR_SETS = {
26174
26188
  black: COLOR_SETS.black,
@@ -26176,9 +26190,10 @@ const DARK_COLOR_SETS = {
26176
26190
  purpleGreen: { ...COLOR_SETS.lightGreen, highlight: COLOR_SETS.purple.highlight },
26177
26191
  redBlue: { ...COLOR_SETS.lightBlue, highlight: COLOR_SETS.red.highlight },
26178
26192
  };
26179
- const lightTemplateColoredText = (colorSet) => ({
26193
+ const lightColoredText = (colorSet) => ({
26180
26194
  category: "light",
26181
- colorName: colorSet.name,
26195
+ templateName: "lightColoredText",
26196
+ primaryColor: colorSet.highlight,
26182
26197
  wholeTable: {
26183
26198
  style: { textColor: colorSet.coloredText },
26184
26199
  border: {
@@ -26190,9 +26205,10 @@ const lightTemplateColoredText = (colorSet) => ({
26190
26205
  totalRow: { border: { top: { color: colorSet.highlight, style: "thin" } } },
26191
26206
  firstRowStripe: { style: { fillColor: colorSet.light } },
26192
26207
  });
26193
- const lightTemplateWithHeader = (colorSet) => ({
26208
+ const lightWithHeader = (colorSet) => ({
26194
26209
  category: "light",
26195
- colorName: colorSet.name,
26210
+ templateName: "lightWithHeader",
26211
+ primaryColor: colorSet.highlight,
26196
26212
  wholeTable: {
26197
26213
  border: {
26198
26214
  top: { color: colorSet.highlight, style: "thin" },
@@ -26209,9 +26225,10 @@ const lightTemplateWithHeader = (colorSet) => ({
26209
26225
  firstRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
26210
26226
  secondRowStripe: { border: { bottom: { color: colorSet.highlight, style: "thin" } } },
26211
26227
  });
26212
- const lightTemplateAllBorders = (colorSet) => ({
26228
+ const lightAllBorders = (colorSet) => ({
26213
26229
  category: "light",
26214
- colorName: colorSet.name,
26230
+ templateName: "lightAllBorders",
26231
+ primaryColor: colorSet.highlight,
26215
26232
  wholeTable: {
26216
26233
  border: {
26217
26234
  top: { color: colorSet.highlight, style: "thin" },
@@ -26227,9 +26244,10 @@ const lightTemplateAllBorders = (colorSet) => ({
26227
26244
  firstRowStripe: { style: { fillColor: colorSet.light } },
26228
26245
  firstColumnStripe: { style: { fillColor: colorSet.light } },
26229
26246
  });
26230
- const mediumTemplateBandedBorders = (colorSet) => ({
26247
+ const mediumBandedBorders = (colorSet) => ({
26231
26248
  category: "medium",
26232
- colorName: colorSet.name,
26249
+ templateName: "mediumBandedBorders",
26250
+ primaryColor: colorSet.highlight,
26233
26251
  wholeTable: {
26234
26252
  border: {
26235
26253
  top: { color: colorSet.mediumBorder, style: "thin" },
@@ -26246,9 +26264,10 @@ const mediumTemplateBandedBorders = (colorSet) => ({
26246
26264
  firstRowStripe: { style: { fillColor: colorSet.light } },
26247
26265
  firstColumnStripe: { style: { fillColor: colorSet.light } },
26248
26266
  });
26249
- const mediumTemplateWhiteBorders = (colorSet) => ({
26267
+ const mediumWhiteBorders = (colorSet) => ({
26250
26268
  category: "medium",
26251
- colorName: colorSet.name,
26269
+ templateName: "mediumWhiteBorders",
26270
+ primaryColor: colorSet.highlight,
26252
26271
  wholeTable: {
26253
26272
  border: {
26254
26273
  horizontal: { color: "#FFFFFF", style: "thin" },
@@ -26269,9 +26288,10 @@ const mediumTemplateWhiteBorders = (colorSet) => ({
26269
26288
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26270
26289
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26271
26290
  });
26272
- const mediumTemplateMinimalBorders = (colorSet) => ({
26291
+ const mediumMinimalBorders = (colorSet) => ({
26273
26292
  category: "medium",
26274
- colorName: colorSet.name,
26293
+ templateName: "mediumMinimalBorders",
26294
+ primaryColor: colorSet.highlight,
26275
26295
  wholeTable: {
26276
26296
  border: {
26277
26297
  top: { color: "#000000", style: "medium" },
@@ -26288,9 +26308,10 @@ const mediumTemplateMinimalBorders = (colorSet) => ({
26288
26308
  firstRowStripe: { style: { fillColor: COLOR_SETS.black.light } },
26289
26309
  firstColumnStripe: { style: { fillColor: COLOR_SETS.black.light } },
26290
26310
  });
26291
- const mediumTemplateAllBorders = (colorSet) => ({
26311
+ const mediumAllBorders = (colorSet) => ({
26292
26312
  category: "medium",
26293
- colorName: colorSet.name,
26313
+ templateName: "mediumAllBorders",
26314
+ primaryColor: colorSet.highlight,
26294
26315
  wholeTable: {
26295
26316
  border: {
26296
26317
  top: { color: colorSet.mediumBorder, style: "thin" },
@@ -26306,9 +26327,10 @@ const mediumTemplateAllBorders = (colorSet) => ({
26306
26327
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26307
26328
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26308
26329
  });
26309
- const darkTemplate = (colorSet) => ({
26330
+ const dark = (colorSet) => ({
26310
26331
  category: "dark",
26311
- colorName: colorSet.name,
26332
+ templateName: "dark",
26333
+ primaryColor: colorSet.highlight,
26312
26334
  wholeTable: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
26313
26335
  totalRow: {
26314
26336
  style: { fillColor: colorSet.dark, textColor: "#FFFFFF" },
@@ -26329,18 +26351,19 @@ const darkTemplate = (colorSet) => ({
26329
26351
  firstRowStripe: { style: { fillColor: colorSet.dark } },
26330
26352
  firstColumnStripe: { style: { fillColor: colorSet.dark } },
26331
26353
  });
26332
- const darkTemplateNoBorders = (colorSet) => ({
26354
+ const darkNoBorders = (colorSet) => ({
26333
26355
  category: "dark",
26334
- colorName: colorSet.name,
26356
+ templateName: "darkNoBorders",
26357
+ primaryColor: colorSet.highlight,
26335
26358
  wholeTable: { style: { fillColor: colorSet.light } },
26336
26359
  totalRow: { border: { top: { color: "#000000", style: "medium" } } }, // @compatibility: should be double line
26337
26360
  headerRow: { style: { fillColor: colorSet.highlight, textColor: "#FFFFFF" } },
26338
26361
  firstRowStripe: { style: { fillColor: colorSet.medium } },
26339
26362
  firstColumnStripe: { style: { fillColor: colorSet.medium } },
26340
26363
  });
26341
- const darkTemplateInBlack = darkTemplate(COLOR_SETS.black);
26364
+ const darkTemplateInBlack = dark(COLOR_SETS.black);
26342
26365
  darkTemplateInBlack.wholeTable.style.fillColor = "#737373";
26343
- const mediumMinimalBordersInBlack = mediumTemplateMinimalBorders(COLOR_SETS.black);
26366
+ const mediumMinimalBordersInBlack = mediumMinimalBorders(COLOR_SETS.black);
26344
26367
  mediumMinimalBordersInBlack.wholeTable.border = {
26345
26368
  ...mediumMinimalBordersInBlack.wholeTable.border,
26346
26369
  left: { color: "#000000", style: "thin" },
@@ -26348,69 +26371,92 @@ mediumMinimalBordersInBlack.wholeTable.border = {
26348
26371
  horizontal: { color: "#000000", style: "thin" },
26349
26372
  vertical: { color: "#000000", style: "thin" },
26350
26373
  };
26374
+ function buildPreset(name, template, colorSet) {
26375
+ return { ...template(colorSet), displayName: `${colorSet.name}, ${name}` };
26376
+ }
26351
26377
  const TABLE_PRESETS = {
26352
- None: { category: "none", colorName: "" },
26353
- TableStyleLight1: lightTemplateColoredText(COLOR_SETS.black),
26354
- TableStyleLight2: lightTemplateColoredText(COLOR_SETS.lightBlue),
26355
- TableStyleLight3: lightTemplateColoredText(COLOR_SETS.red),
26356
- TableStyleLight4: lightTemplateColoredText(COLOR_SETS.lightGreen),
26357
- TableStyleLight5: lightTemplateColoredText(COLOR_SETS.purple),
26358
- TableStyleLight6: lightTemplateColoredText(COLOR_SETS.gray),
26359
- TableStyleLight7: lightTemplateColoredText(COLOR_SETS.orange),
26360
- TableStyleLight8: lightTemplateWithHeader(COLOR_SETS.black),
26361
- TableStyleLight9: lightTemplateWithHeader(COLOR_SETS.lightBlue),
26362
- TableStyleLight10: lightTemplateWithHeader(COLOR_SETS.red),
26363
- TableStyleLight11: lightTemplateWithHeader(COLOR_SETS.lightGreen),
26364
- TableStyleLight12: lightTemplateWithHeader(COLOR_SETS.purple),
26365
- TableStyleLight13: lightTemplateWithHeader(COLOR_SETS.gray),
26366
- TableStyleLight14: lightTemplateWithHeader(COLOR_SETS.orange),
26367
- TableStyleLight15: lightTemplateAllBorders(COLOR_SETS.black),
26368
- TableStyleLight16: lightTemplateAllBorders(COLOR_SETS.lightBlue),
26369
- TableStyleLight17: lightTemplateAllBorders(COLOR_SETS.red),
26370
- TableStyleLight18: lightTemplateAllBorders(COLOR_SETS.lightGreen),
26371
- TableStyleLight19: lightTemplateAllBorders(COLOR_SETS.purple),
26372
- TableStyleLight20: lightTemplateAllBorders(COLOR_SETS.gray),
26373
- TableStyleLight21: lightTemplateAllBorders(COLOR_SETS.orange),
26374
- TableStyleMedium1: mediumTemplateBandedBorders(COLOR_SETS.black),
26375
- TableStyleMedium2: mediumTemplateBandedBorders(COLOR_SETS.lightBlue),
26376
- TableStyleMedium3: mediumTemplateBandedBorders(COLOR_SETS.red),
26377
- TableStyleMedium4: mediumTemplateBandedBorders(COLOR_SETS.lightGreen),
26378
- TableStyleMedium5: mediumTemplateBandedBorders(COLOR_SETS.purple),
26379
- TableStyleMedium6: mediumTemplateBandedBorders(COLOR_SETS.gray),
26380
- TableStyleMedium7: mediumTemplateBandedBorders(COLOR_SETS.orange),
26381
- TableStyleMedium8: mediumTemplateWhiteBorders(COLOR_SETS.black),
26382
- TableStyleMedium9: mediumTemplateWhiteBorders(COLOR_SETS.lightBlue),
26383
- TableStyleMedium10: mediumTemplateWhiteBorders(COLOR_SETS.red),
26384
- TableStyleMedium11: mediumTemplateWhiteBorders(COLOR_SETS.lightGreen),
26385
- TableStyleMedium12: mediumTemplateWhiteBorders(COLOR_SETS.purple),
26386
- TableStyleMedium13: mediumTemplateWhiteBorders(COLOR_SETS.gray),
26387
- TableStyleMedium14: mediumTemplateWhiteBorders(COLOR_SETS.orange),
26388
- TableStyleMedium15: mediumMinimalBordersInBlack,
26389
- TableStyleMedium16: mediumTemplateMinimalBorders(COLOR_SETS.lightBlue),
26390
- TableStyleMedium17: mediumTemplateMinimalBorders(COLOR_SETS.red),
26391
- TableStyleMedium18: mediumTemplateMinimalBorders(COLOR_SETS.lightGreen),
26392
- TableStyleMedium19: mediumTemplateMinimalBorders(COLOR_SETS.purple),
26393
- TableStyleMedium20: mediumTemplateMinimalBorders(COLOR_SETS.gray),
26394
- TableStyleMedium21: mediumTemplateMinimalBorders(COLOR_SETS.orange),
26395
- TableStyleMedium22: mediumTemplateAllBorders(COLOR_SETS.black),
26396
- TableStyleMedium23: mediumTemplateAllBorders(COLOR_SETS.lightBlue),
26397
- TableStyleMedium24: mediumTemplateAllBorders(COLOR_SETS.red),
26398
- TableStyleMedium25: mediumTemplateAllBorders(COLOR_SETS.lightGreen),
26399
- TableStyleMedium26: mediumTemplateAllBorders(COLOR_SETS.purple),
26400
- TableStyleMedium27: mediumTemplateAllBorders(COLOR_SETS.gray),
26401
- TableStyleMedium28: mediumTemplateAllBorders(COLOR_SETS.orange),
26402
- TableStyleDark1: darkTemplateInBlack,
26403
- TableStyleDark2: darkTemplate(COLOR_SETS.lightBlue),
26404
- TableStyleDark3: darkTemplate(COLOR_SETS.red),
26405
- TableStyleDark4: darkTemplate(COLOR_SETS.lightGreen),
26406
- TableStyleDark5: darkTemplate(COLOR_SETS.purple),
26407
- TableStyleDark6: darkTemplate(COLOR_SETS.gray),
26408
- TableStyleDark7: darkTemplate(COLOR_SETS.orange),
26409
- TableStyleDark8: darkTemplateNoBorders(DARK_COLOR_SETS.black),
26410
- TableStyleDark9: darkTemplateNoBorders(DARK_COLOR_SETS.redBlue),
26411
- TableStyleDark10: darkTemplateNoBorders(DARK_COLOR_SETS.purpleGreen),
26412
- TableStyleDark11: darkTemplateNoBorders(DARK_COLOR_SETS.orangeBlue),
26413
- };
26378
+ None: { category: "light", templateName: "none", primaryColor: "", displayName: "none" },
26379
+ TableStyleLight1: buildPreset("TableStyleLight1", lightColoredText, COLOR_SETS.black),
26380
+ TableStyleLight2: buildPreset("TableStyleLight2", lightColoredText, COLOR_SETS.lightBlue),
26381
+ TableStyleLight3: buildPreset("TableStyleLight3", lightColoredText, COLOR_SETS.red),
26382
+ TableStyleLight4: buildPreset("TableStyleLight4", lightColoredText, COLOR_SETS.lightGreen),
26383
+ TableStyleLight5: buildPreset("TableStyleLight5", lightColoredText, COLOR_SETS.purple),
26384
+ TableStyleLight6: buildPreset("TableStyleLight6", lightColoredText, COLOR_SETS.gray),
26385
+ TableStyleLight7: buildPreset("TableStyleLight7", lightColoredText, COLOR_SETS.orange),
26386
+ TableStyleLight8: buildPreset("TableStyleLight8", lightWithHeader, COLOR_SETS.black),
26387
+ TableStyleLight9: buildPreset("TableStyleLight9", lightWithHeader, COLOR_SETS.lightBlue),
26388
+ TableStyleLight10: buildPreset("TableStyleLight10", lightWithHeader, COLOR_SETS.red),
26389
+ TableStyleLight11: buildPreset("TableStyleLight11", lightWithHeader, COLOR_SETS.lightGreen),
26390
+ TableStyleLight12: buildPreset("TableStyleLight12", lightWithHeader, COLOR_SETS.purple),
26391
+ TableStyleLight13: buildPreset("TableStyleLight13", lightWithHeader, COLOR_SETS.gray),
26392
+ TableStyleLight14: buildPreset("TableStyleLight14", lightWithHeader, COLOR_SETS.orange),
26393
+ TableStyleLight15: buildPreset("TableStyleLight15", lightAllBorders, COLOR_SETS.black),
26394
+ TableStyleLight16: buildPreset("TableStyleLight16", lightAllBorders, COLOR_SETS.lightBlue),
26395
+ TableStyleLight17: buildPreset("TableStyleLight17", lightAllBorders, COLOR_SETS.red),
26396
+ TableStyleLight18: buildPreset("TableStyleLight18", lightAllBorders, COLOR_SETS.lightGreen),
26397
+ TableStyleLight19: buildPreset("TableStyleLight19", lightAllBorders, COLOR_SETS.purple),
26398
+ TableStyleLight20: buildPreset("TableStyleLight20", lightAllBorders, COLOR_SETS.gray),
26399
+ TableStyleLight21: buildPreset("TableStyleLight21", lightAllBorders, COLOR_SETS.orange),
26400
+ TableStyleMedium1: buildPreset("TableStyleMedium1", mediumBandedBorders, COLOR_SETS.black),
26401
+ TableStyleMedium2: buildPreset("TableStyleMedium2", mediumBandedBorders, COLOR_SETS.lightBlue),
26402
+ TableStyleMedium3: buildPreset("TableStyleMedium3", mediumBandedBorders, COLOR_SETS.red),
26403
+ TableStyleMedium4: buildPreset("TableStyleMedium4", mediumBandedBorders, COLOR_SETS.lightGreen),
26404
+ TableStyleMedium5: buildPreset("TableStyleMedium5", mediumBandedBorders, COLOR_SETS.purple),
26405
+ TableStyleMedium6: buildPreset("TableStyleMedium6", mediumBandedBorders, COLOR_SETS.gray),
26406
+ TableStyleMedium7: buildPreset("TableStyleMedium7", mediumBandedBorders, COLOR_SETS.orange),
26407
+ TableStyleMedium8: buildPreset("TableStyleMedium8", mediumWhiteBorders, COLOR_SETS.black),
26408
+ TableStyleMedium9: buildPreset("TableStyleMedium9", mediumWhiteBorders, COLOR_SETS.lightBlue),
26409
+ TableStyleMedium10: buildPreset("TableStyleMedium10", mediumWhiteBorders, COLOR_SETS.red),
26410
+ TableStyleMedium11: buildPreset("TableStyleMedium11", mediumWhiteBorders, COLOR_SETS.lightGreen),
26411
+ TableStyleMedium12: buildPreset("TableStyleMedium12", mediumWhiteBorders, COLOR_SETS.purple),
26412
+ TableStyleMedium13: buildPreset("TableStyleMedium13", mediumWhiteBorders, COLOR_SETS.gray),
26413
+ TableStyleMedium14: buildPreset("TableStyleMedium14", mediumWhiteBorders, COLOR_SETS.orange),
26414
+ TableStyleMedium15: { ...mediumMinimalBordersInBlack, displayName: "Black, TableStyleMedium15" },
26415
+ TableStyleMedium16: buildPreset("TableStyleMedium16", mediumMinimalBorders, COLOR_SETS.lightBlue),
26416
+ TableStyleMedium17: buildPreset("TableStyleMedium17", mediumMinimalBorders, COLOR_SETS.red),
26417
+ TableStyleMedium18: buildPreset("TableStyleMedium18", mediumMinimalBorders, COLOR_SETS.lightGreen),
26418
+ TableStyleMedium19: buildPreset("TableStyleMedium19", mediumMinimalBorders, COLOR_SETS.purple),
26419
+ TableStyleMedium20: buildPreset("TableStyleMedium20", mediumMinimalBorders, COLOR_SETS.gray),
26420
+ TableStyleMedium21: buildPreset("TableStyleMedium21", mediumMinimalBorders, COLOR_SETS.orange),
26421
+ TableStyleMedium22: buildPreset("TableStyleMedium22", mediumAllBorders, COLOR_SETS.black),
26422
+ TableStyleMedium23: buildPreset("TableStyleMedium23", mediumAllBorders, COLOR_SETS.lightBlue),
26423
+ TableStyleMedium24: buildPreset("TableStyleMedium24", mediumAllBorders, COLOR_SETS.red),
26424
+ TableStyleMedium25: buildPreset("TableStyleMedium25", mediumAllBorders, COLOR_SETS.lightGreen),
26425
+ TableStyleMedium26: buildPreset("TableStyleMedium26", mediumAllBorders, COLOR_SETS.purple),
26426
+ TableStyleMedium27: buildPreset("TableStyleMedium27", mediumAllBorders, COLOR_SETS.gray),
26427
+ TableStyleMedium28: buildPreset("TableStyleMedium28", mediumAllBorders, COLOR_SETS.orange),
26428
+ TableStyleDark1: { ...darkTemplateInBlack, displayName: "Black, TableStyleDark1" },
26429
+ TableStyleDark2: buildPreset("TableStyleDark2", dark, COLOR_SETS.lightBlue),
26430
+ TableStyleDark3: buildPreset("TableStyleDark3", dark, COLOR_SETS.red),
26431
+ TableStyleDark4: buildPreset("TableStyleDark4", dark, COLOR_SETS.lightGreen),
26432
+ TableStyleDark5: buildPreset("TableStyleDark5", dark, COLOR_SETS.purple),
26433
+ TableStyleDark6: buildPreset("TableStyleDark6", dark, COLOR_SETS.gray),
26434
+ TableStyleDark7: buildPreset("TableStyleDark7", dark, COLOR_SETS.orange),
26435
+ TableStyleDark8: buildPreset("TableStyleDark8", darkNoBorders, DARK_COLOR_SETS.black),
26436
+ TableStyleDark9: buildPreset("TableStyleDark9", darkNoBorders, DARK_COLOR_SETS.redBlue),
26437
+ TableStyleDark10: buildPreset("TableStyleDark10", darkNoBorders, DARK_COLOR_SETS.purpleGreen),
26438
+ TableStyleDark11: buildPreset("TableStyleDark11", darkNoBorders, DARK_COLOR_SETS.orangeBlue),
26439
+ };
26440
+ const TABLE_STYLES_TEMPLATES = {
26441
+ none: () => ({ category: "none", templateName: "none", primaryColor: "", name: "none" }),
26442
+ lightColoredText: lightColoredText,
26443
+ lightAllBorders: lightAllBorders,
26444
+ mediumAllBorders: mediumAllBorders,
26445
+ lightWithHeader: lightWithHeader,
26446
+ mediumBandedBorders: mediumBandedBorders,
26447
+ mediumMinimalBorders: mediumMinimalBorders,
26448
+ darkNoBorders: darkNoBorders,
26449
+ mediumWhiteBorders: mediumWhiteBorders,
26450
+ dark: dark,
26451
+ };
26452
+ function buildTableStyle(name, templateName, primaryColor) {
26453
+ const colorSet = generateTableColorSet("", primaryColor);
26454
+ return {
26455
+ ...TABLE_STYLES_TEMPLATES[templateName](colorSet),
26456
+ category: "custom",
26457
+ displayName: name,
26458
+ };
26459
+ }
26414
26460
 
26415
26461
  /**
26416
26462
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
@@ -26973,7 +27019,7 @@ const findAndReplace = {
26973
27019
  execute: (env) => {
26974
27020
  env.openSidePanel("FindAndReplace", {});
26975
27021
  },
26976
- icon: "o-spreadsheet-Icon.FIND_AND_REPLACE",
27022
+ icon: "o-spreadsheet-Icon.SEARCH",
26977
27023
  };
26978
27024
  const deleteValues = {
26979
27025
  name: _t("Delete values"),
@@ -27473,18 +27519,18 @@ cellMenuRegistry
27473
27519
  .add("delete_row", {
27474
27520
  ...deleteRow,
27475
27521
  sequence: 110,
27476
- icon: "o-spreadsheet-Icon.DELETE",
27522
+ icon: "o-spreadsheet-Icon.TRASH",
27477
27523
  })
27478
27524
  .add("delete_column", {
27479
27525
  ...deleteCol,
27480
27526
  sequence: 120,
27481
- icon: "o-spreadsheet-Icon.DELETE",
27527
+ icon: "o-spreadsheet-Icon.TRASH",
27482
27528
  })
27483
27529
  .add("delete_cell", {
27484
27530
  ...deleteCells,
27485
27531
  sequence: 130,
27486
27532
  separator: true,
27487
- icon: "o-spreadsheet-Icon.DELETE",
27533
+ icon: "o-spreadsheet-Icon.TRASH",
27488
27534
  })
27489
27535
  .addChild("delete_cell_up", ["delete_cell"], {
27490
27536
  ...deleteCellShiftUp,
@@ -27600,6 +27646,7 @@ function createFormatActionSpec({ name, format, descriptionValue, }) {
27600
27646
  }),
27601
27647
  execute: (env) => setFormatter(env, formatCallback(env)),
27602
27648
  isActive: (env) => isFormatSelected(env, formatCallback(env)),
27649
+ format,
27603
27650
  };
27604
27651
  }
27605
27652
  const formatNumberAutomatic = {
@@ -27952,7 +27999,9 @@ function getWrapModeIcon(env) {
27952
27999
 
27953
28000
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
27954
28001
  __proto__: null,
28002
+ EXAMPLE_DATE: EXAMPLE_DATE,
27955
28003
  clearFormat: clearFormat,
28004
+ createFormatActionSpec: createFormatActionSpec,
27956
28005
  decraseDecimalPlaces: decraseDecimalPlaces,
27957
28006
  fillColor: fillColor,
27958
28007
  formatAlignment: formatAlignment,
@@ -28320,7 +28369,7 @@ colMenuRegistry
28320
28369
  .add("delete_column", {
28321
28370
  ...deleteCols,
28322
28371
  sequence: 90,
28323
- icon: "o-spreadsheet-Icon.DELETE",
28372
+ icon: "o-spreadsheet-Icon.TRASH",
28324
28373
  })
28325
28374
  .add("clear_column", {
28326
28375
  ...clearCols,
@@ -28363,64 +28412,119 @@ colMenuRegistry
28363
28412
  isVisible: (env) => canUngroupHeaders(env, "COL"),
28364
28413
  });
28365
28414
 
28366
- const numberFormatMenuRegistry = new MenuItemRegistry();
28415
+ const numberFormatMenuRegistry = new Registry();
28367
28416
  numberFormatMenuRegistry
28368
28417
  .add("format_number_automatic", {
28369
28418
  ...formatNumberAutomatic,
28419
+ id: "format_number_automatic",
28370
28420
  sequence: 10,
28371
28421
  })
28372
28422
  .add("format_number_plain_text", {
28373
28423
  ...formatNumberPlainText,
28424
+ id: "format_number_plain_text",
28374
28425
  sequence: 15,
28375
28426
  separator: true,
28376
28427
  })
28377
28428
  .add("format_number_number", {
28378
28429
  ...formatNumberNumber,
28430
+ id: "format_number_number",
28379
28431
  sequence: 20,
28380
28432
  })
28381
28433
  .add("format_number_percent", {
28382
28434
  ...formatNumberPercent,
28435
+ id: "format_number_percent",
28383
28436
  sequence: 30,
28384
28437
  separator: true,
28385
28438
  })
28386
28439
  .add("format_number_currency", {
28387
28440
  ...formatNumberCurrency,
28441
+ id: "format_number_currency",
28388
28442
  sequence: 40,
28389
28443
  })
28390
28444
  .add("format_number_currency_rounded", {
28391
28445
  ...formatNumberCurrencyRounded,
28446
+ id: "format_number_currency_rounded",
28392
28447
  sequence: 50,
28393
28448
  })
28394
28449
  .add("format_custom_currency", {
28395
28450
  ...formatCustomCurrency,
28451
+ id: "format_custom_currency",
28396
28452
  sequence: 60,
28397
28453
  separator: true,
28398
28454
  })
28399
28455
  .add("format_number_date", {
28400
28456
  ...formatNumberDate,
28457
+ id: "format_number_date",
28401
28458
  sequence: 70,
28402
28459
  })
28403
28460
  .add("format_number_time", {
28404
28461
  ...formatNumberTime,
28462
+ id: "format_number_time",
28405
28463
  sequence: 80,
28406
28464
  })
28407
28465
  .add("format_number_date_time", {
28408
28466
  ...formatNumberDateTime,
28467
+ id: "format_number_date_time",
28409
28468
  sequence: 90,
28410
28469
  })
28411
28470
  .add("format_number_duration", {
28412
28471
  ...formatNumberDuration,
28472
+ id: "format_number_duration",
28413
28473
  sequence: 100,
28414
28474
  separator: true,
28415
28475
  })
28416
28476
  .add("more_formats", {
28417
28477
  ...moreFormats,
28418
- sequence: 110,
28478
+ id: "more_formats",
28479
+ sequence: 120,
28480
+ });
28481
+ function getCustomNumberFormats(env) {
28482
+ const defaultFormats = new Set(numberFormatMenuRegistry
28483
+ .getAll()
28484
+ .map((f) => (typeof f.format === "function" ? f.format(env) : f.format)));
28485
+ const customFormats = new Map();
28486
+ for (const sheetId of env.model.getters.getSheetIds()) {
28487
+ const cells = env.model.getters.getEvaluatedCells(sheetId);
28488
+ for (const cellId in cells) {
28489
+ const cell = cells[cellId];
28490
+ if (cell.format && !customFormats.has(cell.format) && !defaultFormats.has(cell.format)) {
28491
+ const formatType = getNumberFormatType(cell.format);
28492
+ if (formatType === "date" || formatType === "currency") {
28493
+ customFormats.set(cell.format, createFormatActionSpec({
28494
+ descriptionValue: formatType === "currency" ? 1000 : EXAMPLE_DATE,
28495
+ format: cell.format,
28496
+ name: cell.format,
28497
+ }));
28498
+ }
28499
+ }
28500
+ }
28501
+ }
28502
+ return [...customFormats.values()];
28503
+ }
28504
+ const getNumberFormatType = memoize((format) => {
28505
+ if (isDateTimeFormat(format)) {
28506
+ return "date";
28507
+ }
28508
+ else if (format.includes("[$")) {
28509
+ return "currency";
28510
+ }
28511
+ return "number";
28419
28512
  });
28420
28513
  const formatNumberMenuItemSpec = {
28421
28514
  name: _t("More formats"),
28422
28515
  icon: "o-spreadsheet-Icon.NUMBER_FORMATS",
28423
- children: [() => numberFormatMenuRegistry.getAll()],
28516
+ children: [
28517
+ (env) => {
28518
+ const customFormats = getCustomNumberFormats(env).map((action) => ({
28519
+ ...action,
28520
+ sequence: 110,
28521
+ }));
28522
+ if (customFormats.length > 0) {
28523
+ customFormats[customFormats.length - 1].separator = true;
28524
+ }
28525
+ return createActions([...numberFormatMenuRegistry.getAll(), ...customFormats]);
28526
+ },
28527
+ ],
28424
28528
  };
28425
28529
 
28426
28530
  const rowMenuRegistry = new MenuItemRegistry();
@@ -28461,7 +28565,7 @@ rowMenuRegistry
28461
28565
  .add("delete_row", {
28462
28566
  ...deleteRows,
28463
28567
  sequence: 70,
28464
- icon: "o-spreadsheet-Icon.DELETE",
28568
+ icon: "o-spreadsheet-Icon.TRASH",
28465
28569
  })
28466
28570
  .add("clear_row", {
28467
28571
  ...clearRows,
@@ -28612,7 +28716,7 @@ topbarMenuRegistry
28612
28716
  })
28613
28717
  .addChild("delete", ["edit"], {
28614
28718
  name: _t("Delete"),
28615
- icon: "o-spreadsheet-Icon.DELETE",
28719
+ icon: "o-spreadsheet-Icon.TRASH",
28616
28720
  sequence: 70,
28617
28721
  })
28618
28722
  .addChild("edit_delete_cell_values", ["edit", "delete"], {
@@ -30924,6 +31028,14 @@ class ChartPanel extends owl.Component {
30924
31028
  css /* scss */ `
30925
31029
  .o-spreadsheet {
30926
31030
  .o-icon {
31031
+ display: flex;
31032
+ align-items: center;
31033
+ justify-content: center;
31034
+ width: ${ICON_EDGE_LENGTH}px;
31035
+ height: ${ICON_EDGE_LENGTH}px;
31036
+ font-size: ${ICON_EDGE_LENGTH}px;
31037
+ vertical-align: middle;
31038
+
30927
31039
  .small-text {
30928
31040
  font: bold 9px sans-serif;
30929
31041
  }
@@ -30931,6 +31043,9 @@ css /* scss */ `
30931
31043
  font: bold 16px sans-serif;
30932
31044
  }
30933
31045
  }
31046
+ .fa-small {
31047
+ font-size: 14px;
31048
+ }
30934
31049
  }
30935
31050
  `;
30936
31051
  // -----------------------------------------------------------------------------
@@ -33554,15 +33669,14 @@ function createFilter(id, range, config, createRange) {
33554
33669
  function isStaticTable(table) {
33555
33670
  return table.type === "static" || table.type === "forceStatic";
33556
33671
  }
33557
- function getComputedTableStyle(tableConfig, numberOfCols, numberOfRows) {
33672
+ function getComputedTableStyle(tableConfig, style, numberOfCols, numberOfRows) {
33558
33673
  return {
33559
- borders: getAllTableBorders(tableConfig, numberOfCols, numberOfRows),
33560
- styles: getAllTableStyles(tableConfig, numberOfCols, numberOfRows),
33674
+ borders: getAllTableBorders(tableConfig, style, numberOfCols, numberOfRows),
33675
+ styles: getAllTableStyles(tableConfig, style, numberOfCols, numberOfRows),
33561
33676
  };
33562
33677
  }
33563
- function getAllTableBorders(tableConfig, nOfCols, nOfRows) {
33678
+ function getAllTableBorders(tableConfig, style, nOfCols, nOfRows) {
33564
33679
  const borders = generateMatrix(nOfCols, nOfRows, () => ({}));
33565
- const style = TABLE_PRESETS[tableConfig.styleId];
33566
33680
  for (const tableElement of TABLE_ELEMENTS_BY_PRIORITY) {
33567
33681
  const styleBorder = style[tableElement]?.border;
33568
33682
  if (!styleBorder)
@@ -33630,9 +33744,8 @@ function setBorderDescr(computedBorders, dir, borderDescr, col, row, numberOfCol
33630
33744
  return;
33631
33745
  }
33632
33746
  }
33633
- function getAllTableStyles(tableConfig, numberOfCols, numberOfRows) {
33747
+ function getAllTableStyles(tableConfig, style, numberOfCols, numberOfRows) {
33634
33748
  const styles = generateMatrix(numberOfCols, numberOfRows, () => ({}));
33635
- const style = TABLE_PRESETS[tableConfig.styleId];
33636
33749
  for (const tableElement of TABLE_ELEMENTS_BY_PRIORITY) {
33637
33750
  const tableElStyle = style[tableElement];
33638
33751
  const bold = isTableElementInBold(tableElement);
@@ -33726,8 +33839,25 @@ function getTableElementZones(el, tableConfig, numberOfCols, numberOfRows) {
33726
33839
  }
33727
33840
  return zones;
33728
33841
  }
33729
- function getTableStyleName(styleId, tableStyle) {
33730
- return tableStyle.colorName ? `${tableStyle.colorName}, ${styleId}` : styleId;
33842
+
33843
+ function createTableStyleContextMenuActions(env, styleId) {
33844
+ if (!env.model.getters.isTableStyleEditable(styleId)) {
33845
+ return [];
33846
+ }
33847
+ return createActions([
33848
+ {
33849
+ id: "editTableStyle",
33850
+ name: _t("Edit table style"),
33851
+ execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
33852
+ icon: "o-spreadsheet-Icon.EDIT_TABLE",
33853
+ },
33854
+ {
33855
+ id: "deleteTableStyle",
33856
+ name: _t("Delete table style"),
33857
+ execute: (env) => env.model.dispatch("REMOVE_TABLE_STYLE", { tableStyleId: styleId }),
33858
+ icon: "o-spreadsheet-Icon.DELETE_TABLE",
33859
+ },
33860
+ ]);
33731
33861
  }
33732
33862
 
33733
33863
  function drawPreviewTable(ctx, tableStyle, colWidth, rowHeight) {
@@ -33812,46 +33942,59 @@ function drawTexts(ctx, tableStyle, colWidth, rowHeight) {
33812
33942
 
33813
33943
  class TableStylePreview extends owl.Component {
33814
33944
  static template = "o-spreadsheet-TableStylePreview";
33815
- static props = { tableConfig: Object };
33945
+ static props = { tableConfig: Object, tableStyle: { type: Object, optional: true } };
33816
33946
  canvasRef = owl.useRef("canvas");
33817
33947
  setup() {
33818
33948
  owl.onWillUpdateProps((nextProps) => {
33819
- if (!deepEquals(this.props.tableConfig, nextProps.tableConfig)) {
33820
- this.drawTable(nextProps.tableConfig);
33949
+ if (!deepEquals(this.props.tableConfig, nextProps.tableConfig) ||
33950
+ !deepEquals(this.props.tableStyle, nextProps.tableStyle)) {
33951
+ this.drawTable(nextProps);
33821
33952
  }
33822
33953
  });
33823
- owl.onMounted(() => this.drawTable(this.props.tableConfig));
33954
+ owl.onMounted(() => this.drawTable(this.props));
33824
33955
  }
33825
- drawTable(tableConfig) {
33956
+ drawTable(props) {
33826
33957
  const ctx = this.canvasRef.el.getContext("2d");
33827
33958
  const { width, height } = this.canvasRef.el.getBoundingClientRect();
33828
33959
  this.canvasRef.el.width = width;
33829
33960
  this.canvasRef.el.height = height;
33830
- const tableStyle = getComputedTableStyle(tableConfig, 5, 5);
33831
- drawPreviewTable(ctx, tableStyle, (width - 1) / 5, (height - 1) / 5);
33961
+ const computedStyle = getComputedTableStyle(props.tableConfig, props.tableStyle, 5, 5);
33962
+ drawPreviewTable(ctx, computedStyle, (width - 1) / 5, (height - 1) / 5);
33832
33963
  }
33833
33964
  }
33834
33965
 
33835
33966
  css /* scss */ `
33836
33967
  .o-table-style-popover {
33837
33968
  /** 7 tables preview + padding by line */
33838
- max-width: calc((66px + 4px * 2) * 7);
33969
+ width: calc((66px + 4px * 2) * 7);
33839
33970
  background: #fff;
33840
33971
  font-size: 14px;
33972
+ user-select: none;
33973
+
33974
+ .form-check-input {
33975
+ font-size: 12px;
33976
+ }
33977
+
33841
33978
  .o-table-style-list-item {
33842
- padding: 4px;
33843
- &.selected {
33844
- padding: 3px;
33845
- }
33979
+ padding: 3px;
33980
+ }
33981
+
33982
+ .o-table-style-popover-preview {
33983
+ width: 66px;
33984
+ height: 51px;
33985
+ }
33846
33986
 
33847
- .o-table-style-popover-preview {
33848
- width: 66px;
33849
- height: 51px;
33987
+ .o-new-table-style {
33988
+ font-size: 36px;
33989
+ color: #666;
33990
+ &:hover {
33991
+ background: #f5f5f5;
33850
33992
  }
33851
33993
  }
33852
33994
  }
33853
33995
 
33854
33996
  .o-table-style-list-item {
33997
+ border: 1px solid transparent;
33855
33998
  &.selected {
33856
33999
  border: 1px solid #007eff;
33857
34000
  background: #f5f5f5;
@@ -33864,7 +34007,7 @@ css /* scss */ `
33864
34007
  `;
33865
34008
  class TableStylesPopover extends owl.Component {
33866
34009
  static template = "o-spreadsheet-TableStylesPopover";
33867
- static components = { Popover, TableStylePreview };
34010
+ static components = { Popover, TableStylePreview, Menu };
33868
34011
  static props = {
33869
34012
  tableConfig: Object,
33870
34013
  popoverProps: { type: Object, optional: true },
@@ -33872,9 +34015,10 @@ class TableStylesPopover extends owl.Component {
33872
34015
  onStylePicked: Function,
33873
34016
  selectedStyleId: { type: String, optional: true },
33874
34017
  };
33875
- stylePresets = TABLE_PRESETS;
33876
34018
  categories = TABLE_STYLE_CATEGORIES;
33877
34019
  tableStyleListRef = owl.useRef("tableStyleList");
34020
+ state = owl.useState({ selectedCategory: this.initialSelectedCategory });
34021
+ menu = owl.useState({ isOpen: false, position: null, menuItems: [] });
33878
34022
  setup() {
33879
34023
  owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
33880
34024
  }
@@ -33884,14 +34028,33 @@ class TableStylesPopover extends owl.Component {
33884
34028
  ev.hasClosedTableStylesPopover = true;
33885
34029
  }
33886
34030
  }
33887
- getPresetsByCategory(category) {
33888
- return Object.keys(this.stylePresets).filter((key) => this.stylePresets[key].category === category);
34031
+ get displayedStyles() {
34032
+ const styles = this.env.model.getters.getTableStyles();
34033
+ return Object.keys(styles).filter((styleId) => styles[styleId].category === this.state.selectedCategory);
33889
34034
  }
33890
- getTableConfig(styleId) {
33891
- return { ...this.props.tableConfig, styleId: styleId };
34035
+ get initialSelectedCategory() {
34036
+ return this.props.selectedStyleId
34037
+ ? this.env.model.getters.getTableStyle(this.props.selectedStyleId).category
34038
+ : "medium";
33892
34039
  }
33893
34040
  getStyleName(styleId) {
33894
- return getTableStyleName(styleId, TABLE_PRESETS[styleId]);
34041
+ return this.env.model.getters.getTableStyle(styleId).displayName;
34042
+ }
34043
+ newTableStyle() {
34044
+ this.props.closePopover();
34045
+ this.env.openSidePanel("TableStyleEditorPanel", {
34046
+ onStylePicked: this.props.onStylePicked,
34047
+ });
34048
+ }
34049
+ onContextMenu(event, styleId) {
34050
+ this.menu.menuItems = createTableStyleContextMenuActions(this.env, styleId);
34051
+ this.menu.isOpen = true;
34052
+ this.menu.position = { x: event.clientX, y: event.clientY };
34053
+ }
34054
+ closeMenu() {
34055
+ this.menu.isOpen = false;
34056
+ this.menu.position = null;
34057
+ this.menu.menuItems = [];
33895
34058
  }
33896
34059
  }
33897
34060
 
@@ -33911,13 +34074,9 @@ css /* scss */ `
33911
34074
  }
33912
34075
 
33913
34076
  .o-table-style-list-item {
33914
- padding: 4px;
34077
+ padding: 3px;
33915
34078
  margin: 2px 1px;
33916
34079
 
33917
- &.selected {
33918
- padding: 3px;
33919
- }
33920
-
33921
34080
  .o-table-style-picker-preview {
33922
34081
  width: 61px;
33923
34082
  height: 46px;
@@ -33927,11 +34086,14 @@ css /* scss */ `
33927
34086
  `;
33928
34087
  class TableStylePicker extends owl.Component {
33929
34088
  static template = "o-spreadsheet-TableStylePicker";
33930
- static components = { TableStylesPopover, TableStylePreview };
34089
+ static components = { TableStylesPopover, TableStylePreview, Menu };
33931
34090
  static props = { table: Object };
33932
34091
  state = owl.useState({ popoverProps: undefined });
34092
+ menu = owl.useState({ isOpen: false, position: null, menuItems: [] });
33933
34093
  getDisplayedTableStyles() {
33934
- const styles = Object.keys(TABLE_PRESETS);
34094
+ const allStyles = this.env.model.getters.getTableStyles();
34095
+ const selectedStyleCategory = allStyles[this.props.table.config.styleId].category;
34096
+ const styles = Object.keys(allStyles).filter((key) => allStyles[key].category === selectedStyleCategory);
33935
34097
  const selectedStyleIndex = styles.indexOf(this.props.table.config.styleId);
33936
34098
  if (selectedStyleIndex === -1) {
33937
34099
  return styles.slice(0, 4);
@@ -33939,9 +34101,6 @@ class TableStylePicker extends owl.Component {
33939
34101
  const index = Math.floor(selectedStyleIndex / 4) * 4;
33940
34102
  return styles.slice(index, index + 4);
33941
34103
  }
33942
- getTableConfig(styleId) {
33943
- return { ...this.props.table.config, styleId: styleId };
33944
- }
33945
34104
  onStylePicked(styleId) {
33946
34105
  const sheetId = this.env.model.getters.getActiveSheetId();
33947
34106
  this.env.model.dispatch("UPDATE_TABLE", {
@@ -33968,7 +34127,17 @@ class TableStylePicker extends owl.Component {
33968
34127
  this.state.popoverProps = undefined;
33969
34128
  }
33970
34129
  getStyleName(styleId) {
33971
- return getTableStyleName(styleId, TABLE_PRESETS[styleId]);
34130
+ return this.env.model.getters.getTableStyle(styleId).displayName;
34131
+ }
34132
+ onContextMenu(event, styleId) {
34133
+ this.menu.menuItems = createTableStyleContextMenuActions(this.env, styleId);
34134
+ this.menu.isOpen = true;
34135
+ this.menu.position = { x: event.clientX, y: event.clientY };
34136
+ }
34137
+ closeMenu() {
34138
+ this.menu.isOpen = false;
34139
+ this.menu.position = null;
34140
+ this.menu.menuItems = [];
33972
34141
  }
33973
34142
  }
33974
34143
 
@@ -34158,6 +34327,103 @@ class TablePanel extends owl.Component {
34158
34327
  }
34159
34328
  }
34160
34329
 
34330
+ css /* scss */ `
34331
+ .o-table-style-editor-panel {
34332
+ .o-color-preview {
34333
+ width: 30px;
34334
+ height: 15px;
34335
+ margin-left: 2px;
34336
+ outline: 1px solid #3d85c6;
34337
+ outline-offset: 1px;
34338
+ margin-right: 10px;
34339
+
34340
+ cursor: pointer;
34341
+ }
34342
+
34343
+ .o-table-style-list-item {
34344
+ margin: 1px 3px;
34345
+ padding: 3px 6px;
34346
+
34347
+ .o-table-style-edit-template-preview {
34348
+ width: 81px;
34349
+ height: 61px;
34350
+ }
34351
+ }
34352
+ }
34353
+ `;
34354
+ class TableStyleEditorPanel extends owl.Component {
34355
+ static template = "o-spreadsheet-TableStyleEditorPanel";
34356
+ static components = { Section, ColorPickerWidget, TableStylePreview };
34357
+ static props = {
34358
+ onCloseSidePanel: Function,
34359
+ onStylePicked: { type: Function, optional: true },
34360
+ styleId: { type: String, optional: true },
34361
+ };
34362
+ state = owl.useState(this.getInitialState());
34363
+ setup() {
34364
+ owl.useExternalListener(window, "click", () => (this.state.pickerOpened = false));
34365
+ }
34366
+ getInitialState() {
34367
+ const editedStyle = this.props.styleId
34368
+ ? this.env.model.getters.getTableStyle(this.props.styleId)
34369
+ : null;
34370
+ return {
34371
+ pickerOpened: false,
34372
+ primaryColor: editedStyle?.primaryColor || "#3C78D8",
34373
+ selectedTemplateName: editedStyle?.templateName || "lightColoredText",
34374
+ styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
34375
+ };
34376
+ }
34377
+ togglePicker() {
34378
+ this.state.pickerOpened = !this.state.pickerOpened;
34379
+ }
34380
+ onColorPicked(color) {
34381
+ this.state.primaryColor = color;
34382
+ this.state.pickerOpened = false;
34383
+ }
34384
+ onTemplatePicked(templateName) {
34385
+ this.state.selectedTemplateName = templateName;
34386
+ }
34387
+ onConfirm() {
34388
+ const tableStyleId = this.props.styleId || this.env.model.uuidGenerator.uuidv4();
34389
+ this.env.model.dispatch("CREATE_TABLE_STYLE", {
34390
+ tableStyleId,
34391
+ tableStyleName: this.state.styleName,
34392
+ templateName: this.state.selectedTemplateName,
34393
+ primaryColor: this.state.primaryColor,
34394
+ });
34395
+ this.props.onStylePicked?.(tableStyleId);
34396
+ this.props.onCloseSidePanel();
34397
+ }
34398
+ onCancel() {
34399
+ this.props.onCloseSidePanel();
34400
+ }
34401
+ get colorPreviewStyle() {
34402
+ return cssPropertiesToCss({ background: this.state.primaryColor });
34403
+ }
34404
+ get tableTemplates() {
34405
+ return Object.keys(TABLE_STYLES_TEMPLATES).filter((templateName) => templateName !== "none");
34406
+ }
34407
+ get previewTableConfig() {
34408
+ return {
34409
+ bandedColumns: false,
34410
+ bandedRows: true,
34411
+ firstColumn: false,
34412
+ lastColumn: false,
34413
+ numberOfHeaders: 1,
34414
+ totalRow: true,
34415
+ hasFilters: true,
34416
+ styleId: "",
34417
+ };
34418
+ }
34419
+ get selectedStyle() {
34420
+ return this.computeTableStyle(this.state.selectedTemplateName);
34421
+ }
34422
+ computeTableStyle(templateName) {
34423
+ return buildTableStyle(this.state.styleName, templateName, this.state.primaryColor);
34424
+ }
34425
+ }
34426
+
34161
34427
  const sidePanelRegistry = new Registry();
34162
34428
 
34163
34429
  //------------------------------------------------------------------------------
@@ -34222,6 +34488,17 @@ sidePanelRegistry.add("TableSidePanel", {
34222
34488
  return { isOpen: true, props: { table: coreTable }, key: table.id };
34223
34489
  },
34224
34490
  });
34491
+ sidePanelRegistry.add("TableStyleEditorPanel", {
34492
+ title: _t("Create custom table style"),
34493
+ Body: TableStyleEditorPanel,
34494
+ computeState: (getters, initialProps) => {
34495
+ return {
34496
+ isOpen: true,
34497
+ props: { ...initialProps },
34498
+ key: initialProps.styleId ?? "new",
34499
+ };
34500
+ },
34501
+ });
34225
34502
 
34226
34503
  class TopBarComponentRegistry extends Registry {
34227
34504
  mapping = {};
@@ -36307,6 +36584,9 @@ css /* scss */ `
36307
36584
  height: 10000px;
36308
36585
  background-color: ${SELECTION_BORDER_COLOR};
36309
36586
  }
36587
+ .o-unhide {
36588
+ color: ${ICONS_COLOR};
36589
+ }
36310
36590
  .o-unhide:hover {
36311
36591
  z-index: ${ComponentsImportance.Grid + 1};
36312
36592
  background-color: lightgrey;
@@ -36468,6 +36748,9 @@ css /* scss */ `
36468
36748
  height: 1px;
36469
36749
  background-color: ${SELECTION_BORDER_COLOR};
36470
36750
  }
36751
+ .o-unhide {
36752
+ color: ${ICONS_COLOR};
36753
+ }
36471
36754
  .o-unhide:hover {
36472
36755
  z-index: ${ComponentsImportance.Grid + 1};
36473
36756
  background-color: lightgrey;
@@ -39610,6 +39893,13 @@ function hexaToInt(hex) {
39610
39893
  }
39611
39894
  return parseInt(hex.replace("#", ""), 16);
39612
39895
  }
39896
+ /**
39897
+ * When defining style (fontColor, borderColor for instance)
39898
+ * Excel will specify rgb="FF000000"
39899
+ * In that case, We should not consider this value as user-defined but
39900
+ * rather like an instruction: "Use your system default"
39901
+ */
39902
+ const DEFAULT_SYSTEM_COLOR = "FF000000";
39613
39903
 
39614
39904
  /**
39615
39905
  * Get the relative path between two files
@@ -39648,18 +39938,6 @@ function arrayToObject(array, indexOffset = 0) {
39648
39938
  }
39649
39939
  return obj;
39650
39940
  }
39651
- /**
39652
- * Convert an object whose keys are numbers to an array were the element index was their key in the object.
39653
- *
39654
- * eg. : {0:"a", 2:"b"} => ["a", undefined, "b"]
39655
- */
39656
- function objectToArray(obj) {
39657
- const arr = [];
39658
- for (let key of Object.keys(obj).map(Number)) {
39659
- arr[key] = obj[key];
39660
- }
39661
- return arr;
39662
- }
39663
39941
  /**
39664
39942
  * In xlsx we can have string with unicode characters with the format _x00fa_.
39665
39943
  * Replace with characters understandable by JS
@@ -40128,7 +40406,7 @@ function extractStyle(cell, data) {
40128
40406
  vertical: style.verticalAlign
40129
40407
  ? V_ALIGNMENT_EXPORT_CONVERSION_MAP[style.verticalAlign]
40130
40408
  : undefined,
40131
- wrapText: style.wrapping === "wrap",
40409
+ wrapText: style.wrapping === "wrap" || undefined,
40132
40410
  },
40133
40411
  };
40134
40412
  styles.font["strike"] = !!style?.strikethrough || undefined;
@@ -40732,128 +41010,54 @@ function getHeader(sheet, dim, index) {
40732
41010
  : sheet.rows.find((row) => row.index === index);
40733
41011
  }
40734
41012
 
40735
- const TABLE_HEADER_STYLE = {
40736
- fillColor: "#000000",
40737
- textColor: "#ffffff",
40738
- bold: true,
40739
- };
40740
- const TABLE_HIGHLIGHTED_CELL_STYLE = {
40741
- bold: true,
40742
- };
40743
- const TABLE_BORDER_STYLE = { style: "thin", color: "#000000FF" };
40744
41013
  /**
40745
- * Convert the imported XLSX tables.
40746
- *
40747
- * We will create a Table if the imported table have filters, then apply a style in all the cells of the table
40748
- * and convert the table-specific formula references into standard references.
41014
+ * Convert the imported XLSX tables and pivots convert the table-specific formula references into standard references.
40749
41015
  *
40750
41016
  * Change the converted data in-place.
40751
41017
  */
40752
41018
  function convertTables(convertedData, xlsxData) {
40753
41019
  for (const xlsxSheet of xlsxData.sheets) {
41020
+ const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
41021
+ if (!sheet)
41022
+ continue;
41023
+ if (!sheet.tables)
41024
+ sheet.tables = [];
40754
41025
  for (const table of xlsxSheet.tables) {
40755
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
40756
- if (!sheet || !table.autoFilter)
40757
- continue;
40758
- if (!sheet.tables)
40759
- sheet.tables = [];
40760
- sheet.tables.push({ range: table.ref });
41026
+ sheet.tables.push({ range: table.ref, config: convertTableConfig(table) });
41027
+ }
41028
+ for (const pivotTable of xlsxSheet.pivotTables) {
41029
+ sheet.tables.push({
41030
+ range: pivotTable.location.ref,
41031
+ config: convertPivotTableConfig(pivotTable),
41032
+ });
40761
41033
  }
40762
41034
  }
40763
- applyTableStyle(convertedData, xlsxData);
40764
41035
  convertTableFormulaReferences(convertedData.sheets, xlsxData.sheets);
40765
41036
  }
40766
- /**
40767
- * Apply a style to all the cells that are in a table, and add the created styles in the converted data.
40768
- *
40769
- * In XLSXs, the style of the cells of a table are not directly in the sheet, but rather deduced from the style of
40770
- * the table that is defined in the table's XML file. The style of the table is a string referencing a standard style
40771
- * defined in the OpenXML specifications. As there are 80+ different styles, we won't implement every one of them but
40772
- * we will just define a style that will be used for all the imported tables.
40773
- */
40774
- function applyTableStyle(convertedData, xlsxData) {
40775
- const styles = objectToArray(convertedData.styles);
40776
- const borders = objectToArray(convertedData.borders);
40777
- for (let xlsxSheet of xlsxData.sheets) {
40778
- for (let table of xlsxSheet.tables) {
40779
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
40780
- if (!sheet)
40781
- continue;
40782
- const tableZone = toZone(table.ref);
40783
- // Table style
40784
- for (let i = 0; i < table.headerRowCount; i++) {
40785
- applyStyleToZone(TABLE_HEADER_STYLE, { ...tableZone, bottom: tableZone.top + i }, sheet.cells, styles);
40786
- }
40787
- for (let i = 0; i < table.totalsRowCount; i++) {
40788
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, top: tableZone.bottom - i }, sheet.cells, styles);
40789
- }
40790
- if (table.style?.showFirstColumn) {
40791
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, right: tableZone.left }, sheet.cells, styles);
40792
- }
40793
- if (table.style?.showLastColumn) {
40794
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, left: tableZone.right }, sheet.cells, styles);
40795
- }
40796
- // Table borders
40797
- // Borders at : table outline + col(/row) if showColumnStripes(/showRowStripes) + border above totalRow
40798
- for (let col = tableZone.left; col <= tableZone.right; col++) {
40799
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
40800
- const xc = toXC(col, row);
40801
- const cell = sheet.cells[xc];
40802
- const border = {
40803
- left: col === tableZone.left || table.style?.showColumnStripes
40804
- ? TABLE_BORDER_STYLE
40805
- : undefined,
40806
- right: col === tableZone.right ? TABLE_BORDER_STYLE : undefined,
40807
- top: row === tableZone.top ||
40808
- table.style?.showRowStripes ||
40809
- row > tableZone.bottom - table.totalsRowCount
40810
- ? TABLE_BORDER_STYLE
40811
- : undefined,
40812
- bottom: row === tableZone.bottom ? TABLE_BORDER_STYLE : undefined,
40813
- };
40814
- const newBorder = cell?.border ? { ...borders[cell.border], ...border } : border;
40815
- let borderIndex = borders.findIndex((border) => deepEquals(border, newBorder));
40816
- if (borderIndex === -1) {
40817
- borderIndex = borders.length;
40818
- borders.push(newBorder);
40819
- }
40820
- if (cell) {
40821
- cell.border = borderIndex;
40822
- }
40823
- else {
40824
- sheet.cells[xc] = { border: borderIndex };
40825
- }
40826
- }
40827
- }
40828
- }
40829
- }
40830
- convertedData.styles = arrayToObject(styles);
40831
- convertedData.borders = arrayToObject(borders);
41037
+ function convertTableConfig(table) {
41038
+ const styleId = table.style?.name || "";
41039
+ return {
41040
+ hasFilters: table.autoFilter !== undefined,
41041
+ numberOfHeaders: table.headerRowCount,
41042
+ totalRow: table.totalsRowCount > 0,
41043
+ firstColumn: table.style?.showFirstColumn || false,
41044
+ lastColumn: table.style?.showLastColumn || false,
41045
+ bandedRows: table.style?.showRowStripes || false,
41046
+ bandedColumns: table.style?.showColumnStripes || false,
41047
+ styleId: TABLE_PRESETS[styleId] ? styleId : DEFAULT_TABLE_CONFIG.styleId,
41048
+ };
40832
41049
  }
40833
- /**
40834
- * Apply a style to all the cells in the zone. The applied style WILL NOT overwrite values in existing style of the cell.
40835
- *
40836
- * If a style that was not in the styles array was applied, push it into the style array.
40837
- */
40838
- function applyStyleToZone(appliedStyle, zone, cells, styles) {
40839
- for (let col = zone.left; col <= zone.right; col++) {
40840
- for (let row = zone.top; row <= zone.bottom; row++) {
40841
- const xc = toXC(col, row);
40842
- const cell = cells[xc];
40843
- const newStyle = cell?.style ? { ...styles[cell.style], ...appliedStyle } : appliedStyle;
40844
- let styleIndex = styles.findIndex((style) => deepEquals(style, newStyle));
40845
- if (styleIndex === -1) {
40846
- styleIndex = styles.length;
40847
- styles.push(newStyle);
40848
- }
40849
- if (cell) {
40850
- cell.style = styleIndex;
40851
- }
40852
- else {
40853
- cells[xc] = { style: styleIndex };
40854
- }
40855
- }
40856
- }
41050
+ function convertPivotTableConfig(pivotTable) {
41051
+ return {
41052
+ hasFilters: false,
41053
+ numberOfHeaders: pivotTable.location.firstDataRow,
41054
+ totalRow: pivotTable.rowGrandTotals,
41055
+ firstColumn: true,
41056
+ lastColumn: pivotTable.style?.showLastColumn || false,
41057
+ bandedRows: pivotTable.style?.showRowStripes || false,
41058
+ bandedColumns: pivotTable.style?.showColStripes || false,
41059
+ styleId: DEFAULT_TABLE_CONFIG.styleId,
41060
+ };
40857
41061
  }
40858
41062
  /**
40859
41063
  * In all the sheets, replace the table-only references in the formula cells with standard references.
@@ -41022,7 +41226,7 @@ function getDefaultXLSXStructure(data) {
41022
41226
  fillId: 0,
41023
41227
  numFmtId: 0,
41024
41228
  borderId: 0,
41025
- alignment: { vertical: "bottom" },
41229
+ alignment: {},
41026
41230
  },
41027
41231
  ],
41028
41232
  fonts: [
@@ -41030,7 +41234,7 @@ function getDefaultXLSXStructure(data) {
41030
41234
  size: DEFAULT_FONT_SIZE,
41031
41235
  family: 2,
41032
41236
  color: { rgb: "000000" },
41033
- name: "Calibri",
41237
+ name: "Arial",
41034
41238
  },
41035
41239
  ],
41036
41240
  fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
@@ -41322,9 +41526,10 @@ class XlsxBaseExtractor {
41322
41526
  }
41323
41527
  else {
41324
41528
  rgb = this.extractAttr(colorElement, "rgb")?.asString();
41529
+ rgb = rgb === DEFAULT_SYSTEM_COLOR ? undefined : rgb;
41325
41530
  }
41326
41531
  const color = {
41327
- rgb,
41532
+ rgb: rgb || defaultColor,
41328
41533
  auto: this.extractAttr(colorElement, "auto")?.asBool(),
41329
41534
  indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
41330
41535
  tint: this.extractAttr(colorElement, "tint")?.asNum(),
@@ -41761,20 +41966,48 @@ class XlsxPivotExtractor extends XlsxBaseExtractor {
41761
41966
  // pivotTableDefinition elements.
41762
41967
  { query: ":root", parent: this.rootFile.file.xml }, (pivotElement) => {
41763
41968
  return {
41764
- displayName: this.extractAttr(pivotElement, "name", { required: true }).asString(),
41765
- id: this.extractAttr(pivotElement, "name", { required: true }).asString(),
41766
- ref: this.extractChildAttr(pivotElement, "location", "ref", {
41969
+ name: this.extractAttr(pivotElement, "name", { required: true }).asString(),
41970
+ rowGrandTotals: this.extractAttr(pivotElement, "rowGrandTotals", {
41971
+ default: true,
41972
+ }).asBool(),
41973
+ location: this.extractPivotLocation(pivotElement),
41974
+ style: this.extractPivotStyleInfo(pivotElement),
41975
+ };
41976
+ })[0];
41977
+ }
41978
+ extractPivotLocation(pivotElement) {
41979
+ return this.mapOnElements({ query: "location", parent: pivotElement }, (pivotStyleElement) => {
41980
+ return {
41981
+ ref: this.extractAttr(pivotStyleElement, "ref", { required: true }).asString(),
41982
+ firstHeaderRow: this.extractAttr(pivotStyleElement, "firstHeaderRow", {
41983
+ required: true,
41984
+ }).asNum(),
41985
+ firstDataRow: this.extractAttr(pivotStyleElement, "firstDataRow", {
41986
+ required: true,
41987
+ }).asNum(),
41988
+ firstDataCol: this.extractAttr(pivotStyleElement, "firstDataCol", {
41767
41989
  required: true,
41768
- }).asString(),
41769
- headerRowCount: this.extractChildAttr(pivotElement, "location", "firstDataRow", {
41770
- default: 0,
41771
41990
  }).asNum(),
41772
- totalsRowCount: 1,
41773
- cols: [],
41774
- style: {
41775
- showFirstColumn: true,
41776
- showRowStripes: true,
41777
- },
41991
+ };
41992
+ })[0];
41993
+ }
41994
+ extractPivotStyleInfo(pivotElement) {
41995
+ return this.mapOnElements({ query: "pivotTableStyleInfo", parent: pivotElement }, (pivotStyleElement) => {
41996
+ return {
41997
+ name: this.extractAttr(pivotStyleElement, "name", { required: true }).asString(),
41998
+ showRowHeaders: this.extractAttr(pivotStyleElement, "showRowHeaders", {
41999
+ required: true,
42000
+ }).asBool(),
42001
+ showColHeaders: this.extractAttr(pivotStyleElement, "showColHeaders", {
42002
+ required: true,
42003
+ }).asBool(),
42004
+ showRowStripes: this.extractAttr(pivotStyleElement, "showRowStripes", {
42005
+ required: true,
42006
+ }).asBool(),
42007
+ showColStripes: this.extractAttr(pivotStyleElement, "showColStripes", {
42008
+ required: true,
42009
+ }).asBool(),
42010
+ showLastColumn: this.extractAttr(pivotStyleElement, "showLastColumn")?.asBool(),
41778
42011
  };
41779
42012
  })[0];
41780
42013
  }
@@ -41871,7 +42104,8 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
41871
42104
  cfs: this.extractConditionalFormats(),
41872
42105
  figures: this.extractFigures(sheetElement),
41873
42106
  hyperlinks: this.extractHyperLinks(sheetElement),
41874
- tables: [...this.extractTables(sheetElement), ...this.extractPivotTables()],
42107
+ tables: this.extractTables(sheetElement),
42108
+ pivotTables: this.extractPivotTables(),
41875
42109
  isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
41876
42110
  };
41877
42111
  })[0];
@@ -42481,6 +42715,8 @@ function load(data, verboseImport) {
42481
42715
  if (!data) {
42482
42716
  return createEmptyWorkbookData();
42483
42717
  }
42718
+ console.group("Loading data");
42719
+ const start = performance.now();
42484
42720
  if (data["[Content_Types].xml"]) {
42485
42721
  const reader = new XlsxReader(data);
42486
42722
  data = reader.convertXlsx();
@@ -42493,17 +42729,22 @@ function load(data, verboseImport) {
42493
42729
  // apply migrations, if needed
42494
42730
  if ("version" in data) {
42495
42731
  if (data.version < CURRENT_VERSION) {
42732
+ console.info("Migrating data from version", data.version);
42496
42733
  data = migrate(data);
42497
42734
  }
42498
42735
  }
42499
42736
  data = repairData(data);
42737
+ console.info("Data loaded in", performance.now() - start, "ms");
42738
+ console.groupEnd();
42500
42739
  return data;
42501
42740
  }
42502
42741
  function migrate(data) {
42742
+ const start = performance.now();
42503
42743
  const index = MIGRATIONS.findIndex((m) => m.from === data.version);
42504
42744
  for (let i = index; i < MIGRATIONS.length; i++) {
42505
42745
  data = MIGRATIONS[i].applyMigration(data);
42506
42746
  }
42747
+ console.info("Data migrated in", performance.now() - start, "ms");
42507
42748
  return data;
42508
42749
  }
42509
42750
  const MIGRATIONS = [
@@ -43015,6 +43256,7 @@ function createEmptyWorkbookData(sheetName = "Sheet1") {
43015
43256
  settings: { locale: DEFAULT_LOCALE },
43016
43257
  pivots: {},
43017
43258
  pivotNextId: 1,
43259
+ customTableStyles: {},
43018
43260
  };
43019
43261
  return data;
43020
43262
  }
@@ -43223,7 +43465,7 @@ class BordersPlugin extends CorePlugin {
43223
43465
  this.clearBorders(cmd.sheetId, cmd.target);
43224
43466
  break;
43225
43467
  case "REMOVE_COLUMNS_ROWS":
43226
- for (let el of cmd.elements) {
43468
+ for (let el of [...cmd.elements].sort((a, b) => b - a)) {
43227
43469
  if (cmd.dimension === "COL") {
43228
43470
  this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
43229
43471
  }
@@ -47491,10 +47733,9 @@ class TablePlugin extends CorePlugin {
47491
47733
  for (const tableId in tables) {
47492
47734
  const table = tables[tableId];
47493
47735
  if (table && cmd.target.some((zone) => isZoneInside(table.range.zone, zone))) {
47494
- delete tables[tableId];
47736
+ this.dispatch("REMOVE_TABLE", { sheetId: cmd.sheetId, target: [table.range.zone] });
47495
47737
  }
47496
47738
  }
47497
- this.history.update("tables", cmd.sheetId, tables);
47498
47739
  break;
47499
47740
  }
47500
47741
  }
@@ -47591,9 +47832,6 @@ class TablePlugin extends CorePlugin {
47591
47832
  if (config.numberOfHeaders !== undefined && config.numberOfHeaders < 0) {
47592
47833
  return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47593
47834
  }
47594
- if (config.styleId && !TABLE_PRESETS[config.styleId]) {
47595
- return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47596
- }
47597
47835
  if (config.hasFilters && config.numberOfHeaders === 0) {
47598
47836
  return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
47599
47837
  }
@@ -47811,7 +48049,12 @@ class TablePlugin extends CorePlugin {
47811
48049
  }
47812
48050
  }
47813
48051
  exportForExcel(data) {
47814
- this.export(data);
48052
+ for (const sheet of data.sheets) {
48053
+ for (const table of this.getCoreTables(sheet.id)) {
48054
+ const range = zoneToXc(table.range.zone);
48055
+ sheet.tables.push({ range, filters: [], config: table.config });
48056
+ }
48057
+ }
47815
48058
  }
47816
48059
  }
47817
48060
 
@@ -48682,6 +48925,108 @@ class SettingsPlugin extends CorePlugin {
48682
48925
  }
48683
48926
  }
48684
48927
 
48928
+ class TableStylePlugin extends CorePlugin {
48929
+ static getters = [
48930
+ "getNewCustomTableStyleName",
48931
+ "getTableStyle",
48932
+ "getTableStyles",
48933
+ "isTableStyleEditable",
48934
+ ];
48935
+ styles = {};
48936
+ allowDispatch(cmd) {
48937
+ switch (cmd.type) {
48938
+ case "CREATE_TABLE":
48939
+ case "UPDATE_TABLE":
48940
+ if (cmd.config?.styleId && !this.styles[cmd.config.styleId]) {
48941
+ return "InvalidTableConfig" /* CommandResult.InvalidTableConfig */;
48942
+ }
48943
+ break;
48944
+ case "CREATE_TABLE_STYLE":
48945
+ if (!TABLE_STYLES_TEMPLATES[cmd.templateName]) {
48946
+ return "InvalidTableStyle" /* CommandResult.InvalidTableStyle */;
48947
+ }
48948
+ try {
48949
+ toHex(cmd.primaryColor);
48950
+ }
48951
+ catch (e) {
48952
+ return "InvalidTableStyle" /* CommandResult.InvalidTableStyle */;
48953
+ }
48954
+ break;
48955
+ }
48956
+ return "Success" /* CommandResult.Success */;
48957
+ }
48958
+ handle(cmd) {
48959
+ switch (cmd.type) {
48960
+ case "CREATE_TABLE_STYLE":
48961
+ const style = buildTableStyle(cmd.tableStyleName, cmd.templateName, cmd.primaryColor);
48962
+ this.history.update("styles", cmd.tableStyleId, style);
48963
+ break;
48964
+ case "REMOVE_TABLE_STYLE":
48965
+ const styles = { ...this.styles };
48966
+ delete styles[cmd.tableStyleId];
48967
+ this.history.update("styles", styles);
48968
+ for (const sheetId of this.getters.getSheetIds()) {
48969
+ for (const table of this.getters.getCoreTables(sheetId)) {
48970
+ if (table.config.styleId === cmd.tableStyleId) {
48971
+ this.dispatch("UPDATE_TABLE", {
48972
+ sheetId,
48973
+ zone: table.range.zone,
48974
+ config: { styleId: DEFAULT_TABLE_CONFIG.styleId },
48975
+ });
48976
+ }
48977
+ }
48978
+ }
48979
+ break;
48980
+ }
48981
+ }
48982
+ getTableStyle(styleId) {
48983
+ if (!this.styles[styleId]) {
48984
+ throw new Error(`Table style ${styleId} does not exist`);
48985
+ }
48986
+ return this.styles[styleId];
48987
+ }
48988
+ getTableStyles() {
48989
+ return this.styles;
48990
+ }
48991
+ getNewCustomTableStyleName() {
48992
+ let name = _t("Custom Table Style");
48993
+ const styleNames = new Set(Object.values(this.styles).map((style) => style.displayName));
48994
+ if (!styleNames.has(name)) {
48995
+ return name;
48996
+ }
48997
+ let i = 2;
48998
+ while (styleNames.has(`${name} ${i}`)) {
48999
+ i++;
49000
+ }
49001
+ return `${name} ${i}`;
49002
+ }
49003
+ isTableStyleEditable(styleId) {
49004
+ return !TABLE_PRESETS[styleId];
49005
+ }
49006
+ import(data) {
49007
+ for (const presetStyleId in TABLE_PRESETS) {
49008
+ this.styles[presetStyleId] = TABLE_PRESETS[presetStyleId];
49009
+ }
49010
+ for (const styleId in data.customTableStyles) {
49011
+ const styleData = data.customTableStyles[styleId];
49012
+ this.styles[styleId] = buildTableStyle(styleData.displayName, styleData.templateName, styleData.primaryColor);
49013
+ }
49014
+ }
49015
+ export(data) {
49016
+ const exportedStyles = {};
49017
+ for (const styleId in this.styles) {
49018
+ if (!TABLE_PRESETS[styleId]) {
49019
+ exportedStyles[styleId] = {
49020
+ displayName: this.styles[styleId].displayName,
49021
+ templateName: this.styles[styleId].templateName,
49022
+ primaryColor: this.styles[styleId].primaryColor,
49023
+ };
49024
+ }
49025
+ }
49026
+ data.customTableStyles = exportedStyles;
49027
+ }
49028
+ }
49029
+
48685
49030
  /**
48686
49031
  * UI plugins handle any transient data required to display a spreadsheet.
48687
49032
  * They can draw on the grid canvas.
@@ -48742,19 +49087,17 @@ class CompilationParametersBuilder {
48742
49087
  * function for which this parameter is used, we just return the string of the parameter.
48743
49088
  * The `compute` of the formula's function must process it completely
48744
49089
  */
48745
- refFn(range, isMeta, functionName, paramNumber) {
48746
- this.assertRangeValid(range);
49090
+ refFn(range, isMeta) {
49091
+ const rangeError = this.getRangeError(range);
49092
+ if (rangeError) {
49093
+ return rangeError;
49094
+ }
48747
49095
  if (isMeta) {
48748
49096
  // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
48749
49097
  const sheetName = this.getters.getSheetName(range.sheetId);
48750
49098
  return { value: getFullReference(sheetName, zoneToXc(range.zone)) };
48751
49099
  }
48752
- // if the formula definition could have accepted a range, we would pass through the _range function and not here
48753
- if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {
48754
- throw new EvaluationError(paramNumber
48755
- ? _t("Function %s expects the parameter %s to be a single value or a single cell reference, not a range.", functionName.toString(), paramNumber.toString())
48756
- : _t("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
48757
- }
49100
+ // the compiler guarantees only single cell ranges reach this part of the code
48758
49101
  const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
48759
49102
  return this.computeCell(position);
48760
49103
  }
@@ -48767,7 +49110,10 @@ class CompilationParametersBuilder {
48767
49110
  * that are actually present in the grid.
48768
49111
  */
48769
49112
  range(range) {
48770
- this.assertRangeValid(range);
49113
+ const rangeError = this.getRangeError(range);
49114
+ if (rangeError) {
49115
+ return [[rangeError]];
49116
+ }
48771
49117
  const sheetId = range.sheetId;
48772
49118
  const zone = range.zone;
48773
49119
  // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
@@ -48797,13 +49143,14 @@ class CompilationParametersBuilder {
48797
49143
  this.rangeCache[cacheKey] = matrix;
48798
49144
  return matrix;
48799
49145
  }
48800
- assertRangeValid(range) {
49146
+ getRangeError(range) {
48801
49147
  if (!isZoneValid(range.zone)) {
48802
- throw new InvalidReferenceError();
49148
+ return new InvalidReferenceError();
48803
49149
  }
48804
49150
  if (range.invalidSheetName) {
48805
- throw new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
49151
+ return new EvaluationError(_t("Invalid sheet name: %s", range.invalidSheetName));
48806
49152
  }
49153
+ return undefined;
48807
49154
  }
48808
49155
  }
48809
49156
 
@@ -49605,7 +49952,7 @@ class FormulaDependencyGraph {
49605
49952
  }
49606
49953
  }
49607
49954
  /**
49608
- * Return the cell and all cells that depend on it,
49955
+ * Return all the cells that depend on the provided ranges,
49609
49956
  * in the correct order they should be evaluated.
49610
49957
  * This is called a topological ordering (excluding cycles)
49611
49958
  */
@@ -49616,11 +49963,19 @@ class FormulaDependencyGraph {
49616
49963
  const range = queue.pop();
49617
49964
  visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
49618
49965
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
49966
+ const nextInQueue = {};
49619
49967
  for (const position of impactedPositions) {
49620
49968
  if (!visited.has(position)) {
49621
- queue.push({ sheetId: position.sheetId, zone: positionToZone(position) });
49969
+ if (!nextInQueue[position.sheetId]) {
49970
+ nextInQueue[position.sheetId] = [];
49971
+ }
49972
+ nextInQueue[position.sheetId].push(positionToZone(position));
49622
49973
  }
49623
49974
  }
49975
+ for (const sheetId in nextInQueue) {
49976
+ const zones = recomputeZones(nextInQueue[sheetId], []);
49977
+ queue.push(...zones.map((zone) => ({ sheetId, zone })));
49978
+ }
49624
49979
  }
49625
49980
  visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
49626
49981
  return visited;
@@ -49944,6 +50299,7 @@ class Evaluator {
49944
50299
  return new PositionSet(sheetSizes);
49945
50300
  }
49946
50301
  evaluateCells(positions) {
50302
+ const start = performance.now();
49947
50303
  const cellsToCompute = this.createEmptyPositionSet();
49948
50304
  cellsToCompute.addMany(positions);
49949
50305
  const arrayFormulasPositions = this.getArrayFormulasImpactedByChangesOf(positions);
@@ -49951,6 +50307,7 @@ class Evaluator {
49951
50307
  cellsToCompute.addMany(arrayFormulasPositions);
49952
50308
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
49953
50309
  this.evaluate(cellsToCompute);
50310
+ console.info("evaluate Cells", performance.now() - start, "ms");
49954
50311
  }
49955
50312
  getArrayFormulasImpactedByChangesOf(positions) {
49956
50313
  const impactedPositions = this.createEmptyPositionSet();
@@ -49963,7 +50320,7 @@ class Evaluator {
49963
50320
  }
49964
50321
  if (!content) {
49965
50322
  // The previous content could have blocked some array formulas
49966
- impactedPositions.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(position));
50323
+ impactedPositions.addMany(this.getArrayFormulasBlockedBy(position));
49967
50324
  }
49968
50325
  }
49969
50326
  return impactedPositions;
@@ -49985,8 +50342,10 @@ class Evaluator {
49985
50342
  });
49986
50343
  }
49987
50344
  evaluateAllCells() {
50345
+ const start = performance.now();
49988
50346
  this.evaluatedCells = new PositionMap();
49989
50347
  this.evaluate(this.getAllCells());
50348
+ console.info("evaluate all cells", performance.now() - start, "ms");
49990
50349
  }
49991
50350
  evaluateFormula(sheetId, formulaString) {
49992
50351
  const compiledFormula = compile(formulaString);
@@ -50006,14 +50365,23 @@ class Evaluator {
50006
50365
  positions.fillAllPositions();
50007
50366
  return positions;
50008
50367
  }
50009
- getArrayFormulasBlockedByOrSpreadingOn(position) {
50368
+ /**
50369
+ * Return the position of formulas blocked by the given position
50370
+ * as well as all their dependencies.
50371
+ */
50372
+ getArrayFormulasBlockedBy(position) {
50010
50373
  if (!this.spreadingRelations.hasArrayFormulaResult(position)) {
50011
50374
  return [];
50012
50375
  }
50013
50376
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(position);
50014
50377
  const positions = this.createEmptyPositionSet();
50015
50378
  positions.addMany(arrayFormulas);
50016
- positions.addMany(this.getCellsDependingOn(arrayFormulas));
50379
+ const arrayFormulaPosition = this.getArrayFormulaSpreadingOn(position);
50380
+ if (arrayFormulaPosition) {
50381
+ // ignore the formula spreading on the position. Keep only the blocked ones
50382
+ positions.delete(arrayFormulaPosition);
50383
+ }
50384
+ positions.addMany(this.getCellsDependingOn(positions));
50017
50385
  return positions;
50018
50386
  }
50019
50387
  nextPositionsToUpdate = new PositionSet({});
@@ -50154,7 +50522,7 @@ class Evaluator {
50154
50522
  }
50155
50523
  this.evaluatedCells.delete(child);
50156
50524
  this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
50157
- this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
50525
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedBy(child));
50158
50526
  }
50159
50527
  }
50160
50528
  // ----------------------------------------------------------
@@ -50659,7 +51027,7 @@ class CustomColorsPlugin extends UIPlugin {
50659
51027
  const tables = this.getters.getTables(sheetId);
50660
51028
  return tables.flatMap((table) => {
50661
51029
  const config = table.config;
50662
- const style = TABLE_PRESETS[config.styleId];
51030
+ const style = this.getters.getTableStyle(config.styleId);
50663
51031
  return [
50664
51032
  this.getTableStyleElementColors(style.wholeTable),
50665
51033
  config.numberOfHeaders > 0 ? this.getTableStyleElementColors(style.headerRow) : [],
@@ -52601,6 +52969,7 @@ otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["FREEZE_COLUMNS", "FREEZE_ROWS
52601
52969
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["FREEZE_COLUMNS", "FREEZE_ROWS"], freezeTransformation);
52602
52970
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["UPDATE_TABLE"], updateTableTransformation);
52603
52971
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["UPDATE_TABLE"], updateTableTransformation);
52972
+ otRegistry.addTransformation("REMOVE_TABLE_STYLE", ["CREATE_TABLE", "UPDATE_TABLE"], removeTableStyleTransform);
52604
52973
  otRegistry.addTransformation("ADD_COLUMNS_ROWS", ["GROUP_HEADERS", "UNGROUP_HEADERS", "FOLD_HEADER_GROUP", "UNFOLD_HEADER_GROUP"], groupHeadersTransformation);
52605
52974
  otRegistry.addTransformation("REMOVE_COLUMNS_ROWS", ["GROUP_HEADERS", "UNGROUP_HEADERS", "FOLD_HEADER_GROUP", "UNFOLD_HEADER_GROUP"], groupHeadersTransformation);
52606
52975
  otRegistry.addTransformation("REMOVE_PIVOT", ["RENAME_PIVOT", "DUPLICATE_PIVOT", "INSERT_PIVOT", "UPDATE_PIVOT"], pivotTransformation);
@@ -52697,6 +53066,15 @@ function updateTableTransformation(toTransform, executed) {
52697
53066
  : undefined;
52698
53067
  return { ...toTransform, newTableRange, zone: newCmdZone };
52699
53068
  }
53069
+ function removeTableStyleTransform(toTransform, executed) {
53070
+ if (toTransform.config?.styleId !== executed.tableStyleId) {
53071
+ return toTransform;
53072
+ }
53073
+ return {
53074
+ ...toTransform,
53075
+ config: { ...toTransform.config, styleId: DEFAULT_TABLE_CONFIG.styleId },
53076
+ };
53077
+ }
52700
53078
  /**
52701
53079
  * Transform ADD_COLUMNS_ROWS command if some headers were added/removed
52702
53080
  */
@@ -53079,11 +53457,14 @@ class Session extends EventBus {
53079
53457
  this.transportService.onNewMessage(this.clientId, this.onMessageReceived.bind(this));
53080
53458
  }
53081
53459
  loadInitialMessages(messages) {
53460
+ const start = performance.now();
53461
+ const numberOfCommands = messages.reduce((acc, message) => acc + (message.type === "REMOTE_REVISION" ? message.commands.length : 1), 0);
53082
53462
  this.isReplayingInitialRevisions = true;
53083
53463
  for (const message of messages) {
53084
53464
  this.onMessageReceived(message);
53085
53465
  }
53086
53466
  this.isReplayingInitialRevisions = false;
53467
+ console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
53087
53468
  }
53088
53469
  /**
53089
53470
  * Notify the server that the user client left the collaborative session
@@ -54156,7 +54537,7 @@ class SheetUIPlugin extends UIPlugin {
54156
54537
  }
54157
54538
  }
54158
54539
 
54159
- class TableStylePlugin extends UIPlugin {
54540
+ class TableComputedStylePlugin extends UIPlugin {
54160
54541
  static getters = ["getCellTableStyle", "getCellTableBorder"];
54161
54542
  tableStyles = {};
54162
54543
  handle(cmd) {
@@ -54167,7 +54548,12 @@ class TableStylePlugin extends UIPlugin {
54167
54548
  return;
54168
54549
  }
54169
54550
  if (doesCommandInvalidatesTableStyle(cmd)) {
54170
- delete this.tableStyles[cmd.sheetId];
54551
+ if ("sheetId" in cmd) {
54552
+ delete this.tableStyles[cmd.sheetId];
54553
+ }
54554
+ else {
54555
+ this.tableStyles = {};
54556
+ }
54171
54557
  return;
54172
54558
  }
54173
54559
  }
@@ -54200,7 +54586,8 @@ class TableStylePlugin extends UIPlugin {
54200
54586
  computeTableStyle(sheetId, table) {
54201
54587
  return lazy(() => {
54202
54588
  const { config, numberOfCols, numberOfRows } = this.getTableRuntimeConfig(sheetId, table);
54203
- const relativeTableStyle = getComputedTableStyle(config, numberOfCols, numberOfRows);
54589
+ const style = this.getters.getTableStyle(table.config.styleId);
54590
+ const relativeTableStyle = getComputedTableStyle(config, style, numberOfCols, numberOfRows);
54204
54591
  // Return the style with sheet coordinates instead of tables coordinates
54205
54592
  const mapping = this.getTableMapping(sheetId, table);
54206
54593
  const absoluteTableStyle = { borders: {}, styles: {} };
@@ -54301,6 +54688,8 @@ const invalidateTableStyleCommands = [
54301
54688
  "UPDATE_FILTER",
54302
54689
  "REMOVE_TABLE",
54303
54690
  "RESIZE_TABLE",
54691
+ "CREATE_TABLE_STYLE",
54692
+ "REMOVE_TABLE_STYLE",
54304
54693
  ];
54305
54694
  const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
54306
54695
  function doesCommandInvalidatesTableStyle(cmd) {
@@ -54320,8 +54709,14 @@ class CellComputedStylePlugin extends UIPlugin {
54320
54709
  return;
54321
54710
  }
54322
54711
  if (doesCommandInvalidatesTableStyle(cmd)) {
54323
- delete this.styles[cmd.sheetId];
54324
- delete this.borders[cmd.sheetId];
54712
+ if ("sheetId" in cmd) {
54713
+ delete this.styles[cmd.sheetId];
54714
+ delete this.borders[cmd.sheetId];
54715
+ }
54716
+ else {
54717
+ this.styles = {};
54718
+ this.borders = {};
54719
+ }
54325
54720
  return;
54326
54721
  }
54327
54722
  if (invalidateCFEvaluationCommands.has(cmd.type)) {
@@ -55670,9 +56065,7 @@ class FilterEvaluationPlugin extends UIPlugin {
55670
56065
  };
55671
56066
  const filteredValues = this.getFilterHiddenValues(position);
55672
56067
  const filter = this.getters.getFilter(position);
55673
- if (!filter)
55674
- continue;
55675
- const valuesInFilterZone = filter.filteredRange
56068
+ const valuesInFilterZone = filter?.filteredRange
55676
56069
  ? positions(filter.filteredRange.zone).map((position) => this.getters.getEvaluatedCell({ sheetId, ...position }).formattedValue)
55677
56070
  : [];
55678
56071
  if (filteredValues.length) {
@@ -55685,17 +56078,12 @@ class FilterEvaluationPlugin extends UIPlugin {
55685
56078
  displayBlanks: !filteredValues.includes("") && valuesInFilterZone.some((val) => !val),
55686
56079
  });
55687
56080
  }
55688
- // In xlsx, filter header should ALWAYS be a string and should be unique in the table
55689
- const headerPosition = {
55690
- col: filter.col,
55691
- row: filter.rangeWithHeaders.zone.top,
55692
- sheetId,
55693
- };
55694
- const headerString = this.getters.getEvaluatedCell(headerPosition).formattedValue;
56081
+ // In xlsx, column header should ALWAYS be a string and should be unique in the table
56082
+ const headerString = this.getters.getEvaluatedCell(position).formattedValue;
55695
56083
  const headerName = this.getUniqueColNameForExcel(i, headerString, headerNames);
55696
56084
  headerNames.push(headerName);
55697
- sheetData.cells[toXC(headerPosition.col, headerPosition.row)] = {
55698
- ...sheetData.cells[toXC(headerPosition.col, headerPosition.row)],
56085
+ sheetData.cells[toXC(position.col, position.row)] = {
56086
+ ...sheetData.cells[toXC(position.col, position.row)],
55699
56087
  content: headerName,
55700
56088
  value: headerName,
55701
56089
  isFormula: false,
@@ -55742,7 +56130,6 @@ class GridSelectionPlugin extends UIPlugin {
55742
56130
  "getSelection",
55743
56131
  "getActivePosition",
55744
56132
  "getSheetPosition",
55745
- "isSelected",
55746
56133
  "isSingleColSelected",
55747
56134
  "getElementsFromSelection",
55748
56135
  "tryGetActiveSheetId",
@@ -56024,9 +56411,6 @@ class GridSelectionPlugin extends UIPlugin {
56024
56411
  : this.getters.getNextVisibleCellPosition({ sheetId, col: 0, row: 0 });
56025
56412
  }
56026
56413
  }
56027
- isSelected(zone) {
56028
- return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
56029
- }
56030
56414
  isSingleColSelected() {
56031
56415
  const selection = this.getters.getSelectedZones();
56032
56416
  if (selection.length !== 1 || selection[0].left !== selection[0].right) {
@@ -57410,7 +57794,8 @@ const corePluginRegistry = new Registry()
57410
57794
  .add("figures", FigurePlugin)
57411
57795
  .add("chart", ChartPlugin)
57412
57796
  .add("image", ImagePlugin)
57413
- .add("pivot_core", PivotCorePlugin);
57797
+ .add("pivot_core", PivotCorePlugin)
57798
+ .add("tableStyle", TableStylePlugin);
57414
57799
  // Plugins which handle a specific feature, without handling any core commands
57415
57800
  const featurePluginRegistry = new Registry()
57416
57801
  .add("ui_sheet", SheetUIPlugin)
@@ -57431,8 +57816,8 @@ const statefulUIPluginRegistry = new Registry()
57431
57816
  .add("selection", GridSelectionPlugin)
57432
57817
  .add("evaluation_filter", FilterEvaluationPlugin)
57433
57818
  .add("header_visibility_ui", HeaderVisibilityUIPlugin)
57434
- .add("table_style", TableStylePlugin)
57435
57819
  .add("cell_computed_style", CellComputedStylePlugin)
57820
+ .add("table_computed_style", TableComputedStylePlugin)
57436
57821
  .add("header_positions", HeaderPositionsUIPlugin)
57437
57822
  .add("viewport", SheetViewPlugin)
57438
57823
  .add("clipboard", ClipboardPlugin);
@@ -58250,6 +58635,7 @@ css /* scss */ `
58250
58635
  .o-icon {
58251
58636
  height: 18px;
58252
58637
  width: 18px;
58638
+ font-size: 18px;
58253
58639
  }
58254
58640
  }
58255
58641
  }
@@ -59791,6 +60177,8 @@ css /* scss */ `
59791
60177
  display: grid;
59792
60178
  grid-template-columns: auto 350px;
59793
60179
  color: #333;
60180
+ font-size: 14px;
60181
+
59794
60182
  input {
59795
60183
  background-color: white;
59796
60184
  }
@@ -59868,12 +60256,6 @@ css /* scss */ `
59868
60256
  grid-column: 1 / 3;
59869
60257
  }
59870
60258
 
59871
- .o-icon {
59872
- width: ${ICON_EDGE_LENGTH}px;
59873
- height: ${ICON_EDGE_LENGTH}px;
59874
- vertical-align: middle;
59875
- }
59876
-
59877
60259
  .o-cf-icon {
59878
60260
  width: ${CF_ICON_EDGE_LENGTH}px;
59879
60261
  height: ${CF_ICON_EDGE_LENGTH}px;
@@ -62776,20 +63158,14 @@ function addCellWiseConditionalFormatting(dxfs // cell-wise CF
62776
63158
  `;
62777
63159
  }
62778
63160
 
62779
- const TABLE_DEFAULT_ATTRS = [
62780
- ["name", "TableStyleLight8"],
62781
- ["showFirstColumn", "0"],
62782
- ["showLastColumn", "0"],
62783
- ["showRowStripes", "0"],
62784
- ["showColumnStripes", "0"],
62785
- ];
62786
- const TABLE_DEFAULT_STYLE = escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(TABLE_DEFAULT_ATTRS)}/>`;
62787
63161
  function createTable(table, tableId, sheetData) {
62788
63162
  const tableAttributes = [
62789
63163
  ["id", tableId],
62790
63164
  ["name", `Table${tableId}`],
62791
63165
  ["displayName", `Table${tableId}`],
62792
63166
  ["ref", table.range],
63167
+ ["headerRowCount", table.config.numberOfHeaders],
63168
+ ["totalsRowCount", table.config.totalRow ? 1 : 0],
62793
63169
  ["xmlns", NAMESPACE.table],
62794
63170
  ["xmlns:xr", NAMESPACE.revision],
62795
63171
  ["xmlns:xr3", NAMESPACE.revision3],
@@ -62797,9 +63173,9 @@ function createTable(table, tableId, sheetData) {
62797
63173
  ];
62798
63174
  const xml = escapeXml /*xml*/ `
62799
63175
  <table ${formatAttributes(tableAttributes)}>
62800
- ${addAutoFilter(table)}
63176
+ ${table.config.hasFilters ? addAutoFilter(table) : ""}
62801
63177
  ${addTableColumns(table, sheetData)}
62802
- ${TABLE_DEFAULT_STYLE}
63178
+ ${addTableStyle(table)}
62803
63179
  </table>
62804
63180
  `;
62805
63181
  return parseXML(xml);
@@ -62851,6 +63227,16 @@ function addTableColumns(table, sheetData) {
62851
63227
  </tableColumns>
62852
63228
  `;
62853
63229
  }
63230
+ function addTableStyle(table) {
63231
+ const tableStyleAttrs = [
63232
+ ["name", table.config.styleId],
63233
+ ["showFirstColumn", table.config.firstColumn ? 1 : 0],
63234
+ ["showLastColumn", table.config.lastColumn ? 1 : 0],
63235
+ ["showRowStripes", table.config.bandedRows ? 1 : 0],
63236
+ ["showColumnStripes", table.config.bandedColumns ? 1 : 0],
63237
+ ];
63238
+ return escapeXml /*xml*/ `<tableStyleInfo ${formatAttributes(tableStyleAttrs)}/>`;
63239
+ }
62854
63240
 
62855
63241
  function addColumns(cols) {
62856
63242
  if (!Object.values(cols).length) {
@@ -62907,7 +63293,10 @@ function addRows(construct, data, sheet) {
62907
63293
  const attributes = [["r", xc]];
62908
63294
  // style
62909
63295
  const id = normalizeStyle(construct, extractStyle(cell, data));
62910
- attributes.push(["s", id]);
63296
+ // don't add style if default
63297
+ if (id) {
63298
+ attributes.push(["s", id]);
63299
+ }
62911
63300
  let additionalAttrs = [];
62912
63301
  let cellNode = escapeXml ``;
62913
63302
  // Either formula or static value inside the cell
@@ -63405,6 +63794,8 @@ class Model extends EventBus {
63405
63794
  uiHandlers = [];
63406
63795
  coreHandlers = [];
63407
63796
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
63797
+ const start = performance.now();
63798
+ console.group("Model creation");
63408
63799
  super();
63409
63800
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
63410
63801
  const workbookData = load(data, verboseImport);
@@ -63474,12 +63865,17 @@ class Model extends EventBus {
63474
63865
  this.setupSessionEvents();
63475
63866
  this.joinSession();
63476
63867
  if (config.snapshotRequested) {
63868
+ const startSnapshot = performance.now();
63869
+ console.info("Snapshot requested");
63477
63870
  this.session.snapshot(this.exportData());
63478
63871
  this.garbageCollectExternalResources();
63872
+ console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
63479
63873
  }
63480
63874
  // mark all models as "raw", so they will not be turned into reactive objects
63481
63875
  // by owl, since we do not rely on reactivity
63482
63876
  owl.markRaw(this);
63877
+ console.info("Model created in", performance.now() - start, "ms");
63878
+ console.groupEnd();
63483
63879
  }
63484
63880
  joinSession() {
63485
63881
  this.session.join(this.config.client);
@@ -63694,11 +64090,16 @@ class Model extends EventBus {
63694
64090
  }
63695
64091
  this.status = 1 /* Status.Running */;
63696
64092
  const { changes, commands } = this.state.recordChanges(() => {
64093
+ const start = performance.now();
63697
64094
  if (isCoreCommand(command)) {
63698
64095
  this.state.addCommand(command);
63699
64096
  }
63700
64097
  this.dispatchToHandlers(this.handlers, command);
63701
64098
  this.finalize();
64099
+ const time = performance.now() - start;
64100
+ if (time > 5) {
64101
+ console.info(type, time, "ms");
64102
+ }
63702
64103
  });
63703
64104
  this.session.save(command, commands, changes);
63704
64105
  this.status = 0 /* Status.Ready */;
@@ -64147,6 +64548,6 @@ exports.tokenColors = tokenColors;
64147
64548
  exports.tokenize = tokenize;
64148
64549
 
64149
64550
 
64150
- __info__.version = "17.3.0-alpha.5";
64151
- __info__.date = "2024-04-18T14:38:57.166Z";
64152
- __info__.hash = "cff0ef9";
64551
+ __info__.version = "17.3.0-alpha.6";
64552
+ __info__.date = "2024-04-26T07:39:53.611Z";
64553
+ __info__.hash = "f58a0d5";