@odoo/o-spreadsheet 18.1.0-alpha.0 → 18.1.0-alpha.1

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.
@@ -2,14 +2,120 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.1.0-alpha.0
6
- * @date 2024-09-25T13:17:49.636Z
7
- * @hash 288f0b7
5
+ * @version 18.1.0-alpha.1
6
+ * @date 2024-10-14T07:53:17.717Z
7
+ * @hash e9ce3aa
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
11
11
  'use strict';
12
12
 
13
+ function createActions(menuItems) {
14
+ return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
15
+ }
16
+ let nextItemId = 1;
17
+ function createAction(item) {
18
+ const name = item.name;
19
+ const children = item.children;
20
+ const description = item.description;
21
+ const icon = item.icon;
22
+ const secondaryIcon = item.secondaryIcon;
23
+ const itemId = item.id || nextItemId++;
24
+ return {
25
+ id: itemId.toString(),
26
+ name: typeof name === "function" ? name : () => name,
27
+ isVisible: item.isVisible ? item.isVisible : () => true,
28
+ isEnabled: item.isEnabled ? item.isEnabled : () => true,
29
+ isActive: item.isActive,
30
+ execute: item.execute,
31
+ children: children
32
+ ? (env) => {
33
+ return children
34
+ .map((child) => (typeof child === "function" ? child(env) : child))
35
+ .flat()
36
+ .map(createAction);
37
+ }
38
+ : () => [],
39
+ isReadonlyAllowed: item.isReadonlyAllowed || false,
40
+ separator: item.separator || false,
41
+ icon: typeof icon === "function" ? icon : () => icon || "",
42
+ iconColor: item.iconColor,
43
+ secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
44
+ description: typeof description === "function" ? description : () => description || "",
45
+ textColor: item.textColor,
46
+ sequence: item.sequence || 0,
47
+ onStartHover: item.onStartHover,
48
+ onStopHover: item.onStopHover,
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Registry
54
+ *
55
+ * The Registry class is basically just a mapping from a string key to an object.
56
+ * It is really not much more than an object. It is however useful for the
57
+ * following reasons:
58
+ *
59
+ * 1. it let us react and execute code when someone add something to the registry
60
+ * (for example, the FunctionRegistry subclass this for this purpose)
61
+ * 2. it throws an error when the get operation fails
62
+ * 3. it provides a chained API to add items to the registry.
63
+ */
64
+ class Registry {
65
+ content = {};
66
+ /**
67
+ * Add an item to the registry
68
+ *
69
+ * Note that this also returns the registry, so another add method call can
70
+ * be chained
71
+ */
72
+ add(key, value) {
73
+ this.content[key] = value;
74
+ return this;
75
+ }
76
+ /**
77
+ * Get an item from the registry
78
+ */
79
+ get(key) {
80
+ /**
81
+ * Note: key in {} is ~12 times slower than {}[key].
82
+ * So, we check the absence of key only when the direct access returns
83
+ * a falsy value. It's done to ensure that the registry can contains falsy values
84
+ */
85
+ const content = this.content[key];
86
+ if (!content) {
87
+ if (!(key in this.content)) {
88
+ throw new Error(`Cannot find ${key} in this registry!`);
89
+ }
90
+ }
91
+ return content;
92
+ }
93
+ /**
94
+ * Check if the key is already in the registry
95
+ */
96
+ contains(key) {
97
+ return key in this.content;
98
+ }
99
+ /**
100
+ * Get a list of all elements in the registry
101
+ */
102
+ getAll() {
103
+ return Object.values(this.content);
104
+ }
105
+ /**
106
+ * Get a list of all keys in the registry
107
+ */
108
+ getKeys() {
109
+ return Object.keys(this.content);
110
+ }
111
+ /**
112
+ * Remove an item from the registry
113
+ */
114
+ remove(key) {
115
+ delete this.content[key];
116
+ }
117
+ }
118
+
13
119
  const CANVAS_SHIFT = 0.5;
14
120
  // Colors
15
121
  const HIGHLIGHT_COLOR = "#37A850";
@@ -5969,18 +6075,9 @@
5969
6075
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
5970
6076
  * */
5971
6077
  class UuidGenerator {
5972
- isFastIdStrategy = false;
5973
- fastIdStart = 0;
5974
- setIsFastStrategy(isFast) {
5975
- this.isFastIdStrategy = isFast;
5976
- }
5977
6078
  uuidv4() {
5978
- if (this.isFastIdStrategy) {
5979
- this.fastIdStart++;
5980
- return String(this.fastIdStart);
5981
- //@ts-ignore
5982
- }
5983
- else if (window.crypto && window.crypto.getRandomValues) {
6079
+ //@ts-ignore
6080
+ if (window.crypto && window.crypto.getRandomValues) {
5984
6081
  //@ts-ignore
5985
6082
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
5986
6083
  }
@@ -5994,111 +6091,6 @@
5994
6091
  }
5995
6092
  }
5996
6093
 
5997
- function createActions(menuItems) {
5998
- return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
5999
- }
6000
- const uuidGenerator$1 = new UuidGenerator();
6001
- function createAction(item) {
6002
- const name = item.name;
6003
- const children = item.children;
6004
- const description = item.description;
6005
- const icon = item.icon;
6006
- const secondaryIcon = item.secondaryIcon;
6007
- return {
6008
- id: item.id || uuidGenerator$1.uuidv4(),
6009
- name: typeof name === "function" ? name : () => name,
6010
- isVisible: item.isVisible ? item.isVisible : () => true,
6011
- isEnabled: item.isEnabled ? item.isEnabled : () => true,
6012
- isActive: item.isActive,
6013
- execute: item.execute,
6014
- children: children
6015
- ? (env) => {
6016
- return children
6017
- .map((child) => (typeof child === "function" ? child(env) : child))
6018
- .flat()
6019
- .map(createAction);
6020
- }
6021
- : () => [],
6022
- isReadonlyAllowed: item.isReadonlyAllowed || false,
6023
- separator: item.separator || false,
6024
- icon: typeof icon === "function" ? icon : () => icon || "",
6025
- iconColor: item.iconColor,
6026
- secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
6027
- description: typeof description === "function" ? description : () => description || "",
6028
- textColor: item.textColor,
6029
- sequence: item.sequence || 0,
6030
- onStartHover: item.onStartHover,
6031
- onStopHover: item.onStopHover,
6032
- };
6033
- }
6034
-
6035
- /**
6036
- * Registry
6037
- *
6038
- * The Registry class is basically just a mapping from a string key to an object.
6039
- * It is really not much more than an object. It is however useful for the
6040
- * following reasons:
6041
- *
6042
- * 1. it let us react and execute code when someone add something to the registry
6043
- * (for example, the FunctionRegistry subclass this for this purpose)
6044
- * 2. it throws an error when the get operation fails
6045
- * 3. it provides a chained API to add items to the registry.
6046
- */
6047
- class Registry {
6048
- content = {};
6049
- /**
6050
- * Add an item to the registry
6051
- *
6052
- * Note that this also returns the registry, so another add method call can
6053
- * be chained
6054
- */
6055
- add(key, value) {
6056
- this.content[key] = value;
6057
- return this;
6058
- }
6059
- /**
6060
- * Get an item from the registry
6061
- */
6062
- get(key) {
6063
- /**
6064
- * Note: key in {} is ~12 times slower than {}[key].
6065
- * So, we check the absence of key only when the direct access returns
6066
- * a falsy value. It's done to ensure that the registry can contains falsy values
6067
- */
6068
- const content = this.content[key];
6069
- if (!content) {
6070
- if (!(key in this.content)) {
6071
- throw new Error(`Cannot find ${key} in this registry!`);
6072
- }
6073
- }
6074
- return content;
6075
- }
6076
- /**
6077
- * Check if the key is already in the registry
6078
- */
6079
- contains(key) {
6080
- return key in this.content;
6081
- }
6082
- /**
6083
- * Get a list of all elements in the registry
6084
- */
6085
- getAll() {
6086
- return Object.values(this.content);
6087
- }
6088
- /**
6089
- * Get a list of all keys in the registry
6090
- */
6091
- getKeys() {
6092
- return Object.keys(this.content);
6093
- }
6094
- /**
6095
- * Remove an item from the registry
6096
- */
6097
- remove(key) {
6098
- delete this.content[key];
6099
- }
6100
- }
6101
-
6102
6094
  function getClipboardDataPositions(sheetId, zones) {
6103
6095
  const lefts = new Set(zones.map((z) => z.left));
6104
6096
  const rights = new Set(zones.map((z) => z.right));
@@ -8453,31 +8445,34 @@
8453
8445
  for (let col of columnsIndexes) {
8454
8446
  const position = { col, row, sheetId };
8455
8447
  const table = this.getters.getTable(position);
8456
- if (!table || copiedTablesIds.has(table.id)) {
8448
+ if (!table) {
8457
8449
  tableCellsInRow.push({});
8458
8450
  continue;
8459
8451
  }
8460
8452
  const coreTable = this.getters.getCoreTable(position);
8461
8453
  const tableZone = coreTable?.range.zone;
8454
+ let copiedTable = undefined;
8462
8455
  // Copy whole table
8463
- if (coreTable && tableZone && zones.some((z) => isZoneInside(tableZone, z))) {
8464
- copiedTablesIds.add(coreTable.id);
8456
+ if (!copiedTablesIds.has(table.id) &&
8457
+ coreTable &&
8458
+ tableZone &&
8459
+ zones.some((z) => isZoneInside(tableZone, z))) {
8460
+ copiedTablesIds.add(table.id);
8465
8461
  const values = [];
8466
8462
  for (const col of range(tableZone.left, tableZone.right + 1)) {
8467
8463
  values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
8468
8464
  }
8469
- tableCellsInRow.push({
8470
- table: {
8471
- range: coreTable.range.rangeData,
8472
- config: coreTable.config,
8473
- type: coreTable.type,
8474
- },
8475
- });
8476
- }
8477
- // Copy only style of cell
8478
- else if (table) {
8479
- tableCellsInRow.push({ style: this.getTableStyleToCopy(position) });
8465
+ copiedTable = {
8466
+ range: coreTable.range.rangeData,
8467
+ config: coreTable.config,
8468
+ type: coreTable.type,
8469
+ };
8480
8470
  }
8471
+ tableCellsInRow.push({
8472
+ table: copiedTable,
8473
+ style: this.getTableStyleToCopy(position),
8474
+ isWholeTableCopied: copiedTablesIds.has(table.id),
8475
+ });
8481
8476
  }
8482
8477
  }
8483
8478
  return {
@@ -8558,11 +8553,14 @@
8558
8553
  tableType: tableCell.table.type,
8559
8554
  });
8560
8555
  }
8561
- // Do not paste table style if we're inside another table
8562
8556
  // We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
8563
8557
  // dynamic tables are not yet computed
8564
- if (!this.getters.getCoreTable(position)) {
8565
- if (tableCell.style?.style && options?.pasteOption !== "asValue") {
8558
+ if (this.getters.getCoreTable(position) || options?.pasteOption === "asValue") {
8559
+ return;
8560
+ }
8561
+ if ((!options?.pasteOption && !tableCell.isWholeTableCopied) ||
8562
+ options?.pasteOption === "onlyFormat") {
8563
+ if (tableCell.style?.style) {
8566
8564
  this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
8567
8565
  }
8568
8566
  if (tableCell.style?.border) {
@@ -9282,6 +9280,49 @@ stores.inject(MyMetaStore, storeInstance);
9282
9280
  useLeftAxis ||= !useRightAxis;
9283
9281
  return { useLeftAxis, useRightAxis };
9284
9282
  }
9283
+ function getChartAxis(definition, position, type, options) {
9284
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
9285
+ if ((position === "left" && !useLeftAxis) || (position === "right" && !useRightAxis)) {
9286
+ return undefined;
9287
+ }
9288
+ const fontColor = chartFontColor(definition.background);
9289
+ let design;
9290
+ if (position === "bottom") {
9291
+ design = definition.axesDesign?.x;
9292
+ }
9293
+ else if (position === "left") {
9294
+ design = definition.axesDesign?.y;
9295
+ }
9296
+ else {
9297
+ design = definition.axesDesign?.y1;
9298
+ }
9299
+ if (type === "values") {
9300
+ const displayGridLines = position === "left" || (position === "right" && !useLeftAxis);
9301
+ return {
9302
+ position: position,
9303
+ title: getChartAxisTitleRuntime(design),
9304
+ grid: {
9305
+ display: displayGridLines,
9306
+ },
9307
+ beginAtZero: true,
9308
+ stacked: options?.stacked,
9309
+ ticks: {
9310
+ color: fontColor,
9311
+ callback: formatTickValue(options),
9312
+ },
9313
+ };
9314
+ }
9315
+ else {
9316
+ return {
9317
+ ticks: {
9318
+ padding: 5,
9319
+ color: fontColor,
9320
+ },
9321
+ stacked: options?.stacked,
9322
+ title: getChartAxisTitleRuntime(design),
9323
+ };
9324
+ }
9325
+ }
9285
9326
  function computeChartPadding({ displayTitle, displayLegend, }) {
9286
9327
  let top = 25;
9287
9328
  if (displayTitle) {
@@ -9368,6 +9409,12 @@ stores.inject(MyMetaStore, storeInstance);
9368
9409
  return [];
9369
9410
  }
9370
9411
  }
9412
+ function formatChartDatasetValue(axisFormats, locale) {
9413
+ return (value, axisId) => {
9414
+ const format = axisId ? axisFormats?.[axisId] : undefined;
9415
+ return formatTickValue({ format, locale })(value);
9416
+ };
9417
+ }
9371
9418
  function formatTickValue(localeFormat) {
9372
9419
  return (value) => {
9373
9420
  value = Number(value);
@@ -9430,10 +9477,12 @@ stores.inject(MyMetaStore, storeInstance);
9430
9477
  case "bar":
9431
9478
  case "line": {
9432
9479
  const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
9480
+ const horizontalChart = dataset.type === "bar" && options.horizontal;
9481
+ const axisId = horizontalChart ? dataset.xAxisID : dataset.yAxisID;
9433
9482
  for (let i = 0; i < dataset._parsed.length; i++) {
9434
9483
  const point = dataset.data[i];
9435
9484
  const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
9436
- const displayedValue = options.callback(value - 0);
9485
+ const displayedValue = options.callback(value - 0, axisId);
9437
9486
  let xPosition = 0, yPosition = 0;
9438
9487
  if (options.horizontal) {
9439
9488
  yPosition = point.y;
@@ -17842,7 +17891,7 @@ stores.inject(MyMetaStore, storeInstance);
17842
17891
  throw new EvaluationError(_t("Function PIVOT takes an even number of arguments."));
17843
17892
  }
17844
17893
  }
17845
- function addPivotDependencies(evalContext, coreDefinition) {
17894
+ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
17846
17895
  //TODO This function can be very costly when used with PIVOT.VALUE and PIVOT.HEADER
17847
17896
  const dependencies = [];
17848
17897
  if (coreDefinition.type === "SPREADSHEET" && coreDefinition.dataSet) {
@@ -17854,7 +17903,7 @@ stores.inject(MyMetaStore, storeInstance);
17854
17903
  }
17855
17904
  dependencies.push(range);
17856
17905
  }
17857
- for (const measure of coreDefinition.measures) {
17906
+ for (const measure of forMeasures) {
17858
17907
  if (measure.computedBy) {
17859
17908
  const formula = evalContext.getters.getMeasureCompiledFormula(measure);
17860
17909
  dependencies.push(...formula.dependencies.filter((range) => !range.invalidXc));
@@ -18287,7 +18336,7 @@ stores.inject(MyMetaStore, storeInstance);
18287
18336
  assertDomainLength(domainArgs);
18288
18337
  const pivot = this.getters.getPivot(pivotId);
18289
18338
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18290
- addPivotDependencies(this, coreDefinition);
18339
+ addPivotDependencies(this, coreDefinition, coreDefinition.measures.filter((m) => m.id === _measure));
18291
18340
  pivot.init({ reload: pivot.needsReevaluation });
18292
18341
  const error = pivot.assertIsValid({ throwOnError: false });
18293
18342
  if (error) {
@@ -18317,7 +18366,7 @@ stores.inject(MyMetaStore, storeInstance);
18317
18366
  assertDomainLength(domainArgs);
18318
18367
  const pivot = this.getters.getPivot(_pivotId);
18319
18368
  const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
18320
- addPivotDependencies(this, coreDefinition);
18369
+ addPivotDependencies(this, coreDefinition, []);
18321
18370
  pivot.init({ reload: pivot.needsReevaluation });
18322
18371
  const error = pivot.assertIsValid({ throwOnError: false });
18323
18372
  if (error) {
@@ -18368,7 +18417,7 @@ stores.inject(MyMetaStore, storeInstance);
18368
18417
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
18369
18418
  const pivot = this.getters.getPivot(pivotId);
18370
18419
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18371
- addPivotDependencies(this, coreDefinition);
18420
+ addPivotDependencies(this, coreDefinition, coreDefinition.measures);
18372
18421
  pivot.init({ reload: pivot.needsReevaluation });
18373
18422
  const error = pivot.assertIsValid({ throwOnError: false });
18374
18423
  if (error) {
@@ -20164,7 +20213,7 @@ stores.inject(MyMetaStore, storeInstance);
20164
20213
  let startNode = this.findChildAtCharacterIndex(start);
20165
20214
  let endNode = this.findChildAtCharacterIndex(end);
20166
20215
  range.setStart(startNode.node, startNode.offset);
20167
- selection.extend(endNode.node, endNode.offset);
20216
+ range.setEnd(endNode.node, endNode.offset);
20168
20217
  }
20169
20218
  }
20170
20219
  /**
@@ -20662,7 +20711,7 @@ stores.inject(MyMetaStore, storeInstance);
20662
20711
  "Alt+Enter": this.processNewLineEvent,
20663
20712
  "Ctrl+Enter": this.processNewLineEvent,
20664
20713
  Escape: this.processEscapeKey,
20665
- F2: () => console.warn("Not implemented"),
20714
+ F2: (ev) => this.toggleEditionMode(ev),
20666
20715
  F4: (ev) => this.processF4Key(ev),
20667
20716
  Tab: (ev) => this.processTabKey(ev, "right"),
20668
20717
  "Shift+Tab": (ev) => this.processTabKey(ev, "left"),
@@ -20781,6 +20830,11 @@ stores.inject(MyMetaStore, storeInstance);
20781
20830
  this.props.composerStore.cycleReferences();
20782
20831
  this.processContent();
20783
20832
  }
20833
+ toggleEditionMode(ev) {
20834
+ ev.stopPropagation();
20835
+ this.props.composerStore.toggleEditionMode();
20836
+ this.processContent();
20837
+ }
20784
20838
  processNumpadDecimal(ev) {
20785
20839
  ev.stopPropagation();
20786
20840
  ev.preventDefault();
@@ -21006,7 +21060,13 @@ stores.inject(MyMetaStore, storeInstance);
21006
21060
  break;
21007
21061
  case "REFERENCE":
21008
21062
  const { xc, sheetName } = splitReference(token.value);
21009
- result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
21063
+ result.push({
21064
+ value: token.value,
21065
+ color: this.rangeColor(xc, sheetName) || "#000",
21066
+ class: tokenAtCursor === token && this.props.composerStore.editionMode === "selecting"
21067
+ ? "text-decoration-underline"
21068
+ : undefined,
21069
+ });
21010
21070
  break;
21011
21071
  case "SYMBOL":
21012
21072
  const value = token.value;
@@ -21577,6 +21637,7 @@ stores.inject(MyMetaStore, storeInstance);
21577
21637
  // replace the whole token
21578
21638
  start = tokenAtCursor.start;
21579
21639
  }
21640
+ this.composer.stopComposerRangeSelection();
21580
21641
  this.composer.changeComposerCursorSelection(start, end);
21581
21642
  this.composer.replaceComposerCursorSelection(value);
21582
21643
  }
@@ -21594,6 +21655,7 @@ stores.inject(MyMetaStore, storeInstance);
21594
21655
  // replace the whole token
21595
21656
  start = tokenAtCursor.start;
21596
21657
  }
21658
+ this.composer.stopComposerRangeSelection();
21597
21659
  this.composer.changeComposerCursorSelection(start, end);
21598
21660
  this.composer.replaceComposerCursorSelection(value);
21599
21661
  }
@@ -22186,6 +22248,27 @@ stores.inject(MyMetaStore, storeInstance);
22186
22248
  tooltip: content ? { props: { content: tooltipValue } } : undefined,
22187
22249
  };
22188
22250
  },
22251
+ })
22252
+ .add("DATE_INCREMENT_MODIFIER", {
22253
+ apply: (rule, data, getters) => {
22254
+ const date = toJsDate(rule.current, getters.getLocale());
22255
+ date.setFullYear(date.getFullYear() + rule.increment.years || 0);
22256
+ date.setMonth(date.getMonth() + rule.increment.months || 0);
22257
+ date.setDate(date.getDate() + rule.increment.days || 0);
22258
+ const value = jsDateToNumber(date);
22259
+ rule.current = value;
22260
+ const locale = getters.getLocale();
22261
+ const tooltipValue = formatValue(value, { format: data.cell?.format, locale });
22262
+ return {
22263
+ cellData: {
22264
+ border: data.border,
22265
+ style: data.cell && data.cell.style,
22266
+ format: data.cell && data.cell.format,
22267
+ content: value.toString(),
22268
+ },
22269
+ tooltip: value ? { props: { content: tooltipValue } } : undefined,
22270
+ };
22271
+ },
22189
22272
  })
22190
22273
  .add("COPY_MODIFIER", {
22191
22274
  apply: (rule, data, getters) => {
@@ -22266,7 +22349,9 @@ stores.inject(MyMetaStore, storeInstance);
22266
22349
  if (x === cell) {
22267
22350
  found = true;
22268
22351
  }
22269
- const cellValue = x === undefined || x.isFormula ? undefined : evaluateLiteral(x, { locale: DEFAULT_LOCALE });
22352
+ const cellValue = x === undefined || x.isFormula
22353
+ ? undefined
22354
+ : evaluateLiteral(x, { locale: DEFAULT_LOCALE, format: x.format });
22270
22355
  if (cellValue && filter(cellValue)) {
22271
22356
  group.push(cellValue);
22272
22357
  }
@@ -22302,6 +22387,72 @@ stores.inject(MyMetaStore, storeInstance);
22302
22387
  }
22303
22388
  return increment;
22304
22389
  }
22390
+ /**
22391
+ * Iterates on a list of date intervals.
22392
+ * if every interval is the same, return the interval
22393
+ * Otherwise return undefined
22394
+ *
22395
+ */
22396
+ function getEqualInterval(intervals) {
22397
+ if (intervals.length < 2) {
22398
+ return intervals[0] || { years: 0, months: 0, days: 0 };
22399
+ }
22400
+ const equal = intervals.every((interval) => interval.years === intervals[0].years &&
22401
+ interval.months === intervals[0].months &&
22402
+ interval.days === intervals[0].days);
22403
+ return equal ? intervals[0] : undefined;
22404
+ }
22405
+ /**
22406
+ * Based on a group of dates, calculate the increment that should be applied
22407
+ * to the next date.
22408
+ *
22409
+ * This will compute the date difference in calendar terms (years, months, days)
22410
+ * In order to make abstraction of leap years and months with different number of days.
22411
+ *
22412
+ * In case the dates are not equidistant in calendar terms, no rule can be extrapolated
22413
+ * In case of equidistant dates, we either have in that order:
22414
+ * - exact date interval (e.g. +n year OR +n month OR +n day) in which case we increment by the same interval
22415
+ * - exact day interval (e.g. +n days) in which case we increment by the same day interval
22416
+ * - equidistant dates but not the same interval, in which case we return increment of the same interval
22417
+ *
22418
+ * */
22419
+ function calculateDateIncrementBasedOnGroup(group) {
22420
+ if (group.length < 2) {
22421
+ return 1;
22422
+ }
22423
+ const jsDates = group.map((date) => toJsDate(date, DEFAULT_LOCALE));
22424
+ const datesIntervals = getDateIntervals(jsDates);
22425
+ const datesEquidistantInterval = getEqualInterval(datesIntervals);
22426
+ if (datesEquidistantInterval === undefined) {
22427
+ // dates are not equidistant in terms of years, months or days, thus no rule can be extrapolated
22428
+ return undefined;
22429
+ }
22430
+ // The dates are apart by an exact interval of years, months or days
22431
+ // but not a combination of them
22432
+ const exactDateInterval = Object.values(datesEquidistantInterval).filter((value) => value !== 0).length === 1;
22433
+ const isSameDay = Object.values(datesEquidistantInterval).every((el) => el === 0); // handles time values (strict decimals)
22434
+ if (!exactDateInterval || isSameDay) {
22435
+ const timeIntervals = jsDates
22436
+ .map((date, index) => {
22437
+ if (index === 0) {
22438
+ return 0;
22439
+ }
22440
+ const previous = jsDates[index - 1];
22441
+ const days = Math.floor(date.getTime()) - Math.floor(previous.getTime());
22442
+ return days;
22443
+ })
22444
+ .slice(1);
22445
+ const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
22446
+ if (equidistantDates) {
22447
+ return group.length * (group[1] - group[0]);
22448
+ }
22449
+ }
22450
+ return {
22451
+ years: datesEquidistantInterval.years * group.length,
22452
+ months: datesEquidistantInterval.months * group.length,
22453
+ days: datesEquidistantInterval.days * group.length,
22454
+ };
22455
+ }
22305
22456
  autofillRulesRegistry
22306
22457
  .add("simple_value_copy", {
22307
22458
  condition: (cell, cells) => {
@@ -22349,12 +22500,47 @@ stores.inject(MyMetaStore, storeInstance);
22349
22500
  return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
22350
22501
  },
22351
22502
  sequence: 30,
22503
+ })
22504
+ .add("increment_dates", {
22505
+ condition: (cell, cells) => {
22506
+ return (!cell.isFormula &&
22507
+ evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number &&
22508
+ !!cell.format &&
22509
+ isDateTimeFormat(cell.format));
22510
+ },
22511
+ generateRule: (cell, cells) => {
22512
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
22513
+ !!evaluatedCell.format &&
22514
+ isDateTimeFormat(evaluatedCell.format)).map((cell) => Number(cell.value));
22515
+ const increment = calculateDateIncrementBasedOnGroup(group);
22516
+ if (increment === undefined) {
22517
+ return { type: "COPY_MODIFIER" };
22518
+ }
22519
+ /** requires to detect the current date (requires to be an integer value with the right format)
22520
+ * detect if year or if month or if day then extrapolate increment required (+1 month, +1 year + 1 day)
22521
+ */
22522
+ const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
22523
+ if (typeof increment === "object") {
22524
+ return {
22525
+ type: "DATE_INCREMENT_MODIFIER",
22526
+ increment,
22527
+ current: evaluation.type === CellValueType.number ? evaluation.value : 0,
22528
+ };
22529
+ }
22530
+ return {
22531
+ type: "INCREMENT_MODIFIER",
22532
+ increment,
22533
+ current: evaluation.type === CellValueType.number ? evaluation.value : 0,
22534
+ };
22535
+ },
22536
+ sequence: 25,
22352
22537
  })
22353
22538
  .add("increment_number", {
22354
22539
  condition: (cell) => !cell.isFormula &&
22355
22540
  evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
22356
22541
  generateRule: (cell, cells) => {
22357
- const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
22542
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
22543
+ !isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
22358
22544
  const increment = calculateIncrementBasedOnGroup(group);
22359
22545
  const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
22360
22546
  return {
@@ -22365,6 +22551,37 @@ stores.inject(MyMetaStore, storeInstance);
22365
22551
  },
22366
22552
  sequence: 40,
22367
22553
  });
22554
+ /**
22555
+ * Returns the date intervals between consecutive dates of an array
22556
+ * in the format of { years: number, months: number, days: number }
22557
+ *
22558
+ * The split is necessary to make abstraction of leap years and
22559
+ * months with different number of days.
22560
+ *
22561
+ * @param dates
22562
+ */
22563
+ function getDateIntervals(dates) {
22564
+ if (dates.length < 2) {
22565
+ return [{ years: 0, months: 0, days: 0 }];
22566
+ }
22567
+ const res = dates.map((date, index) => {
22568
+ if (index === 0) {
22569
+ return { years: 0, months: 0, days: 0 };
22570
+ }
22571
+ const previous = DateTime.fromTimestamp(dates[index - 1].getTime());
22572
+ const years = getTimeDifferenceInWholeYears(previous, date);
22573
+ const months = getTimeDifferenceInWholeMonths(previous, date) % 12;
22574
+ previous.setFullYear(previous.getFullYear() + years);
22575
+ previous.setMonth(previous.getMonth() + months);
22576
+ const days = getTimeDifferenceInWholeDays(previous, date);
22577
+ return {
22578
+ years,
22579
+ months,
22580
+ days,
22581
+ };
22582
+ });
22583
+ return res.slice(1);
22584
+ }
22368
22585
 
22369
22586
  const cellPopoverRegistry = new Registry();
22370
22587
 
@@ -27875,9 +28092,9 @@ stores.inject(MyMetaStore, storeInstance);
27875
28092
  /**
27876
28093
  * Get a default chart js configuration
27877
28094
  */
27878
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels = true, horizontalChart, }) {
28095
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { axisFormats, locale, truncateLabels = true, horizontalChart }) {
27879
28096
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
27880
- const options = {
28097
+ const chartOptions = {
27881
28098
  // https://www.chartjs.org/docs/latest/general/responsive.html
27882
28099
  responsive: true, // will resize when its container is resized
27883
28100
  maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
@@ -27924,8 +28141,10 @@ stores.inject(MyMetaStore, storeInstance);
27924
28141
  if (!yLabel) {
27925
28142
  yLabel = tooltipItem.parsed;
27926
28143
  }
27927
- const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
27928
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
28144
+ const axisId = horizontalChart
28145
+ ? tooltipItem.dataset.xAxisID
28146
+ : tooltipItem.dataset.yAxisID;
28147
+ const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
27929
28148
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
27930
28149
  },
27931
28150
  },
@@ -27934,7 +28153,7 @@ stores.inject(MyMetaStore, storeInstance);
27934
28153
  };
27935
28154
  return {
27936
28155
  type: chart.type,
27937
- options,
28156
+ options: chartOptions,
27938
28157
  data: {
27939
28158
  labels: truncateLabels ? labels.map(truncateLabel) : labels,
27940
28159
  datasets: [],
@@ -27993,7 +28212,8 @@ stores.inject(MyMetaStore, storeInstance);
27993
28212
  * Get the format to apply to the the dataset values. This format is defined as the first format
27994
28213
  * found in the dataset ranges that isn't a date format.
27995
28214
  */
27996
- function getChartDatasetFormat(getters, dataSets) {
28215
+ function getChartDatasetFormat(getters, allDataSets, axis) {
28216
+ const dataSets = allDataSets.filter((ds) => (axis === "right") === !!ds.rightYAxis);
27997
28217
  for (const ds of dataSets) {
27998
28218
  const formatsInDataset = getters.getRangeFormats(ds.dataRange);
27999
28219
  const format = formatsInDataset.find((f) => f !== undefined && !isDateTimeFormat(f));
@@ -28247,12 +28467,16 @@ stores.inject(MyMetaStore, storeInstance);
28247
28467
  if (chart.aggregated) {
28248
28468
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28249
28469
  }
28250
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
28470
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28471
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28251
28472
  const locale = getters.getLocale();
28252
- const localeFormat = { format: dataSetFormat, locale };
28253
28473
  const fontColor = chartFontColor(chart.background);
28474
+ const axisFormats = chart.horizontal
28475
+ ? { x: leftAxisFormat || rightAxisFormat }
28476
+ : { y: leftAxisFormat, y1: rightAxisFormat };
28254
28477
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
28255
- ...localeFormat,
28478
+ locale,
28479
+ axisFormats,
28256
28480
  horizontalChart: chart.horizontal,
28257
28481
  });
28258
28482
  const legend = {
@@ -28273,51 +28497,27 @@ stores.inject(MyMetaStore, storeInstance);
28273
28497
  };
28274
28498
  config.options.indexAxis = chart.horizontal ? "y" : "x";
28275
28499
  config.options.scales = {};
28276
- const labelsAxis = { ticks: { padding: 5, color: fontColor } };
28277
- const valuesAxis = {
28278
- beginAtZero: true, // the origin of the y axis is always zero
28279
- ticks: {
28280
- color: fontColor,
28281
- callback: formatTickValue(localeFormat),
28282
- },
28283
- };
28284
- const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
28285
- const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
28286
- const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
28287
- config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
28288
- if (useLeftAxis) {
28289
- config.options.scales.y = {
28290
- ...yAxis,
28291
- position: "left",
28292
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
28293
- };
28294
- }
28295
- if (useRightAxis) {
28296
- config.options.scales.y1 = {
28297
- ...yAxis,
28298
- position: "right",
28299
- title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
28300
- };
28500
+ const definition = chart.getDefinition();
28501
+ const options = { stacked: chart.stacked, locale };
28502
+ if (chart.horizontal) {
28503
+ const format = leftAxisFormat || rightAxisFormat;
28504
+ config.options.scales.x = getChartAxis(definition, "bottom", "values", { ...options, format });
28505
+ config.options.scales.y = getChartAxis(definition, "left", "labels", options);
28301
28506
  }
28302
- if (chart.stacked) {
28303
- // @ts-ignore chart.js type is broken
28304
- config.options.scales.x.stacked = true;
28305
- if (useLeftAxis) {
28306
- // @ts-ignore chart.js type is broken
28307
- config.options.scales.y.stacked = true;
28308
- }
28309
- if (useRightAxis) {
28310
- // @ts-ignore chart.js type is broken
28311
- config.options.scales.y1.stacked = true;
28312
- }
28507
+ else {
28508
+ config.options.scales.x = getChartAxis(definition, "bottom", "labels", options);
28509
+ const leftAxisOptions = { ...options, format: leftAxisFormat };
28510
+ config.options.scales.y = getChartAxis(definition, "left", "values", leftAxisOptions);
28511
+ const rightAxisOptions = { ...options, format: rightAxisFormat };
28512
+ config.options.scales.y1 = getChartAxis(definition, "right", "values", rightAxisOptions);
28313
28513
  }
28514
+ config.options.scales = removeFalsyAttributes(config.options.scales);
28314
28515
  config.options.plugins.chartShowValuesPlugin = {
28315
28516
  showValues: chart.showValues,
28316
28517
  background: chart.background,
28317
28518
  horizontal: chart.horizontal,
28318
- callback: formatTickValue(localeFormat),
28519
+ callback: formatChartDatasetValue(axisFormats, locale),
28319
28520
  };
28320
- const definition = chart.getDefinition();
28321
28521
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28322
28522
  const trendDatasets = [];
28323
28523
  for (const index in dataSetsValues) {
@@ -28335,9 +28535,8 @@ stores.inject(MyMetaStore, storeInstance);
28335
28535
  const label = definition.dataSets[index].label;
28336
28536
  dataset.label = label;
28337
28537
  }
28338
- if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
28339
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
28340
- }
28538
+ dataset.yAxisID = chart.horizontal ? "y" : definition.dataSets[index].yAxisId || "y";
28539
+ dataset.xAxisID = "x";
28341
28540
  const trend = definition.dataSets?.[index].trend;
28342
28541
  if (!trend?.display || chart.horizontal) {
28343
28542
  continue;
@@ -28353,7 +28552,7 @@ stores.inject(MyMetaStore, storeInstance);
28353
28552
  */
28354
28553
  const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset.data.length));
28355
28554
  config.options.scales[TREND_LINE_XAXIS_ID] = {
28356
- ...xAxis,
28555
+ ...config.options.scales.x,
28357
28556
  labels: Array(maxLength).fill(""),
28358
28557
  offset: false,
28359
28558
  display: false,
@@ -28643,8 +28842,10 @@ stores.inject(MyMetaStore, storeInstance);
28643
28842
  }
28644
28843
  const locale = getters.getLocale();
28645
28844
  const truncateLabels = axisType === "category";
28646
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
28647
- const options = { format: dataSetFormat, locale, truncateLabels };
28845
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28846
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28847
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
28848
+ const options = { locale, truncateLabels, axisFormats };
28648
28849
  const fontColor = chartFontColor(chart.background);
28649
28850
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
28650
28851
  const legend = {
@@ -28674,52 +28875,18 @@ stores.inject(MyMetaStore, storeInstance);
28674
28875
  displayLegend: chart.legendPosition === "top",
28675
28876
  }),
28676
28877
  };
28677
- const xAxis = {
28678
- ticks: {
28679
- padding: 5,
28680
- color: fontColor,
28681
- },
28682
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
28683
- };
28878
+ const definition = chart.getDefinition();
28879
+ const stacked = "stacked" in chart && chart.stacked;
28684
28880
  config.options.scales = {
28685
- x: xAxis,
28686
- };
28687
- const yAxis = {
28688
- beginAtZero: true, // the origin of the y axis is always zero
28689
- ticks: {
28690
- color: fontColor,
28691
- callback: formatTickValue(options),
28692
- },
28881
+ x: getChartAxis(definition, "bottom", "labels", { locale }),
28882
+ y: getChartAxis(definition, "left", "values", { locale, stacked, format: leftAxisFormat }),
28883
+ y1: getChartAxis(definition, "right", "values", { locale, stacked, format: rightAxisFormat }),
28693
28884
  };
28694
- const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
28695
- if (useLeftAxis) {
28696
- config.options.scales.y = {
28697
- ...yAxis,
28698
- position: "left",
28699
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
28700
- };
28701
- }
28702
- if (useRightAxis) {
28703
- config.options.scales.y1 = {
28704
- ...yAxis,
28705
- position: "right",
28706
- title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
28707
- };
28708
- }
28709
- if ("stacked" in chart && chart.stacked) {
28710
- if (useLeftAxis) {
28711
- // @ts-ignore chart.js type is broken
28712
- config.options.scales.y.stacked = true;
28713
- }
28714
- if (useRightAxis) {
28715
- // @ts-ignore chart.js type is broken
28716
- config.options.scales.y1.stacked = true;
28717
- }
28718
- }
28885
+ config.options.scales = removeFalsyAttributes(config.options.scales);
28719
28886
  config.options.plugins.chartShowValuesPlugin = {
28720
28887
  showValues: chart.showValues,
28721
28888
  background: chart.background,
28722
- callback: formatTickValue(options),
28889
+ callback: formatChartDatasetValue(axisFormats, locale),
28723
28890
  };
28724
28891
  if (chart.dataSetsHaveTitle &&
28725
28892
  dataSetsValues[0] &&
@@ -28746,7 +28913,7 @@ stores.inject(MyMetaStore, storeInstance);
28746
28913
  label = toNumber(label, locale);
28747
28914
  }
28748
28915
  const formattedX = formatValue(label, { locale, format: labelFormat });
28749
- const formattedY = formatValue(dataSetPoint, { locale, format: dataSetFormat });
28916
+ const formattedY = formatValue(dataSetPoint, { locale, format: leftAxisFormat });
28750
28917
  const dataSetTitle = tooltipItem.dataset.label;
28751
28918
  return formattedX
28752
28919
  ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
@@ -28756,7 +28923,6 @@ stores.inject(MyMetaStore, storeInstance);
28756
28923
  const areaChart = "fillArea" in chart ? chart.fillArea : false;
28757
28924
  const stackedChart = "stacked" in chart ? chart.stacked : false;
28758
28925
  const cumulative = "cumulative" in chart ? chart.cumulative : false;
28759
- const definition = chart.getDefinition();
28760
28926
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28761
28927
  for (let [index, { label, data }] of dataSetsValues.entries()) {
28762
28928
  const color = colors.next();
@@ -28797,9 +28963,7 @@ stores.inject(MyMetaStore, storeInstance);
28797
28963
  const label = definition.dataSets[index].label;
28798
28964
  dataset.label = label;
28799
28965
  }
28800
- if (definition.dataSets?.[index]?.yAxisId) {
28801
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
28802
- }
28966
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId || "y";
28803
28967
  const trend = definition.dataSets?.[index].trend;
28804
28968
  if (!trend?.display) {
28805
28969
  continue;
@@ -28816,7 +28980,7 @@ stores.inject(MyMetaStore, storeInstance);
28816
28980
  * set so that the second axis points match the classical x axis
28817
28981
  */
28818
28982
  config.options.scales[TREND_LINE_XAXIS_ID] = {
28819
- ...xAxis,
28983
+ ...config.options.scales.x,
28820
28984
  type: "category",
28821
28985
  labels: range(0, maxLength).map((x) => x.toString()),
28822
28986
  offset: false,
@@ -28838,10 +29002,6 @@ stores.inject(MyMetaStore, storeInstance);
28838
29002
  return {
28839
29003
  chartJsConfig: config,
28840
29004
  background: chart.background || BACKGROUND_CHART_COLOR,
28841
- dataSetsValues,
28842
- labelValues,
28843
- dataSetFormat,
28844
- labelFormat,
28845
29005
  };
28846
29006
  }
28847
29007
 
@@ -28899,6 +29059,7 @@ stores.inject(MyMetaStore, storeInstance);
28899
29059
  ranges.push({
28900
29060
  ...this.dataSetDesign?.[i],
28901
29061
  dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
29062
+ type: this.dataSetDesign?.[i]?.type ?? (i ? "line" : "bar"),
28902
29063
  });
28903
29064
  }
28904
29065
  return {
@@ -28944,9 +29105,13 @@ stores.inject(MyMetaStore, storeInstance);
28944
29105
  return new ComboChart(definition, this.sheetId, this.getters);
28945
29106
  }
28946
29107
  static getDefinitionFromContextCreation(context) {
29108
+ const dataSets = (context.range ?? []).map((ds, index) => ({
29109
+ ...ds,
29110
+ type: index ? "line" : "bar",
29111
+ }));
28947
29112
  return {
28948
29113
  background: context.background,
28949
- dataSets: context.range ?? [],
29114
+ dataSets,
28950
29115
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
28951
29116
  aggregated: context.aggregated,
28952
29117
  legendPosition: context.legendPosition ?? "top",
@@ -28969,10 +29134,8 @@ stores.inject(MyMetaStore, storeInstance);
28969
29134
  }
28970
29135
  }
28971
29136
  function createComboChartRuntime(chart, getters) {
28972
- const mainDataSetFormat = chart.dataSets.length
28973
- ? getChartDatasetFormat(getters, [chart.dataSets[0]])
28974
- : undefined;
28975
- const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
29137
+ const mainDataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29138
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28976
29139
  const locale = getters.getLocale();
28977
29140
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
28978
29141
  let labels = labelValues.formattedValues;
@@ -28986,12 +29149,11 @@ stores.inject(MyMetaStore, storeInstance);
28986
29149
  if (chart.aggregated) {
28987
29150
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28988
29151
  }
28989
- const localeFormat = { format: mainDataSetFormat, locale };
28990
29152
  const fontColor = chartFontColor(chart.background);
28991
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
29153
+ const axisFormats = { y: mainDataSetFormat, y1: lineDataSetsFormat };
29154
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, { locale, axisFormats });
28992
29155
  const legend = {
28993
29156
  labels: { color: fontColor },
28994
- reverse: true,
28995
29157
  };
28996
29158
  if (chart.legendPosition === "none") {
28997
29159
  legend.display = false;
@@ -29006,52 +29168,17 @@ stores.inject(MyMetaStore, storeInstance);
29006
29168
  displayLegend: chart.legendPosition === "top",
29007
29169
  }),
29008
29170
  };
29171
+ const definition = chart.getDefinition();
29009
29172
  config.options.scales = {
29010
- x: {
29011
- ticks: {
29012
- padding: 5,
29013
- color: fontColor,
29014
- },
29015
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
29016
- },
29173
+ x: getChartAxis(definition, "bottom", "labels", { locale }),
29174
+ y: getChartAxis(definition, "left", "values", { locale, format: mainDataSetFormat }),
29175
+ y1: getChartAxis(definition, "right", "values", { locale, format: lineDataSetsFormat }),
29017
29176
  };
29018
- const leftVerticalAxis = {
29019
- beginAtZero: true, // the origin of the y axis is always zero
29020
- ticks: {
29021
- color: fontColor,
29022
- callback: formatTickValue({ format: mainDataSetFormat, locale }),
29023
- },
29024
- };
29025
- const rightVerticalAxis = {
29026
- beginAtZero: true, // the origin of the y axis is always zero
29027
- ticks: {
29028
- color: fontColor,
29029
- callback: formatTickValue({ format: lineDataSetsFormat, locale }),
29030
- },
29031
- };
29032
- const definition = chart.getDefinition();
29033
- const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
29034
- if (useLeftAxis) {
29035
- config.options.scales.y = {
29036
- ...leftVerticalAxis,
29037
- position: "left",
29038
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
29039
- };
29040
- }
29041
- if (useRightAxis) {
29042
- config.options.scales.y1 = {
29043
- ...rightVerticalAxis,
29044
- position: "right",
29045
- grid: {
29046
- display: false,
29047
- },
29048
- title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
29049
- };
29050
- }
29177
+ config.options.scales = removeFalsyAttributes(config.options.scales);
29051
29178
  config.options.plugins.chartShowValuesPlugin = {
29052
29179
  showValues: chart.showValues,
29053
29180
  background: chart.background,
29054
- callback: formatTickValue({ format: mainDataSetFormat, locale }),
29181
+ callback: formatChartDatasetValue(axisFormats, locale),
29055
29182
  };
29056
29183
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
29057
29184
  let maxLength = 0;
@@ -29059,14 +29186,15 @@ stores.inject(MyMetaStore, storeInstance);
29059
29186
  for (let [index, { label, data }] of dataSetsValues.entries()) {
29060
29187
  const design = definition.dataSets[index];
29061
29188
  const color = colors.next();
29189
+ const type = design?.type ?? "line";
29062
29190
  const dataset = {
29063
29191
  label: design?.label ?? label,
29064
29192
  data,
29065
29193
  borderColor: color,
29066
29194
  backgroundColor: color,
29067
29195
  yAxisID: design?.yAxisId ?? "y",
29068
- type: index === 0 ? "bar" : "line",
29069
- order: -index,
29196
+ type,
29197
+ order: type === "bar" ? dataSetsValues.length + index : index,
29070
29198
  };
29071
29199
  config.data.datasets.push(dataset);
29072
29200
  const trend = definition.dataSets?.[index].trend;
@@ -29679,7 +29807,7 @@ stores.inject(MyMetaStore, storeInstance);
29679
29807
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
29680
29808
  }
29681
29809
  ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
29682
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
29810
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29683
29811
  const locale = getters.getLocale();
29684
29812
  const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
29685
29813
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
@@ -29834,7 +29962,7 @@ stores.inject(MyMetaStore, storeInstance);
29834
29962
  return tooltipLabelCallback(tooltipItem);
29835
29963
  };
29836
29964
  const callback = config.options.plugins.chartShowValuesPlugin.callback;
29837
- config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
29965
+ config.options.plugins.chartShowValuesPlugin.callback = (x, axisId) => callback(Math.abs(x), axisId);
29838
29966
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29839
29967
  }
29840
29968
 
@@ -30225,7 +30353,8 @@ stores.inject(MyMetaStore, storeInstance);
30225
30353
  if (chart.showSubTotals) {
30226
30354
  labels.push(_t("Subtotal"));
30227
30355
  }
30228
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
30356
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left") ||
30357
+ getChartDatasetFormat(getters, chart.dataSets, "right");
30229
30358
  const locale = getters.getLocale();
30230
30359
  const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
30231
30360
  const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
@@ -35295,6 +35424,7 @@ stores.inject(MyMetaStore, storeInstance);
35295
35424
  <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
35296
35425
  </svg>
35297
35426
  `;
35427
+ const CHECKBOX_WIDTH = 14;
35298
35428
  css /* scss */ `
35299
35429
  label.o-checkbox {
35300
35430
  input {
@@ -35302,8 +35432,8 @@ stores.inject(MyMetaStore, storeInstance);
35302
35432
  -webkit-appearance: none;
35303
35433
  -moz-appearance: none;
35304
35434
  border-radius: 0;
35305
- width: 14px;
35306
- height: 14px;
35435
+ width: ${CHECKBOX_WIDTH}px;
35436
+ height: ${CHECKBOX_WIDTH}px;
35307
35437
  vertical-align: top;
35308
35438
  box-sizing: border-box;
35309
35439
  outline: none;
@@ -37213,6 +37343,32 @@ stores.inject(MyMetaStore, storeInstance);
37213
37343
  }
37214
37344
  }
37215
37345
 
37346
+ class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
37347
+ static template = "o-spreadsheet-ComboChartDesignPanel";
37348
+ seriesTypeChoices = [
37349
+ { value: "bar", label: _t("Bar") },
37350
+ { value: "line", label: _t("Line") },
37351
+ ];
37352
+ updateDataSeriesType(type) {
37353
+ const dataSets = [...this.props.definition.dataSets];
37354
+ if (!dataSets?.[this.state.index]) {
37355
+ return;
37356
+ }
37357
+ dataSets[this.state.index] = {
37358
+ ...dataSets[this.state.index],
37359
+ type,
37360
+ };
37361
+ this.props.updateChart(this.props.figureId, { dataSets });
37362
+ }
37363
+ getDataSeriesType() {
37364
+ const dataSets = this.props.definition.dataSets;
37365
+ if (!dataSets?.[this.state.index]) {
37366
+ return "bar";
37367
+ }
37368
+ return dataSets[this.state.index].type ?? "line";
37369
+ }
37370
+ }
37371
+
37216
37372
  class GaugeChartConfigPanel extends owl.Component {
37217
37373
  static template = "o-spreadsheet-GaugeChartConfigPanel";
37218
37374
  static components = { ChartErrorSection, ChartDataSeries };
@@ -37639,7 +37795,7 @@ stores.inject(MyMetaStore, storeInstance);
37639
37795
  })
37640
37796
  .add("combo", {
37641
37797
  configuration: GenericChartConfigPanel,
37642
- design: ChartWithAxisDesignPanel,
37798
+ design: ComboChartDesignPanel,
37643
37799
  })
37644
37800
  .add("pie", {
37645
37801
  configuration: GenericChartConfigPanel,
@@ -37925,6 +38081,7 @@ stores.inject(MyMetaStore, storeInstance);
37925
38081
  "stopComposerRangeSelection",
37926
38082
  "cancelEdition",
37927
38083
  "cycleReferences",
38084
+ "toggleEditionMode",
37928
38085
  "changeComposerCursorSelection",
37929
38086
  "replaceComposerCursorSelection",
37930
38087
  ];
@@ -38080,6 +38237,42 @@ stores.inject(MyMetaStore, storeInstance);
38080
38237
  }
38081
38238
  this.setCurrentContent(updated.content, updated.selection);
38082
38239
  }
38240
+ toggleEditionMode() {
38241
+ if (this.editionMode === "inactive")
38242
+ return;
38243
+ const start = Math.min(this.selectionStart, this.selectionEnd);
38244
+ const end = Math.max(this.selectionStart, this.selectionEnd);
38245
+ const refToken = [...this.currentTokens]
38246
+ .reverse()
38247
+ .find((tk) => tk.end >= start && end >= tk.start && tk.type === "REFERENCE");
38248
+ if (this.editionMode === "editing" && refToken) {
38249
+ const currentSheetId = this.getters.getActiveSheetId();
38250
+ const { sheetName, xc } = splitReference(refToken.value);
38251
+ const sheetId = this.getters.getSheetIdByName(sheetName);
38252
+ if (sheetId && sheetId !== currentSheetId) {
38253
+ this.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: currentSheetId, sheetIdTo: sheetId });
38254
+ }
38255
+ // move cursor to the right part of the token
38256
+ this.selectionStart = this.selectionEnd = refToken.end;
38257
+ const zone = this.getters.getRangeFromSheetXC(this.sheetId, xc).zone;
38258
+ this.captureSelection(zone);
38259
+ this.editionMode = "selecting";
38260
+ }
38261
+ else {
38262
+ this.editionMode = "editing";
38263
+ }
38264
+ }
38265
+ captureSelection(zone, col, row) {
38266
+ this.model.selection.capture(this, {
38267
+ cell: { col: col || zone.left, row: row || zone.right },
38268
+ zone,
38269
+ }, {
38270
+ handleEvent: this.handleEvent.bind(this),
38271
+ release: () => {
38272
+ this._stopEdition();
38273
+ },
38274
+ });
38275
+ }
38083
38276
  isSelectionValid(length, start, end) {
38084
38277
  return start >= 0 && start <= length && end >= 0 && end <= length;
38085
38278
  }
@@ -38118,12 +38311,7 @@ stores.inject(MyMetaStore, storeInstance);
38118
38311
  this.setContent(str || this.initialContent, selection);
38119
38312
  this.colorIndexByRange = {};
38120
38313
  const zone = positionToZone({ col: this.col, row: this.row });
38121
- this.model.selection.capture(this, { cell: { col: this.col, row: this.row }, zone }, {
38122
- handleEvent: this.handleEvent.bind(this),
38123
- release: () => {
38124
- this._stopEdition();
38125
- },
38126
- });
38314
+ this.captureSelection(zone, col, row);
38127
38315
  }
38128
38316
  _stopEdition() {
38129
38317
  if (this.editionMode !== "inactive") {
@@ -42818,8 +43006,9 @@ stores.inject(MyMetaStore, storeInstance);
42818
43006
  * This function converts a list of data entry into a spreadsheet pivot table.
42819
43007
  */
42820
43008
  function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
43009
+ const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
42821
43010
  const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
42822
- computeWidthOfColumnsNodes(columnsTree, definition.measures.length);
43011
+ computeWidthOfColumnsNodes(columnsTree, measureIds.length);
42823
43012
  const cols = columnsTreeToColumns(columnsTree, definition);
42824
43013
  const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
42825
43014
  // Add the total row
@@ -42828,7 +43017,6 @@ stores.inject(MyMetaStore, storeInstance);
42828
43017
  values: [],
42829
43018
  indent: 0,
42830
43019
  });
42831
- const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
42832
43020
  const fieldsType = {};
42833
43021
  for (const columns of definition.columns) {
42834
43022
  fieldsType[columns.fieldName] = columns.type;
@@ -43589,7 +43777,7 @@ stores.inject(MyMetaStore, storeInstance);
43589
43777
  onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
43590
43778
  dateGranularities: [...dateGranularities],
43591
43779
  datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
43592
- isMeasureCandidate: (field) => !["date", "boolean"].includes(field.type),
43780
+ isMeasureCandidate: (field) => !["datetime", "boolean"].includes(field.type),
43593
43781
  isGroupable: () => true,
43594
43782
  });
43595
43783
 
@@ -46118,13 +46306,10 @@ stores.inject(MyMetaStore, storeInstance);
46118
46306
  }
