@odoo/o-spreadsheet 17.1.4 → 17.1.7

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.7
6
+ * @date 2024-03-12T07:56:10.783Z
7
+ * @hash 3a9f334
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -452,8 +452,8 @@
452
452
  * If the given item does not exist in the dictionary, it creates one with a new id.
453
453
  */
454
454
  function getItemId(item, itemsDic) {
455
- for (let [key, value] of Object.entries(itemsDic)) {
456
- if (deepEquals(value, item)) {
455
+ for (const key in itemsDic) {
456
+ if (deepEquals(itemsDic[key], item)) {
457
457
  return parseInt(key, 10);
458
458
  }
459
459
  }
@@ -552,11 +552,13 @@
552
552
  if (typeof o1 !== "object")
553
553
  return o1 === o2;
554
554
  // Objects can have different keys if the values are undefined
555
- const keys = new Set();
556
- Object.keys(o1).forEach((key) => keys.add(key));
557
- Object.keys(o2).forEach((key) => keys.add(key));
558
- for (let key of keys) {
559
- if (typeof o1[key] !== typeof o1[key])
555
+ for (const key in o2) {
556
+ if (!(key in o1) && o2[key] !== undefined) {
557
+ return false;
558
+ }
559
+ }
560
+ for (const key in o1) {
561
+ if (typeof o1[key] !== typeof o2[key])
560
562
  return false;
561
563
  if (typeof o1[key] === "object") {
562
564
  if (!deepEquals(o1[key], o2[key]))
@@ -626,18 +628,18 @@
626
628
  return true;
627
629
  }
628
630
  class JetSet extends Set {
629
- add(...iterable) {
631
+ addMany(iterable) {
630
632
  for (const element of iterable) {
631
633
  super.add(element);
632
634
  }
633
635
  return this;
634
636
  }
635
- delete(...iterable) {
636
- let deleted = false;
637
+ deleteMany(iterable) {
638
+ let wasDeleted = false;
637
639
  for (const element of iterable) {
638
- deleted ||= super.delete(element);
640
+ wasDeleted ||= super.delete(element);
639
641
  }
640
- return deleted;
642
+ return wasDeleted;
641
643
  }
642
644
  }
643
645
  /**
@@ -1935,7 +1937,7 @@
1935
1937
  * Static helper which returns a successful DispatchResult
1936
1938
  */
1937
1939
  static get Success() {
1938
- return new DispatchResult();
1940
+ return SUCCESS;
1939
1941
  }
1940
1942
  get isSuccessful() {
1941
1943
  return this.reasons.length === 0;
@@ -1948,6 +1950,7 @@
1948
1950
  return this.reasons.includes(reason);
1949
1951
  }
1950
1952
  }
1953
+ const SUCCESS = new DispatchResult();
1951
1954
  exports.CommandResult = void 0;
1952
1955
  (function (CommandResult) {
1953
1956
  CommandResult["Success"] = "Success";
@@ -6096,8 +6099,10 @@
6096
6099
  if (x === cell) {
6097
6100
  found = true;
6098
6101
  }
6099
- const cellValue = evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
6100
- if (filter(cellValue)) {
6102
+ const cellValue = x?.isFormula
6103
+ ? undefined
6104
+ : evaluateLiteral(x?.content, { locale: DEFAULT_LOCALE });
6105
+ if (cellValue && filter(cellValue)) {
6101
6106
  group.push(cellValue);
6102
6107
  }
6103
6108
  else {
@@ -6321,11 +6326,14 @@
6321
6326
  * Transform CSS properties into a CSS string.
6322
6327
  */
6323
6328
  function cssPropertiesToCss(attributes) {
6324
- const str = Object.entries(attributes)
6325
- .filter(([attName, attValue]) => attValue !== undefined)
6326
- .map(([attName, attValue]) => `${attName}:${attValue};`)
6327
- .join(" ");
6328
- return str;
6329
+ let styleStr = "";
6330
+ for (const attName in attributes) {
6331
+ if (!attributes[attName]) {
6332
+ continue;
6333
+ }
6334
+ styleStr += `${attName}:${attributes[attName]}; `;
6335
+ }
6336
+ return styleStr;
6329
6337
  }
6330
6338
  function getElementMargins(el) {
6331
6339
  const style = window.getComputedStyle(el);
@@ -7654,6 +7662,9 @@
7654
7662
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7655
7663
  return false;
7656
7664
  }
7665
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7666
+ return false;
7667
+ }
7657
7668
  try {
7658
7669
  formatValue(1, { locale, format: "#,##0.00" });
7659
7670
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7744,7 +7755,7 @@
7744
7755
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7745
7756
  return content;
7746
7757
  }
7747
- return content.replace(locale.decimalSeparator, ".");
7758
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7748
7759
  }
7749
7760
  /**
7750
7761
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -10839,6 +10850,12 @@
10839
10850
  let { col, row } = env.model.getters.getActivePosition();
10840
10851
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10841
10852
  };
10853
+ const INSERT_LINK_NAME = (env) => {
10854
+ const sheetId = env.model.getters.getActiveSheetId();
10855
+ const { col, row } = env.model.getters.getActivePosition();
10856
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10857
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10858
+ };
10842
10859
  //------------------------------------------------------------------------------
10843
10860
  // Filters action
10844
10861
  //------------------------------------------------------------------------------
@@ -13040,6 +13057,18 @@
13040
13057
  },
13041
13058
  isExported: true,
13042
13059
  };
13060
+ // -----------------------------------------------------------------------------
13061
+ // INT
13062
+ // -----------------------------------------------------------------------------
13063
+ const INT = {
13064
+ description: _t("Rounds a number down to the nearest integer that is less than or equal to it."),
13065
+ args: [arg("value (number)", _t("The number to round down to the nearest integer."))],
13066
+ returns: ["NUMBER"],
13067
+ compute: function (value) {
13068
+ return Math.floor(toNumber(value, this.locale));
13069
+ },
13070
+ isExported: true,
13071
+ };
13043
13072
 
13044
13073
  var math = /*#__PURE__*/Object.freeze({
13045
13074
  __proto__: null,
@@ -13073,6 +13102,7 @@
13073
13102
  FLOOR: FLOOR,
13074
13103
  FLOOR_MATH: FLOOR_MATH,
13075
13104
  FLOOR_PRECISE: FLOOR_PRECISE,
13105
+ INT: INT,
13076
13106
  ISEVEN: ISEVEN,
13077
13107
  ISODD: ISODD,
13078
13108
  ISO_CEILING: ISO_CEILING,
@@ -18599,10 +18629,11 @@
18599
18629
  const DEFAULT_MATCH_MODE = 0;
18600
18630
  const DEFAULT_SEARCH_MODE = 1;
18601
18631
  const DEFAULT_ABSOLUTE_RELATIVE_MODE = 1;
18602
- function assertAvailable(variable, searchKey) {
18603
- if (variable === undefined) {
18604
- throw new NotAvailableError(_t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)));
18605
- }
18632
+ function valueNotAvailable(searchKey) {
18633
+ return {
18634
+ value: CellErrorType.NotAvailable,
18635
+ message: _t("Did not find value '%s' in [[FUNCTION_NAME]] evaluation.", toString(searchKey)),
18636
+ };
18606
18637
  }
18607
18638
  // -----------------------------------------------------------------------------
18608
18639
  // ADDRESS
@@ -18699,7 +18730,9 @@
18699
18730
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
18700
18731
  : linearSearch(range, searchKey, "strict", range.length, getValueFromRange);
18701
18732
  const col = range[colIndex];
18702
- assertAvailable(col, searchKey?.value);
18733
+ if (col === undefined) {
18734
+ return valueNotAvailable(searchKey);
18735
+ }
18703
18736
  return col[_index - 1];
18704
18737
  },
18705
18738
  isExported: true,
@@ -18756,11 +18789,11 @@
18756
18789
  : (range, index) => range[index][0].value;
18757
18790
  const rangeLength = verticalSearch ? nbRow : nbCol;
18758
18791
  const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
18759
- if (index === -1)
18760
- assertAvailable(undefined, searchKey?.value);
18761
- verticalSearch
18762
- ? assertAvailable(searchArray[0][index], searchKey?.value)
18763
- : assertAvailable(searchArray[index][nbRow - 1], searchKey?.value);
18792
+ if (index === -1 ||
18793
+ (verticalSearch && searchArray[0][index] === undefined) ||
18794
+ (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
18795
+ return valueNotAvailable(searchKey);
18796
+ }
18764
18797
  if (resultRange === undefined) {
18765
18798
  return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
18766
18799
  }
@@ -18810,7 +18843,10 @@
18810
18843
  index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
18811
18844
  break;
18812
18845
  }
18813
- assertAvailable(nbCol === 1 ? range[0][index] : range[index], searchKey);
18846
+ if ((nbCol === 1 && range[0][index] === undefined) ||
18847
+ (nbCol !== 1 && range[index] === undefined)) {
18848
+ return valueNotAvailable(searchKey);
18849
+ }
18814
18850
  return index + 1;
18815
18851
  },
18816
18852
  isExported: true,
@@ -18869,7 +18905,9 @@
18869
18905
  ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
18870
18906
  : linearSearch(range, searchKey, "strict", range[0].length, getValueFromRange);
18871
18907
  const value = range[_index - 1][rowIndex];
18872
- assertAvailable(value, searchKey);
18908
+ if (value === undefined) {
18909
+ return valueNotAvailable(searchKey);
18910
+ }
18873
18911
  return value;
18874
18912
  },
18875
18913
  isExported: true,
@@ -18919,7 +18957,9 @@
18919
18957
  ? returnRange.map((col) => [col[index]])
18920
18958
  : [returnRange[index]];
18921
18959
  }
18922
- assertAvailable(defaultValue, searchKey);
18960
+ if (defaultValue === undefined) {
18961
+ return valueNotAvailable(searchKey);
18962
+ }
18923
18963
  return [[defaultValue]];
18924
18964
  },
18925
18965
  isExported: true,
@@ -19691,8 +19731,7 @@
19691
19731
  function addErrorHandling(compute, functionName) {
19692
19732
  return function (...args) {
19693
19733
  try {
19694
- const computeFormula = compute.bind(this);
19695
- return computeFormula(...args);
19734
+ return compute.apply(this, args);
19696
19735
  }
19697
19736
  catch (e) {
19698
19737
  return handleError(e, functionName);
@@ -19726,8 +19765,7 @@
19726
19765
  }
19727
19766
  function addResultHandling(compute) {
19728
19767
  return function (...args) {
19729
- const computeResult = compute.bind(this);
19730
- const result = computeResult(...args);
19768
+ const result = compute.apply(this, args);
19731
19769
  if (!isMatrix(result)) {
19732
19770
  if (typeof result === "object" && result !== null && "value" in result) {
19733
19771
  return result;
@@ -20083,7 +20121,7 @@
20083
20121
  })
20084
20122
  .add("insert_link", {
20085
20123
  ...insertLink,
20086
- name: _t("Insert link"),
20124
+ name: INSERT_LINK_NAME,
20087
20125
  sequence: 150,
20088
20126
  separator: true,
20089
20127
  });
@@ -21694,7 +21732,7 @@
21694
21732
  ? existingSelectionRanges
21695
21733
  : this.props.ranges().map((xc, id) => ({
21696
21734
  xc,
21697
- id,
21735
+ id: id + 1,
21698
21736
  isFocused: false,
21699
21737
  }));
21700
21738
  return ranges.map((range) => ({
@@ -26526,6 +26564,17 @@
26526
26564
  onValueSelected: Function,
26527
26565
  onValueHovered: Function,
26528
26566
  };
26567
+ autoCompleteListRef = owl.useRef("autoCompleteList");
26568
+ setup() {
26569
+ owl.useEffect(() => {
26570
+ const selectedIndex = this.props.selectedIndex;
26571
+ if (selectedIndex === undefined) {
26572
+ return;
26573
+ }
26574
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26575
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26576
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26577
+ }
26529
26578
  }
26530
26579
 
26531
26580
  class ContentEditableHelper {
@@ -26970,6 +27019,7 @@
26970
27019
  position: absolute;
26971
27020
  margin: 1px 4px;
26972
27021
  pointer-events: none;
27022
+ overflow: auto;
26973
27023
 
26974
27024
  .o-semi-bold {
26975
27025
  /** FIXME: to remove in favor of Bootstrap
@@ -27027,7 +27077,10 @@
27027
27077
  if (this.props.delimitation && this.props.rect) {
27028
27078
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
27029
27079
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
27080
+ assistantStyle["max-height"] = `${remainingHeight}px`;
27030
27081
  if (cellY > remainingHeight) {
27082
+ const availableSpaceAbove = cellY;
27083
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
27031
27084
  // render top
27032
27085
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
27033
27086
  assistantStyle.top = `-3px`;
@@ -27719,7 +27772,11 @@
27719
27772
  }
27720
27773
  }
27721
27774
  updateCellReferenceVisibility() {
27722
- if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27775
+ if (this.env.model.getters.getEditionMode() === "inactive") {
27776
+ this.isCellReferenceVisible = false;
27777
+ return;
27778
+ }
27779
+ if (this.isCellReferenceVisible) {
27723
27780
  return;
27724
27781
  }
27725
27782
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -27748,19 +27805,21 @@
27748
27805
  slots: Object,
27749
27806
  };
27750
27807
  get iconStyle() {
27751
- const x = this.getIconHorizontalPosition();
27752
- const y = this.getIconVerticalPosition();
27808
+ const cellPosition = this.props.cellPosition;
27809
+ const merge = this.env.model.getters.getMerge(cellPosition);
27810
+ const zone = merge || positionToZone(cellPosition);
27811
+ const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
27812
+ const x = this.getIconHorizontalPosition(rect, cellPosition);
27813
+ const y = this.getIconVerticalPosition(rect, cellPosition);
27753
27814
  return cssPropertiesToCss({
27754
27815
  top: `${y + (this.props.offset?.y || 0)}px`,
27755
27816
  left: `${x + (this.props.offset?.x || 0)}px`,
27756
27817
  });
27757
27818
  }
27758
- getIconVerticalPosition() {
27759
- const { sheetId, row } = this.props.cellPosition;
27760
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
27761
- const start = this.env.model.getters.getRowDimensionsInViewport(sheetId, row).start;
27762
- const end = this.env.model.getters.getRowDimensionsInViewport(sheetId, merge ? merge.bottom : row).end;
27763
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
27819
+ getIconVerticalPosition(rect, cellPosition) {
27820
+ const start = rect.y;
27821
+ const end = rect.y + rect.height;
27822
+ const cell = this.env.model.getters.getCell(cellPosition);
27764
27823
  const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
27765
27824
  switch (align) {
27766
27825
  case "bottom":
@@ -27772,13 +27831,11 @@
27772
27831
  return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
27773
27832
  }
27774
27833
  }
27775
- getIconHorizontalPosition() {
27776
- const { sheetId, col } = this.props.cellPosition;
27777
- const merge = this.env.model.getters.getMerge(this.props.cellPosition);
27778
- const start = this.env.model.getters.getColDimensionsInViewport(sheetId, col).start;
27779
- const end = this.env.model.getters.getColDimensionsInViewport(sheetId, merge ? merge.right : col).end;
27780
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
27781
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(this.props.cellPosition);
27834
+ getIconHorizontalPosition(rect, cellPosition) {
27835
+ const start = rect.x;
27836
+ const end = rect.x + rect.width;
27837
+ const cell = this.env.model.getters.getCell(cellPosition);
27838
+ const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
27782
27839
  const align = this.props.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
27783
27840
  switch (align) {
27784
27841
  case "right":
@@ -27862,6 +27919,8 @@
27862
27919
  height: ${CHECKBOX_WIDTH}px;
27863
27920
  accent-color: #808080;
27864
27921
  margin: ${MARGIN}px;
27922
+ /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
27923
+ position: absolute;
27865
27924
  }
27866
27925
  `;
27867
27926
  class DataValidationCheckbox extends owl.Component {
@@ -27880,7 +27939,7 @@
27880
27939
  }
27881
27940
  get isDisabled() {
27882
27941
  const cell = this.env.model.getters.getCell(this.props.cellPosition);
27883
- return !!cell?.isFormula;
27942
+ return this.env.model.getters.isReadonly() || !!cell?.isFormula;
27884
27943
  }
27885
27944
  }
27886
27945
 
@@ -27920,12 +27979,17 @@
27920
27979
  static props = {};
27921
27980
  static components = { GridCellIcon, DataValidationCheckbox, DataValidationListIcon };
27922
27981
  get checkBoxCellPositions() {
27923
- return this.env.model.getters.getDataValidationCheckBoxCellPositions();
27982
+ return this.env.model.getters
27983
+ .getVisibleCellPositions()
27984
+ .filter(this.env.model.getters.isCellValidCheckbox);
27924
27985
  }
27925
27986
  get listIconsCellPositions() {
27926
- return this.env.model.getters.isReadonly()
27927
- ? []
27928
- : this.env.model.getters.getDataValidationListCellsPositions();
27987
+ if (this.env.model.getters.isReadonly()) {
27988
+ return [];
27989
+ }
27990
+ return this.env.model.getters
27991
+ .getVisibleCellPositions()
27992
+ .filter(this.env.model.getters.cellHasListDataValidationIcon);
27929
27993
  }
27930
27994
  }
27931
27995
 
@@ -34287,29 +34351,12 @@
34287
34351
  from: 12,
34288
34352
  to: 12.5,
34289
34353
  applyMigration(data) {
34290
- for (let sheet of data.sheets || []) {
34291
- let knownDataFilterZones = [];
34292
- for (let filterTable of sheet.filterTables || []) {
34293
- const zone = toZone(filterTable.range);
34294
- // See commit message for the details
34295
- const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
34296
- if (intersectZoneIndex !== -1) {
34297
- knownDataFilterZones[intersectZoneIndex] = zone;
34298
- }
34299
- else {
34300
- knownDataFilterZones.push(zone);
34301
- }
34302
- }
34303
- sheet.filterTables = knownDataFilterZones.map((zone) => ({
34304
- range: zoneToXc(zone),
34305
- }));
34306
- }
34307
- return data;
34354
+ return fixOverlappingFilters(data);
34308
34355
  },
34309
34356
  },
34310
34357
  {
34311
34358
  description: "Change Border description structure",
34312
- from: 12,
34359
+ from: 12.5,
34313
34360
  to: 13,
34314
34361
  applyMigration(data) {
34315
34362
  for (const borderId in data.borders) {
@@ -34340,6 +34387,14 @@
34340
34387
  return data;
34341
34388
  },
34342
34389
  },
34390
+ {
34391
+ description: "Fix datafilter duplication (post saas-16.4)",
34392
+ from: 14,
34393
+ to: 14.5,
34394
+ applyMigration(data) {
34395
+ return fixOverlappingFilters(data);
34396
+ },
34397
+ },
34343
34398
  ];
34344
34399
  /**
34345
34400
  * This function is used to repair faulty data independently of the migration.
@@ -34497,6 +34552,26 @@
34497
34552
  }
34498
34553
  return messages;
34499
34554
  }
34555
+ function fixOverlappingFilters(data) {
34556
+ for (let sheet of data.sheets || []) {
34557
+ let knownDataFilterZones = [];
34558
+ for (let filterTable of sheet.filterTables || []) {
34559
+ const zone = toZone(filterTable.range);
34560
+ // See commit message of https://github.com/odoo/o-spreadsheet/pull/3632 of more details
34561
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
34562
+ if (intersectZoneIndex !== -1) {
34563
+ knownDataFilterZones[intersectZoneIndex] = zone;
34564
+ }
34565
+ else {
34566
+ knownDataFilterZones.push(zone);
34567
+ }
34568
+ }
34569
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
34570
+ range: zoneToXc(zone),
34571
+ }));
34572
+ }
34573
+ return data;
34574
+ }
34500
34575
  // -----------------------------------------------------------------------------
34501
34576
  // Helpers
34502
34577
  // -----------------------------------------------------------------------------
@@ -35146,22 +35221,7 @@
35146
35221
  }
35147
35222
  }
35148
35223
  export(data) {
35149
- // Borders
35150
- let borderId = 0;
35151
35224
  const borders = {};
35152
- /**
35153
- * Get the id of the given border. If the border does not exist, it creates
35154
- * one.
35155
- */
35156
- function getBorderId(border) {
35157
- for (let [key, value] of Object.entries(borders)) {
35158
- if (deepEquals(value, border)) {
35159
- return parseInt(key, 10);
35160
- }
35161
- }
35162
- borders[++borderId] = border;
35163
- return borderId;
35164
- }
35165
35225
  for (let sheet of data.sheets) {
35166
35226
  for (let col = 0; col < sheet.colNumber; col++) {
35167
35227
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -35169,7 +35229,7 @@
35169
35229
  if (border) {
35170
35230
  const xc = toXC(col, row);
35171
35231
  const cell = sheet.cells[xc];
35172
- const borderId = getBorderId(border);
35232
+ const borderId = getItemId(border, borders);
35173
35233
  if (cell) {
35174
35234
  cell.border = borderId;
35175
35235
  }
@@ -35958,9 +36018,9 @@
35958
36018
  allowDispatch(cmd) {
35959
36019
  switch (cmd.type) {
35960
36020
  case "UPDATE_CELL":
35961
- return this.checkCellOutOfSheet(cmd);
36021
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35962
36022
  case "CLEAR_CELL":
35963
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
36023
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35964
36024
  default:
35965
36025
  return "Success" /* CommandResult.Success */;
35966
36026
  }
@@ -36110,8 +36170,8 @@
36110
36170
  }
36111
36171
  removeDefaultStyleValues(style) {
36112
36172
  const cleanedStyle = { ...style };
36113
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
36114
- if (cleanedStyle[property] === defaultValue) {
36173
+ for (const property in DEFAULT_STYLE) {
36174
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
36115
36175
  delete cleanedStyle[property];
36116
36176
  }
36117
36177
  }
@@ -36267,10 +36327,7 @@
36267
36327
  else {
36268
36328
  style = before ? before.style : undefined;
36269
36329
  }
36270
- const locale = this.getters.getLocale();
36271
- let format = ("format" in after ? after.format : before && before.format) ||
36272
- detectDateFormat(afterContent, locale) ||
36273
- detectNumberFormat(afterContent);
36330
+ const format = "format" in after ? after.format : before && before.format;
36274
36331
  /* Read the following IF as:
36275
36332
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
36276
36333
  * - the command says the new content is empty and has no border/format/style
@@ -36311,6 +36368,7 @@
36311
36368
  }
36312
36369
  createLiteralCell(id, content, format, style) {
36313
36370
  const locale = this.getters.getLocale();
36371
+ format = format || detectDateFormat(content, locale) || detectNumberFormat(content);
36314
36372
  if (format !== PLAIN_TEXT_FORMAT && !isEvaluationError(content)) {
36315
36373
  content = toString(parseLiteral(content, locale));
36316
36374
  }
@@ -36383,6 +36441,18 @@
36383
36441
  }
36384
36442
  return "Success" /* CommandResult.Success */;
36385
36443
  }
36444
+ checkUselessUpdateCell(cmd) {
36445
+ const cell = this.getters.getCell(cmd);
36446
+ const hasContent = "content" in cmd || "formula" in cmd;
36447
+ const hasStyle = "style" in cmd;
36448
+ const hasFormat = "format" in cmd;
36449
+ if ((!hasContent || cell?.content === cmd.content) &&
36450
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
36451
+ (!hasFormat || cell?.format === cmd.format)) {
36452
+ return "NoChanges" /* CommandResult.NoChanges */;
36453
+ }
36454
+ return "Success" /* CommandResult.Success */;
36455
+ }
36386
36456
  }
36387
36457
  class FormulaCellWithDependencies {
36388
36458
  id;
@@ -39023,7 +39093,6 @@
39023
39093
  "getNumberHeaders",
39024
39094
  "getGridLinesVisibility",
39025
39095
  "getNextSheetName",
39026
- "isEmpty",
39027
39096
  "getSheetSize",
39028
39097
  "getSheetZone",
39029
39098
  "getPaneDivisions",
@@ -39398,14 +39467,6 @@
39398
39467
  // ---------------------------------------------------------------------------
39399
39468
  // Row/Col manipulation
39400
39469
  // ---------------------------------------------------------------------------
39401
- /**
39402
- * Check if a zone only contains empty cells
39403
- */
39404
- isEmpty(sheetId, zone) {
39405
- return positions(zone)
39406
- .map(({ col, row }) => this.getCell({ sheetId, col, row }))
39407
- .every((cell) => !cell || cell.content === "");
39408
- }
39409
39470
  getCommandZones(cmd) {
39410
39471
  const zones = [];
39411
39472
  if ("zone" in cmd) {
@@ -41167,7 +41228,15 @@
41167
41228
  if (!this.rTrees[sheetId]) {
41168
41229
  return;
41169
41230
  }
41170
- this.rTrees[sheetId].remove(item, deepEquals);
41231
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
41232
+ }
41233
+ rtreeItemComparer(left, right) {
41234
+ return (left.data == right.data &&
41235
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
41236
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
41237
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
41238
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
41239
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
41171
41240
  }
41172
41241
  }
41173
41242
  /**
@@ -41245,7 +41314,7 @@
41245
41314
  const queue = Array.from(ranges).reverse();
41246
41315
  while (queue.length > 0) {
41247
41316
  const range = queue.pop();
41248
- visited.add(...this.encoder.encodeBoundingBox(range));
41317
+ visited.addMany(this.encoder.encodeBoundingBox(range));
41249
41318
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41250
41319
  for (const positionId of impactedPositionIds) {
41251
41320
  if (!visited.has(positionId)) {
@@ -41253,7 +41322,7 @@
41253
41322
  }
41254
41323
  }
41255
41324
  }
41256
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41325
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41257
41326
  return visited;
41258
41327
  }
41259
41328
  }
@@ -41393,9 +41462,9 @@
41393
41462
  const cells = positions.map((p) => this.encoder.encode(p));
41394
41463
  const cellsToCompute = new JetSet(cells);
41395
41464
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
41396
- cellsToCompute.add(...this.getCellsDependingOn(cells));
41397
- cellsToCompute.add(...arrayFormulasPositionIds);
41398
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
41465
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
41466
+ cellsToCompute.addMany(arrayFormulasPositionIds);
41467
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
41399
41468
  this.evaluate(cellsToCompute);
41400
41469
  }
41401
41470
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -41409,7 +41478,7 @@
41409
41478
  }
41410
41479
  if (!content) {
41411
41480
  // The previous content could have blocked some array formulas
41412
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41481
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41413
41482
  }
41414
41483
  }
41415
41484
  return impactedPositionIds;
@@ -41461,7 +41530,7 @@
41461
41530
  }
41462
41531
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41463
41532
  const cells = new JetSet(arrayFormulas);
41464
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41533
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
41465
41534
  return cells;
41466
41535
  }
41467
41536
  nextPositionsToUpdate = new JetSet();
@@ -41512,7 +41581,7 @@
41512
41581
  : evaluateLiteral(cell.content, localeFormat);
41513
41582
  }
41514
41583
  catch (e) {
41515
- return createEvaluatedCell(e.value, localeFormat, e.message);
41584
+ return createEvaluatedCell(e?.value || CellErrorType.GenericError, localeFormat, e?.message || implementationErrorMessage);
41516
41585
  }
41517
41586
  finally {
41518
41587
  this.cellsBeingComputed.delete(cellId);
@@ -41599,7 +41668,7 @@
41599
41668
  this.setEvaluatedCell(positionId, evaluatedCell);
41600
41669
  // check if formula dependencies present in the spread zone
41601
41670
  // if so, they need to be recomputed
41602
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41671
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41603
41672
  };
41604
41673
  }
41605
41674
  invalidateSpreading(positionId) {
@@ -41614,8 +41683,8 @@
41614
41683
  continue;
41615
41684
  }
41616
41685
  this.evaluatedCells.delete(child);
41617
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41618
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41686
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41687
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41619
41688
  }
41620
41689
  this.spreadingRelations.removeNode(positionId);
41621
41690
  }
@@ -41846,6 +41915,7 @@
41846
41915
  "getEvaluatedCellsInZone",
41847
41916
  "getSpreadPositionsOf",
41848
41917
  "getArrayFormulaSpreadingOn",
41918
+ "isEmpty",
41849
41919
  ];
41850
41920
  shouldRebuildDependenciesGraph = true;
41851
41921
  evaluator;
@@ -41951,6 +42021,14 @@
41951
42021
  getArrayFormulaSpreadingOn(position) {
41952
42022
  return this.evaluator.getArrayFormulaSpreadingOn(position);
41953
42023
  }
42024
+ /**
42025
+ * Check if a zone only contains empty cells
42026
+ */
42027
+ isEmpty(sheetId, zone) {
42028
+ return positions(zone)
42029
+ .map(({ col, row }) => this.getEvaluatedCell({ sheetId, col, row }))
42030
+ .every((cell) => cell.type === CellValueType.empty);
42031
+ }
41954
42032
  // ---------------------------------------------------------------------------
41955
42033
  // Export
41956
42034
  // ---------------------------------------------------------------------------
@@ -42598,8 +42676,6 @@
42598
42676
  class EvaluationDataValidationPlugin extends UIPlugin {
42599
42677
  static getters = [
42600
42678
  "getDataValidationInvalidCriterionValueMessage",
42601
- "getDataValidationCheckBoxCellPositions",
42602
- "getDataValidationListCellsPositions",
42603
42679
  "getInvalidDataValidationMessage",
42604
42680
  "getValidationResultForCellValue",
42605
42681
  "isCellValidCheckbox",
@@ -42663,19 +42739,6 @@
42663
42739
  const error = this.getRuleErrorForCellValue(cellValue, cellPosition, rule);
42664
42740
  return error ? { error, rule, isValid: false } : VALID_RESULT;
42665
42741
  }
42666
- getDataValidationCheckBoxCellPositions() {
42667
- const rules = this.getters
42668
- .getDataValidationRules(this.getters.getActiveSheetId())
42669
- .filter((rule) => rule.criterion.type === "isBoolean");
42670
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat()).filter((position) => this.isCellValidCheckbox(position));
42671
- }
42672
- getDataValidationListCellsPositions() {
42673
- const rules = this.getters
42674
- .getDataValidationRules(this.getters.getActiveSheetId())
42675
- .filter((rule) => (rule.criterion.type === "isValueInList" || rule.criterion.type === "isValueInRange") &&
42676
- rule.criterion.displayStyle === "arrow");
42677
- return getCellPositionsInRanges(rules.map((rule) => rule.ranges).flat());
42678
- }
42679
42742
  getValidationResultForCell(cellPosition) {
42680
42743
  const { col, row, sheetId } = cellPosition;
42681
42744
  if (!this.validationResults[sheetId]) {
@@ -45484,7 +45547,7 @@
45484
45547
  }
45485
45548
  finalize() {
45486
45549
  if (this.isSearchDirty) {
45487
- this.refreshSearch();
45550
+ this.refreshSearch(false);
45488
45551
  this.isSearchDirty = false;
45489
45552
  }
45490
45553
  }
@@ -45528,10 +45591,10 @@
45528
45591
  /**
45529
45592
  * refresh the matches according to the current search options
45530
45593
  */
45531
- refreshSearch() {
45594
+ refreshSearch(jumpToMatchSheet = true) {
45532
45595
  this.selectedMatchIndex = null;
45533
45596
  this.findMatches();
45534
- this.selectNextCell(Direction.current);
45597
+ this.selectNextCell(Direction.current, jumpToMatchSheet);
45535
45598
  }
45536
45599
  /**
45537
45600
  * Updates the regex based on the current searchOptions and
@@ -45613,7 +45676,7 @@
45613
45676
  * It is also used to keep coherence between the selected searchMatch
45614
45677
  * and selectedMatchIndex.
45615
45678
  */
45616
- selectNextCell(indexChange) {
45679
+ selectNextCell(indexChange, jumpToMatchSheet = true) {
45617
45680
  const matches = this.searchMatches;
45618
45681
  if (!matches.length) {
45619
45682
  this.selectedMatchIndex = null;
@@ -45639,11 +45702,13 @@
45639
45702
  this.selectedMatchIndex = nextIndex;
45640
45703
  const selectedMatch = matches[nextIndex];
45641
45704
  // Switch to the sheet where the match is located
45642
- if (this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45705
+ if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45643
45706
  this.dispatch("ACTIVATE_SHEET", {
45644
45707
  sheetIdFrom: this.getters.getActiveSheetId(),
45645
45708
  sheetIdTo: selectedMatch.sheetId,
45646
45709
  });
45710
+ // We do not want to reset the selection at finalize in this case
45711
+ this.isSearchDirty = false;
45647
45712
  }
45648
45713
  // we want grid selection to capture the selection stream
45649
45714
  this.selection.getBackToDefault();
@@ -46945,19 +47010,17 @@
46945
47010
  break;
46946
47011
  case "FOCUS_RANGE":
46947
47012
  case "CHANGE_RANGE":
46948
- if (cmd.id !== this.focusedInputId) {
46949
- const input = this.inputs[cmd.id];
46950
- const range = input.ranges.find((range) => range.id === cmd.rangeId);
46951
- if (this.isRangeValid(range?.xc || "A1")) {
46952
- const sheetId = this.getters.getActiveSheetId();
46953
- const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
46954
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
46955
- handleEvent: input.handleEvent.bind(input),
46956
- release: () => (this.focusedInputId = null),
46957
- });
46958
- }
46959
- this.focusedInputId = cmd.id;
47013
+ const input = this.inputs[cmd.id];
47014
+ const range = input.ranges.find((range) => range.id === cmd.rangeId);
47015
+ if (range) {
47016
+ const sheetId = this.getters.getActiveSheetId();
47017
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
47018
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
47019
+ handleEvent: input.handleEvent.bind(input),
47020
+ release: () => (this.focusedInputId = null),
47021
+ });
46960
47022
  }
47023
+ this.focusedInputId = cmd.id;
46961
47024
  break;
46962
47025
  }
46963
47026
  this.currentInput?.handle(cmd);
@@ -47211,7 +47274,7 @@
47211
47274
  "getCellText",
47212
47275
  "getCellMultiLineText",
47213
47276
  "getContiguousZone",
47214
- "isCellEmpty",
47277
+ "isEvaluatedCellEmpty",
47215
47278
  ];
47216
47279
  ctx = document.createElement("canvas").getContext("2d");
47217
47280
  // ---------------------------------------------------------------------------
@@ -47339,14 +47402,23 @@
47339
47402
  return zone;
47340
47403
  }
47341
47404
  /**
47342
- * Check if a cell is empty. If the cell is part of a merge,
47343
- * check if the merge containing the cell is empty.
47405
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
47406
+ * the check applies to the main cell of the merge.
47344
47407
  */
47345
- isCellEmpty(position) {
47408
+ isEvaluatedCellEmpty(position) {
47346
47409
  const mainPosition = this.getters.getMainCellPosition(position);
47347
47410
  const cell = this.getters.getEvaluatedCell(mainPosition);
47348
47411
  return cell.type === CellValueType.empty;
47349
47412
  }
47413
+ /**
47414
+ * Checks if a cell is empty (i.e. does not have a content or a formula does not spread over it).
47415
+ * If the cell is part of a merge, the check applies to the main cell of the merge.
47416
+ */
47417
+ isCellEmpty(position) {
47418
+ const mainPosition = this.getters.getMainCellPosition(position);
47419
+ return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
47420
+ this.getters.getCell(mainPosition)?.content);
47421
+ }
47350
47422
  getColMaxWidth(sheetId, index) {
47351
47423
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
47352
47424
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
@@ -50367,6 +50439,8 @@
50367
50439
  "getEdgeScrollRow",
50368
50440
  "getVisibleFigures",
50369
50441
  "getVisibleRect",
50442
+ "getVisibleRectWithoutHeaders",
50443
+ "getVisibleCellPositions",
50370
50444
  "getColRowOffsetInViewport",
50371
50445
  "getMainViewportCoordinates",
50372
50446
  "getActiveSheetScrollInfo",
@@ -50610,6 +50684,26 @@
50610
50684
  const viewports = this.getSubViewports(sheetId);
50611
50685
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
50612
50686
  }
50687
+ /**
50688
+ * Get the positions of all the cells that are visible in the viewport, taking merges into account.
50689
+ */
50690
+ getVisibleCellPositions() {
50691
+ const visibleCols = this.getSheetViewVisibleCols();
50692
+ const visibleRows = this.getSheetViewVisibleRows();
50693
+ const sheetId = this.getters.getActiveSheetId();
50694
+ const positions = [];
50695
+ for (const col of visibleCols) {
50696
+ for (const row of visibleRows) {
50697
+ const position = { sheetId, col, row };
50698
+ const mainPosition = this.getters.getMainCellPosition(position);
50699
+ if (mainPosition.row !== row || mainPosition.col !== col) {
50700
+ continue;
50701
+ }
50702
+ positions.push(position);
50703
+ }
50704
+ }
50705
+ return positions;
50706
+ }
50613
50707
  /**
50614
50708
  * Return the main viewport maximum size relative to the client size.
50615
50709
  */
@@ -50729,6 +50823,13 @@
50729
50823
  * Computes the coordinates and size to draw the zone on the canvas
50730
50824
  */
50731
50825
  getVisibleRect(zone) {
50826
+ const rect = this.getVisibleRectWithoutHeaders(zone);
50827
+ return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
50828
+ }
50829
+ /**
50830
+ * Computes the coordinates and size to draw the zone without taking the grid offset into account
50831
+ */
50832
+ getVisibleRectWithoutHeaders(zone) {
50732
50833
  const sheetId = this.getters.getActiveSheetId();
50733
50834
  const viewportRects = this.getSubViewports(sheetId)
50734
50835
  .map((viewport) => viewport.getRect(zone))
@@ -50740,12 +50841,7 @@
50740
50841
  const y = Math.min(...viewportRects.map((rect) => rect.y));
50741
50842
  const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
50742
50843
  const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
50743
- return {
50744
- x: x + this.gridOffsetX,
50745
- y: y + this.gridOffsetY,
50746
- width,
50747
- height,
50748
- };
50844
+ return { x, y, width, height };
50749
50845
  }
50750
50846
  /**
50751
50847
  * Returns the position of the MainViewport relatively to the start of the grid (without headers)
@@ -50969,13 +51065,8 @@
50969
51065
  }
50970
51066
  break;
50971
51067
  case "UPDATE_CELL":
50972
- if ("content" in cmd || "format" in cmd) {
50973
- this.headerPositions = {};
50974
- this.isDirty = true;
50975
- }
50976
- else {
50977
- this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
50978
- }
51068
+ this.headerPositions = {};
51069
+ this.isDirty = true;
50979
51070
  break;
50980
51071
  case "UPDATE_FILTER":
50981
51072
  case "REMOVE_FILTER_TABLE":
@@ -54888,8 +54979,8 @@
54888
54979
  let currentPosition = startPosition;
54889
54980
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54890
54981
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54891
- let mode = !this.getters.isCellEmpty({ ...currentPosition, sheetId }) &&
54892
- !this.getters.isCellEmpty({ ...nextCellPosition, sheetId })
54982
+ let mode = !this.getters.isEvaluatedCellEmpty({ ...currentPosition, sheetId }) &&
54983
+ !this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId })
54893
54984
  ? "endOfCluster"
54894
54985
  : "nextCluster";
54895
54986
  while (true) {
@@ -54899,7 +54990,7 @@
54899
54990
  currentPosition.row === nextCellPosition.row) {
54900
54991
  break;
54901
54992
  }
54902
- const isNextCellEmpty = this.getters.isCellEmpty({ ...nextCellPosition, sheetId });
54993
+ const isNextCellEmpty = this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId });
54903
54994
  if (mode === "endOfCluster" && isNextCellEmpty) {
54904
54995
  break;
54905
54996
  }
@@ -57184,9 +57275,9 @@
57184
57275
  exports.tokenize = tokenize;
57185
57276
 
57186
57277
 
57187
- __info__.version = "17.1.4";
57188
- __info__.date = "2024-02-09T13:09:13.430Z";
57189
- __info__.hash = "16d3ece";
57278
+ __info__.version = "17.1.7";
57279
+ __info__.date = "2024-03-12T07:56:10.783Z";
57280
+ __info__.hash = "3a9f334";
57190
57281
 
57191
57282
 
57192
57283
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);