[IMP] spreadsheet: update o_spreadsheet to latest version
https://github.com/odoo/o-spreadsheet/commit/dbe36052 [FIX] find_and_replace: select first match automatically https://github.com/odoo/o-spreadsheet/commit/28476c9d [FIX] ClearFormat: clear format actually clears the format https://github.com/odoo/o-spreadsheet/commit/b4dbb747 [FIX] grid: handle selectionInput modes on click events https://github.com/odoo/o-spreadsheet/commit/179ff66d [IMP] functions: make ISTEXT/ISNUMBER/... compatible with Excel/GSheet https://github.com/odoo/o-spreadsheet/commit/ce32cd66 [FIX] scorecard: display percentage between 0 and 0 https://github.com/odoo/o-spreadsheet/commit/44266c5b [IMP] style: add text cell wrapping option https://github.com/odoo/o-spreadsheet/commit/9b08e99a [REF] renderer: extract font size computation https://github.com/odoo/o-spreadsheet/commit/ebff300d [IMP] clipboard: paste on merge consistency https://github.com/odoo/o-spreadsheet/commit/246de550 [IMP] evaluation: add error "unknown function" closes odoo/odoo#100740 Signed-off-by: Pierre Rousseau (pro) <pro@odoo.com>
This commit is contained in:
@@ -181,6 +181,7 @@
|
||||
CellErrorType["InvalidReference"] = "#REF";
|
||||
CellErrorType["BadExpression"] = "#BAD_EXPR";
|
||||
CellErrorType["CircularDependency"] = "#CYCLE";
|
||||
CellErrorType["UnknownFunction"] = "#NAME?";
|
||||
CellErrorType["GenericError"] = "#ERROR";
|
||||
})(CellErrorType || (CellErrorType = {}));
|
||||
var CellErrorLevel;
|
||||
@@ -215,6 +216,11 @@
|
||||
super(CellErrorType.NotAvailable, _lt("Data not available"), CellErrorLevel.silent);
|
||||
}
|
||||
}
|
||||
class UnknownFunctionError extends EvaluationError {
|
||||
constructor(fctName) {
|
||||
super(CellErrorType.UnknownFunction, _lt('Unknown function: "%s"', fctName));
|
||||
}
|
||||
}
|
||||
|
||||
const CANVAS_SHIFT = 0.5;
|
||||
// Colors
|
||||
@@ -661,28 +667,36 @@
|
||||
function clip(val, min, max) {
|
||||
return val < min ? min : val > max ? max : val;
|
||||
}
|
||||
/** Get the default height of the cell. The height depends on the font size */
|
||||
function getDefaultCellHeight(style) {
|
||||
/** Get the default height of the cell. The height depends on the font size and
|
||||
* the number of broken line text in the cell */
|
||||
function getDefaultCellHeight(style, numberOfLines) {
|
||||
if (!(style === null || style === void 0 ? void 0 : style.fontSize)) {
|
||||
return DEFAULT_CELL_HEIGHT;
|
||||
}
|
||||
return fontSizeInPixels(style) + 2 * PADDING_AUTORESIZE_VERTICAL;
|
||||
return ((numberOfLines || 1) * (computeTextFontSizeInPixels(style) + MIN_CELL_TEXT_MARGIN) -
|
||||
MIN_CELL_TEXT_MARGIN +
|
||||
2 * PADDING_AUTORESIZE_VERTICAL);
|
||||
}
|
||||
function fontSizeInPixels(style) {
|
||||
function computeTextWidth(context, text, style) {
|
||||
context.save();
|
||||
context.font = computeTextFont(style);
|
||||
const textWidth = context.measureText(text).width;
|
||||
context.restore();
|
||||
return textWidth;
|
||||
}
|
||||
function computeTextFont(style) {
|
||||
const italic = style.italic ? "italic " : "";
|
||||
const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
|
||||
const size = computeTextFontSizeInPixels(style);
|
||||
return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
|
||||
}
|
||||
function computeTextFontSizeInPixels(style) {
|
||||
const sizeInPt = style.fontSize || DEFAULT_FONT_SIZE;
|
||||
if (!fontSizeMap[sizeInPt]) {
|
||||
throw new Error("Size of the font is not supported");
|
||||
}
|
||||
return fontSizeMap[sizeInPt];
|
||||
}
|
||||
function computeTextWidth(context, text, style) {
|
||||
const italic = style.italic ? "italic " : "";
|
||||
const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
|
||||
const sizeInPt = style.fontSize || DEFAULT_FONT_SIZE;
|
||||
const size = fontSizeMap[sizeInPt];
|
||||
context.font = `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
|
||||
return context.measureText(text).width;
|
||||
}
|
||||
/**
|
||||
* Return the font size that makes the width of a text match the given line width.
|
||||
* Minimum font size is 1.
|
||||
@@ -713,10 +727,8 @@
|
||||
}
|
||||
return fontSize;
|
||||
}
|
||||
function computeIconWidth(context, style) {
|
||||
const sizeInPt = style.fontSize || DEFAULT_FONT_SIZE;
|
||||
const size = fontSizeMap[sizeInPt];
|
||||
return size + 2 * MIN_CF_ICON_MARGIN;
|
||||
function computeIconWidth(style) {
|
||||
return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
|
||||
}
|
||||
/**
|
||||
* Create a range from start (included) to end (excluded).
|
||||
@@ -4072,6 +4084,10 @@
|
||||
const result = env.model.dispatch("PASTE", { target, pasteOption });
|
||||
handlePasteResult(env, result);
|
||||
}
|
||||
function interactivePasteFromOS(env, target, text) {
|
||||
const result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", { target, text });
|
||||
handlePasteResult(env, result);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -4139,10 +4155,7 @@
|
||||
const osClipboard = await readOsClipboard(env);
|
||||
const target = env.model.getters.getSelectedZones();
|
||||
if (osClipboard && osClipboard !== spreadsheetClipboard) {
|
||||
env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
|
||||
target,
|
||||
text: osClipboard,
|
||||
});
|
||||
interactivePasteFromOS(env, target, osClipboard);
|
||||
}
|
||||
else {
|
||||
interactivePaste(env, target);
|
||||
@@ -5434,17 +5447,36 @@
|
||||
.addChild("format_font_size", ["format"], {
|
||||
name: _lt("Font size"),
|
||||
sequence: 60,
|
||||
})
|
||||
.addChild("format_wrapping", ["format"], {
|
||||
name: _lt("Wrapping"),
|
||||
sequence: 70,
|
||||
separator: true,
|
||||
})
|
||||
.addChild("format_wrapping_overflow", ["format", "format_wrapping"], {
|
||||
name: "Overflow",
|
||||
sequence: 10,
|
||||
action: (env) => setStyle(env, { wrapping: "overflow" }),
|
||||
})
|
||||
.addChild("format_wrapping_wrap", ["format", "format_wrapping"], {
|
||||
name: "Wrap",
|
||||
sequence: 20,
|
||||
action: (env) => setStyle(env, { wrapping: "wrap" }),
|
||||
})
|
||||
.addChild("format_wrapping_clip", ["format", "format_wrapping"], {
|
||||
name: "Clip",
|
||||
sequence: 30,
|
||||
action: (env) => setStyle(env, { wrapping: "clip" }),
|
||||
})
|
||||
.addChild("format_cf", ["format"], {
|
||||
name: _lt("Conditional formatting"),
|
||||
sequence: 70,
|
||||
sequence: 80,
|
||||
action: OPEN_CF_SIDEPANEL_ACTION,
|
||||
separator: true,
|
||||
})
|
||||
.addChild("format_clearFormat", ["format"], {
|
||||
name: _lt("Clear formatting"),
|
||||
sequence: 80,
|
||||
sequence: 90,
|
||||
action: FORMAT_CLEARFORMAT_ACTION,
|
||||
separator: true,
|
||||
});
|
||||
@@ -6055,7 +6087,7 @@
|
||||
}
|
||||
else {
|
||||
let diff = keyValue.value - baselineEvaluated.value;
|
||||
if (baselineMode === "percentage") {
|
||||
if (baselineMode === "percentage" && diff !== 0) {
|
||||
diff = (diff / baselineEvaluated.value) * 100;
|
||||
}
|
||||
if (baselineMode !== "percentage" && baselineEvaluated.format) {
|
||||
@@ -14052,10 +14084,15 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
const ISLOGICAL = {
|
||||
description: _lt("Whether a value is `true` or `false`."),
|
||||
args: args(`value (any) ${_lt("The value to be verified as a logical TRUE or FALSE.")}`),
|
||||
args: args(`value (any, lazy) ${_lt("The value to be verified as a logical TRUE or FALSE.")}`),
|
||||
returns: ["BOOLEAN"],
|
||||
compute: function (value) {
|
||||
return typeof value === "boolean";
|
||||
try {
|
||||
return typeof value() === "boolean";
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isExported: true,
|
||||
};
|
||||
@@ -14082,10 +14119,15 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
const ISNONTEXT = {
|
||||
description: _lt("Whether a value is non-textual."),
|
||||
args: args(`value (any) ${_lt("The value to be checked.")}`),
|
||||
args: args(`value (any, lazy) ${_lt("The value to be checked.")}`),
|
||||
returns: ["BOOLEAN"],
|
||||
compute: function (value) {
|
||||
return typeof value !== "string";
|
||||
try {
|
||||
return typeof value() !== "string";
|
||||
}
|
||||
catch (e) {
|
||||
return true;
|
||||
}
|
||||
},
|
||||
isExported: true,
|
||||
};
|
||||
@@ -14094,10 +14136,15 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
const ISNUMBER = {
|
||||
description: _lt("Whether a value is a number."),
|
||||
args: args(`value (any) ${_lt("The value to be verified as a number.")}`),
|
||||
args: args(`value (any, lazy) ${_lt("The value to be verified as a number.")}`),
|
||||
returns: ["BOOLEAN"],
|
||||
compute: function (value) {
|
||||
return typeof value === "number";
|
||||
try {
|
||||
return typeof value() === "number";
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isExported: true,
|
||||
};
|
||||
@@ -14106,10 +14153,15 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
const ISTEXT = {
|
||||
description: _lt("Whether a value is text."),
|
||||
args: args(`value (any) ${_lt("The value to be verified as text.")}`),
|
||||
args: args(`value (any, lazy) ${_lt("The value to be verified as text.")}`),
|
||||
returns: ["BOOLEAN"],
|
||||
compute: function (value) {
|
||||
return typeof value === "string";
|
||||
try {
|
||||
return typeof value() === "string";
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
isExported: true,
|
||||
};
|
||||
@@ -15359,6 +15411,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
const functionRegex = /[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*/;
|
||||
const UNARY_OPERATORS_PREFIX = ["-", "+"];
|
||||
const UNARY_OPERATORS_POSTFIX = ["%"];
|
||||
const ASSOCIATIVE_OPERATORS = ["*", "+", "&"];
|
||||
@@ -15396,7 +15449,7 @@
|
||||
throw new Error(_lt("Unknown token: %s", token.value));
|
||||
}
|
||||
function parsePrefix(current, tokens) {
|
||||
var _a, _b, _c;
|
||||
var _a, _b, _c, _d;
|
||||
switch (current.type) {
|
||||
case "DEBUGGER":
|
||||
const next = parseExpression(tokens, 1000);
|
||||
@@ -15461,6 +15514,9 @@
|
||||
}
|
||||
else {
|
||||
if (current.value) {
|
||||
if (functionRegex.test(current.value) && ((_d = tokens[0]) === null || _d === void 0 ? void 0 : _d.type) === "LEFT_PAREN") {
|
||||
throw new UnknownFunctionError(current.value);
|
||||
}
|
||||
throw new Error(_lt("Invalid formula"));
|
||||
}
|
||||
return { type: "STRING", value: current.value };
|
||||
@@ -19512,6 +19568,9 @@
|
||||
// not main button, probably a context menu
|
||||
return;
|
||||
}
|
||||
if (ev.ctrlKey) {
|
||||
this.env.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
|
||||
}
|
||||
const [col, row] = this.getCartesianCoordinates(ev);
|
||||
if (col < 0 || row < 0) {
|
||||
return;
|
||||
@@ -19588,7 +19647,7 @@
|
||||
};
|
||||
const onMouseUp = (ev) => {
|
||||
clearTimeout(timeOutId);
|
||||
this.env.model.dispatch(ev.ctrlKey ? "PREPARE_SELECTION_INPUT_EXPANSION" : "STOP_SELECTION_INPUT");
|
||||
this.env.model.dispatch("STOP_SELECTION_INPUT");
|
||||
this.gridOverlay.el.removeEventListener("mousemove", onMouseMove);
|
||||
if (this.env.model.getters.isPaintingFormat()) {
|
||||
this.env.model.dispatch("PASTE", {
|
||||
@@ -19761,10 +19820,7 @@
|
||||
interactivePaste(this.env, target);
|
||||
}
|
||||
else {
|
||||
this.env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
|
||||
target,
|
||||
text: content,
|
||||
});
|
||||
interactivePasteFromOS(this.env, target, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20021,7 +20077,7 @@
|
||||
* Cell containing a formula which could not be compiled
|
||||
* or a content which could not be parsed.
|
||||
*/
|
||||
class BadExpressionCell extends AbstractCell {
|
||||
class ErrorCell extends AbstractCell {
|
||||
/**
|
||||
* @param id
|
||||
* @param content Invalid formula string
|
||||
@@ -20030,7 +20086,7 @@
|
||||
*/
|
||||
constructor(id, content, error, properties) {
|
||||
super(id, lazy({
|
||||
value: CellErrorType.BadExpression,
|
||||
value: error.errorType,
|
||||
type: CellValueType.error,
|
||||
error,
|
||||
}), properties);
|
||||
@@ -20129,7 +20185,9 @@
|
||||
return builder.createCell(id, content, properties, sheetId, getters);
|
||||
}
|
||||
catch (error) {
|
||||
return new BadExpressionCell(id, content, new BadExpressionError(error.message || DEFAULT_ERROR_MESSAGE), properties);
|
||||
return new ErrorCell(id, content, error instanceof EvaluationError
|
||||
? error
|
||||
: new BadExpressionError(error.message || DEFAULT_ERROR_MESSAGE), properties);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -24276,7 +24334,7 @@
|
||||
}
|
||||
break;
|
||||
case "CLEAR_FORMATTING":
|
||||
this.clearStyles(cmd.sheetId, cmd.target);
|
||||
this.clearFormatting(cmd.sheetId, cmd.target);
|
||||
break;
|
||||
case "ADD_COLUMNS_ROWS":
|
||||
if (cmd.dimension === "COL") {
|
||||
@@ -24319,9 +24377,9 @@
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Clear the styles of zones
|
||||
* Clear the styles and format of zones
|
||||
*/
|
||||
clearStyles(sheetId, zones) {
|
||||
clearFormatting(sheetId, zones) {
|
||||
for (let zone of zones) {
|
||||
for (let col = zone.left; col <= zone.right; col++) {
|
||||
for (let row = zone.top; row <= zone.bottom; row++) {
|
||||
@@ -24331,6 +24389,7 @@
|
||||
col,
|
||||
row,
|
||||
style: null,
|
||||
format: "",
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -25414,6 +25473,7 @@
|
||||
return DEFAULT_CELL_HEIGHT;
|
||||
}
|
||||
const cell = this.getters.getCell(sheetId, col, row);
|
||||
// TO DO: take multiline cells into account to compute the cell height
|
||||
return getDefaultCellHeight(cell === null || cell === void 0 ? void 0 : cell.style);
|
||||
}
|
||||
/**
|
||||
@@ -25783,6 +25843,22 @@
|
||||
const mergeId = sheetMap ? col in sheetMap && ((_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row]) : undefined;
|
||||
return mergeId ? this.getMergeById(sheetId, mergeId) : undefined;
|
||||
}
|
||||
getMergesInZone(sheetId, zone) {
|
||||
var _a;
|
||||
const sheetMap = this.mergeCellMap[sheetId];
|
||||
if (!sheetMap)
|
||||
return [];
|
||||
const mergeIds = new Set();
|
||||
for (const { col, row } of positions(zone)) {
|
||||
const mergeId = (_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row];
|
||||
if (mergeId) {
|
||||
mergeIds.add(mergeId);
|
||||
}
|
||||
}
|
||||
return Array.from(mergeIds)
|
||||
.map((mergeId) => this.getMergeById(sheetId, mergeId))
|
||||
.filter(isDefined$1);
|
||||
}
|
||||
/**
|
||||
* Return true if the zone intersects an existing merge:
|
||||
* if they have at least a common cell
|
||||
@@ -26094,6 +26170,7 @@
|
||||
"doesRowsHaveCommonMerges",
|
||||
"getMerges",
|
||||
"getMerge",
|
||||
"getMergesInZone",
|
||||
"isSingleCellOrMerge",
|
||||
];
|
||||
function exportMerges(merges) {
|
||||
@@ -28154,11 +28231,19 @@
|
||||
.split("\n")
|
||||
.map((vals) => vals.split("\t"));
|
||||
}
|
||||
isPasteAllowed(target, clipboardOption) {
|
||||
const sheetId = this.getters.getActiveSheetId();
|
||||
const pasteZone = this.getPasteZone(target);
|
||||
if (this.getters.doesIntersectMerge(sheetId, pasteZone)) {
|
||||
return 2 /* WillRemoveExistingMerge */;
|
||||
}
|
||||
return 0 /* Success */;
|
||||
}
|
||||
paste(target) {
|
||||
const values = this.values;
|
||||
const { left: activeCol, top: activeRow } = target[0];
|
||||
const width = Math.max.apply(Math, values.map((a) => a.length));
|
||||
const height = values.length;
|
||||
const pasteZone = this.getPasteZone(target);
|
||||
const { left: activeCol, top: activeRow } = pasteZone;
|
||||
const { width, height } = zoneToDimension(pasteZone);
|
||||
const sheetId = this.getters.getActiveSheetId();
|
||||
this.addMissingDimensions(width, height, activeCol, activeRow);
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
@@ -28182,6 +28267,17 @@
|
||||
getClipboardContent() {
|
||||
return this.values.map((values) => values.join("\t")).join("\n");
|
||||
}
|
||||
getPasteZone(target) {
|
||||
const height = this.values.length;
|
||||
const width = Math.max(...this.values.map((a) => a.length));
|
||||
const { left: activeCol, top: activeRow } = target[0];
|
||||
return {
|
||||
top: activeRow,
|
||||
left: activeCol,
|
||||
bottom: activeRow + height - 1,
|
||||
right: activeCol + width - 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28211,6 +28307,10 @@
|
||||
}
|
||||
const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
|
||||
return this.state.isPasteAllowed(cmd.target, { pasteOption });
|
||||
case "PASTE_FROM_OS_CLIPBOARD": {
|
||||
const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
|
||||
return state.isPasteAllowed(cmd.target);
|
||||
}
|
||||
case "INSERT_CELL": {
|
||||
const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
|
||||
const state = this.getClipboardStateForCopyCells(cut, "CUT");
|
||||
@@ -29287,7 +29387,7 @@
|
||||
updateSearch(toSearch, searchOptions) {
|
||||
this.searchOptions = searchOptions;
|
||||
if (toSearch !== this.toSearch) {
|
||||
this.selectedMatchIndex = 0;
|
||||
this.selectedMatchIndex = null;
|
||||
}
|
||||
this.toSearch = toSearch;
|
||||
this.updateRegex();
|
||||
@@ -29366,7 +29466,7 @@
|
||||
}
|
||||
//modulo of negative value to be able to cycle in both directions with previous and next
|
||||
nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;
|
||||
if (this.selectedMatchIndex !== nextIndex) {
|
||||
if (this.selectedMatchIndex === null || this.selectedMatchIndex !== nextIndex) {
|
||||
this.selectedMatchIndex = nextIndex;
|
||||
this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);
|
||||
}
|
||||
@@ -29886,17 +29986,13 @@
|
||||
}
|
||||
drawTexts(renderingContext) {
|
||||
const { ctx, thinLineWidth } = renderingContext;
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.textBaseline = "top";
|
||||
let currentFont;
|
||||
for (let box of this.boxes) {
|
||||
if (box.content) {
|
||||
const style = box.style || {};
|
||||
const align = box.content.align || "left";
|
||||
const italic = style.italic ? "italic " : "";
|
||||
const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
|
||||
const sizeInPt = style.fontSize || DEFAULT_FONT_SIZE;
|
||||
const size = fontSizeMap[sizeInPt];
|
||||
const font = `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
|
||||
const font = computeTextFont(style);
|
||||
if (font !== currentFont) {
|
||||
currentFont = font;
|
||||
ctx.font = font;
|
||||
@@ -29921,21 +30017,29 @@
|
||||
ctx.rect(x, y, width, height);
|
||||
ctx.clip();
|
||||
}
|
||||
ctx.fillText(box.content.text, Math.round(x), Math.round(y));
|
||||
if (style.strikethrough || style.underline) {
|
||||
if (align === "right") {
|
||||
x = x - box.content.width;
|
||||
}
|
||||
else if (align === "center") {
|
||||
x = x - box.content.width / 2;
|
||||
}
|
||||
if (style.strikethrough) {
|
||||
ctx.fillRect(x, y, box.content.width, 2.6 * thinLineWidth);
|
||||
}
|
||||
if (style.underline) {
|
||||
y = box.y + box.height / 2 + 1 + size / 2;
|
||||
ctx.fillRect(x, y, box.content.width, 1.3 * thinLineWidth);
|
||||
const brokenLineNumber = box.content.multiLineText.length;
|
||||
const size = computeTextFontSizeInPixels(style);
|
||||
const contentHeight = brokenLineNumber * (size + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
|
||||
let brokenLineY = y - contentHeight / 2;
|
||||
for (let brokenLine of box.content.multiLineText) {
|
||||
ctx.fillText(brokenLine, Math.round(x), Math.round(brokenLineY));
|
||||
if (style.strikethrough || style.underline) {
|
||||
const lineWidth = computeTextWidth(ctx, brokenLine, style);
|
||||
let _x = x;
|
||||
if (align === "right") {
|
||||
_x -= lineWidth;
|
||||
}
|
||||
else if (align === "center") {
|
||||
_x -= lineWidth / 2;
|
||||
}
|
||||
if (style.strikethrough) {
|
||||
ctx.fillRect(_x, brokenLineY + size / 2, lineWidth, 2.6 * thinLineWidth);
|
||||
}
|
||||
if (style.underline) {
|
||||
ctx.fillRect(_x, brokenLineY + size + 1, lineWidth, 1.3 * thinLineWidth);
|
||||
}
|
||||
}
|
||||
brokenLineY += MIN_CELL_TEXT_MARGIN + size;
|
||||
}
|
||||
if (box.clipRect) {
|
||||
ctx.restore();
|
||||
@@ -30091,8 +30195,7 @@
|
||||
}
|
||||
/** Icon CF */
|
||||
const cfIcon = this.getters.getConditionalIcon(col, row);
|
||||
const fontSize = box.style.fontSize || DEFAULT_FONT_SIZE;
|
||||
const fontSizePX = fontSizeMap[fontSize];
|
||||
const fontSizePX = computeTextFontSizeInPixels(box.style);
|
||||
const iconBoxWidth = cfIcon ? 2 * MIN_CF_ICON_MARGIN + fontSizePX : 0;
|
||||
if (cfIcon) {
|
||||
box.image = {
|
||||
@@ -30105,11 +30208,15 @@
|
||||
/** Content */
|
||||
const text = this.getters.getCellText(cell, showFormula);
|
||||
const textWidth = this.getters.getTextWidth(cell);
|
||||
const wrapping = this.getters.getCellStyle(cell).wrapping || "overflow";
|
||||
const multiLineText = wrapping === "wrap"
|
||||
? this.getters.getCellMultiLineText(cell, width - 2 * MIN_CELL_TEXT_MARGIN)
|
||||
: [text];
|
||||
const contentWidth = iconBoxWidth + textWidth;
|
||||
const align = this.computeCellAlignment(cell, contentWidth > width);
|
||||
box.content = {
|
||||
text,
|
||||
width: textWidth,
|
||||
multiLineText,
|
||||
width: wrapping === "overflow" ? textWidth : width,
|
||||
align,
|
||||
};
|
||||
/** Error */
|
||||
@@ -30118,7 +30225,7 @@
|
||||
box.error = cell.evaluated.error.message;
|
||||
}
|
||||
/** ClipRect */
|
||||
const isOverflowing = contentWidth > width || fontSizeMap[fontSize] > height;
|
||||
const isOverflowing = contentWidth > width || fontSizePX > height;
|
||||
if (cfIcon) {
|
||||
box.clipRect = {
|
||||
x: box.x + iconBoxWidth,
|
||||
@@ -30127,7 +30234,7 @@
|
||||
height,
|
||||
};
|
||||
}
|
||||
else if (isOverflowing) {
|
||||
else if (isOverflowing && wrapping === "overflow") {
|
||||
let nextColIndex, previousColIndex;
|
||||
const isCellInMerge = this.getters.isInMerge(sheetId, col, row);
|
||||
if (isCellInMerge) {
|
||||
@@ -30173,6 +30280,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (wrapping === "clip" || wrapping === "wrap") {
|
||||
box.clipRect = {
|
||||
x: box.x,
|
||||
y: box.y,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}
|
||||
return box;
|
||||
}
|
||||
getGridBoxes(renderingContext) {
|
||||
@@ -32053,7 +32168,7 @@
|
||||
this.dispatch("RESIZE_COLUMNS_ROWS", {
|
||||
elements: [col],
|
||||
dimension: "COL",
|
||||
size: size + 2 * PADDING_AUTORESIZE_HORIZONTAL,
|
||||
size,
|
||||
sheetId: cmd.sheetId,
|
||||
});
|
||||
}
|
||||
@@ -32075,13 +32190,19 @@
|
||||
// Getters
|
||||
// ---------------------------------------------------------------------------
|
||||
getCellWidth(cell) {
|
||||
let width = this.getTextWidth(cell);
|
||||
let contentWidth = this.getTextWidth(cell);
|
||||
const cellPosition = this.getters.getCellPosition(cell.id);
|
||||
const icon = this.getters.getConditionalIcon(cellPosition.col, cellPosition.row);
|
||||
if (icon) {
|
||||
width += computeIconWidth(this.ctx, this.getters.getCellStyle(cell));
|
||||
contentWidth += computeIconWidth(this.getters.getCellStyle(cell));
|
||||
}
|
||||
return width;
|
||||
contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
|
||||
if (this.getters.getCellStyle(cell).wrapping === "wrap") {
|
||||
const zone = positionToZone(this.getters.getCellPosition(cell.id));
|
||||
const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), zone.left);
|
||||
return Math.min(colWidth, contentWidth);
|
||||
}
|
||||
return contentWidth;
|
||||
}
|
||||
getTextWidth(cell) {
|
||||
const text = this.getters.getCellText(cell, this.getters.shouldShowFormulas());
|
||||
@@ -32096,6 +32217,54 @@
|
||||
return cell.formattedValue;
|
||||
}
|
||||
}
|
||||
getCellMultiLineText(cell, width) {
|
||||
const style = this.getters.getCellStyle(cell);
|
||||
const text = this.getters.getCellText(cell);
|
||||
const words = text.split(" ");
|
||||
const brokenText = [];
|
||||
let textLine = "";
|
||||
let availableWidth = width;
|
||||
for (let word of words) {
|
||||
const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);
|
||||
const lastPart = splitWord.pop();
|
||||
const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);
|
||||
// At this step: "splitWord" is an array composed of parts of word whose
|
||||
// length is at most equal to "width".
|
||||
// Last part contains the end of the word.
|
||||
// Note that: When word length is less than width, then lastPart is equal
|
||||
// to word and splitWord is empty
|
||||
if (splitWord.length) {
|
||||
if (textLine !== "") {
|
||||
brokenText.push(textLine);
|
||||
textLine = "";
|
||||
availableWidth = width;
|
||||
}
|
||||
splitWord.forEach((wordPart) => {
|
||||
brokenText.push(wordPart);
|
||||
});
|
||||
textLine = lastPart;
|
||||
availableWidth = width - lastPartWidth;
|
||||
}
|
||||
else {
|
||||
// here "lastPart" is equal to "word" and the "word" size is smaller than "width"
|
||||
const _word = textLine === "" ? lastPart : " " + lastPart;
|
||||
const wordWidth = computeTextWidth(this.ctx, _word, style);
|
||||
if (wordWidth <= availableWidth) {
|
||||
textLine += _word;
|
||||
availableWidth -= wordWidth;
|
||||
}
|
||||
else {
|
||||
brokenText.push(textLine);
|
||||
textLine = lastPart;
|
||||
availableWidth = width - lastPartWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (textLine !== "") {
|
||||
brokenText.push(textLine);
|
||||
}
|
||||
return brokenText;
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Grid manipulation
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -32104,8 +32273,28 @@
|
||||
const sizes = cells.map((cell) => this.getCellWidth(cell));
|
||||
return Math.max(0, ...sizes);
|
||||
}
|
||||
splitWordToSpecificWidth(ctx, word, width, style) {
|
||||
const wordWidth = computeTextWidth(ctx, word, style);
|
||||
if (wordWidth <= width) {
|
||||
return [word];
|
||||
}
|
||||
const splitWord = [];
|
||||
let wordPart = "";
|
||||
for (let l of word) {
|
||||
const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
|
||||
if (wordPartWidth > width) {
|
||||
splitWord.push(wordPart);
|
||||
wordPart = l;
|
||||
}
|
||||
else {
|
||||
wordPart += l;
|
||||
}
|
||||
}
|
||||
splitWord.push(wordPart);
|
||||
return splitWord;
|
||||
}
|
||||
}
|
||||
SheetUIPlugin.getters = ["getCellWidth", "getTextWidth", "getCellText"];
|
||||
SheetUIPlugin.getters = ["getCellWidth", "getTextWidth", "getCellText", "getCellMultiLineText"];
|
||||
|
||||
/**
|
||||
* Viewport plugin.
|
||||
@@ -33517,7 +33706,6 @@
|
||||
clipboard: navigator.clipboard,
|
||||
});
|
||||
owl.useExternalListener(window, "resize", () => this.render(true));
|
||||
owl.useExternalListener(document.body, "keyup", this.onKeyup.bind(this));
|
||||
owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
|
||||
owl.onMounted(() => this.bindModelEvents());
|
||||
owl.onWillUnmount(() => this.unbindModelEvents());
|
||||
@@ -33575,15 +33763,7 @@
|
||||
var _a, _b;
|
||||
(_b = (_a = this.props).onContentSaved) === null || _b === void 0 ? void 0 : _b.call(_a, this.model.exportData());
|
||||
}
|
||||
onKeyup(ev) {
|
||||
if (ev.key === "Control") {
|
||||
this.model.dispatch("STOP_SELECTION_INPUT");
|
||||
}
|
||||
}
|
||||
onKeydown(ev) {
|
||||
if (ev.key === "Control" && !ev.repeat) {
|
||||
this.model.dispatch("PREPARE_SELECTION_INPUT_EXPANSION");
|
||||
}
|
||||
let keyDownString = "";
|
||||
if (ev.ctrlKey || ev.metaKey) {
|
||||
keyDownString += "CTRL+";
|
||||
@@ -37580,8 +37760,8 @@
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
exports.__info__.version = '2.0.0';
|
||||
exports.__info__.date = '2022-09-13T07:37:09.243Z';
|
||||
exports.__info__.hash = 'fd4cd7c';
|
||||
exports.__info__.date = '2022-09-21T15:12:22.485Z';
|
||||
exports.__info__.hash = 'dbe3605';
|
||||
|
||||
})(this.o_spreadsheet = this.o_spreadsheet || {}, owl);
|
||||
//# sourceMappingURL=o_spreadsheet.js.map
|
||||
|
||||
Reference in New Issue
Block a user