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

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.2
6
+ * @date 2024-10-24T08:53:21.828Z
7
+ * @hash 2a01250
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";
@@ -224,6 +330,7 @@
224
330
  const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#43C5B1";
225
331
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#EA6175";
226
332
  const LINE_FILL_TRANSPARENCY = 0.4;
333
+ const DEFAULT_WINDOW_SIZE = 2;
227
334
  // session
228
335
  const DEBOUNCE_TIME = 200;
229
336
  const MESSAGE_VERSION = 1;
@@ -269,7 +376,7 @@
269
376
  bandedRows: true,
270
377
  bandedColumns: false,
271
378
  styleId: "TableStyleMedium5",
272
- automaticAutofill: true,
379
+ automaticAutofill: false,
273
380
  };
274
381
  const DEFAULT_CURRENCY = {
275
382
  symbol: "$",
@@ -485,7 +592,7 @@
485
592
  */
486
593
  function parseSheetUrl(sheetLink) {
487
594
  if (sheetLink.startsWith(O_SPREADSHEET_LINK_PREFIX)) {
488
- return sheetLink.substr(O_SPREADSHEET_LINK_PREFIX.length);
595
+ return sheetLink.slice(O_SPREADSHEET_LINK_PREFIX.length);
489
596
  }
490
597
  throw new Error(`${sheetLink} is not a valid sheet link`);
491
598
  }
@@ -3050,8 +3157,7 @@
3050
3157
  const p2 = pMinus + pNumber + pCurrencyFormat;
3051
3158
  const p3 = pCurrencyFormat + pMinus + pNumber;
3052
3159
  const pNumberExp = "^(?:(?:" + [p1, p2, p3].join(")|(?:") + "))$";
3053
- const numberRegexp = new RegExp(pNumberExp, "i");
3054
- return numberRegexp;
3160
+ return new RegExp(pNumberExp, "i");
3055
3161
  });
3056
3162
  /**
3057
3163
  * Return true if the argument is a "number string".
@@ -5725,8 +5831,7 @@
5725
5831
  textWidthCache[font] = {};
5726
5832
  }
5727
5833
  if (textWidthCache[font][text] === undefined) {
5728
- const textWidth = context.measureText(text).width;
5729
- textWidthCache[font][text] = textWidth;
5834
+ textWidthCache[font][text] = context.measureText(text).width;
5730
5835
  }
5731
5836
  return textWidthCache[font][text];
5732
5837
  }
@@ -5969,136 +6074,22 @@
5969
6074
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
5970
6075
  * */
5971
6076
  class UuidGenerator {
5972
- isFastIdStrategy = false;
5973
- fastIdStart = 0;
5974
- setIsFastStrategy(isFast) {
5975
- this.isFastIdStrategy = isFast;
5976
- }
5977
6077
  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) {
6078
+ //@ts-ignore
6079
+ if (window.crypto && window.crypto.getRandomValues) {
5984
6080
  //@ts-ignore
5985
6081
  return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
5986
6082
  }
5987
6083
  else {
5988
6084
  // mainly for jest and other browsers that do not have the crypto functionality
5989
6085
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
5990
- var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
6086
+ const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
5991
6087
  return v.toString(16);
5992
6088
  });
5993
6089
  }
5994
6090
  }
5995
6091
  }
5996
6092
 
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
6093
  function getClipboardDataPositions(sheetId, zones) {
6103
6094
  const lefts = new Set(zones.map((z) => z.left));
6104
6095
  const rights = new Set(zones.map((z) => z.right));
@@ -6589,10 +6580,9 @@
6589
6580
  return literal;
6590
6581
  }
6591
6582
  const decimalNumberRegex = getDecimalNumberRegex(DEFAULT_LOCALE);
6592
- const localized = literal.replace(decimalNumberRegex, (match) => {
6583
+ return literal.replace(decimalNumberRegex, (match) => {
6593
6584
  return match.replace(".", locale.decimalSeparator);
6594
6585
  });
6595
- return localized;
6596
6586
  }
6597
6587
  /**
6598
6588
  * Change a literal string from its canonical form (en_US locale) to the given locale. Also convert date string.
@@ -7077,6 +7067,21 @@
7077
7067
  });
7078
7068
  return newY.length === newX.length ? newY : transposeMatrix(newY);
7079
7069
  }
7070
+ function getMovingAverageValues(dataset, windowSize = DEFAULT_WINDOW_SIZE) {
7071
+ const values = [];
7072
+ // Fill the starting values with null until we have a full window
7073
+ for (let i = 0; i < windowSize - 1; i++) {
7074
+ values.push(null);
7075
+ }
7076
+ for (let i = 0; i <= dataset.length - windowSize; i++) {
7077
+ let sum = 0;
7078
+ for (let j = i; j < i + windowSize; j++) {
7079
+ sum += dataset[j];
7080
+ }
7081
+ values.push(sum / windowSize);
7082
+ }
7083
+ return values;
7084
+ }
7080
7085
 
7081
7086
  const PREVIOUS_VALUE = "(previous)";
7082
7087
  const NEXT_VALUE = "(next)";
@@ -7612,8 +7617,7 @@
7612
7617
  return 0;
7613
7618
  }
7614
7619
  const nums = keys.map((id) => parseInt(id, 10));
7615
- const max = Math.max(...nums);
7616
- return max;
7620
+ return Math.max(...nums);
7617
7621
  }
7618
7622
  const ALL_PERIODS = {
7619
7623
  year: _t("Year"),
@@ -8453,31 +8457,30 @@
8453
8457
  for (let col of columnsIndexes) {
8454
8458
  const position = { col, row, sheetId };
8455
8459
  const table = this.getters.getTable(position);
8456
- if (!table || copiedTablesIds.has(table.id)) {
8460
+ if (!table) {
8457
8461
  tableCellsInRow.push({});
8458
8462
  continue;
8459
8463
  }
8460
8464
  const coreTable = this.getters.getCoreTable(position);
8461
8465
  const tableZone = coreTable?.range.zone;
8466
+ let copiedTable = undefined;
8462
8467
  // Copy whole table
8463
- if (coreTable && tableZone && zones.some((z) => isZoneInside(tableZone, z))) {
8464
- copiedTablesIds.add(coreTable.id);
8465
- const values = [];
8466
- for (const col of range(tableZone.left, tableZone.right + 1)) {
8467
- values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
8468
- }
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) });
8468
+ if (!copiedTablesIds.has(table.id) &&
8469
+ coreTable &&
8470
+ tableZone &&
8471
+ zones.some((z) => isZoneInside(tableZone, z))) {
8472
+ copiedTablesIds.add(table.id);
8473
+ copiedTable = {
8474
+ range: coreTable.range.rangeData,
8475
+ config: coreTable.config,
8476
+ type: coreTable.type,
8477
+ };
8480
8478
  }
8479
+ tableCellsInRow.push({
8480
+ table: copiedTable,
8481
+ style: this.getTableStyleToCopy(position),
8482
+ isWholeTableCopied: copiedTablesIds.has(table.id),
8483
+ });
8481
8484
  }
8482
8485
  }
8483
8486
  return {
@@ -8558,11 +8561,14 @@
8558
8561
  tableType: tableCell.table.type,
8559
8562
  });
8560
8563
  }
8561
- // Do not paste table style if we're inside another table
8562
8564
  // We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
8563
8565
  // dynamic tables are not yet computed
8564
- if (!this.getters.getCoreTable(position)) {
8565
- if (tableCell.style?.style && options?.pasteOption !== "asValue") {
8566
+ if (this.getters.getCoreTable(position) || options?.pasteOption === "asValue") {
8567
+ return;
8568
+ }
8569
+ if ((!options?.pasteOption && !tableCell.isWholeTableCopied) ||
8570
+ options?.pasteOption === "onlyFormat") {
8571
+ if (tableCell.style?.style) {
8566
8572
  this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
8567
8573
  }
8568
8574
  if (tableCell.style?.border) {
@@ -9244,11 +9250,10 @@ stores.inject(MyMetaStore, storeInstance);
9244
9250
  const { x, y } = getters.getMainViewportCoordinates();
9245
9251
  const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9246
9252
  const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9247
- const position = {
9253
+ return {
9248
9254
  x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9249
9255
  y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9250
9256
  }; // Position at the center of the scrollable viewport
9251
- return position;
9252
9257
  }
9253
9258
  function getChartAxisTitleRuntime(design) {
9254
9259
  if (design?.title?.text) {
@@ -9282,6 +9287,49 @@ stores.inject(MyMetaStore, storeInstance);
9282
9287
  useLeftAxis ||= !useRightAxis;
9283
9288
  return { useLeftAxis, useRightAxis };
9284
9289
  }
9290
+ function getChartAxis(definition, position, type, options) {
9291
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
9292
+ if ((position === "left" && !useLeftAxis) || (position === "right" && !useRightAxis)) {
9293
+ return undefined;
9294
+ }
9295
+ const fontColor = chartFontColor(definition.background);
9296
+ let design;
9297
+ if (position === "bottom") {
9298
+ design = definition.axesDesign?.x;
9299
+ }
9300
+ else if (position === "left") {
9301
+ design = definition.axesDesign?.y;
9302
+ }
9303
+ else {
9304
+ design = definition.axesDesign?.y1;
9305
+ }
9306
+ if (type === "values") {
9307
+ const displayGridLines = position === "left" || (position === "right" && !useLeftAxis);
9308
+ return {
9309
+ position: position,
9310
+ title: getChartAxisTitleRuntime(design),
9311
+ grid: {
9312
+ display: displayGridLines,
9313
+ },
9314
+ beginAtZero: true,
9315
+ stacked: options?.stacked,
9316
+ ticks: {
9317
+ color: fontColor,
9318
+ callback: formatTickValue(options),
9319
+ },
9320
+ };
9321
+ }
9322
+ else {
9323
+ return {
9324
+ ticks: {
9325
+ padding: 5,
9326
+ color: fontColor,
9327
+ },
9328
+ stacked: options?.stacked,
9329
+ title: getChartAxisTitleRuntime(design),
9330
+ };
9331
+ }
9332
+ }
9285
9333
  function computeChartPadding({ displayTitle, displayLegend, }) {
9286
9334
  let top = 25;
9287
9335
  if (displayTitle) {
@@ -9322,7 +9370,7 @@ stores.inject(MyMetaStore, storeInstance);
9322
9370
  return {
9323
9371
  ...dataset,
9324
9372
  type: "line",
9325
- xAxisID: TREND_LINE_XAXIS_ID,
9373
+ xAxisID: config.type !== "trailingMovingAverage" ? TREND_LINE_XAXIS_ID : "x",
9326
9374
  label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
9327
9375
  data,
9328
9376
  order: -1,
@@ -9364,10 +9412,19 @@ stores.inject(MyMetaStore, storeInstance);
9364
9412
  case "logarithmic": {
9365
9413
  return predictLinearValues([values], logM([labels]), logM([newLabels]), true)[0];
9366
9414
  }
9415
+ case "trailingMovingAverage": {
9416
+ return getMovingAverageValues(values, config.window);
9417
+ }
9367
9418
  default:
9368
9419
  return [];
9369
9420
  }
9370
9421
  }
9422
+ function formatChartDatasetValue(axisFormats, locale) {
9423
+ return (value, axisId) => {
9424
+ const format = axisId ? axisFormats?.[axisId] : undefined;
9425
+ return formatTickValue({ format, locale })(value);
9426
+ };
9427
+ }
9371
9428
  function formatTickValue(localeFormat) {
9372
9429
  return (value) => {
9373
9430
  value = Number(value);
@@ -9403,70 +9460,115 @@ stores.inject(MyMetaStore, storeInstance);
9403
9460
  ctx.save();
9404
9461
  ctx.textAlign = "center";
9405
9462
  ctx.textBaseline = "middle";
9406
- ctx.fillStyle = chartFontColor(options.background);
9407
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9408
- chart._metasets.forEach(function (dataset) {
9409
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9410
- return; // ignore trend lines
9411
- }
9412
- switch (dataset.type) {
9413
- case "doughnut":
9414
- case "pie": {
9415
- for (let i = 0; i < dataset._parsed.length; i++) {
9416
- const bar = dataset.data[i];
9417
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9418
- const midAngle = (startAngle + endAngle) / 2;
9419
- const midRadius = (innerRadius + outerRadius) / 2;
9420
- const x = bar.x + midRadius * Math.cos(midAngle);
9421
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9422
- ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
9423
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9424
- const value = options.callback(dataset._parsed[i]);
9425
- ctx.strokeText(value, x, y);
9426
- ctx.fillText(value, x, y);
9427
- }
9428
- break;
9429
- }
9430
- case "bar":
9431
- case "line": {
9432
- const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
9433
- for (let i = 0; i < dataset._parsed.length; i++) {
9434
- const point = dataset.data[i];
9435
- const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
9436
- const displayedValue = options.callback(value - 0);
9437
- let xPosition = 0, yPosition = 0;
9438
- if (options.horizontal) {
9439
- yPosition = point.y;
9440
- if (value < 0) {
9441
- ctx.textAlign = "right";
9442
- xPosition = point.x - yOffset;
9443
- }
9444
- else {
9445
- ctx.textAlign = "left";
9446
- xPosition = point.x + yOffset;
9447
- }
9448
- }
9449
- else {
9450
- xPosition = point.x;
9451
- if (value < 0) {
9452
- ctx.textBaseline = "top";
9453
- yPosition = point.y + yOffset;
9454
- }
9455
- else {
9456
- ctx.textBaseline = "bottom";
9457
- yPosition = point.y - yOffset;
9458
- }
9459
- }
9460
- ctx.strokeText(displayedValue, xPosition, yPosition);
9461
- ctx.fillText(displayedValue, xPosition, yPosition);
9462
- }
9463
- break;
9464
- }
9465
- }
9466
- });
9463
+ ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
9464
+ switch (chart.config.type) {
9465
+ case "pie":
9466
+ case "doughnut":
9467
+ drawPieChartValues(chart, options, ctx);
9468
+ break;
9469
+ case "bar":
9470
+ case "line":
9471
+ options.horizontal
9472
+ ? drawHorizontalBarChartValues(chart, options, ctx)
9473
+ : drawLineOrBarChartValues(chart, options, ctx);
9474
+ break;
9475
+ }
9467
9476
  ctx.restore();
9468
9477
  },
9469
9478
  };
9479
+ function drawTextWithBackground(text, x, y, ctx) {
9480
+ ctx.lineWidth = 3; // Stroke the text with a big lineWidth width to have some kind of background
9481
+ ctx.strokeText(text, x, y);
9482
+ ctx.lineWidth = 1;
9483
+ ctx.fillText(text, x, y);
9484
+ }
9485
+ function drawLineOrBarChartValues(chart, options, ctx) {
9486
+ const yMax = chart.chartArea.bottom;
9487
+ const yMin = chart.chartArea.top;
9488
+ const textsPositions = {};
9489
+ for (const dataset of chart._metasets) {
9490
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9491
+ return; // ignore trend lines
9492
+ }
9493
+ for (let i = 0; i < dataset._parsed.length; i++) {
9494
+ const value = dataset._parsed[i].y;
9495
+ const displayValue = options.callback(value - 0, dataset.yAxisID);
9496
+ const point = dataset.data[i];
9497
+ const xPosition = point.x;
9498
+ let yPosition = 0;
9499
+ if (chart.config.type === "line") {
9500
+ yPosition = point.y - 10;
9501
+ }
9502
+ else {
9503
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
9504
+ }
9505
+ yPosition = Math.min(yPosition, yMax);
9506
+ yPosition = Math.max(yPosition, yMin);
9507
+ // Avoid overlapping texts with same X
9508
+ if (!textsPositions[xPosition]) {
9509
+ textsPositions[xPosition] = [];
9510
+ }
9511
+ for (const otherPosition of textsPositions[xPosition] || []) {
9512
+ if (Math.abs(otherPosition - yPosition) < 13) {
9513
+ yPosition = otherPosition - 13;
9514
+ }
9515
+ }
9516
+ textsPositions[xPosition].push(yPosition);
9517
+ ctx.fillStyle = point.options.backgroundColor;
9518
+ ctx.strokeStyle = options.background || "#ffffff";
9519
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9520
+ }
9521
+ }
9522
+ }
9523
+ function drawHorizontalBarChartValues(chart, options, ctx) {
9524
+ const xMax = chart.chartArea.right;
9525
+ const xMin = chart.chartArea.left;
9526
+ const textsPositions = {};
9527
+ for (const dataset of chart._metasets) {
9528
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9529
+ return; // ignore trend lines
9530
+ }
9531
+ for (let i = 0; i < dataset._parsed.length; i++) {
9532
+ const value = dataset._parsed[i].x;
9533
+ const displayValue = options.callback(value - 0, dataset.xAxisID);
9534
+ const point = dataset.data[i];
9535
+ const yPosition = point.y;
9536
+ let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
9537
+ xPosition = Math.min(xPosition, xMax);
9538
+ xPosition = Math.max(xPosition, xMin);
9539
+ // Avoid overlapping texts with same Y
9540
+ if (!textsPositions[yPosition]) {
9541
+ textsPositions[yPosition] = [];
9542
+ }
9543
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
9544
+ for (const otherPosition of textsPositions[yPosition]) {
9545
+ if (Math.abs(otherPosition - xPosition) < textWidth) {
9546
+ xPosition = otherPosition + textWidth + 3;
9547
+ }
9548
+ }
9549
+ textsPositions[yPosition].push(xPosition);
9550
+ ctx.fillStyle = point.options.backgroundColor;
9551
+ ctx.strokeStyle = options.background || "#ffffff";
9552
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9553
+ }
9554
+ }
9555
+ }
9556
+ function drawPieChartValues(chart, options, ctx) {
9557
+ for (const dataset of chart._metasets) {
9558
+ for (let i = 0; i < dataset._parsed.length; i++) {
9559
+ const bar = dataset.data[i];
9560
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9561
+ const midAngle = (startAngle + endAngle) / 2;
9562
+ const midRadius = (innerRadius + outerRadius) / 2;
9563
+ const x = bar.x + midRadius * Math.cos(midAngle);
9564
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9565
+ ctx.fillStyle = chartFontColor(options.background);
9566
+ ctx.strokeStyle = options.background || "#ffffff";
9567
+ const value = options.callback(dataset._parsed[i]);
9568
+ drawTextWithBackground(value, x, y, ctx);
9569
+ }
9570
+ }
9571
+ }
9470
9572
 
9471
9573
  /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
9472
9574
  const waterfallLinesPlugin = {
@@ -10244,8 +10346,7 @@ stores.inject(MyMetaStore, storeInstance);
10244
10346
  value = value.slice(index + 1);
10245
10347
  }
10246
10348
  pendingHtmlContent.push({ value });
10247
- const htmlContent = pendingHtmlContent.filter((content) => content.value);
10248
- return htmlContent;
10349
+ return pendingHtmlContent.filter((content) => content.value);
10249
10350
  }
10250
10351
 
10251
10352
  //------------------------------------------------------------------------------
@@ -10649,7 +10750,7 @@ stores.inject(MyMetaStore, storeInstance);
10649
10750
  assertSquareMatrix(_t("The argument square_matrix must have the same number of columns and rows."), _matrix);
10650
10751
  const { inverted } = invertMatrix(_matrix);
10651
10752
  if (!inverted) {
10652
- throw new EvaluationError(_t("The matrix is not invertible."));
10753
+ return new EvaluationError(_t("The matrix is not invertible."));
10653
10754
  }
10654
10755
  return inverted;
10655
10756
  },
@@ -10728,7 +10829,7 @@ stores.inject(MyMetaStore, storeInstance);
10728
10829
  }
10729
10830
  }
10730
10831
  if (!validPairFound) {
10731
- throw new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10832
+ return new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10732
10833
  }
10733
10834
  return result;
10734
10835
  }
@@ -10809,7 +10910,7 @@ stores.inject(MyMetaStore, storeInstance);
10809
10910
  .flat()
10810
10911
  .filter(shouldKeepValue(_ignore));
10811
10912
  if (result.length === 0) {
10812
- throw new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10913
+ return new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10813
10914
  }
10814
10915
  return [result];
10815
10916
  },
@@ -10830,7 +10931,7 @@ stores.inject(MyMetaStore, storeInstance);
10830
10931
  .filter(shouldKeepValue(_ignore))
10831
10932
  .map((item) => [item]);
10832
10933
  if (result.length === 0 || result[0].length === 0) {
10833
- throw new NotAvailableError(_t("No results for the given arguments of TOROW."));
10934
+ return new NotAvailableError(_t("No results for the given arguments of TOROW."));
10834
10935
  }
10835
10936
  return result;
10836
10937
  },
@@ -11390,7 +11491,7 @@ stores.inject(MyMetaStore, storeInstance);
11390
11491
  * Return error if 'value' is positive.
11391
11492
  * Remove '-?' in the next regex to catch this error.
11392
11493
  */
11393
- assert(() => !!DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11494
+ assert(() => DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11394
11495
  const deci = parseInt(_value, _base);
11395
11496
  assert(() => !isNaN(deci), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11396
11497
  return deci;
@@ -11658,7 +11759,7 @@ stores.inject(MyMetaStore, storeInstance);
11658
11759
  count += 1;
11659
11760
  }
11660
11761
  if (isEvaluationError(f)) {
11661
- throw j;
11762
+ return j;
11662
11763
  }
11663
11764
  }
11664
11765
  }
@@ -12161,9 +12262,8 @@ stores.inject(MyMetaStore, storeInstance);
12161
12262
  }
