@odoo/o-spreadsheet 17.4.8 → 17.4.10

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,119 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.4.8
6
- * @date 2024-09-17T08:10:18.777Z
7
- * @hash caad8bd
5
+ * @version 17.4.10
6
+ * @date 2024-10-24T08:54:56.262Z
7
+ * @hash c82d9c1
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
+ secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
43
+ description: typeof description === "function" ? description : () => description || "",
44
+ textColor: item.textColor,
45
+ sequence: item.sequence || 0,
46
+ onStartHover: item.onStartHover,
47
+ onStopHover: item.onStopHover,
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Registry
53
+ *
54
+ * The Registry class is basically just a mapping from a string key to an object.
55
+ * It is really not much more than an object. It is however useful for the
56
+ * following reasons:
57
+ *
58
+ * 1. it let us react and execute code when someone add something to the registry
59
+ * (for example, the FunctionRegistry subclass this for this purpose)
60
+ * 2. it throws an error when the get operation fails
61
+ * 3. it provides a chained API to add items to the registry.
62
+ */
63
+ class Registry {
64
+ content = {};
65
+ /**
66
+ * Add an item to the registry
67
+ *
68
+ * Note that this also returns the registry, so another add method call can
69
+ * be chained
70
+ */
71
+ add(key, value) {
72
+ this.content[key] = value;
73
+ return this;
74
+ }
75
+ /**
76
+ * Get an item from the registry
77
+ */
78
+ get(key) {
79
+ /**
80
+ * Note: key in {} is ~12 times slower than {}[key].
81
+ * So, we check the absence of key only when the direct access returns
82
+ * a falsy value. It's done to ensure that the registry can contains falsy values
83
+ */
84
+ const content = this.content[key];
85
+ if (!content) {
86
+ if (!(key in this.content)) {
87
+ throw new Error(`Cannot find ${key} in this registry!`);
88
+ }
89
+ }
90
+ return content;
91
+ }
92
+ /**
93
+ * Check if the key is already in the registry
94
+ */
95
+ contains(key) {
96
+ return key in this.content;
97
+ }
98
+ /**
99
+ * Get a list of all elements in the registry
100
+ */
101
+ getAll() {
102
+ return Object.values(this.content);
103
+ }
104
+ /**
105
+ * Get a list of all keys in the registry
106
+ */
107
+ getKeys() {
108
+ return Object.keys(this.content);
109
+ }
110
+ /**
111
+ * Remove an item from the registry
112
+ */
113
+ remove(key) {
114
+ delete this.content[key];
115
+ }
116
+ }
117
+
13
118
  const CANVAS_SHIFT = 0.5;
14
119
  // Colors
15
120
  const HIGHLIGHT_COLOR = "#37A850";
@@ -166,7 +271,7 @@
166
271
  underline: false,
167
272
  fontSize: 10,
168
273
  fillColor: "",
169
- textColor: "#000000",
274
+ textColor: "",
170
275
  };
171
276
  const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
172
277
  const DEFAULT_WRAPPING_MODE = DEFAULT_STYLE.wrapping;
@@ -5398,110 +5503,6 @@
5398
5503
  }
5399
5504
  }
5400
5505
 