46119
46307
  }
46120
46308
 
46121
- const CHECKBOX_WIDTH = 15;
46122
46309
  const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
46123
46310
  css /* scss */ `
46124
46311
  .o-dv-checkbox {
46125
46312
  box-sizing: border-box !important;
46126
- width: ${CHECKBOX_WIDTH}px;
46127
- height: ${CHECKBOX_WIDTH}px;
46128
46313
  accent-color: #808080;
46129
46314
  margin: ${MARGIN}px;
46130
46315
  /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
@@ -46133,13 +46318,15 @@ stores.inject(MyMetaStore, storeInstance);
46133
46318
  `;
46134
46319
  class DataValidationCheckbox extends owl.Component {
46135
46320
  static template = "o-spreadsheet-DataValidationCheckbox";
46321
+ static components = {
46322
+ Checkbox,
46323
+ };
46136
46324
  static props = {
46137
46325
  cellPosition: Object,
46138
46326
  };
46139
- onCheckboxChange(ev) {
46140
- const newValue = ev.target.checked;
46327
+ onCheckboxChange(value) {
46141
46328
  const { sheetId, col, row } = this.props.cellPosition;
46142
- const cellContent = newValue ? "TRUE" : "FALSE";
46329
+ const cellContent = value ? "TRUE" : "FALSE";
46143
46330
  this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
46144
46331
  }
46145
46332
  get checkBoxValue() {
@@ -46953,7 +47140,11 @@ stores.inject(MyMetaStore, storeInstance);
46953
47140
  class PaintFormatStore extends SpreadsheetStore {
46954
47141
  mutators = ["activate", "cancel", "pasteFormat"];
46955
47142
  highlightStore = this.get(HighlightStore);
46956
- cellClipboardHandler = new CellClipboardHandler(this.getters, this.model.dispatch);
47143
+ clipboardHandlers = [
47144
+ new CellClipboardHandler(this.getters, this.model.dispatch),
47145
+ new BorderClipboardHandler(this.getters, this.model.dispatch),
47146
+ new TableClipboardHandler(this.getters, this.model.dispatch),
47147
+ ];
46957
47148
  status = "inactive";
46958
47149
  copiedData;
46959
47150
  constructor(get) {
@@ -46974,10 +47165,12 @@ stores.inject(MyMetaStore, storeInstance);
46974
47165
  pasteFormat(target) {
46975
47166
  if (this.copiedData) {
46976
47167
  const sheetId = this.getters.getActiveSheetId();
46977
- this.cellClipboardHandler.paste({ zones: target, sheetId }, this.copiedData, {
46978
- isCutOperation: false,
46979
- pasteOption: "onlyFormat",
46980
- });
47168
+ for (const handler of this.clipboardHandlers) {
47169
+ handler.paste({ zones: target, sheetId }, this.copiedData, {
47170
+ isCutOperation: false,
47171
+ pasteOption: "onlyFormat",
47172
+ });
47173
+ }
46981
47174
  }
46982
47175
  if (this.status === "oneOff") {
46983
47176
  this.cancel();
@@ -46989,7 +47182,11 @@ stores.inject(MyMetaStore, storeInstance);
46989
47182
  copyFormats() {
46990
47183
  const sheetId = this.getters.getActiveSheetId();
46991
47184
  const zones = this.getters.getSelectedZones();
46992
- return this.cellClipboardHandler.copy(getClipboardDataPositions(sheetId, zones));
47185
+ const copiedData = {};
47186
+ for (const handler of this.clipboardHandlers) {
47187
+ Object.assign(copiedData, handler.copy(getClipboardDataPositions(sheetId, zones)));
47188
+ }
47189
+ return copiedData;
46993
47190
  }
46994
47191
  get highlights() {
46995
47192
  const data = this.copiedData;
@@ -49797,12 +49994,10 @@ stores.inject(MyMetaStore, storeInstance);
49797
49994
  */
49798
49995
  class CorePlugin extends BasePlugin {
49799
49996
  getters;
49800
- uuidGenerator;
49801
- constructor({ getters, stateObserver, range, dispatch, canDispatch, uuidGenerator, }) {
49997
+ constructor({ getters, stateObserver, range, dispatch, canDispatch }) {
49802
49998
  super(stateObserver, dispatch, canDispatch);
49803
49999
  range.addRangeProvider(this.adaptRanges.bind(this));
49804
50000
  this.getters = getters;
49805
- this.uuidGenerator = uuidGenerator;
49806
50001
  }
49807
50002
  // ---------------------------------------------------------------------------
49808
50003
  // Import/Export
@@ -54300,6 +54495,7 @@ stores.inject(MyMetaStore, storeInstance);
54300
54495
  }
54301
54496
  }
54302
54497
 
54498
+ let nextTableId = 1;
54303
54499
  class TablePlugin extends CorePlugin {
54304
54500
  static getters = ["getCoreTable", "getCoreTables", "getCoreTableMatchingTopLeft"];
54305
54501
  tables = {};
@@ -54367,7 +54563,7 @@ stores.inject(MyMetaStore, storeInstance);
54367
54563
  const union = this.getters.getRangesUnion(ranges);
54368
54564
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
54369
54565
  this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
54370
- const id = this.uuidGenerator.uuidv4();
54566
+ const id = `${nextTableId++}`;
54371
54567
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
54372
54568
  const newTable = cmd.tableType === "dynamic"
54373
54569
  ? this.createDynamicTable(id, union, config)
@@ -54520,7 +54716,7 @@ stores.inject(MyMetaStore, storeInstance);
54520
54716
  filters = [];
54521
54717
  for (const i of range(zone.left, zone.right + 1)) {
54522
54718
  const filterZone = { ...zone, left: i, right: i };
54523
- const uid = this.uuidGenerator.uuidv4();
54719
+ const uid = `${nextTableId++}`;
54524
54720
  filters.push(this.createFilterFromZone(uid, tableRange.sheetId, filterZone, config));
54525
54721
  }
54526
54722
  }
@@ -54585,7 +54781,7 @@ stores.inject(MyMetaStore, storeInstance);
54585
54781
  ? table.filters.find((f) => f.col === i)
54586
54782
  : undefined;
54587
54783
  const filterZone = { ...tableZone, left: i, right: i };
54588
- const filterId = oldFilter?.id || this.uuidGenerator.uuidv4();
54784
+ const filterId = oldFilter?.id || `${nextTableId++}`;
54589
54785
  filters.push(this.createFilterFromZone(filterId, tableRange.sheetId, filterZone, config));
54590
54786
  }
54591
54787
  }
@@ -54686,7 +54882,7 @@ stores.inject(MyMetaStore, storeInstance);
54686
54882
  if (filters.length < zoneToDimension(tableZone).numberOfCols) {
54687
54883
  for (let col = tableZone.left; col <= tableZone.right; col++) {
54688
54884
  if (!filters.find((filter) => filter.col === col)) {
54689
- const uid = this.uuidGenerator.uuidv4();
54885
+ const uid = `${nextTableId++}`;
54690
54886
  const filterZone = { ...tableZone, left: col, right: col };
54691
54887
  filters.push(this.createFilterFromZone(uid, sheetId, filterZone, table.config));
54692
54888
  }
@@ -54702,7 +54898,7 @@ stores.inject(MyMetaStore, storeInstance);
54702
54898
  import(data) {
54703
54899
  for (const sheet of data.sheets) {
54704
54900
  for (const tableData of sheet.tables || []) {
54705
- const uuid = this.uuidGenerator.uuidv4();
54901
+ const uuid = `${nextTableId++}`;
54706
54902
  const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
54707
54903
  const range = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
54708
54904
  const tableType = tableData.type || "static";
@@ -59051,7 +59247,7 @@ stores.inject(MyMetaStore, storeInstance);
59051
59247
  const ranking = {};
59052
59248
  const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
59053
59249
  const secondaryDimension = mainDimension === "row" ? "column" : "row";
59054
- let pivotCells = this.getPivotValueCells();
59250
+ let pivotCells = this.getPivotValueCells(measure.id);
59055
59251
  if (mainDimension === "column") {
59056
59252
  // Transpose the pivot cells so we can do the same operations on the columns as on the rows
59057
59253
  // This means that we need to transpose back the ranking at the end
@@ -59095,7 +59291,7 @@ stores.inject(MyMetaStore, storeInstance);
59095
59291
  const cellsRunningTotals = {};
59096
59292
  const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
59097
59293
  const secondaryDimension = mainDimension === "row" ? "column" : "row";
59098
- let pivotCells = this.getPivotValueCells();
59294
+ let pivotCells = this.getPivotValueCells(measure.id);
59099
59295
  if (mainDimension === "column") {
59100
59296
  // Transpose the pivot cells so we can do the same operations on the columns as on the rows
59101
59297
  // This means that we need to transpose back the totals at the end
@@ -59174,10 +59370,10 @@ stores.inject(MyMetaStore, storeInstance);
59174
59370
  const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
59175
59371
  return comparedValueNumber;
59176
59372
  }
59177
- getPivotValueCells() {
59373
+ getPivotValueCells(measureId) {
59178
59374
  return this.getTableStructure()
59179
59375
  .getPivotCells()
59180
- .map((col) => col.filter((cell) => cell.type === "VALUE"))
59376
+ .map((col) => col.filter((cell) => cell.type === "VALUE" && cell.measure === measureId))
59181
59377
  .filter((col) => col.length > 0);
59182
59378
  }
59183
59379
  measureValueToNumber(result) {
@@ -60945,6 +61141,7 @@ stores.inject(MyMetaStore, storeInstance);
60945
61141
  case "REMOTE_REVISION":
60946
61142
  case "REVISION_REDONE":
60947
61143
  case "REVISION_UNDONE":
61144
+ case "SNAPSHOT_CREATED":
60948
61145
  return this.processedRevisions.has(message.nextRevisionId);
60949
61146
  default:
60950
61147
  return false;
@@ -61507,7 +61704,7 @@ stores.inject(MyMetaStore, storeInstance);
61507
61704
  const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
61508
61705
  const formulaId = this.getters.getPivotFormulaId(newPivotId);
61509
61706
  const newPivotName = this.getters.getPivotName(newPivotId);
61510
- this.dispatch("CREATE_SHEET", {
61707
+ const result = this.dispatch("CREATE_SHEET", {
61511
61708
  sheetId: newSheetId,
61512
61709
  name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
61513
61710
  newPivotName,
@@ -61515,20 +61712,19 @@ stores.inject(MyMetaStore, storeInstance);
61515
61712
  })),
61516
61713
  position,
61517
61714
  });
61518
- this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
61519
- this.dispatch("UPDATE_CELL", {
61520
- sheetId: newSheetId,
61521
- col: 0,
61522
- row: 0,
61523
- content: `=PIVOT(${formulaId})`,
61524
- });
61715
+ if (result.isSuccessful) {
61716
+ this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
61717
+ const pivot = this.getters.getPivot(pivotId);
61718
+ this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.getTableStructure().export(), "dynamic");
61719
+ }
61525
61720
  }
61526
61721
  getPivotDuplicateSheetName(pivotName) {
61527
61722
  let i = 1;
61528
61723
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
61529
- let name = pivotName;
61724
+ const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
61725
+ let name = sanitizedName;
61530
61726
  while (names.includes(name)) {
61531
- name = `${pivotName} (${i})`;
61727
+ name = `${sanitizedName} (${i})`;
61532
61728
  i++;
61533
61729
  }
61534
61730
  return name;
@@ -70881,6 +71077,15 @@ stores.inject(MyMetaStore, storeInstance);
70881
71077
  ["id", i + 1], // id cannot be 0
70882
71078
  ["name", colName],
70883
71079
  ];
71080
+ if (table.config.totalRow) {
71081
+ // Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
71082
+ // `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
71083
+ const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
71084
+ const colTotalContent = sheetData.cells[colTotalXc]?.content;
71085
+ if (colTotalContent?.startsWith("=")) {
71086
+ colAttributes.push(["totalsRowFunction", "custom"]);
71087
+ }
71088
+ }
70884
71089
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
70885
71090
  }
70886
71091
  return escapeXml /*xml*/ `
@@ -70975,8 +71180,9 @@ stores.inject(MyMetaStore, storeInstance);
70975
71180
  }
70976
71181
  else if (cell.content && cell.content !== "") {
70977
71182
  const isTableHeader = isCellTableHeader(c, r, sheet);
71183
+ const isTableTotal = isCellTableTotal(c, r, sheet);
70978
71184
  const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
70979
- ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
71185
+ ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
70980
71186
  }
70981
71187
  attributes.push(...additionalAttrs);
70982
71188
  // prettier-ignore
@@ -71010,6 +71216,16 @@ stores.inject(MyMetaStore, storeInstance);
71010
71216
  return isInside(col, row, headerZone);
71011
71217
  });
71012
71218
  }
71219
+ function isCellTableTotal(col, row, sheet) {
71220
+ return sheet.tables.some((table) => {
71221
+ if (!table.config.totalRow) {
71222
+ return false;
71223
+ }
71224
+ const zone = toZone(table.range);
71225
+ const totalZone = { ...zone, top: zone.bottom };
71226
+ return isInside(col, row, totalZone);
71227
+ });
71228
+ }
71013
71229
  function addHyperlinks(construct, data, sheetIndex) {
71014
71230
  const sheet = data.sheets[sheetIndex];
71015
71231
  const cells = sheet.cells;
@@ -71500,7 +71716,6 @@ stores.inject(MyMetaStore, storeInstance);
71500
71716
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
71501
71717
  isDashboard: () => this.config.mode === "dashboard",
71502
71718
  };
71503
- this.uuidGenerator.setIsFastStrategy(true);
71504
71719
  // Initiate stream processor
71505
71720
  this.selection = new SelectionStreamProcessorImpl(this.getters);
71506
71721
  this.coreHandlers.push(this.range);
@@ -71532,7 +71747,6 @@ stores.inject(MyMetaStore, storeInstance);
71532
71747
  this.handlers.push(plugin);
71533
71748
  this.uiHandlers.push(plugin);
71534
71749
  }
71535
- this.uuidGenerator.setIsFastStrategy(false);
71536
71750
  // starting plugins
71537
71751
  this.dispatch("START");
71538
71752
  // Model should be the last permanent subscriber in the list since he should render
@@ -71680,7 +71894,6 @@ stores.inject(MyMetaStore, storeInstance);
71680
71894
  range: this.range,
71681
71895
  dispatch: this.dispatchFromCorePlugin,
71682
71896
  canDispatch: this.canDispatch,
71683
- uuidGenerator: this.uuidGenerator,
71684
71897
  custom: this.config.custom,
71685
71898
  external: this.config.external,
71686
71899
  };
@@ -72174,9 +72387,9 @@ stores.inject(MyMetaStore, storeInstance);
72174
72387
  exports.tokenize = tokenize;
72175
72388
 
72176
72389
 
72177
- __info__.version = "18.1.0-alpha.0";
72178
- __info__.date = "2024-09-25T13:17:49.636Z";
72179
- __info__.hash = "288f0b7";
72390
+ __info__.version = "18.1.0-alpha.1";
72391
+ __info__.date = "2024-10-14T07:53:17.717Z";
72392
+ __info__.hash = "e9ce3aa";
72180
72393
 
72181
72394
 
72182
72395
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);