12162
12263
  function variance(args, isSample, textAs0, locale) {
12163
12264
  let count = 0;
12164
- let sum = 0;
12165
12265
  const reduceFunction = textAs0 ? reduceNumbersTextAs0 : reduceNumbers;
12166
- sum = reduceFunction(args, (acc, a) => {
12266
+ const sum = reduceFunction(args, (acc, a) => {
12167
12267
  count += 1;
12168
12268
  return acc + a;
12169
12269
  }, 0, locale);
@@ -12560,7 +12660,7 @@ stores.inject(MyMetaStore, storeInstance);
12560
12660
  const flatY = dataY.flat();
12561
12661
  assertSameNumberOfElements(flatX, flatY);
12562
12662
  if (flatX.length === 0) {
12563
- throw new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12663
+ return new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12564
12664
  }
12565
12665
  const n = flatX.length;
12566
12666
  let trueN = 0, trueP = 0, falseP = 0, falseN = 0;
@@ -13360,9 +13460,8 @@ stores.inject(MyMetaStore, storeInstance);
13360
13460
  // 4 - return for each database row corresponding, the cells corresponding to the field parameter
13361
13461
  const fieldCol = database[index];
13362
13462
  // Example continuation:: fieldCol = ["C", "j", "k", 7]
13363
- const matchingCells = [...matchingRows].map((x) => fieldCol[x + 1]);
13364
13463
  // Example continuation:: matchingCells = ["j", 7]
13365
- return matchingCells;
13464
+ return [...matchingRows].map((x) => fieldCol[x + 1]);
13366
13465
  }
13367
13466
  const databaseArgs = [
13368
13467
  arg("database (range)", _t("The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.")),
@@ -14547,7 +14646,7 @@ stores.inject(MyMetaStore, storeInstance);
14547
14646
  }
14548
14647
  }
14549
14648
  if (!result.length) {
14550
- throw new NotAvailableError(_t("No match found in FILTER evaluation"));
14649
+ return new NotAvailableError(_t("No match found in FILTER evaluation"));
14551
14650
  }
14552
14651
  return mode === "row" ? transposeMatrix(result) : result;
14553
14652
  },
@@ -17245,7 +17344,7 @@ stores.inject(MyMetaStore, storeInstance);
17245
17344
  argsTokens[argPosition].push({ value: token.value, type: token.type });
17246
17345
  }
17247
17346
  }
17248
- const res = tokens.map((token, i) => {
17347
+ return tokens.map((token, i) => {
17249
17348
  if (!["SPACE", "LEFT_PAREN"].includes(token.type)) {
17250
17349
  functionStarted = "";
17251
17350
  }
@@ -17283,7 +17382,6 @@ stores.inject(MyMetaStore, storeInstance);
17283
17382
  }
17284
17383
  return token;
17285
17384
  });
17286
- return res;
17287
17385
  }
17288
17386
  /**
17289
17387
  * Parse the list of tokens that compose the arguments of a function to
@@ -17739,7 +17837,7 @@ stores.inject(MyMetaStore, storeInstance);
17739
17837
  return result;
17740
17838
  }
17741
17839
  }
17742
- throw new EvaluationError(_t("No match."));
17840
+ return new EvaluationError(_t("No match."));
17743
17841
  },
17744
17842
  isExported: true,
17745
17843
  };
@@ -17842,7 +17940,7 @@ stores.inject(MyMetaStore, storeInstance);
17842
17940
  throw new EvaluationError(_t("Function PIVOT takes an even number of arguments."));
17843
17941
  }
17844
17942
  }
17845
- function addPivotDependencies(evalContext, coreDefinition) {
17943
+ function addPivotDependencies(evalContext, coreDefinition, forMeasures) {
17846
17944
  //TODO This function can be very costly when used with PIVOT.VALUE and PIVOT.HEADER
17847
17945
  const dependencies = [];
17848
17946
  if (coreDefinition.type === "SPREADSHEET" && coreDefinition.dataSet) {
@@ -17854,7 +17952,7 @@ stores.inject(MyMetaStore, storeInstance);
17854
17952
  }
17855
17953
  dependencies.push(range);
17856
17954
  }
17857
- for (const measure of coreDefinition.measures) {
17955
+ for (const measure of forMeasures) {
17858
17956
  if (measure.computedBy) {
17859
17957
  const formula = evalContext.getters.getMeasureCompiledFormula(measure);
17860
17958
  dependencies.push(...formula.dependencies.filter((range) => !range.invalidXc));
@@ -17903,8 +18001,8 @@ stores.inject(MyMetaStore, storeInstance);
17903
18001
  let cellReference;
17904
18002
  if (_useA1Notation) {
17905
18003
  const rangePart = {
17906
- rowFixed: [1, 2].includes(_absoluteRelativeMode) ? true : false,
17907
- colFixed: [1, 3].includes(_absoluteRelativeMode) ? true : false,
18004
+ rowFixed: [1, 2].includes(_absoluteRelativeMode),
18005
+ colFixed: [1, 3].includes(_absoluteRelativeMode),
17908
18006
  };
17909
18007
  cellReference = toXC(colNumber - 1, rowNumber - 1, rangePart);
17910
18008
  }
@@ -17930,7 +18028,7 @@ stores.inject(MyMetaStore, storeInstance);
17930
18028
  ],
17931
18029
  compute: function (cellReference) {
17932
18030
  if (isEvaluationError(cellReference?.value)) {
17933
- throw cellReference;
18031
+ return cellReference;
17934
18032
  }
17935
18033
  const column = cellReference === undefined
17936
18034
  ? this.__originCellPosition?.col
@@ -17948,7 +18046,7 @@ stores.inject(MyMetaStore, storeInstance);
17948
18046
  args: [arg("range (meta)", _t("The range whose column count will be returned."))],
17949
18047
  compute: function (range) {
17950
18048
  if (isEvaluationError(range?.value)) {
17951
- throw range;
18049
+ return range;
17952
18050
  }
17953
18051
  const zone = toZone(range.value);
17954
18052
  return zone.right - zone.left + 1;
@@ -18028,11 +18126,11 @@ stores.inject(MyMetaStore, storeInstance);
18028
18126
  compute: function (reference, useA1Notation = { value: true }) {
18029
18127
  let _reference = reference?.value?.toString();
18030
18128
  if (!_reference) {
18031
- throw new InvalidReferenceError(_t("Reference should be defined."));
18129
+ return new InvalidReferenceError(_t("Reference should be defined."));
18032
18130
  }
18033
18131
  const _useA1Notation = toBoolean(useA1Notation);
18034
18132
  if (!_useA1Notation) {
18035
- throw new EvaluationError(_t("R1C1 notation is not supported."));
18133
+ return new EvaluationError(_t("R1C1 notation is not supported."));
18036
18134
  }
18037
18135
  const sheetId = this.__originSheetId;
18038
18136
  const originPosition = this.__originCellPosition;
@@ -18044,7 +18142,7 @@ stores.inject(MyMetaStore, storeInstance);
18044
18142
  }
18045
18143
  const range = this.getters.getRangeFromSheetXC(sheetId, _reference);
18046
18144
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18047
- throw new InvalidReferenceError();
18145
+ return new InvalidReferenceError();
18048
18146
  }
18049
18147
  if (originPosition) {
18050
18148
  this.addDependencies?.(originPosition, [range]);
@@ -18152,7 +18250,7 @@ stores.inject(MyMetaStore, storeInstance);
18152
18250
  ],
18153
18251
  compute: function (cellReference) {
18154
18252
  if (isEvaluationError(cellReference?.value)) {
18155
- throw cellReference;
18253
+ return cellReference;
18156
18254
  }
18157
18255
  const row = cellReference === undefined
18158
18256
  ? this.__originCellPosition?.row
@@ -18170,7 +18268,7 @@ stores.inject(MyMetaStore, storeInstance);
18170
18268
  args: [arg("range (meta)", _t("The range whose row count will be returned."))],
18171
18269
  compute: function (range) {
18172
18270
  if (isEvaluationError(range?.value)) {
18173
- throw range;
18271
+ return range;
18174
18272
  }
18175
18273
  const zone = toZone(range.value);
18176
18274
  return zone.bottom - zone.top + 1;
@@ -18287,7 +18385,7 @@ stores.inject(MyMetaStore, storeInstance);
18287
18385
  assertDomainLength(domainArgs);
18288
18386
  const pivot = this.getters.getPivot(pivotId);
18289
18387
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18290
- addPivotDependencies(this, coreDefinition);
18388
+ addPivotDependencies(this, coreDefinition, coreDefinition.measures.filter((m) => m.id === _measure));
18291
18389
  pivot.init({ reload: pivot.needsReevaluation });
18292
18390
  const error = pivot.assertIsValid({ throwOnError: false });
18293
18391
  if (error) {
@@ -18317,7 +18415,7 @@ stores.inject(MyMetaStore, storeInstance);
18317
18415
  assertDomainLength(domainArgs);
18318
18416
  const pivot = this.getters.getPivot(_pivotId);
18319
18417
  const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
18320
- addPivotDependencies(this, coreDefinition);
18418
+ addPivotDependencies(this, coreDefinition, []);
18321
18419
  pivot.init({ reload: pivot.needsReevaluation });
18322
18420
  const error = pivot.assertIsValid({ throwOnError: false });
18323
18421
  if (error) {
@@ -18357,18 +18455,18 @@ stores.inject(MyMetaStore, storeInstance);
18357
18455
  const _pivotFormulaId = toString(pivotFormulaId);
18358
18456
  const _rowCount = toNumber(rowCount, this.locale);
18359
18457
  if (_rowCount < 0) {
18360
- throw new EvaluationError(_t("The number of rows must be positive."));
18458
+ return new EvaluationError(_t("The number of rows must be positive."));
18361
18459
  }
18362
18460
  const _columnCount = toNumber(columnCount, this.locale);
18363
18461
  if (_columnCount < 0) {
18364
- throw new EvaluationError(_t("The number of columns must be positive."));
18462
+ return new EvaluationError(_t("The number of columns must be positive."));
18365
18463
  }
18366
18464
  const _includeColumnHeaders = toBoolean(includeColumnHeaders);
18367
18465
  const _includedTotal = toBoolean(includeTotal);
18368
18466
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
18369
18467
  const pivot = this.getters.getPivot(pivotId);
18370
18468
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18371
- addPivotDependencies(this, coreDefinition);
18469
+ addPivotDependencies(this, coreDefinition, coreDefinition.measures);
18372
18470
  pivot.init({ reload: pivot.needsReevaluation });
18373
18471
  const error = pivot.assertIsValid({ throwOnError: false });
18374
18472
  if (error) {
@@ -18560,6 +18658,12 @@ stores.inject(MyMetaStore, storeInstance);
18560
18658
  arg("value2 (any)", _t("The value to test against value1 for equality.")),
18561
18659
  ],
18562
18660
  compute: function (value1, value2) {
18661
+ if (isEvaluationError(value1?.value)) {
18662
+ return value1;
18663
+ }
18664
+ if (isEvaluationError(value2?.value)) {
18665
+ return value2;
18666
+ }
18563
18667
  let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18564
18668
  let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18565
18669
  if (typeof _value1 === "string") {
@@ -18568,27 +18672,21 @@ stores.inject(MyMetaStore, storeInstance);
18568
18672
  if (typeof _value2 === "string") {
18569
18673
  _value2 = _value2.toUpperCase();
18570
18674
  }
18571
- if (isEvaluationError(_value1)) {
18572
- throw value1;
18573
- }
18574
- if (isEvaluationError(_value2)) {
18575
- throw value2;
18576
- }
18577
- return _value1 === _value2;
18675
+ return { value: _value1 === _value2 };
18578
18676
  },
18579
18677
  };
18580
18678
  // -----------------------------------------------------------------------------
18581
18679
  // GT
18582
18680
  // -----------------------------------------------------------------------------
18583
18681
  function applyRelationalOperator(value1, value2, cb) {
18584
- let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18585
- let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18586
- if (isEvaluationError(_value1)) {
18587
- throw value1;
18682
+ if (isEvaluationError(value1?.value)) {
18683
+ return value1;
18588
18684
  }
18589
- if (isEvaluationError(_value2)) {
18590
- throw value2;
18685
+ if (isEvaluationError(value2?.value)) {
18686
+ return value2;
18591
18687
  }
18688
+ let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18689
+ let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18592
18690
  if (typeof _value1 !== "number") {
18593
18691
  _value1 = toString(_value1).toUpperCase();
18594
18692
  }
@@ -18598,12 +18696,12 @@ stores.inject(MyMetaStore, storeInstance);
18598
18696
  const tV1 = typeof _value1;
18599
18697
  const tV2 = typeof _value2;
18600
18698
  if (tV1 === "string" && tV2 === "number") {
18601
- return true;
18699
+ return { value: true };
18602
18700
  }
18603
18701
  if (tV2 === "string" && tV1 === "number") {
18604
- return false;
18702
+ return { value: false };
18605
18703
  }
18606
- return cb(_value1, _value2);
18704
+ return { value: cb(_value1, _value2) };
18607
18705
  }
18608
18706
  const GT = {
18609
18707
  description: _t("Strictly greater than."),
@@ -18642,7 +18740,11 @@ stores.inject(MyMetaStore, storeInstance);
18642
18740
  arg("value2 (any)", _t("The second value.")),
18643
18741
  ],
18644
18742
  compute: function (value1, value2) {
18645
- return !GTE.compute.bind(this)(value1, value2);
18743
+ const result = GTE.compute.bind(this)(value1, value2);
18744
+ if (isEvaluationError(result.value)) {
18745
+ return result;
18746
+ }
18747
+ return { value: !result.value };
18646
18748
  },
18647
18749
  };
18648
18750
  // -----------------------------------------------------------------------------
@@ -18655,7 +18757,11 @@ stores.inject(MyMetaStore, storeInstance);
18655
18757
  arg("value2 (any)", _t("The second value.")),
18656
18758
  ],
18657
18759
  compute: function (value1, value2) {
18658
- return !GT.compute.bind(this)(value1, value2);
18760
+ const result = GT.compute.bind(this)(value1, value2);
18761
+ if (isEvaluationError(result.value)) {
18762
+ return result;
18763
+ }
18764
+ return { value: !result.value };
18659
18765
  },
18660
18766
  };
18661
18767
  // -----------------------------------------------------------------------------
@@ -18700,7 +18806,11 @@ stores.inject(MyMetaStore, storeInstance);
18700
18806
  arg("value2 (any)", _t("The value to test against value1 for inequality.")),
18701
18807
  ],
18702
18808
  compute: function (value1, value2) {
18703
- return !EQ.compute.bind(this)(value1, value2);
18809
+ const result = EQ.compute.bind(this)(value1, value2);
18810
+ if (isEvaluationError(result.value)) {
18811
+ return result;
18812
+ }
18813
+ return { value: !result.value };
18704
18814
  },
18705
18815
  };
18706
18816
  // -----------------------------------------------------------------------------
@@ -19859,13 +19969,12 @@ stores.inject(MyMetaStore, storeInstance);
19859
19969
  }
19860
19970
  function getElementMargins(el) {
19861
19971
  const style = window.getComputedStyle(el);
19862
- const margins = {
19972
+ return {
19863
19973
  top: parseInt(style.marginTop, 10) || 0,
19864
19974
  bottom: parseInt(style.marginBottom, 10) || 0,
19865
19975
  left: parseInt(style.marginLeft, 10) || 0,
19866
19976
  right: parseInt(style.marginRight, 10) || 0,
19867
19977
  };
19868
- return margins;
19869
19978
  }
19870
19979
 
19871
19980
  const macRegex = /Mac/i;
@@ -20164,7 +20273,7 @@ stores.inject(MyMetaStore, storeInstance);
20164
20273
  let startNode = this.findChildAtCharacterIndex(start);
20165
20274
  let endNode = this.findChildAtCharacterIndex(end);
20166
20275
  range.setStart(startNode.node, startNode.offset);
20167
- selection.extend(endNode.node, endNode.offset);
20276
+ range.setEnd(endNode.node, endNode.offset);
20168
20277
  }
20169
20278
  }
20170
20279
  /**
@@ -20662,7 +20771,7 @@ stores.inject(MyMetaStore, storeInstance);
20662
20771
  "Alt+Enter": this.processNewLineEvent,
20663
20772
  "Ctrl+Enter": this.processNewLineEvent,
20664
20773
  Escape: this.processEscapeKey,
20665
- F2: () => console.warn("Not implemented"),
20774
+ F2: (ev) => this.toggleEditionMode(ev),
20666
20775
  F4: (ev) => this.processF4Key(ev),
20667
20776
  Tab: (ev) => this.processTabKey(ev, "right"),
20668
20777
  "Shift+Tab": (ev) => this.processTabKey(ev, "left"),
@@ -20781,6 +20890,11 @@ stores.inject(MyMetaStore, storeInstance);
20781
20890
  this.props.composerStore.cycleReferences();
20782
20891
  this.processContent();
20783
20892
  }
20893
+ toggleEditionMode(ev) {
20894
+ ev.stopPropagation();
20895
+ this.props.composerStore.toggleEditionMode();
20896
+ this.processContent();
20897
+ }
20784
20898
  processNumpadDecimal(ev) {
20785
20899
  ev.stopPropagation();
20786
20900
  ev.preventDefault();
@@ -21006,7 +21120,13 @@ stores.inject(MyMetaStore, storeInstance);
21006
21120
  break;
21007
21121
  case "REFERENCE":
21008
21122
  const { xc, sheetName } = splitReference(token.value);
21009
- result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
21123
+ result.push({
21124
+ value: token.value,
21125
+ color: this.rangeColor(xc, sheetName) || "#000",
21126
+ class: tokenAtCursor === token && this.props.composerStore.editionMode === "selecting"
21127
+ ? "text-decoration-underline"
21128
+ : undefined,
21129
+ });
21010
21130
  break;
21011
21131
  case "SYMBOL":
21012
21132
  const value = token.value;
@@ -21577,6 +21697,7 @@ stores.inject(MyMetaStore, storeInstance);
21577
21697
  // replace the whole token
21578
21698
  start = tokenAtCursor.start;
21579
21699
  }
21700
+ this.composer.stopComposerRangeSelection();
21580
21701
  this.composer.changeComposerCursorSelection(start, end);
21581
21702
  this.composer.replaceComposerCursorSelection(value);
21582
21703
  }
@@ -21594,6 +21715,7 @@ stores.inject(MyMetaStore, storeInstance);
21594
21715
  // replace the whole token
21595
21716
  start = tokenAtCursor.start;
21596
21717
  }
21718
+ this.composer.stopComposerRangeSelection();
21597
21719
  this.composer.changeComposerCursorSelection(start, end);
21598
21720
  this.composer.replaceComposerCursorSelection(value);
21599
21721
  }
@@ -21727,9 +21849,13 @@ stores.inject(MyMetaStore, storeInstance);
21727
21849
  const colFields = columns.map((groupBy) => groupBy.nameWithGranularity);
21728
21850
  const rowFields = rows.map((groupBy) => groupBy.nameWithGranularity);
21729
21851
  const proposals = [];
21730
- const previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21852
+ let previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21731
21853
  ? argGroupBys.at(-1)
21732
21854
  : argGroupBys.at(-2);
21855
+ const isPositionalSupported = supportedPivotPositionalFormulaRegistry.get(pivot.type);
21856
+ if (isPositionalSupported && previousGroupBy?.startsWith("#")) {
21857
+ previousGroupBy = previousGroupBy.slice(1);
21858
+ }
21733
21859
  if (previousGroupBy === undefined) {
21734
21860
  proposals.push(colFields[0]);
21735
21861
  proposals.push(rowFields[0]);
@@ -21751,7 +21877,7 @@ stores.inject(MyMetaStore, storeInstance);
21751
21877
  return field ? makeFieldProposal(field, granularity) : undefined;
21752
21878
  })
21753
21879
  .concat(groupBys.map((groupBy) => {
21754
- if (!supportedPivotPositionalFormulaRegistry.get(pivot.type)) {
21880
+ if (!isPositionalSupported) {
21755
21881
  return undefined;
21756
21882
  }
21757
21883
  const fieldName = groupBy.split(":")[0];
@@ -21760,13 +21886,12 @@ stores.inject(MyMetaStore, storeInstance);
21760
21886
  return undefined;
21761
21887
  }
21762
21888
  const positionalFieldArg = `"#${groupBy}"`;
21763
- const positionalProposal = {
21889
+ return {
21764
21890
  text: positionalFieldArg,
21765
21891
  description: _t("%s (positional)", field.string) + (field.help ? ` (${field.help})` : ""),
21766
21892
  htmlContent: [{ value: positionalFieldArg, color: tokenColors.STRING }],
21767
21893
  fuzzySearchKey: field.string + positionalFieldArg, // search on translated name and on technical name
21768
21894
  };
21769
- return positionalProposal;
21770
21895
  }))
21771
21896
  .filter(isDefined);
21772
21897
  },
@@ -22035,7 +22160,7 @@ stores.inject(MyMetaStore, storeInstance);
22035
22160
  return internalDate.value;
22036
22161
  }
22037
22162
  if (isBoolean(content)) {
22038
- return content.toUpperCase() === "TRUE" ? true : false;
22163
+ return content.toUpperCase() === "TRUE";
22039
22164
  }
22040
22165
  return content;
22041
22166
  }
@@ -22186,6 +22311,27 @@ stores.inject(MyMetaStore, storeInstance);
22186
22311
  tooltip: content ? { props: { content: tooltipValue } } : undefined,
22187
22312
  };
22188
22313
  },
22314
+ })
22315
+ .add("DATE_INCREMENT_MODIFIER", {
22316
+ apply: (rule, data, getters) => {
22317
+ const date = toJsDate(rule.current, getters.getLocale());
22318
+ date.setFullYear(date.getFullYear() + rule.increment.years || 0);
22319
+ date.setMonth(date.getMonth() + rule.increment.months || 0);
22320
+ date.setDate(date.getDate() + rule.increment.days || 0);
22321
+ const value = jsDateToNumber(date);
22322
+ rule.current = value;
22323
+ const locale = getters.getLocale();
22324
+ const tooltipValue = formatValue(value, { format: data.cell?.format, locale });
22325
+ return {
22326
+ cellData: {
22327
+ border: data.border,
22328
+ style: data.cell && data.cell.style,
22329
+ format: data.cell && data.cell.format,
22330
+ content: value.toString(),
22331
+ },
22332
+ tooltip: value ? { props: { content: tooltipValue } } : undefined,
22333
+ };
22334
+ },
22189
22335
  })
22190
22336
  .add("COPY_MODIFIER", {
22191
22337
  apply: (rule, data, getters) => {
@@ -22266,7 +22412,9 @@ stores.inject(MyMetaStore, storeInstance);
22266
22412
  if (x === cell) {
22267
22413
  found = true;
22268
22414
  }
22269
- const cellValue = x === undefined || x.isFormula ? undefined : evaluateLiteral(x, { locale: DEFAULT_LOCALE });
22415
+ const cellValue = x === undefined || x.isFormula
22416
+ ? undefined
22417
+ : evaluateLiteral(x, { locale: DEFAULT_LOCALE, format: x.format });
22270
22418
  if (cellValue && filter(cellValue)) {
22271
22419
  group.push(cellValue);
22272
22420
  }
@@ -22302,6 +22450,71 @@ stores.inject(MyMetaStore, storeInstance);
22302
22450
  }
22303
22451
  return increment;
22304
22452
  }
22453
+ /**
22454
+ * Iterates on a list of date intervals.
22455
+ * if every interval is the same, return the interval
22456
+ * Otherwise return undefined
22457
+ *
22458
+ */
22459
+ function getEqualInterval(intervals) {
22460
+ if (intervals.length < 2) {
22461
+ return intervals[0] || { years: 0, months: 0, days: 0 };
22462
+ }
22463
+ const equal = intervals.every((interval) => interval.years === intervals[0].years &&
22464
+ interval.months === intervals[0].months &&
22465
+ interval.days === intervals[0].days);
22466
+ return equal ? intervals[0] : undefined;
22467
+ }
22468
+ /**
22469
+ * Based on a group of dates, calculate the increment that should be applied
22470
+ * to the next date.
22471
+ *
22472
+ * This will compute the date difference in calendar terms (years, months, days)
22473
+ * In order to make abstraction of leap years and months with different number of days.
22474
+ *
22475
+ * In case the dates are not equidistant in calendar terms, no rule can be extrapolated
22476
+ * In case of equidistant dates, we either have in that order:
22477
+ * - exact date interval (e.g. +n year OR +n month OR +n day) in which case we increment by the same interval
22478
+ * - exact day interval (e.g. +n days) in which case we increment by the same day interval
22479
+ * - equidistant dates but not the same interval, in which case we return increment of the same interval
22480
+ *
22481
+ * */
22482
+ function calculateDateIncrementBasedOnGroup(group) {
22483
+ if (group.length < 2) {
22484
+ return 1;
22485
+ }
22486
+ const jsDates = group.map((date) => toJsDate(date, DEFAULT_LOCALE));
22487
+ const datesIntervals = getDateIntervals(jsDates);
22488
+ const datesEquidistantInterval = getEqualInterval(datesIntervals);
22489
+ if (datesEquidistantInterval === undefined) {
22490
+ // dates are not equidistant in terms of years, months or days, thus no rule can be extrapolated
22491
+ return undefined;
22492
+ }
22493
+ // The dates are apart by an exact interval of years, months or days
22494
+ // but not a combination of them
22495
+ const exactDateInterval = Object.values(datesEquidistantInterval).filter((value) => value !== 0).length === 1;
22496
+ const isSameDay = Object.values(datesEquidistantInterval).every((el) => el === 0); // handles time values (strict decimals)
22497
+ if (!exactDateInterval || isSameDay) {
22498
+ const timeIntervals = jsDates
22499
+ .map((date, index) => {
22500
+ if (index === 0) {
22501
+ return 0;
22502
+ }
22503
+ const previous = jsDates[index - 1];
22504
+ return Math.floor(date.getTime()) - Math.floor(previous.getTime());
22505
+ })
22506
+ .slice(1);
22507
+ const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
22508
+ if (equidistantDates) {
22509
+ return group.length * (group[1] - group[0]);
22510
+ }
22511
+ }
22512
+ return {
22513
+ years: datesEquidistantInterval.years * group.length,
22514
+ months: datesEquidistantInterval.months * group.length,
22515
+ days: datesEquidistantInterval.days * group.length,
22516
+ };
22517
+ }
22305
22518
  autofillRulesRegistry
