diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js index f1d208d6c50..342d65c448e 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 17.0.9 - * @date 2024-01-29T15:41:09.261Z - * @hash 3c1209a38 + * @version 17.0.11 + * @date 2024-02-09T14:36:35.988Z + * @hash bb51d6315 */ (function (exports, owl) { @@ -2889,6 +2889,9 @@ function formatValue(value, { format, locale }) { switch (typeof value) { case "string": + if (value.includes('\\"')) { + return value.replace(/\\"/g, '"'); + } return value; case "boolean": return value ? "TRUE" : "FALSE"; @@ -4933,14 +4936,8 @@ : undefined)); } } - else if (zone.left === zone.right && zone.top === zone.bottom) { - // A single cell. If it's only the title, the dataset is not added. - if (!dataSetsHaveTitle) { - dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined)); - } - } else { - /* 1 row or 1 column */ + /* 1 cell, 1 row or 1 column */ dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle ? { top: zone.top, @@ -4989,8 +4986,10 @@ } const dataRange = ds.dataRange.clone({ zone: dataZone }); return { - label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined, - range: getters.getRangeString(dataRange, "forceSheetReference"), + label: ds.labelCell + ? getters.getRangeString(ds.labelCell, "forceSheetReference", { useFixedReference: true }) + : undefined, + range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }), }; } function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) { @@ -5003,7 +5002,7 @@ zone.top = zone.top + 1; } const range = labelRange.clone({ zone }); - return getters.getRangeString(range, "forceSheetReference"); + return getters.getRangeString(range, "forceSheetReference", { useFixedReference: true }); } /** * Transform a chart definition which supports dataSets (dataSets and LabelRange) @@ -7384,7 +7383,7 @@ while (!chars.isOver()) { let token = tokenizeSpace(chars) || tokenizeArgsSeparator(chars, locale) || - tokenizeMisc(chars) || + tokenizeParenthesis(chars) || tokenizeOperator(chars) || tokenizeString(chars) || tokenizeDebugger(chars) || @@ -7405,15 +7404,14 @@ } return null; } - const misc$1 = { - "(": "LEFT_PAREN", - ")": "RIGHT_PAREN", + const parenthesis = { + "(": { type: "LEFT_PAREN", value: "(" }, + ")": { type: "RIGHT_PAREN", value: ")" }, }; - function tokenizeMisc(chars) { - if (chars.current in misc$1) { + function tokenizeParenthesis(chars) { + if (chars.current === "(" || chars.current === ")") { const value = chars.shift(); - const type = misc$1[value]; - return { type, value }; + return parenthesis[value]; } return null; } @@ -7434,7 +7432,12 @@ } return null; } + const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789"); function tokenizeNumber(chars, locale) { + if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) && + chars.current !== locale.decimalSeparator) { + return null; + } const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator)); if (match) { chars.advanceBy(match[0].length); @@ -7459,7 +7462,7 @@ } return null; } - const separatorRegexp = /\w|\.|!|\$/; + const separatorRegexp = /^[\w\.!\$]+/; /** * A "Symbol" is just basically any word-like element that can appear in a * formula, which is not a string. So: @@ -7499,8 +7502,11 @@ }; } } - while (chars.current && separatorRegexp.test(chars.current)) { - result += chars.shift(); + const match = chars.remaining().match(separatorRegexp); + if (match) { + const value = match[0]; + result += value; + chars.advanceBy(value.length); } if (result.length) { const value = result; @@ -7562,7 +7568,10 @@ return this.text.substring(this.currentIndex); } currentStartsWith(str) { - for (let j = 0; j < str.length; j++) { + if (this.current !== str[0]) { + return false; + } + for (let j = 1; j < str.length; j++) { if (this.text[this.currentIndex + j] !== str[j]) { return false; } @@ -8593,7 +8602,7 @@ return undefined; const dataSets = this.dataSets .map((ds) => toExcelDataset(this.getters, ds)) - .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ? + .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING); const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle)); return { ...this.getDefinition(), @@ -9171,7 +9180,7 @@ return undefined; const dataSets = this.dataSets .map((ds) => toExcelDataset(this.getters, ds)) - .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ? + .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING); const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle)); return { ...this.getDefinition(), @@ -9477,7 +9486,7 @@ return undefined; const dataSets = this.dataSets .map((ds) => toExcelDataset(this.getters, ds)) - .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ? + .filter((ds) => ds.range !== "" && ds.range !== INCORRECT_RANGE_STRING); const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle)); return { ...this.getDefinition(), @@ -19920,19 +19929,20 @@ const formatNumberCurrency = createFormatActionSpec({ name: _t("Currency"), descriptionValue: 1000.12, - format: (env) => env.model.config.defaultCurrencyFormat, + format: (env) => env.model.config.defaultCurrencyFormat ?? DEFAULT_CURRENCY_FORMAT, }); const formatNumberCurrencyRounded = { ...createFormatActionSpec({ name: _t("Currency rounded"), descriptionValue: 1000, - format: (env) => roundFormat(env.model.config.defaultCurrencyFormat), + format: (env) => roundFormat(env.model.config.defaultCurrencyFormat ?? DEFAULT_CURRENCY_FORMAT), }), isVisible: (env) => { const currencyFormat = env.model.config.defaultCurrencyFormat; - return currencyFormat !== roundFormat(currencyFormat); + return currencyFormat !== roundFormat(currencyFormat ?? DEFAULT_CURRENCY_FORMAT); }, }; + const DEFAULT_CURRENCY_FORMAT = "[$$]#,##0.00"; const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE); const formatCustomCurrency = { name: _t("Custom currency"), @@ -26500,6 +26510,7 @@ static components = { TextValueProvider, FunctionDescriptionProvider }; static defaultProps = { inputStyle: "", + isDefaultFocus: false, }; composerRef = owl.useRef("o_composer"); contentHelper = new ContentEditableHelper(this.composerRef.el); @@ -26556,7 +26567,6 @@ F2: () => console.warn("Not implemented"), F4: this.processF4Key, Tab: (ev) => this.processTabKey(ev), - " ": (ev) => this.processSpaceKey(ev), }; keyCodeMapping = { NumpadDecimal: this.processNumpadDecimal, @@ -26564,12 +26574,12 @@ setup() { owl.onMounted(() => { const el = this.composerRef.el; + if (this.props.isDefaultFocus) { + this.env.focusableElement.setFocusableElement(el); + } this.contentHelper.updateEl(el); this.processTokenAtCursor(); }); - owl.onWillUnmount(() => { - this.props.onComposerUnmounted?.(); - }); owl.useEffect(() => { this.processContent(); }); @@ -26578,7 +26588,8 @@ // Handlers // --------------------------------------------------------------------------- processArrowKeys(ev) { - if (this.env.model.getters.isSelectingForComposer()) { + if (this.env.model.getters.isSelectingForComposer() || + this.env.model.getters.getEditionMode() === "inactive") { this.functionDescriptionState.showDescription = false; // Prevent the default content editable behavior which moves the cursor ev.preventDefault(); @@ -26621,26 +26632,20 @@ processTabKey(ev) { ev.preventDefault(); ev.stopPropagation(); - const state = this.autoCompleteState; - if (state.showProvider && state.selectedIndex !== undefined) { - const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text; - if (autoCompleteValue) { - this.autoComplete(autoCompleteValue); - return; + if (this.env.model.getters.getEditionMode() !== "inactive") { + const state = this.autoCompleteState; + if (state.showProvider && state.selectedIndex !== undefined) { + const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text; + if (autoCompleteValue) { + this.autoComplete(autoCompleteValue); + return; + } } + interactiveStopEdition(this.env); } const direction = ev.shiftKey ? "left" : "right"; - interactiveStopEdition(this.env); this.env.model.selection.moveAnchorCell(direction, 1); } - processSpaceKey(ev) { - if (ev.ctrlKey) { - ev.preventDefault(); - ev.stopPropagation(); - this.showFunctionAutocomplete(""); - this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION"); - } - } processEnterKey(ev) { ev.preventDefault(); ev.stopPropagation(); @@ -26693,6 +26698,9 @@ this.compositionActive = false; } onKeydown(ev) { + if (this.env.model.getters.getEditionMode() === "inactive") { + return; + } let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code]; if (handler) { handler.call(this, ev); @@ -26701,14 +26709,33 @@ ev.stopPropagation(); } } + onPaste(ev) { + if (this.env.model.getters.getEditionMode() !== "inactive") { + ev.stopPropagation(); + } + } /* * Triggered automatically by the content-editable between the keydown and key up * */ onInput(ev) { - if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) { + if (!this.shouldProcessInputEvents) { return; } - let content = this.contentHelper.getText(); + if (ev.inputType === "insertFromPaste" && + this.env.model.getters.getEditionMode() === "inactive") { + return; + } + ev.stopPropagation(); + let content; + if (this.env.model.getters.getEditionMode() === "inactive") { + content = ev.data || ""; + } + else { + content = this.contentHelper.getText(); + } + if (this.props.focus === "inactive") { + return this.props.onComposerCellFocused?.(content); + } let selection = this.contentHelper.getCurrentSelection(); if (ev.inputType === "insertParagraph") { const start = Math.min(selection.start, selection.end); @@ -26790,9 +26817,8 @@ } const newSelection = this.contentHelper.getCurrentSelection(); this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION"); - if (this.props.focus === "inactive") { - this.props.onComposerContentFocused(newSelection); - } + this.props.onComposerContentFocused(); + if (this.props.focus === "inactive") ; this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection); this.processTokenAtCursor(); } @@ -26959,7 +26985,7 @@ const highlights = this.env.model.getters.getHighlights(); const refSheet = sheetName ? this.env.model.getters.getSheetIdByName(sheetName) - : this.env.model.getters.getCurrentEditedCell().sheetId; + : this.env.model.getters.getCurrentEditedCell()?.sheetId; const highlight = highlights.find((highlight) => { if (highlight.sheetId !== refSheet) return false; @@ -27052,12 +27078,13 @@ } } Composer.props = { - focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) }, - onComposerContentFocused: Function, inputStyle: { type: String, optional: true }, rect: { type: Object, optional: true }, delimitation: { type: Object, optional: true }, - onComposerUnmounted: { type: Function, optional: true }, + focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) }, + onComposerCellFocused: { type: Function, optional: true }, + onComposerContentFocused: Function, + isDefaultFocus: { type: Boolean, optional: true }, }; const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1; @@ -27091,51 +27118,25 @@ class GridComposer extends owl.Component { static template = "o-spreadsheet-GridComposer"; static components = { Composer }; - gridComposerRef; - zone; - rect; + rect = this.defaultRect; + isEditing = false; isCellReferenceVisible; - composerState; + get defaultRect() { + return { x: 0, y: 0, width: 0, height: 0 }; + } setup() { - this.gridComposerRef = owl.useRef("gridComposer"); - this.composerState = owl.useState({ - rect: undefined, - delimitation: undefined, - }); - const { sheetId, col, row } = this.env.model.getters.getActivePosition(); - this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row })); - this.rect = this.env.model.getters.getVisibleRect(this.zone); - this.isCellReferenceVisible = false; - owl.onMounted(() => { - const el = this.gridComposerRef.el; - this.composerState.rect = { - x: this.rect.x, - y: this.rect.y, - width: el.clientWidth, - height: el.clientHeight, - }; - this.composerState.delimitation = { - width: this.props.gridDims.width, - height: this.props.gridDims.height, - }; - }); owl.onWillUpdateProps(() => { - if (this.isCellReferenceVisible) { - return; - } - const sheetId = this.env.model.getters.getActiveSheetId(); - const zone = this.env.model.getters.getSelectedZone(); - const rect = this.env.model.getters.getVisibleRect(zone); - if (!deepEquals(rect, this.rect) || - sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) { - this.isCellReferenceVisible = true; - } + this.updateComponentPosition(); + this.updateCellReferenceVisibility(); }); } get shouldDisplayCellReference() { return this.isCellReferenceVisible; } get cellReference() { + if (!this.env.model.getters.getCurrentEditedCell()) { + return ""; + } const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell(); const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId(); return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`; @@ -27147,7 +27148,27 @@ top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`, }); } + get composerProps() { + const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders(); + return { + rect: { ...this.rect }, + delimitation: { + width, + height, + }, + focus: this.props.focus, + isDefaultFocus: true, + onComposerContentFocused: this.props.onComposerContentFocused, + onComposerCellFocused: this.props.onComposerCellFocused, + }; + } get containerStyle() { + if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) { + return ` + position: absolute; + z-index: -1000; + `; + } const isFormula = this.env.model.getters.getCurrentContent().startsWith("="); const cell = this.env.model.getters.getActiveCell(); const position = this.env.model.getters.getActivePosition(); @@ -27167,6 +27188,8 @@ if (!isFormula) { textAlign = style.align || cell.defaultAlign; } + const maxHeight = this.props.gridDims.height - this.rect.y; + const maxWidth = this.props.gridDims.width - this.rect.x; /** * min-size is on the container, not the composer element, because we want to have the same size as the cell by default, * including all the paddings/margins of the composer @@ -27178,6 +27201,8 @@ top: `${top}px`, "min-width": `${width + 1}px`, "min-height": `${height + 1}px`, + "max-width": `${maxWidth}px`, + "max-height": `${maxHeight}px`, background, color, "font-size": `${fontSizeInPixels(fontSize)}px`, @@ -27187,20 +27212,38 @@ "text-align": textAlign, }); } - get composerStyle() { - const maxHeight = this.props.gridDims.height - this.rect.y; - const maxWidth = this.props.gridDims.width - this.rect.x; - return cssPropertiesToCss({ - "max-width": `${maxWidth}px`, - "max-height": `${maxHeight}px`, - }); + updateComponentPosition() { + const isEditing = this.env.model.getters.getEditionMode() !== "inactive"; + if (this.isEditing !== isEditing) { + this.isEditing = isEditing; + if (!isEditing) { + this.rect = this.defaultRect; + this.env.focusableElement.focus(); + return; + } + const position = this.env.model.getters.getActivePosition(); + const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position)); + this.rect = this.env.model.getters.getVisibleRect(zone); + } + } + updateCellReferenceVisibility() { + if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") { + return; + } + const sheetId = this.env.model.getters.getActiveSheetId(); + const zone = this.env.model.getters.getSelectedZone(); + const rect = this.env.model.getters.getVisibleRect(zone); + if (!deepEquals(rect, this.rect) || + sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) { + this.isCellReferenceVisible = true; + } } } GridComposer.props = { focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) }, - onComposerUnmounted: Function, onComposerContentFocused: Function, gridDims: Object, + onComposerCellFocused: Function, }; const CSS$1 = css /* scss */ ` @@ -28291,7 +28334,6 @@ this.props.onCellDoubleClicked(col, row); } onContextMenu(ev) { - ev.preventDefault(); const [col, row] = this.getCartesianCoordinates(ev); this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY }); } @@ -29352,6 +29394,7 @@ GROUP_HEADERS: groupHeadersMenuRegistry, UNGROUP_HEADERS: unGroupHeadersMenuRegistry, }; + const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"]; // ----------------------------------------------------------------------------- // JS // ----------------------------------------------------------------------------- @@ -29375,7 +29418,6 @@ HEADER_WIDTH = HEADER_WIDTH; menuState; gridRef; - hiddenInput; onMouseWheel; canvasPosition; hoveredCell; @@ -29386,17 +29428,16 @@ menuItems: [], }); this.gridRef = owl.useRef("grid"); - this.hiddenInput = owl.useRef("hiddenInput"); this.canvasPosition = useAbsoluteBoundingRect(this.gridRef); this.hoveredCell = owl.useState({ col: undefined, row: undefined }); owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() }); owl.useExternalListener(document.body, "cut", this.copy.bind(this, true)); owl.useExternalListener(document.body, "copy", this.copy.bind(this, false)); owl.useExternalListener(document.body, "paste", this.paste); - owl.onMounted(() => this.focus()); - this.props.exposeFocus(() => this.focus()); + owl.onMounted(() => this.focusDefaultElement()); + this.props.exposeFocus(() => this.focusDefaultElement()); useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders()); - owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]); + owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]); this.onMouseWheel = useWheelHandler((deltaX, deltaY) => { this.moveCanvas(deltaX, deltaY); this.hoveredCell.col = undefined; @@ -29419,7 +29460,7 @@ if (this.env.model.getters.hasOpenedPopover()) { this.closeOpenedPopover(); } - this.focus(); + this.focusDefaultElement(); } // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key // down itself @@ -29598,10 +29639,10 @@ "ALT+SHIFT+ARROWUP": () => this.processHeaderGroupingKey("up"), "ALT+SHIFT+ARROWDOWN": () => this.processHeaderGroupingKey("down"), }; - focus() { + focusDefaultElement() { if (!this.env.model.getters.getSelectedFigureId() && this.env.model.getters.getEditionMode() === "inactive") { - this.hiddenInput.el?.focus(); + this.env.focusableElement.focus(); } } get gridEl() { @@ -29724,16 +29765,15 @@ } } onKeydown(ev) { - const eventKey = ev.key.toUpperCase(); let keyDownString = ""; - if (isCtrlKey(ev) && eventKey !== "CTRL") - keyDownString += "CTRL+"; - if (ev.metaKey) - keyDownString += "CTRL+"; - if (ev.altKey && eventKey !== "ALT") - keyDownString += "ALT+"; - if (ev.shiftKey && eventKey !== "SHIFT") - keyDownString += "SHIFT+"; + if (!MODIFIER_KEYS.includes(ev.key)) { + if (isCtrlKey(ev)) + keyDownString += "CTRL+"; + if (ev.altKey) + keyDownString += "ALT+"; + if (ev.shiftKey) + keyDownString += "SHIFT+"; + } keyDownString += ev.key.toUpperCase(); let handler = this.keyDownMapping[keyDownString]; if (handler) { @@ -29747,19 +29787,6 @@ return; } } - onInput(ev) { - // the user meant to paste in the sheet, not open the composer with the pasted content - if (!ev.isComposing && ev.inputType === "insertFromPaste") { - return; - } - if (ev.data) { - // if the user types a character on the grid, it means he wants to start composing the selected cell with that - // character - ev.preventDefault(); - ev.stopPropagation(); - this.props.onGridComposerCellFocused(ev.data); - } - } // --------------------------------------------------------------------------- // Context Menu // --------------------------------------------------------------------------- @@ -29874,7 +29901,7 @@ } closeMenu() { this.menuState.isOpen = false; - this.focus(); + this.focusDefaultElement(); } processHeaderGroupingKey(direction) { if (this.env.model.getters.getSelectedZones().length !== 1) { @@ -33769,6 +33796,31 @@ return data; }, }, + { + description: "Fix datafilter duplication", + from: 12, + to: 12.5, + applyMigration(data) { + for (let sheet of data.sheets || []) { + let knownDataFilterZones = []; + for (let filterTable of sheet.filterTables || []) { + const zone = toZone(filterTable.range); + // See commit message for the details + const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone)); + if (intersectZoneIndex !== -1) { + knownDataFilterZones[intersectZoneIndex] = zone; + } + else { + knownDataFilterZones.push(zone); + } + } + sheet.filterTables = knownDataFilterZones.map((zone) => ({ + range: zoneToXc(zone), + })); + } + return data; + }, + }, { description: "Change Border description structure", from: 12, @@ -35614,13 +35666,13 @@ /* * Reconstructs the original formula string based on a normalized form and its dependencies */ - getFormulaCellContent(sheetId, compiledFormula, dependencies) { + getFormulaCellContent(sheetId, compiledFormula, dependencies, useFixedReference = false) { const ranges = dependencies || compiledFormula.dependencies; let rangeIndex = 0; return concat(compiledFormula.tokens.map((token) => { if (token.type === "REFERENCE") { const range = ranges[rangeIndex++]; - return this.getters.getRangeString(range, sheetId); + return this.getters.getRangeString(range, sheetId, { useFixedReference }); } return token.value; })); @@ -35821,8 +35873,11 @@ * being a computed property to rebuild the dependencies XC. */ createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) { - const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc)); - return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this)); + const dependencies = []; + for (const xc of compiledFormula.dependencies) { + dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc)); + } + return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString); } createErrorFormula(id, content, format, style, error) { return { @@ -35876,7 +35931,7 @@ const tokens = compiledFormula.tokens.map((token) => { if (token.type === "REFERENCE") { const index = rangeIndex++; - return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId)); + return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString); } return token; }); @@ -35889,15 +35944,34 @@ get content() { return concat(this.compiledFormula.tokens.map((token) => token.value)); } + get contentWithFixedReferences() { + let rangeIndex = 0; + return concat(this.compiledFormula.tokens.map((token) => { + if (token.type === "REFERENCE") { + const index = rangeIndex++; + return this.getRangeString(this.compiledFormula.dependencies[index], this.sheetId, { + useFixedReference: true, + }); + } + return token.value; + })); + } } class RangeReferenceToken { - getValue; + ranges; + rangeIndex; + sheetId; + getRangeString; type = "REFERENCE"; - constructor(getValue) { - this.getValue = getValue; + constructor(ranges, rangeIndex, sheetId, getRangeString) { + this.ranges = ranges; + this.rangeIndex = rangeIndex; + this.sheetId = sheetId; + this.getRangeString = getRangeString; } get value() { - return this.getValue(); + const range = this.ranges[this.rangeIndex]; + return this.getRangeString(range, this.sheetId); } } @@ -36193,7 +36267,13 @@ } } exportForExcel(data) { - this.export(data); + if (data.sheets) { + for (let sheet of data.sheets) { + if (this.cfRules[sheet.id]) { + sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useFixedReference: true })); + } + } + } } // --------------------------------------------------------------------------- // Getters @@ -36258,11 +36338,11 @@ // --------------------------------------------------------------------------- // Private // --------------------------------------------------------------------------- - mapToConditionalFormat(sheetId, cf) { + mapToConditionalFormat(sheetId, cf, { useFixedReference } = { useFixedReference: false }) { return { ...cf, ranges: cf.ranges.map((range) => { - return this.getters.getRangeString(range, sheetId); + return this.getters.getRangeString(range, sheetId, { useFixedReference }); }), }; } @@ -38330,8 +38410,10 @@ * * @param range the range (received from getRangeFromXC or getRangeFromZone) * @param forSheetId the id of the sheet where the range string is supposed to be used. + * @param options + * @param options.useFixedReference if true, the range will be returned with fixed row and column */ - getRangeString(range, forSheetId) { + getRangeString(range, forSheetId, options = { useFixedReference: false }) { if (!range) { return INCORRECT_RANGE_STRING; } @@ -38358,7 +38440,7 @@ if (prefixSheet && !sheetName) { return INCORRECT_RANGE_STRING; } - let rangeString = this.getRangePartString(rangeImpl, 0); + let rangeString = this.getRangePartString(rangeImpl, 0, options); if (rangeImpl.parts && rangeImpl.parts.length === 2) { // this if converts A2:A2 into A2 except if any part of the original range had fixed row or column (with $) if (rangeImpl.zone.top !== rangeImpl.zone.bottom || @@ -38368,7 +38450,7 @@ rangeImpl.parts[1].rowFixed || rangeImpl.parts[1].colFixed) { rangeString += ":"; - rangeString += this.getRangePartString(rangeImpl, 1); + rangeString += this.getRangePartString(rangeImpl, 1, options); } } return `${prefixSheet ? sheetName + "!" : ""}${rangeString}`; @@ -38409,13 +38491,13 @@ /** * Get a Xc string that represent a part of a range */ - getRangePartString(range, part) { + getRangePartString(range, part, options = { useFixedReference: false }) { const colFixed = range.parts && range.parts[part]?.colFixed ? "$" : ""; const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right); const rowFixed = range.parts && range.parts[part]?.rowFixed ? "$" : ""; const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1); let str = ""; - if (range.isFullCol) { + if (range.isFullCol && !options.useFixedReference) { if (part === 0 && range.unboundedZone.hasHeader) { str = colFixed + col + rowFixed + row; } @@ -38423,7 +38505,7 @@ str = colFixed + col; } } - else if (range.isFullRow) { + else if (range.isFullRow && !options.useFixedReference) { if (part === 0 && range.unboundedZone.hasHeader) { str = colFixed + col + rowFixed + row; } @@ -41428,7 +41510,13 @@ const format = newFormat ? getItemId(newFormat, data.formats) : exportedCellData.format; - const content = !isExported ? newContent : exportedCellData.content; + let content; + if (formulaCell instanceof FormulaCellWithDependencies) { + content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true); + } + else { + content = !isExported ? newContent : exportedCellData.content; + } exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format }; } } @@ -50744,6 +50832,16 @@ } } + class FocusableElement { + focusableElement = undefined; + setFocusableElement(element) { + this.focusableElement = element; + } + focus() { + this.focusableElement?.focus(); + } + } + const RIPPLE_KEY_FRAMES = [ { transform: "scale(0)" }, { transform: "scale(0.8)", offset: 0.33 }, @@ -52900,6 +52998,7 @@ toggleSidePanel: this.toggleSidePanel.bind(this), clipboard: this.env.clipboard || instantiateClipboard(), startCellEdition: (content) => this.onGridComposerCellFocused(content), + focusableElement: new FocusableElement(), }); owl.useExternalListener(window, "resize", () => this.render(true)); owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this)); @@ -53006,16 +53105,17 @@ } this.composer.topBarFocus = "contentFocus"; this.composer.gridFocusMode = "inactive"; - this.setComposerContent({ selection } || {}); + this.setComposerContent({ selection }); } - onGridComposerContentFocused() { + onGridComposerContentFocused(selection) { if (this.model.getters.isReadonly()) { return; } this.composer.topBarFocus = "inactive"; this.composer.gridFocusMode = "contentFocus"; - this.setComposerContent({}); + this.setComposerContent({ selection }); } + // TODO: either both are defined or none of them. change those args to an object onGridComposerCellFocused(content, selection) { if (this.model.getters.isReadonly()) { return; @@ -54950,12 +55050,7 @@ attrs.push(["t", "str"]); cycle = escapeXml /*xml*/ `${cell.value}`; } - node = escapeXml /*xml*/ ` - - ${XlsxFormula} - - ${cycle} - `; + node = escapeXml /*xml*/ `${XlsxFormula}${cycle}`; return { attrs, node }; } function addContent(content, sharedStrings, forceString = false) { @@ -55774,11 +55869,10 @@ ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader)); } attributes.push(...additionalAttrs); - cellNodes.push(escapeXml /*xml*/ ` - - ${cellNode} - - `); + // prettier-ignore + cellNodes.push(escapeXml /*xml*/ ` + ${cellNode} +`); } } if (cellNodes.length || row.size !== DEFAULT_CELL_HEIGHT || row.isHidden) { @@ -56374,7 +56468,6 @@ ...config, mode: config.mode || "normal", custom: config.custom || {}, - defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00", external: this.setupExternalConfig(config.external || {}), transportService, client, @@ -56412,6 +56505,7 @@ custom: this.config.custom, uiActions: this.config, session: this.session, + defaultCurrencyFormat: this.config.defaultCurrencyFormat, }; } // --------------------------------------------------------------------------- @@ -56784,9 +56878,9 @@ exports.tokenize = tokenize; - __info__.version = "17.0.9"; - __info__.date = "2024-01-29T15:41:09.261Z"; - __info__.hash = "3c1209a38"; + __info__.version = "17.0.11"; + __info__.date = "2024-02-09T14:36:35.988Z"; + __info__.hash = "bb51d6315"; })(this.o_spreadsheet = this.o_spreadsheet || {}, owl); diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml index 4d6669049c9..51372752dff 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 @@ @@ -467,7 +467,7 @@ t-on-mousedown="onMousedown" t-on-click="onClick" t-on-keyup="onKeyup" - t-on-paste.stop="" + t-on-paste="onPaste" t-on-compositionstart="onCompositionStart" t-on-compositionend="onCompositionEnd" t-on-dblclick="onDblClick" @@ -565,14 +565,7 @@ t-esc="cellReference" />
- +
@@ -953,9 +946,10 @@
- - - - - - - + + t-on-contextmenu.stop.prevent="onContextMenu">