@odoo/o-spreadsheet 18.2.0-alpha.4 → 18.2.0-alpha.6

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.2.0-alpha.4
6
- * @date 2025-01-29T06:30:12.773Z
7
- * @hash 6838c26
5
+ * @version 18.2.0-alpha.6
6
+ * @date 2025-02-05T06:50:47.008Z
7
+ * @hash dae9ab2
8
8
  */
9
9
 
10
10
  'use strict';
@@ -3361,11 +3361,11 @@ const getInvaluableSymbolsRegexp = memoize(function getInvaluableSymbolsRegexp(l
3361
3361
  * number from the point of view of the isNumber function.
3362
3362
  */
3363
3363
  function parseNumber(str, locale) {
3364
+ // remove invaluable characters
3365
+ str = str.replace(getInvaluableSymbolsRegexp(locale), "");
3364
3366
  if (locale.decimalSeparator !== ".") {
3365
3367
  str = str.replace(locale.decimalSeparator, ".");
3366
3368
  }
3367
- // remove invaluable characters
3368
- str = str.replace(getInvaluableSymbolsRegexp(locale), "");
3369
3369
  let n = Number(str);
3370
3370
  if (isNaN(n) && str.includes("%")) {
3371
3371
  n = Number(str.split("%")[0]);
@@ -18634,19 +18634,20 @@ const HLOOKUP = {
18634
18634
  description: _t("Horizontal lookup"),
18635
18635
  args: [
18636
18636
  arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18637
- 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.")),
18637
+ 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.")),
18638
18638
  arg("index (number)", _t("The row index of the value to be returned, where the first row in range is numbered 1.")),
18639
18639
  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.")),
18640
18640
  ],
18641
18641
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18642
18642
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18643
- assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18643
+ const _range = toMatrix(range);
18644
+ assert(() => 1 <= _index && _index <= _range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18644
18645
  const getValueFromRange = (range, index) => range[index][0].value;
18645
18646
  const _isSorted = toBoolean(isSorted.value);
18646
18647
  const colIndex = _isSorted
18647
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
18648
- : linearSearch(range, searchKey, "wildcard", range.length, getValueFromRange);
18649
- const col = range[colIndex];
18648
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
18649
+ : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
18650
+ const col = _range[colIndex];
18650
18651
  if (col === undefined) {
18651
18652
  return valueNotAvailable(searchKey);
18652
18653
  }
@@ -18738,35 +18739,37 @@ const LOOKUP = {
18738
18739
  description: _t("Look up a value."),
18739
18740
  args: [
18740
18741
  arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18741
- 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.")),
18742
- 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.")),
18742
+ 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.")),
18743
+ 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.")),
18743
18744
  ],
18744
18745
  compute: function (searchKey, searchArray, resultRange) {
18745
- let nbCol = searchArray.length;
18746
- let nbRow = searchArray[0].length;
18746
+ const _searchArray = toMatrix(searchArray);
18747
+ const _resultRange = toMatrix(resultRange);
18748
+ let nbCol = _searchArray.length;
18749
+ let nbRow = _searchArray[0].length;
18747
18750
  const verticalSearch = nbRow >= nbCol;
18748
18751
  const getElement = verticalSearch
18749
18752
  ? (range, index) => range[0][index].value
18750
18753
  : (range, index) => range[index][0].value;
18751
18754
  const rangeLength = verticalSearch ? nbRow : nbCol;
18752
- const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
18755
+ const index = dichotomicSearch(_searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
18753
18756
  if (index === -1 ||
18754
- (verticalSearch && searchArray[0][index] === undefined) ||
18755
- (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
18757
+ (verticalSearch && _searchArray[0][index] === undefined) ||
18758
+ (!verticalSearch && _searchArray[index][nbRow - 1] === undefined)) {
18756
18759
  return valueNotAvailable(searchKey);
18757
18760
  }
18758
- if (resultRange === undefined) {
18759
- return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
18761
+ if (_resultRange[0].length === 0) {
18762
+ return verticalSearch ? _searchArray[nbCol - 1][index] : _searchArray[index][nbRow - 1];
18760
18763
  }
18761
- nbCol = resultRange.length;
18762
- nbRow = resultRange[0].length;
18764
+ nbCol = _resultRange.length;
18765
+ nbRow = _resultRange[0].length;
18763
18766
  assert(() => nbCol === 1 || nbRow === 1, _t("The result_range must be a single row or a single column."));
18764
18767
  if (nbCol > 1) {
18765
18768
  assert(() => index <= nbCol - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range row value %s.", (index + 1).toString()));
18766
- return resultRange[index][0];
18769
+ return _resultRange[index][0];
18767
18770
  }
18768
18771
  assert(() => index <= nbRow - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range column value %s.", (index + 1).toString()));
18769
- return resultRange[0][index];
18772
+ return _resultRange[0][index];
18770
18773
  },
18771
18774
  isExported: true,
18772
18775
  };
@@ -18783,28 +18786,29 @@ const MATCH = {
18783
18786
  ],
18784
18787
  compute: function (searchKey, range, searchType = { value: DEFAULT_SEARCH_TYPE }) {
18785
18788
  let _searchType = toNumber(searchType, this.locale);
18786
- const nbCol = range.length;
18787
- const nbRow = range[0].length;
18789
+ const _range = toMatrix(range);
18790
+ const nbCol = _range.length;
18791
+ const nbRow = _range[0].length;
18788
18792
  assert(() => nbCol === 1 || nbRow === 1, _t("The range must be a single row or a single column."));
18789
18793
  let index = -1;
18790
18794
  const getElement = nbCol === 1
18791
- ? (range, index) => range[0][index].value
18792
- : (range, index) => range[index][0].value;
18793
- const rangeLen = nbCol === 1 ? range[0].length : range.length;
18795
+ ? (_range, index) => _range[0][index].value
18796
+ : (_range, index) => _range[index][0].value;
18797
+ const rangeLen = nbCol === 1 ? _range[0].length : _range.length;
18794
18798
  _searchType = Math.sign(_searchType);
18795
18799
  switch (_searchType) {
18796
18800
  case 1:
18797
- index = dichotomicSearch(range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18801
+ index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18798
18802
  break;
18799
18803
  case 0:
18800
- index = linearSearch(range, searchKey, "wildcard", rangeLen, getElement);
18804
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
18801
18805
  break;
18802
18806
  case -1:
18803
- index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
18807
+ index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
18804
18808
  break;
18805
18809
  }
18806
- if ((nbCol === 1 && range[0][index] === undefined) ||
18807
- (nbCol !== 1 && range[index] === undefined)) {
18810
+ if ((nbCol === 1 && _range[0][index] === undefined) ||
18811
+ (nbCol !== 1 && _range[index] === undefined)) {
18808
18812
  return valueNotAvailable(searchKey);
18809
18813
  }
18810
18814
  return index + 1;
@@ -18859,13 +18863,14 @@ const VLOOKUP = {
18859
18863
  ],
18860
18864
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18861
18865
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18862
- assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18866
+ const _range = toMatrix(range);
18867
+ assert(() => 1 <= _index && _index <= _range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18863
18868
  const getValueFromRange = (range, index) => range[0][index].value;
18864
18869
  const _isSorted = toBoolean(isSorted.value);
18865
18870
  const rowIndex = _isSorted
18866
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
18867
- : linearSearch(range, searchKey, "wildcard", range[0].length, getValueFromRange);
18868
- const value = range[_index - 1][rowIndex];
18871
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
18872
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
18873
+ const value = _range[_index - 1][rowIndex];
18869
18874
  if (value === undefined) {
18870
18875
  return valueNotAvailable(searchKey);
18871
18876
  }
@@ -18902,27 +18907,29 @@ const XLOOKUP = {
18902
18907
  compute: function (searchKey, lookupRange, returnRange, defaultValue, matchMode = { value: DEFAULT_MATCH_MODE }, searchMode = { value: DEFAULT_SEARCH_MODE }) {
18903
18908
  const _matchMode = Math.trunc(toNumber(matchMode.value, this.locale));
18904
18909
  const _searchMode = Math.trunc(toNumber(searchMode.value, this.locale));
18905
- assert(() => lookupRange.length === 1 || lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
18910
+ const _lookupRange = toMatrix(lookupRange);
18911
+ const _returnRange = toMatrix(returnRange);
18912
+ assert(() => _lookupRange.length === 1 || _lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
18906
18913
  assert(() => [-1, 1, -2, 2].includes(_searchMode), _t("search_mode should be a value in [-1, 1, -2, 2]."));
18907
18914
  assert(() => [-1, 0, 1, 2].includes(_matchMode), _t("match_mode should be a value in [-1, 0, 1, 2]."));
18908
- const lookupDirection = lookupRange.length === 1 ? "col" : "row";
18915
+ const lookupDirection = _lookupRange.length === 1 ? "col" : "row";
18909
18916
  assert(() => !(_matchMode === 2 && [-2, 2].includes(_searchMode)), _t("the search and match mode combination is not supported for XLOOKUP evaluation."));
18910
18917
  assert(() => lookupDirection === "col"
18911
- ? returnRange[0].length === lookupRange[0].length
18912
- : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18918
+ ? _returnRange[0].length === _lookupRange[0].length
18919
+ : _returnRange.length === _lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18913
18920
  const getElement = lookupDirection === "col"
18914
18921
  ? (range, index) => range[0][index].value
18915
18922
  : (range, index) => range[index][0].value;
18916
- const rangeLen = lookupDirection === "col" ? lookupRange[0].length : lookupRange.length;
18923
+ const rangeLen = lookupDirection === "col" ? _lookupRange[0].length : _lookupRange.length;
18917
18924
  const mode = MATCH_MODE[_matchMode];
18918
18925
  const reverseSearch = _searchMode === -1;
18919
18926
  const index = _searchMode === 2 || _searchMode === -2
18920
- ? dichotomicSearch(lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18921
- : linearSearch(lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
18927
+ ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18928
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
18922
18929
  if (index !== -1) {
18923
18930
  return lookupDirection === "col"
18924
- ? returnRange.map((col) => [col[index]])
18925
- : [returnRange[index]];
18931
+ ? _returnRange.map((col) => [col[index]])
18932
+ : [_returnRange[index]];
18926
18933
  }
18927
18934
  if (defaultValue === undefined) {
18928
18935
  return valueNotAvailable(searchKey);
@@ -28458,11 +28465,7 @@ function interpolateData(config, values, labels, newLabels) {
28458
28465
  if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
28459
28466
  return [];
28460
28467
  }
28461
- const labelMin = Math.min(...labels);
28462
- const labelMax = Math.max(...labels);
28463
- const labelRange = labelMax - labelMin;
28464
- const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
28465
- const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
28468
+ const { normalizedLabels, normalizedNewLabels } = normalizeLabels(labels, newLabels, config);
28466
28469
  try {
28467
28470
  switch (config.type) {
28468
28471
  case "polynomial": {
@@ -28507,6 +28510,30 @@ function interpolateData(config, values, labels, newLabels) {
28507
28510
  return newLabels.map((x) => ({ x, y: NaN }));
28508
28511
  }
28509
28512
  }
28513
+ function normalizeLabels(labels, newLabels, config) {
28514
+ let normalizedLabels = [];
28515
+ let normalizedNewLabels = [];
28516
+ if (config.type === "logarithmic") {
28517
+ // Logarithmic trends in charts are used to visualize proportional growth or
28518
+ // relative changes. Therefore, we change the normalization technique for
28519
+ // logarithmic trend lines for a better fit. The method used here is Max Absolute
28520
+ // Scaling. This Technique is ideal for data spanning several orders of magnitude,
28521
+ // as it balances differences between small and large values by compressing larger
28522
+ // values while preserving proportionality and ensuring all values are scaled relative
28523
+ // to the largest magnitude.
28524
+ const labelMax = Math.max(...labels.map(Math.abs));
28525
+ normalizedLabels = labels.map((l) => l / labelMax);
28526
+ normalizedNewLabels = newLabels.map((l) => l / labelMax);
28527
+ }
28528
+ else {
28529
+ const labelMax = Math.max(...labels);
28530
+ const labelMin = Math.min(...labels);
28531
+ const labelRange = labelMax - labelMin;
28532
+ normalizedLabels = labels.map((l) => (l - labelMax) / labelRange);
28533
+ normalizedNewLabels = newLabels.map((l) => (l - labelMax) / labelRange);
28534
+ }
28535
+ return { normalizedLabels, normalizedNewLabels };
28536
+ }
28510
28537
  function getChartAxisType(chart, labelRange, getters) {
28511
28538
  if (isDateChart(chart, labelRange, getters) && isLuxonTimeAdapterInstalled()) {
28512
28539
  return "time";
@@ -32538,36 +32565,58 @@ class ErrorToolTip extends owl.Component {
32538
32565
  static maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
32539
32566
  static template = "o-spreadsheet-ErrorToolTip";
32540
32567
  static props = {
32541
- errors: Array,
32568
+ cellPosition: Object,
32542
32569
  onClosed: { type: Function, optional: true },
32543
32570
  };
32571
+ get dataValidationErrorMessage() {
32572
+ return this.env.model.getters.getInvalidDataValidationMessage(this.props.cellPosition);
32573
+ }
32574
+ get evaluationError() {
32575
+ const cell = this.env.model.getters.getEvaluatedCell(this.props.cellPosition);
32576
+ if (cell.message) {
32577
+ return cell;
32578
+ }
32579
+ return undefined;
32580
+ }
32581
+ get errorOriginPositionString() {
32582
+ const evaluationError = this.evaluationError;
32583
+ const position = evaluationError?.errorOriginPosition;
32584
+ if (!position || deepEquals(position, this.props.cellPosition)) {
32585
+ return "";
32586
+ }
32587
+ const sheetId = position.sheetId;
32588
+ return this.env.model.getters.getRangeString(this.env.model.getters.getRangeFromZone(sheetId, positionToZone(position)), this.env.model.getters.getActiveSheetId());
32589
+ }
32590
+ selectCell() {
32591
+ const position = this.evaluationError?.errorOriginPosition;
32592
+ if (!position) {
32593
+ return;
32594
+ }
32595
+ const activeSheetId = this.env.model.getters.getActiveSheetId();
32596
+ if (position.sheetId !== activeSheetId) {
32597
+ this.env.model.dispatch("ACTIVATE_SHEET", {
32598
+ sheetIdFrom: activeSheetId,
32599
+ sheetIdTo: position.sheetId,
32600
+ });
32601
+ }
32602
+ this.env.model.selection.selectCell(position.col, position.row);
32603
+ }
32544
32604
  }
32545
32605
  const ErrorToolTipPopoverBuilder = {
32546
32606
  onHover: (position, getters) => {
32547
32607
  const cell = getters.getEvaluatedCell(position);
32548
- const errors = [];
32549
- if (cell.type === CellValueType.error && !!cell.message) {
32550
- errors.push({
32551
- title: _t("Error"),
32552
- message: cell.message,
32553
- });
32554
- }
32555
- const validationErrorMessage = getters.getInvalidDataValidationMessage(position);
32556
- if (validationErrorMessage) {
32557
- errors.push({
32558
- title: _t("Invalid"),
32559
- message: validationErrorMessage,
32560
- });
32561
- }
32562
- if (!errors.length) {
32563
- return { isOpen: false };
32608
+ if ((cell.type === CellValueType.error && !!cell.message) ||
32609
+ getters.getInvalidDataValidationMessage(position)) {
32610
+ return {
32611
+ isOpen: true,
32612
+ props: {
32613
+ cellPosition: position,
32614
+ },
32615
+ Component: ErrorToolTip,
32616
+ cellCorner: "TopRight",
32617
+ };
32564
32618
  }
32565
- return {
32566
- isOpen: true,
32567
- props: { errors: errors },
32568
- Component: ErrorToolTip,
32569
- cellCorner: "TopRight",
32570
- };
32619
+ return { isOpen: false };
32571
32620
  },
32572
32621
  };
32573
32622
 
@@ -33131,6 +33180,100 @@ function* iterateChildren(el) {
33131
33180
  function getOpenedMenus() {
33132
33181
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33133
33182
  }
33183
+ function getCurrentSelection(el) {
33184
+ let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33185
+ let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33186
+ let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33187
+ return {
33188
+ start: startSizeBefore,
33189
+ end: endSizeBefore,
33190
+ };
33191
+ }
33192
+ function getStartAndEndSelection(el) {
33193
+ const selection = document.getSelection();
33194
+ return {
33195
+ startElement: selection.anchorNode || el,
33196
+ startSelectionOffset: selection.anchorOffset,
33197
+ endElement: selection.focusNode || el,
33198
+ endSelectionOffset: selection.focusOffset,
33199
+ };
33200
+ }
33201
+ /**
33202
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33203
+ * The selected node is either a Text node or an Element node.
33204
+ *
33205
+ * case 1 -Text node:
33206
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
33207
+ * content length of all previous nodes.
33208
+ *
33209
+ * case 2 - Element node:
33210
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
33211
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
33212
+ *
33213
+ * See the MDN documentation for more details.
33214
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33215
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33216
+ *
33217
+ */
33218
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
33219
+ let usedCharacters = 0;
33220
+ let it = iterateChildren(el);
33221
+ let current = it.next();
33222
+ let isFirstParagraph = true;
33223
+ while (!current.done && current.value !== nodeToFind) {
33224
+ if (!current.value.hasChildNodes()) {
33225
+ if (current.value.textContent) {
33226
+ usedCharacters += current.value.textContent.length;
33227
+ }
33228
+ }
33229
+ // One new paragraph = one new line character, except for the first paragraph
33230
+ if (current.value.nodeName === "P" ||
33231
+ (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33232
+ ) {
33233
+ if (isFirstParagraph) {
33234
+ isFirstParagraph = false;
33235
+ }
33236
+ else {
33237
+ usedCharacters++;
33238
+ }
33239
+ }
33240
+ current = it.next();
33241
+ }
33242
+ if (current.value !== nodeToFind) {
33243
+ /** This situation can happen if the code is called while the selection is not currently on the element.
33244
+ * In this case, we return 0 because we don't know the size of the text before the selection.
33245
+ *
33246
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33247
+ */
33248
+ return 0;
33249
+ }
33250
+ else {
33251
+ if (!current.value.hasChildNodes()) {
33252
+ usedCharacters += nodeOffset;
33253
+ }
33254
+ else {
33255
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
33256
+ usedCharacters += children.reduce((acc, child, index) => {
33257
+ if (child.textContent !== null) {
33258
+ // need to account for paragraph nodes that implicitly add a new line
33259
+ // except for the last paragraph
33260
+ let chars = child.textContent.length;
33261
+ if (child.nodeName === "P" && index !== children.length - 1) {
33262
+ chars++;
33263
+ }
33264
+ return acc + chars;
33265
+ }
33266
+ else {
33267
+ return acc;
33268
+ }
33269
+ }, 0);
33270
+ }
33271
+ }
33272
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33273
+ usedCharacters++;
33274
+ }
33275
+ return usedCharacters;
33276
+ }
33134
33277
  const letterRegex = /^[a-zA-Z]$/;
33135
33278
  /**
33136
33279
  * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
@@ -37058,7 +37201,7 @@ function dragAndDropBeyondTheViewport(env, cbMouseMove, cbMouseUp, only = false)
37058
37201
  }
37059
37202
  const { x: offsetCorrectionX, y: offsetCorrectionY } = getters.getMainViewportCoordinates();
37060
37203
  let { top, left, bottom, right } = getters.getActiveMainViewport();
37061
- let { scrollX, scrollY } = getters.getActiveSheetDOMScrollInfo();
37204
+ let { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
37062
37205
  const { xSplit, ySplit } = getters.getPaneDivisions(sheetId);
37063
37206
  let canEdgeScroll = false;
37064
37207
  let timeoutDelay = MAX_DELAY;
@@ -40338,6 +40481,10 @@ class ContentEditableHelper {
40338
40481
  if (currentStart === start && currentEnd === end) {
40339
40482
  return;
40340
40483
  }
40484
+ if (selection.rangeCount === 0) {
40485
+ const range = document.createRange();
40486
+ selection.addRange(range);
40487
+ }
40341
40488
  const currentRange = selection.getRangeAt(0);
40342
40489
  let range;
40343
40490
  if (this.el.contains(currentRange.startContainer)) {
@@ -40500,7 +40647,7 @@ class ContentEditableHelper {
40500
40647
  if (!focusedNode || !this.el.contains(focusedNode))
40501
40648
  return;
40502
40649
  const element = focusedNode instanceof HTMLElement ? focusedNode : focusedNode.parentElement;
40503
- element?.scrollIntoView({ block: "nearest" });
40650
+ element?.scrollIntoView?.({ block: "nearest" });
40504
40651
  }
40505
40652
  /**
40506
40653
  * remove the current selection of the user
@@ -40520,100 +40667,7 @@ class ContentEditableHelper {
40520
40667
  * finds the indexes of the current selection.
40521
40668
  * */
40522
40669
  getCurrentSelection() {
40523
- let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();
40524
- let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
40525
- let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
40526
- return {
40527
- start: startSizeBefore,
40528
- end: endSizeBefore,
40529
- };
40530
- }
40531
- /**
40532
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
40533
- * The selected node is either a Text node or an Element node.
40534
- *
40535
- * case 1 -Text node:
40536
- * the offset is the number of characters from the start of the node. We have to add this offset to the
40537
- * content length of all previous nodes.
40538
- *
40539
- * case 2 - Element node:
40540
- * the offset is the number of child nodes before the selected node. We have to add the content length of
40541
- * all the bnodes prior to the selected node as well as the content of the child node before the offset.
40542
- *
40543
- * See the MDN documentation for more details.
40544
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
40545
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
40546
- *
40547
- */
40548
- findSelectionIndex(nodeToFind, nodeOffset) {
40549
- let usedCharacters = 0;
40550
- let it = iterateChildren(this.el);
40551
- let current = it.next();
40552
- let isFirstParagraph = true;
40553
- while (!current.done && current.value !== nodeToFind) {
40554
- if (!current.value.hasChildNodes()) {
40555
- if (current.value.textContent) {
40556
- usedCharacters += current.value.textContent.length;
40557
- }
40558
- }
40559
- // One new paragraph = one new line character, except for the first paragraph
40560
- if (current.value.nodeName === "P" ||
40561
- (current.value.nodeName === "DIV" && current.value !== this.el) // On paste, the HTML may contain <div> instead of <p>
40562
- ) {
40563
- if (isFirstParagraph) {
40564
- isFirstParagraph = false;
40565
- }
40566
- else {
40567
- usedCharacters++;
40568
- }
40569
- }
40570
- current = it.next();
40571
- }
40572
- if (current.value !== nodeToFind) {
40573
- /** This situation can happen if the code is called while the selection is not currently on the ContentEditableHelper.
40574
- * In this case, we return 0 because we don't know the size of the text before the selection.
40575
- *
40576
- * A known occurence is triggered since the introduction of commit d4663158 (PR #2038).
40577
- *
40578
- * FIXME: find a way to test eventhough the selection API is not available in jsDOM.
40579
- */
40580
- return 0;
40581
- }
40582
- else {
40583
- if (!current.value.hasChildNodes()) {
40584
- usedCharacters += nodeOffset;
40585
- }
40586
- else {
40587
- const children = [...current.value.childNodes].slice(0, nodeOffset);
40588
- usedCharacters += children.reduce((acc, child, index) => {
40589
- if (child.textContent !== null) {
40590
- // need to account for paragraph nodes that implicitely add a new line
40591
- // except for the last paragraph
40592
- let chars = child.textContent.length;
40593
- if (child.nodeName === "P" && index !== children.length - 1) {
40594
- chars++;
40595
- }
40596
- return acc + chars;
40597
- }
40598
- else {
40599
- return acc;
40600
- }
40601
- }, 0);
40602
- }
40603
- }
40604
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
40605
- usedCharacters++;
40606
- }
40607
- return usedCharacters;
40608
- }
40609
- getStartAndEndSelection() {
40610
- const selection = document.getSelection();
40611
- return {
40612
- startElement: selection.anchorNode || this.el,
40613
- startSelectionOffset: selection.anchorOffset,
40614
- endElement: selection.focusNode || this.el,
40615
- endSelectionOffset: selection.focusOffset,
40616
- };
40670
+ return getCurrentSelection(this.el);
40617
40671
  }
40618
40672
  getText() {
40619
40673
  let text = "";
@@ -40877,6 +40931,12 @@ class Composer extends owl.Component {
40877
40931
  }
40878
40932
  this.contentHelper.updateEl(el);
40879
40933
  });
40934
+ this.env.model.selection.observe(this, {
40935
+ handleEvent: () => this.autoCompleteState.hide(),
40936
+ });
40937
+ owl.onWillUnmount(() => {
40938
+ this.env.model.selection.detachObserver(this);
40939
+ });
40880
40940
  owl.useEffect(() => {
40881
40941
  this.processContent();
40882
40942
  if (document.activeElement === this.contentHelper.el &&
@@ -41314,6 +41374,16 @@ class StandaloneComposerStore extends AbstractComposerStore {
41314
41374
  }
41315
41375
  return providersDefinitions;
41316
41376
  }
41377
+ /**
41378
+ * Replace the current reference selected by the new one.
41379
+ * */
41380
+ getZoneReference(zone) {
41381
+ const res = super.getZoneReference(zone);
41382
+ if (this.args().defaultStatic) {
41383
+ return setXcToFixedReferenceType(res, "colrow");
41384
+ }
41385
+ return res;
41386
+ }
41317
41387
  getComposerContent() {
41318
41388
  if (this.editionMode === "inactive") {
41319
41389
  // References in the content might not be linked to the current active sheet
@@ -41379,6 +41449,7 @@ class StandaloneComposer extends owl.Component {
41379
41449
  static props = {
41380
41450
  composerContent: { type: String, optional: true },
41381
41451
  defaultRangeSheetId: { type: String, optional: true },
41452
+ defaultStatic: { type: Boolean, optional: true },
41382
41453
  onConfirm: Function,
41383
41454
  contextualAutocomplete: { type: Object, optional: true },
41384
41455
  placeholder: { type: String, optional: true },
@@ -41389,6 +41460,7 @@ class StandaloneComposer extends owl.Component {
41389
41460
  static components = { Composer };
41390
41461
  static defaultProps = {
41391
41462
  composerContent: "",
41463
+ defaultStatic: false,
41392
41464
  };
41393
41465
  composerFocusStore;
41394
41466
  standaloneComposerStore;
@@ -41399,6 +41471,7 @@ class StandaloneComposer extends owl.Component {
41399
41471
  const standaloneComposerStore = useLocalStore(StandaloneComposerStore, () => ({
41400
41472
  onConfirm: this.props.onConfirm,
41401
41473
  content: this.props.composerContent,
41474
+ defaultStatic: this.props.defaultStatic ?? false,
41402
41475
  contextualAutocomplete: this.props.contextualAutocomplete,
41403
41476
  defaultRangeSheetId: this.props.defaultRangeSheetId,
41404
41477
  getContextualColoredSymbolToken: this.props.getContextualColoredSymbolToken,
@@ -42134,6 +42207,7 @@ class ConditionalFormattingEditor extends owl.Component {
42134
42207
  },
42135
42208
  composerContent: this.state.rules.cellIs.values[valueIndex],
42136
42209
  placeholder: _t("Value or formula"),
42210
+ defaultStatic: true,
42137
42211
  invalid: isInvalid,
42138
42212
  class: "o-sidePanel-composer",
42139
42213
  defaultRangeSheetId: this.env.model.getters.getActiveSheetId(),
@@ -42152,6 +42226,7 @@ class ConditionalFormattingEditor extends owl.Component {
42152
42226
  },
42153
42227
  composerContent: threshold.value || "",
42154
42228
  placeholder: _t("Formula"),
42229
+ defaultStatic: true,
42155
42230
  invalid: isInvalid,
42156
42231
  class: "o-sidePanel-composer",
42157
42232
  defaultRangeSheetId: this.env.model.getters.getActiveSheetId(),
@@ -42167,6 +42242,7 @@ class ConditionalFormattingEditor extends owl.Component {
42167
42242
  },
42168
42243
  composerContent: inflection.value || "",
42169
42244
  placeholder: _t("Formula"),
42245
+ defaultStatic: true,
42170
42246
  invalid: isInvalid,
42171
42247
  class: "o-sidePanel-composer",
42172
42248
  defaultRangeSheetId: this.env.model.getters.getActiveSheetId(),
@@ -49216,7 +49292,7 @@ function isAxisVisible(getters, figure, axis) {
49216
49292
  axisStartEndPositions.push({ x: axis.position, y: figure.y + figure.height });
49217
49293
  break;
49218
49294
  }
49219
- return axisStartEndPositions.some(getters.isPositionVisible);
49295
+ return axisStartEndPositions.some(getters.isPixelPositionVisible);
49220
49296
  }
49221
49297
  /**
49222
49298
  * Get a snap line for the given figure, if the figure can snap to any other figure
@@ -49775,7 +49851,7 @@ class GridAddRowsFooter extends owl.Component {
49775
49851
  });
49776
49852
  this.props.focusGrid();
49777
49853
  // After adding new rows, scroll down to the new last row
49778
- const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();
49854
+ const { scrollX } = this.env.model.getters.getActiveSheetScrollInfo();
49779
49855
  const { end } = this.env.model.getters.getRowDimensions(activeSheetId, rowNumber + quantity - 1);
49780
49856
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
49781
49857
  offsetX: scrollX,
@@ -50033,7 +50109,7 @@ class GridOverlay extends owl.Component {
50033
50109
  resizeObserver.disconnect();
50034
50110
  });
50035
50111
  useTouchMove(this.gridOverlay, this.props.onGridMoved, () => {
50036
- const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
50112
+ const { scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
50037
50113
  return scrollY > 0;
50038
50114
  });
50039
50115
  this.cellPopovers = useStore(CellPopoverStore);
@@ -50687,17 +50763,6 @@ class GridRenderer {
50687
50763
  get renderingLayers() {
50688
50764
  return ["Background", "Headers"];
50689
50765
  }
50690
- /**
50691
- * Get the offset of a header (see getColRowOffsetInViewport), adjusted with the header
50692
- * size (HEADER_HEIGHT and HEADER_WIDTH)
50693
- */
50694
- getHeaderOffset(dimension, start, index) {
50695
- let size = this.getters.getColRowOffsetInViewport(dimension, start, index);
50696
- if (!this.getters.isDashboard()) {
50697
- size += dimension === "ROW" ? HEADER_HEIGHT : HEADER_WIDTH;
50698
- }
50699
- return size;
50700
- }
50701
50766
  // ---------------------------------------------------------------------------
50702
50767
  // Grid rendering
50703
50768
  // ---------------------------------------------------------------------------
@@ -50705,11 +50770,10 @@ class GridRenderer {
50705
50770
  switch (layer) {
50706
50771
  case "Background":
50707
50772
  this.drawGlobalBackground(renderingContext);
50708
- for (const zone of this.getters.getAllActiveViewportsZones()) {
50773
+ for (const { zone, rect } of this.getters.getAllActiveViewportsZonesAndRect()) {
50709
50774
  const { ctx } = renderingContext;
50710
50775
  ctx.save();
50711
50776
  ctx.beginPath();
50712
- const rect = this.getters.getVisibleRect(zone);
50713
50777
  ctx.rect(rect.x, rect.y, rect.width, rect.height);
50714
50778
  ctx.clip();
50715
50779
  const boxes = this.getGridBoxes(zone);
@@ -50985,10 +51049,8 @@ class GridRenderer {
50985
51049
  const { ctx, thinLineWidth } = renderingContext;
50986
51050
  const visibleCols = this.getters.getSheetViewVisibleCols();
50987
51051
  const left = visibleCols[0];
50988
- const right = visibleCols[visibleCols.length - 1];
50989
51052
  const visibleRows = this.getters.getSheetViewVisibleRows();
50990
51053
  const top = visibleRows[0];
50991
- const bottom = visibleRows[visibleRows.length - 1];
50992
51054
  const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
50993
51055
  const selection = this.getters.getSelectedZones();
50994
51056
  const selectedCols = getZonesCols(selection);
@@ -51004,7 +51066,7 @@ class GridRenderer {
51004
51066
  ctx.lineWidth = thinLineWidth;
51005
51067
  ctx.strokeStyle = "#333";
51006
51068
  // Columns headers background
51007
- for (let col = left; col <= right; col++) {
51069
+ for (const col of visibleCols) {
51008
51070
  const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
51009
51071
  const { x, width } = this.getters.getVisibleRect(colZone);
51010
51072
  const isColActive = activeCols.has(col);
@@ -51021,7 +51083,7 @@ class GridRenderer {
51021
51083
  ctx.fillRect(x, 0, width, HEADER_HEIGHT);
51022
51084
  }
51023
51085
  // Rows headers background
51024
- for (let row = top; row <= bottom; row++) {
51086
+ for (const row of visibleRows) {
51025
51087
  const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
51026
51088
  const { y, height } = this.getters.getVisibleRect(rowZone);
51027
51089
  const isRowActive = activeRows.has(row);
@@ -51045,27 +51107,41 @@ class GridRenderer {
51045
51107
  ctx.lineTo(width, HEADER_HEIGHT);
51046
51108
  ctx.strokeStyle = HEADER_BORDER_COLOR;
51047
51109
  ctx.stroke();
51048
- ctx.beginPath();
51049
51110
  // column text + separator
51050
- for (const i of visibleCols) {
51051
- const colSize = this.getters.getColSize(sheetId, i);
51052
- const colName = numberToLetters(i);
51053
- ctx.fillStyle = activeCols.has(i) ? "#fff" : TEXT_HEADER_COLOR;
51054
- let colStart = this.getHeaderOffset("COL", left, i);
51111
+ for (const col of visibleCols) {
51112
+ const colName = numberToLetters(col);
51113
+ ctx.fillStyle = activeCols.has(col) ? "#fff" : TEXT_HEADER_COLOR;
51114
+ const zone = { left: col, right: col, top: top, bottom: top };
51115
+ const { x: colStart, width: colSize } = this.getters.getRect(zone);
51116
+ const { x, width } = this.getters.getVisibleRect(zone);
51117
+ ctx.save();
51118
+ ctx.beginPath();
51119
+ ctx.rect(x, 0, width, HEADER_HEIGHT);
51120
+ ctx.clip();
51055
51121
  ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);
51122
+ ctx.restore();
51123
+ ctx.beginPath();
51056
51124
  ctx.moveTo(colStart + colSize, 0);
51057
51125
  ctx.lineTo(colStart + colSize, HEADER_HEIGHT);
51126
+ ctx.stroke();
51058
51127
  }
51059
51128
  // row text + separator
51060
- for (const i of visibleRows) {
51061
- const rowSize = this.getters.getRowSize(sheetId, i);
51062
- ctx.fillStyle = activeRows.has(i) ? "#fff" : TEXT_HEADER_COLOR;
51063
- let rowStart = this.getHeaderOffset("ROW", top, i);
51064
- ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
51129
+ for (const row of visibleRows) {
51130
+ ctx.fillStyle = activeRows.has(row) ? "#fff" : TEXT_HEADER_COLOR;
51131
+ const zone = { top: row, bottom: row, left: left, right: left };
51132
+ const { y: rowStart, height: rowSize } = this.getters.getRect(zone);
51133
+ const { y, height } = this.getters.getVisibleRect(zone);
51134
+ ctx.save();
51135
+ ctx.beginPath();
51136
+ ctx.rect(0, y, HEADER_WIDTH, height);
51137
+ ctx.clip();
51138
+ ctx.fillText(String(row + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
51139
+ ctx.restore();
51140
+ ctx.beginPath();
51065
51141
  ctx.moveTo(0, rowStart + rowSize);
51066
51142
  ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);
51143
+ ctx.stroke();
51067
51144
  }
51068
- ctx.stroke();
51069
51145
  }
51070
51146
  drawFrozenPanesHeaders(renderingContext) {
51071
51147
  const { ctx, thinLineWidth } = renderingContext;
@@ -51243,8 +51319,8 @@ class GridRenderer {
51243
51319
  previousColIndex = col;
51244
51320
  }
51245
51321
  else {
51246
- nextColIndex = this.findNextEmptyCol(col, right, row);
51247
- previousColIndex = this.findPreviousEmptyCol(col, left, row);
51322
+ nextColIndex = box.border?.right ? zone.right : this.findNextEmptyCol(col, right, row);
51323
+ previousColIndex = box.border?.left ? zone.left : this.findPreviousEmptyCol(col, left, row);
51248
51324
  box.isOverflow = true;
51249
51325
  }
51250
51326
  switch (align) {
@@ -51368,6 +51444,9 @@ function useGridDrawing(refName, model, canvasSize) {
51368
51444
  canvas.width = width * dpr;
51369
51445
  canvas.height = height * dpr;
51370
51446
  canvas.setAttribute("style", `width:${width}px;height:${height}px;`);
51447
+ if (width === 0 || height === 0) {
51448
+ return;
51449
+ }
51371
51450
  // Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2…)
51372
51451
  // are the edges of the squares. If you draw a one-unit-wide line between whole-number
51373
51452
  // coordinates, it will overlap opposite sides of the pixel square, and the resulting
@@ -51711,7 +51790,7 @@ class HorizontalScrollBar extends owl.Component {
51711
51790
  leftOffset: 0,
51712
51791
  };
51713
51792
  get offset() {
51714
- return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollX;
51793
+ return this.env.model.getters.getActiveSheetScrollInfo().scrollX;
51715
51794
  }
51716
51795
  get width() {
51717
51796
  return this.env.model.getters.getMainViewportRect().width;
@@ -51730,7 +51809,7 @@ class HorizontalScrollBar extends owl.Component {
51730
51809
  };
51731
51810
  }
51732
51811
  onScroll(offset) {
51733
- const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
51812
+ const { scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
51734
51813
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
51735
51814
  offsetX: offset,
51736
51815
  offsetY: scrollY, // offsetY is the same
@@ -51756,7 +51835,7 @@ class VerticalScrollBar extends owl.Component {
51756
51835
  topOffset: 0,
51757
51836
  };
51758
51837
  get offset() {
51759
- return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollY;
51838
+ return this.env.model.getters.getActiveSheetScrollInfo().scrollY;
51760
51839
  }
51761
51840
  get height() {
51762
51841
  return this.env.model.getters.getMainViewportRect().height;
@@ -51775,7 +51854,7 @@ class VerticalScrollBar extends owl.Component {
51775
51854
  };
51776
51855
  }
51777
51856
  onScroll(offset) {
51778
- const { scrollX } = this.env.model.getters.getActiveSheetDOMScrollInfo();
51857
+ const { scrollX } = this.env.model.getters.getActiveSheetScrollInfo();
51779
51858
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
51780
51859
  offsetX: scrollX, // offsetX is the same
51781
51860
  offsetY: offset,
@@ -52246,7 +52325,7 @@ class Grid extends owl.Component {
52246
52325
  });
52247
52326
  }
52248
52327
  moveCanvas(deltaX, deltaY) {
52249
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
52328
+ const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
52250
52329
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
52251
52330
  offsetX: scrollX + deltaX,
52252
52331
  offsetY: scrollY + deltaY,
@@ -52717,7 +52796,7 @@ class BordersPlugin extends CorePlugin {
52717
52796
  // map and slice preserve empty values and do not set `undefined` instead
52718
52797
  const bordersCopy = borders
52719
52798
  .slice()
52720
- .map((col) => col?.slice().map((border) => ({ ...border })));
52799
+ .map((col) => col?.slice().map((border) => deepCopy(border)));
52721
52800
  this.history.update("borders", cmd.sheetIdTo, bordersCopy);
52722
52801
  }
52723
52802
  break;
@@ -52747,32 +52826,12 @@ class BordersPlugin extends CorePlugin {
52747
52826
  const elements = [...cmd.elements].sort((a, b) => b - a);
52748
52827
  for (const group of groupConsecutive(elements)) {
52749
52828
  if (cmd.dimension === "COL") {
52750
- if (group[0] >= this.getters.getNumberCols(cmd.sheetId)) {
52751
- for (let row = 0; row < this.getters.getNumberRows(cmd.sheetId); row++) {
52752
- this.history.update("borders", cmd.sheetId, group[0] + 1, row, "vertical", undefined);
52753
- }
52754
- }
52755
- if (group[group.length - 1] === 0) {
52756
- for (let row = 0; row < this.getters.getNumberRows(cmd.sheetId); row++) {
52757
- this.history.update("borders", cmd.sheetId, 0, row, "vertical", undefined);
52758
- }
52759
- }
52760
- const zone = this.getters.getColsZone(cmd.sheetId, group[group.length - 1] + 1, group[0]);
52829
+ const zone = this.getters.getColsZone(cmd.sheetId, group[group.length - 1], group[0]);
52761
52830
  this.clearInsideBorders(cmd.sheetId, [zone]);
52762
52831
  this.shiftBordersHorizontally(cmd.sheetId, group[0] + 1, -group.length);
52763
52832
  }
52764
52833
  else {
52765
- if (group[0] >= this.getters.getNumberRows(cmd.sheetId)) {
52766
- for (let col = 0; col < this.getters.getNumberCols(cmd.sheetId); col++) {
52767
- this.history.update("borders", cmd.sheetId, col, group[0] + 1, "horizontal", undefined);
52768
- }
52769
- }
52770
- if (group[group.length - 1] === 0) {
52771
- for (let col = 0; col < this.getters.getNumberCols(cmd.sheetId); col++) {
52772
- this.history.update("borders", cmd.sheetId, col, 0, "horizontal", undefined);
52773
- }
52774
- }
52775
- const zone = this.getters.getRowsZone(cmd.sheetId, group[group.length - 1] + 1, group[0]);
52834
+ const zone = this.getters.getRowsZone(cmd.sheetId, group[group.length - 1], group[0]);
52776
52835
  this.clearInsideBorders(cmd.sheetId, [zone]);
52777
52836
  this.shiftBordersVertically(cmd.sheetId, group[0] + 1, -group.length);
52778
52837
  }
@@ -52797,16 +52856,12 @@ class BordersPlugin extends CorePlugin {
52797
52856
  let colLeftOfInsertion;
52798
52857
  let colRightOfInsertion;
52799
52858
  if (cmd.position === "before") {
52800
- this.shiftBordersHorizontally(cmd.sheetId, cmd.base, cmd.quantity, {
52801
- moveFirstLeftBorder: true,
52802
- });
52859
+ this.shiftBordersHorizontally(cmd.sheetId, cmd.base, cmd.quantity);
52803
52860
  colLeftOfInsertion = cmd.base - 1;
52804
52861
  colRightOfInsertion = cmd.base + cmd.quantity;
52805
52862
  }
52806
52863
  else {
52807
- this.shiftBordersHorizontally(cmd.sheetId, cmd.base + 1, cmd.quantity, {
52808
- moveFirstLeftBorder: false,
52809
- });
52864
+ this.shiftBordersHorizontally(cmd.sheetId, cmd.base + 1, cmd.quantity);
52810
52865
  colLeftOfInsertion = cmd.base;
52811
52866
  colRightOfInsertion = cmd.base + cmd.quantity + 1;
52812
52867
  }
@@ -52821,16 +52876,12 @@ class BordersPlugin extends CorePlugin {
52821
52876
  let rowAboveInsertion;
52822
52877
  let rowBelowInsertion;
52823
52878
  if (cmd.position === "before") {
52824
- this.shiftBordersVertically(cmd.sheetId, cmd.base, cmd.quantity, {
52825
- moveFirstTopBorder: true,
52826
- });
52879
+ this.shiftBordersVertically(cmd.sheetId, cmd.base, cmd.quantity);
52827
52880
  rowAboveInsertion = cmd.base - 1;
52828
52881
  rowBelowInsertion = cmd.base + cmd.quantity;
52829
52882
  }
52830
52883
  else {
52831
- this.shiftBordersVertically(cmd.sheetId, cmd.base + 1, cmd.quantity, {
52832
- moveFirstTopBorder: false,
52833
- });
52884
+ this.shiftBordersVertically(cmd.sheetId, cmd.base + 1, cmd.quantity);
52834
52885
  rowAboveInsertion = cmd.base;
52835
52886
  rowBelowInsertion = cmd.base + cmd.quantity + 1;
52836
52887
  }
@@ -52840,16 +52891,8 @@ class BordersPlugin extends CorePlugin {
52840
52891
  // Getters
52841
52892
  // ---------------------------------------------------------------------------
52842
52893
  getCellBorder({ sheetId, col, row }) {
52843
- const border = {
52844
- top: this.borders[sheetId]?.[col]?.[row]?.horizontal,
52845
- bottom: this.borders[sheetId]?.[col]?.[row + 1]?.horizontal,
52846
- left: this.borders[sheetId]?.[col]?.[row]?.vertical,
52847
- right: this.borders[sheetId]?.[col + 1]?.[row]?.vertical,
52848
- };
52849
- if (!border.bottom && !border.left && !border.right && !border.top) {
52850
- return null;
52851
- }
52852
- return border;
52894
+ const border = this.borders[sheetId]?.[col]?.[row];
52895
+ return border?.top || border?.bottom || border?.left || border?.right ? deepCopy(border) : null;
52853
52896
  }
52854
52897
  getBordersColors(sheetId) {
52855
52898
  const colors = [];
@@ -52857,11 +52900,13 @@ class BordersPlugin extends CorePlugin {
52857
52900
  if (sheetBorders) {
52858
52901
  for (const borders of sheetBorders.filter(isDefined)) {
52859
52902
  for (const cellBorder of borders) {
52860
- if (cellBorder?.horizontal) {
52861
- colors.push(cellBorder.horizontal.color);
52862
- }
52863
- if (cellBorder?.vertical) {
52864
- colors.push(cellBorder.vertical.color);
52903
+ if (cellBorder) {
52904
+ for (const direction of ["top", "bottom", "left", "right"]) {
52905
+ const color = cellBorder[direction]?.color;
52906
+ if (color) {
52907
+ colors.push(color);
52908
+ }
52909
+ }
52865
52910
  }
52866
52911
  }
52867
52912
  }
@@ -52914,7 +52959,7 @@ class BordersPlugin extends CorePlugin {
52914
52959
  getCommonSides(border1, border2) {
52915
52960
  const commonBorder = {};
52916
52961
  for (let side of ["top", "bottom", "left", "right"]) {
52917
- if (border1[side] && border1[side] === border2[side]) {
52962
+ if (border1[side] && deepEquals(border1[side], border2[side])) {
52918
52963
  commonBorder[side] = border1[side];
52919
52964
  }
52920
52965
  }
@@ -52959,23 +53004,15 @@ class BordersPlugin extends CorePlugin {
52959
53004
  * @param start starting column (included)
52960
53005
  * @param delta how much borders will be moved (negative if moved to the left)
52961
53006
  */
52962
- shiftBordersHorizontally(sheetId, start, delta, { moveFirstLeftBorder } = {}) {
53007
+ shiftBordersHorizontally(sheetId, start, delta) {
52963
53008
  const borders = this.borders[sheetId];
52964
53009
  if (!borders)
52965
53010
  return;
52966
- if (delta < 0) {
52967
- this.moveBordersOfColumn(sheetId, start, delta, "vertical", {
52968
- destructive: false,
52969
- });
52970
- }
52971
53011
  this.getColumnsWithBorders(sheetId)
52972
53012
  .filter((col) => col >= start)
52973
53013
  .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
52974
53014
  .forEach((col) => {
52975
- if ((col === start && moveFirstLeftBorder) || col !== start) {
52976
- this.moveBordersOfColumn(sheetId, col, delta, "vertical");
52977
- }
52978
- this.moveBordersOfColumn(sheetId, col, delta, "horizontal");
53015
+ this.moveBordersOfColumn(sheetId, col, delta);
52979
53016
  });
52980
53017
  }
52981
53018
  /**
@@ -52984,12 +53021,12 @@ class BordersPlugin extends CorePlugin {
52984
53021
  * @param start starting row (included)
52985
53022
  * @param delta how much borders will be moved (negative if moved to the above)
52986
53023
  */
52987
- shiftBordersVertically(sheetId, start, delta, { moveFirstTopBorder } = {}) {
53024
+ shiftBordersVertically(sheetId, start, delta) {
52988
53025
  const borders = this.borders[sheetId];
52989
53026
  if (!borders)
52990
53027
  return;
52991
53028
  if (delta < 0) {
52992
- this.moveBordersOfRow(sheetId, start, delta, "horizontal", {
53029
+ this.moveBordersOfRow(sheetId, start, delta, {
52993
53030
  destructive: false,
52994
53031
  });
52995
53032
  }
@@ -52997,10 +53034,7 @@ class BordersPlugin extends CorePlugin {
52997
53034
  .filter((row) => row >= start)
52998
53035
  .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
52999
53036
  .forEach((row) => {
53000
- if ((row === start && moveFirstTopBorder) || row !== start) {
53001
- this.moveBordersOfRow(sheetId, row, delta, "horizontal");
53002
- }
53003
- this.moveBordersOfRow(sheetId, row, delta, "vertical");
53037
+ this.moveBordersOfRow(sheetId, row, delta);
53004
53038
  });
53005
53039
  }
53006
53040
  /**
@@ -53014,15 +53048,15 @@ class BordersPlugin extends CorePlugin {
53014
53048
  * argument `destructive` is given false, the target border is preserved if
53015
53049
  * the moved border is empty
53016
53050
  */
53017
- moveBordersOfRow(sheetId, row, delta, borderDirection, { destructive } = { destructive: true }) {
53051
+ moveBordersOfRow(sheetId, row, delta, { destructive } = { destructive: true }) {
53018
53052
  const borders = this.borders[sheetId];
53019
53053
  if (!borders)
53020
53054
  return;
53021
53055
  this.getColumnsWithBorders(sheetId).forEach((col) => {
53022
- const targetBorder = borders[col]?.[row + delta]?.[borderDirection];
53023
- const movedBorder = borders[col]?.[row]?.[borderDirection];
53024
- this.history.update("borders", sheetId, col, row + delta, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
53025
- this.history.update("borders", sheetId, col, row, borderDirection, undefined);
53056
+ const targetBorder = borders[col]?.[row + delta];
53057
+ const movedBorder = borders[col]?.[row];
53058
+ this.history.update("borders", sheetId, col, row + delta, destructive ? movedBorder : movedBorder || targetBorder);
53059
+ this.history.update("borders", sheetId, col, row, undefined);
53026
53060
  });
53027
53061
  }
53028
53062
  /**
@@ -53036,15 +53070,17 @@ class BordersPlugin extends CorePlugin {
53036
53070
  * argument `destructive` is given false, the target border is preserved if
53037
53071
  * the moved border is empty
53038
53072
  */
53039
- moveBordersOfColumn(sheetId, col, delta, borderDirection, { destructive } = { destructive: true }) {
53073
+ moveBordersOfColumn(sheetId, col, delta, { destructive } = { destructive: true }) {
53040
53074
  const borders = this.borders[sheetId];
53041
53075
  if (!borders)
53042
53076
  return;
53043
53077
  this.getRowsRange(sheetId).forEach((row) => {
53044
- const targetBorder = borders[col + delta]?.[row]?.[borderDirection];
53045
- const movedBorder = borders[col]?.[row]?.[borderDirection];
53046
- this.history.update("borders", sheetId, col + delta, row, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
53047
- this.history.update("borders", sheetId, col, row, borderDirection, undefined);
53078
+ const targetBorder = borders[col + delta]?.[row];
53079
+ const movedBorder = borders[col]?.[row];
53080
+ this.history.update("borders", sheetId, col + delta, row, destructive ? movedBorder : movedBorder || targetBorder);
53081
+ if (destructive) {
53082
+ this.history.update("borders", sheetId, col, row, undefined);
53083
+ }
53048
53084
  });
53049
53085
  }
53050
53086
  /**
@@ -53052,33 +53088,69 @@ class BordersPlugin extends CorePlugin {
53052
53088
  * It overrides the current border if override === true.
53053
53089
  */
53054
53090
  setBorder(sheetId, col, row, border, override = true) {
53055
- if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
53056
- this.history.update("borders", sheetId, col, row, "vertical", border?.left);
53091
+ const maxCol = this.getters.getNumberCols(sheetId) - 1;
53092
+ const maxRow = this.getters.getNumberRows(sheetId) - 1;
53093
+ if (override || !this.borders[sheetId]?.[col]?.[row]?.left) {
53094
+ this.history.update("borders", sheetId, col, row, "left", border?.left);
53095
+ if (border?.left &&
53096
+ col > 0 &&
53097
+ !deepEquals(this.getCellBorder({ sheetId, col: col - 1, row })?.right, border?.left)) {
53098
+ this.history.update("borders", sheetId, col - 1, row, "right", undefined);
53099
+ }
53057
53100
  }
53058
- if (override || !this.borders?.[sheetId]?.[col]?.[row]?.horizontal) {
53059
- this.history.update("borders", sheetId, col, row, "horizontal", border?.top);
53101
+ if (override || !this.borders[sheetId]?.[col]?.[row]?.top) {
53102
+ this.history.update("borders", sheetId, col, row, "top", border?.top);
53103
+ if (border?.top &&
53104
+ row > 0 &&
53105
+ !deepEquals(this.getCellBorder({ sheetId, col, row: row - 1 })?.bottom, border?.top)) {
53106
+ this.history.update("borders", sheetId, col, row - 1, "bottom", undefined);
53107
+ }
53060
53108
  }
53061
- if (override || !this.borders?.[sheetId]?.[col + 1]?.[row]?.vertical) {
53062
- this.history.update("borders", sheetId, col + 1, row, "vertical", border?.right);
53109
+ if (override || !this.borders[sheetId]?.[col]?.[row]?.right) {
53110
+ this.history.update("borders", sheetId, col, row, "right", border?.right);
53111
+ if (border?.right &&
53112
+ col < maxCol &&
53113
+ !deepEquals(this.getCellBorder({ sheetId, col: col + 1, row })?.left, border?.right)) {
53114
+ this.history.update("borders", sheetId, col + 1, row, "left", undefined);
53115
+ }
53063
53116
  }
53064
- if (override || !this.borders?.[sheetId]?.[col]?.[row + 1]?.horizontal) {
53065
- this.history.update("borders", sheetId, col, row + 1, "horizontal", border?.bottom);
53117
+ if (override || !this.borders[sheetId]?.[col]?.[row]?.bottom) {
53118
+ this.history.update("borders", sheetId, col, row, "bottom", border?.bottom);
53119
+ if (border?.bottom &&
53120
+ row < maxRow &&
53121
+ !deepEquals(this.getCellBorder({ sheetId, col, row: row + 1 })?.top, border?.bottom)) {
53122
+ this.history.update("borders", sheetId, col, row + 1, "top", undefined);
53123
+ }
53066
53124
  }
53067
53125
  }
53068
53126
  /**
53069
53127
  * Remove the borders of a zone
53070
53128
  */
53071
- clearBorders(sheetId, zones) {
53129
+ clearBorders(sheetId, zones, eraseBoundaries = false) {
53130
+ const maxCol = this.getters.getNumberCols(sheetId) - 1;
53131
+ const maxRow = this.getters.getNumberRows(sheetId) - 1;
53072
53132
  for (let zone of recomputeZones(zones)) {
53073
53133
  for (let row = zone.top; row <= zone.bottom; row++) {
53074
- this.history.update("borders", sheetId, zone.right + 1, row, "vertical", undefined);
53134
+ if (eraseBoundaries) {
53135
+ if (zone.left > 0) {
53136
+ this.history.update("borders", sheetId, zone.left - 1, row, "right", undefined);
53137
+ }
53138
+ if (zone.right < maxCol) {
53139
+ this.history.update("borders", sheetId, zone.right + 1, row, "left", undefined);
53140
+ }
53141
+ }
53075
53142
  for (let col = zone.left; col <= zone.right; col++) {
53076
53143
  this.history.update("borders", sheetId, col, row, undefined);
53144
+ if (eraseBoundaries) {
53145
+ if (zone.top > 0) {
53146
+ this.history.update("borders", sheetId, col, zone.top - 1, "bottom", undefined);
53147
+ }
53148
+ if (zone.bottom < maxRow) {
53149
+ this.history.update("borders", sheetId, col, zone.bottom + 1, "top", undefined);
53150
+ }
53151
+ }
53077
53152
  }
53078
53153
  }
53079
- for (let col = zone.left; col <= zone.right; col++) {
53080
- this.history.update("borders", sheetId, col, zone.bottom + 1, "horizontal", undefined);
53081
- }
53082
53154
  }
53083
53155
  }
53084
53156
  /**
@@ -53108,41 +53180,63 @@ class BordersPlugin extends CorePlugin {
53108
53180
  */
53109
53181
  setBorders(sheetId, zones, position, border) {
53110
53182
  if (position === "clear") {
53111
- return this.clearBorders(sheetId, zones);
53183
+ return this.clearBorders(sheetId, zones, true);
53112
53184
  }
53113
53185
  for (let zone of recomputeZones(zones)) {
53114
- if (position === "h" || position === "hv" || position === "all") {
53115
- for (let row = zone.top + 1; row <= zone.bottom; row++) {
53186
+ if (position === "all") {
53187
+ for (let row = zone.top; row <= zone.bottom; row++) {
53116
53188
  for (let col = zone.left; col <= zone.right; col++) {
53117
- this.addBorder(sheetId, col, row, { top: border });
53189
+ this.addBorder(sheetId, col, row, {
53190
+ top: border,
53191
+ right: border,
53192
+ bottom: border,
53193
+ left: border,
53194
+ });
53118
53195
  }
53119
53196
  }
53120
53197
  }
53121
- if (position === "v" || position === "hv" || position === "all") {
53198
+ if (position === "h" || position === "hv") {
53199
+ if (zone.top === zone.bottom) {
53200
+ continue;
53201
+ }
53202
+ for (let col = zone.left; col <= zone.right; col++) {
53203
+ this.addBorder(sheetId, col, zone.top, { bottom: border });
53204
+ for (let row = zone.top + 1; row < zone.bottom; row++) {
53205
+ this.addBorder(sheetId, col, row, { top: border, bottom: border });
53206
+ }
53207
+ this.addBorder(sheetId, col, zone.bottom, { top: border });
53208
+ }
53209
+ }
53210
+ if (position === "v" || position === "hv") {
53211
+ if (zone.left === zone.right) {
53212
+ continue;
53213
+ }
53122
53214
  for (let row = zone.top; row <= zone.bottom; row++) {
53123
- for (let col = zone.left + 1; col <= zone.right; col++) {
53124
- this.addBorder(sheetId, col, row, { left: border });
53215
+ this.addBorder(sheetId, zone.left, row, { right: border });
53216
+ for (let col = zone.left + 1; col < zone.right; col++) {
53217
+ this.addBorder(sheetId, col, row, { left: border, right: border });
53125
53218
  }
53219
+ this.addBorder(sheetId, zone.right, row, { left: border });
53126
53220
  }
53127
53221
  }
53128
- if (position === "left" || position === "all" || position === "external") {
53222
+ if (position === "left" || position === "external") {
53129
53223
  for (let row = zone.top; row <= zone.bottom; row++) {
53130
53224
  this.addBorder(sheetId, zone.left, row, { left: border });
53131
53225
  }
53132
53226
  }
53133
- if (position === "right" || position === "all" || position === "external") {
53227
+ if (position === "right" || position === "external") {
53134
53228
  for (let row = zone.top; row <= zone.bottom; row++) {
53135
- this.addBorder(sheetId, zone.right + 1, row, { left: border });
53229
+ this.addBorder(sheetId, zone.right, row, { right: border });
53136
53230
  }
53137
53231
  }
53138
- if (position === "top" || position === "all" || position === "external") {
53232
+ if (position === "top" || position === "external") {
53139
53233
  for (let col = zone.left; col <= zone.right; col++) {
53140
53234
  this.addBorder(sheetId, col, zone.top, { top: border });
53141
53235
  }
53142
53236
  }
53143
- if (position === "bottom" || position === "all" || position === "external") {
53237
+ if (position === "bottom" || position === "external") {
53144
53238
  for (let col = zone.left; col <= zone.right; col++) {
53145
- this.addBorder(sheetId, col, zone.bottom + 1, { top: border });
53239
+ this.addBorder(sheetId, col, zone.bottom, { bottom: border });
53146
53240
  }
53147
53241
  }
53148
53242
  }
@@ -59887,7 +59981,11 @@ class Evaluator {
59887
59981
  computeFormulaCell(formulaPosition, cellData) {
59888
59982
  const formulaReturn = updateEvalContextAndExecute(cellData.compiledFormula, this.compilationParams, formulaPosition.sheetId, this.buildSafeGetSymbolValue(), formulaPosition);
59889
59983
  if (!isMatrix(formulaReturn)) {
59890
- return createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData);
59984
+ const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(formulaReturn), this.getters.getLocale(), cellData);
59985
+ if (evaluatedCell.type === CellValueType.error) {
59986
+ evaluatedCell.errorOriginPosition = formulaReturn.errorOriginPosition ?? formulaPosition;
59987
+ }
59988
+ return evaluatedCell;
59891
59989
  }
59892
59990
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
59893
59991
  const nbColumns = formulaReturn.length;
@@ -59960,6 +60058,9 @@ class Evaluator {
59960
60058
  const position = { sheetId, col: i + col, row: j + row };
59961
60059
  const cell = this.getters.getCell(position);
59962
60060
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
60061
+ if (evaluatedCell.type === CellValueType.error) {
60062
+ evaluatedCell.errorOriginPosition = matrixResult[i][j].errorOriginPosition ?? position;
60063
+ }
59963
60064
  this.evaluatedCells.set(position, evaluatedCell);
59964
60065
  };
59965
60066
  return spreadValues;
@@ -61518,7 +61619,7 @@ function withPivotPresentationLayer (PivotClass) {
61518
61619
  const symbolIndex = rowDomain.findIndex((row) => row.field === symbolName);
61519
61620
  return this.getPivotHeaderValueAndFormat(rowDomain.slice(0, symbolIndex + 1));
61520
61621
  }
61521
- return this._getPivotCellValueAndFormat(symbolName, domain);
61622
+ return this.getPivotCellValueAndFormat(symbolName, domain);
61522
61623
  };
61523
61624
  const result = this.getters.evaluateCompiledFormula(measure.computedBy.sheetId, formula, getSymbolValue);
61524
61625
  if (isMatrix(result)) {
@@ -64704,14 +64805,12 @@ class SheetUIPlugin extends UIPlugin {
64704
64805
  }
64705
64806
  break;
64706
64807
  case "AUTORESIZE_ROWS":
64707
- for (let row of cmd.rows) {
64708
- this.dispatch("RESIZE_COLUMNS_ROWS", {
64709
- elements: [row],
64710
- dimension: "ROW",
64711
- size: null,
64712
- sheetId: cmd.sheetId,
64713
- });
64714
- }
64808
+ this.dispatch("RESIZE_COLUMNS_ROWS", {
64809
+ elements: cmd.rows,
64810
+ dimension: "ROW",
64811
+ size: null,
64812
+ sheetId: cmd.sheetId,
64813
+ });
64715
64814
  break;
64716
64815
  }
64717
64816
  }
@@ -67107,8 +67206,6 @@ class InternalViewport {
67107
67206
  right;
67108
67207
  offsetX;
67109
67208
  offsetY;
67110
- offsetScrollbarX;
67111
- offsetScrollbarY;
67112
67209
  canScrollVertically;
67113
67210
  canScrollHorizontally;
67114
67211
  viewportWidth;
@@ -67119,10 +67216,17 @@ class InternalViewport {
67119
67216
  this.getters = getters;
67120
67217
  this.sheetId = sheetId;
67121
67218
  this.boundaries = boundaries;
67122
- this.viewportWidth = sizeInGrid.width;
67123
- this.viewportHeight = sizeInGrid.height;
67124
- this.offsetScrollbarX = offsets.x;
67125
- this.offsetScrollbarY = offsets.y;
67219
+ if (sizeInGrid.width < 0 || sizeInGrid.height < 0) {
67220
+ throw new Error("Viewport size cannot be negative");
67221
+ }
67222
+ this.viewportWidth = sizeInGrid.height && sizeInGrid.width;
67223
+ this.viewportHeight = sizeInGrid.width && sizeInGrid.height;
67224
+ this.top = boundaries.top;
67225
+ this.bottom = boundaries.bottom;
67226
+ this.left = boundaries.left;
67227
+ this.right = boundaries.right;
67228
+ this.offsetX = offsets.x;
67229
+ this.offsetY = offsets.y;
67126
67230
  this.canScrollVertically = options.canScrollVertically;
67127
67231
  this.canScrollHorizontally = options.canScrollHorizontally;
67128
67232
  this.offsetCorrectionX = this.getters.getColDimensions(this.sheetId, this.boundaries.left).start;
@@ -67163,9 +67267,9 @@ class InternalViewport {
67163
67267
  Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
67164
67268
  );
67165
67269
  height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
67166
- }
67167
- if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
67168
- height += FOOTER_HEIGHT;
67270
+ if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
67271
+ height += FOOTER_HEIGHT;
67272
+ }
67169
67273
  }
67170
67274
  return { width, height };
67171
67275
  }
@@ -67178,7 +67282,7 @@ class InternalViewport {
67178
67282
  if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.viewportWidth) {
67179
67283
  return -1;
67180
67284
  }
67181
- return this.searchHeaderIndex("COL", x - this.offsetCorrectionX, this.left);
67285
+ return this.searchHeaderIndex("COL", x - this.offsetCorrectionX + this.snapCorrection.x, this.left);
67182
67286
  }
67183
67287
  /**
67184
67288
  * Return the index of a row given an offset y, based on the pane top
@@ -67189,7 +67293,7 @@ class InternalViewport {
67189
67293
  if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.viewportHeight) {
67190
67294
  return -1;
67191
67295
  }
67192
- return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY, this.top);
67296
+ return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY + this.snapCorrection.y, this.top);
67193
67297
  }
67194
67298
  /**
67195
67299
  * This function will make sure that the provided cell position (or current selected position) is part of
@@ -67209,55 +67313,29 @@ class InternalViewport {
67209
67313
  }
67210
67314
  adjustPositionX(targetCol) {
67211
67315
  const sheetId = this.sheetId;
67212
- const { end } = this.getters.getColDimensions(sheetId, targetCol);
67213
- if (this.offsetX + this.offsetCorrectionX + this.viewportWidth < end) {
67214
- const maxCol = this.getters.getNumberCols(sheetId);
67215
- let finalTarget = targetCol;
67216
- while (this.getters.isColHidden(sheetId, finalTarget) && finalTarget < maxCol) {
67217
- finalTarget++;
67218
- }
67219
- const finalTargetEnd = this.getters.getColDimensions(sheetId, finalTarget).end;
67220
- const startIndex = this.searchHeaderIndex("COL", finalTargetEnd - this.viewportWidth - this.offsetCorrectionX, this.boundaries.left);
67221
- this.offsetScrollbarX =
67222
- this.getters.getColDimensions(sheetId, startIndex).end - this.offsetCorrectionX;
67223
- }
67224
- else if (this.left > targetCol) {
67225
- let finalTarget = targetCol;
67226
- while (this.getters.isColHidden(sheetId, finalTarget) && finalTarget > 0) {
67227
- finalTarget--;
67228
- }
67229
- this.offsetScrollbarX =
67230
- this.getters.getColDimensions(sheetId, finalTarget).start - this.offsetCorrectionX;
67316
+ const { start, end } = this.getters.getColDimensions(sheetId, targetCol);
67317
+ if (this.offsetX + this.viewportWidth + this.offsetCorrectionX < end) {
67318
+ this.offsetX = end - this.viewportWidth;
67319
+ }
67320
+ else if (this.offsetX + this.offsetCorrectionX > start) {
67321
+ this.offsetX = start - this.offsetCorrectionX;
67231
67322
  }
67232
67323
  this.adjustViewportZoneX();
67233
67324
  }
67234
67325
  adjustPositionY(targetRow) {
67235
67326
  const sheetId = this.sheetId;
67236
- const { end } = this.getters.getRowDimensions(sheetId, targetRow);
67327
+ const { start, end } = this.getters.getRowDimensions(sheetId, targetRow);
67237
67328
  if (this.offsetY + this.viewportHeight + this.offsetCorrectionY < end) {
67238
- const maxRow = this.getters.getNumberRows(sheetId);
67239
- let finalTarget = targetRow;
67240
- while (this.getters.isRowHidden(sheetId, finalTarget) && finalTarget < maxRow) {
67241
- finalTarget++;
67242
- }
67243
- const finalTargetEnd = this.getters.getRowDimensions(sheetId, finalTarget).end;
67244
- const startIndex = this.searchHeaderIndex("ROW", finalTargetEnd - this.viewportHeight - this.offsetCorrectionY, this.boundaries.top);
67245
- this.offsetScrollbarY =
67246
- this.getters.getRowDimensions(sheetId, startIndex).end - this.offsetCorrectionY;
67329
+ this.offsetY = end - this.viewportHeight;
67247
67330
  }
67248
- else if (this.top > targetRow) {
67249
- let finalTarget = targetRow;
67250
- while (this.getters.isRowHidden(sheetId, finalTarget) && finalTarget > 0) {
67251
- finalTarget--;
67252
- }
67253
- this.offsetScrollbarY =
67254
- this.getters.getRowDimensions(sheetId, finalTarget).start - this.offsetCorrectionY;
67331
+ else if (this.offsetY + this.offsetCorrectionY > start) {
67332
+ this.offsetY = start - this.offsetCorrectionY;
67255
67333
  }
67256
67334
  this.adjustViewportZoneY();
67257
67335
  }
67258
67336
  willNewOffsetScrollViewport(offsetX, offsetY) {
67259
- return ((this.canScrollHorizontally && this.offsetScrollbarX !== offsetX) ||
67260
- (this.canScrollVertically && this.offsetScrollbarY !== offsetY));
67337
+ return ((this.canScrollHorizontally && this.offsetX !== offsetX) ||
67338
+ (this.canScrollVertically && this.offsetY !== offsetY));
67261
67339
  }
67262
67340
  setViewportOffset(offsetX, offsetY) {
67263
67341
  this.setViewportOffsetX(offsetX);
@@ -67274,11 +67352,19 @@ class InternalViewport {
67274
67352
  */
67275
67353
  getVisibleRect(zone) {
67276
67354
  const targetZone = intersection(zone, this);
67355
+ const scrollDeltaX = this.snapCorrection.x;
67356
+ const scrollDeltaY = this.snapCorrection.y;
67277
67357
  if (targetZone) {
67278
- const x = this.getters.getColRowOffset("COL", this.left, targetZone.left) + this.offsetCorrectionX;
67279
- const y = this.getters.getColRowOffset("ROW", this.top, targetZone.top) + this.offsetCorrectionY;
67280
- const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
67281
- const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
67358
+ const x = this.getters.getColRowOffset("COL", this.left, targetZone.left) +
67359
+ this.offsetCorrectionX -
67360
+ (this.left !== targetZone.left ? scrollDeltaX : 0);
67361
+ const y = this.getters.getColRowOffset("ROW", this.top, targetZone.top) +
67362
+ this.offsetCorrectionY -
67363
+ (this.top !== targetZone.top ? scrollDeltaY : 0);
67364
+ const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1) -
67365
+ (this.left === targetZone.left ? scrollDeltaX : 0), this.viewportWidth);
67366
+ const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1) -
67367
+ (this.top === targetZone.top ? scrollDeltaY : 0), this.viewportHeight);
67282
67368
  return { x, y, width, height };
67283
67369
  }
67284
67370
  return undefined;
@@ -67290,12 +67376,14 @@ class InternalViewport {
67290
67376
  */
67291
67377
  getFullRect(zone) {
67292
67378
  const targetZone = intersection(zone, this);
67379
+ const scrollDeltaX = this.snapCorrection.x;
67380
+ const scrollDeltaY = this.snapCorrection.y;
67293
67381
  if (targetZone) {
67294
67382
  const x = this.getters.getColRowOffset("COL", this.left, zone.left) + this.offsetCorrectionX;
67295
67383
  const y = this.getters.getColRowOffset("ROW", this.top, zone.top) + this.offsetCorrectionY;
67296
67384
  const width = this.getters.getColRowOffset("COL", zone.left, zone.right + 1);
67297
67385
  const height = this.getters.getColRowOffset("ROW", zone.top, zone.bottom + 1);
67298
- return { x, y, width, height };
67386
+ return { x: x - scrollDeltaX, y: y - scrollDeltaY, width, height };
67299
67387
  }
67300
67388
  return undefined;
67301
67389
  }
@@ -67306,6 +67394,9 @@ class InternalViewport {
67306
67394
  !this.getters.isRowHidden(this.sheetId, row));
67307
67395
  }
67308
67396
  searchHeaderIndex(dimension, position, startIndex = 0) {
67397
+ if (this.viewportWidth <= 0 || this.viewportHeight <= 0) {
67398
+ return -1;
67399
+ }
67309
67400
  const sheetId = this.sheetId;
67310
67401
  const headers = this.getters.getNumberHeaders(sheetId, dimension);
67311
67402
  // using a binary search:
@@ -67331,36 +67422,36 @@ class InternalViewport {
67331
67422
  if (!this.canScrollHorizontally) {
67332
67423
  return;
67333
67424
  }
67334
- this.offsetScrollbarX = offsetX;
67425
+ this.offsetX = offsetX;
67335
67426
  this.adjustViewportZoneX();
67336
67427
  }
67337
67428
  setViewportOffsetY(offsetY) {
67338
67429
  if (!this.canScrollVertically) {
67339
67430
  return;
67340
67431
  }
67341
- this.offsetScrollbarY = offsetY;
67432
+ this.offsetY = offsetY;
67342
67433
  this.adjustViewportZoneY();
67343
67434
  }
67344
67435
  /** Corrects the viewport's horizontal offset based on the current structure
67345
- * To make sure that at least on column is visible inside the viewport.
67436
+ * To make sure that at least one column is visible inside the viewport.
67346
67437
  */
67347
67438
  adjustViewportOffsetX() {
67348
67439
  if (this.canScrollHorizontally) {
67349
67440
  const { width: viewportWidth } = this.getMaxSize();
67350
- if (this.viewportWidth + this.offsetScrollbarX > viewportWidth) {
67351
- this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
67441
+ if (this.viewportWidth + this.offsetX > viewportWidth) {
67442
+ this.offsetX = Math.max(0, viewportWidth - this.viewportWidth);
67352
67443
  }
67353
67444
  }
67354
67445
  this.adjustViewportZoneX();
67355
67446
  }
67356
67447
  /** Corrects the viewport's vertical offset based on the current structure
67357
- * To make sure that at least on row is visible inside the viewport.
67448
+ * To make sure that at least one row is visible inside the viewport.
67358
67449
  */
67359
67450
  adjustViewportOffsetY() {
67360
67451
  if (this.canScrollVertically) {
67361
67452
  const { height: paneHeight } = this.getMaxSize();
67362
- if (this.viewportHeight + this.offsetScrollbarY > paneHeight) {
67363
- this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
67453
+ if (this.viewportHeight + this.offsetY > paneHeight) {
67454
+ this.offsetY = Math.max(0, paneHeight - this.viewportHeight);
67364
67455
  }
67365
67456
  }
67366
67457
  this.adjustViewportZoneY();
@@ -67368,34 +67459,47 @@ class InternalViewport {
67368
67459
  /** Updates the pane zone and snapped offset based on its horizontal
67369
67460
  * offset (will find Left) and its width (will find Right) */
67370
67461
  adjustViewportZoneX() {
67371
- const sheetId = this.sheetId;
67372
- this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
67373
- this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
67462
+ this.left = this.searchHeaderIndex("COL", this.offsetX, this.boundaries.left);
67463
+ this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL",
67464
+ // if we hit the border of two cells, we want to match the previous
67465
+ Math.max(this.viewportWidth + this.snapCorrection.x - 0.1), this.left));
67466
+ if (!this.viewportWidth) {
67467
+ return;
67468
+ }
67374
67469
  if (this.left === -1) {
67375
67470
  this.left = this.boundaries.left;
67376
67471
  }
67377
67472
  if (this.right === -1) {
67378
- this.right = this.getters.getNumberCols(sheetId) - 1;
67473
+ this.right = this.boundaries.right;
67379
67474
  }
67380
- this.offsetX =
67381
- this.getters.getColDimensions(sheetId, this.left).start -
67382
- this.getters.getColDimensions(sheetId, this.boundaries.left).start;
67383
67475
  }
67384
67476
  /** Updates the pane zone and snapped offset based on its vertical
67385
67477
  * offset (will find Top) and its width (will find Bottom) */
67386
67478
  adjustViewportZoneY() {
67387
- const sheetId = this.sheetId;
67388
- this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
67389
- this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
67479
+ this.top = this.searchHeaderIndex("ROW", this.offsetY, this.boundaries.top);
67480
+ this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW",
67481
+ // if we hit the border of two cells, we want to match the previous
67482
+ Math.max(this.viewportHeight + this.snapCorrection.y - 0.1, 0), this.top));
67483
+ if (!this.viewportHeight) {
67484
+ return;
67485
+ }
67390
67486
  if (this.top === -1) {
67391
67487
  this.top = this.boundaries.top;
67392
67488
  }
67393
67489
  if (this.bottom === -1) {
67394
- this.bottom = this.getters.getNumberRows(sheetId) - 1;
67490
+ this.bottom = this.boundaries.bottom;
67395
67491
  }
67396
- this.offsetY =
67397
- this.getters.getRowDimensions(sheetId, this.top).start -
67398
- this.getters.getRowDimensions(sheetId, this.boundaries.top).start;
67492
+ }
67493
+ /** represents the part of the header on the topLeft that could be partially
67494
+ * hidden due to the scroll
67495
+ * */
67496
+ get snapCorrection() {
67497
+ return {
67498
+ x: Math.abs(this.offsetX -
67499
+ this.getters.getColRowOffset("COL", this.boundaries.left, Math.max(0, this.left))),
67500
+ y: Math.abs(this.offsetY -
67501
+ this.getters.getColRowOffset("ROW", this.boundaries.top, Math.max(0, this.top))),
67502
+ };
67399
67503
  }
67400
67504
  }
67401
67505
 
@@ -67458,14 +67562,13 @@ class SheetViewPlugin extends UIPlugin {
67458
67562
  "getColRowOffsetInViewport",
67459
67563
  "getMainViewportCoordinates",
67460
67564
  "getActiveSheetScrollInfo",
67461
- "getActiveSheetDOMScrollInfo",
67462
67565
  "getSheetViewVisibleCols",
67463
67566
  "getSheetViewVisibleRows",
67464
67567
  "getFrozenSheetViewRatio",
67465
- "isPositionVisible",
67568
+ "isPixelPositionVisible",
67466
67569
  "getColDimensionsInViewport",
67467
67570
  "getRowDimensionsInViewport",
67468
- "getAllActiveViewportsZones",
67571
+ "getAllActiveViewportsZonesAndRect",
67469
67572
  "getRect",
67470
67573
  ];
67471
67574
  viewports = {};
@@ -67668,8 +67771,8 @@ class SheetViewPlugin extends UIPlugin {
67668
67771
  return this.getMainViewport(sheetId);
67669
67772
  }
67670
67773
  /**
67671
- * Return the scroll info of the active sheet, ie. the offset between the viewport left/top side and
67672
- * the grid left/top side, snapped to the columns/rows.
67774
+ * Return the DOM scroll info of the active sheet, ie. the offset between the viewport left/top side and
67775
+ * the grid left/top side, corresponding to the scroll of the scrollbars and not snapped to the grid.
67673
67776
  */
67674
67777
  getActiveSheetScrollInfo() {
67675
67778
  const sheetId = this.getters.getActiveSheetId();
@@ -67679,28 +67782,16 @@ class SheetViewPlugin extends UIPlugin {
67679
67782
  scrollY: viewport.offsetY,
67680
67783
  };
67681
67784
  }
67682
- /**
67683
- * Return the DOM scroll info of the active sheet, ie. the offset between the viewport left/top side and
67684
- * the grid left/top side, corresponding to the scroll of the scrollbars and not snapped to the grid.
67685
- */
67686
- getActiveSheetDOMScrollInfo() {
67687
- const sheetId = this.getters.getActiveSheetId();
67688
- const viewport = this.getMainInternalViewport(sheetId);
67689
- return {
67690
- scrollX: viewport.offsetScrollbarX,
67691
- scrollY: viewport.offsetScrollbarY,
67692
- };
67693
- }
67694
67785
  getSheetViewVisibleCols() {
67695
67786
  const sheetId = this.getters.getActiveSheetId();
67696
67787
  const viewports = this.getSubViewports(sheetId);
67697
67788
  //TODO ake another commit to eimprove this
67698
- return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => !this.getters.isHeaderHidden(sheetId, "COL", col));
67789
+ return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => col >= 0 && !this.getters.isHeaderHidden(sheetId, "COL", col));
67699
67790
  }
67700
67791
  getSheetViewVisibleRows() {
67701
67792
  const sheetId = this.getters.getActiveSheetId();
67702
67793
  const viewports = this.getSubViewports(sheetId);
67703
- return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
67794
+ return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => row >= 0 && !this.getters.isHeaderHidden(sheetId, "ROW", row));
67704
67795
  }
67705
67796
  /**
67706
67797
  * Get the positions of all the cells that are visible in the viewport, taking merges into account.
@@ -67743,19 +67834,19 @@ class SheetViewPlugin extends UIPlugin {
67743
67834
  maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
67744
67835
  };
67745
67836
  }
67746
- getColRowOffsetInViewport(dimension, referenceIndex, index) {
67747
- const sheetId = this.getters.getActiveSheetId();
67748
- const visibleCols = this.getters.getSheetViewVisibleCols();
67749
- const visibleRows = this.getters.getSheetViewVisibleRows();
67750
- if (index < referenceIndex) {
67751
- return -this.getColRowOffsetInViewport(dimension, index, referenceIndex);
67837
+ getColRowOffsetInViewport(dimension, referenceHeaderIndex, targetHeaderIndex) {
67838
+ if (targetHeaderIndex < referenceHeaderIndex) {
67839
+ return -this.getColRowOffsetInViewport(dimension, targetHeaderIndex, referenceHeaderIndex);
67752
67840
  }
67841
+ const sheetId = this.getters.getActiveSheetId();
67842
+ const visibleHeaders = dimension === "COL"
67843
+ ? this.getters.getSheetViewVisibleCols()
67844
+ : this.getters.getSheetViewVisibleRows();
67845
+ const startIndex = visibleHeaders.findIndex((header) => referenceHeaderIndex >= header);
67846
+ const endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
67847
+ const relevantIndexes = visibleHeaders.slice(startIndex, endIndex);
67753
67848
  let offset = 0;
67754
- const visibleIndexes = dimension === "COL" ? visibleCols : visibleRows;
67755
- for (let i = referenceIndex; i < index; i++) {
67756
- if (!visibleIndexes.includes(i)) {
67757
- continue;
67758
- }
67849
+ for (const i of relevantIndexes) {
67759
67850
  offset += this.getters.getHeaderSize(sheetId, dimension, i);
67760
67851
  }
67761
67852
  return offset;
@@ -67802,7 +67893,7 @@ class SheetViewPlugin extends UIPlugin {
67802
67893
  }
67803
67894
  return { canEdgeScroll, direction, delay };
67804
67895
  }
67805
- getEdgeScrollRow(y, previousY, tartingY) {
67896
+ getEdgeScrollRow(y, previousY, startingY) {
67806
67897
  let canEdgeScroll = false;
67807
67898
  let direction = 0;
67808
67899
  let delay = 0;
@@ -67823,7 +67914,7 @@ class SheetViewPlugin extends UIPlugin {
67823
67914
  delay = scrollDelay(y - height);
67824
67915
  direction = 1;
67825
67916
  }
67826
- else if (y < offsetCorrectionY && tartingY >= offsetCorrectionY && currentOffsetY > 0) {
67917
+ else if (y < offsetCorrectionY && startingY >= offsetCorrectionY && currentOffsetY > 0) {
67827
67918
  // 2
67828
67919
  canEdgeScroll = true;
67829
67920
  delay = scrollDelay(offsetCorrectionY - y);
@@ -67849,13 +67940,7 @@ class SheetViewPlugin extends UIPlugin {
67849
67940
  */
67850
67941
  getVisibleRectWithoutHeaders(zone) {
67851
67942
  const sheetId = this.getters.getActiveSheetId();
67852
- const viewportRects = this.getSubViewports(sheetId)
67853
- .map((viewport) => viewport.getVisibleRect(zone))
67854
- .filter(isDefined);
67855
- if (viewportRects.length === 0) {
67856
- return { x: 0, y: 0, width: 0, height: 0 };
67857
- }
67858
- return this.recomposeRect(viewportRects);
67943
+ return this.mapViewportsToRect(sheetId, (viewport) => viewport.getVisibleRect(zone));
67859
67944
  }
67860
67945
  /**
67861
67946
  * Computes the actual size and position (:Rect) of the zone on the canvas
@@ -67863,13 +67948,7 @@ class SheetViewPlugin extends UIPlugin {
67863
67948
  */
67864
67949
  getRect(zone) {
67865
67950
  const sheetId = this.getters.getActiveSheetId();
67866
- const viewportRects = this.getSubViewports(sheetId)
67867
- .map((viewport) => viewport.getFullRect(zone))
67868
- .filter(isDefined);
67869
- if (viewportRects.length === 0) {
67870
- return { x: 0, y: 0, width: 0, height: 0 };
67871
- }
67872
- const rect = this.recomposeRect(viewportRects);
67951
+ const rect = this.mapViewportsToRect(sheetId, (viewport) => viewport.getFullRect(zone));
67873
67952
  return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
67874
67953
  }
67875
67954
  /**
@@ -67889,34 +67968,43 @@ class SheetViewPlugin extends UIPlugin {
67889
67968
  * column of the current viewport
67890
67969
  */
67891
67970
  getColDimensionsInViewport(sheetId, col) {
67892
- const left = largeMin(this.getters.getSheetViewVisibleCols());
67893
- const start = this.getters.getColRowOffsetInViewport("COL", left, col);
67894
- const size = this.getters.getColSize(sheetId, col);
67895
- const isColHidden = this.getters.isColHidden(sheetId, col);
67896
- return {
67897
- start,
67898
- size: size,
67899
- end: start + (isColHidden ? 0 : size),
67971
+ const zone = {
67972
+ left: col,
67973
+ right: col,
67974
+ top: 0,
67975
+ bottom: this.getters.getNumberRows(sheetId) - 1,
67900
67976
  };
67977
+ const { x, width } = this.getVisibleRect(zone);
67978
+ const start = x - this.gridOffsetX;
67979
+ return { start, size: width, end: start + width };
67901
67980
  }
67902
67981
  /**
67903
67982
  * Returns the size, start and end coordinates of a row relative to the top row
67904
67983
  * of the current viewport
67905
67984
  */
67906
67985
  getRowDimensionsInViewport(sheetId, row) {
67907
- const top = largeMin(this.getters.getSheetViewVisibleRows());
67908
- const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
67909
- const size = this.getters.getRowSize(sheetId, row);
67910
- const isRowHidden = this.getters.isRowHidden(sheetId, row);
67911
- return {
67912
- start,
67913
- size: size,
67914
- end: start + (isRowHidden ? 0 : size),
67986
+ const zone = {
67987
+ left: 0,
67988
+ right: this.getters.getNumberCols(sheetId) - 1,
67989
+ top: row,
67990
+ bottom: row,
67915
67991
  };
67992
+ const { y, height } = this.getVisibleRect(zone);
67993
+ const start = y - this.gridOffsetY;
67994
+ return { start, size: height, end: start + height };
67916
67995
  }
67917
- getAllActiveViewportsZones() {
67996
+ getAllActiveViewportsZonesAndRect() {
67918
67997
  const sheetId = this.getters.getActiveSheetId();
67919
- return this.getSubViewports(sheetId);
67998
+ return this.getSubViewports(sheetId).map((viewport) => {
67999
+ return {
68000
+ zone: viewport,
68001
+ rect: {
68002
+ x: viewport.offsetCorrectionX + this.gridOffsetX,
68003
+ y: viewport.offsetCorrectionY + this.gridOffsetY,
68004
+ ...viewport.getMaxSize(),
68005
+ },
68006
+ };
68007
+ });
67920
68008
  }
67921
68009
  // ---------------------------------------------------------------------------
67922
68010
  // Private
@@ -67975,12 +68063,11 @@ class SheetViewPlugin extends UIPlugin {
67975
68063
  }
67976
68064
  /** gets rid of deprecated sheetIds */
67977
68065
  cleanViewports() {
67978
- const sheetIds = this.getters.getSheetIds();
67979
- for (let sheetId of Object.keys(this.viewports)) {
67980
- if (!sheetIds.includes(sheetId)) {
67981
- delete this.viewports[sheetId];
67982
- }
68066
+ const newViewport = {};
68067
+ for (const sheetId of this.getters.getSheetIds()) {
68068
+ newViewport[sheetId] = this.viewports[sheetId];
67983
68069
  }
68070
+ this.viewports = newViewport;
67984
68071
  }
67985
68072
  resizeSheetView(height, width, gridOffsetX = 0, gridOffsetY = 0) {
67986
68073
  this.sheetViewHeight = height;
@@ -67990,7 +68077,7 @@ class SheetViewPlugin extends UIPlugin {
67990
68077
  this.recomputeViewports();
67991
68078
  }
67992
68079
  recomputeViewports() {
67993
- for (let sheetId of Object.keys(this.viewports)) {
68080
+ for (const sheetId of this.getters.getSheetIds()) {
67994
68081
  this.resetViewports(sheetId);
67995
68082
  }
67996
68083
  }
@@ -68001,8 +68088,8 @@ class SheetViewPlugin extends UIPlugin {
68001
68088
  }
68002
68089
  getViewportOffset(sheetId) {
68003
68090
  return {
68004
- x: this.viewports[sheetId]?.bottomRight.offsetScrollbarX || 0,
68005
- y: this.viewports[sheetId]?.bottomRight.offsetScrollbarY || 0,
68091
+ x: this.viewports[sheetId]?.bottomRight.offsetX || 0,
68092
+ y: this.viewports[sheetId]?.bottomRight.offsetY || 0,
68006
68093
  };
68007
68094
  }
68008
68095
  resetViewports(sheetId) {
@@ -68012,8 +68099,10 @@ class SheetViewPlugin extends UIPlugin {
68012
68099
  const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
68013
68100
  const nCols = this.getters.getNumberCols(sheetId);
68014
68101
  const nRows = this.getters.getNumberRows(sheetId);
68015
- const colOffset = this.getters.getColRowOffset("COL", 0, xSplit, sheetId);
68016
- const rowOffset = this.getters.getColRowOffset("ROW", 0, ySplit, sheetId);
68102
+ const colOffset = Math.min(this.getters.getColRowOffset("COL", 0, xSplit, sheetId), this.sheetViewWidth);
68103
+ const rowOffset = Math.min(this.getters.getColRowOffset("ROW", 0, ySplit, sheetId), this.sheetViewHeight);
68104
+ const unfrozenWidth = Math.max(this.sheetViewWidth - colOffset, 0);
68105
+ const unfrozenHeight = Math.max(this.sheetViewHeight - rowOffset, 0);
68017
68106
  const { xRatio, yRatio } = this.getFrozenSheetViewRatio(sheetId);
68018
68107
  const canScrollHorizontally = xRatio < 1.0;
68019
68108
  const canScrollVertically = yRatio < 1.0;
@@ -68024,14 +68113,14 @@ class SheetViewPlugin extends UIPlugin {
68024
68113
  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 })) ||
68025
68114
  undefined,
68026
68115
  topRight: (ySplit &&
68027
- 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 })) ||
68116
+ 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 })) ||
68028
68117
  undefined,
68029
68118
  bottomLeft: (xSplit &&
68030
- 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 })) ||
68119
+ 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 })) ||
68031
68120
  undefined,
68032
68121
  bottomRight: new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: ySplit, bottom: nRows - 1 }, {
68033
- width: this.sheetViewWidth - colOffset,
68034
- height: this.sheetViewHeight - rowOffset,
68122
+ width: unfrozenWidth,
68123
+ height: unfrozenHeight,
68035
68124
  }, { canScrollHorizontally, canScrollVertically }, {
68036
68125
  x: canScrollHorizontally ? previousOffset.x : 0,
68037
68126
  y: canScrollVertically ? previousOffset.y : 0,
@@ -68086,7 +68175,7 @@ class SheetViewPlugin extends UIPlugin {
68086
68175
  }
68087
68176
  return result;
68088
68177
  }
68089
- isPositionVisible(position) {
68178
+ isPixelPositionVisible(position) {
68090
68179
  const { scrollX, scrollY } = this.getters.getActiveSheetScrollInfo();
68091
68180
  const { x: mainViewportX, y: mainViewportY } = this.getters.getMainViewportCoordinates();
68092
68181
  const { width, height } = this.getters.getSheetViewDimension();
@@ -68108,12 +68197,26 @@ class SheetViewPlugin extends UIPlugin {
68108
68197
  const height = this.sheetViewHeight + this.gridOffsetY;
68109
68198
  return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };
68110
68199
  }
68111
- recomposeRect(viewportRects) {
68112
- const x = Math.min(...viewportRects.map((rect) => rect.x));
68113
- const y = Math.min(...viewportRects.map((rect) => rect.y));
68114
- const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
68115
- const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
68116
- return { x, y, width, height };
68200
+ mapViewportsToRect(sheetId, rectCallBack) {
68201
+ let x = Infinity;
68202
+ let y = Infinity;
68203
+ let width = 0;
68204
+ let height = 0;
68205
+ let hasViewports = false;
68206
+ for (const viewport of this.getSubViewports(sheetId)) {
68207
+ const rect = rectCallBack(viewport);
68208
+ if (rect) {
68209
+ hasViewports = true;
68210
+ x = Math.min(x, rect.x);
68211
+ y = Math.min(y, rect.y);
68212
+ width = Math.max(width, rect.x + rect.width);
68213
+ height = Math.max(height, rect.y + rect.height);
68214
+ }
68215
+ }
68216
+ if (!hasViewports) {
68217
+ return { x: 0, y: 0, width: 0, height: 0 };
68218
+ }
68219
+ return { x, y, width: width - x, height: height - y };
68117
68220
  }
68118
68221
  }
68119
68222
 
@@ -69288,7 +69391,7 @@ class SpreadsheetDashboard extends owl.Component {
69288
69391
  });
69289
69392
  }
69290
69393
  moveCanvas(deltaX, deltaY) {
69291
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
69394
+ const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
69292
69395
  this.env.model.dispatch("SET_VIEWPORT_OFFSET", {
69293
69396
  offsetX: scrollX + deltaX,
69294
69397
  offsetY: scrollY + deltaY,
@@ -70481,12 +70584,8 @@ css /* scss */ `
70481
70584
  .o-spreadsheet {
70482
70585
  position: relative;
70483
70586
  display: grid;
70484
- color: ${TEXT_BODY};
70485
70587
  font-size: 14px;
70486
70588
 
70487
- input {
70488
- background-color: white;
70489
- }
70490
70589
  .text-muted {
70491
70590
  color: ${TEXT_BODY_MUTED} !important;
70492
70591
  }
@@ -71733,6 +71832,9 @@ class EventStream {
71733
71832
  observe(owner, callbacks) {
71734
71833
  this.observers.set(owner, { owner, callbacks });
71735
71834
  }
71835
+ detachObserver(owner) {
71836
+ this.observers.delete(owner);
71837
+ }
71736
71838
  /**
71737
71839
  * Capture the stream for yourself
71738
71840
  */
@@ -71825,6 +71927,9 @@ class SelectionStreamProcessorImpl {
71825
71927
  observe(owner, callbacks) {
71826
71928
  this.stream.observe(owner, callbacks);
71827
71929
  }
71930
+ detachObserver(owner) {
71931
+ this.stream.detachObserver(owner);
71932
+ }
71828
71933
  release(owner) {
71829
71934
  if (this.stream.isListening(owner)) {
71830
71935
  this.stream.release(owner);
@@ -75274,6 +75379,6 @@ exports.tokenColors = tokenColors;
75274
75379
  exports.tokenize = tokenize;
75275
75380
 
75276
75381
 
75277
- __info__.version = "18.2.0-alpha.4";
75278
- __info__.date = "2025-01-29T06:30:12.773Z";
75279
- __info__.hash = "6838c26";
75382
+ __info__.version = "18.2.0-alpha.6";
75383
+ __info__.date = "2025-02-05T06:50:47.008Z";
75384
+ __info__.hash = "dae9ab2";