@odoo/o-spreadsheet 18.1.5 → 18.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 18.1.5
6
- * @date 2025-01-31T08:00:10.263Z
7
- * @hash 97acb8b
5
+ * @version 18.1.7
6
+ * @date 2025-02-10T09:00:28.556Z
7
+ * @hash 338d8a1
8
8
  */
9
9
 
10
10
  'use strict';
@@ -770,9 +770,16 @@ function deepEqualsArray(arr1, arr2) {
770
770
  }
771
771
  return true;
772
772
  }
773
- /** Check if the given array contains all the values of the other array. */
773
+ /**
774
+ * Check if the given array contains all the values of the other array.
775
+ * It makes the assumption that both array do not contain duplicates.
776
+ */
774
777
  function includesAll(arr, values) {
775
- return values.every((value) => arr.includes(value));
778
+ if (arr.length < values.length) {
779
+ return false;
780
+ }
781
+ const set = new Set(arr);
782
+ return values.every((value) => set.has(value));
776
783
  }
777
784
  /**
778
785
  * Return an object with all the keys in the object that have a falsy value removed.
@@ -18456,19 +18463,20 @@ const HLOOKUP = {
18456
18463
  description: _t("Horizontal lookup"),
18457
18464
  args: [
18458
18465
  arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18459
- arg("range (range)", _t("The range to consider for the search. The first row in the range is searched for the key specified in search_key.")),
18466
+ arg("range (any, range)", _t("The range to consider for the search. The first row in the range is searched for the key specified in search_key.")),
18460
18467
  arg("index (number)", _t("The row index of the value to be returned, where the first row in range is numbered 1.")),
18461
18468
  arg(`is_sorted (boolean, default=${DEFAULT_IS_SORTED})`, _t("Indicates whether the row to be searched (the first row of the specified range) is sorted, in which case the closest match for search_key will be returned.")),
18462
18469
  ],
18463
18470
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18464
18471
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18465
- assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18472
+ const _range = toMatrix(range);
18473
+ assert(() => 1 <= _index && _index <= _range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18466
18474
  const getValueFromRange = (range, index) => range[index][0].value;
18467
18475
  const _isSorted = toBoolean(isSorted.value);
18468
18476
  const colIndex = _isSorted
18469
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
18470
- : linearSearch(range, searchKey, "wildcard", range.length, getValueFromRange);
18471
- const col = range[colIndex];
18477
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
18478
+ : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
18479
+ const col = _range[colIndex];
18472
18480
  if (col === undefined) {
18473
18481
  return valueNotAvailable(searchKey);
18474
18482
  }
@@ -18560,35 +18568,37 @@ const LOOKUP = {
18560
18568
  description: _t("Look up a value."),
18561
18569
  args: [
18562
18570
  arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18563
- arg("search_array (range)", _t("One method of using this function is to provide a single sorted row or column search_array to look through for the search_key with a second argument result_range. The other way is to combine these two arguments into one search_array where the first row or column is searched and a value is returned from the last row or column in the array. If search_key is not found, a non-exact match may be returned.")),
18564
- arg("result_range (range, optional)", _t("The range from which to return a result. The value returned corresponds to the location where search_key is found in search_range. This range must be only a single row or column and should not be used if using the search_result_array method.")),
18571
+ arg("search_array (any, range)", _t("One method of using this function is to provide a single sorted row or column search_array to look through for the search_key with a second argument result_range. The other way is to combine these two arguments into one search_array where the first row or column is searched and a value is returned from the last row or column in the array. If search_key is not found, a non-exact match may be returned.")),
18572
+ arg("result_range (any, range, optional)", _t("The range from which to return a result. The value returned corresponds to the location where search_key is found in search_range. This range must be only a single row or column and should not be used if using the search_result_array method.")),
18565
18573
  ],
18566
18574
  compute: function (searchKey, searchArray, resultRange) {
18567
- let nbCol = searchArray.length;
18568
- let nbRow = searchArray[0].length;
18575
+ const _searchArray = toMatrix(searchArray);
18576
+ const _resultRange = toMatrix(resultRange);
18577
+ let nbCol = _searchArray.length;
18578
+ let nbRow = _searchArray[0].length;
18569
18579
  const verticalSearch = nbRow >= nbCol;
18570
18580
  const getElement = verticalSearch
18571
18581
  ? (range, index) => range[0][index].value
18572
18582
  : (range, index) => range[index][0].value;
18573
18583
  const rangeLength = verticalSearch ? nbRow : nbCol;
18574
- const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
18584
+ const index = dichotomicSearch(_searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
18575
18585
  if (index === -1 ||
18576
- (verticalSearch && searchArray[0][index] === undefined) ||
18577
- (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
18586
+ (verticalSearch && _searchArray[0][index] === undefined) ||
18587
+ (!verticalSearch && _searchArray[index][nbRow - 1] === undefined)) {
18578
18588
  return valueNotAvailable(searchKey);
18579
18589
  }
18580
- if (resultRange === undefined) {
18581
- return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
18590
+ if (_resultRange[0].length === 0) {
18591
+ return verticalSearch ? _searchArray[nbCol - 1][index] : _searchArray[index][nbRow - 1];
18582
18592
  }
18583
- nbCol = resultRange.length;
18584
- nbRow = resultRange[0].length;
18593
+ nbCol = _resultRange.length;
18594
+ nbRow = _resultRange[0].length;
18585
18595
  assert(() => nbCol === 1 || nbRow === 1, _t("The result_range must be a single row or a single column."));
18586
18596
  if (nbCol > 1) {
18587
18597
  assert(() => index <= nbCol - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range row value %s.", (index + 1).toString()));
18588
- return resultRange[index][0];
18598
+ return _resultRange[index][0];
18589
18599
  }
18590
18600
  assert(() => index <= nbRow - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range column value %s.", (index + 1).toString()));
18591
- return resultRange[0][index];
18601
+ return _resultRange[0][index];
18592
18602
  },
18593
18603
  isExported: true,
18594
18604
  };
@@ -18605,28 +18615,29 @@ const MATCH = {
18605
18615
  ],
18606
18616
  compute: function (searchKey, range, searchType = { value: DEFAULT_SEARCH_TYPE }) {
18607
18617
  let _searchType = toNumber(searchType, this.locale);
18608
- const nbCol = range.length;
18609
- const nbRow = range[0].length;
18618
+ const _range = toMatrix(range);
18619
+ const nbCol = _range.length;
18620
+ const nbRow = _range[0].length;
18610
18621
  assert(() => nbCol === 1 || nbRow === 1, _t("The range must be a single row or a single column."));
18611
18622
  let index = -1;
18612
18623
  const getElement = nbCol === 1
18613
- ? (range, index) => range[0][index].value
18614
- : (range, index) => range[index][0].value;
18615
- const rangeLen = nbCol === 1 ? range[0].length : range.length;
18624
+ ? (_range, index) => _range[0][index].value
18625
+ : (_range, index) => _range[index][0].value;
18626
+ const rangeLen = nbCol === 1 ? _range[0].length : _range.length;
18616
18627
  _searchType = Math.sign(_searchType);
18617
18628
  switch (_searchType) {
18618
18629
  case 1:
18619
- index = dichotomicSearch(range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18630
+ index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18620
18631
  break;
18621
18632
  case 0:
18622
- index = linearSearch(range, searchKey, "wildcard", rangeLen, getElement);
18633
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
18623
18634
  break;
18624
18635
  case -1:
18625
- index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
18636
+ index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
18626
18637
  break;
18627
18638
  }
18628
- if ((nbCol === 1 && range[0][index] === undefined) ||
18629
- (nbCol !== 1 && range[index] === undefined)) {
18639
+ if ((nbCol === 1 && _range[0][index] === undefined) ||
18640
+ (nbCol !== 1 && _range[index] === undefined)) {
18630
18641
  return valueNotAvailable(searchKey);
18631
18642
  }
18632
18643
  return index + 1;
@@ -18681,13 +18692,14 @@ const VLOOKUP = {
18681
18692
  ],
18682
18693
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18683
18694
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18684
- assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18695
+ const _range = toMatrix(range);
18696
+ assert(() => 1 <= _index && _index <= _range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18685
18697
  const getValueFromRange = (range, index) => range[0][index].value;
18686
18698
  const _isSorted = toBoolean(isSorted.value);
18687
18699
  const rowIndex = _isSorted
18688
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
18689
- : linearSearch(range, searchKey, "wildcard", range[0].length, getValueFromRange);
18690
- const value = range[_index - 1][rowIndex];
18700
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
18701
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
18702
+ const value = _range[_index - 1][rowIndex];
18691
18703
  if (value === undefined) {
18692
18704
  return valueNotAvailable(searchKey);
18693
18705
  }
@@ -18724,27 +18736,29 @@ const XLOOKUP = {
18724
18736
  compute: function (searchKey, lookupRange, returnRange, defaultValue, matchMode = { value: DEFAULT_MATCH_MODE }, searchMode = { value: DEFAULT_SEARCH_MODE }) {
18725
18737
  const _matchMode = Math.trunc(toNumber(matchMode.value, this.locale));
18726
18738
  const _searchMode = Math.trunc(toNumber(searchMode.value, this.locale));
18727
- assert(() => lookupRange.length === 1 || lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
18739
+ const _lookupRange = toMatrix(lookupRange);
18740
+ const _returnRange = toMatrix(returnRange);
18741
+ assert(() => _lookupRange.length === 1 || _lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
18728
18742
  assert(() => [-1, 1, -2, 2].includes(_searchMode), _t("search_mode should be a value in [-1, 1, -2, 2]."));
18729
18743
  assert(() => [-1, 0, 1, 2].includes(_matchMode), _t("match_mode should be a value in [-1, 0, 1, 2]."));
18730
- const lookupDirection = lookupRange.length === 1 ? "col" : "row";
18744
+ const lookupDirection = _lookupRange.length === 1 ? "col" : "row";
18731
18745
  assert(() => !(_matchMode === 2 && [-2, 2].includes(_searchMode)), _t("the search and match mode combination is not supported for XLOOKUP evaluation."));
18732
18746
  assert(() => lookupDirection === "col"
18733
- ? returnRange[0].length === lookupRange[0].length
18734
- : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18747
+ ? _returnRange[0].length === _lookupRange[0].length
18748
+ : _returnRange.length === _lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18735
18749
  const getElement = lookupDirection === "col"
18736
18750
  ? (range, index) => range[0][index].value
18737
18751
  : (range, index) => range[index][0].value;
18738
- const rangeLen = lookupDirection === "col" ? lookupRange[0].length : lookupRange.length;
18752
+ const rangeLen = lookupDirection === "col" ? _lookupRange[0].length : _lookupRange.length;
18739
18753
  const mode = MATCH_MODE[_matchMode];
18740
18754
  const reverseSearch = _searchMode === -1;
18741
18755
  const index = _searchMode === 2 || _searchMode === -2
18742
- ? dichotomicSearch(lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18743
- : linearSearch(lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
18756
+ ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18757
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
18744
18758
  if (index !== -1) {
18745
18759
  return lookupDirection === "col"
18746
- ? returnRange.map((col) => [col[index]])
18747
- : [returnRange[index]];
18760
+ ? _returnRange.map((col) => [col[index]])
18761
+ : [_returnRange[index]];
18748
18762
  }
18749
18763
  if (defaultValue === undefined) {
18750
18764
  return valueNotAvailable(searchKey);
@@ -28436,11 +28450,7 @@ function interpolateData(config, values, labels, newLabels) {
28436
28450
  if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
28437
28451
  return [];
28438
28452
  }
28439
- const labelMin = Math.min(...labels);
28440
- const labelMax = Math.max(...labels);
28441
- const labelRange = labelMax - labelMin;
28442
- const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
28443
- const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
28453
+ const { normalizedLabels, normalizedNewLabels } = normalizeLabels(labels, newLabels, config);
28444
28454
  try {
28445
28455
  switch (config.type) {
28446
28456
  case "polynomial": {
@@ -28485,6 +28495,30 @@ function interpolateData(config, values, labels, newLabels) {
28485
28495
  return newLabels.map((x) => ({ x, y: NaN }));
28486
28496
  }
28487
28497
  }
28498
+ function normalizeLabels(labels, newLabels, config) {
28499
+ let normalizedLabels = [];
28500
+ let normalizedNewLabels = [];
28501
+ if (config.type === "logarithmic") {
28502
+ // Logarithmic trends in charts are used to visualize proportional growth or
28503
+ // relative changes. Therefore, we change the normalization technique for
28504
+ // logarithmic trend lines for a better fit. The method used here is Max Absolute
28505
+ // Scaling. This Technique is ideal for data spanning several orders of magnitude,
28506
+ // as it balances differences between small and large values by compressing larger
28507
+ // values while preserving proportionality and ensuring all values are scaled relative
28508
+ // to the largest magnitude.
28509
+ const labelMax = Math.max(...labels.map(Math.abs));
28510
+ normalizedLabels = labels.map((l) => l / labelMax);
28511
+ normalizedNewLabels = newLabels.map((l) => l / labelMax);
28512
+ }
28513
+ else {
28514
+ const labelMax = Math.max(...labels);
28515
+ const labelMin = Math.min(...labels);
28516
+ const labelRange = labelMax - labelMin;
28517
+ normalizedLabels = labels.map((l) => (l - labelMax) / labelRange);
28518
+ normalizedNewLabels = newLabels.map((l) => (l - labelMax) / labelRange);
28519
+ }
28520
+ return { normalizedLabels, normalizedNewLabels };
28521
+ }
28488
28522
  function getChartAxisType(chart, labelRange, getters) {
28489
28523
  if (isDateChart(chart, labelRange, getters) && isLuxonTimeAdapterInstalled()) {
28490
28524
  return "time";
@@ -41004,8 +41038,8 @@ function useDragAndDropListItems() {
41004
41038
  document.body.style.cursor = "move";
41005
41039
  state.draggedItemId = args.draggedItemId;
41006
41040
  const container = direction === "horizontal"
41007
- ? new HorizontalContainer(args.containerEl)
41008
- : new VerticalContainer(args.containerEl);
41041
+ ? new HorizontalContainer(args.scrollableContainerEl)
41042
+ : new VerticalContainer(args.scrollableContainerEl);
41009
41043
  dndHelper = new DOMDndHelper({
41010
41044
  ...args,
41011
41045
  container,
@@ -41016,8 +41050,8 @@ function useDragAndDropListItems() {
41016
41050
  const stopListening = startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
41017
41051
  cleanupFns.push(stopListening);
41018
41052
  const onScroll = dndHelper.onScroll.bind(dndHelper);
41019
- args.containerEl.addEventListener("scroll", onScroll);
41020
- cleanupFns.push(() => args.containerEl.removeEventListener("scroll", onScroll));
41053
+ args.scrollableContainerEl.addEventListener("scroll", onScroll);
41054
+ cleanupFns.push(() => args.scrollableContainerEl.removeEventListener("scroll", onScroll));
41021
41055
  cleanupFns.push(dndHelper.destroy.bind(dndHelper));
41022
41056
  };
41023
41057
  owl.onWillUnmount(() => {
@@ -41474,7 +41508,7 @@ class ConditionalFormatPreviewList extends owl.Component {
41474
41508
  draggedItemId: cf.id,
41475
41509
  initialMousePosition: event.clientY,
41476
41510
  items: items,
41477
- containerEl: this.cfListRef.el,
41511
+ scrollableContainerEl: this.cfListRef.el,
41478
41512
  onDragEnd: (cfId, finalIndex) => this.onDragEnd(cfId, finalIndex),
41479
41513
  });
41480
41514
  }
@@ -44656,6 +44690,7 @@ class PivotLayoutConfigurator extends owl.Component {
44656
44690
  unusedGranularities: Object,
44657
44691
  dateGranularities: Array,
44658
44692
  datetimeGranularities: Array,
44693
+ getScrollableContainerEl: { type: Function, optional: true },
44659
44694
  pivotId: String,
44660
44695
  };
44661
44696
  dimensionsRef = owl.useRef("pivot-dimensions");
@@ -44689,7 +44724,7 @@ class PivotLayoutConfigurator extends owl.Component {
44689
44724
  draggedItemId: dimension.nameWithGranularity,
44690
44725
  initialMousePosition: event.clientY,
44691
44726
  items: draggableItems,
44692
- containerEl: this.dimensionsRef.el,
44727
+ scrollableContainerEl: this.props.getScrollableContainerEl?.() || this.dimensionsRef.el,
44693
44728
  onDragEnd: (dimensionName, finalIndex) => {
44694
44729
  const originalIndex = draggableIds.findIndex((id) => id === dimensionName);
44695
44730
  if (originalIndex === finalIndex) {
@@ -44738,7 +44773,7 @@ class PivotLayoutConfigurator extends owl.Component {
44738
44773
  draggedItemId: measure.id,
44739
44774
  initialMousePosition: event.clientY,
44740
44775
  items: draggableItems,
44741
- containerEl: this.dimensionsRef.el,
44776
+ scrollableContainerEl: this.props.getScrollableContainerEl?.() || this.dimensionsRef.el,
44742
44777
  onDragEnd: (measureName, finalIndex) => {
44743
44778
  const originalIndex = draggableIds.findIndex((id) => id === measureName);
44744
44779
  if (originalIndex === finalIndex) {
@@ -45371,7 +45406,7 @@ class SpreadsheetPivotTable {
45371
45406
  rowTreeToRows(tree, parentRow) {
45372
45407
  return tree.flatMap((node) => {
45373
45408
  const row = {
45374
- indent: parentRow ? parentRow.indent + 1 : 0,
45409
+ indent: parentRow ? parentRow.indent + 1 : 1,
45375
45410
  fields: [...(parentRow?.fields || []), node.field],
45376
45411
  values: [...(parentRow?.values || []), node.value],
45377
45412
  };
@@ -45427,7 +45462,7 @@ function dataEntriesToRows(dataEntries, index, rows, fields, values) {
45427
45462
  pivotTableRows.push({
45428
45463
  fields: _fields,
45429
45464
  values: _values,
45430
- indent: index,
45465
+ indent: index + 1,
45431
45466
  });
45432
45467
  const record = groups[value];
45433
45468
  if (record) {
@@ -46425,6 +46460,7 @@ class PivotSpreadsheetSidePanel extends owl.Component {
46425
46460
  };
46426
46461
  store;
46427
46462
  state;
46463
+ pivotSidePanelRef = owl.useRef("pivotSidePanel");
46428
46464
  setup() {
46429
46465
  this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
46430
46466
  this.state = owl.useState({
@@ -46453,6 +46489,9 @@ class PivotSpreadsheetSidePanel extends owl.Component {
46453
46489
  get definition() {
46454
46490
  return this.store.definition;
46455
46491
  }
46492
+ getScrollableContainerEl() {
46493
+ return this.pivotSidePanelRef.el;
46494
+ }
46456
46495
  onSelectionChanged(ranges) {
46457
46496
  this.state.rangeHasChanged = true;
46458
46497
  this.state.range = ranges[0];
@@ -50488,11 +50527,10 @@ class GridRenderer {
50488
50527
  switch (layer) {
50489
50528
  case "Background":
50490
50529
  this.drawGlobalBackground(renderingContext);
50491
- for (const zone of this.getters.getAllActiveViewportsZones()) {
50530
+ for (const { zone, rect } of this.getters.getAllActiveViewportsZonesAndRect()) {
50492
50531
  const { ctx } = renderingContext;
50493
50532
  ctx.save();
50494
50533
  ctx.beginPath();
50495
- const rect = this.getters.getVisibleRect(zone);
50496
50534
  ctx.rect(rect.x, rect.y, rect.width, rect.height);
50497
50535
  ctx.clip();
50498
50536
  const boxes = this.getGridBoxes(zone);
@@ -50768,10 +50806,8 @@ class GridRenderer {
50768
50806
  const { ctx, thinLineWidth } = renderingContext;
50769
50807
  const visibleCols = this.getters.getSheetViewVisibleCols();
50770
50808
  const left = visibleCols[0];
50771
- const right = visibleCols[visibleCols.length - 1];
50772
50809
  const visibleRows = this.getters.getSheetViewVisibleRows();
50773
50810
  const top = visibleRows[0];
50774
- const bottom = visibleRows[visibleRows.length - 1];
50775
50811
  const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
50776
50812
  const selection = this.getters.getSelectedZones();
50777
50813
  const selectedCols = getZonesCols(selection);
@@ -50787,7 +50823,7 @@ class GridRenderer {
50787
50823
  ctx.lineWidth = thinLineWidth;
50788
50824
  ctx.strokeStyle = "#333";
50789
50825
  // Columns headers background
50790
- for (let col = left; col <= right; col++) {
50826
+ for (const col of visibleCols) {
50791
50827
  const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
50792
50828
  const { x, width } = this.getters.getVisibleRect(colZone);
50793
50829
  const isColActive = activeCols.has(col);
@@ -50804,7 +50840,7 @@ class GridRenderer {
50804
50840
  ctx.fillRect(x, 0, width, HEADER_HEIGHT);
50805
50841
  }
50806
50842
  // Rows headers background
50807
- for (let row = top; row <= bottom; row++) {
50843
+ for (const row of visibleRows) {
50808
50844
  const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
50809
50845
  const { y, height } = this.getters.getVisibleRect(rowZone);
50810
50846
  const isRowActive = activeRows.has(row);
@@ -50830,21 +50866,21 @@ class GridRenderer {
50830
50866
  ctx.stroke();
50831
50867
  ctx.beginPath();
50832
50868
  // column text + separator
50833
- for (const i of visibleCols) {
50834
- const colSize = this.getters.getColSize(sheetId, i);
50835
- const colName = numberToLetters(i);
50836
- ctx.fillStyle = activeCols.has(i) ? "#fff" : TEXT_HEADER_COLOR;
50837
- let colStart = this.getHeaderOffset("COL", left, i);
50869
+ for (const col of visibleCols) {
50870
+ const colSize = this.getters.getColSize(sheetId, col);
50871
+ const colName = numberToLetters(col);
50872
+ ctx.fillStyle = activeCols.has(col) ? "#fff" : TEXT_HEADER_COLOR;
50873
+ let colStart = this.getHeaderOffset("COL", left, col);
50838
50874
  ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);
50839
50875
  ctx.moveTo(colStart + colSize, 0);
50840
50876
  ctx.lineTo(colStart + colSize, HEADER_HEIGHT);
50841
50877
  }
50842
50878
  // row text + separator
50843
- for (const i of visibleRows) {
50844
- const rowSize = this.getters.getRowSize(sheetId, i);
50845
- ctx.fillStyle = activeRows.has(i) ? "#fff" : TEXT_HEADER_COLOR;
50846
- let rowStart = this.getHeaderOffset("ROW", top, i);
50847
- ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
50879
+ for (const row of visibleRows) {
50880
+ const rowSize = this.getters.getRowSize(sheetId, row);
50881
+ ctx.fillStyle = activeRows.has(row) ? "#fff" : TEXT_HEADER_COLOR;
50882
+ let rowStart = this.getHeaderOffset("ROW", top, row);
50883
+ ctx.fillText(String(row + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
50848
50884
  ctx.moveTo(0, rowStart + rowSize);
50849
50885
  ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);
50850
50886
  }
@@ -51151,6 +51187,9 @@ function useGridDrawing(refName, model, canvasSize) {
51151
51187
  canvas.width = width * dpr;
51152
51188
  canvas.height = height * dpr;
51153
51189
  canvas.setAttribute("style", `width:${width}px;height:${height}px;`);
51190
+ if (width === 0 || height === 0) {
51191
+ return;
51192
+ }
51154
51193
  // Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2…)
51155
51194
  // are the edges of the squares. If you draw a one-unit-wide line between whole-number
51156
51195
  // coordinates, it will overlap opposite sides of the pixel square, and the resulting
@@ -52697,7 +52736,7 @@ class BordersPlugin extends CorePlugin {
52697
52736
  getCommonSides(border1, border2) {
52698
52737
  const commonBorder = {};
52699
52738
  for (let side of ["top", "bottom", "left", "right"]) {
52700
- if (border1[side] && border1[side] === border2[side]) {
52739
+ if (border1[side] && deepEquals(border1[side], border2[side])) {
52701
52740
  commonBorder[side] = border1[side];
52702
52741
  }
52703
52742
  }
@@ -66913,8 +66952,17 @@ class InternalViewport {
66913
66952
  this.getters = getters;
66914
66953
  this.sheetId = sheetId;
66915
66954
  this.boundaries = boundaries;
66916
- this.viewportWidth = sizeInGrid.width;
66917
- this.viewportHeight = sizeInGrid.height;
66955
+ if (sizeInGrid.width < 0 || sizeInGrid.height < 0) {
66956
+ throw new Error("Viewport size cannot be negative");
66957
+ }
66958
+ this.viewportWidth = sizeInGrid.height && sizeInGrid.width;
66959
+ this.viewportHeight = sizeInGrid.width && sizeInGrid.height;
66960
+ this.top = boundaries.top;
66961
+ this.bottom = boundaries.bottom;
66962
+ this.left = boundaries.left;
66963
+ this.right = boundaries.right;
66964
+ this.offsetX = offsets.x;
66965
+ this.offsetY = offsets.y;
66918
66966
  this.offsetScrollbarX = offsets.x;
66919
66967
  this.offsetScrollbarY = offsets.y;
66920
66968
  this.canScrollVertically = options.canScrollVertically;
@@ -66957,9 +67005,9 @@ class InternalViewport {
66957
67005
  Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
66958
67006
  );
66959
67007
  height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
66960
- }
66961
- if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
66962
- height += FOOTER_HEIGHT;
67008
+ if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
67009
+ height += FOOTER_HEIGHT;
67010
+ }
66963
67011
  }
66964
67012
  return { width, height };
66965
67013
  }
@@ -67100,6 +67148,9 @@ class InternalViewport {
67100
67148
  !this.getters.isRowHidden(this.sheetId, row));
67101
67149
  }
67102
67150
  searchHeaderIndex(dimension, position, startIndex = 0) {
67151
+ if (this.viewportWidth <= 0 || this.viewportHeight <= 0) {
67152
+ return -1;
67153
+ }
67103
67154
  const sheetId = this.sheetId;
67104
67155
  const headers = this.getters.getNumberHeaders(sheetId, dimension);
67105
67156
  // using a binary search:
@@ -67136,7 +67187,7 @@ class InternalViewport {
67136
67187
  this.adjustViewportZoneY();
67137
67188
  }
67138
67189
  /** Corrects the viewport's horizontal offset based on the current structure
67139
- * To make sure that at least on column is visible inside the viewport.
67190
+ * To make sure that at least one column is visible inside the viewport.
67140
67191
  */