5401
- function createActions(menuItems) {
5402
- return menuItems.map(createAction).sort((a, b) => a.sequence - b.sequence);
5403
- }
5404
- const uuidGenerator$1 = new UuidGenerator();
5405
- function createAction(item) {
5406
- const name = item.name;
5407
- const children = item.children;
5408
- const description = item.description;
5409
- const icon = item.icon;
5410
- const secondaryIcon = item.secondaryIcon;
5411
- return {
5412
- id: item.id || uuidGenerator$1.uuidv4(),
5413
- name: typeof name === "function" ? name : () => name,
5414
- isVisible: item.isVisible ? item.isVisible : () => true,
5415
- isEnabled: item.isEnabled ? item.isEnabled : () => true,
5416
- isActive: item.isActive,
5417
- execute: item.execute,
5418
- children: children
5419
- ? (env) => {
5420
- return children
5421
- .map((child) => (typeof child === "function" ? child(env) : child))
5422
- .flat()
5423
- .map(createAction);
5424
- }
5425
- : () => [],
5426
- isReadonlyAllowed: item.isReadonlyAllowed || false,
5427
- separator: item.separator || false,
5428
- icon: typeof icon === "function" ? icon : () => icon || "",
5429
- secondaryIcon: typeof secondaryIcon === "function" ? secondaryIcon : () => secondaryIcon || "",
5430
- description: typeof description === "function" ? description : () => description || "",
5431
- textColor: item.textColor,
5432
- sequence: item.sequence || 0,
5433
- onStartHover: item.onStartHover,
5434
- onStopHover: item.onStopHover,
5435
- };
5436
- }
5437
-
5438
- /**
5439
- * Registry
5440
- *
5441
- * The Registry class is basically just a mapping from a string key to an object.
5442
- * It is really not much more than an object. It is however useful for the
5443
- * following reasons:
5444
- *
5445
- * 1. it let us react and execute code when someone add something to the registry
5446
- * (for example, the FunctionRegistry subclass this for this purpose)
5447
- * 2. it throws an error when the get operation fails
5448
- * 3. it provides a chained API to add items to the registry.
5449
- */
5450
- class Registry {
5451
- content = {};
5452
- /**
5453
- * Add an item to the registry
5454
- *
5455
- * Note that this also returns the registry, so another add method call can
5456
- * be chained
5457
- */
5458
- add(key, value) {
5459
- this.content[key] = value;
5460
- return this;
5461
- }
5462
- /**
5463
- * Get an item from the registry
5464
- */
5465
- get(key) {
5466
- /**
5467
- * Note: key in {} is ~12 times slower than {}[key].
5468
- * So, we check the absence of key only when the direct access returns
5469
- * a falsy value. It's done to ensure that the registry can contains falsy values
5470
- */
5471
- const content = this.content[key];
5472
- if (!content) {
5473
- if (!(key in this.content)) {
5474
- throw new Error(`Cannot find ${key} in this registry!`);
5475
- }
5476
- }
5477
- return content;
5478
- }
5479
- /**
5480
- * Check if the key is already in the registry
5481
- */
5482
- contains(key) {
5483
- return key in this.content;
5484
- }
5485
- /**
5486
- * Get a list of all elements in the registry
5487
- */
5488
- getAll() {
5489
- return Object.values(this.content);
5490
- }
5491
- /**
5492
- * Get a list of all keys in the registry
5493
- */
5494
- getKeys() {
5495
- return Object.keys(this.content);
5496
- }
5497
- /**
5498
- * Remove an item from the registry
5499
- */
5500
- remove(key) {
5501
- delete this.content[key];
5502
- }
5503
- }
5504
-
5505
5506
  function getClipboardDataPositions(sheetId, zones) {
5506
5507
  const lefts = new Set(zones.map((z) => z.left));
5507
5508
  const rights = new Set(zones.map((z) => z.right));
@@ -7280,31 +7281,34 @@
7280
7281
  for (let col of columnsIndexes) {
7281
7282
  const position = { col, row, sheetId };
7282
7283
  const table = this.getters.getTable(position);
7283
- if (!table || copiedTablesIds.has(table.id)) {
7284
+ if (!table) {
7284
7285
  tableCellsInRow.push({});
7285
7286
  continue;
7286
7287
  }
7287
7288
  const coreTable = this.getters.getCoreTable(position);
7288
7289
  const tableZone = coreTable?.range.zone;
7290
+ let copiedTable = undefined;
7289
7291
  // Copy whole table
7290
- if (coreTable && tableZone && zones.some((z) => isZoneInside(tableZone, z))) {
7291
- copiedTablesIds.add(coreTable.id);
7292
+ if (!copiedTablesIds.has(table.id) &&
7293
+ coreTable &&
7294
+ tableZone &&
7295
+ zones.some((z) => isZoneInside(tableZone, z))) {
7296
+ copiedTablesIds.add(table.id);
7292
7297
  const values = [];
7293
7298
  for (const col of range(tableZone.left, tableZone.right + 1)) {
7294
7299
  values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
7295
7300
  }
7296
- tableCellsInRow.push({
7297
- table: {
7298
- range: coreTable.range,
7299
- config: coreTable.config,
7300
- type: coreTable.type,
7301
- },
7302
- });
7303
- }
7304
- // Copy only style of cell
7305
- else if (table) {
7306
- tableCellsInRow.push({ style: this.getTableStyleToCopy(position) });
7301
+ copiedTable = {
7302
+ range: coreTable.range,
7303
+ config: coreTable.config,
7304
+ type: coreTable.type,
7305
+ };
7307
7306
  }
7307
+ tableCellsInRow.push({
7308
+ table: copiedTable,
7309
+ style: this.getTableStyleToCopy(position),
7310
+ isWholeTableCopied: copiedTablesIds.has(table.id),
7311
+ });
7308
7312
  }
7309
7313
  }
7310
7314
  return {
@@ -7385,11 +7389,14 @@
7385
7389
  tableType: tableCell.table.type,
7386
7390
  });
7387
7391
  }
7388
- // Do not paste table style if we're inside another table
7389
7392
  // We cannot check for dynamic tables, because at this point the paste can have changed the evaluation, and the
7390
7393
  // dynamic tables are not yet computed
