@odoo/o-spreadsheet 17.1.4 → 17.1.5

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 17.1.4
6
- * @date 2024-02-09T13:09:13.430Z
7
- * @hash 16d3ece
5
+ * @version 17.1.5
6
+ * @date 2024-02-16T14:23:54.651Z
7
+ * @hash 29d8ad6
8
8
  */
9
9
 
10
10
  import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
@@ -451,8 +451,8 @@ function isObjectEmptyRecursive(argument) {
451
451
  * If the given item does not exist in the dictionary, it creates one with a new id.
452
452
  */
453
453
  function getItemId(item, itemsDic) {
454
- for (let [key, value] of Object.entries(itemsDic)) {
455
- if (deepEquals(value, item)) {
454
+ for (const key in itemsDic) {
455
+ if (deepEquals(itemsDic[key], item)) {
456
456
  return parseInt(key, 10);
457
457
  }
458
458
  }
@@ -551,11 +551,13 @@ function deepEquals(o1, o2) {
551
551
  if (typeof o1 !== "object")
552
552
  return o1 === o2;
553
553
  // Objects can have different keys if the values are undefined
554
- const keys = new Set();
555
- Object.keys(o1).forEach((key) => keys.add(key));
556
- Object.keys(o2).forEach((key) => keys.add(key));
557
- for (let key of keys) {
558
- if (typeof o1[key] !== typeof o1[key])
554
+ for (const key in o2) {
555
+ if (!(key in o1) && o2[key] !== undefined) {
556
+ return false;
557
+ }
558
+ }
559
+ for (const key in o1) {
560
+ if (typeof o1[key] !== typeof o2[key])
559
561
  return false;
560
562
  if (typeof o1[key] === "object") {
561
563
  if (!deepEquals(o1[key], o2[key]))
@@ -625,18 +627,18 @@ function isConsecutive(iterable) {
625
627
  return true;
626
628
  }
627
629
  class JetSet extends Set {
628
- add(...iterable) {
630
+ addMany(iterable) {
629
631
  for (const element of iterable) {
630
632
  super.add(element);
631
633
  }
632
634
  return this;
633
635
  }
634
- delete(...iterable) {
635
- let deleted = false;
636
+ deleteMany(iterable) {
637
+ let wasDeleted = false;
636
638
  for (const element of iterable) {
637
- deleted ||= super.delete(element);
639
+ wasDeleted ||= super.delete(element);
638
640
  }
639
- return deleted;
641
+ return wasDeleted;
640
642
  }
641
643
  }
642
644
  /**
@@ -1934,7 +1936,7 @@ class DispatchResult {
1934
1936
  * Static helper which returns a successful DispatchResult
1935
1937
  */
1936
1938
  static get Success() {
1937
- return new DispatchResult();
1939
+ return SUCCESS;
1938
1940
  }
1939
1941
  get isSuccessful() {
1940
1942
  return this.reasons.length === 0;
@@ -1947,6 +1949,7 @@ class DispatchResult {
1947
1949
  return this.reasons.includes(reason);
1948
1950
  }
1949
1951
  }
1952
+ const SUCCESS = new DispatchResult();
1950
1953
  var CommandResult;
1951
1954
  (function (CommandResult) {
1952
1955
  CommandResult["Success"] = "Success";
@@ -7653,6 +7656,9 @@ function isValidLocale(locale) {
7653
7656
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7654
7657
  return false;
7655
7658
  }
7659
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7660
+ return false;
7661
+ }
7656
7662
  try {
7657
7663
  formatValue(1, { locale, format: "#,##0.00" });
7658
7664
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7743,7 +7749,7 @@ function canonicalizeNumberLiteral(content, locale) {
7743
7749
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7744
7750
  return content;
7745
7751
  }
7746
- return content.replace(locale.decimalSeparator, ".");
7752
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7747
7753
  }
7748
7754
  /**
7749
7755
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -10838,6 +10844,12 @@ const INSERT_LINK = (env) => {
10838
10844
  let { col, row } = env.model.getters.getActivePosition();
10839
10845
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10840
10846
  };
10847
+ const INSERT_LINK_NAME = (env) => {
10848
+ const sheetId = env.model.getters.getActiveSheetId();
10849
+ const { col, row } = env.model.getters.getActivePosition();
10850
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10851
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10852
+ };
10841
10853
  //------------------------------------------------------------------------------
10842
10854
  // Filters action
10843
10855
  //------------------------------------------------------------------------------
@@ -20082,7 +20094,7 @@ cellMenuRegistry
20082
20094
  })
20083
20095
  .add("insert_link", {
20084
20096
  ...insertLink,
20085
- name: _t("Insert link"),
20097
+ name: INSERT_LINK_NAME,
20086
20098
  sequence: 150,
20087
20099
  separator: true,
20088
20100
  });
@@ -26525,6 +26537,17 @@ class TextValueProvider extends Component {
26525
26537
  onValueSelected: Function,
26526
26538
  onValueHovered: Function,
26527
26539
  };
26540
+ autoCompleteListRef = useRef("autoCompleteList");
26541
+ setup() {
26542
+ useEffect(() => {
26543
+ const selectedIndex = this.props.selectedIndex;
26544
+ if (selectedIndex === undefined) {
26545
+ return;
26546
+ }
26547
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26548
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26549
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26550
+ }
26528
26551
  }
26529
26552
 
26530
26553
  class ContentEditableHelper {
@@ -26969,6 +26992,7 @@ css /* scss */ `
26969
26992
  position: absolute;
26970
26993
  margin: 1px 4px;
26971
26994
  pointer-events: none;
26995
+ overflow: auto;
26972
26996
 
26973
26997
  .o-semi-bold {
26974
26998
  /** FIXME: to remove in favor of Bootstrap
@@ -27026,7 +27050,10 @@ class Composer extends Component {
27026
27050
  if (this.props.delimitation && this.props.rect) {
27027
27051
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
27028
27052
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
27053
+ assistantStyle["max-height"] = `${remainingHeight}px`;
27029
27054
  if (cellY > remainingHeight) {
27055
+ const availableSpaceAbove = cellY;
27056
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
27030
27057
  // render top
27031
27058
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
27032
27059
  assistantStyle.top = `-3px`;
@@ -35145,22 +35172,7 @@ class BordersPlugin extends CorePlugin {
35145
35172
  }
35146
35173
  }
35147
35174
  export(data) {
35148
- // Borders
35149
- let borderId = 0;
35150
35175
  const borders = {};
35151
- /**
35152
- * Get the id of the given border. If the border does not exist, it creates
35153
- * one.
35154
- */
35155
- function getBorderId(border) {
35156
- for (let [key, value] of Object.entries(borders)) {
35157
- if (deepEquals(value, border)) {
35158
- return parseInt(key, 10);
35159
- }
35160
- }
35161
- borders[++borderId] = border;
35162
- return borderId;
35163
- }
35164
35176
  for (let sheet of data.sheets) {
35165
35177
  for (let col = 0; col < sheet.colNumber; col++) {
35166
35178
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -35168,7 +35180,7 @@ class BordersPlugin extends CorePlugin {
35168
35180
  if (border) {
35169
35181
  const xc = toXC(col, row);
35170
35182
  const cell = sheet.cells[xc];
35171
- const borderId = getBorderId(border);
35183
+ const borderId = getItemId(border, borders);
35172
35184
  if (cell) {
35173
35185
  cell.border = borderId;
35174
35186
  }
@@ -35957,9 +35969,9 @@ class CellPlugin extends CorePlugin {
35957
35969
  allowDispatch(cmd) {
35958
35970
  switch (cmd.type) {
35959
35971
  case "UPDATE_CELL":
35960
- return this.checkCellOutOfSheet(cmd);
35972
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35961
35973
  case "CLEAR_CELL":
35962
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35974
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35963
35975
  default:
35964
35976
  return "Success" /* CommandResult.Success */;
35965
35977
  }
@@ -36109,8 +36121,8 @@ class CellPlugin extends CorePlugin {
36109
36121
  }
36110
36122
  removeDefaultStyleValues(style) {
36111
36123
  const cleanedStyle = { ...style };
36112
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
36113
- if (cleanedStyle[property] === defaultValue) {
36124
+ for (const property in DEFAULT_STYLE) {
36125
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
36114
36126
  delete cleanedStyle[property];
36115
36127
  }
36116
36128
  }
@@ -36266,10 +36278,7 @@ class CellPlugin extends CorePlugin {
36266
36278
  else {
36267
36279
  style = before ? before.style : undefined;
36268
36280
  }
36269
- const locale = this.getters.getLocale();
36270
- let format = ("format" in after ? after.format : before && before.format) ||
36271
- detectDateFormat(afterContent, locale) ||
36272
- detectNumberFormat(afterContent);
36281
+ const format = "format" in after ? after.format : before && before.format;
36273
36282
  /* Read the following IF as:
36274
36283
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
36275
36284
  * - the command says the new content is empty and has no border/format/style
@@ -36310,6 +36319,7 @@ class CellPlugin extends CorePlugin {
36310
36319
  }
36311
36320
  createLiteralCell(id, content, format, style) {
36312
36321
  const locale = this.getters.getLocale();
36322
+ format = format || detectDateFormat(content, locale) || detectNumberFormat(content);
36313
36323
  if (format !== PLAIN_TEXT_FORMAT && !isEvaluationError(content)) {
36314
36324
  content = toString(parseLiteral(content, locale));
36315
36325
  }
@@ -36382,6 +36392,18 @@ class CellPlugin extends CorePlugin {
36382
36392
  }
36383
36393
  return "Success" /* CommandResult.Success */;
36384
36394
  }
36395
+ checkUselessUpdateCell(cmd) {
36396
+ const cell = this.getters.getCell(cmd);
36397
+ const hasContent = "content" in cmd || "formula" in cmd;
36398
+ const hasStyle = "style" in cmd;
36399
+ const hasFormat = "format" in cmd;
36400
+ if ((!hasContent || cell?.content === cmd.content) &&
36401
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
36402
+ (!hasFormat || cell?.format === cmd.format)) {
36403
+ return "NoChanges" /* CommandResult.NoChanges */;
36404
+ }
36405
+ return "Success" /* CommandResult.Success */;
36406
+ }
36385
36407
  }
36386
36408
  class FormulaCellWithDependencies {
36387
36409
  id;
@@ -41166,7 +41188,15 @@ class SpreadsheetRTree {
41166
41188
  if (!this.rTrees[sheetId]) {
41167
41189
  return;
41168
41190
  }
41169
- this.rTrees[sheetId].remove(item, deepEquals);
41191
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
41192
+ }
41193
+ rtreeItemComparer(left, right) {
41194
+ return (left.data == right.data &&
41195
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
41196
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
41197
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
41198
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
41199
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
41170
41200
  }
41171
41201
  }
41172
41202
  /**
@@ -41244,7 +41274,7 @@ class FormulaDependencyGraph {
41244
41274
  const queue = Array.from(ranges).reverse();
41245
41275
  while (queue.length > 0) {
41246
41276
  const range = queue.pop();
41247
- visited.add(...this.encoder.encodeBoundingBox(range));
41277
+ visited.addMany(this.encoder.encodeBoundingBox(range));
41248
41278
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41249
41279
  for (const positionId of impactedPositionIds) {
41250
41280
  if (!visited.has(positionId)) {
@@ -41252,7 +41282,7 @@ class FormulaDependencyGraph {
41252
41282
  }
41253
41283
  }
41254
41284
  }
41255
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41285
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41256
41286
  return visited;
41257
41287
  }
41258
41288
  }
@@ -41392,9 +41422,9 @@ class Evaluator {
41392
41422
  const cells = positions.map((p) => this.encoder.encode(p));
41393
41423
  const cellsToCompute = new JetSet(cells);
41394
41424
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
41395
- cellsToCompute.add(...this.getCellsDependingOn(cells));
41396
- cellsToCompute.add(...arrayFormulasPositionIds);
41397
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
41425
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
41426
+ cellsToCompute.addMany(arrayFormulasPositionIds);
41427
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
41398
41428
  this.evaluate(cellsToCompute);
41399
41429
  }
41400
41430
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -41408,7 +41438,7 @@ class Evaluator {
41408
41438
  }
41409
41439
  if (!content) {
41410
41440
  // The previous content could have blocked some array formulas
41411
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41441
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41412
41442
  }
41413
41443
  }
41414
41444
  return impactedPositionIds;
@@ -41460,7 +41490,7 @@ class Evaluator {
41460
41490
  }
41461
41491
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41462
41492
  const cells = new JetSet(arrayFormulas);
41463
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41493
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
41464
41494
  return cells;
41465
41495
  }
41466
41496
  nextPositionsToUpdate = new JetSet();
@@ -41598,7 +41628,7 @@ class Evaluator {
41598
41628
  this.setEvaluatedCell(positionId, evaluatedCell);
41599
41629
  // check if formula dependencies present in the spread zone
41600
41630
  // if so, they need to be recomputed
41601
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41631
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41602
41632
  };
41603
41633
  }
41604
41634
  invalidateSpreading(positionId) {
@@ -41613,8 +41643,8 @@ class Evaluator {
41613
41643
  continue;
41614
41644
  }
41615
41645
  this.evaluatedCells.delete(child);
41616
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41617
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41646
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41647
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41618
41648
  }
41619
41649
  this.spreadingRelations.removeNode(positionId);
41620
41650
  }
@@ -45483,7 +45513,7 @@ class FindAndReplacePlugin extends UIPlugin {
45483
45513
  }
45484
45514
  finalize() {
45485
45515
  if (this.isSearchDirty) {
45486
- this.refreshSearch();
45516
+ this.refreshSearch(false);
45487
45517
  this.isSearchDirty = false;
45488
45518
  }
45489
45519
  }
@@ -45527,10 +45557,10 @@ class FindAndReplacePlugin extends UIPlugin {
45527
45557
  /**
45528
45558
  * refresh the matches according to the current search options
45529
45559
  */
45530
- refreshSearch() {
45560
+ refreshSearch(jumpToMatchSheet = true) {
45531
45561
  this.selectedMatchIndex = null;
45532
45562
  this.findMatches();
45533
- this.selectNextCell(Direction.current);
45563
+ this.selectNextCell(Direction.current, jumpToMatchSheet);
45534
45564
  }
45535
45565
  /**
45536
45566
  * Updates the regex based on the current searchOptions and
@@ -45612,7 +45642,7 @@ class FindAndReplacePlugin extends UIPlugin {
45612
45642
  * It is also used to keep coherence between the selected searchMatch
45613
45643
  * and selectedMatchIndex.
45614
45644
  */
45615
- selectNextCell(indexChange) {
45645
+ selectNextCell(indexChange, jumpToMatchSheet = true) {
45616
45646
  const matches = this.searchMatches;
45617
45647
  if (!matches.length) {
45618
45648
  this.selectedMatchIndex = null;
@@ -45638,7 +45668,7 @@ class FindAndReplacePlugin extends UIPlugin {
45638
45668
  this.selectedMatchIndex = nextIndex;
45639
45669
  const selectedMatch = matches[nextIndex];
45640
45670
  // Switch to the sheet where the match is located
45641
- if (this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45671
+ if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45642
45672
  this.dispatch("ACTIVATE_SHEET", {
45643
45673
  sheetIdFrom: this.getters.getActiveSheetId(),
45644
45674
  sheetIdTo: selectedMatch.sheetId,
@@ -47210,7 +47240,7 @@ class SheetUIPlugin extends UIPlugin {
47210
47240
  "getCellText",
47211
47241
  "getCellMultiLineText",
47212
47242
  "getContiguousZone",
47213
- "isCellEmpty",
47243
+ "isEvaluatedCellEmpty",
47214
47244
  ];
47215
47245
  ctx = document.createElement("canvas").getContext("2d");
47216
47246
  // ---------------------------------------------------------------------------
@@ -47338,14 +47368,23 @@ class SheetUIPlugin extends UIPlugin {
47338
47368
  return zone;
47339
47369
  }
47340
47370
  /**
47341
- * Check if a cell is empty. If the cell is part of a merge,
47342
- * check if the merge containing the cell is empty.
47371
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
47372
+ * the check applies to the main cell of the merge.
47343
47373
  */
47344
- isCellEmpty(position) {
47374
+ isEvaluatedCellEmpty(position) {
47345
47375
  const mainPosition = this.getters.getMainCellPosition(position);
47346
47376
  const cell = this.getters.getEvaluatedCell(mainPosition);
47347
47377
  return cell.type === CellValueType.empty;
47348
47378
  }
47379
+ /**
47380
+ * Checks if a cell is empty (i.e. does not have a content or a formula does not spread over it).
47381
+ * If the cell is part of a merge, the check applies to the main cell of the merge.
47382
+ */
47383
+ isCellEmpty(position) {
47384
+ const mainPosition = this.getters.getMainCellPosition(position);
47385
+ return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
47386
+ this.getters.getCell(mainPosition)?.content);
47387
+ }
47349
47388
  getColMaxWidth(sheetId, index) {
47350
47389
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
47351
47390
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
@@ -54887,8 +54926,8 @@ class SelectionStreamProcessorImpl {
54887
54926
  let currentPosition = startPosition;
54888
54927
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54889
54928
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54890
- let mode = !this.getters.isCellEmpty({ ...currentPosition, sheetId }) &&
54891
- !this.getters.isCellEmpty({ ...nextCellPosition, sheetId })
54929
+ let mode = !this.getters.isEvaluatedCellEmpty({ ...currentPosition, sheetId }) &&
54930
+ !this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId })
54892
54931
  ? "endOfCluster"
54893
54932
  : "nextCluster";
54894
54933
  while (true) {
@@ -54898,7 +54937,7 @@ class SelectionStreamProcessorImpl {
54898
54937
  currentPosition.row === nextCellPosition.row) {
54899
54938
  break;
54900
54939
  }
54901
- const isNextCellEmpty = this.getters.isCellEmpty({ ...nextCellPosition, sheetId });
54940
+ const isNextCellEmpty = this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId });
54902
54941
  if (mode === "endOfCluster" && isNextCellEmpty) {
54903
54942
  break;
54904
54943
  }
@@ -57147,6 +57186,6 @@ const constants = {
57147
57186
  export { AbstractChart, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
57148
57187
 
57149
57188
 
57150
- __info__.version = "17.1.4";
57151
- __info__.date = "2024-02-09T13:09:13.430Z";
57152
- __info__.hash = "16d3ece";
57189
+ __info__.version = "17.1.5";
57190
+ __info__.date = "2024-02-16T14:23:54.651Z";
57191
+ __info__.hash = "29d8ad6";