22306
22519
  .add("simple_value_copy", {
22307
22520
  condition: (cell, cells) => {
@@ -22349,12 +22562,47 @@ stores.inject(MyMetaStore, storeInstance);
22349
22562
  return { type: "FORMULA_MODIFIER", increment: cells.length, current: 0 };
22350
22563
  },
22351
22564
  sequence: 30,
22565
+ })
22566
+ .add("increment_dates", {
22567
+ condition: (cell, cells) => {
22568
+ return (!cell.isFormula &&
22569
+ evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number &&
22570
+ !!cell.format &&
22571
+ isDateTimeFormat(cell.format));
22572
+ },
22573
+ generateRule: (cell, cells) => {
22574
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
22575
+ !!evaluatedCell.format &&
22576
+ isDateTimeFormat(evaluatedCell.format)).map((cell) => Number(cell.value));
22577
+ const increment = calculateDateIncrementBasedOnGroup(group);
22578
+ if (increment === undefined) {
22579
+ return { type: "COPY_MODIFIER" };
22580
+ }
22581
+ /** requires to detect the current date (requires to be an integer value with the right format)
22582
+ * detect if year or if month or if day then extrapolate increment required (+1 month, +1 year + 1 day)
22583
+ */
22584
+ const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
22585
+ if (typeof increment === "object") {
22586
+ return {
22587
+ type: "DATE_INCREMENT_MODIFIER",
22588
+ increment,
22589
+ current: evaluation.type === CellValueType.number ? evaluation.value : 0,
22590
+ };
22591
+ }
22592
+ return {
22593
+ type: "INCREMENT_MODIFIER",
22594
+ increment,
22595
+ current: evaluation.type === CellValueType.number ? evaluation.value : 0,
22596
+ };
22597
+ },
22598
+ sequence: 25,
22352
22599
  })
22353
22600
  .add("increment_number", {
22354
22601
  condition: (cell) => !cell.isFormula &&
22355
22602
  evaluateLiteral(cell, { locale: DEFAULT_LOCALE }).type === CellValueType.number,
22356
22603
  generateRule: (cell, cells) => {
22357
- const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number).map((cell) => Number(cell.value));
22604
+ const group = getGroup(cell, cells, (evaluatedCell) => evaluatedCell.type === CellValueType.number &&
22605
+ !isDateTimeFormat(evaluatedCell.format || "")).map((cell) => Number(cell.value));
22358
22606
  const increment = calculateIncrementBasedOnGroup(group);
22359
22607
  const evaluation = evaluateLiteral(cell, { locale: DEFAULT_LOCALE });
22360
22608
  return {
@@ -22365,6 +22613,37 @@ stores.inject(MyMetaStore, storeInstance);
22365
22613
  },
22366
22614
  sequence: 40,
22367
22615
  });
22616
+ /**
22617
+ * Returns the date intervals between consecutive dates of an array
22618
+ * in the format of { years: number, months: number, days: number }
22619
+ *
22620
+ * The split is necessary to make abstraction of leap years and
22621
+ * months with different number of days.
22622
+ *
22623
+ * @param dates
22624
+ */
22625
+ function getDateIntervals(dates) {
22626
+ if (dates.length < 2) {
22627
+ return [{ years: 0, months: 0, days: 0 }];
22628
+ }
22629
+ const res = dates.map((date, index) => {
22630
+ if (index === 0) {
22631
+ return { years: 0, months: 0, days: 0 };
22632
+ }
22633
+ const previous = DateTime.fromTimestamp(dates[index - 1].getTime());
22634
+ const years = getTimeDifferenceInWholeYears(previous, date);
22635
+ const months = getTimeDifferenceInWholeMonths(previous, date) % 12;
22636
+ previous.setFullYear(previous.getFullYear() + years);
22637
+ previous.setMonth(previous.getMonth() + months);
22638
+ const days = getTimeDifferenceInWholeDays(previous, date);
22639
+ return {
22640
+ years,
22641
+ months,
22642
+ days,
22643
+ };
22644
+ });
22645
+ return res.slice(1);
22646
+ }
22368
22647
 
22369
22648
  const cellPopoverRegistry = new Registry();
22370
22649
 
@@ -22781,6 +23060,7 @@ stores.inject(MyMetaStore, storeInstance);
22781
23060
  "surface3DChart",
22782
23061
  "bubbleChart",
22783
23062
  "comboChart",
23063
+ "radarChart",
22784
23064
  ];
22785
23065
 
22786
23066
  /** In XLSX color format (no #) */
@@ -23321,7 +23601,7 @@ stores.inject(MyMetaStore, storeInstance);
23321
23601
  lineChart: "line",
23322
23602
  line3DChart: undefined,
23323
23603
  stockChart: undefined,
23324
- radarChart: undefined,
23604
+ radarChart: "radar",
23325
23605
  scatterChart: "scatter",
23326
23606
  pieChart: "pie",
23327
23607
  pie3DChart: undefined,
@@ -24734,7 +25014,7 @@ stores.inject(MyMetaStore, storeInstance);
24734
25014
  custom: _t("Custom"),
24735
25015
  };
24736
25016
  const DEFAULT_TABLE_CONFIG = {
24737
- hasFilters: true,
25017
+ hasFilters: false,
24738
25018
  totalRow: false,
24739
25019
  firstColumn: false,
24740
25020
  lastColumn: false,
@@ -25403,12 +25683,11 @@ stores.inject(MyMetaStore, storeInstance);
25403
25683
  * Get the list of all the XLSX files in the XLSX file structure
25404
25684
  */
25405
25685
  getListOfXMLFiles() {
25406
- const XMLFiles = Object.entries(this.xlsxFileStructure)
25686
+ return Object.entries(this.xlsxFileStructure)
25407
25687
  .filter(([key]) => key !== "images")
25408
25688
  .map(([_, value]) => value)
25409
25689
  .flat()
25410
25690
  .filter(isDefined);
25411
- return XMLFiles;
25412
25691
  }
25413
25692
  /**
25414
25693
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -25572,13 +25851,12 @@ stores.inject(MyMetaStore, storeInstance);
25572
25851
  rgb = this.extractAttr(colorElement, "rgb")?.asString();
25573
25852
  rgb = rgb === DEFAULT_SYSTEM_COLOR ? undefined : rgb;
25574
25853
  }
25575
- const color = {
25854
+ return {
25576
25855
  rgb: rgb || defaultColor,
25577
25856
  auto: this.extractAttr(colorElement, "auto")?.asBool(),
25578
25857
  indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
25579
25858
  tint: this.extractAttr(colorElement, "tint")?.asNum(),
25580
25859
  };
25581
- return color;
25582
25860
  }
25583
25861
  /**
25584
25862
  * Returns the xml file targeted by a relationship.
@@ -26170,7 +26448,7 @@ stores.inject(MyMetaStore, storeInstance);
26170
26448
  hyperlinks: this.extractHyperLinks(sheetElement),
26171
26449
  tables: this.extractTables(sheetElement),
26172
26450
  pivotTables: this.extractPivotTables(),
26173
- isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
26451
+ isVisible: sheetWorkbookInfo.state === "visible",
26174
26452
  };
26175
26453
  })[0];
26176
26454
  }
@@ -26241,8 +26519,7 @@ stores.inject(MyMetaStore, storeInstance);
26241
26519
  const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
26242
26520
  const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
26243
26521
  const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);
26244
- const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26245
- return figures;
26522
+ return new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26246
26523
  })[0];
26247
26524
  return figures || [];
26248
26525
  }
@@ -26260,8 +26537,7 @@ stores.inject(MyMetaStore, storeInstance);
26260
26537
  .filter((relationship) => relationship.type.endsWith("pivotTable"))
26261
26538
  .map((pivotRelationship) => {
26262
26539
  const pivotFile = this.getTargetXmlFile(pivotRelationship);
26263
- const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26264
- return pivot;
26540
+ return new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26265
26541
  });
26266
26542
  }
26267
26543
  catch (e) {
@@ -26660,8 +26936,7 @@ stores.inject(MyMetaStore, storeInstance);
26660
26936
  }
26661
26937
  convertXlsx() {
26662
26938
  const xlsxData = this.getXlsxData();
26663
- const convertedData = this.convertImportedData(xlsxData);
26664
- return convertedData;
26939
+ return this.convertImportedData(xlsxData);
26665
26940
  }
26666
26941
  // ---------------------------------------------------------------------------
26667
26942
  // Parsing XMLs
@@ -27252,6 +27527,20 @@ stores.inject(MyMetaStore, storeInstance);
27252
27527
  }
27253
27528
  return data;
27254
27529
  },
27530
+ })
27531
+ .add("migration_22", {
27532
+ // "tables are no longer inserted with filters by default",
27533
+ versionFrom: "22",
27534
+ migrate(data) {
27535
+ for (const sheet of data.sheets || []) {
27536
+ for (const table of sheet.tables || []) {
27537
+ if (!table.config) {
27538
+ table.config = { ...DEFAULT_TABLE_CONFIG, hasFilters: true };
27539
+ }
27540
+ }
27541
+ }
27542
+ return data;
27543
+ },
27255
27544
  });
27256
27545
  function fixOverlappingFilters(data) {
27257
27546
  for (let sheet of data.sheets || []) {
@@ -27279,7 +27568,7 @@ stores.inject(MyMetaStore, storeInstance);
27279
27568
  * a breaking change is made in the way the state is handled, and an upgrade
27280
27569
  * function should be defined
27281
27570
  */
27282
- const CURRENT_VERSION = 22;
27571
+ const CURRENT_VERSION = 23;
27283
27572
  const INITIAL_SHEET_ID = "Sheet1";
27284
27573
  /**
27285
27574
  * This function tries to load anything that could look like a valid
@@ -27292,7 +27581,7 @@ stores.inject(MyMetaStore, storeInstance);
27292
27581
  if (!data) {
27293
27582
  return createEmptyWorkbookData();
27294
27583
  }
27295
- console.group("Loading data");
27584
+ console.debug("### Loading data ###");
27296
27585
  const start = performance.now();
27297
27586
  if (data["[Content_Types].xml"]) {
27298
27587
  const reader = new XlsxReader(data);
@@ -27306,13 +27595,13 @@ stores.inject(MyMetaStore, storeInstance);
27306
27595
  // apply migrations, if needed
27307
27596
  if ("version" in data) {
27308
27597
  if (data.version < CURRENT_VERSION) {
27309
- console.info("Migrating data from version", data.version);
27598
+ console.debug("Migrating data from version", data.version);
27310
27599
  data = migrate(data);
27311
27600
  }
27312
27601
  }
27313
27602
  data = repairData(data);
27314
- console.info("Data loaded in", performance.now() - start, "ms");
27315
- console.groupEnd();
27603
+ console.debug("Data loaded in", performance.now() - start, "ms");
27604
+ console.debug("###");
27316
27605
  return data;
27317
27606
  }
27318
27607
  // -----------------------------------------------------------------------------
@@ -27342,7 +27631,7 @@ stores.inject(MyMetaStore, storeInstance);
27342
27631
  for (let i = index; i < steps.length; i++) {
27343
27632
  data = steps[i].migrate(data);
27344
27633
  }
27345
- console.info("Data migrated in", performance.now() - start, "ms");
27634
+ console.debug("Data migrated in", performance.now() - start, "ms");
27346
27635
  return data;
27347
27636
  }
27348
27637
  /**
@@ -27524,7 +27813,7 @@ stores.inject(MyMetaStore, storeInstance);
27524
27813
  };
27525
27814
  }
27526
27815
  function createEmptyWorkbookData(sheetName = "Sheet1") {
27527
- const data = {
27816
+ return {
27528
27817
  version: CURRENT_VERSION,
27529
27818
  sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],
27530
27819
  styles: {},
@@ -27537,7 +27826,6 @@ stores.inject(MyMetaStore, storeInstance);
27537
27826
  pivotNextId: 1,
27538
27827
  customTableStyles: {},
27539
27828
  };
27540
- return data;
27541
27829
  }
27542
27830
  function createEmptyExcelSheet(sheetId, name) {
27543
27831
  return {
@@ -27875,9 +28163,9 @@ stores.inject(MyMetaStore, storeInstance);
27875
28163
  /**
27876
28164
  * Get a default chart js configuration
27877
28165
  */
27878
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels = true, horizontalChart, }) {
28166
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { axisFormats, locale, truncateLabels = true, horizontalChart }) {
27879
28167
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
27880
- const options = {
28168
+ const chartOptions = {
27881
28169
  // https://www.chartjs.org/docs/latest/general/responsive.html
27882
28170
  responsive: true, // will resize when its container is resized
27883
28171
  maintainAspectRatio: false, // doesn't maintain the aspect ration (width/height =2 by default) so the user has the choice of the exact layout
@@ -27921,11 +28209,13 @@ stores.inject(MyMetaStore, storeInstance);
27921
28209
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
27922
28210
  // tooltipItem.parsed can be an object or a number for pie charts
27923
28211
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
27924
- if (!yLabel) {
28212
+ if (yLabel === undefined || yLabel === null) {
27925
28213
  yLabel = tooltipItem.parsed;
27926
28214
  }
27927
- const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
27928
- const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
28215
+ const axisId = horizontalChart
28216
+ ? tooltipItem.dataset.xAxisID
28217
+ : tooltipItem.dataset.yAxisID;
28218
+ const yLabelStr = formatChartDatasetValue(axisFormats, locale)(yLabel, axisId);
27929
28219
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
27930
28220
  },
27931
28221
  },
@@ -27934,7 +28224,7 @@ stores.inject(MyMetaStore, storeInstance);
27934
28224
  };
27935
28225
  return {
27936
28226
  type: chart.type,
27937
- options,
28227
+ options: chartOptions,
27938
28228
  data: {
27939
28229
  labels: truncateLabels ? labels.map(truncateLabel) : labels,
27940
28230
  datasets: [],
@@ -27993,7 +28283,8 @@ stores.inject(MyMetaStore, storeInstance);
27993
28283
  * Get the format to apply to the the dataset values. This format is defined as the first format
27994
28284
  * found in the dataset ranges that isn't a date format.
27995
28285
  */
27996
- function getChartDatasetFormat(getters, dataSets) {
28286
+ function getChartDatasetFormat(getters, allDataSets, axis) {
28287
+ const dataSets = allDataSets.filter((ds) => (axis === "right") === !!ds.rightYAxis);
27997
28288
  for (const ds of dataSets) {
27998
28289
  const formatsInDataset = getters.getRangeFormats(ds.dataRange);
27999
28290
  const format = formatsInDataset.find((f) => f !== undefined && !isDateTimeFormat(f));
@@ -28247,12 +28538,16 @@ stores.inject(MyMetaStore, storeInstance);
28247
28538
  if (chart.aggregated) {
28248
28539
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28249
28540
  }
28250
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
28541
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28542
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28251
28543
  const locale = getters.getLocale();
28252
- const localeFormat = { format: dataSetFormat, locale };
28253
28544
  const fontColor = chartFontColor(chart.background);
28545
+ const axisFormats = chart.horizontal
28546
+ ? { x: leftAxisFormat || rightAxisFormat }
28547
+ : { y: leftAxisFormat, y1: rightAxisFormat };
28254
28548
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
28255
- ...localeFormat,
28549
+ locale,
28550
+ axisFormats,
28256
28551
  horizontalChart: chart.horizontal,
28257
28552
  });
28258
28553
  const legend = {
@@ -28273,51 +28568,27 @@ stores.inject(MyMetaStore, storeInstance);
28273
28568
  };
28274
28569
  config.options.indexAxis = chart.horizontal ? "y" : "x";
28275
28570
  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
- };
28571
+ const definition = chart.getDefinition();
28572
+ const options = { stacked: chart.stacked, locale };
28573
+ if (chart.horizontal) {
28574
+ const format = leftAxisFormat || rightAxisFormat;
28575
+ config.options.scales.x = getChartAxis(definition, "bottom", "values", { ...options, format });
28576
+ config.options.scales.y = getChartAxis(definition, "left", "labels", options);
28301
28577
  }
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
- }
28578
+ else {
28579
+ config.options.scales.x = getChartAxis(definition, "bottom", "labels", options);
28580
+ const leftAxisOptions = { ...options, format: leftAxisFormat };
28581
+ config.options.scales.y = getChartAxis(definition, "left", "values", leftAxisOptions);
28582
+ const rightAxisOptions = { ...options, format: rightAxisFormat };
28583
+ config.options.scales.y1 = getChartAxis(definition, "right", "values", rightAxisOptions);
28313
28584
  }
28585
+ config.options.scales = removeFalsyAttributes(config.options.scales);
28314
28586
  config.options.plugins.chartShowValuesPlugin = {
28315
28587
  showValues: chart.showValues,
28316
28588
  background: chart.background,
28317
28589
  horizontal: chart.horizontal,
28318
- callback: formatTickValue(localeFormat),
28590
+ callback: formatChartDatasetValue(axisFormats, locale),
28319
28591
  };
28320
- const definition = chart.getDefinition();
28321
28592
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28322
28593
  const trendDatasets = [];
28323
28594
  for (const index in dataSetsValues) {
@@ -28332,12 +28603,10 @@ stores.inject(MyMetaStore, storeInstance);
28332
28603
  };
28333
28604
  config.data.datasets.push(dataset);
28334
28605
  if (definition.dataSets?.[index]?.label) {
28335
- const label = definition.dataSets[index].label;
28336
- dataset.label = label;
28337
- }
28338
- if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
28339
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
28606
+ dataset.label = definition.dataSets[index].label;
28340
28607
  }
28608
+ dataset.yAxisID = chart.horizontal ? "y" : definition.dataSets[index].yAxisId || "y";
28609
+ dataset.xAxisID = "x";
28341
28610
  const trend = definition.dataSets?.[index].trend;
28342
28611
  if (!trend?.display || chart.horizontal) {
28343
28612
  continue;
@@ -28353,7 +28622,7 @@ stores.inject(MyMetaStore, storeInstance);
28353
28622
  */
28354
28623
  const maxLength = Math.max(...trendDatasets.map((trendDataset) => trendDataset.data.length));
28355
28624
  config.options.scales[TREND_LINE_XAXIS_ID] = {
28356
- ...xAxis,
28625
+ ...config.options.scales.x,
28357
28626
  labels: Array(maxLength).fill(""),
28358
28627
  offset: false,
28359
28628
  display: false,
@@ -28362,13 +28631,10 @@ stores.inject(MyMetaStore, storeInstance);
28362
28631
  * datasets to ensure the way we distinguish the originals and trendLine datasets after
28363
28632
  */
28364
28633
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28365
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28366
28634
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28367
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28368
- // @ts-expect-error
28369
- return originalTooltipTitle?.(tooltipItems);
28370
- }
28371
- return "";
28635
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28636
+ ? undefined
28637
+ : "";
28372
28638
  };
28373
28639
  }