7391
- if (!this.getters.getCoreTable(position)) {
7392
- if (tableCell.style?.style && options?.pasteOption !== "asValue") {
7394
+ if (this.getters.getCoreTable(position) || options?.pasteOption === "asValue") {
7395
+ return;
7396
+ }
7397
+ if ((!options?.pasteOption && !tableCell.isWholeTableCopied) ||
7398
+ options?.pasteOption === "onlyFormat") {
7399
+ if (tableCell.style?.style) {
7393
7400
  this.dispatch("UPDATE_CELL", { ...position, style: tableCell.style.style });
7394
7401
  }
7395
7402
  if (tableCell.style?.border) {
@@ -10617,67 +10624,108 @@ stores.inject(MyMetaStore, storeInstance);
10617
10624
  ctx.save();
10618
10625
  ctx.textAlign = "center";
10619
10626
  ctx.textBaseline = "middle";
10620
- ctx.fillStyle = chartFontColor(options.background);
10621
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10622
- chart._metasets.forEach(function (dataset) {
10623
- switch (dataset.type) {
10624
- case "doughnut":
10625
- case "pie": {
10626
- for (let i = 0; i < dataset._parsed.length; i++) {
10627
- const bar = dataset.data[i];
10628
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10629
- const midAngle = (startAngle + endAngle) / 2;
10630
- const midRadius = (innerRadius + outerRadius) / 2;
10631
- const x = bar.x + midRadius * Math.cos(midAngle);
10632
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10633
- ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
10634
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10635
- const value = options.callback(dataset._parsed[i]);
10636
- ctx.strokeText(value, x, y);
10637
- ctx.fillText(value, x, y);
10638
- }
10639
- break;
10640
- }
10641
- case "bar":
10642
- case "line": {
10643
- const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
10644
- for (let i = 0; i < dataset._parsed.length; i++) {
10645
- const point = dataset.data[i];
10646
- const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
10647
- const displayedValue = options.callback(value - 0);
10648
- let xPosition = 0, yPosition = 0;
10649
- if (options.horizontal) {
10650
- yPosition = point.y;
10651
- if (value < 0) {
10652
- ctx.textAlign = "right";
10653
- xPosition = point.x - yOffset;
10654
- }
10655
- else {
10656
- ctx.textAlign = "left";
10657
- xPosition = point.x + yOffset;
10658
- }
10659
- }
10660
- else {
10661
- xPosition = point.x;
10662
- if (value < 0) {
10663
- ctx.textBaseline = "top";
10664
- yPosition = point.y + yOffset;
10665
- }
10666
- else {
10667
- ctx.textBaseline = "bottom";
10668
- yPosition = point.y - yOffset;
10669
- }
10670
- }
10671
- ctx.strokeText(displayedValue, xPosition, yPosition);
10672
- ctx.fillText(displayedValue, xPosition, yPosition);
10673
- }
10674
- break;
10675
- }
10676
- }
10677
- });
10627
+ ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
10628
+ switch (chart.config.type) {
10629
+ case "pie":
10630
+ case "doughnut":
10631
+ drawPieChartValues(chart, options, ctx);
10632
+ break;
10633
+ case "bar":
10634
+ case "line":
10635
+ options.horizontal
10636
+ ? drawHorizontalBarChartValues(chart, options, ctx)
10637
+ : drawLineOrBarChartValues(chart, options, ctx);
10638
+ break;
10639
+ }
10678
10640
  ctx.restore();
10679
10641
  },
10680
10642
  };
10643
+ function drawTextWithBackground(text, x, y, ctx) {
10644
+ ctx.lineWidth = 3; // Stroke the text with a big lineWidth width to have some kind of background
10645
+ ctx.strokeText(text, x, y);
10646
+ ctx.lineWidth = 1;
10647
+ ctx.fillText(text, x, y);
10648
+ }
10649
+ function drawLineOrBarChartValues(chart, options, ctx) {
10650
+ const yMax = chart.chartArea.bottom;
10651
+ const yMin = chart.chartArea.top;
10652
+ const textsPositions = {};
10653
+ for (const dataset of chart._metasets) {
10654
+ for (let i = 0; i < dataset._parsed.length; i++) {
10655
+ const value = dataset._parsed[i].y;
10656
+ const point = dataset.data[i];
10657
+ const xPosition = point.x;
10658
+ let yPosition = 0;
10659
+ if (chart.config.type === "line") {
10660
+ yPosition = point.y - 10;
10661
+ }
10662
+ else {
10663
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10664
+ }
10665
+ yPosition = Math.min(yPosition, yMax);
10666
+ yPosition = Math.max(yPosition, yMin);
10667
+ // Avoid overlapping texts with same X
10668
+ if (!textsPositions[xPosition]) {
10669
+ textsPositions[xPosition] = [];
10670
+ }
10671
+ for (const otherPosition of textsPositions[xPosition] || []) {
10672
+ if (Math.abs(otherPosition - yPosition) < 13) {
10673
+ yPosition = otherPosition - 13;
10674
+ }
10675
+ }
10676
+ textsPositions[xPosition].push(yPosition);
10677
+ ctx.fillStyle = point.options.backgroundColor;
10678
+ ctx.strokeStyle = options.background || "#ffffff";
10679
+ drawTextWithBackground(options.callback(value - 0), xPosition, yPosition, ctx);
10680
+ }
10681
+ }
10682
+ }
10683
+ function drawHorizontalBarChartValues(chart, options, ctx) {
10684
+ const xMax = chart.chartArea.right;
10685
+ const xMin = chart.chartArea.left;
10686
+ const textsPositions = {};
10687
+ for (const dataset of chart._metasets) {
10688
+ for (let i = 0; i < dataset._parsed.length; i++) {
10689
+ const value = dataset._parsed[i].x;
10690
+ const displayValue = options.callback(value - 0);
10691
+ const point = dataset.data[i];
10692
+ const yPosition = point.y;
10693
+ let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10694
+ xPosition = Math.min(xPosition, xMax);
10695
+ xPosition = Math.max(xPosition, xMin);
10696
+ // Avoid overlapping texts with same Y
10697
+ if (!textsPositions[yPosition]) {
10698
+ textsPositions[yPosition] = [];
10699
+ }
10700
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10701
+ for (const otherPosition of textsPositions[yPosition]) {
10702
+ if (Math.abs(otherPosition - xPosition) < textWidth) {
10703
+ xPosition = otherPosition + textWidth + 3;
10704
+ }
10705
+ }
10706
+ textsPositions[yPosition].push(xPosition);
10707
+ ctx.fillStyle = point.options.backgroundColor;
10708
+ ctx.strokeStyle = options.background || "#ffffff";
10709
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10710
+ }
10711
+ }
10712
+ }
10713
+ function drawPieChartValues(chart, options, ctx) {
10714
+ for (const dataset of chart._metasets) {
10715
+ for (let i = 0; i < dataset._parsed.length; i++) {
10716
+ const bar = dataset.data[i];
10717
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10718
+ const midAngle = (startAngle + endAngle) / 2;
10719
+ const midRadius = (innerRadius + outerRadius) / 2;
10720
+ const x = bar.x + midRadius * Math.cos(midAngle);
10721
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10722
+ ctx.fillStyle = chartFontColor(options.background);
10723
+ ctx.strokeStyle = options.background || "#ffffff";
10724
+ const value = options.callback(dataset._parsed[i]);
10725
+ drawTextWithBackground(value, x, y, ctx);
10726
+ }
10727
+ }
10728
+ }
10681
10729
 
10682
10730
  /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
10683
10731
  const waterfallLinesPlugin = {
@@ -19155,9 +19203,10 @@ stores.inject(MyMetaStore, storeInstance);
19155
19203
  return error;
19156
19204
  }
19157
19205
  if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19206
+ const suggestion = _t("Consider using a dynamic pivot formula: %s. Or re-insert the static pivot from the Data menu.", `=PIVOT(${_pivotFormulaId})`);
19158
19207
  return {
19159
19208
  value: CellErrorType.GenericError,
19160
- message: _t("Dimensions don't match the pivot definition"),
19209
+ message: _t("Dimensions don't match the pivot definition") + ". " + suggestion,
19161
19210
  };
19162
19211
  }
19163
19212
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
@@ -19184,9 +19233,10 @@ stores.inject(MyMetaStore, storeInstance);
19184
19233
  return error;
19185
19234
  }
