summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPierre Rousseau <pro@odoo.com>2025-02-05 08:24:16 +0100
committerPierre Rousseau <pro@odoo.com>2025-02-05 12:54:35 +0000
commitca5139046d46c858bca6066566bdb8eda9c766d8 (patch)
tree636a98e681e38e865c3193dd7f18366529914df6
parent2917b4cf37fe8d5d75d550912d0760a89a4b5096 (diff)
[FIX] spreadsheet: update o_spreadsheet to latest version
### Contains the following commits: https://github.com/odoo/o-spreadsheet/commit/90f2af4ff [REL] 18.0.14 Task: 0 https://github.com/odoo/o-spreadsheet/commit/3590a49ee [FIX] formulas: accept simple values on search range for lookup formulas Task: 4543381 https://github.com/odoo/o-spreadsheet/commit/48e368c47 [FIX] Rendering: Skip hidden headers Task: 4535794 https://github.com/odoo/o-spreadsheet/commit/fc0aa2a66 [FIX] SheetView: avoid some array allocation https://github.com/odoo/o-spreadsheet/commit/4bcac65b0 [FIX] SheetView: remove useless/costy calls to Object.x https://github.com/odoo/o-spreadsheet/commit/a1ab57a4e [FIX] Renderer: Do not draw if no canvas Task: 4535794 https://github.com/odoo/o-spreadsheet/commit/95266d1ab [FIX] SheetView: Fix viewports with negative dimensions Task: 4535794 https://github.com/odoo/o-spreadsheet/commit/8c4ee5d8f [FIX] Borders: Fix border continuity Task: 4523890 https://github.com/odoo/o-spreadsheet/commit/e1ffbf678 [FIX] renderer: Re-fix box rendering Task: 4526742 closes odoo/odoo#196557 Signed-off-by: Vincent Schippefilt (vsc) <vsc@odoo.com> Co-authored-by: Anthony Hendrickx (anhe) <anhe@odoo.com> Co-authored-by: Alexis Lacroix (laa) <laa@odoo.com> Co-authored-by: Lucas Lefèvre (lul) <lul@odoo.com> Co-authored-by: Dhrutik Patel (dhrp) <dhrp@odoo.com> Co-authored-by: Adrien Minne (adrm) <adrm@odoo.com> Co-authored-by: Mehdi Rachico (mera) <mera@odoo.com> Co-authored-by: Florian Damhaut (flda) <flda@odoo.com> Co-authored-by: Rémi Rahir (rar) <rar@odoo.com> Co-authored-by: Pierre Rousseau (pro) <pro@odoo.com> Co-authored-by: Vincent Schippefilt (vsc) <vsc@odoo.com>
-rw-r--r--addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js283
-rw-r--r--addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml6
2 files changed, 163 insertions, 126 deletions
diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
index 0149c8fb53f..1c8f3e339d7 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
@@ -2,9 +2,9 @@
/**
* This file is generated by o-spreadsheet build tools. Do not edit it.
* @see https://github.com/odoo/o-spreadsheet
- * @version 18.0.13
- * @date 2025-01-31T08:06:35.677Z
- * @hash f505971d4
+ * @version 18.0.14
+ * @date 2025-02-05T07:24:11.049Z
+ * @hash 90f2af4ff
*/
import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -18382,19 +18382,20 @@ const HLOOKUP = {
description: _t("Horizontal lookup"),
args: [
arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
- 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.")),
+ 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.")),
arg("index (number)", _t("The row index of the value to be returned, where the first row in range is numbered 1.")),
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.")),
],
compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
const _index = Math.trunc(toNumber(index?.value, this.locale));
- assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
+ const _range = toMatrix(range);
+ assert(() => 1 <= _index && _index <= _range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
const getValueFromRange = (range, index) => range[index][0].value;
const _isSorted = toBoolean(isSorted.value);
const colIndex = _isSorted
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range.length, getValueFromRange)
- : linearSearch(range, searchKey, "wildcard", range.length, getValueFromRange, this.lookupCaches);
- const col = range[colIndex];
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
+ : linearSearch(range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
+ const col = _range[colIndex];
if (col === undefined) {
return valueNotAvailable(searchKey);
}
@@ -18486,35 +18487,37 @@ const LOOKUP = {
description: _t("Look up a value."),
args: [
arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
- 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.")),
- 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.")),
+ 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.")),
+ 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.")),
],
compute: function (searchKey, searchArray, resultRange) {
- let nbCol = searchArray.length;
- let nbRow = searchArray[0].length;
+ const _searchArray = toMatrix(searchArray);
+ const _resultRange = toMatrix(resultRange);
+ let nbCol = _searchArray.length;
+ let nbRow = _searchArray[0].length;
const verticalSearch = nbRow >= nbCol;
const getElement = verticalSearch
? (range, index) => range[0][index].value
: (range, index) => range[index][0].value;
const rangeLength = verticalSearch ? nbRow : nbCol;
- const index = dichotomicSearch(searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
+ const index = dichotomicSearch(_searchArray, searchKey, "nextSmaller", "asc", rangeLength, getElement);
if (index === -1 ||
- (verticalSearch && searchArray[0][index] === undefined) ||
- (!verticalSearch && searchArray[index][nbRow - 1] === undefined)) {
+ (verticalSearch && _searchArray[0][index] === undefined) ||
+ (!verticalSearch && _searchArray[index][nbRow - 1] === undefined)) {
return valueNotAvailable(searchKey);
}
- if (resultRange === undefined) {
- return verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1];
+ if (_resultRange[0].length === 0) {
+ return verticalSearch ? _searchArray[nbCol - 1][index] : _searchArray[index][nbRow - 1];
}
- nbCol = resultRange.length;
- nbRow = resultRange[0].length;
+ nbCol = _resultRange.length;
+ nbRow = _resultRange[0].length;
assert(() => nbCol === 1 || nbRow === 1, _t("The result_range must be a single row or a single column."));
if (nbCol > 1) {
assert(() => index <= nbCol - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range row value %s.", (index + 1).toString()));
- return resultRange[index][0];
+ return _resultRange[index][0];
}
assert(() => index <= nbRow - 1, _t("[[FUNCTION_NAME]] evaluates to an out of range column value %s.", (index + 1).toString()));
- return resultRange[0][index];
+ return _resultRange[0][index];
},
isExported: true,
};
@@ -18531,28 +18534,29 @@ const MATCH = {
],
compute: function (searchKey, range, searchType = { value: DEFAULT_SEARCH_TYPE }) {
let _searchType = toNumber(searchType, this.locale);
- const nbCol = range.length;
- const nbRow = range[0].length;
+ const _range = toMatrix(range);
+ const nbCol = _range.length;
+ const nbRow = _range[0].length;
assert(() => nbCol === 1 || nbRow === 1, _t("The range must be a single row or a single column."));
let index = -1;
const getElement = nbCol === 1
- ? (range, index) => range[0][index].value
- : (range, index) => range[index][0].value;
- const rangeLen = nbCol === 1 ? range[0].length : range.length;
+ ? (_range, index) => _range[0][index].value
+ : (_range, index) => _range[index][0].value;
+ const rangeLen = nbCol === 1 ? _range[0].length : _range.length;
_searchType = Math.sign(_searchType);
switch (_searchType) {
case 1:
- index = dichotomicSearch(range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
+ index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
break;
case 0:
- index = linearSearch(range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
break;
case -1:
- index = dichotomicSearch(range, searchKey, "nextGreater", "desc", rangeLen, getElement);
+ index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
break;
}
- if ((nbCol === 1 && range[0][index] === undefined) ||
- (nbCol !== 1 && range[index] === undefined)) {
+ if ((nbCol === 1 && _range[0][index] === undefined) ||
+ (nbCol !== 1 && _range[index] === undefined)) {
return valueNotAvailable(searchKey);
}
return index + 1;
@@ -18607,13 +18611,14 @@ const VLOOKUP = {
],
compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
const _index = Math.trunc(toNumber(index?.value, this.locale));
- assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
+ const _range = toMatrix(range);
+ assert(() => 1 <= _index && _index <= _range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
const getValueFromRange = (range, index) => range[0][index].value;
const _isSorted = toBoolean(isSorted.value);
const rowIndex = _isSorted
- ? dichotomicSearch(range, searchKey, "nextSmaller", "asc", range[0].length, getValueFromRange)
- : linearSearch(range, searchKey, "wildcard", range[0].length, getValueFromRange, this.lookupCaches);
- const value = range[_index - 1][rowIndex];
+ ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
+ const value = _range[_index - 1][rowIndex];
if (value === undefined) {
return valueNotAvailable(searchKey);
}
@@ -18650,27 +18655,29 @@ const XLOOKUP = {
compute: function (searchKey, lookupRange, returnRange, defaultValue, matchMode = { value: DEFAULT_MATCH_MODE }, searchMode = { value: DEFAULT_SEARCH_MODE }) {
const _matchMode = Math.trunc(toNumber(matchMode.value, this.locale));
const _searchMode = Math.trunc(toNumber(searchMode.value, this.locale));
- assert(() => lookupRange.length === 1 || lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
+ const _lookupRange = toMatrix(lookupRange);
+ const _returnRange = toMatrix(returnRange);
+ assert(() => _lookupRange.length === 1 || _lookupRange[0].length === 1, _t("lookup_range should be either a single row or single column."));
assert(() => [-1, 1, -2, 2].includes(_searchMode), _t("search_mode should be a value in [-1, 1, -2, 2]."));
assert(() => [-1, 0, 1, 2].includes(_matchMode), _t("match_mode should be a value in [-1, 0, 1, 2]."));
- const lookupDirection = lookupRange.length === 1 ? "col" : "row";
+ const lookupDirection = _lookupRange.length === 1 ? "col" : "row";
assert(() => !(_matchMode === 2 && [-2, 2].includes(_searchMode)), _t("the search and match mode combination is not supported for XLOOKUP evaluation."));
assert(() => lookupDirection === "col"
- ? returnRange[0].length === lookupRange[0].length
- : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
+ ? _returnRange[0].length === _lookupRange[0].length
+ : _returnRange.length === _lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
const getElement = lookupDirection === "col"
? (range, index) => range[0][index].value
: (range, index) => range[index][0].value;
- const rangeLen = lookupDirection === "col" ? lookupRange[0].length : lookupRange.length;
+ const rangeLen = lookupDirection === "col" ? _lookupRange[0].length : _lookupRange.length;
const mode = MATCH_MODE[_matchMode];
const reverseSearch = _searchMode === -1;
const index = _searchMode === 2 || _searchMode === -2
- ? dichotomicSearch(lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
- : linearSearch(lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
+ ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
if (index !== -1) {
return lookupDirection === "col"
- ? returnRange.map((col) => [col[index]])
- : [returnRange[index]];
+ ? _returnRange.map((col) => [col[index]])
+ : [_returnRange[index]];
}
if (defaultValue === undefined) {
return valueNotAvailable(searchKey);
@@ -48476,11 +48483,10 @@ class GridRenderer {
switch (layer) {
case "Background":
this.drawGlobalBackground(renderingContext);
- for (const zone of this.getters.getAllActiveViewportsZones()) {
+ for (const { zone, rect } of this.getters.getAllActiveViewportsZonesAndRect()) {
const { ctx } = renderingContext;
ctx.save();
ctx.beginPath();
- const rect = this.getters.getVisibleRect(zone);
ctx.rect(rect.x, rect.y, rect.width, rect.height);
ctx.clip();
const boxes = this.getGridBoxes(zone);
@@ -48756,10 +48762,8 @@ class GridRenderer {
const { ctx, thinLineWidth } = renderingContext;
const visibleCols = this.getters.getSheetViewVisibleCols();
const left = visibleCols[0];
- const right = visibleCols[visibleCols.length - 1];
const visibleRows = this.getters.getSheetViewVisibleRows();
const top = visibleRows[0];
- const bottom = visibleRows[visibleRows.length - 1];
const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
const selection = this.getters.getSelectedZones();
const selectedCols = getZonesCols(selection);
@@ -48775,7 +48779,7 @@ class GridRenderer {
ctx.lineWidth = thinLineWidth;
ctx.strokeStyle = "#333";
// Columns headers background
- for (let col = left; col <= right; col++) {
+ for (const col of visibleCols) {
const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
const { x, width } = this.getters.getVisibleRect(colZone);
const isColActive = activeCols.has(col);
@@ -48792,7 +48796,7 @@ class GridRenderer {
ctx.fillRect(x, 0, width, HEADER_HEIGHT);
}
// Rows headers background
- for (let row = top; row <= bottom; row++) {
+ for (const row of visibleRows) {
const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
const { y, height } = this.getters.getVisibleRect(rowZone);
const isRowActive = activeRows.has(row);
@@ -48818,21 +48822,21 @@ class GridRenderer {
ctx.stroke();
ctx.beginPath();
// column text + separator
- for (const i of visibleCols) {
- const colSize = this.getters.getColSize(sheetId, i);
- const colName = numberToLetters(i);
- ctx.fillStyle = activeCols.has(i) ? "#fff" : TEXT_HEADER_COLOR;
- let colStart = this.getHeaderOffset("COL", left, i);
+ for (const col of visibleCols) {
+ const colSize = this.getters.getColSize(sheetId, col);
+ const colName = numberToLetters(col);
+ ctx.fillStyle = activeCols.has(col) ? "#fff" : TEXT_HEADER_COLOR;
+ let colStart = this.getHeaderOffset("COL", left, col);
ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);
ctx.moveTo(colStart + colSize, 0);
ctx.lineTo(colStart + colSize, HEADER_HEIGHT);
}
// row text + separator
- for (const i of visibleRows) {
- const rowSize = this.getters.getRowSize(sheetId, i);
- ctx.fillStyle = activeRows.has(i) ? "#fff" : TEXT_HEADER_COLOR;
- let rowStart = this.getHeaderOffset("ROW", top, i);
- ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
+ for (const row of visibleRows) {
+ const rowSize = this.getters.getRowSize(sheetId, row);
+ ctx.fillStyle = activeRows.has(row) ? "#fff" : TEXT_HEADER_COLOR;
+ let rowStart = this.getHeaderOffset("ROW", top, row);
+ ctx.fillText(String(row + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
ctx.moveTo(0, rowStart + rowSize);
ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);
}
@@ -49132,6 +49136,9 @@ function useGridDrawing(refName, model, canvasSize) {
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.setAttribute("style", `width:${width}px;height:${height}px;`);
+ if (width === 0 || height === 0) {
+ return;
+ }
// Imagine each pixel as a large square. The whole-number coordinates (0, 1, 2…)
// are the edges of the squares. If you draw a one-unit-wide line between whole-number
// coordinates, it will overlap opposite sides of the pixel square, and the resulting
@@ -50703,7 +50710,7 @@ class BordersPlugin extends CorePlugin {
getCommonSides(border1, border2) {
const commonBorder = {};
for (let side of ["top", "bottom", "left", "right"]) {
- if (border1[side] && border1[side] === border2[side]) {
+ if (border1[side] && deepEquals(border1[side], border2[side])) {
commonBorder[side] = border1[side];
}
}
@@ -64973,8 +64980,17 @@ class InternalViewport {
this.getters = getters;
this.sheetId = sheetId;
this.boundaries = boundaries;
- this.viewportWidth = sizeInGrid.width;
- this.viewportHeight = sizeInGrid.height;
+ if (sizeInGrid.width < 0 || sizeInGrid.height < 0) {
+ throw new Error("Viewport size cannot be negative");
+ }
+ this.viewportWidth = sizeInGrid.height && sizeInGrid.width;
+ this.viewportHeight = sizeInGrid.width && sizeInGrid.height;
+ this.top = boundaries.top;
+ this.bottom = boundaries.bottom;
+ this.left = boundaries.left;
+ this.right = boundaries.right;
+ this.offsetX = offsets.x;
+ this.offsetY = offsets.y;
this.offsetScrollbarX = offsets.x;
this.offsetScrollbarY = offsets.y;
this.canScrollVertically = options.canScrollVertically;
@@ -65017,9 +65033,9 @@ class InternalViewport {
Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
);
height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
- }
- if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
- height += FOOTER_HEIGHT;
+ if (lastRowEnd + FOOTER_HEIGHT > height && !this.getters.isReadonly()) {
+ height += FOOTER_HEIGHT;
+ }
}
return { width, height };
}
@@ -65160,6 +65176,9 @@ class InternalViewport {
!this.getters.isRowHidden(this.sheetId, row));
}
searchHeaderIndex(dimension, position, startIndex = 0) {
+ if (this.viewportWidth <= 0 || this.viewportHeight <= 0) {
+ return -1;
+ }
const sheetId = this.sheetId;
const headers = this.getters.getNumberHeaders(sheetId, dimension);
// using a binary search:
@@ -65196,7 +65215,7 @@ class InternalViewport {
this.adjustViewportZoneY();
}
/** Corrects the viewport's horizontal offset based on the current structure
- * To make sure that at least on column is visible inside the viewport.
+ * To make sure that at least one column is visible inside the viewport.
*/
adjustViewportOffsetX() {
if (this.canScrollHorizontally) {
@@ -65208,7 +65227,7 @@ class InternalViewport {
this.adjustViewportZoneX();
}
/** Corrects the viewport's vertical offset based on the current structure
- * To make sure that at least on row is visible inside the viewport.
+ * To make sure that at least one row is visible inside the viewport.
*/
adjustViewportOffsetY() {
if (this.canScrollVertically) {
@@ -65225,11 +65244,14 @@ class InternalViewport {
const sheetId = this.sheetId;
this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
+ if (!this.viewportWidth) {
+ return;
+ }
if (this.left === -1) {
this.left = this.boundaries.left;
}
if (this.right === -1) {
- this.right = this.getters.getNumberCols(sheetId) - 1;
+ this.right = this.boundaries.right;
}
this.offsetX =
this.getters.getColDimensions(sheetId, this.left).start -
@@ -65241,11 +65263,14 @@ class InternalViewport {
const sheetId = this.sheetId;
this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
+ if (!this.viewportHeight) {
+ return;
+ }
if (this.top === -1) {
this.top = this.boundaries.top;
}
if (this.bottom === -1) {
- this.bottom = this.getters.getNumberRows(sheetId) - 1;
+ this.bottom = this.boundaries.bottom;
}
this.offsetY =
this.getters.getRowDimensions(sheetId, this.top).start -
@@ -65319,7 +65344,7 @@ class SheetViewPlugin extends UIPlugin {
"isPositionVisible",
"getColDimensionsInViewport",
"getRowDimensionsInViewport",
- "getAllActiveViewportsZones",
+ "getAllActiveViewportsZonesAndRect",
"getRect",
];
viewports = {};
@@ -65552,12 +65577,12 @@ class SheetViewPlugin extends UIPlugin {
const sheetId = this.getters.getActiveSheetId();
const viewports = this.getSubViewports(sheetId);
//TODO ake another commit to eimprove this
- return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => !this.getters.isHeaderHidden(sheetId, "COL", col));
+ return [...new Set(viewports.map((v) => range(v.left, v.right + 1)).flat())].filter((col) => col >= 0 && !this.getters.isHeaderHidden(sheetId, "COL", col));
}
getSheetViewVisibleRows() {
const sheetId = this.getters.getActiveSheetId();
const viewports = this.getSubViewports(sheetId);
- return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
+ return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => row >= 0 && !this.getters.isHeaderHidden(sheetId, "ROW", row));
}
/**
* Get the positions of all the cells that are visible in the viewport, taking merges into account.
@@ -65600,19 +65625,19 @@ class SheetViewPlugin extends UIPlugin {
maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
};
}
- getColRowOffsetInViewport(dimension, referenceIndex, index) {
- const sheetId = this.getters.getActiveSheetId();
- const visibleCols = this.getters.getSheetViewVisibleCols();
- const visibleRows = this.getters.getSheetViewVisibleRows();
- if (index < referenceIndex) {
- return -this.getColRowOffsetInViewport(dimension, index, referenceIndex);
+ getColRowOffsetInViewport(dimension, referenceHeaderIndex, targetHeaderIndex) {
+ if (targetHeaderIndex < referenceHeaderIndex) {
+ return -this.getColRowOffsetInViewport(dimension, targetHeaderIndex, referenceHeaderIndex);
}
+ const sheetId = this.getters.getActiveSheetId();
+ const visibleHeaders = dimension === "COL"
+ ? this.getters.getSheetViewVisibleCols()
+ : this.getters.getSheetViewVisibleRows();
+ const startIndex = visibleHeaders.findIndex((header) => referenceHeaderIndex >= header);
+ const endIndex = visibleHeaders.findIndex((header) => targetHeaderIndex <= header);
+ const relevantIndexes = visibleHeaders.slice(startIndex, endIndex);
let offset = 0;
- const visibleIndexes = dimension === "COL" ? visibleCols : visibleRows;
- for (let i = referenceIndex; i < index; i++) {
- if (!visibleIndexes.includes(i)) {
- continue;
- }
+ for (const i of relevantIndexes) {
offset += this.getters.getHeaderSize(sheetId, dimension, i);
}
return offset;
@@ -65659,7 +65684,7 @@ class SheetViewPlugin extends UIPlugin {
}
return { canEdgeScroll, direction, delay };
}
- getEdgeScrollRow(y, previousY, tartingY) {
+ getEdgeScrollRow(y, previousY, startingY) {
let canEdgeScroll = false;
let direction = 0;
let delay = 0;
@@ -65680,7 +65705,7 @@ class SheetViewPlugin extends UIPlugin {
delay = scrollDelay(y - height);
direction = 1;
}
- else if (y < offsetCorrectionY && tartingY >= offsetCorrectionY && currentOffsetY > 0) {
+ else if (y < offsetCorrectionY && startingY >= offsetCorrectionY && currentOffsetY > 0) {
// 2
canEdgeScroll = true;
delay = scrollDelay(offsetCorrectionY - y);
@@ -65706,13 +65731,7 @@ class SheetViewPlugin extends UIPlugin {
*/
getVisibleRectWithoutHeaders(zone) {
const sheetId = this.getters.getActiveSheetId();
- const viewportRects = this.getSubViewports(sheetId)
- .map((viewport) => viewport.getVisibleRect(zone))
- .filter(isDefined);
- if (viewportRects.length === 0) {
- return { x: 0, y: 0, width: 0, height: 0 };
- }
- return this.recomposeRect(viewportRects);
+ return this.mapViewportsToRect(sheetId, (viewport) => viewport.getVisibleRect(zone));
}
/**
* Computes the actual size and position (:Rect) of the zone on the canvas
@@ -65720,13 +65739,7 @@ class SheetViewPlugin extends UIPlugin {
*/
getRect(zone) {
const sheetId = this.getters.getActiveSheetId();
- const viewportRects = this.getSubViewports(sheetId)
- .map((viewport) => viewport.getFullRect(zone))
- .filter(isDefined);
- if (viewportRects.length === 0) {
- return { x: 0, y: 0, width: 0, height: 0 };
- }
- const rect = this.recomposeRect(viewportRects);
+ const rect = this.mapViewportsToRect(sheetId, (viewport) => viewport.getFullRect(zone));
return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
}
/**
@@ -65771,9 +65784,18 @@ class SheetViewPlugin extends UIPlugin {
end: start + (isRowHidden ? 0 : size),
};
}
- getAllActiveViewportsZones() {
+ getAllActiveViewportsZonesAndRect() {
const sheetId = this.getters.getActiveSheetId();
- return this.getSubViewports(sheetId);
+ return this.getSubViewports(sheetId).map((viewport) => {
+ return {
+ zone: viewport,
+ rect: {
+ x: viewport.offsetCorrectionX + this.gridOffsetX,
+ y: viewport.offsetCorrectionY + this.gridOffsetY,
+ ...viewport.getMaxSize(),
+ },
+ };
+ });
}
// ---------------------------------------------------------------------------
// Private
@@ -65832,12 +65854,11 @@ class SheetViewPlugin extends UIPlugin {
}
/** gets rid of deprecated sheetIds */
cleanViewports() {
- const sheetIds = this.getters.getSheetIds();
- for (let sheetId of Object.keys(this.viewports)) {
- if (!sheetIds.includes(sheetId)) {
- delete this.viewports[sheetId];
- }
+ const newViewport = {};
+ for (const sheetId of this.getters.getSheetIds()) {
+ newViewport[sheetId] = this.viewports[sheetId];
}
+ this.viewports = newViewport;
}
resizeSheetView(height, width, gridOffsetX = 0, gridOffsetY = 0) {
this.sheetViewHeight = height;
@@ -65847,7 +65868,7 @@ class SheetViewPlugin extends UIPlugin {
this.recomputeViewports();
}
recomputeViewports() {
- for (let sheetId of Object.keys(this.viewports)) {
+ for (const sheetId of this.getters.getSheetIds()) {
this.resetViewports(sheetId);
}
}
@@ -65869,8 +65890,10 @@ class SheetViewPlugin extends UIPlugin {
const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
const nCols = this.getters.getNumberCols(sheetId);
const nRows = this.getters.getNumberRows(sheetId);
- const colOffset = this.getters.getColRowOffset("COL", 0, xSplit, sheetId);
- const rowOffset = this.getters.getColRowOffset("ROW", 0, ySplit, sheetId);
+ const colOffset = Math.min(this.getters.getColRowOffset("COL", 0, xSplit, sheetId), this.sheetViewWidth);
+ const rowOffset = Math.min(this.getters.getColRowOffset("ROW", 0, ySplit, sheetId), this.sheetViewHeight);
+ const unfrozenWidth = Math.max(this.sheetViewWidth - colOffset, 0);
+ const unfrozenHeight = Math.max(this.sheetViewHeight - rowOffset, 0);
const { xRatio, yRatio } = this.getFrozenSheetViewRatio(sheetId);
const canScrollHorizontally = xRatio < 1.0;
const canScrollVertically = yRatio < 1.0;
@@ -65881,14 +65904,14 @@ class SheetViewPlugin extends UIPlugin {
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 })) ||
undefined,
topRight: (ySplit &&
- 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 })) ||
+ 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 })) ||
undefined,
bottomLeft: (xSplit &&
- 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 })) ||
+ 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 })) ||
undefined,
bottomRight: new InternalViewport(this.getters, sheetId, { left: xSplit, right: nCols - 1, top: ySplit, bottom: nRows - 1 }, {
- width: this.sheetViewWidth - colOffset,
- height: this.sheetViewHeight - rowOffset,
+ width: unfrozenWidth,
+ height: unfrozenHeight,
}, { canScrollHorizontally, canScrollVertically }, {
x: canScrollHorizontally ? previousOffset.x : 0,
y: canScrollVertically ? previousOffset.y : 0,
@@ -65965,12 +65988,26 @@ class SheetViewPlugin extends UIPlugin {
const height = this.sheetViewHeight + this.gridOffsetY;
return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };
}
- recomposeRect(viewportRects) {
- const x = Math.min(...viewportRects.map((rect) => rect.x));
- const y = Math.min(...viewportRects.map((rect) => rect.y));
- const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
- const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
- return { x, y, width, height };
+ mapViewportsToRect(sheetId, rectCallBack) {
+ let x = Infinity;
+ let y = Infinity;
+ let width = 0;
+ let height = 0;
+ let hasViewports = false;
+ for (const viewport of this.getSubViewports(sheetId)) {
+ const rect = rectCallBack(viewport);
+ if (rect) {
+ hasViewports = true;
+ x = Math.min(x, rect.x);
+ y = Math.min(y, rect.y);
+ width = Math.max(width, rect.x + rect.width);
+ height = Math.max(height, rect.y + rect.height);
+ }
+ }
+ if (!hasViewports) {
+ return { x: 0, y: 0, width: 0, height: 0 };
+ }
+ return { x, y, width: width - x, height: height - y };
}
}
@@ -73088,7 +73125,7 @@ const constants = {
export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
-__info__.version = "18.0.13";
-__info__.date = "2025-01-31T08:06:35.677Z";
-__info__.hash = "f505971d4";
+__info__.version = "18.0.14";
+__info__.date = "2025-02-05T07:24:11.049Z";
+__info__.hash = "90f2af4ff";
//# sourceMappingURL=o_spreadsheet.js.map
diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
index 5b8b107da3a..371fe1b376e 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
@@ -1,9 +1,9 @@
<!--
This file is generated by o-spreadsheet build tools. Do not edit it.
@see https://github.com/odoo/o-spreadsheet
- @version 18.0.13
- @date 2025-01-31T08:06:39.800Z
- @hash f505971d4
+ @version 18.0.14
+ @date 2025-02-05T07:24:15.390Z
+ @hash 90f2af4ff
-->
<odoo>
<t t-name="o-spreadsheet-ActionButton">