diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
index cdf30f9e057..553943ef435 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
@@ -109,8 +109,8 @@
const HEADER_BORDER_COLOR = "#C0C0C0";
const CELL_BORDER_COLOR = "#E2E3E3";
const BACKGROUND_CHART_COLOR = "#FFFFFF";
- const MENU_ITEM_DISABLED_COLOR = "#CACACA";
const BG_HOVER_COLOR = "#EBEBEB";
+ const DISABLED_TEXT_COLOR = "#CACACA";
const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
const LINK_COLOR = "#01666b";
const FILTERS_COLOR = "#188038";
@@ -236,6 +236,7 @@
const HEADER_FONT_SIZE = 11;
const DEFAULT_FONT = "'Roboto', arial";
const DEFAULT_VERTICAL_ALIGN = "bottom";
+ const DEFAULT_WRAPPING_MODE = "overflow";
// Borders
const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
@@ -393,82 +394,6 @@
function clip(val, min, max) {
return val < min ? min : val > max ? max : val;
}
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
- return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
- }
- /**
- * Get the default height of the cell given its style.
- */
- function getDefaultCellHeight(cell) {
- if (!cell || !cell.content) {
- return DEFAULT_CELL_HEIGHT;
- }
- const fontSize = computeTextFontSizeInPixels(cell.style);
- // the number of lines should be computed from the formula result, but it's not evaluated at this point
- const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
- }
- function computeTextWidth(context, text, style) {
- const font = computeTextFont(style);
- if (!textWidthCache[font]) {
- textWidthCache[font] = {};
- }
- if (textWidthCache[font][text] === undefined) {
- context.save();
- context.font = font;
- const textWidth = context.measureText(text).width;
- context.restore();
- textWidthCache[font][text] = textWidth;
- }
- return textWidthCache[font][text];
- }
- const textWidthCache = {};
- function fontSizeInPixels(fontSize) {
- return Math.round((fontSize * 96) / 72);
- }
- 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;
- return fontSizeInPixels(sizeInPt);
- }
- /**
- * Return the font size that makes the width of a text match the given line width.
- * Minimum font size is 1.
- *
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
- */
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
- let minFontSize = 1;
- if (getTextWidth(minFontSize) > lineWidth)
- return minFontSize;
- if (getTextWidth(maxFontSize) < lineWidth)
- return maxFontSize;
- // Dichotomic search
- let fontSize = (minFontSize + maxFontSize) / 2;
- let currentTextWidth = getTextWidth(fontSize);
- // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
- let iterations = 0;
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
- if (currentTextWidth >= lineWidth) {
- maxFontSize = (minFontSize + maxFontSize) / 2;
- }
- else {
- minFontSize = (minFontSize + maxFontSize) / 2;
- }
- fontSize = (minFontSize + maxFontSize) / 2;
- currentTextWidth = getTextWidth(fontSize);
- iterations++;
- }
- return fontSize;
- }
- function computeIconWidth(style) {
- return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
- }
/**
* Create a range from start (included) to end (excluded).
* range(10, 13) => [10, 11, 12]
@@ -723,10 +648,6 @@
Object.keys(cleanObject).forEach((key) => !cleanObject[key] && delete cleanObject[key]);
return cleanObject;
}
- /** Transform a string to lower case. If the string is undefined, return an empty string */
- function toLowerCase(str) {
- return str ? str.toLowerCase() : "";
- }
function transpose2dArray(matrix, callback = (val) => val) {
if (!matrix.length)
return [];
@@ -816,6 +737,14 @@
},
}[funcName];
}
+ function removeIndexesFromArray(array, indexes) {
+ return array.filter((_, index) => !indexes.includes(index));
+ }
+ function insertItemsAtIndex(array, items, index) {
+ const newArray = [...array];
+ newArray.splice(index, 0, ...items);
+ return newArray;
+ }
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
@@ -958,13 +887,13 @@
let g = rgba.g.toString(16);
let b = rgba.b.toString(16);
let a = Math.round(rgba.a * 255).toString(16);
- if (r.length == 1)
+ if (r.length === 1)
r = "0" + r;
- if (g.length == 1)
+ if (g.length === 1)
g = "0" + g;
- if (b.length == 1)
+ if (b.length === 1)
b = "0" + b;
- if (a.length == 1)
+ if (a.length === 1)
a = "0" + a;
if (a === "ff")
a = "";
@@ -1067,13 +996,13 @@
let l = 0;
// Calculate hue
// No difference
- if (delta == 0)
+ if (delta === 0)
h = 0;
// Red is max
- else if (cMax == r)
+ else if (cMax === r)
h = ((g - b) / delta) % 6;
// Green is max
- else if (cMax == g)
+ else if (cMax === g)
h = (b - r) / delta + 2;
// Blue is max
else
@@ -1084,7 +1013,7 @@
h += 360;
l = (cMax + cMin) / 2;
// Calculate saturation
- s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
// Multiply l and s by 100
s = +(s * 100).toFixed(1);
l = +(l * 100).toFixed(1);
@@ -1256,7 +1185,7 @@
const mdyDateRegexp = /^\d{1,2}(\/|-|\s)\d{1,2}((\/|-|\s)\d{1,4})?$/;
const ymdDateRegexp = /^\d{3,4}(\/|-|\s)\d{1,2}(\/|-|\s)\d{1,2}$/;
const dateSeparatorsRegex = /\/|-|\s/;
- const dateRegexp = /^(\d{1,4})[\/-\s](\d{1,2})([\/-\s](\d{1,4}))?$/;
+ const dateRegexp = /^(\d{1,4})[\/-\s](\d{1,4})([\/-\s](\d{1,4}))?$/;
const timeRegexp = /((\d+(:\d+)?(:\d+)?\s*(AM|PM))|(\d+:\d+(:\d+)?))$/;
function isDateTime(str, locale) {
return parseDateTime(str, locale) !== null;
@@ -1317,6 +1246,10 @@
}
const localeDateType = getLocaleDateFormatType(locale);
if (!part3) {
+ if (part2.length > 2) {
+ // e.g. 11/2023
+ return { month: part1, year: part2, day: undefined, dateString, type: localeDateType };
+ }
if (localeDateType === "dmy") {
return { day: part1, month: part2, year: part3, dateString, type: "dmy" };
}
@@ -1521,7 +1454,7 @@
}
function isLeapYear(year) {
const _year = Math.trunc(year);
- return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 == 0;
+ return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 === 0;
}
function getYearFrac(startDate, endDate, _dayCountConvention) {
if (startDate === endDate) {
@@ -2182,6 +2115,39 @@
}
return undefined;
}
+ function createCurrencyFormat(currency) {
+ const decimalPlaces = currency.decimalPlaces ?? 2;
+ const position = currency.position ?? "before";
+ const code = currency.code ?? "";
+ const symbol = currency.symbol ?? "";
+ const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
+ const numberFormat = "#,##0" + decimalRepresentation;
+ let textExpression = `${code} ${symbol}`.trim();
+ if (position === "after" && code) {
+ textExpression = " " + textExpression;
+ }
+ return insertTextInFormat(textExpression, position, numberFormat);
+ }
+ function insertTextInFormat(text, position, format) {
+ const textExpression = `[$${text}]`;
+ return position === "before" ? textExpression + format : format + textExpression;
+ }
+ function roundFormat(format) {
+ const internalFormat = parseFormat(format);
+ const roundedFormat = internalFormat.map((formatPart) => {
+ if (formatPart.type === "NUMBER") {
+ return {
+ type: formatPart.type,
+ format: {
+ ...formatPart.format,
+ decimalPart: undefined,
+ },
+ };
+ }
+ return formatPart;
+ });
+ return convertInternalFormatToFormat(roundedFormat);
+ }
function createLargeNumberFormat(format, magnitude, postFix, locale) {
const internalFormat = parseFormat(format || "#,##0");
const largeNumberFormat = internalFormat
@@ -3275,6 +3241,169 @@
return rows;
}
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
+ return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
+ }
+ /**
+ * Get the default height of the cell given its style.
+ */
+ function getDefaultCellHeight(ctx, cell, colSize) {
+ if (!cell || !cell.content)
+ return DEFAULT_CELL_HEIGHT;
+ const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
+ const numberOfLines = cell.isFormula
+ ? 1
+ : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
+ const fontSize = computeTextFontSizeInPixels(cell.style);
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
+ }
+ const textWidthCache = {};
+ function computeTextWidth(context, text, style) {
+ const font = computeTextFont(style);
+ if (!textWidthCache[font]) {
+ textWidthCache[font] = {};
+ }
+ if (textWidthCache[font][text] === undefined) {
+ context.save();
+ context.font = font;
+ const textWidth = context.measureText(text).width;
+ context.restore();
+ textWidthCache[font][text] = textWidth;
+ }
+ return textWidthCache[font][text];
+ }
+ function fontSizeInPixels(fontSize) {
+ return Math.round((fontSize * 96) / 72);
+ }
+ 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;
+ return fontSizeInPixels(sizeInPt);
+ }
+ function 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;
+ }
+ /**
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
+ */
+ function splitTextToWidth(ctx, text, style, width) {
+ if (!style)
+ style = {};
+ const brokenText = [];
+ // Checking if text contains NEWLINE before split makes it very slightly slower if text contains it,
+ // but 5-10x faster if it doesn't
+ const lines = text.includes(NEWLINE) ? text.split(NEWLINE) : [text];
+ for (const line of lines) {
+ const words = line.includes(" ") ? line.split(" ") : [line];
+ if (!width) {
+ brokenText.push(line);
+ continue;
+ }
+ let textLine = "";
+ let availableWidth = width;
+ for (let word of words) {
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
+ const lastPart = splitWord.pop();
+ const lastPartWidth = computeTextWidth(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(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;
+ }
+ /**
+ * Return the font size that makes the width of a text match the given line width.
+ * Minimum font size is 1.
+ *
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
+ */
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
+ let minFontSize = 1;
+ if (getTextWidth(minFontSize) > lineWidth)
+ return minFontSize;
+ if (getTextWidth(maxFontSize) < lineWidth)
+ return maxFontSize;
+ // Dichotomic search
+ let fontSize = (minFontSize + maxFontSize) / 2;
+ let currentTextWidth = getTextWidth(fontSize);
+ // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
+ let iterations = 0;
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
+ if (currentTextWidth >= lineWidth) {
+ maxFontSize = (minFontSize + maxFontSize) / 2;
+ }
+ else {
+ minFontSize = (minFontSize + maxFontSize) / 2;
+ }
+ fontSize = (minFontSize + maxFontSize) / 2;
+ currentTextWidth = getTextWidth(fontSize);
+ iterations++;
+ }
+ return fontSize;
+ }
+ function computeIconWidth(style) {
+ return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
+ }
+ /** Transform a string to lower case. If the string is undefined, return an empty string */
+ function toLowerCase(str) {
+ return str ? str.toLowerCase() : "";
+ }
+
/*
* https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
* */
@@ -3297,7 +3426,7 @@
else {
// mainly for jest and other browsers that do not have the crypto functionality
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
- var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
+ var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
@@ -3927,7 +4056,6 @@
"SET_VIEWPORT_OFFSET",
"SELECT_SEARCH_NEXT_MATCH",
"SELECT_SEARCH_PREVIOUS_MATCH",
- "REFRESH_SEARCH",
"UPDATE_SEARCH",
"CLEAR_SEARCH",
"EVALUATE_CELLS",
@@ -4125,6 +4253,7 @@
CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 92] = "NoSplitSeparatorInSelection";
CommandResult[CommandResult["NoActiveSheet"] = 93] = "NoActiveSheet";
CommandResult[CommandResult["InvalidLocale"] = 94] = "InvalidLocale";
+ CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 95] = "AlreadyInPaintingFormatMode";
})(exports.CommandResult || (exports.CommandResult = {}));
const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5865,7 +5994,7 @@
}
}
&.disabled {
- color: ${MENU_ITEM_DISABLED_COLOR};
+ color: ${DISABLED_TEXT_COLOR};
cursor: not-allowed;
}
}
@@ -5940,8 +6069,8 @@
return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
}
getIconName(menu) {
- if (menu.icon) {
- return menu.icon;
+ if (menu.icon(this.env)) {
+ return menu.icon(this.env);
}
if (menu.isActive?.(this.env)) {
return "o-spreadsheet-Icon.CHECK";
@@ -6375,7 +6504,7 @@
for (let pivot = 0; pivot < dim; pivot++) {
let diagonalElement = C[pivot][pivot];
// if we have a 0 on the diagonal we'll need to swap with a lower row
- if (diagonalElement == 0) {
+ if (diagonalElement === 0) {
//look through every row below the i'th row
for (let row = pivot + 1; row < dim; row++) {
//if the ii'th row has a non-0 in the i'th col, swap it with that row
@@ -6402,7 +6531,7 @@
// the other rows so that there will be 0's in this column in the
// rows above and below this one
for (let row = 0; row < dim; row++) {
- if (row == pivot) {
+ if (row === pivot) {
continue;
}
// We want to change this element to 0
@@ -12467,7 +12596,7 @@
return false;
}
catch (e) {
- return e?.errorType == CellErrorType.NotAvailable;
+ return e?.errorType === CellErrorType.NotAvailable;
}
},
isExported: true,
@@ -15183,6 +15312,8 @@
function createAction(item) {
const name = item.name;
const children = item.children;
+ const description = item.description;
+ const icon = item.icon;
return {
id: item.id || uuidGenerator$2.uuidv4(),
name: typeof name === "function" ? name : () => name,
@@ -15200,8 +15331,8 @@
: () => [],
isReadonlyAllowed: item.isReadonlyAllowed || false,
separator: item.separator || false,
- icon: item.icon || "",
- description: item.description || "",
+ icon: typeof icon === "function" ? icon : () => icon || "",
+ description: typeof description === "function" ? description : () => description || "",
textColor: item.textColor,
sequence: item.sequence || 0,
};
@@ -16667,7 +16798,7 @@
function convertDateFormatForMoment(format) {
format = format.replace(/y/g, "Y");
format = format.replace(/d/g, "D");
- // "m" before "h" == month, "m" after "h" == minute
+ // "m" before "h" === month, "m" after "h" === minute
const indexH = format.indexOf("h");
if (indexH >= 0) {
format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
@@ -17521,6 +17652,7 @@
env.model.dispatch("SELECT_FIGURE", { id: figureId });
env.openSidePanel("ChartPanel");
},
+ icon: "o-spreadsheet-Icon.EDIT",
},
getCopyMenuItem(figureId, env),
getCutMenuItem(figureId, env),
@@ -17552,6 +17684,7 @@
width,
});
},
+ icon: "o-spreadsheet-Icon.REFRESH",
},
getDeleteMenuItem(figureId, onFigureDeleted, env),
];
@@ -17568,6 +17701,7 @@
env.model.dispatch("COPY");
await env.clipboard.write(env.model.getters.getClipboardContent());
},
+ icon: "o-spreadsheet-Icon.COPY",
};
}
function getCutMenuItem(figureId, env) {
@@ -17581,6 +17715,7 @@
env.model.dispatch("CUT");
await env.clipboard.write(env.model.getters.getClipboardContent());
},
+ icon: "o-spreadsheet-Icon.CUT",
};
}
function getDeleteMenuItem(figureId, onFigureDeleted, env) {
@@ -17595,6 +17730,7 @@
});
onFigureDeleted();
},
+ icon: "o-spreadsheet-Icon.DELETE",
};
}
@@ -18662,7 +18798,7 @@
};
const categorieFunctionAll = {
name: _lt("All"),
- children: allFunctionListMenuBuilder(),
+ children: [allFunctionListMenuBuilder],
};
function allFunctionListMenuBuilder() {
const fnNames = functionRegistry.getKeys();
@@ -18994,7 +19130,10 @@
};
const formatNumberNumber = {
name: _lt("Number"),
- description: "1,000.12",
+ description: (env) => formatValue(1000.12, {
+ format: "#,##0.00",
+ locale: env.model.getters.getLocale(),
+ }),
execute: (env) => setFormatter(env, "#,##0.00"),
isActive: (env) => isFormatSelected(env, "#,##0.00"),
};
@@ -19005,21 +19144,34 @@
};
const formatNumberPercent = {
name: _lt("Percent"),
- description: "10.12%",
+ description: (env) => formatValue(0.1012, {
+ format: "0.00%",
+ locale: env.model.getters.getLocale(),
+ }),
execute: FORMAT_PERCENT_ACTION,
isActive: (env) => isFormatSelected(env, "0.00%"),
};
const formatNumberCurrency = {
name: _lt("Currency"),
- description: "$1,000.12",
- execute: (env) => setFormatter(env, "[$$]#,##0.00"),
- isActive: (env) => isFormatSelected(env, "[$$]#,##0.00"),
+ description: (env) => formatValue(1000.12, {
+ format: env.model.config.defaultCurrencyFormat,
+ locale: env.model.getters.getLocale(),
+ }),
+ execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
+ isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
};
const formatNumberCurrencyRounded = {
name: _lt("Currency rounded"),
- description: "$1,000",
- execute: (env) => setFormatter(env, "[$$]#,##0"),
- isActive: (env) => isFormatSelected(env, "[$$]#,##0"),
+ description: (env) => formatValue(1000, {
+ format: roundFormat(env.model.config.defaultCurrencyFormat),
+ locale: env.model.getters.getLocale(),
+ }),
+ execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
+ isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
+ isVisible: (env) => {
+ const currencyFormat = env.model.config.defaultCurrencyFormat;
+ return currencyFormat !== roundFormat(currencyFormat);
+ },
};
const formatCustomCurrency = {
name: _lt("Custom currency"),
@@ -19028,19 +19180,37 @@
};
const formatNumberDate = {
name: _lt("Date"),
- description: "9/26/2008",
+ description: (env) => {
+ const locale = env.model.getters.getLocale();
+ return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
+ format: locale.dateFormat,
+ locale,
+ });
+ },
execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
};
const formatNumberTime = {
name: _lt("Time"),
- description: "10:43:00 PM",
+ description: (env) => {
+ const locale = env.model.getters.getLocale();
+ return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
+ format: locale.timeFormat,
+ locale,
+ });
+ },
execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
};
const formatNumberDateTime = {
name: _lt("Date time"),
- description: "9/26/2008 22:43:00",
+ description: (env) => {
+ const locale = env.model.getters.getLocale();
+ return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
+ format: locale.dateFormat + " " + locale.timeFormat,
+ locale,
+ });
+ },
execute: (env) => {
const locale = env.model.getters.getLocale();
setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
@@ -19112,7 +19282,7 @@
};
const formatAlignmentHorizontal = {
name: _lt("Horizontal align"),
- icon: "o-spreadsheet-Icon.ALIGN_LEFT",
+ icon: (env) => getHorizontalAlignmentIcon(env),
};
const formatAlignmentLeft = {
name: _lt("Left"),
@@ -19137,46 +19307,50 @@
};
const formatAlignmentVertical = {
name: _lt("Vertical align"),
- icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
+ icon: (env) => getVerticalAlignmentIcon(env),
};
const formatAlignmentTop = {
name: _lt("Top"),
execute: (env) => setStyle(env, { verticalAlign: "top" }),
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "top",
+ isActive: (env) => getVerticalAlign(env) === "top",
icon: "o-spreadsheet-Icon.ALIGN_TOP",
};
const formatAlignmentMiddle = {
name: _lt("Middle"),
execute: (env) => setStyle(env, { verticalAlign: "middle" }),
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "middle",
+ isActive: (env) => getVerticalAlign(env) === "middle",
icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
};
const formatAlignmentBottom = {
name: _lt("Bottom"),
execute: (env) => setStyle(env, { verticalAlign: "bottom" }),
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "bottom",
+ isActive: (env) => getVerticalAlign(env) === "bottom",
icon: "o-spreadsheet-Icon.ALIGN_BOTTOM",
};
+ const formatWrappingIcon = {
+ name: _lt("Wrapping"),
+ icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
+ };
const formatWrapping = {
name: _lt("Wrapping"),
- icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
+ icon: (env) => getWrapModeIcon(env),
};
const formatWrappingOverflow = {
name: _lt("Overflow"),
execute: (env) => setStyle(env, { wrapping: "overflow" }),
- isActive: (env) => (env.model.getters.getCurrentStyle().wrapping || "overflow") === "overflow",
+ isActive: (env) => getWrappingMode(env) === "overflow",
icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
};
const formatWrappingWrap = {
name: _lt("Wrap"),
execute: (env) => setStyle(env, { wrapping: "wrap" }),
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "wrap",
+ isActive: (env) => getWrappingMode(env) === "wrap",
icon: "o-spreadsheet-Icon.WRAPPING_WRAP",
};
const formatWrappingClip = {
name: _lt("Clip"),
execute: (env) => setStyle(env, { wrapping: "clip" }),
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "clip",
+ isActive: (env) => getWrappingMode(env) === "clip",
icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
};
const textColor = {
@@ -19192,14 +19366,6 @@
execute: OPEN_CF_SIDEPANEL_ACTION,
icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
};
- const paintFormat = {
- name: _lt("Paint Format"),
- execute: (env) => env.model.dispatch("ACTIVATE_PAINT_FORMAT", {
- target: env.model.getters.getSelectedZones(),
- }),
- icon: "o-spreadsheet-Icon.PAINT_FORMAT",
- isActive: (env) => env.model.getters.isPaintingFormat(),
- };
const clearFormat = {
name: _lt("Clear formatting"),
description: "Ctrl+<",
@@ -19240,6 +19406,53 @@
const cell = env.model.getters.getActiveCell();
return cell.defaultAlign;
}
+ function getVerticalAlign(env) {
+ const style = env.model.getters.getCurrentStyle();
+ if (style.verticalAlign) {
+ return style.verticalAlign;
+ }
+ return DEFAULT_VERTICAL_ALIGN;
+ }
+ function getWrappingMode(env) {
+ const style = env.model.getters.getCurrentStyle();
+ if (style.wrapping) {
+ return style.wrapping;
+ }
+ return DEFAULT_WRAPPING_MODE;
+ }
+ function getHorizontalAlignmentIcon(env) {
+ const horizontalAlign = getHorizontalAlign(env);
+ switch (horizontalAlign) {
+ case "right":
+ return "o-spreadsheet-Icon.ALIGN_RIGHT";
+ case "center":
+ return "o-spreadsheet-Icon.ALIGN_CENTER";
+ default:
+ return "o-spreadsheet-Icon.ALIGN_LEFT";
+ }
+ }
+ function getVerticalAlignmentIcon(env) {
+ const verticalAlign = getVerticalAlign(env);
+ switch (verticalAlign) {
+ case "top":
+ return "o-spreadsheet-Icon.ALIGN_TOP";
+ case "middle":
+ return "o-spreadsheet-Icon.ALIGN_MIDDLE";
+ default:
+ return "o-spreadsheet-Icon.ALIGN_BOTTOM";
+ }
+ }
+ function getWrapModeIcon(env) {
+ const wrapMode = getWrappingMode(env);
+ switch (wrapMode) {
+ case "wrap":
+ return "o-spreadsheet-Icon.WRAPPING_WRAP";
+ case "clip":
+ return "o-spreadsheet-Icon.WRAPPING_CLIP";
+ default:
+ return "o-spreadsheet-Icon.WRAPPING_OVERFLOW";
+ }
+ }
var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
__proto__: null,
@@ -19270,6 +19483,7 @@
formatAlignmentTop: formatAlignmentTop,
formatAlignmentMiddle: formatAlignmentMiddle,
formatAlignmentBottom: formatAlignmentBottom,
+ formatWrappingIcon: formatWrappingIcon,
formatWrapping: formatWrapping,
formatWrappingOverflow: formatWrappingOverflow,
formatWrappingWrap: formatWrappingWrap,
@@ -19277,7 +19491,6 @@
textColor: textColor,
fillColor: fillColor,
formatCF: formatCF,
- paintFormat: paintFormat,
clearFormat: clearFormat
});
@@ -20040,7 +20253,7 @@
separator: true,
})
.addChild("format_wrapping", ["format"], {
- ...formatWrapping,
+ ...formatWrappingIcon,
sequence: 80,
separator: true,
})
@@ -22330,17 +22543,45 @@
}
get formatProposals() {
const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];
- const proposalBases = this.initProposalBases(currency.decimalPlaces);
- const firstPosition = currency.position;
- const secondPosition = currency.position === "before" ? "after" : "before";
+ const position = currency.position;
+ const opposite = currency.position === "before" ? "after" : "before";
const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : "";
const code = this.state.currencyCode.trim() ? this.state.currencyCode : "";
- return code || symbol
- ? [
- ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),
- ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),
- ]
- : [];
+ const decimalPlaces = currency.decimalPlaces;
+ if (!symbol && !code) {
+ return [];
+ }
+ const simple = symbol ? createCurrencyFormat({ symbol, position, decimalPlaces }) : "";
+ const rounded = simple ? roundFormat(simple) : "";
+ const simpleWithCode = createCurrencyFormat({ symbol, position, decimalPlaces, code });
+ const roundedWithCode = roundFormat(simpleWithCode);
+ const simpleOpposite = symbol
+ ? createCurrencyFormat({ symbol, position: opposite, decimalPlaces })
+ : "";
+ const roundedOpposite = simpleOpposite ? roundFormat(simpleOpposite) : "";
+ const simpleOppositeWithCode = createCurrencyFormat({
+ symbol,
+ position: opposite,
+ decimalPlaces,
+ code,
+ });
+ const roundedOppositeWithCode = roundFormat(simpleOppositeWithCode);
+ const formats = new Set([
+ rounded,
+ simple,
+ roundedWithCode,
+ simpleWithCode,
+ roundedOpposite,
+ simpleOpposite,
+ roundedOppositeWithCode,
+ simpleOppositeWithCode,
+ ]);
+ return [...formats]
+ .filter((format) => format !== "")
+ .map((format) => ({
+ format,
+ example: formatValue(1000.0, { format, locale: this.env.model.getters.getLocale() }),
+ }));
}
get isSameFormat() {
const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];
@@ -22397,41 +22638,6 @@
initAvailableCurrencies() {
this.state.selectedCurrencyIndex = 0;
}
- initProposalBases(decimalPlaces) {
- const result = [{ format: "#,##0", example: "1,000" }];
- const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
- if (decimalRepresentation) {
- result.push({
- format: "#,##0" + decimalRepresentation,
- example: "1,000" + decimalRepresentation,
- });
- }
- return result;
- }
- createFormatProposals(proposalBases, symbol, code, position) {
- let formatProposals = [];
- // 1 - add proposal with symbol and without code
- if (symbol) {
- for (let base of proposalBases) {
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));
- }
- }
- // 2 - if code exist --> add more proposal with symbol and with code
- if (code) {
- for (let base of proposalBases) {
- const expression = (position === "after" ? " " : "") + code + " " + symbol;
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));
- }
- }
- return formatProposals;
- }
- createFormatProposal(position, baseExample, formatBase, expression) {
- const formatExpression = "[$" + expression + "]";
- return {
- example: position === "before" ? expression + baseExample : baseExample + expression,
- format: position === "before" ? formatExpression + formatBase : formatBase + formatExpression,
- };
- }
getCommonFormat() {
const selectedZones = this.env.model.getters.getSelectedZones();
const sheetId = this.env.model.getters.getActiveSheetId();
@@ -22488,6 +22694,7 @@
state = owl.useState(this.initialState());
debounceTimeoutId;
showFormulaState = false;
+ debouncedUpdateSearch;
findAndReplaceRef = owl.useRef("findAndReplace");
get hasSearchResult() {
return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
@@ -22497,11 +22704,16 @@
}
setup() {
this.showFormulaState = this.env.model.getters.shouldShowFormulas();
+ this.debouncedUpdateSearch = debounce(this.updateSearch.bind(this), 200);
owl.onMounted(() => this.focusInput());
owl.onWillUnmount(() => {
this.env.model.dispatch("CLEAR_SEARCH");
this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
});
+ owl.useEffect(() => {
+ this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
+ this.searchFormulas();
+ }, () => [this.env.model.getters.shouldShowFormulas()]);
}
onInput(ev) {
this.state.toSearch = ev.target.value;
@@ -22521,10 +22733,6 @@
this.replace();
}
}
- onFocusSidePanel() {
- this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
- this.env.model.dispatch("REFRESH_SEARCH");
- }
searchFormulas() {
this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
show: this.state.searchOptions.searchFormulas,
@@ -22543,13 +22751,6 @@
searchOptions: this.state.searchOptions,
});
}
- debouncedUpdateSearch() {
- clearTimeout(this.debounceTimeoutId);
- this.debounceTimeoutId = setTimeout(() => {
- this.updateSearch();
- this.debounceTimeoutId = undefined;
- }, 400);
- }
replace() {
this.env.model.dispatch("REPLACE_SEARCH", {
replaceWith: this.state.replaceWith,
@@ -23234,7 +23435,7 @@
selection.removeAllRanges();
selection.addRange(range);
}
- if (start == end && start === 0) {
+ if (start === end && start === 0) {
range.setStart(this.el, 0);
range.setEnd(this.el, 0);
}
@@ -23487,7 +23688,7 @@
}, 0);
}
}
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
usedCharacters++;
}
return usedCharacters;
@@ -25034,6 +25235,14 @@
};
}
+ const CURSOR_SVG = /*xml*/ `
+
+`;
+ css /* scss */ `
+ .o-paint-format-cursor {
+ cursor: url("data:image/svg+xml,${encodeURIComponent(CURSOR_SVG)}"), auto;
+ }
+`;
function useCellHovered(env, gridRef, callback) {
let hoveredPosition = {
col: undefined,
@@ -25157,6 +25366,12 @@
}
return this.gridOverlay.el;
}
+ get style() {
+ return this.props.gridOverlayDimensions;
+ }
+ get isPaintingFormat() {
+ return this.env.model.getters.isPaintingFormat();
+ }
onMouseDown(ev) {
if (ev.button > 0) {
// not main button, probably a context menu
@@ -26334,6 +26549,9 @@
else if (this.menuState.isOpen) {
this.closeMenu();
}
+ else if (this.env.model.getters.isPaintingFormat()) {
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
+ }
else {
this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
}
@@ -27536,8 +27754,8 @@
throw new Error("Multiple escaped blocks in format");
}
convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ === ignore width of next char for align purposes. Not supported ATM
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * === repeat next character enough to fill the line. Not supported ATM
convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
if (isXlsxDateFormat(convertedFormat)) {
@@ -28829,29 +29047,26 @@
return new XMLString(concat(str));
}
/**
- * Removes the namespace of all the xml tags in the string.
+ * Removes the escaped namespace of all the xml tags in the string.
*
- * Eg. : "ns:test a" => "test a"
+ * Eg. : "NAMESPACEnsNAMESPACEtest a" => "test a"
*/
- function removeNamespaces(query) {
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
+ function removeTagEscapedNamespaces(tag) {
+ return tag.replace(/NAMESPACE.*NAMESPACE(.*)/, "$1");
}
/**
- * Escape the namespace's colons of all the xml tags in the string.
+ * Encase the namespaces in the element's tags with NAMESPACE string
*
- * Eg. : "ns:test a" => "ns\\:test a"
+ * e.g. becomes
+ *
+ * That's useful because namespaces aren't supported by the HTML specification, so it's arbitrary whether a HTML parser/querySelector
+ * implementation will support namespaces in the tags or not.
*/
- function escapeNamespaces(query) {
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
+ function escapeTagNamespaces(str) {
+ return str.replaceAll(/(<\/?)([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "$1" + "NAMESPACE" + "$2" + "NAMESPACE" + "$3");
}
- /**
- * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.
- *
- * Should return true if it's running on a browser, and false if it's running on jest (jsdom).
- */
- function areNamespaceIgnoredByQuerySelector() {
- const doc = new DOMParser().parseFromString("", "text/xml");
- return doc.querySelector("test") !== null;
+ function escapeQueryNameSpaces(query) {
+ return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
}
class AttributeValue {
@@ -28881,14 +29096,20 @@
// The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but
// the XLSXImportFile contains both the main .xml file, and the .rels file
currentFile = undefined;
- // If the parser querySelector() implementation ignores tag namespaces or not
- areNamespaceIgnored;
+ /**
+ * /!\ Important : There should be no namespaces in the tags of the XML files.
+ *
+ * This class use native querySelector and querySelectorAll, that's used for HTML (not XML). These aren't supposed to
+ * handled namespaces, as they are not supported by the HTML specification. Some implementations (most browsers) do
+ * actually support namespaces, but some don't (e.g. jsdom).
+ *
+ * The namespace should be escaped as with NAMESPACE string (eg. => ).
+ */
constructor(rootFile, xlsxStructure, warningManager) {
this.rootFile = rootFile;
this.currentFile = rootFile.file.fileName;
this.xlsxFileStructure = xlsxStructure;
this.warningManager = warningManager;
- this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();
this.relationships = {};
if (rootFile.rels) {
this.extractRelationships(rootFile.rels).map((rel) => {
@@ -29114,29 +29335,13 @@
throw new Error("Cannot find target file");
return f;
}
- /**
- * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
- *
- * Why we need to do this :
- * - For an XML ""
- * - on Jest(jsdom) : xml.querySelector("test") == null, xml.querySelector("t\\:test") ==
- * - on Browser : xml.querySelector("test") == , xml.querySelector("t\\:test") == null
- */
querySelector(element, query) {
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
- return element.querySelector(query);
+ const escapedQuery = escapeQueryNameSpaces(query);
+ return element.querySelector(escapedQuery);
}
- /**
- * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
- *
- * Why we need to do this :
- * - For an XML ""
- * - on Jest(jsdom) : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == []
- * - on Browser : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == []
- */
querySelectorAll(element, query) {
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
- return element.querySelectorAll(query);
+ const escapedQuery = escapeQueryNameSpaces(query);
+ return element.querySelectorAll(escapedQuery);
}
/**
* Get a color from its id in the Theme's colorScheme.
@@ -29376,7 +29581,7 @@
throw new Error("Missing plot area in the chart definition.");
}
for (let child of plotAreaElement.children) {
- const tag = removeNamespaces(child.tagName);
+ const tag = removeTagEscapedNamespaces(child.tagName);
if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
return tag;
}
@@ -29388,7 +29593,7 @@
class XlsxFigureExtractor extends XlsxBaseExtractor {
extractFigures() {
return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
- const anchorType = removeNamespaces(figureElement.tagName);
+ const anchorType = removeTagEscapedNamespaces(figureElement.tagName);
if (anchorType !== "twoCellAnchor") {
throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
}
@@ -30022,7 +30227,8 @@
for (let key of Object.keys(files)) {
// Random files can be in xlsx (like a bin file for printer settings)
if (key.endsWith(".xml") || key.endsWith(".rels")) {
- this.xmls[key] = parseXML(new XMLString(files[key]));
+ const contentString = escapeTagNamespaces(files[key]);
+ this.xmls[key] = parseXML(new XMLString(contentString));
}
else if (key.includes("media/image")) {
this.images.push({
@@ -31089,7 +31295,7 @@
}
/**
* Set the borders of a cell.
- * It overrides the current border if override == true.
+ * It overrides the current border if override === true.
*/
setBorder(sheetId, col, row, border, override = true) {
if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
@@ -32359,7 +32565,9 @@
const numHeader = this.getters.getNumberRows(sheetId);
let gridHeight = 0;
for (let i = 0; i < numHeader; i++) {
- gridHeight += this.getters.getRowSize(sheetId, i);
+ // TODO : since the row size is an UI value now, this doesn't work anymore. Using the default cell height is
+ // a temporary solution at best, but is broken.
+ gridHeight += this.getters.getUserRowSize(sheetId, i) || DEFAULT_CELL_HEIGHT;
}
const figures = this.getters.getFigures(sheetId);
for (const figure of figures) {
@@ -32785,33 +32993,15 @@
}
class HeaderSizePlugin extends CorePlugin {
- static getters = ["getRowSize", "getColSize"];
+ static getters = ["getUserRowSize", "getColSize"];
sizes = {};
handle(cmd) {
switch (cmd.type) {
case "CREATE_SHEET": {
- const computedSizes = this.computeSheetSizes(cmd.sheetId);
- const sizes = {
- COL: computedSizes.COL.map((size) => ({
- manualSize: undefined,
- computedSize: lazy(size),
- })),
- ROW: computedSizes.ROW.map((size) => ({
- manualSize: undefined,
- computedSize: lazy(size),
- })),
- };
- this.history.update("sizes", cmd.sheetId, sizes);
+ this.history.update("sizes", cmd.sheetId, { COL: [], ROW: [] });
break;
}
case "DUPLICATE_SHEET":
- // make sure the values are computed in case the original sheet is deleted
- for (const row of this.sizes[cmd.sheetId].ROW) {
- row.computedSize();
- }
- for (const col of this.sizes[cmd.sheetId].COL) {
- col.computedSize();
- }
this.history.update("sizes", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));
break;
case "DELETE_SHEET":
@@ -32820,21 +33010,8 @@
this.history.update("sizes", sizes);
break;
case "REMOVE_COLUMNS_ROWS": {
- let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
- for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {
- sizes.splice(headerIndex, 1);
- }
- const min = Math.min(...cmd.elements);
- sizes = sizes.map((size, row) => {
- if (cmd.dimension === "ROW" && row >= min) {
- // invalidate sizes
- return {
- manualSize: size.manualSize,
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
- };
- }
- return size;
- });
+ const arr = this.sizes[cmd.sheetId][cmd.dimension];
+ const sizes = removeIndexesFromArray(arr, cmd.elements);
this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
break;
}
@@ -32843,51 +33020,18 @@
const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
const baseSize = sizes[cmd.base];
sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
- sizes = sizes.map((size, row) => {
- if (cmd.dimension === "ROW" && row > cmd.base + cmd.quantity) {
- // invalidate sizes
- return {
- manualSize: size.manualSize,
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
- };
- }
- return size;
- });
this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
break;
}
case "RESIZE_COLUMNS_ROWS":
- for (let el of cmd.elements) {
- if (cmd.dimension === "ROW") {
- const height = this.getRowTallestCellSize(cmd.sheetId, el);
- const size = height;
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
- manualSize: cmd.size || undefined,
- computedSize: lazy(size),
- });
- }
- else {
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
- manualSize: cmd.size || undefined,
- computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),
- });
+ if (cmd.dimension === "ROW") {
+ for (const el of cmd.elements) {
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
}
}
- break;
- case "UPDATE_CELL":
- if (!this.sizes[cmd.sheetId]?.["ROW"]?.[cmd.row]?.manualSize) {
- const { sheetId, row } = cmd;
- this.history.update("sizes", sheetId, "ROW", row, "computedSize", lazy(() => this.getRowTallestCellSize(sheetId, row)));
- }
- break;
- case "ADD_MERGE":
- case "REMOVE_MERGE":
- for (let target of cmd.target) {
- for (let row of range(target.top, target.bottom + 1)) {
- const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);
- if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {
- this.history.update("sizes", cmd.sheetId, "ROW", row, "computedSize", lazy(rowHeight));
- }
+ else {
+ for (const el of cmd.elements) {
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
}
}
break;
@@ -32895,94 +33039,29 @@
return;
}
getColSize(sheetId, index) {
- return this.getHeaderSize(sheetId, "COL", index);
+ return Math.round(this.sizes[sheetId]?.["COL"][index] || DEFAULT_CELL_WIDTH);
}
- getRowSize(sheetId, index) {
- return this.getHeaderSize(sheetId, "ROW", index);
- }
- getHeaderSize(sheetId, dimension, index) {
- return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
- this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
- this.getDefaultHeaderSize(dimension));
- }
- computeSheetSizes(sheetId) {
- const sizes = { COL: [], ROW: [] };
- for (let col of range(0, this.getters.getNumberCols(sheetId))) {
- sizes.COL.push(this.getHeaderSize(sheetId, "COL", col));
- }
- for (let row of range(0, this.getters.getNumberRows(sheetId))) {
- let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
- if (!rowSize) {
- const height = this.getRowTallestCellSize(sheetId, row);
- rowSize = height;
- }
- sizes.ROW.push(rowSize);
- }
- return sizes;
- }
- getDefaultHeaderSize(dimension) {
- return dimension === "COL" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;
- }
- /**
- * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
- * merge, or the height of the cell computed based on its font size.
- */
- getCellHeight(position) {
- const merge = this.getters.getMerge(position);
- if (merge && merge.bottom !== merge.top) {
- return DEFAULT_CELL_HEIGHT;
- }
- const cell = this.getters.getCell(position);
- return getDefaultCellHeight(cell);
- }
- /**
- * Get the tallest cell of a row and its size.
- *
- * The tallest cell of the row correspond to the cell with the biggest font size,
- * and that is not part of a multi-line merge.
- */
- getRowTallestCellSize(sheetId, row) {
- const cellIds = this.getters.getRowCells(sheetId, row);
- let maxHeight = 0;
- for (let i = 0; i < cellIds.length; i++) {
- const cell = this.getters.getCellById(cellIds[i]);
- if (!cell)
- continue;
- const position = this.getters.getCellPosition(cell.id);
- const cellHeight = this.getCellHeight(position);
- if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
- maxHeight = cellHeight;
- }
- }
- if (maxHeight <= DEFAULT_CELL_HEIGHT) {
- return DEFAULT_CELL_HEIGHT;
- }
- return maxHeight;
+ getUserRowSize(sheetId, index) {
+ const rowSize = this.sizes[sheetId]?.["ROW"][index];
+ return rowSize ? Math.round(rowSize) : undefined;
}
import(data) {
for (let sheet of data.sheets) {
- const manualSizes = { COL: [], ROW: [] };
+ const sizes = {
+ COL: Array(sheet.colNumber).fill(undefined),
+ ROW: Array(sheet.rowNumber).fill(undefined),
+ };
for (let [rowIndex, row] of Object.entries(sheet.rows)) {
if (row.size) {
- manualSizes["ROW"][rowIndex] = row.size;
+ sizes["ROW"][rowIndex] = row.size;
}
}
for (let [colIndex, col] of Object.entries(sheet.cols)) {
if (col.size) {
- manualSizes["COL"][colIndex] = col.size;
+ sizes["COL"][colIndex] = col.size;
}
}
- const computedSizes = this.computeSheetSizes(sheet.id);
- this.sizes[sheet.id] = {
- COL: computedSizes.COL.map((size, i) => ({
- manualSize: manualSizes.COL[i],
- computedSize: lazy(size),
- })),
- ROW: computedSizes.ROW.map((size, i) => ({
- manualSize: manualSizes.ROW[i],
- computedSize: lazy(size),
- })),
- };
+ this.sizes[sheet.id] = sizes;
}
return;
}
@@ -33003,9 +33082,12 @@
if (sheet.rows === undefined) {
sheet.rows = {};
}
- for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
- if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
- sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
+ for (const row of range(0, this.getters.getNumberRows(sheet.id))) {
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]) {
+ sheet.rows[row] = {
+ ...sheet.rows[row],
+ size: this.getUserRowSize(sheet.id, row) ?? DEFAULT_CELL_HEIGHT,
+ };
}
}
// Export col sizes
@@ -33013,7 +33095,7 @@
sheet.cols = {};
}
for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
- if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]) {
sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
}
}
@@ -36569,7 +36651,7 @@
case "ContainsText":
return cell.value.toString().indexOf(values[0].toString()) > -1;
case "NotContains":
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) === -1;
case "GreaterThan":
return cell.value > values[0];
case "GreaterThanOrEqual":
@@ -36596,6 +36678,156 @@
};
}
+ class UIRowSizePlugin extends UIPlugin {
+ static getters = ["getRowSize"];
+ tallestCellInRow = {};
+ ctx = document.createElement("canvas").getContext("2d");
+ handle(cmd) {
+ switch (cmd.type) {
+ case "START":
+ for (const sheetId of this.getters.getSheetIds()) {
+ this.initializeSheet(sheetId);
+ }
+ break;
+ case "CREATE_SHEET": {
+ this.initializeSheet(cmd.sheetId);
+ break;
+ }
+ case "DUPLICATE_SHEET": {
+ const tallestCells = deepCopy(this.tallestCellInRow[cmd.sheetId]);
+ this.history.update("tallestCellInRow", cmd.sheetIdTo, tallestCells);
+ break;
+ }
+ case "DELETE_SHEET":
+ const tallestCells = { ...this.tallestCellInRow };
+ delete tallestCells[cmd.sheetId];
+ this.history.update("tallestCellInRow", tallestCells);
+ break;
+ case "REMOVE_COLUMNS_ROWS": {
+ if (cmd.dimension === "COL") {
+ return;
+ }
+ const tallestCells = removeIndexesFromArray(this.tallestCellInRow[cmd.sheetId], cmd.elements);
+ this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
+ break;
+ }
+ case "ADD_COLUMNS_ROWS": {
+ if (cmd.dimension === "COL") {
+ return;
+ }
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
+ const newCells = Array(cmd.quantity).fill(undefined);
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
+ break;
+ }
+ case "RESIZE_COLUMNS_ROWS":
+ {
+ const sheetId = cmd.sheetId;
+ if (cmd.dimension === "ROW") {
+ for (const row of cmd.elements) {
+ const tallestCell = this.getRowTallestCell(sheetId, row);
+ this.history.update("tallestCellInRow", sheetId, row, tallestCell);
+ }
+ }
+ else {
+ // Recompute row heights on col size change, they might have changed because of wrapped text
+ for (const row of range(0, this.getters.getNumberRows(sheetId))) {
+ for (const col of cmd.elements) {
+ this.updateRowSizeForCellChange(sheetId, row, col);
+ }
+ }
+ }
+ }
+ break;
+ case "UPDATE_CELL":
+ this.updateRowSizeForCellChange(cmd.sheetId, cmd.row, cmd.col);
+ break;
+ case "ADD_MERGE":
+ case "REMOVE_MERGE":
+ for (const target of cmd.target) {
+ for (const position of positions(target)) {
+ this.updateRowSizeForCellChange(cmd.sheetId, position.row, position.col);
+ }
+ }
+ }
+ return;
+ }
+ getRowSize(sheetId, row) {
+ return Math.round(this.getters.getUserRowSize(sheetId, row) ??
+ this.tallestCellInRow[sheetId][row]?.size ??
+ DEFAULT_CELL_HEIGHT);
+ }
+ updateRowSizeForCellChange(sheetId, row, col) {
+ const tallestCellInRow = this.tallestCellInRow[sheetId]?.[row];
+ if (tallestCellInRow?.cell.col === col) {
+ const newTallestCell = this.getRowTallestCell(sheetId, row);
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
+ }
+ const updatedCellHeight = this.getCellHeight({ sheetId, col, row });
+ if (updatedCellHeight <= DEFAULT_CELL_HEIGHT) {
+ return;
+ }
+ if ((!tallestCellInRow && updatedCellHeight > DEFAULT_CELL_HEIGHT) ||
+ (tallestCellInRow && updatedCellHeight > tallestCellInRow.size)) {
+ const newTallestCell = { cell: { sheetId, col, row }, size: updatedCellHeight };
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
+ }
+ }
+ initializeSheet(sheetId) {
+ const tallestCells = [];
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
+ const tallestCell = this.getRowTallestCell(sheetId, row);
+ tallestCells.push(tallestCell);
+ }
+ this.history.update("tallestCellInRow", sheetId, tallestCells);
+ }
+ /**
+ * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
+ * merge, or the height of the cell computed based on its style/content.
+ */
+ getCellHeight(position) {
+ if (this.isInMultiRowMerge(position)) {
+ return DEFAULT_CELL_HEIGHT;
+ }
+ const cell = this.getters.getCell(position);
+ const colSize = this.getters.getColSize(position.sheetId, position.col);
+ return getDefaultCellHeight(this.ctx, cell, colSize);
+ }
+ isInMultiRowMerge(position) {
+ const merge = this.getters.getMerge(position);
+ return !!merge && merge.bottom !== merge.top;
+ }
+ /**
+ * Get the tallest cell of a row and its size.
+ */
+ getRowTallestCell(sheetId, row) {
+ const userRowSize = this.getters.getUserRowSize(sheetId, row);
+ if (userRowSize !== undefined) {
+ return undefined;
+ }
+ const cellIds = this.getters.getRowCells(sheetId, row);
+ let maxHeight = 0;
+ let tallestCell = undefined;
+ for (let i = 0; i < cellIds.length; i++) {
+ const cell = this.getters.getCellById(cellIds[i]);
+ if (!cell) {
+ continue;
+ }
+ const position = this.getters.getCellPosition(cell.id);
+ const cellHeight = this.getCellHeight(position);
+ if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
+ maxHeight = cellHeight;
+ tallestCell = { cell: position, size: cellHeight };
+ }
+ }
+ if (tallestCell && tallestCell.size > DEFAULT_CELL_HEIGHT) {
+ return tallestCell;
+ }
+ return undefined;
+ }
+ }
+
/**
* This plugin manage the autofill.
*
@@ -38297,6 +38529,7 @@
searchFormulas: false,
};
toSearch = "";
+ isSearchDirty = false;
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -38324,14 +38557,21 @@
case "REDO":
case "REMOVE_COLUMNS_ROWS":
case "ADD_COLUMNS_ROWS":
- this.clearSearch();
+ case "EVALUATE_CELLS":
+ case "UPDATE_CELL":
+ this.isSearchDirty = true;
break;
case "ACTIVATE_SHEET":
- case "REFRESH_SEARCH":
this.refreshSearch();
break;
}
}
+ finalize() {
+ if (this.isSearchDirty) {
+ this.refreshSearch();
+ this.isSearchDirty = false;
+ }
+ }
// ---------------------------------------------------------------------------
// Getters
// ---------------------------------------------------------------------------
@@ -38387,6 +38627,11 @@
if (this.toSearch) {
for (const cell of Object.values(cells)) {
const { col, row } = this.getters.getCellPosition(cell.id);
+ const isColHidden = this.getters.isColHidden(sheetId, col);
+ const isRowHidden = this.getters.isRowHidden(sheetId, row);
+ if (isColHidden || isRowHidden) {
+ continue;
+ }
if (cell &&
this.currentSearchRegex &&
this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
@@ -40029,10 +40274,8 @@
}
updateCellCommands.push(newCellValues);
}
- for (const cmd of updateCellCommands) {
- this.dispatch("UPDATE_CELL", cmd);
- }
}
+ updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
}
/**
* Return the distances between main merge cells in the zone.
@@ -40133,24 +40376,28 @@
// Getters
// ---------------------------------------------------------------------------
getCellWidth(position) {
- const text = this.getCellText(position);
const style = this.getters.getCellComputedStyle(position);
- const multiLineText = text.split(NEWLINE);
- let contentWidth = Math.max(...multiLineText.map((line) => this.getTextWidth(line, style)));
+ let contentWidth = 0;
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
+ if (content) {
+ const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
+ contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
+ }
const icon = this.getters.getConditionalIcon(position);
if (icon) {
- contentWidth += computeIconWidth(this.getters.getCellStyle(position));
+ contentWidth += computeIconWidth(style);
}
const isFilterHeader = this.getters.isFilterHeader(position);
if (isFilterHeader) {
contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;
}
- if (contentWidth > 0) {
- contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
- if (this.getters.getCellStyle(position).wrapping === "wrap") {
- const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
- return Math.min(colWidth, contentWidth);
- }
+ if (contentWidth === 0) {
+ return 0;
+ }
+ contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
+ if (style.wrapping === "wrap") {
+ const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
+ return Math.min(colWidth, contentWidth);
}
return contentWidth;
}
@@ -40173,56 +40420,7 @@
getCellMultiLineText(position, width) {
const style = this.getters.getCellStyle(position);
const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());
- const brokenText = [];
- for (const line of text.split("\n")) {
- const words = line.split(" ");
- if (!width) {
- brokenText.push(line);
- continue;
- }
- 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;
+ return splitTextToWidth(this.ctx, text, style, width);
}
/**
* Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -40281,26 +40479,6 @@
const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
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;
- }
/**
* Check that any "sheetId" in the command matches an existing
* sheet.
@@ -41292,7 +41470,7 @@
.join("\n") || "\t");
}
getHTMLContent() {
- if (this.cells.length == 1 && this.cells[0].length == 1) {
+ if (this.cells.length === 1 && this.cells[0].length === 1) {
return this.getters.getCellText(this.cells[0][0].position);
}
let htmlTable = '';
@@ -41617,7 +41795,7 @@
status = "invisible";
state;
lastPasteState;
- _isPaintingFormat = false;
+ paintFormatStatus = "inactive";
originSheetId;
// ---------------------------------------------------------------------------
// Command Handling
@@ -41632,7 +41810,7 @@
if (!this.state) {
return 24 /* CommandResult.EmptyClipboard */;
}
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "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);
@@ -41648,6 +41826,11 @@
const state = this.getClipboardStateForCopyCells(cut, "CUT");
return state.isPasteAllowed(paste);
}
+ case "ACTIVATE_PAINT_FORMAT": {
+ if (this.paintFormatStatus !== "inactive") {
+ return 95 /* CommandResult.AlreadyInPaintingFormatMode */;
+ }
+ }
}
return 0 /* CommandResult.Success */;
}
@@ -41664,11 +41847,13 @@
if (!this.state) {
break;
}
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
- this._isPaintingFormat = false;
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
this.lastPasteState = this.state;
- this.status = "invisible";
+ if (this.paintFormatStatus === "oneOff") {
+ this.paintFormatStatus = "inactive";
+ this.status = "invisible";
+ }
break;
case "CLEAN_CLIPBOARD_HIGHLIGHT":
this.status = "invisible";
@@ -41736,8 +41921,13 @@
case "ACTIVATE_PAINT_FORMAT": {
const zones = this.getters.getSelectedZones();
this.state = this.getClipboardStateForCopyCells(zones, "COPY");
- this._isPaintingFormat = true;
this.status = "visible";
+ if (cmd.persistent) {
+ this.paintFormatStatus = "persistent";
+ }
+ else {
+ this.paintFormatStatus = "oneOff";
+ }
break;
}
case "DELETE_SHEET":
@@ -41749,6 +41939,11 @@
this.status = "invisible";
}
break;
+ case "CANCEL_PAINT_FORMAT": {
+ this.paintFormatStatus = "inactive";
+ this.status = "invisible";
+ break;
+ }
default:
if (isCoreCommand(cmd)) {
this.status = "invisible";
@@ -41779,7 +41974,7 @@
return this.state ? this.state.operation === "CUT" : false;
}
isPaintingFormat() {
- return this._isPaintingFormat;
+ return this.paintFormatStatus !== "inactive";
}
// ---------------------------------------------------------------------------
// Private methods
@@ -44414,6 +44609,7 @@
.add("evaluation", EvaluationPlugin)
.add("evaluation_chart", EvaluationChartPlugin)
.add("evaluation_cf", EvaluationConditionalFormatPlugin)
+ .add("row_size", UIRowSizePlugin)
.add("custom_colors", CustomColorsPlugin);
const clickableCellRegistry = new Registry();
@@ -45536,6 +45732,11 @@
.o-sidePanelButton:enabled {
cursor: pointer;
}
+
+ .o-sidePanelButton:disabled {
+ color: ${DISABLED_TEXT_COLOR};
+ }
+
.o-sidePanelButton:last-child {
margin-right: 0px;
}
@@ -45669,11 +45870,11 @@
}
get title() {
const name = this.actionButton.name(this.env);
- const description = this.actionButton.description;
+ const description = this.actionButton.description(this.env);
return name + (description ? ` (${description})` : "");
}
get iconTitle() {
- return this.actionButton.icon;
+ return this.actionButton.icon(this.env);
}
onClick(ev) {
if (this.isEnabled) {
@@ -46058,6 +46259,27 @@
class: String,
};
+ class PaintFormatButton extends owl.Component {
+ static template = "o-spreadsheet-PaintFormatButton";
+ get isActive() {
+ return this.env.model.getters.isPaintingFormat();
+ }
+ onDblClick() {
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: true });
+ }
+ togglePaintFormat() {
+ if (this.isActive) {
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
+ }
+ else {
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: false });
+ }
+ }
+ }
+ PaintFormatButton.props = {
+ class: { type: String, optional: true },
+ };
+
// -----------------------------------------------------------------------------
// TopBar
// -----------------------------------------------------------------------------
@@ -46158,6 +46380,7 @@
TopBarComposer,
FontSizeEditor,
ActionButton,
+ PaintFormatButton,
BorderEditorWidget,
};
state = owl.useState({
@@ -48010,7 +48233,7 @@
: "nextCluster";
while (true) {
const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);
- // Break if nextPosition == currentPosition, which happens if there's no next valid position
+ // Break if nextPosition === currentPosition, which happens if there's no next valid position
if (currentPosition.col === nextCellPosition.col &&
currentPosition.row === nextCellPosition.row) {
break;
@@ -49924,6 +50147,7 @@
...config,
mode: config.mode || "normal",
custom: config.custom || {},
+ defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
external: this.setupExternalConfig(config.external || {}),
transportService,
client,
@@ -50221,6 +50445,7 @@
numberToLetters,
UuidGenerator,
formatValue,
+ createCurrencyFormat,
computeTextWidth,
createEmptyWorkbookData,
createEmptySheet,
@@ -50306,9 +50531,9 @@
Object.defineProperty(exports, '__esModule', { value: true });
- __info__.version = '16.4.0-alpha.7';
- __info__.date = '2023-07-04T14:12:23.240Z';
- __info__.hash = 'b04cf78';
+ __info__.version = '16.5.0-alpha.1';
+ __info__.date = '2023-07-26T13:08:47.460Z';
+ __info__.hash = '9160731';
})(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 76fd232ba4a..5ebafb39901 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
@@ -599,8 +599,8 @@
t-on-contextmenu.prevent=""
t-att-style="getCellClickableStyle(clickableCell.coordinates)"
/>
+
-
@@ -1007,7 +1007,8 @@
@@ -2213,10 +2214,11 @@
+