19186
19235
  if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19236
+ const suggestion = _t("Consider using a dynamic pivot formula: %s. Or re-insert the static pivot from the Data menu.", `=PIVOT(${_pivotFormulaId})`);
19187
19237
  return {
19188
19238
  value: CellErrorType.GenericError,
19189
- message: _t("Dimensions don't match the pivot definition"),
19239
+ message: _t("Dimensions don't match the pivot definition") + ". " + suggestion,
19190
19240
  };
19191
19241
  }
19192
19242
  const domain = pivot.parseArgsToPivotDomain(domainArgs);
@@ -22362,6 +22412,7 @@ stores.inject(MyMetaStore, storeInstance);
22362
22412
  // replace the whole token
22363
22413
  start = tokenAtCursor.start;
22364
22414
  }
22415
+ this.composer.stopComposerRangeSelection();
22365
22416
  this.composer.changeComposerCursorSelection(start, end);
22366
22417
  this.composer.replaceComposerCursorSelection(value);
22367
22418
  }
@@ -22379,6 +22430,7 @@ stores.inject(MyMetaStore, storeInstance);
22379
22430
  // replace the whole token
22380
22431
  start = tokenAtCursor.start;
22381
22432
  }
22433
+ this.composer.stopComposerRangeSelection();
22382
22434
  this.composer.changeComposerCursorSelection(start, end);
22383
22435
  this.composer.replaceComposerCursorSelection(value);
22384
22436
  }
@@ -23403,7 +23455,7 @@ stores.inject(MyMetaStore, storeInstance);
23403
23455
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
23404
23456
  // tooltipItem.parsed can be an object or a number for pie charts