67141
67192
  adjustViewportOffsetX() {
67142
67193
  if (this.canScrollHorizontally) {
@@ -67148,7 +67199,7 @@ class InternalViewport {
67148
67199
  this.adjustViewportZoneX();
67149
67200
  }
67150
67201
  /** Corrects the viewport's vertical offset based on the current structure
67151
- * To make sure that at least on row is visible inside the viewport.
67202
+ * To make sure that at least one row is visible inside the viewport.
67152
67203
  */
67153
67204
  adjustViewportOffsetY() {
67154
67205
  if (this.canScrollVertically) {
@@ -67165,11 +67216,14 @@ class InternalViewport {
67165
67216
  const sheetId = this.sheetId;
67166
67217
  this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
67167
67218
  this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
67219
+ if (!this.viewportWidth) {
67220
+ return;
67221
+ }
67168
67222
  if (this.left === -1) {
67169
67223
  this.left = this.boundaries.left;
67170
67224
  }
67171
67225
  if (this.right === -1) {
67172
- this.right = this.getters.getNumberCols(sheetId) - 1;
67226
+ this.right = this.boundaries.right;
67173
67227
  }
67174
67228
  this.offsetX =
67175
67229
  this.getters.getColDimensions(sheetId, this.left).start -
@@ -67181,11 +67235,14 @@ class InternalViewport {
67181
67235
  const sheetId = this.sheetId;
67182
67236
  this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
67183
67237
  this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
67238
+ if (!this.viewportHeight) {
67239
+ return;
67240
+ }
67184
67241
  if (this.top === -1) {
67185
67242
  this.top = this.boundaries.top;
67186
67243
  }
67187
67244
  if (this.bottom === -1) {
67188
- this.bottom = this.getters.getNumberRows(sheetId) - 1;
67245
+ this.bottom = this.boundaries.bottom;
67189
67246
  }
67190
67247
  this.offsetY =
67191
67248
  this.getters.getRowDimensions(sheetId, this.top).start -
@@ -67259,7 +67316,7 @@ class SheetViewPlugin extends UIPlugin {
67259
67316
  "isPositionVisible",
67260
67317
  "getColDimensionsInViewport",
67261
67318
  "getRowDimensionsInViewport",
67262
- "getAllActiveViewportsZones",
67319
+ "getAllActiveViewportsZonesAndRect",
67263
67320
  "getRect",
67264
67321
  ];
67265
67322
  viewports = {};
@@ -67489,12 +67546,12 @@ class SheetViewPlugin extends UIPlugin {
67489
67546
  const sheetId = this.getters.getActiveSheetId();
67490
67547
  const viewports = this.getSubViewports(sheetId);
67491
67548
  //TODO ake another commit to eimprove this
67492
- return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => !this.getters.isHeaderHidden(sheetId, "COL", col));
67549
+ return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => col >= 0 && !this.getters.isHeaderHidden(sheetId, "COL", col));
67493
67550
  }
67494
67551
  getSheetViewVisibleRows() {
67495
67552
  const sheetId = this.getters.getActiveSheetId();
67496
67553
  const viewports = this.getSubViewports(sheetId);
67497
- return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
67554
+ return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => row >= 0 && !this.getters.isHeaderHidden(sheetId, "ROW", row));
67498
67555
  }
67499
67556
  /**
67500
67557
  * Get the positions of all the cells that are visible in the viewport, taking merges into account.
@@ -67537,19 +67594,19 @@ class SheetViewPlugin extends UIPlugin {
67537
67594
  maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
67538
67595
  };
67539
67596
  }
67540
- getColRowOffsetInViewport(dimension, referenceIndex, index) {
67541
- const sheetId = this.getters.getActiveSheetId();
67542
- const visibleCols = this.getters.getSheetViewVisibleCols();
67543
- const visibleRows = this.getters.getSheetViewVisibleRows();
67544
- if (index < referenceIndex) {
67545
- return -this.getColRowOffsetInViewport(dimension, index, referenceIndex);
67597
+ getColRowOffsetInViewport(dimension, referenceHeaderIndex, targetHeaderIndex) {
67598
+ if (targetHeaderIndex < referenceHeaderIndex) {
67599
+ return -this.getColRowOffsetInViewport(dimension, targetHeaderIndex, referenceHeaderIndex);
67546
67600
  }
67601
+ const sheetId = this.getters.getActiveSheetId();
67602
+ const visibleHeaders = dimension === "COL"
67603
+ ? this.getters.getSheetViewVisibleCols()
67604
+ : this.getters.getSheetViewVisibleRows();
67605
+ const startIndex = visibleHeaders.findIndex((header) => referenceHeaderIndex >= header);
67606
+ const endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
67607
+ const relevantIndexes = visibleHeaders.slice(startIndex, endIndex);
67547
67608
  let offset = 0;
67548
- const visibleIndexes = dimension === "COL" ? visibleCols : visibleRows;
67549
- for (let i = referenceIndex; i < index; i++) {
67550
- if (!visibleIndexes.includes(i)) {
67551
- continue;
67552
- }
67609
+ for (const i of relevantIndexes) {
67553
67610
  offset += this.getters.getHeaderSize(sheetId, dimension, i);
67554
67611
  }
67555
67612
  return offset;
@@ -67596,7 +67653,7 @@ class SheetViewPlugin extends UIPlugin {
67596
67653
  }
67597
67654
  return { canEdgeScroll, direction, delay };
67598
67655
  }
67599
- getEdgeScrollRow(y, previousY, tartingY) {
67656
+ getEdgeScrollRow(y, previousY, startingY) {
67600
67657
  let canEdgeScroll = false;
67601
67658
  let direction = 0;
67602
67659
  let delay = 0;
@@ -67617,7 +67674,7 @@ class SheetViewPlugin extends UIPlugin {
67617
67674
  delay = scrollDelay(y - height);
67618
67675
  direction = 1;
67619
67676
  }
67620
- else if (y < offsetCorrectionY && tartingY >= offsetCorrectionY && currentOffsetY > 0) {
67677
+ else if (y < offsetCorrectionY && startingY >= offsetCorrectionY && currentOffsetY > 0) {
67621
67678
  // 2
67622
67679
  canEdgeScroll = true;
67623
67680
  delay = scrollDelay(offsetCorrectionY - y);
@@ -67643,13 +67700,7 @@ class SheetViewPlugin extends UIPlugin {
67643
67700
  */
67644
67701
  getVisibleRectWithoutHeaders(zone) {
67645
67702
  const sheetId = this.getters.getActiveSheetId();
67646
- const viewportRects = this.getSubViewports(sheetId)
67647
- .map((viewport) => viewport.getVisibleRect(zone))
67648
- .filter(isDefined);
67649
- if (viewportRects.length === 0) {
67650
- return { x: 0, y: 0, width: 0, height: 0 };
67651
- }
67652
- return this.recomposeRect(viewportRects);
67703
+ return this.mapViewportsToRect(sheetId, (viewport) => viewport.getVisibleRect(zone));
67653
67704
  }
67654
67705
  /**
67655
67706
  * Computes the actual size and position (:Rect) of the zone on the canvas
@@ -67657,13 +67708,7 @@ class SheetViewPlugin extends UIPlugin {
67657
67708
  */
67658
67709
  getRect(zone) {
67659
67710
  const sheetId = this.getters.getActiveSheetId();
67660
- const viewportRects = this.getSubViewports(sheetId)
67661
- .map((viewport) => viewport.getFullRect(zone))
67662
- .filter(isDefined);
67663
- if (viewportRects.length === 0) {
67664
- return { x: 0, y: 0, width: 0, height: 0 };
67665
- }
67666
- const rect = this.recomposeRect(viewportRects);
67711
+ const rect = this.mapViewportsToRect(sheetId, (viewport) => viewport.getFullRect(zone));
67667
67712
  return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
67668
67713
  }
67669
67714
  /**
@@ -67708,9 +67753,18 @@ class SheetViewPlugin extends UIPlugin {
67708
67753
  end: start + (isRowHidden ? 0 : size),
67709
67754
  };
67710
67755
  }
67711
- getAllActiveViewportsZones() {
67756
+ getAllActiveViewportsZonesAndRect() {
67712
67757
  const sheetId = this.getters.getActiveSheetId();
67713
- return this.getSubViewports(sheetId);
67758
+ return this.getSubViewports(sheetId).map((viewport) => {
67759
+ return {
67760
+ zone: viewport,
67761
+ rect: {
67762
+ x: viewport.offsetCorrectionX + this.gridOffsetX,
67763
+ y: viewport.offsetCorrectionY + this.gridOffsetY,
67764
+ ...viewport.getMaxSize(),
67765
+ },
67766
+ };
67767
+ });
67714
67768
  }
67715
67769
  // ---------------------------------------------------------------------------
67716
67770
  // Private
@@ -67769,12 +67823,11 @@ class SheetViewPlugin extends UIPlugin {
67769
67823
  }
67770
67824
  /** gets rid of deprecated sheetIds */
67771
67825
  cleanViewports() {
67772
- const sheetIds = this.getters.getSheetIds();
67773
- for (let sheetId of Object.keys(this.viewports)) {
67774
- if (!sheetIds.includes(sheetId)) {
67775
- delete this.viewports[sheetId];
67776
- }
67826
+ const newViewport = {};
67827
+ for (const sheetId of this.getters.getSheetIds()) {
67828
+ newViewport[sheetId] = this.viewports[sheetId];
67777
67829
  }
67830
+ this.viewports = newViewport;
67778
67831
  }
67779
67832
  resizeSheetView(height, width, gridOffsetX = 0, gridOffsetY = 0) {
67780
67833
  this.sheetViewHeight = height;
@@ -67784,7 +67837,7 @@ class SheetViewPlugin extends UIPlugin {
67784
67837
  this.recomputeViewports();
67785
67838
  }
67786
67839
  recomputeViewports() {
67787
- for (let sheetId of Object.keys(this.viewports)) {
67840
+ for (const sheetId of this.getters.getSheetIds()) {
67788
67841
  this.resetViewports(sheetId);
67789
67842
  }
67790
67843
  }
@@ -67806,8 +67859,10 @@ class SheetViewPlugin extends UIPlugin {
67806
67859
  const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
67807
67860
  const nCols = this.getters.getNumberCols(sheetId);
67808
67861
  const nRows = this.getters.getNumberRows(sheetId);
67809
- const colOffset = this.getters.getColRowOffset("COL", 0, xSplit, sheetId);
67810
- const rowOffset = this.getters.getColRowOffset("ROW", 0, ySplit, sheetId);
67862
+ const colOffset = Math.min(this.getters.getColRowOffset("COL", 0, xSplit, sheetId), this.sheetViewWidth);
67863
+ const rowOffset = Math.min(this.getters.getColRowOffset("ROW", 0, ySplit, sheetId), this.sheetViewHeight);
67864
+ const unfrozenWidth = Math.max(this.sheetViewWidth - colOffset, 0);
67865
+ const unfrozenHeight = Math.max(this.sheetViewHeight - rowOffset, 0);
67811
67866
  const { xRatio, yRatio } = this.getFrozenSheetViewRatio(sheetId);
67812
67867
  const canScrollHorizontally = xRatio < 1.0;
67813
67868
  const canScrollVertically = yRatio < 1.0;
@@ -67818,14 +67873,14 @@ class SheetViewPlugin extends UIPlugin {
67818
67873
  new InternalViewport(this.getters, sheetId, { left: 0, right: xSplit - 1, top: 0, bottom: ySplit - 1 }, { width: colOffset, height: rowOffset }, { canScrollHorizontally: false, canScrollVertically: false }, { x: 0, y: 0 })) ||
67819
67874
  undefined,
67820
67875
  topRight: (ySplit &&
67821
- new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: 0, bottom: ySplit - 1 }, { width: this.sheetViewWidth - colOffset, height: rowOffset }, { canScrollHorizontally, canScrollVertically: false }, { x: canScrollHorizontally ? previousOffset.x : 0, y: 0 })) ||
67876
+ new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: 0, bottom: ySplit - 1 }, { width: unfrozenWidth, height: rowOffset }, { canScrollHorizontally, canScrollVertically: false }, { x: canScrollHorizontally ? previousOffset.x : 0, y: 0 })) ||
67822
67877
  undefined,
67823
67878
  bottomLeft: (xSplit &&
67824
- new InternalViewport(this.getters, sheetId, { left: 0, right: xSplit - 1, top: ySplit, bottom: nRows - 1 }, { width: colOffset, height: this.sheetViewHeight - rowOffset }, { canScrollHorizontally: false, canScrollVertically }, { x: 0, y: canScrollVertically ? previousOffset.y : 0 })) ||
67879
+ new InternalViewport(this.getters, sheetId, { left: 0, right: xSplit - 1, top: ySplit, bottom: nRows - 1 }, { width: colOffset, height: unfrozenHeight }, { canScrollHorizontally: false, canScrollVertically }, { x: 0, y: canScrollVertically ? previousOffset.y : 0 })) ||
67825
67880
  undefined,
67826
67881
  bottomRight: new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: ySplit, bottom: nRows - 1 }, {
67827
- width: this.sheetViewWidth - colOffset,
67828
- height: this.sheetViewHeight - rowOffset,
67882
+ width: unfrozenWidth,
67883
+ height: unfrozenHeight,
67829
67884
  }, { canScrollHorizontally, canScrollVertically }, {
67830
67885
  x: canScrollHorizontally ? previousOffset.x : 0,
67831
67886
  y: canScrollVertically ? previousOffset.y : 0,
@@ -67902,12 +67957,26 @@ class SheetViewPlugin extends UIPlugin {
67902
67957
  const height = this.sheetViewHeight + this.gridOffsetY;
67903
67958
  return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };
67904
67959
  }