28374
28640
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -28643,8 +28909,10 @@ stores.inject(MyMetaStore, storeInstance);
28643
28909
  }
28644
28910
  const locale = getters.getLocale();
28645
28911
  const truncateLabels = axisType === "category";
28646
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
28647
- const options = { format: dataSetFormat, locale, truncateLabels };
28912
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
28913
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28914
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
28915
+ const options = { locale, truncateLabels, axisFormats };
28648
28916
  const fontColor = chartFontColor(chart.background);
28649
28917
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, options);
28650
28918
  const legend = {
@@ -28674,52 +28942,18 @@ stores.inject(MyMetaStore, storeInstance);
28674
28942
  displayLegend: chart.legendPosition === "top",
28675
28943
  }),
28676
28944
  };
28677
- const xAxis = {
28678
- ticks: {
28679
- padding: 5,
28680
- color: fontColor,
28681
- },
28682
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
28683
- };
28945
+ const definition = chart.getDefinition();
28946
+ const stacked = "stacked" in chart && chart.stacked;
28684
28947
  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
- },
28948
+ x: getChartAxis(definition, "bottom", "labels", { locale }),
28949
+ y: getChartAxis(definition, "left", "values", { locale, stacked, format: leftAxisFormat }),
28950
+ y1: getChartAxis(definition, "right", "values", { locale, stacked, format: rightAxisFormat }),
28693
28951
  };
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
- }
28952
+ config.options.scales = removeFalsyAttributes(config.options.scales);
28719
28953
  config.options.plugins.chartShowValuesPlugin = {
28720
28954
  showValues: chart.showValues,
28721
28955
  background: chart.background,
28722
- callback: formatTickValue(options),
28956
+ callback: formatChartDatasetValue(axisFormats, locale),
28723
28957
  };
28724
28958
  if (chart.dataSetsHaveTitle &&
28725
28959
  dataSetsValues[0] &&
@@ -28738,7 +28972,6 @@ stores.inject(MyMetaStore, storeInstance);
28738
28972
  else if (axisType === "linear") {
28739
28973
  config.options.scales.x.type = "linear";
28740
28974
  config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
28741
- config.options.plugins.tooltip.callbacks.title = () => "";
28742
28975
  config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
28743
28976
  const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
28744
28977
  let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
@@ -28746,7 +28979,7 @@ stores.inject(MyMetaStore, storeInstance);
28746
28979
  label = toNumber(label, locale);
28747
28980
  }
28748
28981
  const formattedX = formatValue(label, { locale, format: labelFormat });
28749
- const formattedY = formatValue(dataSetPoint, { locale, format: dataSetFormat });
28982
+ const formattedY = formatValue(dataSetPoint, { locale, format: leftAxisFormat });
28750
28983
  const dataSetTitle = tooltipItem.dataset.label;
28751
28984
  return formattedX
28752
28985
  ? `${dataSetTitle}: (${formattedX}, ${formattedY})`
@@ -28756,7 +28989,6 @@ stores.inject(MyMetaStore, storeInstance);
28756
28989
  const areaChart = "fillArea" in chart ? chart.fillArea : false;
28757
28990
  const stackedChart = "stacked" in chart ? chart.stacked : false;
28758
28991
  const cumulative = "cumulative" in chart ? chart.cumulative : false;
28759
- const definition = chart.getDefinition();
28760
28992
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28761
28993
  for (let [index, { label, data }] of dataSetsValues.entries()) {
28762
28994
  const color = colors.next();
@@ -28794,12 +29026,9 @@ stores.inject(MyMetaStore, storeInstance);
28794
29026
  const trendDatasets = [];
28795
29027
  for (const [index, dataset] of config.data.datasets.entries()) {
28796
29028
  if (definition.dataSets?.[index]?.label) {
28797
- const label = definition.dataSets[index].label;
28798
- dataset.label = label;
28799
- }
28800
- if (definition.dataSets?.[index]?.yAxisId) {
28801
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
29029
+ dataset.label = definition.dataSets[index].label;
28802
29030
  }
29031
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId || "y";
28803
29032
  const trend = definition.dataSets?.[index].trend;
28804
29033
  if (!trend?.display) {
28805
29034
  continue;
@@ -28816,7 +29045,7 @@ stores.inject(MyMetaStore, storeInstance);
28816
29045
  * set so that the second axis points match the classical x axis
28817
29046
  */
28818
29047
  config.options.scales[TREND_LINE_XAXIS_ID] = {
28819
- ...xAxis,
29048
+ ...config.options.scales.x,
28820
29049
  type: "category",
28821
29050
  labels: range(0, maxLength).map((x) => x.toString()),
28822
29051
  offset: false,
@@ -28826,22 +29055,15 @@ stores.inject(MyMetaStore, storeInstance);
28826
29055
  * distinguish the originals and trendLine datasets after
28827
29056
  */
28828
29057
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28829
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28830
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28831
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28832
- // @ts-expect-error
28833
- return originalTooltipTitle?.(tooltipItems);
28834
- }
28835
- return "";
28836
- };
28837
29058
  }
29059
+ config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29060
+ const displayTooltipTitle = axisType !== "linear" &&
29061
+ tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
29062
+ return displayTooltipTitle ? undefined : "";
29063
+ };
28838
29064
  return {
28839
29065
  chartJsConfig: config,
28840
29066
  background: chart.background || BACKGROUND_CHART_COLOR,
28841
- dataSetsValues,
28842
- labelValues,
28843
- dataSetFormat,
28844
- labelFormat,
28845
29067
  };
28846
29068
  }
28847
29069
 
@@ -28899,6 +29121,7 @@ stores.inject(MyMetaStore, storeInstance);
28899
29121
  ranges.push({
28900
29122
  ...this.dataSetDesign?.[i],
28901
29123
  dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
29124
+ type: this.dataSetDesign?.[i]?.type ?? (i ? "line" : "bar"),
28902
29125
  });
28903
29126
  }
28904
29127
  return {
@@ -28944,9 +29167,13 @@ stores.inject(MyMetaStore, storeInstance);
28944
29167
  return new ComboChart(definition, this.sheetId, this.getters);
28945
29168
  }
28946
29169
  static getDefinitionFromContextCreation(context) {
29170
+ const dataSets = (context.range ?? []).map((ds, index) => ({
29171
+ ...ds,
29172
+ type: index ? "line" : "bar",
29173
+ }));
28947
29174
  return {
28948
29175
  background: context.background,
28949
- dataSets: context.range ?? [],
29176
+ dataSets,
28950
29177
  dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
28951
29178
  aggregated: context.aggregated,
28952
29179
  legendPosition: context.legendPosition ?? "top",
@@ -28969,10 +29196,8 @@ stores.inject(MyMetaStore, storeInstance);
28969
29196
  }
28970
29197
  }
28971
29198
  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));
29199
+ const mainDataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29200
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
28976
29201
  const locale = getters.getLocale();
28977
29202
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
28978
29203
  let labels = labelValues.formattedValues;
@@ -28986,12 +29211,11 @@ stores.inject(MyMetaStore, storeInstance);
28986
29211
  if (chart.aggregated) {
28987
29212
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28988
29213
  }
28989
- const localeFormat = { format: mainDataSetFormat, locale };
28990
29214
  const fontColor = chartFontColor(chart.background);
28991
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
29215
+ const axisFormats = { y: mainDataSetFormat, y1: lineDataSetsFormat };
29216
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, { locale, axisFormats });
28992
29217
  const legend = {
28993
29218
  labels: { color: fontColor },
28994
- reverse: true,
28995
29219
  };
28996
29220
  if (chart.legendPosition === "none") {
28997
29221
  legend.display = false;
@@ -29006,52 +29230,17 @@ stores.inject(MyMetaStore, storeInstance);
29006
29230
  displayLegend: chart.legendPosition === "top",
29007
29231
  }),
29008
29232
  };
29233
+ const definition = chart.getDefinition();
29009
29234
  config.options.scales = {
29010
- x: {
29011
- ticks: {
29012
- padding: 5,
29013
- color: fontColor,
29014
- },
29015
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
29016
- },
29235
+ x: getChartAxis(definition, "bottom", "labels", { locale }),
29236
+ y: getChartAxis(definition, "left", "values", { locale, format: mainDataSetFormat }),
29237
+ y1: getChartAxis(definition, "right", "values", { locale, format: lineDataSetsFormat }),
29017
29238
  };
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
- }
29239
+ config.options.scales = removeFalsyAttributes(config.options.scales);
29051
29240
  config.options.plugins.chartShowValuesPlugin = {
29052
29241
  showValues: chart.showValues,
29053
29242
  background: chart.background,
29054
- callback: formatTickValue({ format: mainDataSetFormat, locale }),
29243
+ callback: formatChartDatasetValue(axisFormats, locale),
29055
29244
  };
29056
29245
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
29057
29246
  let maxLength = 0;
@@ -29059,14 +29248,15 @@ stores.inject(MyMetaStore, storeInstance);
29059
29248
  for (let [index, { label, data }] of dataSetsValues.entries()) {
29060
29249
  const design = definition.dataSets[index];
29061
29250
  const color = colors.next();
29251
+ const type = design?.type ?? "line";
29062
29252
  const dataset = {
29063
29253
  label: design?.label ?? label,
29064
29254
  data,
29065
29255
  borderColor: color,
29066
29256
  backgroundColor: color,
29067
29257
  yAxisID: design?.yAxisId ?? "y",
29068
- type: index === 0 ? "bar" : "line",
29069
- order: -index,
29258
+ type,
29259
+ order: type === "bar" ? dataSetsValues.length + index : index,
29070
29260
  };
29071
29261
  config.data.datasets.push(dataset);
29072
29262
  const trend = definition.dataSets?.[index].trend;
@@ -29093,13 +29283,10 @@ stores.inject(MyMetaStore, storeInstance);
29093
29283
  * distinguish the originals and trendLine datasets after
29094
29284
  */
29095
29285
  trendDatasets.forEach((x) => config.data.datasets.push(x));
29096
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
29097
29286
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29098
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
29099
- // @ts-expect-error
29100
- return originalTooltipTitle?.(tooltipItems);
29101
- }
29102
- return "";
29287
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
29288
+ ? undefined
29289
+ : "";
29103
29290
  };
29104
29291
  }
29105
29292
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -29679,7 +29866,7 @@ stores.inject(MyMetaStore, storeInstance);
29679
29866
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
29680
29867
  }
29681
29868
  ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
29682
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
29869
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
29683
29870
  const locale = getters.getLocale();
29684
29871
  const config = getPieConfiguration(chart, labels, { format: dataSetFormat, locale });
29685
29872
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
@@ -29834,7 +30021,199 @@ stores.inject(MyMetaStore, storeInstance);
29834
30021
  return tooltipLabelCallback(tooltipItem);
29835
30022
  };
29836
30023
  const callback = config.options.plugins.chartShowValuesPlugin.callback;
29837
- config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
30024
+ config.options.plugins.chartShowValuesPlugin.callback = (x, axisId) => callback(Math.abs(x), axisId);
30025
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30026
+ }
30027
+
30028
+ class RadarChart extends AbstractChart {
30029
+ dataSets;
30030
+ labelRange;
30031
+ background;
30032
+ legendPosition;
30033
+ stacked;
30034
+ aggregated;
30035
+ type = "radar";
30036
+ dataSetsHaveTitle;
30037
+ dataSetDesign;
30038
+ fillArea;
30039
+ constructor(definition, sheetId, getters) {
30040
+ super(definition, sheetId, getters);
30041
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
30042
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
30043
+ this.background = definition.background;
30044
+ this.legendPosition = definition.legendPosition;
30045
+ this.stacked = definition.stacked;
30046
+ this.aggregated = definition.aggregated;
30047
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
30048
+ this.dataSetDesign = definition.dataSets;
30049
+ this.fillArea = definition.fillArea;
30050
+ }
30051
+ static transformDefinition(definition, executed) {
30052
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
30053
+ }
30054
+ static validateChartDefinition(validator, definition) {
30055
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
30056
+ }
30057
+ static getDefinitionFromContextCreation(context) {
30058
+ return {
30059
+ background: context.background,
30060
+ dataSets: context.range ?? [],
30061
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
30062
+ stacked: context.stacked ?? false,
30063
+ aggregated: context.aggregated ?? false,
30064
+ legendPosition: context.legendPosition ?? "top",
30065
+ title: context.title || { text: "" },
30066
+ type: "radar",
30067
+ labelRange: context.auxiliaryRange || undefined,
30068
+ fillArea: context.fillArea ?? false,
30069
+ };
30070
+ }
30071
+ getContextCreation() {
30072
+ const range = [];
30073
+ for (const [i, dataSet] of this.dataSets.entries()) {
30074
+ range.push({
30075
+ ...this.dataSetDesign?.[i],
30076
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
30077
+ });
30078
+ }
30079
+ return {
30080
+ ...this,
30081
+ range,
30082
+ auxiliaryRange: this.labelRange
30083
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
30084
+ : undefined,
30085
+ };
30086
+ }
30087
+ copyForSheetId(sheetId) {
30088
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30089
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30090
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30091
+ return new RadarChart(definition, sheetId, this.getters);
30092
+ }
30093
+ copyInSheetId(sheetId) {
30094
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
30095
+ return new RadarChart(definition, sheetId, this.getters);
30096
+ }
30097
+ getDefinition() {
30098
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
30099
+ }
30100
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
30101
+ const ranges = [];
30102
+ for (const [i, dataSet] of dataSets.entries()) {
30103
+ ranges.push({
30104
+ ...this.dataSetDesign?.[i],
30105
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
30106
+ });
30107
+ }
30108
+ return {
30109
+ type: "radar",
30110
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
30111
+ background: this.background,
30112
+ dataSets: ranges,
30113
+ legendPosition: this.legendPosition,
30114
+ labelRange: labelRange
30115
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
30116
+ : undefined,
30117
+ title: this.title,
30118
+ stacked: this.stacked,
30119
+ aggregated: this.aggregated,
30120
+ fillArea: this.fillArea,
30121
+ };
30122
+ }
30123
+ getDefinitionForExcel() {
30124
+ if (this.aggregated) {
30125
+ return undefined;
30126
+ }
30127
+ const dataSets = this.dataSets
30128
+ .map((ds) => toExcelDataset(this.getters, ds))
30129
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
30130
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
30131
+ const definition = this.getDefinition();
30132
+ return {
30133
+ ...definition,
30134
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
30135
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
30136
+ dataSets,
30137
+ labelRange,
30138
+ };
30139
+ }
30140
+ updateRanges(applyChange) {
30141
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
30142
+ if (!isStale) {
30143
+ return this;
30144
+ }
30145
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
30146
+ return new RadarChart(definition, this.sheetId, this.getters);
30147
+ }
30148
+ }
30149
+ function createRadarChartRuntime(chart, getters) {
30150
+ const definition = chart.getDefinition();
30151
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
30152
+ let labels = labelValues.formattedValues;
30153
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
30154
+ if (chart.dataSetsHaveTitle &&
30155
+ dataSetsValues[0] &&
30156
+ labels.length > dataSetsValues[0].data.length) {
30157
+ labels.shift();
30158
+ }
30159
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
30160
+ if (chart.aggregated) {
30161
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
30162
+ }
30163
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
30164
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
30165
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
30166
+ const locale = getters.getLocale();
30167
+ const fontColor = chartFontColor(chart.background);
30168
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
30169
+ axisFormats,
30170
+ locale,
30171
+ });
30172
+ const legend = {
30173
+ labels: { color: fontColor },
30174
+ };
30175
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
30176
+ legend.display = false;
30177
+ }
30178
+ else {
30179
+ legend.position = chart.legendPosition;
30180
+ }
30181
+ const fill = definition.fillArea ?? false;
30182
+ if (!fill) {
30183
+ legend.labels["boxHeight"] = 0;
30184
+ }
30185
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
30186
+ config.options.plugins.tooltip = {
30187
+ ...config.options.plugins?.tooltip,
30188
+ callbacks: {
30189
+ label: function (tooltipItem) {
30190
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
30191
+ const yLabel = tooltipItem.parsed.r;
30192
+ return xLabel ? `${xLabel}: ${yLabel}` : yLabel.toString();
30193
+ },
30194
+ },
30195
+ };
30196
+ config.options.layout = {
30197
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
30198
+ };
30199
+ const colorGenerator = getChartColorsGenerator(definition, dataSetsValues.length);
30200
+ for (let i = 0; i < dataSetsValues.length; i++) {
30201
+ let { label, data } = dataSetsValues[i];
30202
+ if (definition.dataSets?.[i]?.label) {
30203
+ label = definition.dataSets[i].label;
30204
+ }
30205
+ const borderColor = colorGenerator.next();
30206
+ const dataset = {
30207
+ label,
30208
+ data,
30209
+ borderColor,
30210
+ };
30211
+ if (fill) {
30212
+ dataset.backgroundColor = setColorAlpha(borderColor, 0.3);
30213
+ dataset["fill"] = true;
30214
+ }
30215
+ config.data.datasets.push(dataset);
30216
+ }
29838
30217
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29839
30218
  }
29840
30219
 
@@ -30225,7 +30604,8 @@ stores.inject(MyMetaStore, storeInstance);
30225
30604
  if (chart.showSubTotals) {
30226
30605
  labels.push(_t("Subtotal"));
30227
30606
  }
30228
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
30607
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets, "left") ||
30608
+ getChartDatasetFormat(getters, chart.dataSets, "right");
30229
30609
  const locale = getters.getLocale();
30230
30610
  const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
30231
30611
  const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
@@ -30359,6 +30739,15 @@ stores.inject(MyMetaStore, storeInstance);
30359
30739
  getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
30360
30740
  sequence: 80,
30361
30741
  });
30742
+ chartRegistry.add("radar", {
30743
+ match: (type) => type === "radar",
30744
+ createChart: (definition, sheetId, getters) => new RadarChart(definition, sheetId, getters),
30745
+ getChartRuntime: createRadarChartRuntime,
30746
+ validateChartDefinition: RadarChart.validateChartDefinition,
30747
+ transformDefinition: RadarChart.transformDefinition,
30748
+ getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30749
+ sequence: 80,
30750
+ });
30362
30751
  const chartComponentRegistry = new Registry();
30363
30752
  chartComponentRegistry.add("line", ChartJsComponent);
30364
30753
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30369,6 +30758,7 @@ stores.inject(MyMetaStore, storeInstance);
30369
30758
  chartComponentRegistry.add("scorecard", ScorecardChart);
30370
30759
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30371
30760
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30761
+ chartComponentRegistry.add("radar", ChartJsComponent);
30372
30762
  const chartCategories = {
30373
30763
  line: _t("Line"),
30374
30764
  column: _t("Column"),
@@ -30510,6 +30900,24 @@ stores.inject(MyMetaStore, storeInstance);
30510
30900
  chartType: "pyramid",
30511
30901
  category: "misc",
30512
30902
  preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
30903
+ })
30904
+ .add("radar", {
30905
+ matcher: (definition) => definition.type === "radar" && !definition.fillArea,
30906
+ displayName: _t("Radar"),
30907
+ chartSubtype: "radar",
30908
+ chartType: "radar",
30909
+ subtypeDefinition: { fillArea: false },
30910
+ category: "misc",
30911
+ preview: "o-spreadsheet-ChartPreview.RADAR_CHART",
30912
+ })
30913
+ .add("filled_radar", {
30914
+ matcher: (definition) => definition.type === "radar" && !!definition.fillArea,
30915
+ displayName: _t("Filled Radar"),
30916
+ chartType: "radar",
30917
+ chartSubtype: "filled_radar",
30918
+ subtypeDefinition: { fillArea: true },
30919
+ category: "misc",
30920
+ preview: "o-spreadsheet-ChartPreview.FILLED_RADAR_CHART",
30513
30921
  });