23405
23457
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
23406
- if (!yLabel) {
23458
+ if (yLabel === undefined || yLabel === null) {
23407
23459
  yLabel = tooltipItem.parsed;
23408
23460
  }
23409
23461
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
@@ -37194,6 +37246,8 @@ stores.inject(MyMetaStore, storeInstance);
37194
37246
  mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
37195
37247
  updatesAreDeferred = false;
37196
37248
  draft = null;
37249
+ notification = this.get(NotificationStore);
37250
+ alreadyNotified = false;
37197
37251
  constructor(get, pivotId) {
37198
37252
  super(get);
37199
37253
  this.pivotId = pivotId;
@@ -37300,6 +37354,16 @@ stores.inject(MyMetaStore, storeInstance);
37300
37354
  pivot: this.draft,
37301
37355
  });
37302
37356
  this.draft = null;
37357
+ if (!this.alreadyNotified && !this.isDynamicPivotInViewport()) {
37358
+ const formulaId = this.getters.getPivotFormulaId(this.pivotId);
37359
+ const pivotExample = `=PIVOT(${formulaId})`;
37360
+ this.alreadyNotified = true;
37361
+ this.notification.notifyUser({
37362
+ type: "info",
37363
+ text: _t("Pivot updates only work with dynamic pivot tables. Use %s or re-insert the static pivot from the Data menu.", pivotExample),
37364
+ sticky: false,
37365
+ });
37366
+ }
37303
37367
  }
37304
37368
  }
37305
37369
  discardPendingUpdate() {
@@ -37330,15 +37394,22 @@ stores.inject(MyMetaStore, storeInstance);
37330
37394
  return;
37331
37395
  }
37332
37396
  const cleanedWithGranularity = this.addDefaultDateTimeGranularity(this.fields, cleanedDefinition);
