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