30514
30922
 
30515
30923
  /**
@@ -30577,11 +30985,10 @@ stores.inject(MyMetaStore, storeInstance);
30577
30985
  const rect = getters.getVisibleRect(getters.getActiveMainViewport());
30578
30986
  const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
30579
30987
  const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
30580
- const position = {
30988
+ return {
30581
30989
  x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
30582
30990
  y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
30583
30991
  }; // Position at the center of the scrollable viewport
30584
- return position;
30585
30992
  }
30586
30993
  function getMaxFigureSize(getters, figureSize) {
30587
30994
  const size = deepCopy(figureSize);
@@ -31154,14 +31561,13 @@ stores.inject(MyMetaStore, storeInstance);
31154
31561
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
31155
31562
  const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
31156
31563
  verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
31157
- const cssProperties = {
31564
+ return {
31158
31565
  top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
31159
31566
  this.spreadsheetOffset.y -
31160
31567
  verticalOffset +
31161
31568
  "px",
31162
31569
  left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
31163
31570
  };
31164
- return cssProperties;
31165
31571
  }
31166
31572
  getCurrentPosition(elDims) {
31167
31573
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
@@ -32389,7 +32795,7 @@ stores.inject(MyMetaStore, storeInstance);
32389
32795
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
32390
32796
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
32391
32797
  */
32392
- function interactiveCreateTable(env, sheetId, tableConfig) {
32798
+ function interactiveCreateTable(env, sheetId, tableConfig = DEFAULT_TABLE_CONFIG) {
32393
32799
  let target = env.model.getters.getSelectedZones();
32394
32800
  let isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
32395
32801
  if (target.length === 1 && !isDynamic && getZoneArea(target[0]) === 1) {
@@ -33448,7 +33854,7 @@ stores.inject(MyMetaStore, storeInstance);
33448
33854
  }
33449
33855
 
33450
33856
  const pivotProperties = {
33451
- name: _t("Edit Pivot"),
33857
+ name: _t("See pivot properties"),
33452
33858
  execute(env) {
33453
33859
  const position = env.model.getters.getActivePosition();
33454
33860
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
@@ -33602,8 +34008,7 @@ stores.inject(MyMetaStore, storeInstance);
33602
34008
  })
33603
34009
  .add("pivot_properties", {
33604
34010
  ...pivotProperties,
33605
- sequence: 160,
33606
- separator: true,
34011
+ sequence: 170,
33607
34012
  });
33608
34013
 
33609
34014
  const sortRange = {
@@ -35295,6 +35700,7 @@ stores.inject(MyMetaStore, storeInstance);
35295
35700
  <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
35296
35701
  </svg>
35297
35702
  `;
35703
+ const CHECKBOX_WIDTH = 14;
35298
35704
  css /* scss */ `
35299
35705
  label.o-checkbox {
35300
35706
  input {
@@ -35302,8 +35708,8 @@ stores.inject(MyMetaStore, storeInstance);
35302
35708
  -webkit-appearance: none;
35303
35709
  -moz-appearance: none;
35304
35710
  border-radius: 0;
35305
- width: 14px;
35306
- height: 14px;
35711
+ width: ${CHECKBOX_WIDTH}px;
35712
+ height: ${CHECKBOX_WIDTH}px;
35307
35713
  vertical-align: top;
35308
35714
  box-sizing: border-box;
35309
35715
  outline: none;
@@ -35349,6 +35755,7 @@ stores.inject(MyMetaStore, storeInstance);
35349
35755
  static template = "o_spreadsheet.Section";
35350
35756
  static props = {
35351
35757
  class: { type: String, optional: true },
35758
+ title: { type: String, optional: true },
35352
35759
  slots: Object,
35353
35760
  };
35354
35761
  }
@@ -36078,17 +36485,11 @@ stores.inject(MyMetaStore, storeInstance);
36078
36485
  stacked,
36079
36486
  });
36080
36487
  }
36081
- onUpdateAggregated(aggregated) {
36082
- this.props.updateChart(this.props.figureId, {
36083
- aggregated,
36084
- });
36085
- }
36086
36488
  }
36087
36489
 
36088
36490
  css /* scss */ `
36089
36491
  .o_side_panel_collapsible_title {
36090
36492
  font-size: 16px;
36091
- font-weight: bold;
36092
36493
  cursor: pointer;
36093
36494
  padding: 6px 0px 6px 6px !important;
36094
36495
 
@@ -36125,49 +36526,40 @@ stores.inject(MyMetaStore, storeInstance);
36125
36526
  static template = "o-spreadsheet-SidePanelCollapsible";
36126
36527
  static props = {
36127
36528
  slots: Object,
36529
+ title: { type: String, optional: true },
36128
36530
  collapsedAtInit: { type: Boolean, optional: true },
36129
36531
  class: { type: String, optional: true },
36130
36532
  };
36131
36533
  currentId = (CURRENT_COLLAPSIBLE_ID++).toString();
36132
36534
  }
36133
36535
 
36134
- const CIRCLE_SVG = /*xml*/ `
36135
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
36136
- <circle r="2" fill="#FFF"/>
36137
- </svg>
36138
- `;
36139
36536
  css /* scss */ `