37333
- if (this.updatesAreDeferred) {
37334
- this.draft = cleanedWithGranularity;
37397
+ this.draft = cleanedWithGranularity;
37398
+ if (!this.updatesAreDeferred) {
37399
+ this.applyUpdate();
37335
37400
  }
37336
- else {
37337
- this.model.dispatch("UPDATE_PIVOT", {
37338
- pivotId: this.pivotId,
37339
- pivot: cleanedWithGranularity,
37340
- });
37401
+ }
37402
+ isDynamicPivotInViewport() {
37403
+ const sheetId = this.getters.getActiveSheetId();
37404
+ for (const col of this.getters.getSheetViewVisibleCols()) {
37405
+ for (const row of this.getters.getSheetViewVisibleRows()) {
37406
+ const isDynamicPivot = this.getters.isSpillPivotFormula({ sheetId, col, row });
37407
+ if (isDynamicPivot) {
37408
+ return true;
37409
+ }
37410
+ }
37341
37411
  }
37412
+ return false;
37342
37413
  }
37343
37414
  addDefaultDateTimeGranularity(fields, definition) {
37344
37415
  const { columns, rows } = definition;
@@ -46501,7 +46572,7 @@ stores.inject(MyMetaStore, storeInstance);
46501
46572
  if (!data) {
46502
46573
  return createEmptyWorkbookData();
46503
46574
  }
46504
- console.group("Loading data");
46575
+ console.debug("### Loading data ###");
46505
46576
  const start = performance.now();
46506
46577
  if (data["[Content_Types].xml"]) {
46507
46578
  const reader = new XlsxReader(data);
@@ -46515,13 +46586,13 @@ stores.inject(MyMetaStore, storeInstance);
46515
46586
  // apply migrations, if needed
46516
46587
  if ("version" in data) {
46517
46588
  if (data.version < CURRENT_VERSION) {
46518
- console.info("Migrating data from version", data.version);
46589
+ console.debug("Migrating data from version", data.version);
46519
46590
  data = migrate(data);
46520
46591
  }
46521
46592
  }
46522
46593
  data = repairData(data);
46523
- console.info("Data loaded in", performance.now() - start, "ms");
46524
- console.groupEnd();
46594
+ console.debug("Data loaded in", performance.now() - start, "ms");
46595
+ console.debug("###");
46525
46596
  return data;
46526
46597
  }
46527
46598
  function migrate(data) {
@@ -46530,7 +46601,7 @@ stores.inject(MyMetaStore, storeInstance);
46530
46601
  for (let i = index; i < MIGRATIONS.length; i++) {
46531
46602
  data = MIGRATIONS[i].applyMigration(data);
46532
46603
  }
46533
- console.info("Data migrated in", performance.now() - start, "ms");
46604
+ console.debug("Data migrated in", performance.now() - start, "ms");
46534
46605
  return data;
46535
46606
  }
46536
46607
  const MIGRATIONS = [
@@ -52429,7 +52500,7 @@ stores.inject(MyMetaStore, storeInstance);
52429
52500
  case "DUPLICATE_PIVOT": {
52430
52501
  const { pivotId, newPivotId } = cmd;
52431
52502
  const pivot = deepCopy(this.getPivotCore(pivotId).definition);
52432
- pivot.name = _t("%s (copy)", pivot.name);
52503
+ pivot.name = cmd.duplicatedPivotName ?? pivot.name + " (copy)";
52433
52504
  this.addPivot(newPivotId, pivot);
52434
52505
  break;
52435
52506
  }
@@ -54090,7 +54161,7 @@ stores.inject(MyMetaStore, storeInstance);
54090
54161
  cellsToCompute.addMany(arrayFormulasPositions);
54091
54162
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
54092
54163
  this.evaluate(cellsToCompute);
54093
- console.info("evaluate Cells", performance.now() - start, "ms");
54164
+ console.debug("evaluate Cells", performance.now() - start, "ms");
54094
54165
  }
54095
54166
  getArrayFormulasImpactedByChangesOf(positions) {
54096
54167
  const impactedPositions = this.createEmptyPositionSet();
@@ -54129,7 +54200,7 @@ stores.inject(MyMetaStore, storeInstance);
54129
54200
  const start = performance.now();
54130
54201
  this.evaluatedCells = new PositionMap();
54131
54202
  this.evaluate(this.getAllCells());
54132
- console.info("evaluate all cells", performance.now() - start, "ms");
54203
+ console.debug("evaluate all cells", performance.now() - start, "ms");
54133
54204
  }
54134
54205
  evaluateFormulaResult(sheetId, formulaString) {
54135
54206
  try {
@@ -57279,7 +57350,7 @@ stores.inject(MyMetaStore, storeInstance);
57279
57350
  this.onMessageReceived(message);
57280
57351
  }
57281
57352
  this.isReplayingInitialRevisions = false;
57282
- console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
57353
+ console.debug("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
57283
57354
  }
57284
57355
  /**
57285
57356
  * Notify the server that the user client left the collaborative session
@@ -57518,6 +57589,7 @@ stores.inject(MyMetaStore, storeInstance);
57518
57589
  case "REMOTE_REVISION":
57519
57590
  case "REVISION_REDONE":
57520
57591
  case "REVISION_UNDONE":
57592
+ case "SNAPSHOT_CREATED":
57521
57593
  return this.processedRevisions.has(message.nextRevisionId);
57522
57594
  default:
57523
57595
  return false;
@@ -58033,12 +58105,13 @@ stores.inject(MyMetaStore, storeInstance);
58033
58105
  this.dispatch("DUPLICATE_PIVOT", {
58034
58106
  pivotId,
58035
58107
  newPivotId,
58108
+ duplicatedPivotName: _t("%s (copy)", this.getters.getPivotCoreDefinition(pivotId).name),
58036
58109
  });
58037
58110
  const activeSheetId = this.getters.getActiveSheetId();
58038
58111
  const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
58039
58112
  const formulaId = this.getters.getPivotFormulaId(newPivotId);
58040
58113
  const newPivotName = this.getters.getPivotName(newPivotId);
58041
- this.dispatch("CREATE_SHEET", {
58114
+ const result = this.dispatch("CREATE_SHEET", {
58042
58115
  sheetId: newSheetId,
58043
58116
  name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
58044
58117
  newPivotName,
@@ -58046,20 +58119,23 @@ stores.inject(MyMetaStore, storeInstance);
58046
58119
  })),
58047
58120
  position,
58048
58121
  });
58049
- this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
58050
- this.dispatch("UPDATE_CELL", {
58051
- sheetId: newSheetId,
58052
- col: 0,
58053
- row: 0,
58054
- content: `=PIVOT(${formulaId})`,
58055
- });
58122
+ if (result.isSuccessful) {
58123
+ this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
58124
+ this.dispatch("UPDATE_CELL", {
58125
+ sheetId: newSheetId,
58126
+ col: 0,
58127
+ row: 0,
58128
+ content: `=PIVOT(${formulaId})`,
58129
+ });
58130
+ }
58056
58131
  }
58057
58132
  getPivotDuplicateSheetName(pivotName) {
58058
58133
  let i = 1;
58059
58134
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
58060
- let name = pivotName;
58135
+ const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
58136
+ let name = sanitizedName;
58061
58137
  while (names.includes(name)) {
58062
- name = `${pivotName} (${i})`;
58138
+ name = `${sanitizedName} (${i})`;
58063
58139
  i++;
58064
58140
  }
58065
58141
  return name;
@@ -58640,9 +58716,12 @@ stores.inject(MyMetaStore, storeInstance);
58640
58716
  "HIDE_COLUMNS_ROWS",
58641
58717
  "UNHIDE_COLUMNS_ROWS",
58642
58718
  "UNFOLD_HEADER_GROUP",
58719
+ "UNGROUP_HEADERS",
58643
58720
  "FOLD_HEADER_GROUP",
58644
58721
  "FOLD_ALL_HEADER_GROUPS",
58645
58722
  "UNFOLD_ALL_HEADER_GROUPS",
58723
+ "FOLD_HEADER_GROUPS_IN_ZONE",
58724
+ "UNFOLD_HEADER_GROUPS_IN_ZONE",
58646
58725
  "CREATE_TABLE",
58647
58726
  "UPDATE_TABLE",
58648
58727
  "UPDATE_FILTER",
@@ -59958,6 +60037,8 @@ stores.inject(MyMetaStore, storeInstance);
59958
60037
  case "UNFOLD_HEADER_GROUP":
59959
60038
  case "FOLD_ALL_HEADER_GROUPS":
59960
60039
  case "UNFOLD_ALL_HEADER_GROUPS":
60040
+ case "FOLD_HEADER_GROUPS_IN_ZONE":
60041
+ case "UNFOLD_HEADER_GROUPS_IN_ZONE":
59961
60042
  this.updateHiddenRows(cmd.sheetId);
59962
60043
  break;
59963
60044
  case "UPDATE_FILTER":
@@ -65904,7 +65985,9 @@ stores.inject(MyMetaStore, storeInstance);
65904
65985
  * The value does not matter, it can be hardcoded.
65905
65986
  */
65906
65987
  const catAxId = 17781237;
65988
+ const secondaryCatAxId = 17781238;
65907
65989
  const valAxId = 88853993;
65990
+ const secondaryValAxId = 88853994;
65908
65991
  function createChart(chart, chartSheetIndex, data) {
65909
65992
  const namespaces = [
65910
65993
  ["xmlns:r", RELATIONSHIP_NSR],
@@ -66220,8 +66303,8 @@ stores.inject(MyMetaStore, storeInstance);
66220
66303
  <!-- each data marker in the series does not have a different color -->
66221
66304
  <c:varyColors val="0"/>
66222
66305
  ${barDataSetNode}
66223
- <c:axId val="${catAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
66224
- <c:axId val="${valAxId + (useRightAxisForBarSerie ? 1 : 0)}" />
66306
+ <c:axId val="${useRightAxisForBarSerie ? secondaryCatAxId : catAxId}" />
66307
+ <c:axId val="${useRightAxisForBarSerie ? secondaryValAxId : valAxId}" />
66225
66308
  </c:barChart>
66226
66309
  ${leftDataSetsNodes.length
66227
66310
  ? escapeXml /*xml*/ `
@@ -66242,21 +66325,21 @@ stores.inject(MyMetaStore, storeInstance);
66242
66325
  <!-- each data marker in the series does not have a different color -->
66243
66326
  <c:varyColors val="0"/>
66244
66327
  ${joinXmlNodes(rightDataSetsNodes)}
66245
- <c:axId val="${catAxId + 1}" />
66246
- <c:axId val="${valAxId + 1}" />
66328
+ <c:axId val="${secondaryCatAxId}" />
66329
+ <c:axId val="${secondaryValAxId}" />
66247
66330
  </c:lineChart>
66248
66331
  `
66249
66332
  : ""}
66250
66333
  ${!useRightAxisForBarSerie || leftDataSetsNodes.length
66251
66334
  ? escapeXml /*xml*/ `
66252
- ${addAx("b", "c:catAx", catAxId + 1, valAxId + 1, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
66253
- ${addAx("r", "c:valAx", valAxId + 1, catAxId + 1, chart.axesDesign?.y1?.title, chart.fontColor)}
66335
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length ? 1 : 0)}
66336
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
66254
66337
  `
66255
66338
  : ""}
66256
66339
  ${useRightAxisForBarSerie || rightDataSetsNodes.length
66257
66340
  ? escapeXml /*xml*/ `
66258
- ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length || !useRightAxisForBarSerie ? 1 : 0)}
66259
- ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
66341
+ ${addAx("b", "c:catAx", secondaryCatAxId, secondaryValAxId, chart.axesDesign?.x?.title, chart.fontColor, leftDataSetsNodes.length || !useRightAxisForBarSerie ? 1 : 0)}
66342
+ ${addAx("r", "c:valAx", secondaryValAxId, secondaryCatAxId, chart.axesDesign?.y1?.title, chart.fontColor)}
66260
66343
  `
66261
66344
  : ""}
66262
66345
  `;
@@ -67277,6 +67360,15 @@ stores.inject(MyMetaStore, storeInstance);
67277
67360
  ["id", i + 1], // id cannot be 0
67278
67361
  ["name", colName],
67279
67362
  ];
67363
+ if (table.config.totalRow) {
67364
+ // Note: To be 100% complete, we could also add a `totalsRowLabel` attribute for total strings, and a tag
67365
+ // `<totalsRowFormula>` for the formula of the total. But those doesn't seem to be mandatory for Excel.
67366
+ const colTotalXc = toXC(tableZone.left + i, tableZone.bottom);
67367
+ const colTotalContent = sheetData.cells[colTotalXc]?.content;
67368
+ if (colTotalContent?.startsWith("=")) {
67369
+ colAttributes.push(["totalsRowFunction", "custom"]);
67370
+ }
67371
+ }
67280
67372
  columns.push(escapeXml /*xml*/ `<tableColumn ${formatAttributes(colAttributes)}/>`);
67281
67373
  }
67282
67374
  return escapeXml /*xml*/ `
@@ -67371,8 +67463,9 @@ stores.inject(MyMetaStore, storeInstance);
67371
67463
  }
67372
67464
  else if (cell.content && cell.content !== "") {
67373
67465
  const isTableHeader = isCellTableHeader(c, r, sheet);
67466
+ const isTableTotal = isCellTableTotal(c, r, sheet);
67374
67467
  const isPlainText = !!(cell.format && data.formats[cell.format] === PLAIN_TEXT_FORMAT);
67375
- ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isPlainText));
67468
+ ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader || isTableTotal || isPlainText));
67376
67469
  }