67905
- recomposeRect(viewportRects) {
67906
- const x = Math.min(...viewportRects.map((rect) => rect.x));
67907
- const y = Math.min(...viewportRects.map((rect) => rect.y));
67908
- const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
67909
- const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
67910
- return { x, y, width, height };
67960
+ mapViewportsToRect(sheetId, rectCallBack) {
67961
+ let x = Infinity;
67962
+ let y = Infinity;
67963
+ let width = 0;
67964
+ let height = 0;
67965
+ let hasViewports = false;
67966
+ for (const viewport of this.getSubViewports(sheetId)) {
67967
+ const rect = rectCallBack(viewport);
67968
+ if (rect) {
67969
+ hasViewports = true;
67970
+ x = Math.min(x, rect.x);
67971
+ y = Math.min(y, rect.y);
67972
+ width = Math.max(width, rect.x + rect.width);
67973
+ height = Math.max(height, rect.y + rect.height);
67974
+ }
67975
+ }
67976
+ if (!hasViewports) {
67977
+ return { x: 0, y: 0, width: 0, height: 0 };
67978
+ }
67979
+ return { x, y, width: width - x, height: height - y };
67911
67980
  }
67912
67981
  }
67913
67982
 
@@ -68904,7 +68973,7 @@ class BottomBar extends owl.Component {
68904
68973
  draggedItemId: sheetId,
68905
68974
  initialMousePosition: event.clientX,
68906
68975
  items: sheets,
68907
- containerEl: this.sheetListRef.el,
68976
+ scrollableContainerEl: this.sheetListRef.el,
68908
68977
  onDragEnd: (sheetId, finalIndex) => this.onDragEnd(sheetId, finalIndex),
68909
68978
  });
68910
68979
  }
@@ -75043,6 +75112,6 @@ exports.tokenColors = tokenColors;
75043
75112
  exports.tokenize = tokenize;
75044
75113
 
75045
75114
 
75046
- __info__.version = "18.1.5";
75047
- __info__.date = "2025-01-31T08:00:10.263Z";
75048
- __info__.hash = "97acb8b";
75115
+ __info__.version = "18.1.7";
75116
+ __info__.date = "2025-02-10T09:00:28.556Z";
75117
+ __info__.hash = "338d8a1";