36140
- .o-radio {
36141
- input {
36142
- appearance: none;
36143
- -webkit-appearance: none;
36144
- -moz-appearance: none;
36145
- width: 14px;
36146
- height: 14px;
36147
- border: 1px solid ${GRAY_300};
36148
- box-sizing: border-box;
36149
- outline: none;
36150
- border-radius: 8px;
36151
-
36152
- &:checked {
36153
- background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
36154
- background-color: ${ACTION_COLOR};
36537
+ .o-badge-selection {
36538
+ gap: 1px;
36539
+ button.o-button {
36540
+ border-radius: 0;
36541
+ &.selected {
36542
+ color: ${GRAY_900};
36155
36543
  border-color: ${ACTION_COLOR};
36544
+ background: ${BADGE_SELECTED_COLOR};
36545
+ font-weight: 600;
36546
+ }
36547
+
36548
+ &:first-child {
36549
+ border-radius: 4px 0 0 4px;
36550
+ }
36551
+ &:last-child {
36552
+ border-radius: 0 4px 4px 0;
36156
36553
  }
36157
36554
  }
36158
36555
  }
36159
36556
  `;
36160
- class RadioSelection extends owl.Component {
36161
- static template = "o-spreadsheet.RadioSelection";
36557
+ class BadgeSelection extends owl.Component {
36558
+ static template = "o-spreadsheet.BadgeSelection";
36162
36559
  static props = {
36163
36560
  choices: Array,
36164
36561
  onChange: Function,
36165
- selectedValue: { optional: false },
36166
- name: String,
36167
- direction: { type: String, optional: true },
36168
- };
36169
- static defaultProps = {
36170
- direction: "horizontal",
36562
+ selectedValue: String,
36171
36563
  };
36172
36564
  }
36173
36565
 
@@ -36712,86 +37104,6 @@ stores.inject(MyMetaStore, storeInstance);
36712
37104
  }
36713
37105
  }
36714
37106
 
36715
- const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
36716
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
36717
- <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
36718
- </svg>
36719
- `;
36720
- css /* scss */ `
36721
- .o-round-color-picker-button {
36722
- width: 18px;
36723
- height: 18px;
36724
- cursor: pointer;
36725
- border: 1px solid ${GRAY_300};
36726
- background-position: 1px 1px;
36727
- background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
36728
- }
36729
- `;
36730
- class RoundColorPicker extends owl.Component {
36731
- static template = "o-spreadsheet.RoundColorPicker";
36732
- static components = { ColorPickerWidget, Section, ColorPicker };
36733
- static props = {
36734
- currentColor: { type: String, optional: true },
36735
- title: { type: String, optional: true },
36736
- onColorPicked: Function,
36737
- };
36738
- colorPickerButtonRef = owl.useRef("colorPickerButton");
36739
- state;
36740
- setup() {
36741
- this.state = owl.useState({ pickerOpened: false });
36742
- owl.useExternalListener(window, "click", this.closePicker);
36743
- }
36744
- closePicker() {
36745
- this.state.pickerOpened = false;
36746
- }
36747
- togglePicker() {
36748
- this.state.pickerOpened = !this.state.pickerOpened;
36749
- }
36750
- onColorPicked(color) {
36751
- this.props.onColorPicked(color);
36752
- this.state.pickerOpened = false;
36753
- }
36754
- get colorPickerAnchorRect() {
36755
- const button = this.colorPickerButtonRef.el;
36756
- return getBoundingRectAsPOJO(button);
36757
- }
36758
- get buttonStyle() {
36759
- return cssPropertiesToCss({
36760
- background: this.props.currentColor,
36761
- });
36762
- }
36763
- }
36764
-
36765
- css /* scss */ `
36766
- .o-badge-selection {
36767
- gap: 1px;
36768
- button.o-button {
36769
- border-radius: 0;
36770
- &.selected {
36771
- color: ${GRAY_900};
36772
- border-color: ${ACTION_COLOR};
36773
- background: ${BADGE_SELECTED_COLOR};
36774
- font-weight: 600;
36775
- }
36776
-
36777
- &:first-child {
36778
- border-radius: 4px 0 0 4px;
36779
- }
36780
- &:last-child {
36781
- border-radius: 0 4px 4px 0;
36782
- }
36783
- }
36784
- }
36785
- `;
36786
- class BadgeSelection extends owl.Component {
36787
- static template = "o-spreadsheet.BadgeSelection";
36788
- static props = {
36789
- choices: Array,
36790
- onChange: Function,
36791
- selectedValue: String,
36792
- };
36793
- }
36794
-
36795
37107
  css /* scss */ `
36796
37108
  .o-chart-title-designer {
36797
37109
  > span {
@@ -36946,8 +37258,7 @@ stores.inject(MyMetaStore, storeInstance);
36946
37258
  this.props.updateChart(this.props.figureId, { axesDesign });
36947
37259
  }
36948
37260
  updateAxisEditor(ev) {
36949
- const axis = ev.target.value;
36950
- this.state.currentAxis = axis;
37261
+ this.state.currentAxis = ev.target.value;
36951
37262
  }
36952
37263
  getAxisTitle() {
36953
37264
  const axesDesign = this.props.definition.axesDesign ?? {};
@@ -36966,6 +37277,56 @@ stores.inject(MyMetaStore, storeInstance);
36966
37277
  }
36967
37278
  }
36968
37279
 
37280
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
37281
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
37282
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
37283
+ </svg>
37284
+ `;
37285
+ css /* scss */ `
37286
+ .o-round-color-picker-button {
37287
+ width: 18px;
37288
+ height: 18px;
37289
+ cursor: pointer;
37290
+ border: 1px solid ${GRAY_300};
37291
+ background-position: 1px 1px;
37292
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
37293
+ }
37294
+ `;
37295
+ class RoundColorPicker extends owl.Component {
37296
+ static template = "o-spreadsheet.RoundColorPicker";
37297
+ static components = { Section, ColorPicker };
37298
+ static props = {
37299
+ currentColor: { type: String, optional: true },
37300
+ title: { type: String, optional: true },
37301
+ onColorPicked: Function,
37302
+ };
37303
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
37304
+ state;
37305
+ setup() {
37306
+ this.state = owl.useState({ pickerOpened: false });
37307
+ owl.useExternalListener(window, "click", this.closePicker);
37308
+ }
37309
+ closePicker() {
37310
+ this.state.pickerOpened = false;
37311
+ }
37312
+ togglePicker() {
37313
+ this.state.pickerOpened = !this.state.pickerOpened;
37314
+ }
37315
+ onColorPicked(color) {
37316
+ this.props.onColorPicked(color);
37317
+ this.state.pickerOpened = false;
37318
+ }
37319
+ get colorPickerAnchorRect() {
37320
+ const button = this.colorPickerButtonRef.el;
37321
+ return getBoundingRectAsPOJO(button);
37322
+ }
37323
+ get buttonStyle() {
37324
+ return cssPropertiesToCss({
37325
+ background: this.props.currentColor,
37326
+ });
37327
+ }
37328
+ }
37329
+
36969
37330
  class GeneralDesignEditor extends owl.Component {
36970
37331
  static template = "o-spreadsheet-GeneralDesignEditor";
36971
37332
  static components = {
@@ -37030,58 +37391,75 @@ stores.inject(MyMetaStore, storeInstance);
37030
37391
  }
37031
37392
  }
37032
37393
 
37033
- class ChartWithAxisDesignPanel extends owl.Component {
37034
- static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37394
+ const CIRCLE_SVG = /*xml*/ `
37395
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
37396
+ <circle r="2" fill="#FFF"/>
37397
+ </svg>
37398
+ `;
37399
+ css /* scss */ `
37400
+ .o-radio {
37401
+ input {
37402
+ appearance: none;
37403
+ -webkit-appearance: none;
37404
+ -moz-appearance: none;
37405
+ width: 14px;
37406
+ height: 14px;
37407
+ border: 1px solid ${GRAY_300};
37408
+ box-sizing: border-box;
37409
+ outline: none;
37410
+ border-radius: 8px;
37411
+
37412
+ &:checked {
37413
+ background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
37414
+ background-color: ${ACTION_COLOR};
37415
+ border-color: ${ACTION_COLOR};
37416
+ }
37417
+ }
37418
+ }
37419
+ `;
37420
+ class RadioSelection extends owl.Component {
37421
+ static template = "o-spreadsheet.RadioSelection";
37422
+ static props = {
37423
+ choices: Array,
37424
+ onChange: Function,
37425
+ selectedValue: { optional: false },
37426
+ name: String,
37427
+ direction: { type: String, optional: true },
37428
+ };
37429
+ static defaultProps = {
37430
+ direction: "horizontal",
37431
+ };
37432
+ }
37433
+
37434
+ class SeriesDesignEditor extends owl.Component {
37435
+ static template = "o-spreadsheet-SeriesDesignEditor";
37035
37436
  static components = {
37036
- GeneralDesignEditor,
37037
37437
  SidePanelCollapsible,
37038
37438
  Section,
37039
- AxisDesignEditor,
37040
37439
  RoundColorPicker,
37041
- Checkbox,
37042
- RadioSelection,
37043
37440
  };
37044
37441
  static props = {
37045
37442
  figureId: String,
37046
37443
  definition: Object,
37047
- canUpdateChart: Function,
37048
37444
  updateChart: Function,
37445
+ canUpdateChart: Function,
37446
+ slots: { type: Object, optional: true },
37049
37447
  };
37050
- axisChoices = CHART_AXIS_CHOICES;
37051
37448
  state = owl.useState({ index: 0 });
37052
- get axesList() {
37053
- const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37054
- let axes = [{ id: "x", name: _t("Horizontal axis") }];
37055
- if (useLeftAxis) {
37056
- axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37057
- }
37058
- if (useRightAxis) {
37059
- axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37060
- }
37061
- return axes;
37062
- }
37063
- updateLegendPosition(ev) {
37064
- this.props.updateChart(this.props.figureId, {
37065
- legendPosition: ev.target.value,
37066
- });
37067
- }
37068
37449
  getDataSeries() {
37069
- return this.props.definition.dataSets.map((d, i) => d.label ?? `${ChartTerms.Series} ${i + 1}`);
37450
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
37451
+ if (!runtime || !("chartJsConfig" in runtime)) {
37452
+ return [];
37453
+ }
37454
+ return runtime.chartJsConfig.data.datasets.map((d) => d.label);
37070
37455
  }
37071
37456
  updateSerieEditor(ev) {
37072
- const chartId = this.props.figureId;
37073
- const selectedIndex = ev.target.selectedIndex;
37074
- const runtime = this.env.model.getters.getChartRuntime(chartId);
37075
- if (!runtime) {
37076
- return;
37077
- }
37078
- this.state.index = selectedIndex;
37457
+ this.state.index = ev.target.selectedIndex;
37079
37458
  }
37080
37459
  updateDataSeriesColor(color) {
37081
- const dataSets = [...this.props.definition.dataSets];
37082
- if (!dataSets?.[this.state.index]) {
37460
+ const dataSets = this.props.definition.dataSets;
37461
+ if (!dataSets?.[this.state.index])
37083
37462
  return;
37084
- }
37085
37463
  dataSets[this.state.index] = {
37086
37464
  ...dataSets[this.state.index],
37087
37465
  backgroundColor: color,
@@ -37090,71 +37468,87 @@ stores.inject(MyMetaStore, storeInstance);
37090
37468
  }
37091
37469
  getDataSerieColor() {
37092
37470
  const dataSets = this.props.definition.dataSets;
37093
- if (!dataSets?.[this.state.index]) {
37471
+ if (!dataSets?.[this.state.index])
37094
37472
  return "";
37095
- }
37096
37473
  const color = dataSets[this.state.index].backgroundColor;
37097
- return color ? toHex(color) : getNthColor(this.state.index, getColorsPalette(dataSets.length));
37474
+ return color
37475
+ ? toHex(color)
37476
+ : getNthColor(this.state.index, getColorsPalette(this.props.definition.dataSets.length));
37098
37477
  }
37099
- updateDataSeriesAxis(axis) {
37100
- const dataSets = [...this.props.definition.dataSets];
37101
- if (!dataSets?.[this.state.index]) {
37478
+ updateDataSeriesLabel(ev) {
37479
+ const label = ev.target.value;
37480
+ const dataSets = this.props.definition.dataSets;
37481
+ if (!dataSets?.[this.state.index])
37102
37482
  return;
37103
- }
37104
37483
  dataSets[this.state.index] = {
37105
37484
  ...dataSets[this.state.index],
37106
- yAxisId: axis === "left" ? "y" : "y1",
37485
+ label,
37107
37486
  };
37108
37487
  this.props.updateChart(this.props.figureId, { dataSets });
37109
37488
  }
37110
- getDataSerieAxis() {
37489
+ getDataSerieLabel() {
37111
37490
  const dataSets = this.props.definition.dataSets;
37112
- if (!dataSets?.[this.state.index]) {
37113
- return "left";
37114
- }
37115
- return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
37116
- }
37117
- get canHaveTwoVerticalAxis() {
37118
- return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
37491
+ return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37119
37492
  }
37120
- updateDataSeriesLabel(ev) {
37121
- const label = ev.target.value;
37493
+ }
37494
+
37495
+ class SeriesWithAxisDesignEditor extends owl.Component {
37496
+ static template = "o-spreadsheet-SeriesWithAxisDesignEditor";
37497
+ static components = {
37498
+ SeriesDesignEditor,
37499
+ Checkbox,
37500
+ RadioSelection,
37501
+ Section,
37502
+ RoundColorPicker,
37503
+ };
37504
+ static props = {
37505
+ figureId: String,
37506
+ definition: Object,
37507
+ canUpdateChart: Function,
37508
+ updateChart: Function,
37509
+ slots: { type: Object, optional: true },
37510
+ };
37511
+ axisChoices = CHART_AXIS_CHOICES;
37512
+ updateDataSeriesAxis(index, axis) {
37122
37513
  const dataSets = [...this.props.definition.dataSets];
37123
- if (!dataSets?.[this.state.index]) {
37514
+ if (!dataSets?.[index]) {
37124
37515
  return;
37125
37516
  }
37126
- dataSets[this.state.index] = {
37127
- ...dataSets[this.state.index],
37128
- label,
37517
+ dataSets[index] = {
37518
+ ...dataSets[index],
37519
+ yAxisId: axis === "left" ? "y" : "y1",
37129
37520
  };
37130
37521
  this.props.updateChart(this.props.figureId, { dataSets });
37131
37522
  }
37132
- getDataSerieLabel() {
37523
+ getDataSerieAxis(index) {
37133
37524
  const dataSets = this.props.definition.dataSets;
37134
- return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37525
+ if (!dataSets?.[index]) {
37526
+ return "left";
37527
+ }
37528
+ return dataSets[index].yAxisId === "y1" ? "right" : "left";
37135
37529
  }
37136
- updateShowValues(showValues) {
37137
- this.props.updateChart(this.props.figureId, { showValues });
37530
+ get canHaveTwoVerticalAxis() {
37531
+ return !("horizontal" in this.props.definition && this.props.definition.horizontal);
37138
37532
  }
37139
- toggleDataTrend(display) {
37533
+ toggleDataTrend(index, display) {
37140
37534
  const dataSets = [...this.props.definition.dataSets];
37141
- if (!dataSets?.[this.state.index]) {
37535
+ if (!dataSets?.[index]) {
37142
37536
  return;
37143
37537
  }
37144
- dataSets[this.state.index] = {
37145
- ...dataSets[this.state.index],
37538
+ dataSets[index] = {
37539
+ ...dataSets[index],
37146
37540
  trend: {
37147
37541
  type: "polynomial",
37148
37542
  order: 1,
37149
- ...dataSets[this.state.index].trend,
37543
+ ...dataSets[index].trend,
37150
37544
  display,
37151
37545
  },
37152
37546
  };
37153
37547
  this.props.updateChart(this.props.figureId, { dataSets });
37154
37548
  }
37155
- getTrendLineConfiguration() {
37549
+ getTrendLineConfiguration(index) {
37156
37550
  const dataSets = this.props.definition.dataSets;
37157
- return dataSets?.[this.state.index]?.trend;
37551
+ return dataSets?.[index]?.trend;
37158
37552
  }
37159
37553
  getTrendType(config) {
37160
37554
  if (!config) {
@@ -37162,7 +37556,7 @@ stores.inject(MyMetaStore, storeInstance);
37162
37556
  }
37163
37557
  return config.type === "polynomial" && config.order === 1 ? "linear" : config.type;
37164
37558
  }
37165
- onChangeTrendType(ev) {
37559
+ onChangeTrendType(index, ev) {
37166
37560
  const type = ev.target.value;
37167
37561
  let config;
37168
37562
  switch (type) {
@@ -37175,37 +37569,59 @@ stores.inject(MyMetaStore, storeInstance);
37175
37569
  break;
37176
37570
  case "exponential":
37177
37571
  case "logarithmic":
37572
+ case "trailingMovingAverage":
37178
37573
  config = { type };
37179
37574
  break;
37180
37575
  default:
37181
37576
  return;
37182
37577
  }
37183
- this.updateTrendLineValue(config);
37578
+ this.updateTrendLineValue(index, config);
37184
37579
  }
37185
- onChangePolynomialDegree(ev) {
37580
+ onChangePolynomialDegree(index, ev) {
37186
37581
  const element = ev.target;
37187
37582
  const order = parseInt(element.value || "1");
37188
37583
  if (order < 2) {
37189
- element.value = `${this.getTrendLineConfiguration()?.order ?? 2}`;
37584
+ element.value = `${this.getTrendLineConfiguration(index)?.order ?? 2}`;
37190
37585
  return;
37191
37586
  }
37192
- this.updateTrendLineValue({ order });
37587
+ this.updateTrendLineValue(index, { order });
37588
+ }
37589
+ get defaultWindowSize() {
37590
+ return DEFAULT_WINDOW_SIZE;
37591
+ }
37592
+ onChangeMovingAverageWindow(index, ev) {
37593
+ const element = ev.target;
37594
+ let window = parseInt(element.value) || DEFAULT_WINDOW_SIZE;
37595
+ if (window <= 1) {
37596
+ window = DEFAULT_WINDOW_SIZE;
37597
+ }
37598
+ this.updateTrendLineValue(index, { window });
37193
37599
  }
37194
- getTrendLineColor() {
37195
- return this.getTrendLineConfiguration()?.color ?? setColorAlpha(this.getDataSerieColor(), 0.5);
37600
+ getDataSerieColor(index) {
37601
+ const dataSets = this.props.definition.dataSets;
37602
+ if (!dataSets?.[index])
37603
+ return "";
37604
+ const color = dataSets[index].backgroundColor;
37605
+ return color
37606
+ ? toHex(color)
37607
+ : getNthColor(index, getColorsPalette(this.props.definition.dataSets.length));
37608
+ }
37609
+ getTrendLineColor(index) {
37610
+ return (this.getTrendLineConfiguration(index)?.color ??
37611
+ setColorAlpha(this.getDataSerieColor(index), 0.5));
37196
37612
  }
37197
- updateTrendLineColor(color) {
37198
- this.updateTrendLineValue({ color });
37613
+ updateTrendLineColor(index, color) {
37614
+ this.updateTrendLineValue(index, { color });
37199
37615
  }
37200
- updateTrendLineValue(config) {
37616
+ updateTrendLineValue(index, config) {
37201
37617
  const dataSets = [...this.props.definition.dataSets];
37202
- if (!dataSets?.[this.state.index]) {
37618
+ if (!dataSets?.[index]) {
37203
37619
  return;
37204
37620
  }
37205
- dataSets[this.state.index] = {
37206
- ...dataSets[this.state.index],
37621
+ dataSets[index] = {
37622
+ ...dataSets[index],
37207
37623
  trend: {
37208
- ...dataSets[this.state.index].trend,
37624
+ ...dataSets[index].trend,
37209
37625
  ...config,
37210
37626
  },
37211
37627
  };
@@ -37213,6 +37629,70 @@ stores.inject(MyMetaStore, storeInstance);
37213
37629
  }
37214
37630
  }
37215
37631
 
37632
+ class ChartWithAxisDesignPanel extends owl.Component {
37633
+ static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37634
+ static components = {
37635
+ GeneralDesignEditor,
37636
+ SidePanelCollapsible,
37637
+ Section,
37638
+ AxisDesignEditor,
37639
+ Checkbox,
37640
+ SeriesWithAxisDesignEditor,
37641
+ };
37642
+ static props = {
37643
+ figureId: String,
37644
+ definition: Object,
37645
+ canUpdateChart: Function,
37646
+ updateChart: Function,
37647
+ };
37648
+ get axesList() {
37649
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37650
+ let axes = [{ id: "x", name: _t("Horizontal axis") }];
37651
+ if (useLeftAxis) {
37652
+ axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37653
+ }
37654
+ if (useRightAxis) {
37655
+ axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37656
+ }
37657
+ return axes;
37658
+ }
37659
+ updateLegendPosition(ev) {
37660
+ this.props.updateChart(this.props.figureId, {
37661
+ legendPosition: ev.target.value,
37662
+ });
37663
+ }
37664
+ }
37665
+
37666
+ class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
37667
+ static template = "o-spreadsheet-ComboChartDesignPanel";
37668
+ static components = {
37669
+ ...ChartWithAxisDesignPanel.components,
37670
+ RadioSelection,
37671
+ };
37672
+ seriesTypeChoices = [
37673
+ { value: "bar", label: _t("Bar") },
37674
+ { value: "line", label: _t("Line") },
37675
+ ];
37676
+ updateDataSeriesType(index, type) {
37677
+ const dataSets = [...this.props.definition.dataSets];
37678
+ if (!dataSets?.[index]) {
37679
+ return;
37680
+ }
37681
+ dataSets[index] = {
37682
+ ...dataSets[index],
37683
+ type,
37684
+ };
37685
+ this.props.updateChart(this.props.figureId, { dataSets });
37686
+ }
37687
+ getDataSeriesType(index) {
37688
+ const dataSets = this.props.definition.dataSets;
37689
+ if (!dataSets?.[index]) {
37690
+ return "bar";
37691
+ }
37692
+ return dataSets[index].type ?? "line";
37693
+ }
37694
+ }
37695
+
37216
37696
  class GaugeChartConfigPanel extends owl.Component {
37217
37697
  static template = "o-spreadsheet-GaugeChartConfigPanel";
37218
37698
  static components = { ChartErrorSection, ChartDataSeries };
@@ -37392,11 +37872,6 @@ stores.inject(MyMetaStore, storeInstance);
37392
37872
  stacked,
37393
37873
  });
37394
37874
  }
37395
- onUpdateAggregated(aggregated) {
37396
- this.props.updateChart(this.props.figureId, {
37397
- aggregated,
37398
- });
37399
- }
37400
37875
  onUpdateCumulative(cumulative) {
37401
37876
  this.props.updateChart(this.props.figureId, {
37402
37877
  cumulative,
@@ -37424,6 +37899,27 @@ stores.inject(MyMetaStore, storeInstance);
37424
37899
  }
37425
37900
  }
37426
37901
 
37902
+ class RadarChartDesignPanel extends owl.Component {
37903
+ static template = "o-spreadsheet-RadarChartDesignPanel";
37904
+ static components = {
37905
+ GeneralDesignEditor,
37906
+ SeriesDesignEditor,
37907
+ Section,
37908
+ Checkbox,
37909
+ };
37910
+ static props = {
37911
+ figureId: String,
37912
+ definition: Object,
37913
+ canUpdateChart: Function,
37914
+ updateChart: Function,
37915
+ };
37916
+ updateLegendPosition(ev) {
37917
+ this.props.updateChart(this.props.figureId, {
37918
+ legendPosition: ev.target.value,
37919
+ });
37920
+ }
37921
+ }
37922
+
37427
37923
  class ScatterConfigPanel extends GenericChartConfigPanel {
37428
37924
  static template = "o-spreadsheet-ScatterConfigPanel";
37429
37925
  get canTreatLabelsAsText() {
@@ -37618,9 +38114,6 @@ stores.inject(MyMetaStore, storeInstance);
37618
38114
  verticalAxisPosition: value,
37619
38115
  });
37620
38116
  }
37621
- updateShowValues(showValues) {
37622
- this.props.updateChart(this.props.figureId, { showValues });
37623
- }
37624
38117
  }
37625
38118
 
37626
38119
  const chartSidePanelComponentRegistry = new Registry();
@@ -37639,7 +38132,7 @@ stores.inject(MyMetaStore, storeInstance);
37639
38132
  })
37640
38133
  .add("combo", {
37641
38134
  configuration: GenericChartConfigPanel,
37642
- design: ChartWithAxisDesignPanel,
38135
+ design: ComboChartDesignPanel,
37643
38136
  })
37644
38137
  .add("pie", {
37645
38138
  configuration: GenericChartConfigPanel,
@@ -37660,6 +38153,10 @@ stores.inject(MyMetaStore, storeInstance);
37660
38153
  .add("pyramid", {
37661
38154
  configuration: GenericChartConfigPanel,
37662
38155
  design: ChartWithAxisDesignPanel,
38156
+ })
38157
+ .add("radar", {
38158
+ configuration: GenericChartConfigPanel,
38159
+ design: RadarChartDesignPanel,
37663
38160
  });
37664
38161
 
37665
38162
  css /* scss */ `
@@ -37925,6 +38422,7 @@ stores.inject(MyMetaStore, storeInstance);
37925
38422
  "stopComposerRangeSelection",
37926
38423
  "cancelEdition",
37927
38424
  "cycleReferences",
38425
+ "toggleEditionMode",
37928
38426
  "changeComposerCursorSelection",
37929
38427
  "replaceComposerCursorSelection",
37930
38428
  ];
@@ -38080,6 +38578,42 @@ stores.inject(MyMetaStore, storeInstance);
38080
38578
  }
38081
38579
  this.setCurrentContent(updated.content, updated.selection);
38082
38580
  }
38581
+ toggleEditionMode() {
38582
+ if (this.editionMode === "inactive")
38583
+ return;
38584
+ const start = Math.min(this.selectionStart, this.selectionEnd);
38585
+ const end = Math.max(this.selectionStart, this.selectionEnd);
38586
+ const refToken = [...this.currentTokens]
38587
+ .reverse()
38588
+ .find((tk) => tk.end >= start && end >= tk.start && tk.type === "REFERENCE");
38589
+ if (this.editionMode === "editing" && refToken) {
38590
+ const currentSheetId = this.getters.getActiveSheetId();
38591
+ const { sheetName, xc } = splitReference(refToken.value);
38592
+ const sheetId = this.getters.getSheetIdByName(sheetName);
38593
+ if (sheetId && sheetId !== currentSheetId) {
38594
+ this.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: currentSheetId, sheetIdTo: sheetId });
38595
+ }
38596
+ // move cursor to the right part of the token
38597
+ this.selectionStart = this.selectionEnd = refToken.end;
38598
+ const zone = this.getters.getRangeFromSheetXC(this.sheetId, xc).zone;
38599
+ this.captureSelection(zone);
38600
+ this.editionMode = "selecting";
38601
+ }
38602
+ else {
38603
+ this.editionMode = "editing";
38604
+ }
38605
+ }
38606
+ captureSelection(zone, col, row) {
38607
+ this.model.selection.capture(this, {
38608
+ cell: { col: col || zone.left, row: row || zone.right },
38609
+ zone,
38610
+ }, {
38611
+ handleEvent: this.handleEvent.bind(this),
38612
+ release: () => {
38613
+ this._stopEdition();
38614
+ },
38615
+ });
38616
+ }
38083
38617
  isSelectionValid(length, start, end) {
38084
38618
  return start >= 0 && start <= length && end >= 0 && end <= length;
38085
38619
  }
@@ -38118,12 +38652,7 @@ stores.inject(MyMetaStore, storeInstance);
38118
38652
  this.setContent(str || this.initialContent, selection);
38119
38653
  this.colorIndexByRange = {};
38120
38654
  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
- });
38655
+ this.captureSelection(zone, col, row);
38127
38656
  }
38128
38657
  _stopEdition() {
38129
38658
  if (this.editionMode !== "inactive") {
@@ -38830,13 +39359,6 @@ stores.inject(MyMetaStore, storeInstance);
38830
39359
  return;
38831
39360
  this.edgeScrollIntervalId = window.setInterval(() => {
38832
39361
  const offset = direction * 3;
38833
- let newPosition = this.currentMousePosition + offset;
38834
- if (newPosition < Math.min(this.container.start, this.minPosition)) {
38835
- newPosition = Math.min(this.container.start, this.minPosition);
38836
- }
38837
- else if (newPosition > Math.max(this.container.end, this.maxPosition)) {
38838
- newPosition = Math.max(this.container.end, this.maxPosition);
38839
- }
38840
39362
  this.container.scroll += offset;
38841
39363
  }, 5);
38842
39364
  }
@@ -39013,7 +39535,6 @@ stores.inject(MyMetaStore, storeInstance);
39013
39535
  width: 142px;
39014
39536
  .o-cf-preview-description-rule {
39015
39537
  margin-bottom: 4px;
39016
- font-weight: 600;
39017
39538
  max-height: 2.8em;
39018
39539
  line-height: 1.4em;
39019
39540
  }
@@ -39485,7 +40006,7 @@ stores.inject(MyMetaStore, storeInstance);
39485
40006
  setColorScaleColor(target, color) {
39486
40007
  const point = this.state.rules.colorScale[target];
39487
40008
  if (point) {
39488
- point.color = Number.parseInt(color.substr(1), 16);
40009
+ point.color = Number.parseInt(color.slice(1), 16);
39489
40010
  }
39490
40011
  this.closeMenus();
39491
40012
  }
@@ -39596,7 +40117,7 @@ stores.inject(MyMetaStore, storeInstance);
39596
40117
  return [this.state.rules.dataBar.rangeValues || ""];
39597
40118
  }
39598
40119
  updateDataBarColor(color) {
39599
- this.state.rules.dataBar.color = Number.parseInt(color.substr(1), 16);
40120
+ this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
39600
40121
  }
39601
40122
  onDataBarRangeUpdate(ranges) {
39602
40123
  this.state.rules.dataBar.rangeValues = ranges[0];
@@ -41009,10 +41530,11 @@ stores.inject(MyMetaStore, storeInstance);
41009
41530
  activeSheetMatches = [];
41010
41531
  specificRangeMatches = [];
41011
41532
  currentSearchRegex = null;
41012
- isSearchDirty = false;
41013
41533
  initialShowFormulaState;
41014
41534
  preserveSelectedMatchIndex = false;
41015
41535
  irreplaceableMatchCount = 0;
41536
+ isSearchDirty = false;
41537
+ shouldFinalizeUpdateSelection = false;
41016
41538
  notificationStore = this.get(NotificationStore);
41017
41539
  // fixme: why do we make selectedMatchIndex on top of a selected
41018
41540
  // property in the matches?
@@ -41058,10 +41580,13 @@ stores.inject(MyMetaStore, storeInstance);
41058
41580
  this.updateSearchOptions({ searchFormulas: showFormula });
41059
41581
  }
41060
41582
  selectPreviousMatch() {
41061
- this.selectNextCell(Direction.previous);
41583
+ this.selectNextCell(Direction.previous, {
41584
+ jumpToMatchSheet: true,
41585
+ updateSelection: true,
41586
+ });
41062
41587
  }
41063
41588
  selectNextMatch() {
41064
- this.selectNextCell(Direction.next);
41589
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41065
41590
  }
41066
41591
  handle(cmd) {
41067
41592
  switch (cmd.type) {
@@ -41078,8 +41603,11 @@ stores.inject(MyMetaStore, storeInstance);
41078
41603
  case "ADD_COLUMNS_ROWS":
41079
41604
  case "EVALUATE_CELLS":
41080
41605
  case "UPDATE_CELL":
41606
+ this.isSearchDirty = true;
41607
+ break;
41081
41608
  case "ACTIVATE_SHEET":
41082
41609
  this.isSearchDirty = true;
41610
+ this.shouldFinalizeUpdateSelection = true;
41083
41611
  break;
41084
41612
  case "REPLACE_SEARCH":
41085
41613
  for (const match of cmd.matches) {
@@ -41094,7 +41622,11 @@ stores.inject(MyMetaStore, storeInstance);
41094
41622
  }
41095
41623
  finalize() {
41096
41624
  if (this.isSearchDirty) {
41097
- this.refreshSearch(false);
41625
+ this.refreshSearch({
41626
+ jumpToMatchSheet: false,
41627
+ updateSelection: this.shouldFinalizeUpdateSelection,
41628
+ });
41629
+ this.shouldFinalizeUpdateSelection = false;
41098
41630
  this.isSearchDirty = false;
41099
41631
  }
41100
41632
  }
@@ -41121,17 +41653,17 @@ stores.inject(MyMetaStore, storeInstance);
41121
41653
  }
41122
41654
  this.toSearch = toSearch;
41123
41655
  this.currentSearchRegex = getSearchRegex(this.toSearch, this.searchOptions);
41124
- this.refreshSearch();
41656
+ this.refreshSearch({ jumpToMatchSheet: true, updateSelection: true });
41125
41657
  }
41126
41658
  /**
41127
41659
  * refresh the matches according to the current search options
41128
41660
  */
41129
- refreshSearch(jumpToMatchSheet = true) {
41661
+ refreshSearch(options) {
41130
41662
  if (!this.preserveSelectedMatchIndex) {
41131
41663
  this.selectedMatchIndex = null;
41132
41664
  }
41133
41665
  this.findMatches();
41134
- this.selectNextCell(Direction.current, jumpToMatchSheet);
41666
+ this.selectNextCell(Direction.current, options);
41135
41667
  }
41136
41668
  getSheetsInSearchOrder() {
41137
41669
  switch (this.searchOptions.searchScope) {
@@ -41201,7 +41733,7 @@ stores.inject(MyMetaStore, storeInstance);
41201
41733
  * It is also used to keep coherence between the selected searchMatch
41202
41734
  * and selectedMatchIndex.
41203
41735
  */
41204
- selectNextCell(indexChange, jumpToMatchSheet = true) {
41736
+ selectNextCell(indexChange, options) {
41205
41737
  const matches = this.searchMatches;
41206
41738
  if (!matches.length) {
41207
41739
  this.selectedMatchIndex = null;
@@ -41227,7 +41759,7 @@ stores.inject(MyMetaStore, storeInstance);
41227
41759
  this.selectedMatchIndex = nextIndex;
41228
41760
  const selectedMatch = matches[nextIndex];
41229
41761
  // Switch to the sheet where the match is located
41230
- if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41762
+ if (options.jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41231
41763
  // We set `preserveSelectedMatchIndex` to true to avoid resetting the selected search
41232
41764
  // index in the `refreshSearch` function when a new sheet is activated. The reason being
41233
41765
  // that, when we automatically go back to previous sheet while performing a search, the
@@ -41243,7 +41775,9 @@ stores.inject(MyMetaStore, storeInstance);
41243
41775
  }
41244
41776
  // we want grid selection to capture the selection stream
41245
41777
  this.model.selection.getBackToDefault();
41246
- this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41778
+ if (options.updateSelection) {
41779
+ this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41780
+ }
41247
41781
  }
41248
41782
  /**
41249
41783
  * Replace the value of the currently selected match
@@ -41258,7 +41792,7 @@ stores.inject(MyMetaStore, storeInstance);
41258
41792
  matches: [this.searchMatches[this.selectedMatchIndex]],
41259
41793
  searchOptions: this.searchOptions,
41260
41794
  });
41261
- this.selectNextCell(Direction.next);
41795
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41262
41796
  }
41263
41797
  /**
41264
41798
  * Apply the replace function to all the matches one time.
@@ -41440,6 +41974,14 @@ stores.inject(MyMetaStore, storeInstance);
41440
41974
  owl.onMounted(() => this.searchInput.el?.focus());
41441
41975
  owl.onWillUnmount(() => this.updateSearchContent.stopDebounce());
41442
41976
  this.updateSearchContent = debounce(this.store.updateSearchContent, 200);
41977
+ owl.useExternalListener(window, "keydown", (ev) => {
41978
+ const code = keyboardEventToShortcutString(ev);
41979
+ if (code === "Ctrl+F" || code === "Ctrl+H") {
41980
+ this.searchInput.el?.focus();
41981
+ ev.preventDefault();
41982
+ ev.stopPropagation();
41983
+ }
41984
+ }, { capture: true });
41443
41985
  }
41444
41986
  onFocusSearch() {
41445
41987
  this.updateDataRange();
@@ -42015,7 +42557,6 @@ stores.inject(MyMetaStore, storeInstance);
42015
42557
  sequence: 0,
42016
42558
  autoSelectFirstProposal: true,
42017
42559
  getProposals(tokenAtCursor) {
42018
- // return []
42019
42560
  const measureProposals = pivot.measures
42020
42561
  .filter((m) => m !== forComputedMeasure)
42021
42562
  .map((measure) => {
@@ -42818,8 +43359,9 @@ stores.inject(MyMetaStore, storeInstance);
42818
43359
  * This function converts a list of data entry into a spreadsheet pivot table.
42819
43360
  */
42820
43361
  function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
43362
+ const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
42821
43363
  const columnsTree = dataEntriesToColumnsTree(dataEntries, definition.columns, 0);
42822
- computeWidthOfColumnsNodes(columnsTree, definition.measures.length);
43364
+ computeWidthOfColumnsNodes(columnsTree, measureIds.length);
42823
43365
  const cols = columnsTreeToColumns(columnsTree, definition);
42824
43366
  const rows = dataEntriesToRows(dataEntries, 0, definition.rows, [], []);
42825
43367
  // Add the total row
@@ -42828,7 +43370,6 @@ stores.inject(MyMetaStore, storeInstance);
42828
43370
  values: [],
42829
43371
  indent: 0,
42830
43372
  });
42831
- const measureIds = definition.measures.filter((measure) => !measure.isHidden).map((m) => m.id);
42832
43373
  const fieldsType = {};
42833
43374
  for (const columns of definition.columns) {
42834
43375
  fieldsType[columns.fieldName] = columns.type;
@@ -43589,20 +44130,22 @@ stores.inject(MyMetaStore, storeInstance);
43589
44130
  onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
43590
44131
  dateGranularities: [...dateGranularities],
43591
44132
  datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
43592
- isMeasureCandidate: (field) => !["date", "boolean"].includes(field.type),
44133
+ isMeasureCandidate: (field) => !["datetime", "boolean"].includes(field.type),
43593
44134
  isGroupable: () => true,
43594
44135
  });
43595
44136
 
43596
44137
  class PivotSidePanelStore extends SpreadsheetStore {
43597
44138
  pivotId;
43598
44139
  mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
43599
- updatesAreDeferred = false;
44140
+ updatesAreDeferred;
43600
44141
  draft = null;
43601
44142
  notification = this.get(NotificationStore);
43602
44143
  alreadyNotified = false;
43603
44144
  constructor(get, pivotId) {
43604
44145
  super(get);
43605
44146
  this.pivotId = pivotId;
44147
+ this.updatesAreDeferred =
44148
+ this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
43606
44149
  }
43607
44150
  handle(cmd) {
43608
44151
  switch (cmd.type) {
@@ -43690,10 +44233,14 @@ stores.inject(MyMetaStore, storeInstance);
43690
44233
  this.draft = null;
43691
44234
  }
43692
44235
  deferUpdates(shouldDefer) {
43693
- this.updatesAreDeferred = shouldDefer;
43694
44236
  if (shouldDefer === false && this.draft) {
44237
+ this.draft.deferUpdates = false;
43695
44238
  this.applyUpdate();
43696
44239
  }
44240
+ else {
44241
+ this.update({ deferUpdates: shouldDefer });
44242
+ }
44243
+ this.updatesAreDeferred = shouldDefer;
43697
44244
  }
43698
44245
  applyUpdate() {
43699
44246
  if (this.draft) {
@@ -43951,7 +44498,7 @@ stores.inject(MyMetaStore, storeInstance);
43951
44498
  return colLabel;
43952
44499
  }
43953
44500
  get isEveryColumnSelected() {
43954
- return Object.values(this.state.columns).every((value) => value === true);
44501
+ return Object.values(this.state.columns).every((value) => value);
43955
44502
  }
43956
44503
  get errorMessages() {
43957
44504
  const cancelledReasons = this.env.model.canDispatch("REMOVE_DUPLICATES", {
@@ -44051,8 +44598,7 @@ stores.inject(MyMetaStore, storeInstance);
44051
44598
  const currentLocale = this.currentLocale;
44052
44599
  const localeInLoadedLocales = this.loadedLocales.find((l) => l.code === currentLocale.code);
44053
44600
  if (!localeInLoadedLocales) {
44054
- const locales = [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44055
- return locales;
44601
+ return [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44056
44602
  }
44057
44603
  else if (!deepEquals(currentLocale, localeInLoadedLocales)) {
44058
44604
  const index = this.loadedLocales.indexOf(localeInLoadedLocales);
@@ -45946,10 +46492,9 @@ stores.inject(MyMetaStore, storeInstance);
45946
46492
  });
45947
46493
  }
45948
46494
  get focus() {
45949
- const focus = this.composerFocusStore.activeComposer === this.composerInterface
46495
+ return this.composerFocusStore.activeComposer === this.composerInterface
45950
46496
  ? this.composerFocusStore.focusMode
45951
46497
  : "inactive";
45952
- return focus;
45953
46498
  }
45954
46499
  get composerProps() {
45955
46500
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
@@ -46118,13 +46663,10 @@ stores.inject(MyMetaStore, storeInstance);
46118
46663
  }
46119
46664
  }
46120
46665
 
46121
- const CHECKBOX_WIDTH = 15;
46122
46666
  const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
46123
46667
  css /* scss */ `
46124
46668
  .o-dv-checkbox {
46125
46669
  box-sizing: border-box !important;
46126
- width: ${CHECKBOX_WIDTH}px;
46127
- height: ${CHECKBOX_WIDTH}px;
46128
46670
  accent-color: #808080;
46129
46671
  margin: ${MARGIN}px;
46130
46672
  /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
@@ -46133,13 +46675,15 @@ stores.inject(MyMetaStore, storeInstance);
46133
46675
  `;
46134
46676
  class DataValidationCheckbox extends owl.Component {
46135
46677
  static template = "o-spreadsheet-DataValidationCheckbox";
46678
+ static components = {
46679
+ Checkbox,
46680
+ };
46136
46681
  static props = {
46137
46682
  cellPosition: Object,
46138
46683
  };
46139
- onCheckboxChange(ev) {
46140
- const newValue = ev.target.checked;
46684
+ onCheckboxChange(value) {
46141
46685
  const { sheetId, col, row } = this.props.cellPosition;
46142
- const cellContent = newValue ? "TRUE" : "FALSE";
46686
+ const cellContent = value ? "TRUE" : "FALSE";
46143
46687
  this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
46144
46688
  }
46145
46689
  get checkBoxValue() {
@@ -46953,7 +47497,12 @@ stores.inject(MyMetaStore, storeInstance);
46953
47497
  class PaintFormatStore extends SpreadsheetStore {
46954
47498
  mutators = ["activate", "cancel", "pasteFormat"];
46955
47499
  highlightStore = this.get(HighlightStore);
46956
- cellClipboardHandler = new CellClipboardHandler(this.getters, this.model.dispatch);
47500
+ clipboardHandlers = [
47501
+ new CellClipboardHandler(this.getters, this.model.dispatch),
47502
+ new BorderClipboardHandler(this.getters, this.model.dispatch),
47503
+ new TableClipboardHandler(this.getters, this.model.dispatch),
47504
+ new ConditionalFormatClipboardHandler(this.getters, this.model.dispatch),
47505
+ ];
46957
47506
  status = "inactive";
46958
47507
  copiedData;
46959
47508
  constructor(get) {
@@ -46963,6 +47512,13 @@ stores.inject(MyMetaStore, storeInstance);
46963
47512
  this.highlightStore.unRegister(this);
46964
47513
  });
46965
47514
  }
47515
+ handle(cmd) {
47516
+ switch (cmd.type) {
47517
+ case "PAINT_FORMAT":
47518
+ this.paintFormat(cmd.sheetId, cmd.target);
47519
+ break;
47520
+ }
47521
+ }
46966
47522
  activate(args) {
46967
47523
  this.copiedData = this.copyFormats();
46968
47524
  this.status = args.persistent ? "persistent" : "oneOff";
@@ -46972,16 +47528,7 @@ stores.inject(MyMetaStore, storeInstance);
46972
47528
  this.copiedData = undefined;
46973
47529
  }
46974
47530
  pasteFormat(target) {
46975
- if (this.copiedData) {
46976
- const sheetId = this.getters.getActiveSheetId();
46977
- this.cellClipboardHandler.paste({ zones: target, sheetId }, this.copiedData, {
46978
- isCutOperation: false,
46979
- pasteOption: "onlyFormat",
46980
- });
46981
- }
46982
- if (this.status === "oneOff") {
46983
- this.cancel();
46984
- }
47531
+ this.model.dispatch("PAINT_FORMAT", { target, sheetId: this.getters.getActiveSheetId() });
46985
47532
  }
46986
47533
  get isActive() {
46987
47534
  return this.status !== "inactive";
@@ -46989,7 +47536,24 @@ stores.inject(MyMetaStore, storeInstance);
46989
47536
  copyFormats() {
46990
47537
  const sheetId = this.getters.getActiveSheetId();
46991
47538
  const zones = this.getters.getSelectedZones();
46992
- return this.cellClipboardHandler.copy(getClipboardDataPositions(sheetId, zones));
47539
+ const copiedData = {};
47540
+ for (const handler of this.clipboardHandlers) {
47541
+ Object.assign(copiedData, handler.copy(getClipboardDataPositions(sheetId, zones)));
47542
+ }
47543
+ return copiedData;
47544
+ }
47545
+ paintFormat(sheetId, target) {
47546
+ if (this.copiedData) {
47547
+ for (const handler of this.clipboardHandlers) {
47548
+ handler.paste({ zones: target, sheetId }, this.copiedData, {
47549
+ isCutOperation: false,
47550
+ pasteOption: "onlyFormat",
47551
+ });
47552
+ }
47553
+ }
47554
+ if (this.status === "oneOff") {
47555
+ this.cancel();
47556
+ }
46993
47557
  }
46994
47558
  get highlights() {
46995
47559
  const data = this.copiedData;
@@ -47367,7 +47931,7 @@ stores.inject(MyMetaStore, storeInstance);
47367
47931
  if (index < 0) {
47368
47932
  return;
47369
47933
  }
47370
- if (this.state.waitingForMove === true) {
47934
+ if (this.state.waitingForMove) {
47371
47935
  if (!this.env.model.getters.isGridSelectionActive()) {
47372
47936
  this._selectElement(index, false);
47373
47937
  }
@@ -48926,7 +49490,7 @@ stores.inject(MyMetaStore, storeInstance);
48926
49490
  }
48927
49491
  open(componentTag, panelProps = {}) {
48928
49492
  const state = this.computeState(componentTag, panelProps);
48929
- if (state.isOpen === false) {
49493
+ if (!state.isOpen) {
48930
49494
  return;
48931
49495
  }
48932
49496
  if (this.isOpen && componentTag !== this.componentTag) {
@@ -49265,6 +49829,8 @@ stores.inject(MyMetaStore, storeInstance);
49265
49829
  },
49266
49830
  "Ctrl+D": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ABOVE"),
49267
49831
  "Ctrl+R": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ON_LEFT"),
49832
+ "Ctrl+H": () => this.sidePanel.open("FindAndReplace", {}),
49833
+ "Ctrl+F": () => this.sidePanel.open("FindAndReplace", {}),
49268
49834
  "Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
49269
49835
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
49270
49836
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
@@ -49672,31 +50238,6 @@ stores.inject(MyMetaStore, storeInstance);
49672
50238
  }
49673
50239
  }
49674
50240
 
49675
- /** @odoo-module */
49676
- class EditableName extends owl.Component {
49677
- static template = "o-spreadsheet-EditableName";
49678
- static props = {
49679
- name: String,
49680
- displayName: String,
49681
- onChanged: Function,
49682
- };
49683
- state;
49684
- setup() {
49685
- this.state = owl.useState({
49686
- isEditing: false,
49687
- name: "",
49688
- });
49689
- }
49690
- rename() {
49691
- this.state.isEditing = true;
49692
- this.state.name = this.props.name;
49693
- }
49694
- save() {
49695
- this.props.onChanged(this.state.name.trim());
49696
- this.state.isEditing = false;
49697
- }
49698
- }
49699
-
49700
50241
  /**
49701
50242
  * BasePlugin
49702
50243
  *
@@ -49797,12 +50338,10 @@ stores.inject(MyMetaStore, storeInstance);
49797
50338
  */
49798
50339
  class CorePlugin extends BasePlugin {
49799
50340
  getters;
49800
- uuidGenerator;
49801
- constructor({ getters, stateObserver, range, dispatch, canDispatch, uuidGenerator, }) {
50341
+ constructor({ getters, stateObserver, range, dispatch, canDispatch }) {
49802
50342
  super(stateObserver, dispatch, canDispatch);
49803
50343
  range.addRangeProvider(this.adaptRanges.bind(this));
49804
50344
  this.getters = getters;
49805
- this.uuidGenerator = uuidGenerator;
49806
50345
  }
49807
50346
  // ---------------------------------------------------------------------------
49808
50347
  // Import/Export
@@ -53616,7 +54155,7 @@ stores.inject(MyMetaStore, storeInstance);
53616
54155
  this.sheetIdsMapName[sheet.name] = sheet.id;
53617
54156
  }
53618
54157
  for (let sheetData of data.sheets) {
53619
- const name = sheetData.name || _t("Sheet") + (Object.keys(this.sheets).length + 1);
54158
+ const name = sheetData.name || "Sheet" + (Object.keys(this.sheets).length + 1);
53620
54159
  const { colNumber, rowNumber } = this.getImportedSheetSize(sheetData);
53621
54160
  const sheet = {
53622
54161
  id: sheetData.id,
@@ -54300,6 +54839,7 @@ stores.inject(MyMetaStore, storeInstance);
54300
54839
  }
54301
54840
  }
54302
54841
 
54842
+ let nextTableId = 1;
54303
54843
  class TablePlugin extends CorePlugin {
54304
54844
  static getters = ["getCoreTable", "getCoreTables", "getCoreTableMatchingTopLeft"];
54305
54845
  tables = {};
@@ -54367,7 +54907,7 @@ stores.inject(MyMetaStore, storeInstance);
54367
54907
  const union = this.getters.getRangesUnion(ranges);
54368
54908
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
54369
54909
  this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
54370
- const id = this.uuidGenerator.uuidv4();
54910
+ const id = `${nextTableId++}`;
54371
54911
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
54372
54912
  const newTable = cmd.tableType === "dynamic"
54373
54913
  ? this.createDynamicTable(id, union, config)
@@ -54520,7 +55060,7 @@ stores.inject(MyMetaStore, storeInstance);
54520
55060
  filters = [];
54521
55061
  for (const i of range(zone.left, zone.right + 1)) {
54522
55062
  const filterZone = { ...zone, left: i, right: i };
54523
- const uid = this.uuidGenerator.uuidv4();
55063
+ const uid = `${nextTableId++}`;
54524
55064
  filters.push(this.createFilterFromZone(uid, tableRange.sheetId, filterZone, config));
54525
55065
  }
54526
55066
  }
@@ -54585,7 +55125,7 @@ stores.inject(MyMetaStore, storeInstance);
54585
55125
  ? table.filters.find((f) => f.col === i)
54586
55126
  : undefined;
54587
55127
  const filterZone = { ...tableZone, left: i, right: i };
54588
- const filterId = oldFilter?.id || this.uuidGenerator.uuidv4();
55128
+ const filterId = oldFilter?.id || `${nextTableId++}`;
54589
55129
  filters.push(this.createFilterFromZone(filterId, tableRange.sheetId, filterZone, config));
54590
55130
  }
54591
55131
  }
@@ -54686,7 +55226,7 @@ stores.inject(MyMetaStore, storeInstance);
54686
55226
  if (filters.length < zoneToDimension(tableZone).numberOfCols) {
54687
55227
  for (let col = tableZone.left; col <= tableZone.right; col++) {
54688
55228
  if (!filters.find((filter) => filter.col === col)) {
54689
- const uid = this.uuidGenerator.uuidv4();
55229
+ const uid = `${nextTableId++}`;
54690
55230
  const filterZone = { ...tableZone, left: col, right: col };
54691
55231
  filters.push(this.createFilterFromZone(uid, sheetId, filterZone, table.config));
54692
55232
  }
@@ -54702,7 +55242,7 @@ stores.inject(MyMetaStore, storeInstance);
54702
55242
  import(data) {
54703
55243
  for (const sheet of data.sheets) {
54704
55244
  for (const tableData of sheet.tables || []) {
54705
- const uuid = this.uuidGenerator.uuidv4();
55245
+ const uuid = `${nextTableId++}`;
54706
55246
  const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
54707
55247
  const range = this.getters.getRangeFromSheetXC(sheet.id, tableData.range);
54708
55248
  const tableType = tableData.type || "static";
@@ -55234,7 +55774,7 @@ stores.inject(MyMetaStore, storeInstance);
55234
55774
  case "DUPLICATE_PIVOT": {
55235
55775
  const { pivotId, newPivotId } = cmd;
55236
55776
  const pivot = deepCopy(this.getPivotCore(pivotId).definition);
55237
- pivot.name = _t("%s (copy)", pivot.name);
55777
+ pivot.name = cmd.duplicatedPivotName ?? pivot.name + " (copy)";
55238
55778
  this.addPivot(newPivotId, pivot);
55239
55779
  break;
55240
55780
  }
@@ -55274,7 +55814,7 @@ stores.inject(MyMetaStore, storeInstance);
55274
55814
  return `(#${formulaId}) ${this.getPivotName(pivotId)}`;
55275
55815
  }
55276
55816
  getPivotName(pivotId) {
55277
- return _t(this.getPivotCore(pivotId).definition.name);
55817
+ return this.getPivotCore(pivotId).definition.name;
55278
55818
  }
55279
55819
  /**
55280
55820
  * Returns the pivot core definition of the pivot with the given id.
@@ -56916,7 +57456,7 @@ stores.inject(MyMetaStore, storeInstance);
56916
57456
  cellsToCompute.addMany(arrayFormulasPositions);
56917
57457
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
56918
57458
  this.evaluate(cellsToCompute);
56919
- console.info("evaluate Cells", performance.now() - start, "ms");
57459
+ console.debug("evaluate Cells", performance.now() - start, "ms");
56920
57460
  }
56921
57461
  getArrayFormulasImpactedByChangesOf(positions) {
56922
57462
  const impactedPositions = this.createEmptyPositionSet();
@@ -56960,7 +57500,7 @@ stores.inject(MyMetaStore, storeInstance);
56960
57500
  const start = performance.now();
56961
57501
  this.evaluatedCells = new PositionMap();
56962
57502
  this.evaluate(this.getAllCells());
56963
- console.info("evaluate all cells", performance.now() - start, "ms");
57503
+ console.debug("evaluate all cells", performance.now() - start, "ms");
56964
57504
  }
56965
57505
  evaluateFormulaResult(sheetId, formulaString) {
56966
57506
  const compiledFormula = compile(formulaString);
@@ -57966,8 +58506,7 @@ stores.inject(MyMetaStore, storeInstance);
57966
58506
  .map((cell) => cell.value);
57967
58507
  switch (threshold.type) {
57968
58508
  case "value":
57969
- const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
57970
- return result;
58509
+ return functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
57971
58510
  case "number":
57972
58511
  return Number(threshold.value);
57973
58512
  case "percentage":
@@ -59051,7 +59590,7 @@ stores.inject(MyMetaStore, storeInstance);
59051
59590
  const ranking = {};
59052
59591
  const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
59053
59592
  const secondaryDimension = mainDimension === "row" ? "column" : "row";
59054
- let pivotCells = this.getPivotValueCells();
59593
+ let pivotCells = this.getPivotValueCells(measure.id);
59055
59594
  if (mainDimension === "column") {
59056
59595
  // Transpose the pivot cells so we can do the same operations on the columns as on the rows
59057
59596
  // This means that we need to transpose back the ranking at the end
@@ -59095,7 +59634,7 @@ stores.inject(MyMetaStore, storeInstance);
59095
59634
  const cellsRunningTotals = {};
59096
59635
  const mainDimension = getFieldDimensionType(this, fieldNameWithGranularity);
59097
59636
  const secondaryDimension = mainDimension === "row" ? "column" : "row";
59098
- let pivotCells = this.getPivotValueCells();
59637
+ let pivotCells = this.getPivotValueCells(measure.id);
59099
59638
  if (mainDimension === "column") {
59100
59639
  // Transpose the pivot cells so we can do the same operations on the columns as on the rows
59101
59640
  // This means that we need to transpose back the totals at the end
@@ -59171,13 +59710,12 @@ stores.inject(MyMetaStore, storeInstance);
59171
59710
  throw new NotAvailableError();
59172
59711
  }
59173
59712
  const comparedValue = this._getPivotCellValueAndFormat(measure.id, comparedDomain);
59174
- const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
59175
- return comparedValueNumber;
59713
+ return this.strictMeasureValueToNumber(comparedValue);
59176
59714
  }
59177
- getPivotValueCells() {
59715
+ getPivotValueCells(measureId) {
59178
59716
  return this.getTableStructure()
59179
59717
  .getPivotCells()
59180
- .map((col) => col.filter((cell) => cell.type === "VALUE"))
59718
+ .map((col) => col.filter((cell) => cell.type === "VALUE" && cell.measure === measureId))
59181
59719
  .filter((col) => col.length > 0);
59182
59720
  }
59183
59721
  measureValueToNumber(result) {
@@ -60706,7 +61244,7 @@ stores.inject(MyMetaStore, storeInstance);
60706
61244
  this.onMessageReceived(message);
60707
61245
  }
60708
61246
  this.isReplayingInitialRevisions = false;
60709
- console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
61247
+ console.debug("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
60710
61248
  }
60711
61249
  /**
60712
61250
  * Notify the server that the user client left the collaborative session
@@ -60878,7 +61416,6 @@ stores.inject(MyMetaStore, storeInstance);
60878
61416
  if (this.waitingAck) {
60879
61417
  return;
60880
61418
  }
60881
- this.waitingAck = true;
60882
61419
  this.sendPendingMessage();
60883
61420
  }
60884
61421
  /**
@@ -60915,6 +61452,7 @@ stores.inject(MyMetaStore, storeInstance);
60915
61452
  throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.
60916
61453
  ${JSON.stringify(message)}`);
60917
61454
  }
61455
+ this.waitingAck = true;
60918
61456
  this.transportService.sendMessage({
60919
61457
  ...message,
60920
61458
  serverRevisionId: this.serverRevisionId,
@@ -60945,6 +61483,7 @@ stores.inject(MyMetaStore, storeInstance);
60945
61483
  case "REMOTE_REVISION":
60946
61484
  case "REVISION_REDONE":
60947
61485
  case "REVISION_UNDONE":
61486
+ case "SNAPSHOT_CREATED":
60948
61487
  return this.processedRevisions.has(message.nextRevisionId);
60949
61488
  default:
60950
61489
  return false;
@@ -61073,8 +61612,7 @@ stores.inject(MyMetaStore, storeInstance);
61073
61612
  }
61074
61613
  const color = client.color;
61075
61614
  /* Cell background */
61076
- const cellBackgroundColor = `${color}10`;
61077
- ctx.fillStyle = cellBackgroundColor;
61615
+ ctx.fillStyle = `${color}10`;
61078
61616
  ctx.lineWidth = 4 * thinLineWidth;
61079
61617
  ctx.strokeStyle = color;
61080
61618
  ctx.globalCompositeOperation = "multiply";
@@ -61441,8 +61979,7 @@ stores.inject(MyMetaStore, storeInstance);
61441
61979
  exportForExcel(data) {
61442
61980
  for (const sheetData of data.sheets) {
61443
61981
  for (const [row, rowData] of Object.entries(sheetData.rows)) {
61444
- const isHidden = this.isRowHidden(sheetData.id, Number(row));
61445
- rowData.isHidden = isHidden;
61982
+ rowData.isHidden = this.isRowHidden(sheetData.id, Number(row));
61446
61983
  }
61447
61984
  }
61448
61985
  }
@@ -61502,12 +62039,13 @@ stores.inject(MyMetaStore, storeInstance);
61502
62039
  this.dispatch("DUPLICATE_PIVOT", {
61503
62040
  pivotId,
61504
62041
  newPivotId,
62042
+ duplicatedPivotName: _t("%s (copy)", this.getters.getPivotCoreDefinition(pivotId).name),
61505
62043
  });
61506
62044
  const activeSheetId = this.getters.getActiveSheetId();
61507
62045
  const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
61508
62046
  const formulaId = this.getters.getPivotFormulaId(newPivotId);
61509
62047
  const newPivotName = this.getters.getPivotName(newPivotId);
61510
- this.dispatch("CREATE_SHEET", {
62048
+ const result = this.dispatch("CREATE_SHEET", {
61511
62049
  sheetId: newSheetId,
61512
62050
  name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
61513
62051
  newPivotName,
@@ -61515,20 +62053,19 @@ stores.inject(MyMetaStore, storeInstance);
61515
62053
  })),
61516
62054
  position,
61517
62055
  });
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
- });
62056
+ if (result.isSuccessful) {
62057
+ this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
62058
+ const pivot = this.getters.getPivot(pivotId);
62059
+ this.insertPivotWithTable(newSheetId, 0, 0, newPivotId, pivot.getTableStructure().export(), "dynamic");
62060
+ }
61525
62061
  }
61526
62062
  getPivotDuplicateSheetName(pivotName) {
61527
62063
  let i = 1;
61528
62064
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
61529
- let name = pivotName;
62065
+ const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
62066
+ let name = sanitizedName;
61530
62067
  while (names.includes(name)) {
61531
- name = `${pivotName} (${i})`;
62068
+ name = `${sanitizedName} (${i})`;
61532
62069
  i++;
61533
62070
  }
61534
62071
  return name;
@@ -61967,7 +62504,6 @@ stores.inject(MyMetaStore, storeInstance);
61967
62504
  if (!isEqual(zone, newZone)) {
61968
62505
  hasExpanded = true;
61969
62506
  zone = newZone;
61970
- continue;
61971
62507
  }
61972
62508
  } while (hasExpanded);
61973
62509
  return zone;
@@ -63060,7 +63596,7 @@ stores.inject(MyMetaStore, storeInstance);
63060
63596
  case "ADD_COLUMNS_ROWS": {
63061
63597
  this.status = "invisible";
63062
63598
  // If we add a col/row inside or before the cut area, we invalidate the clipboard
63063
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63599
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63064
63600
  return;
63065
63601
  }
63066
63602
  const isClipboardDirty = this.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -63072,7 +63608,7 @@ stores.inject(MyMetaStore, storeInstance);
63072
63608
  case "REMOVE_COLUMNS_ROWS": {
63073
63609
  this.status = "invisible";
63074
63610
  // If we remove a col/row inside or before the cut area, we invalidate the clipboard
63075
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63611
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63076
63612
  return;
63077
63613
  }
63078
63614
  for (let el of cmd.elements) {
@@ -63094,7 +63630,7 @@ stores.inject(MyMetaStore, storeInstance);
63094
63630
  break;
63095
63631
  }
63096
63632
  case "DELETE_SHEET":
63097
- if (this._isCutOperation !== true) {
63633
+ if (!this._isCutOperation) {
63098
63634
  return;
63099
63635
  }
63100
63636
  if (this.originSheetId === cmd.sheetId) {
@@ -63987,8 +64523,7 @@ stores.inject(MyMetaStore, storeInstance);
63987
64523
  this.setSelectionMixin({ zone, cell: { col, row } }, [zone]);
63988
64524
  }
63989
64525
  setActiveSheet(id) {
63990
- const sheet = this.getters.getSheet(id);
63991
- this.activeSheet = sheet;
64526
+ this.activeSheet = this.getters.getSheet(id);
63992
64527
  }
63993
64528
  activateNextSheet(direction) {
63994
64529
  const sheetIds = this.getters.getSheetIds();
@@ -64682,9 +65217,6 @@ stores.inject(MyMetaStore, storeInstance);
64682
65217
  case "UNFREEZE_COLUMNS_ROWS":
64683
65218
  this.resetViewports(this.getters.getActiveSheetId());
64684
65219
  break;
64685
- case "DELETE_SHEET":
64686
- this.sheetsWithDirtyViewports.delete(cmd.sheetId);
64687
- break;
64688
65220
  case "SCROLL_TO_CELL":
64689
65221
  this.refreshViewport(this.getters.getActiveSheetId(), { col: cmd.col, row: cmd.row });
64690
65222
  break;
@@ -66636,10 +67168,9 @@ stores.inject(MyMetaStore, storeInstance);
66636
67168
  user-select: none;
66637
67169
  color: ${TEXT_BODY};
66638
67170
 
66639
- .o-heading-3 {
67171
+ .o-sidePanelTitle {
66640
67172
  line-height: 20px;
66641
67173
  font-size: 16px;
66642
- font-weight: 600;
66643
67174
  }
66644
67175
 
66645
67176
  .o-sidePanelHeader {
@@ -66724,6 +67255,10 @@ stores.inject(MyMetaStore, storeInstance);
66724
67255
  }
66725
67256
  }
66726
67257
  }
67258
+
67259
+ .o-fw-bold {
67260
+ font-weight: 500;
67261
+ }
66727
67262
  `;
66728
67263
  class SidePanel extends owl.Component {
66729
67264
  static template = "o-spreadsheet-SidePanel";
@@ -67555,8 +68090,7 @@ stores.inject(MyMetaStore, storeInstance);
67555
68090
  for (const item of clipboardItems) {
67556
68091
  for (const type of item.types) {
67557
68092
  const blob = await item.getType(type);
67558
- const text = await blob.text();
67559
- clipboardContent[type] = text;
68093
+ clipboardContent[type] = await blob.text();
67560
68094
  }
67561
68095
  }
67562
68096
  return { status: "ok", content: clipboardContent };
@@ -67868,7 +68402,6 @@ stores.inject(MyMetaStore, storeInstance);
67868
68402
  spreadsheetRef = owl.useRef("spreadsheet");
67869
68403
  spreadsheetRect = useSpreadsheetRect();
67870
68404
  _focusGrid;
67871
- keyDownMapping;
67872
68405
  isViewportTooSmall = false;
67873
68406
  notificationStore;
67874
68407
  composerFocusStore;
@@ -67892,10 +68425,6 @@ stores.inject(MyMetaStore, storeInstance);
67892
68425
  this.notificationStore = useStore(NotificationStore);
67893
68426
  this.composerFocusStore = useStore(ComposerFocusStore);
67894
68427
  this.sidePanel = useStore(SidePanelStore);
67895
- this.keyDownMapping = {
67896
- "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
67897
- "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
67898
- };
67899
68428
  const fileStore = this.model.config.external.fileStore;
67900
68429
  owl.useSubEnv({
67901
68430
  model: this.model,
@@ -67995,20 +68524,6 @@ stores.inject(MyMetaStore, storeInstance);
67995
68524
  }
67996
68525
  this._focusGrid();
67997
68526
  }
67998
- onKeydown(ev) {
67999
- let keyDownString = "";
68000
- if (isCtrlKey(ev)) {
68001
- keyDownString += "CTRL+";
68002
- }
68003
- keyDownString += ev.key.toUpperCase();
68004
- let handler = this.keyDownMapping[keyDownString];
68005
- if (handler) {
68006
- ev.preventDefault();
68007
- ev.stopPropagation();
68008
- handler();
68009
- return;
68010
- }
68011
- }
68012
68527
  get gridHeight() {
68013
68528
  const { height } = this.env.model.getters.getSheetViewDimension();
68014
68529
  return height;
@@ -69423,10 +69938,10 @@ stores.inject(MyMetaStore, storeInstance);
69423
69938
  getNextCellPosition(currentPosition, dimension, direction) {
69424
69939
  const dimOfInterest = dimension === "cols" ? "col" : "row";
69425
69940
  const startingPosition = { ...currentPosition };
69426
- const nextCoord = dimension === "cols"
69427
- ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69428
- : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69429
- startingPosition[dimOfInterest] = nextCoord;
69941
+ startingPosition[dimOfInterest] =
69942
+ dimension === "cols"
69943
+ ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69944
+ : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69430
69945
  return { col: startingPosition.col, row: startingPosition.row };
69431
69946
  }
69432
69947
  getPosition() {
@@ -69550,6 +70065,8 @@ stores.inject(MyMetaStore, storeInstance);
69550
70065
  case "pie":
69551
70066
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
69552
70067
  break;
70068
+ case "radar":
70069
+ plot = addRadarChart(chart.data);
69553
70070
  }
69554
70071
  let position = "t";
69555
70072
  switch (chart.data.legendPosition) {
@@ -70008,6 +70525,53 @@ stores.inject(MyMetaStore, storeInstance);
70008
70525
  `
70009
70526
  : ""}`;
70010
70527
  }
70528
+ function addRadarChart(chart) {
70529
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
70530
+ const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
70531
+ const dataSetsNodes = [];
70532
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
70533
+ const color = toXlsxHexColor(colors.next());
70534
+ const dataShapeProperty = shapeProperty({
70535
+ line: {
70536
+ width: 2.5,
70537
+ style: "solid",
70538
+ color,
70539
+ },
70540
+ });
70541
+ const dataSetNode = escapeXml /*xml*/ `
70542
+ <c:ser>
70543
+ <c:idx val="${dsIndex}"/>
70544
+ <c:order val="${dsIndex}"/>
70545
+ <c:smooth val="0"/>
70546
+ <c:marker>
70547
+ <c:symbol val="circle" />
70548
+ <c:size val="5"/>
70549
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
70550
+ </c:marker>
70551
+ ${extractDataSetLabel(dataset.label)}
70552
+ ${dataShapeProperty}
70553
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
70554
+ <c:val> <!-- x-coordinate values -->
70555
+ ${numberRef(dataset.range)}
70556
+ </c:val>
70557
+ </c:ser>
70558
+ `;
70559
+ dataSetsNodes.push(dataSetNode);
70560
+ }
70561
+ return escapeXml /*xml*/ `
70562
+ ${escapeXml /*xml*/ `
70563
+ <c:radarChart>
70564
+ <c:radarStyle val="marker"/>
70565
+ <c:varyColors val="0"/>
70566
+ ${joinXmlNodes(dataSetsNodes)}
70567
+ <c:axId val="${catAxId}" />
70568
+ <c:axId val="${valAxId}" />
70569
+ </c:radarChart>
70570
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
70571
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
70572
+ `}
70573
+ `;
70574
+ }
70011
70575
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
70012
70576
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
70013
70577
  const colors = new ColorGenerator(maxLength);
@@ -70881,6 +71445,15 @@ stores.inject(MyMetaStore, storeInstance);
70881
71445
  ["id", i + 1], // id cannot be 0
70882
71446
  ["name", colName],
70883
71447
  ];
71448
+ if (table.config.totalRow) {
71449
+ // Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
71450
+ // `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
71451
+ const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
71452
+ const colTotalContent = sheetData.cells[colTotalXc]?.content;
71453
+ if (colTotalContent?.startsWith("=")) {
71454
+ colAttributes.push(["totalsRowFunction", "custom"]);
71455
+ }
71456
+ }
70884
71457
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
70885
71458
  }
70886
71459
  return escapeXml /*xml*/ `
@@ -70975,8 +71548,9 @@ stores.inject(MyMetaStore, storeInstance);
70975
71548
  }
70976
71549
  else if (cell.content && cell.content !== "") {
70977
71550
  const isTableHeader = isCellTableHeader(c, r, sheet);
71551
+ const isTableTotal = isCellTableTotal(c, r, sheet);
70978
71552
  const isPlainText = !!(cell.format && isTextFormat(data.formats[cell.format]));
70979
- ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
71553
+ ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
70980
71554
  }
70981
71555
  attributes.push(...additionalAttrs);
70982
71556
  // prettier-ignore
@@ -71010,6 +71584,16 @@ stores.inject(MyMetaStore, storeInstance);
71010
71584
  return isInside(col, row, headerZone);
71011
71585
  });
71012
71586
  }
71587
+ function isCellTableTotal(col, row, sheet) {
71588
+ return sheet.tables.some((table) => {
71589
+ if (!table.config.totalRow) {
71590
+ return false;
71591
+ }
71592
+ const zone = toZone(table.range);
71593
+ const totalZone = { ...zone, top: zone.bottom };
71594
+ return isInside(col, row, totalZone);
71595
+ });
71596
+ }
71013
71597
  function addHyperlinks(construct, data, sheetIndex) {
71014
71598
  const sheet = data.sheets[sheetIndex];
71015
71599
  const cells = sheet.cells;
@@ -71095,25 +71679,23 @@ stores.inject(MyMetaStore, storeInstance);
71095
71679
  ["showGridLines", sheet.areGridLinesVisible ? 1 : 0],
71096
71680
  ["workbookViewId", 0],
71097
71681
  ];
71098
- let sheetView = escapeXml /*xml*/ `
71682
+ return escapeXml /*xml*/ `
71099
71683
  <sheetViews>
71100
71684
  <sheetView ${formatAttributes(sheetViewAttrs)}>
71101
71685
  ${splitPanes}
71102
71686
  </sheetView>
71103
71687
  </sheetViews>
71104
71688
  `;
71105
- return sheetView;
71106
71689
  }
71107
71690
  function addSheetProperties(sheet) {
71108
71691
  if (!sheet.color) {
71109
71692
  return "";
71110
71693
  }
71111
- let sheetView = escapeXml /*xml*/ `
71694
+ return escapeXml /*xml*/ `
71112
71695
  <sheetPr>
71113
71696
  <tabColor ${formatAttributes([["rgb", toXlsxHexColor(sheet.color)]])} />
71114
71697
  </sheetPr>
71115
71698
  `;
71116
- return sheetView;
71117
71699
  }
71118
71700
 
71119
71701
  /**
@@ -71426,9 +72008,7 @@ stores.inject(MyMetaStore, storeInstance);
71426
72008
  })(Status || (Status = {}));
71427
72009
  class Model extends EventBus {
71428
72010
  corePlugins = [];
71429
- featurePlugins = [];
71430
72011
  statefulUIPlugins = [];
71431
- coreViewsPlugins = [];
71432
72012
  range;
71433
72013
  session;
71434
72014
  /**
@@ -71473,7 +72053,7 @@ stores.inject(MyMetaStore, storeInstance);
71473
72053
  coreHandlers = [];
71474
72054
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = false) {
71475
72055
  const start = performance.now();
71476
- console.group("Model creation");
72056
+ console.debug("##### Model creation #####");
71477
72057
  super();
71478
72058
  setDefaultTranslationMethod();
71479
72059
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
@@ -71500,7 +72080,6 @@ stores.inject(MyMetaStore, storeInstance);
71500
72080
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
71501
72081
  isDashboard: () => this.config.mode === "dashboard",
71502
72082
  };
71503
- this.uuidGenerator.setIsFastStrategy(true);
71504
72083
  // Initiate stream processor
71505
72084
  this.selection = new SelectionStreamProcessorImpl(this.getters);
71506
72085
  this.coreHandlers.push(this.range);
@@ -71515,7 +72094,6 @@ stores.inject(MyMetaStore, storeInstance);
71515
72094
  this.session.loadInitialMessages(stateUpdateMessages);
71516
72095
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
71517
72096
  const plugin = this.setupUiPlugin(Plugin);
71518
- this.coreViewsPlugins.push(plugin);
71519
72097
  this.handlers.push(plugin);
71520
72098
  this.uiHandlers.push(plugin);
71521
72099
  this.coreHandlers.push(plugin);
@@ -71528,11 +72106,9 @@ stores.inject(MyMetaStore, storeInstance);
71528
72106
  }
71529
72107
  for (let Plugin of featurePluginRegistry.getAll()) {
71530
72108
  const plugin = this.setupUiPlugin(Plugin);
71531
- this.featurePlugins.push(plugin);
71532
72109
  this.handlers.push(plugin);
71533
72110
  this.uiHandlers.push(plugin);
71534
72111
  }
71535
- this.uuidGenerator.setIsFastStrategy(false);
71536
72112
  // starting plugins
71537
72113
  this.dispatch("START");
71538
72114
  // Model should be the last permanent subscriber in the list since he should render
@@ -71546,16 +72122,16 @@ stores.inject(MyMetaStore, storeInstance);
71546
72122
  this.joinSession();
71547
72123
  if (config.snapshotRequested) {
71548
72124
  const startSnapshot = performance.now();
71549
- console.info("Snapshot requested");
72125
+ console.debug("Snapshot requested");
71550
72126
  this.session.snapshot(this.exportData());
71551
72127
  this.garbageCollectExternalResources();
71552
- console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
72128
+ console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
71553
72129
  }
71554
72130
  // mark all models as "raw", so they will not be turned into reactive objects
71555
72131
  // by owl, since we do not rely on reactivity
71556
72132
  owl.markRaw(this);
71557
- console.info("Model created in", performance.now() - start, "ms");
71558
- console.groupEnd();
72133
+ console.debug("Model created in", performance.now() - start, "ms");
72134
+ console.debug("######");
71559
72135
  }
71560
72136
  joinSession() {
71561
72137
  this.session.join(this.config.client);
@@ -71614,7 +72190,7 @@ stores.inject(MyMetaStore, storeInstance);
71614
72190
  this.finalize();
71615
72191
  }
71616
72192
  setupSession(revisionId) {
71617
- const session = new Session(buildRevisionLog({
72193
+ return new Session(buildRevisionLog({
71618
72194
  initialRevisionId: revisionId,
71619
72195
  recordChanges: this.state.recordChanges.bind(this.state),
71620
72196
  dispatch: (command) => {
@@ -71627,7 +72203,6 @@ stores.inject(MyMetaStore, storeInstance);
71627
72203
  this.isReplayingCommand = false;
71628
72204
  },
71629
72205
  }), this.config.transportService, revisionId);
71630
- return session;
71631
72206
  }
71632
72207
  setupSessionEvents() {
71633
72208
  this.session.on("remote-revision-received", this, this.onRemoteRevisionReceived);
@@ -71680,7 +72255,6 @@ stores.inject(MyMetaStore, storeInstance);
71680
72255
  range: this.range,
71681
72256
  dispatch: this.dispatchFromCorePlugin,
71682
72257
  canDispatch: this.canDispatch,
71683
- uuidGenerator: this.uuidGenerator,
71684
72258
  custom: this.config.custom,
71685
72259
  external: this.config.external,
71686
72260
  };
@@ -71721,8 +72295,7 @@ stores.inject(MyMetaStore, storeInstance);
71721
72295
  return results;
71722
72296
  }
71723
72297
  checkDispatchAllowedLocalCommand(command) {
71724
- const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
71725
- return results;
72298
+ return this.uiHandlers.map((handler) => handler.allowDispatch(command));
71726
72299
  }
71727
72300
  finalize() {
71728
72301
  this.status = 3 /* Status.Finalizing */;
@@ -71779,7 +72352,7 @@ stores.inject(MyMetaStore, storeInstance);
71779
72352
  this.finalize();
71780
72353
  const time = performance.now() - start;
71781
72354
  if (time > 5) {
71782
- console.info(type, time, "ms");
72355
+ console.debug(type, time, "ms");
71783
72356
  }
71784
72357
  });
71785
72358
  this.session.save(command, commands, changes);
@@ -72082,7 +72655,6 @@ stores.inject(MyMetaStore, storeInstance);
72082
72655
  PivotDimensionOrder,
72083
72656
  PivotDimension,
72084
72657
  PivotLayoutConfigurator,
72085
- EditableName,
72086
72658
  PivotDeferUpdate,
72087
72659
  PivotTitleSection,
72088
72660
  CogWheelMenu,
@@ -72174,9 +72746,9 @@ stores.inject(MyMetaStore, storeInstance);
72174
72746
  exports.tokenize = tokenize;
72175
72747
 
72176
72748
 
72177
- __info__.version = "18.1.0-alpha.0";
72178
- __info__.date = "2024-09-25T13:17:49.636Z";
72179
- __info__.hash = "288f0b7";
72749
+ __info__.version = "18.1.0-alpha.2";
72750
+ __info__.date = "2024-10-24T08:53:21.828Z";
72751
+ __info__.hash = "2a01250";
72180
72752
 
72181
72753
 
72182
72754
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);