67377
67470
  attributes.push(...additionalAttrs);
67378
67471
  // prettier-ignore
@@ -67406,6 +67499,16 @@ stores.inject(MyMetaStore, storeInstance);
67406
67499
  return isInside(col, row, headerZone);
67407
67500
  });
67408
67501
  }
67502
+ function isCellTableTotal(col, row, sheet) {
67503
+ return sheet.tables.some((table) => {
67504
+ if (!table.config.totalRow) {
67505
+ return false;
67506
+ }
67507
+ const zone = toZone(table.range);
67508
+ const totalZone = { ...zone, top: zone.bottom };
67509
+ return isInside(col, row, totalZone);
67510
+ });
67511
+ }
67409
67512
  function addHyperlinks(construct, data, sheetIndex) {
67410
67513
  const sheet = data.sheets[sheetIndex];
67411
67514
  const cells = sheet.cells;
@@ -67857,7 +67960,7 @@ stores.inject(MyMetaStore, storeInstance);
67857
67960
  coreHandlers = [];
67858
67961
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
67859
67962
  const start = performance.now();
67860
- console.group("Model creation");
67963
+ console.debug("##### Model creation #####");
67861
67964
  super();
67862
67965
  setDefaultTranslationMethod();
67863
67966
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
@@ -67884,7 +67987,6 @@ stores.inject(MyMetaStore, storeInstance);
67884
67987
  isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
67885
67988
  isDashboard: () => this.config.mode === "dashboard",
67886
67989
  };
