@odoo/o-spreadsheet 18.0.18 → 18.0.20

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,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.0.18
6
- * @date 2025-03-07T10:38:36.883Z
7
- * @hash 06c9bc5
5
+ * @version 18.0.20
6
+ * @date 2025-03-19T08:21:32.426Z
7
+ * @hash 3f48d8b
8
8
  */
9
9
 
10
10
  'use strict';
@@ -421,7 +421,6 @@ function escapeRegExp(str) {
421
421
  * Sparse arrays remain sparse.
422
422
  */
423
423
  function deepCopy(obj) {
424
- const result = Array.isArray(obj) ? [] : {};
425
424
  switch (typeof obj) {
426
425
  case "object": {
427
426
  if (obj === null) {
@@ -433,8 +432,18 @@ function deepCopy(obj) {
433
432
  else if (!(isPlainObject(obj) || obj instanceof Array)) {
434
433
  throw new Error("Unsupported type: only objects and arrays are supported");
435
434
  }
436
- for (const key in obj) {
437
- result[key] = deepCopy(obj[key]);
435
+ const result = Array.isArray(obj) ? new Array(obj.length) : {};
436
+ if (Array.isArray(obj)) {
437
+ for (let i = 0, len = obj.length; i < len; i++) {
438
+ if (i in obj) {
439
+ result[i] = deepCopy(obj[i]);
440
+ }
441
+ }
442
+ }
443
+ else {
444
+ for (const key in obj) {
445
+ result[key] = deepCopy(obj[key]);
446
+ }
438
447
  }
439
448
  return result;
440
449
  }
@@ -2536,21 +2545,30 @@ function mergeContiguousZones(zones) {
2536
2545
  return mergedZones;
2537
2546
  }
2538
2547
 
2548
+ const globalReverseLookup$1 = new WeakMap();
2549
+ const globalIdCounter = new WeakMap();
2539
2550
  /**
2540
2551
  * Get the id of the given item (its key in the given dictionary).
2541
2552
  * If the given item does not exist in the dictionary, it creates one with a new id.
2542
2553
  */
2543
2554
  function getItemId(item, itemsDic) {
2544
- for (const key in itemsDic) {
2545
- if (deepEquals(itemsDic[key], item)) {
2546
- return parseInt(key, 10);
2547
- }
2555
+ if (!globalReverseLookup$1.has(itemsDic)) {
2556
+ globalReverseLookup$1.set(itemsDic, new Map());
2557
+ globalIdCounter.set(itemsDic, 0);
2558
+ }
2559
+ const reverseLookup = globalReverseLookup$1.get(itemsDic);
2560
+ const canonical = getCanonicalRepresentation(item);
2561
+ if (reverseLookup.has(canonical)) {
2562
+ const id = reverseLookup.get(canonical);
2563
+ itemsDic[id] = item;
2564
+ return id;
2548
2565
  }
2549
2566
  // Generate new Id if the item didn't exist in the dictionary
2550
- const ids = Object.keys(itemsDic);
2551
- const maxId = ids.length === 0 ? 0 : largeMax(ids.map((id) => parseInt(id, 10)));
2552
- itemsDic[maxId + 1] = item;
2553
- return maxId + 1;
2567
+ const newId = globalIdCounter.get(itemsDic) + 1;
2568
+ reverseLookup.set(canonical, newId);
2569
+ globalIdCounter.set(itemsDic, newId);
2570
+ itemsDic[newId] = item;
2571
+ return newId;
2554
2572
  }
2555
2573
  function groupItemIdsByZones(positionsByItemId) {
2556
2574
  const result = {};
@@ -2562,6 +2580,33 @@ function groupItemIdsByZones(positionsByItemId) {
2562
2580
  }
2563
2581
  return result;
2564
2582
  }
2583
+ function getCanonicalRepresentation(item) {
2584
+ if (item === null)
2585
+ return "null";
2586
+ if (item === undefined)
2587
+ return "undefined";
2588
+ if (typeof item !== "object")
2589
+ return String(item);
2590
+ if (Array.isArray(item)) {
2591
+ const len = item.length;
2592
+ let result = "[";
2593
+ for (let i = 0; i < len; i++) {
2594
+ if (i > 0)
2595
+ result += ",";
2596
+ result += getCanonicalRepresentation(item[i]);
2597
+ }
2598
+ return result + "]";
2599
+ }
2600
+ const keys = Object.keys(item).sort();
2601
+ let repr = "{";
2602
+ for (const key of keys) {
2603
+ if (item[key] !== undefined) {
2604
+ repr += `"${key}":${getCanonicalRepresentation(item[key])},`;
2605
+ }
2606
+ }
2607
+ repr += "}";
2608
+ return repr;
2609
+ }
2565
2610
 
2566
2611
  // -----------------------------------------------------------------------------
2567
2612
  // Date Type
@@ -6064,11 +6109,13 @@ function getDefaultCellHeight(ctx, cell, colSize) {
6064
6109
  if (!cell || (!cell.isFormula && !cell.content)) {
6065
6110
  return DEFAULT_CELL_HEIGHT;
6066
6111
  }
6067
- const maxWidth = cell.style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6068
- const numberOfLines = cell.isFormula
6069
- ? 1
6070
- : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
6071
- const fontSize = computeTextFontSizeInPixels(cell.style);
6112
+ const content = cell.isFormula ? "" : cell.content;
6113
+ return getCellContentHeight(ctx, content, cell.style, colSize);
6114
+ }
6115
+ function getCellContentHeight(ctx, content, style, colSize) {
6116
+ const maxWidth = style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
6117
+ const numberOfLines = splitTextToWidth(ctx, content, style, maxWidth).length;
6118
+ const fontSize = computeTextFontSizeInPixels(style);
6072
6119
  return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
6073
6120
  }
6074
6121
  function getDefaultContextFont(fontSize, bold = false, italic = false) {
@@ -8231,13 +8278,6 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
8231
8278
  this.clearClippedZones(content);
8232
8279
  const selection = target[0];
8233
8280
  this.pasteZone(sheetId, selection.left, selection.top, content.cells, options);
8234
- this.dispatch("MOVE_RANGES", {
8235
- target: content.zones,
8236
- sheetId: content.sheetId,
8237
- targetSheetId: sheetId,
8238
- col: selection.left,
8239
- row: selection.top,
8240
- });
8241
8281
  }
8242
8282
  /**
8243
8283
  * Clear the clipped zones: remove the cells and clear the formatting
@@ -8746,14 +8786,15 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
8746
8786
  }
8747
8787
  merges.push(mergesInRow);
8748
8788
  }
8749
- return { merges };
8789
+ return { merges, sheetId };
8750
8790
  }
8751
8791
  /**
8752
8792
  * Paste the clipboard content in the given target
8753
8793
  */
8754
8794
  paste(target, content, options) {
8755
8795
  if (options.isCutOperation) {
8756
- return;
8796
+ const copiedMerges = content.merges.flat().filter(isDefined);
8797
+ this.dispatch("REMOVE_MERGE", { sheetId: content.sheetId, target: copiedMerges });
8757
8798
  }
8758
8799
  this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
8759
8800
  }
@@ -8788,6 +8829,27 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
8788
8829
  }
8789
8830
  }
8790
8831
 
8832
+ class ReferenceClipboardHandler extends AbstractCellClipboardHandler {
8833
+ copy(data) {
8834
+ return {
8835
+ zones: data.clippedZones,
8836
+ sheetId: data.sheetId,
8837
+ };
8838
+ }
8839
+ paste(target, content, options) {
8840
+ if (options.isCutOperation) {
8841
+ const selection = target.zones[0];
8842
+ this.dispatch("MOVE_RANGES", {
8843
+ target: content.zones,
8844
+ sheetId: content.sheetId,
8845
+ targetSheetId: target.sheetId,
8846
+ col: selection.left,
8847
+ row: selection.top,
8848
+ });
8849
+ }
8850
+ }
8851
+ }
8852
+
8791
8853
  class SheetClipboardHandler extends AbstractCellClipboardHandler {
8792
8854
  isPasteAllowed(sheetId, target, content, options) {
8793
8855
  if (!("cells" in content)) {
@@ -8955,7 +9017,8 @@ clipboardHandlersRegistries.cellHandlers
8955
9017
  .add("merge", MergeClipboardHandler)
8956
9018
  .add("border", BorderClipboardHandler)
8957
9019
  .add("table", TableClipboardHandler)
8958
- .add("conditionalFormat", ConditionalFormatClipboardHandler);
9020
+ .add("conditionalFormat", ConditionalFormatClipboardHandler)
9021
+ .add("references", ReferenceClipboardHandler);
8959
9022
 
8960
9023
  function transformZone(zone, executed) {
8961
9024
  if (executed.type === "REMOVE_COLUMNS_ROWS") {
@@ -11564,16 +11627,25 @@ function addRelsToFile(relsFiles, path, rel) {
11564
11627
  }
11565
11628
  return id;
11566
11629
  }
11630
+ const globalReverseLookup = new WeakMap();
11567
11631
  function pushElement(property, propertyList) {
11568
- let len = propertyList.length;
11569
- const operator = typeof property === "object" ? deepEquals : (a, b) => a === b;
11570
- for (let i = 0; i < len; i++) {
11571
- if (operator(property, propertyList[i])) {
11572
- return i;
11632
+ let reverseLookup = globalReverseLookup.get(propertyList);
11633
+ if (!reverseLookup) {
11634
+ reverseLookup = new Map();
11635
+ for (let i = 0; i < propertyList.length; i++) {
11636
+ const canonical = getCanonicalRepresentation(propertyList[i]);
11637
+ reverseLookup.set(canonical, i);
11573
11638
  }
11639
+ globalReverseLookup.set(propertyList, reverseLookup);
11640
+ }
11641
+ const canonical = getCanonicalRepresentation(property);
11642
+ if (reverseLookup.has(canonical)) {
11643
+ return reverseLookup.get(canonical);
11574
11644
  }
11575
- propertyList[propertyList.length] = property;
11576
- return propertyList.length - 1;
11645
+ const maxId = propertyList.length;
11646
+ propertyList.push(property);
11647
+ reverseLookup.set(canonical, maxId);
11648
+ return maxId;
11577
11649
  }
11578
11650
  const chartIds = [];
11579
11651
  /**
@@ -13426,7 +13498,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
13426
13498
  title: { text: chartTitle },
13427
13499
  type: CHART_TYPE_CONVERSION_MAP[chartType],
13428
13500
  dataSets: this.extractChartDatasets(this.querySelectorAll(rootChartElement, `c:${chartType}`), chartType),
13429
- labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
13501
+ labelRange: this.extractLabelRange(chartType, rootChartElement),
13430
13502
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
13431
13503
  default: "ffffff",
13432
13504
  }).asString(),
@@ -13438,6 +13510,13 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
13438
13510
  };
13439
13511
  })[0];
13440
13512
  }
13513
+ extractLabelRange(chartType, rootChartElement) {
13514
+ if (chartType === "scatterChart") {
13515
+ return (this.extractChildTextContent(rootChartElement, `c:ser c:strRef c:f`) ||
13516
+ this.extractChildTextContent(rootChartElement, `c:ser c:numRef c:f`));
13517
+ }
13518
+ return this.extractChildTextContent(rootChartElement, `c:ser c:cat c:f`);
13519
+ }
13441
13520
  extractComboChart(chartElement) {
13442
13521
  // Title can be separated into multiple xml elements (for styling and such), we only import the text
13443
13522
  const chartTitle = this.mapOnElements({ parent: chartElement, query: "c:title a:t" }, (textElement) => {
@@ -16899,6 +16978,7 @@ class ScorecardChart extends owl.Component {
16899
16978
  const autoCompleteProviders = new Registry();
16900
16979
 
16901
16980
  autoCompleteProviders.add("dataValidation", {
16981
+ displayAllOnInitialContent: true,
16902
16982
  getProposals(tokenAtCursor, content) {
16903
16983
  if (content.startsWith("=")) {
16904
16984
  return [];
@@ -28375,7 +28455,7 @@ autoCompleteProviders.add("pivot_group_values", {
28375
28455
  text,
28376
28456
  description: usedLabel,
28377
28457
  htmlContent: [{ value: text, color }],
28378
- fuzzySearchKey: value + usedLabel,
28458
+ fuzzySearchKey: text + usedLabel,
28379
28459
  };
28380
28460
  });
28381
28461
  },
@@ -38778,8 +38858,8 @@ class AbstractComposerStore extends SpreadsheetStore {
38778
38858
  this.updateRangeColor();
38779
38859
  }
38780
38860
  cancelEdition() {
38781
- this.cancelEditionAndActivateSheet();
38782
38861
  this.resetContent();
38862
+ this.cancelEditionAndActivateSheet();
38783
38863
  }
38784
38864
  setCurrentContent(content, selection) {
38785
38865
  if (selection && !this.isSelectionValid(content.length, selection.start, selection.end)) {
@@ -38795,8 +38875,8 @@ class AbstractComposerStore extends SpreadsheetStore {
38795
38875
  switch (cmd.type) {
38796
38876
  case "SELECT_FIGURE":
38797
38877
  if (cmd.id) {
38798
- this.cancelEditionAndActivateSheet();
38799
38878
  this.resetContent();
38879
+ this.cancelEditionAndActivateSheet();
38800
38880
  }
38801
38881
  break;
38802
38882
  case "START_CHANGE_HIGHLIGHT":
@@ -39147,6 +39227,15 @@ class AbstractComposerStore extends SpreadsheetStore {
39147
39227
  const exactMatch = proposals?.find((p) => p.text === tokenAtCursor.value);
39148
39228
  // remove tokens that are likely to be other parts of the formula that slipped in the token if it's a string
39149
39229
  const searchTerm = tokenAtCursor.value.replace(/[ ,\(\)]/g, "");
39230
+ if (this._currentContent === this.initialContent &&
39231
+ provider.displayAllOnInitialContent &&
39232
+ proposals?.length) {
39233
+ return {
39234
+ proposals,
39235
+ selectProposal: provider.selectProposal,
39236
+ autoSelectFirstProposal: provider.autoSelectFirstProposal ?? false,
39237
+ };
39238
+ }
39150
39239
  if (exactMatch && this._currentContent !== this.initialContent) {
39151
39240
  // this means the user has chosen a proposal
39152
39241
  return;
@@ -44682,6 +44771,7 @@ class RemoveDuplicatesPanel extends owl.Component {
44682
44771
  columns: {},
44683
44772
  });
44684
44773
  setup() {
44774
+ this.updateColumns();
44685
44775
  owl.onWillUpdateProps(() => this.updateColumns());
44686
44776
  }
44687
44777
  toggleHasHeader() {
@@ -46470,8 +46560,8 @@ class CellComposerStore extends AbstractComposerStore {
46470
46560
  const sheetIdExists = !!this.getters.tryGetSheet(this.sheetId);
46471
46561
  if (!sheetIdExists && this.editionMode !== "inactive") {
46472
46562
  this.sheetId = this.getters.getActiveSheetId();
46473
- this.cancelEditionAndActivateSheet();
46474
46563
  this.resetContent();
46564
+ this.cancelEditionAndActivateSheet();
46475
46565
  this.notificationStore.raiseError(CELL_DELETED_MESSAGE);
46476
46566
  }
46477
46567
  break;
@@ -62827,12 +62917,7 @@ class SheetUIPlugin extends UIPlugin {
62827
62917
  }
62828
62918
  break;
62829
62919
  case "AUTORESIZE_ROWS":
62830
- this.dispatch("RESIZE_COLUMNS_ROWS", {
62831
- elements: cmd.rows,
62832
- dimension: "ROW",
62833
- size: null,
62834
- sheetId: cmd.sheetId,
62835
- });
62920
+ this.autoResizeRows(cmd.sheetId, cmd.rows);
62836
62921
  break;
62837
62922
  }
62838
62923
  }
@@ -62997,6 +63082,48 @@ class SheetUIPlugin extends UIPlugin {
62997
63082
  }
62998
63083
  return "Success" /* CommandResult.Success */;
62999
63084
  }
63085
+ autoResizeRows(sheetId, rows) {
63086
+ const rowSizes = [];
63087
+ for (const row of rows) {
63088
+ let evaluatedRowSize = 0;
63089
+ for (const cellId of this.getters.getRowCells(sheetId, row)) {
63090
+ const cell = this.getters.getCellById(cellId);
63091
+ if (!cell) {
63092
+ continue;
63093
+ }
63094
+ const position = this.getters.getCellPosition(cell.id);
63095
+ const colSize = this.getters.getColSize(sheetId, position.col);
63096
+ if (cell.isFormula) {
63097
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
63098
+ const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
63099
+ if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
63100
+ evaluatedRowSize = evaluatedSize;
63101
+ }
63102
+ }
63103
+ else {
63104
+ const content = cell.content;
63105
+ const dynamicRowSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
63106
+ // Only keep the size of evaluated cells if it's bigger than the dynamic row size
63107
+ if (dynamicRowSize >= evaluatedRowSize && dynamicRowSize > DEFAULT_CELL_HEIGHT) {
63108
+ evaluatedRowSize = 0;
63109
+ }
63110
+ }
63111
+ }
63112
+ rowSizes.push(evaluatedRowSize || null);
63113
+ }
63114
+ const groupedSizes = new Map(rowSizes.map((size) => [size, []]));
63115
+ for (let i = 0; i < rowSizes.length; i++) {
63116
+ groupedSizes.get(rowSizes[i])?.push(rows[i]);
63117
+ }
63118
+ for (const [size, rows] of groupedSizes) {
63119
+ this.dispatch("RESIZE_COLUMNS_ROWS", {
63120
+ elements: rows,
63121
+ dimension: "ROW",
63122
+ size,
63123
+ sheetId,
63124
+ });
63125
+ }
63126
+ }
63000
63127
  }
63001
63128
 
63002
63129
  class TableComputedStylePlugin extends UIPlugin {
@@ -72016,7 +72143,7 @@ function addStyles(styles) {
72016
72143
  }
72017
72144
  if (alignAttrs.length > 0) {
72018
72145
  attributes.push(["applyAlignment", "1"]); // for Libre Office
72019
- styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)}>${escapeXml /*xml*/ `<alignment ${formatAttributes(alignAttrs)} />`}</xf> `);
72146
+ styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)}><alignment ${formatAttributes(alignAttrs)} /></xf> `);
72020
72147
  }
72021
72148
  else {
72022
72149
  styleNodes.push(escapeXml /*xml*/ `<xf ${formatAttributes(attributes)} />`);
@@ -73480,6 +73607,6 @@ exports.tokenColors = tokenColors;
73480
73607
  exports.tokenize = tokenize;
73481
73608
 
73482
73609
 
73483
- __info__.version = "18.0.18";
73484
- __info__.date = "2025-03-07T10:38:36.883Z";
73485
- __info__.hash = "06c9bc5";
73610
+ __info__.version = "18.0.20";
73611
+ __info__.date = "2025-03-19T08:21:32.426Z";
73612
+ __info__.hash = "3f48d8b";
@@ -5276,6 +5276,7 @@ declare class SheetUIPlugin extends UIPlugin {
5276
5276
  * not outside the sheet.
5277
5277
  */
5278
5278
  private checkZonesAreInSheet;
5279
+ private autoResizeRows;
5279
5280
  }
5280
5281
 
5281
5282
  declare class HeaderPositionsUIPlugin extends UIPlugin {
@@ -8211,11 +8212,12 @@ interface ComposerStoreInterface {
8211
8212
  * We declare the providers in the registry as an object (rather than a class)
8212
8213
  * to allow a type-safe way to declare the provider.
8213
8214
  * We still want to be able to use `this` for the getters and dispatch for simplicity.
8214
- * Binding happens at runtime in the edition plugin.
8215
+ * Binding happens at runtime in the composer store.
8215
8216
  */
8216
8217
  interface AutoCompleteProviderDefinition {
8217
8218
  sequence?: number;
8218
8219
  autoSelectFirstProposal?: boolean;
8220
+ displayAllOnInitialContent?: boolean;
8219
8221
  maxDisplayedProposals?: number;
8220
8222
  getProposals(this: {
8221
8223
  composer: ComposerStoreInterface;