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