67887
- this.uuidGenerator.setIsFastStrategy(true);
67888
67990
  // Initiate stream processor
67889
67991
  this.selection = new SelectionStreamProcessorImpl(this.getters);
67890
67992
  this.coreHandlers.push(this.range);
@@ -67930,16 +68032,16 @@ stores.inject(MyMetaStore, storeInstance);
67930
68032
  this.joinSession();
67931
68033
  if (config.snapshotRequested) {
67932
68034
  const startSnapshot = performance.now();
67933
- console.info("Snapshot requested");
68035
+ console.debug("Snapshot requested");
67934
68036
  this.session.snapshot(this.exportData());
67935
68037
  this.garbageCollectExternalResources();
67936
- console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
68038
+ console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
67937
68039
  }
67938
68040
  // mark all models as "raw", so they will not be turned into reactive objects
67939
68041
  // by owl, since we do not rely on reactivity
67940
68042
  owl.markRaw(this);
67941
- console.info("Model created in", performance.now() - start, "ms");
67942
- console.groupEnd();
68043
+ console.debug("Model created in", performance.now() - start, "ms");
68044
+ console.debug("######");
67943
68045
  }
67944
68046
  joinSession() {
67945
68047
  this.session.join(this.config.client);
@@ -68162,7 +68264,7 @@ stores.inject(MyMetaStore, storeInstance);
68162
68264
  this.finalize();
68163
68265
  const time = performance.now() - start;
68164
68266
  if (time > 5) {
68165
- console.info(type, time, "ms");
68267
+ console.debug(type, time, "ms");
68166
68268
  }
68167
68269
  });
68168
68270
  this.session.save(command, commands, changes);
@@ -68548,9 +68650,9 @@ stores.inject(MyMetaStore, storeInstance);
68548
68650
  exports.tokenize = tokenize;
68549
68651
 
68550
68652
 
68551
- __info__.version = "17.4.8";
68552
- __info__.date = "2024-09-17T08:10:18.777Z";
68553
- __info__.hash = "caad8bd";
68653
+ __info__.version = "17.4.10";
68654
+ __info__.date = "2024-10-24T08:54:56.262Z";
68655
+ __info__.hash = "c82d9c1";
68554
68656
 
68555
68657
 
68556
68658
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);