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