diff --git a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
index b5717f70916..83c08258a3e 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.js
@@ -34,6 +34,7 @@
return new LazyTranslatedString(str, values);
};
class LazyTranslatedString extends String {
+ values;
constructor(str, values) {
super(str);
this.values = values;
@@ -62,6 +63,8 @@
CellErrorLevel[CellErrorLevel["error"] = 1] = "error";
})(CellErrorLevel || (CellErrorLevel = {}));
class EvaluationError extends Error {
+ errorType;
+ logLevel;
constructor(errorType, message, logLevel = CellErrorLevel.error) {
super(message);
this.errorType = errorType;
@@ -223,7 +226,7 @@
const MENU_WIDTH = 250;
const MENU_VERTICAL_PADDING = 6;
const MENU_ITEM_HEIGHT = 26;
- const MENU_ITEM_PADDING_HORIZONTAL = 13;
+ const MENU_ITEM_PADDING_HORIZONTAL = 11;
const MENU_ITEM_PADDING_VERTICAL = 4;
const MENU_SEPARATOR_BORDER_WIDTH = 1;
const MENU_SEPARATOR_PADDING = 5;
@@ -232,12 +235,10 @@
const DEFAULT_FONT_SIZE = 10;
const HEADER_FONT_SIZE = 11;
const DEFAULT_FONT = "'Roboto', arial";
- const DEFAULT_VERTICAL_ALIGN = "middle";
+ const DEFAULT_VERTICAL_ALIGN = "bottom";
// Borders
const DEFAULT_BORDER_DESC = ["thin", "#000"];
const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
- // DateTimeRegex
- const DATETIME_FORMAT = /[ymdhs:]/;
// Ranges
const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
// Max Number of history steps kept in memory
@@ -708,7 +709,7 @@
* Check if the object is a plain old javascript object.
*/
function isPlainObject(obj) {
- return typeof obj === "object" && (obj === null || obj === void 0 ? void 0 : obj.constructor) === Object;
+ return typeof obj === "object" && obj?.constructor === Object;
}
/**
* Sanitize the name of a sheet, by eventually removing quotes
@@ -726,8 +727,7 @@
* @param sheetName Name of the sheet
*/
function getCanonicalSheetName(sheetName) {
- var _a;
- if (((_a = sheetName.match(/\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {
+ if (sheetName.match(/\w/g)?.length !== sheetName.length) {
sheetName = `'${sheetName}'`;
}
return sheetName;
@@ -767,7 +767,7 @@
return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
}
function computeTextFontSizeInPixels(style) {
- const sizeInPt = (style === null || style === void 0 ? void 0 : style.fontSize) || DEFAULT_FONT_SIZE;
+ const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
return fontSizeInPixels(sizeInPt);
}
/**
@@ -1048,6 +1048,10 @@
}
return true;
}
+ /** Check if the given array contains all the values of the other array. */
+ function includesAll(arr, values) {
+ return values.every((value) => arr.includes(value));
+ }
/**
* Return an object with all the keys in the object that have a falsy value removed.
*/
@@ -1101,7 +1105,7 @@
}
const RBA_REGEX = /rgba?\(|\s+|\)/gi;
- const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/g;
+ const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
const colors$1 = [
"#eb6d00",
"#0074d9",
@@ -1157,7 +1161,7 @@
}
hexColor = `#${hexColor}`;
}
- if (!hexColor.match(HEX_MATCH)) {
+ if (!HEX_MATCH.test(hexColor)) {
throw new Error(`invalid color input: ${color}`);
}
return hexColor;
@@ -1368,55 +1372,8 @@
return isColorValid(color1) && isColorValid(color2) && toHex(color1) === toHex(color2);
}
- /** Reference of a cell (eg. A1, $B$5) */
- const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
- // Same as above, but matches the exact string (nothing before or after)
- const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
- /** Reference of a column header (eg. A, AB) */
- const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
- /** Reference of a column (eg. A, $CA, Sheet1!B) */
- const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
- /** Reference of a row (eg. 1, 59, Sheet1!9) */
- const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
- /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
- const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
- /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
- const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
- /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
- const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
- "(" +
- [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
- ")" +
- /$/.source, "i");
- /**
- * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
- */
- function isColReference(xc) {
- return colReference.test(xc);
- }
- /**
- * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
- */
- function isRowReference(xc) {
- return rowReference.test(xc);
- }
- function isColHeader(str) {
- return colHeader.test(str);
- }
- /**
- * Return true if the given xc is the reference of a single cell,
- * without any specified sheet (e.g. A1)
- */
- function isSingleCellReference(xc) {
- return singleCellReference.test(xc);
- }
- function splitReference(ref) {
- const parts = ref.split("!");
- const xc = parts.pop();
- const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
- return { sheetName, xc };
- }
-
+ //------------------------------------------------------------------------------
+ // Coordinate
//------------------------------------------------------------------------------
/**
* Convert a (col) number to the corresponding letter.
@@ -1438,6 +1395,13 @@
return numberToLetters(Math.floor(n / 26) - 1) + numberToLetters(n % 26);
}
}
+ const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const LETTERS_NUMBER_MAPPING = {};
+ for (const letter of LETTERS) {
+ const colIndex = letter.charCodeAt(0) - 64;
+ LETTERS_NUMBER_MAPPING[letter] = colIndex;
+ LETTERS_NUMBER_MAPPING[letter.toLowerCase()] = colIndex;
+ }
/**
* Convert a string (describing a column) to its number value.
*
@@ -1447,14 +1411,22 @@
* 'AA' => 26
*/
function lettersToNumber(letters) {
- let result = 0;
+ let result = -1;
const l = letters.length;
- for (let i = 0; i < l; i++) {
- let n = letters.charCodeAt(i) - 65 + (i < l - 1 ? 1 : 0);
- result += n * 26 ** (l - i - 1);
+ let pow = 1;
+ for (let i = l - 1; i >= 0; i--) {
+ const charCode = LETTERS_NUMBER_MAPPING[letters[i]];
+ result += charCode * pow;
+ pow *= 26;
}
return result;
}
+ function isCharALetter(char) {
+ return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
+ }
+ function isCharADigit(char) {
+ return char >= "0" && char <= "9";
+ }
/**
* Convert a "XC" coordinate to cartesian coordinates.
*
@@ -1465,17 +1437,46 @@
* Note: it also accepts lowercase coordinates, but not fixed references
*/
function toCartesian(xc) {
- xc = xc.toUpperCase().trim();
- const match = xc.match(cellReference);
- if (match !== null) {
- const [m, letters, numbers] = match;
- if (m === xc) {
- const col = lettersToNumber(letters);
- const row = parseInt(numbers, 10) - 1;
- return { col, row };
+ xc = xc.trim();
+ let numberPartStart = undefined;
+ // Note: looping by hand is uglier but ~2x faster than using a regex to match number/letter parts
+ for (let i = 0; i < xc.length; i++) {
+ const char = xc[i];
+ // as long as we haven't found the number part, keep advancing
+ if (!numberPartStart) {
+ if ((char === "$" && i === 0) || isCharALetter(char)) {
+ continue;
+ }
+ numberPartStart = i;
+ }
+ // Number part
+ if (!isCharADigit(char)) {
+ if (char === "$" && i === numberPartStart) {
+ continue;
+ }
+ throw new Error(`Invalid cell description: ${xc}`);
}
}
- throw new Error(`Invalid cell description: ${xc}`);
+ if (!numberPartStart || numberPartStart === xc.length) {
+ throw new Error(`Invalid cell description: ${xc}`);
+ }
+ const letterPart = xc[0] === "$" ? xc.slice(1, numberPartStart) : xc.slice(0, numberPartStart);
+ const numberPart = xc[numberPartStart] === "$" ? xc.slice(numberPartStart + 1) : xc.slice(numberPartStart);
+ // limit to max 3 letters and 7 numbers to avoid
+ // gigantic numbers that would be a performance killer
+ // down the road
+ if (letterPart.length < 1 ||
+ letterPart.length > 3 ||
+ numberPart.length < 1 ||
+ numberPart.length > 7) {
+ throw new Error(`Invalid cell description: ${xc}`);
+ }
+ const col = lettersToNumber(letterPart);
+ const row = Number(numberPart) - 1;
+ if (isNaN(row)) {
+ throw new Error(`Invalid cell description: ${xc}`);
+ }
+ return { col, row };
}
/**
* Convert from cartesian coordinate to the "XC" coordinate system.
@@ -1686,7 +1687,6 @@
return formattedValue;
}
function applyIntegerFormat(integerDigits, integerFormat, hasSeparator) {
- var _a;
const _integerDigits = integerDigits === "0" ? "" : integerDigits;
let formattedInteger = _integerDigits;
const delta = integerFormat.length - _integerDigits.length;
@@ -1697,7 +1697,7 @@
formattedInteger = "0".repeat(countZero) + formattedInteger; // return "000123"
}
if (hasSeparator) {
- formattedInteger = ((_a = formattedInteger.match(thousandsGroupsRegexp)) === null || _a === void 0 ? void 0 : _a.join(",")) || formattedInteger;
+ formattedInteger = formattedInteger.match(thousandsGroupsRegexp)?.join(",") || formattedInteger;
}
return formattedInteger;
}
@@ -1812,6 +1812,10 @@
* Check if the given format is a time, date or date time format.
*/
function isDateTimeFormat(format) {
+ if (!allowedDateTimeFormatFirstChar.has(format[0])) {
+ // first check for performance reason
+ return false;
+ }
try {
applyDateTimeFormat(1, format);
return true;
@@ -1820,6 +1824,7 @@
return false;
}
}
+ const allowedDateTimeFormatFirstChar = new Set(["h", "m", "y", "d"]);
function applyDateTimeFormat(value, format) {
// TODO: unify the format functions for date and datetime
// This requires some code to 'parse' or 'tokenize' the format, keep it in a
@@ -1842,8 +1847,7 @@
return strDate + (strDate && strTime ? " " : "") + strTime;
}
function formatJSDate(jsDate, format) {
- var _a;
- const sep = (_a = format.match(/\/|-|\s/)) === null || _a === void 0 ? void 0 : _a[0];
+ const sep = format.match(/\/|-|\s/)?.[0];
const parts = sep ? format.split(sep) : [format];
return parts
.map((p) => {
@@ -1993,10 +1997,9 @@
return newFormat;
}
function changeInternalNumberFormatDecimalPlaces(format, step) {
- var _a;
const _format = { ...format };
const sign = Math.sign(step);
- const decimalLength = ((_a = _format.decimalPart) === null || _a === void 0 ? void 0 : _a.length) || 0;
+ const decimalLength = _format.decimalPart?.length || 0;
const countZero = Math.min(Math.max(0, decimalLength + sign), MAX_DECIMAL_PLACES);
_format.decimalPart = "0".repeat(countZero);
if (_format.decimalPart === "") {
@@ -2041,7 +2044,7 @@
const nextPartIndex = format.substring(currentIndex).indexOf("[");
closingIndex = nextPartIndex > -1 ? nextPartIndex + currentIndex : format.length;
const subFormat = format.substring(currentIndex, closingIndex);
- if (subFormat.match(DATETIME_FORMAT)) {
+ if (isDateTimeFormat(subFormat)) {
result.push({ type: "DATE", format: subFormat });
}
else {
@@ -2060,17 +2063,16 @@
* @param format a formatString that is only applicable to numbers. I.e. composed of characters 0 # , . %
*/
function convertToInternalNumberFormat(format) {
- var _a;
format = format.trim();
if (containsInvalidNumberChars(format)) {
throw new Error(`Invalid number format: ${format}`);
}
const isPercent = format.includes("%");
- const magnitudeCommas = ((_a = format.match(magnitudeRegex)) === null || _a === void 0 ? void 0 : _a[0]) || "";
+ const magnitudeCommas = format.match(magnitudeRegex)?.[0] || "";
const magnitude = !magnitudeCommas ? 1 : 1000 ** magnitudeCommas.length;
let _format = format.slice(0, format.length - (magnitudeCommas.length || 0));
const thousandsSeparator = _format.includes(",");
- if (_format.match(/\..*,/)) {
+ if (/\..*,/.test(_format)) {
throw new Error("A format can't contain ',' symbol in the decimal part");
}
_format = _format.replace("%", "").replace(",", "");
@@ -2137,10 +2139,65 @@
return format;
}
+ /** Reference of a cell (eg. A1, $B$5) */
+ const cellReference = new RegExp(/\$?([A-Z]{1,3})\$?([0-9]{1,7})/, "i");
+ // Same as above, but matches the exact string (nothing before or after)
+ const singleCellReference = new RegExp(/^\$?([A-Z]{1,3})\$?([0-9]{1,7})$/, "i");
+ /** Reference of a column header (eg. A, AB) */
+ const colHeader = new RegExp(/^([A-Z]{1,3})+$/, "i");
+ /** Reference of a column (eg. A, $CA, Sheet1!B) */
+ const colReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([A-Z]{1,3})$/, "i");
+ /** Reference of a row (eg. 1, 59, Sheet1!9) */
+ const rowReference = new RegExp(/^\s*('.+'!|[^']+!)?\$?([0-9]{1,7})$/, "i");
+ /** Reference of a normal range or a full row range (eg. A1:B1, 1:$5, $A2:5) */
+ const fullRowXc = /(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*:\s*(\$?[A-Z]{1,3})?\$?[0-9]{1,7}\s*/i;
+ /** Reference of a normal range or a column row range (eg. A1:B1, A:$B, $A1:C) */
+ const fullColXc = /\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*:\s*\$?[A-Z]{1,3}(\$?[0-9]{1,7})?\s*/i;
+ /** Reference of a cell or a range, it can be a bounded range, a full row or a full column */
+ const rangeReference = new RegExp(/^\s*('.+'!|[^']+!)?/.source +
+ "(" +
+ [cellReference.source, fullRowXc.source, fullColXc.source].join("|") +
+ ")" +
+ /$/.source, "i");
+ /**
+ * Return true if the given xc is the reference of a column (e.g. A or AC or Sheet1!A)
+ */
+ function isColReference(xc) {
+ return colReference.test(xc);
+ }
+ /**
+ * Return true if the given xc is the reference of a column (e.g. 1 or Sheet1!1)
+ */
+ function isRowReference(xc) {
+ return rowReference.test(xc);
+ }
+ function isColHeader(str) {
+ return colHeader.test(str);
+ }
+ /**
+ * Return true if the given xc is the reference of a single cell,
+ * without any specified sheet (e.g. A1)
+ */
+ function isSingleCellReference(xc) {
+ return singleCellReference.test(xc);
+ }
+ function splitReference(ref) {
+ const parts = ref.split("!");
+ const xc = parts.pop();
+ const sheetName = getUnquotedSheetName(parts.join("!")) || undefined;
+ return { sheetName, xc };
+ }
+
class RangeImpl {
+ getSheetSize;
+ _zone;
+ parts;
+ invalidXc;
+ prefixSheet = false;
+ sheetId; // the sheet on which the range is defined
+ invalidSheetName; // the name of any sheet that is invalid
constructor(args, getSheetSize) {
this.getSheetSize = getSheetSize;
- this.prefixSheet = false;
this._zone = args.zone;
this.parts = args.parts;
this.prefixSheet = args.prefixSheet;
@@ -2215,7 +2272,6 @@
* If it's not the case, simply invert them, and invert the linked parts
*/
orderZone() {
- var _a, _b, _c, _d, _e, _f, _g, _h;
const zone = { ...this._zone };
let parts = this.parts;
if (zone.right !== undefined && zone.right < zone.left) {
@@ -2224,12 +2280,12 @@
zone.left = right;
parts = [
{
- colFixed: ((_a = parts[1]) === null || _a === void 0 ? void 0 : _a.colFixed) || false,
- rowFixed: ((_b = parts[0]) === null || _b === void 0 ? void 0 : _b.rowFixed) || false,
+ colFixed: parts[1]?.colFixed || false,
+ rowFixed: parts[0]?.rowFixed || false,
},
{
- colFixed: ((_c = parts[0]) === null || _c === void 0 ? void 0 : _c.colFixed) || false,
- rowFixed: ((_d = parts[1]) === null || _d === void 0 ? void 0 : _d.rowFixed) || false,
+ colFixed: parts[0]?.colFixed || false,
+ rowFixed: parts[1]?.rowFixed || false,
},
];
}
@@ -2239,12 +2295,12 @@
zone.top = bottom;
parts = [
{
- colFixed: ((_e = parts[0]) === null || _e === void 0 ? void 0 : _e.colFixed) || false,
- rowFixed: ((_f = parts[1]) === null || _f === void 0 ? void 0 : _f.rowFixed) || false,
+ colFixed: parts[0]?.colFixed || false,
+ rowFixed: parts[1]?.rowFixed || false,
},
{
- colFixed: ((_g = parts[1]) === null || _g === void 0 ? void 0 : _g.colFixed) || false,
- rowFixed: ((_h = parts[0]) === null || _h === void 0 ? void 0 : _h.rowFixed) || false,
+ colFixed: parts[1]?.colFixed || false,
+ rowFixed: parts[0]?.rowFixed || false,
},
];
}
@@ -2256,18 +2312,18 @@
*/
clone(rangeParams) {
return new RangeImpl({
- zone: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.zone) ? rangeParams.zone : { ...this._zone },
- sheetId: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.sheetId) ? rangeParams.sheetId : this.sheetId,
+ zone: rangeParams?.zone ? rangeParams.zone : { ...this._zone },
+ sheetId: rangeParams?.sheetId ? rangeParams.sheetId : this.sheetId,
invalidSheetName: rangeParams && "invalidSheetName" in rangeParams // 'attr in obj' instead of just 'obj.attr' because we accept undefined values
? rangeParams.invalidSheetName
: this.invalidSheetName,
invalidXc: rangeParams && "invalidXc" in rangeParams ? rangeParams.invalidXc : this.invalidXc,
- parts: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.parts)
+ parts: rangeParams?.parts
? rangeParams.parts
: this.parts.map((part) => {
return { rowFixed: part.rowFixed, colFixed: part.colFixed };
}),
- prefixSheet: (rangeParams === null || rangeParams === void 0 ? void 0 : rangeParams.prefixSheet) ? rangeParams.prefixSheet : this.prefixSheet,
+ prefixSheet: rangeParams?.prefixSheet ? rangeParams.prefixSheet : this.prefixSheet,
}, this.getSheetSize);
}
}
@@ -2350,10 +2406,8 @@
* https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
* */
class UuidGenerator {
- constructor() {
- this.isFastIdStrategy = false;
- this.fastIdStart = 0;
- }
+ isFastIdStrategy = false;
+ fastIdStart = 0;
setIsFastStrategy(isFast) {
this.isFastIdStrategy = isFast;
}
@@ -2394,11 +2448,19 @@
*
*/
function toZoneWithoutBoundaryChanges(xc) {
- xc = xc.split("!").pop();
- const ranges = xc
- .replace(/\$/g, "")
- .split(":")
- .map((x) => x.trim());
+ if (xc.includes("!")) {
+ xc = xc.split("!").at(-1);
+ }
+ if (xc.includes("$")) {
+ xc = xc.replace(/\$/g, "");
+ }
+ let ranges;
+ if (xc.includes(":")) {
+ ranges = xc.split(":").map((x) => x.trim());
+ }
+ else {
+ ranges = [xc.trim()];
+ }
let top, bottom, left, right;
let fullCol = false;
let fullRow = false;
@@ -2968,10 +3030,9 @@
}
class ChartJsComponent extends owl.Component {
- constructor() {
- super(...arguments);
- this.canvas = owl.useRef("graphContainer");
- }
+ static template = "o-spreadsheet-ChartJsComponent";
+ canvas = owl.useRef("graphContainer");
+ chart;
get background() {
return this.chartRuntime.background;
}
@@ -3006,15 +3067,14 @@
this.chart = new window.Chart(ctx, chartData);
}
updateChartJs(chartRuntime) {
- var _a, _b, _c, _d, _e;
const chartData = chartRuntime.chartJsConfig;
if (chartData.data && chartData.data.datasets) {
this.chart.data = chartData.data;
- if ((_a = chartData.options) === null || _a === void 0 ? void 0 : _a.title) {
+ if (chartData.options?.title) {
this.chart.config.options.title = chartData.options.title;
}
if (chartData.options && "valueLabel" in chartData.options) {
- if ((_b = chartData.options) === null || _b === void 0 ? void 0 : _b.valueLabel) {
+ if (chartData.options?.valueLabel) {
this.chart.config.options.valueLabel =
chartData.options.valueLabel;
}
@@ -3023,13 +3083,12 @@
else {
this.chart.data.datasets = undefined;
}
- this.chart.config.options.tooltips = (_c = chartData.options) === null || _c === void 0 ? void 0 : _c.tooltips;
- this.chart.config.options.legend = (_d = chartData.options) === null || _d === void 0 ? void 0 : _d.legend;
- this.chart.config.options.scales = (_e = chartData.options) === null || _e === void 0 ? void 0 : _e.scales;
+ this.chart.config.options.tooltips = chartData.options?.tooltips;
+ this.chart.config.options.legend = chartData.options?.legend;
+ this.chart.config.options.scales = chartData.options?.scales;
this.chart.update({ duration: 0 });
}
}
- ChartJsComponent.template = "o-spreadsheet-ChartJsComponent";
ChartJsComponent.props = {
figure: Object,
};
@@ -3243,10 +3302,8 @@
}
`;
class ScorecardChart$1 extends owl.Component {
- constructor() {
- super(...arguments);
- this.ctx = document.createElement("canvas").getContext("2d");
- }
+ static template = "o-spreadsheet-ScorecardChart";
+ ctx = document.createElement("canvas").getContext("2d");
get runtime() {
return this.env.model.getters.getChartRuntime(this.props.figure.id);
}
@@ -3351,7 +3408,6 @@
: keyValue;
}
}
- ScorecardChart$1.template = "o-spreadsheet-ScorecardChart";
class BaselineElement {
getElementWidth(fontSize, ctx, chart) {
if (!chart.runtime)
@@ -3407,9 +3463,7 @@
* 3. it provides a chained API to add items to the registry.
*/
class Registry {
- constructor() {
- this.content = {};
- }
+ content = {};
/**
* Add an item to the registry
*
@@ -4211,6 +4265,7 @@
* for one or more reasons.
*/
class DispatchResult {
+ reasons;
constructor(results = []) {
if (!Array.isArray(results)) {
results = [results];
@@ -4323,6 +4378,12 @@
CommandResult[CommandResult["InvalidSelectionStep"] = 83] = "InvalidSelectionStep";
CommandResult[CommandResult["DuplicatedChartId"] = 84] = "DuplicatedChartId";
CommandResult[CommandResult["ChartDoesNotExist"] = 85] = "ChartDoesNotExist";
+ CommandResult[CommandResult["InvalidHeaderIndex"] = 86] = "InvalidHeaderIndex";
+ CommandResult[CommandResult["InvalidQuantity"] = 87] = "InvalidQuantity";
+ CommandResult[CommandResult["MoreThanOneColumnSelected"] = 88] = "MoreThanOneColumnSelected";
+ CommandResult[CommandResult["EmptySplitSeparator"] = 89] = "EmptySplitSeparator";
+ CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
+ CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
})(exports.CommandResult || (exports.CommandResult = {}));
var DIRECTION;
@@ -4513,10 +4574,9 @@
})
.add("INCREMENT_MODIFIER", {
apply: (rule, data) => {
- var _a;
rule.current += rule.increment;
const content = rule.current.toString();
- const tooltipValue = formatValue(rule.current, (_a = data.cell) === null || _a === void 0 ? void 0 : _a.format);
+ const tooltipValue = formatValue(rule.current, data.cell?.format);
return {
cellData: {
border: data.border,
@@ -4530,8 +4590,7 @@
})
.add("COPY_MODIFIER", {
apply: (rule, data, getters) => {
- var _a, _b, _c;
- const content = ((_a = data.cell) === null || _a === void 0 ? void 0 : _a.content) || "";
+ const content = data.cell?.content || "";
return {
cellData: {
border: data.border,
@@ -4542,7 +4601,7 @@
tooltip: content
? {
props: {
- content: evaluateLiteral((_b = data.cell) === null || _b === void 0 ? void 0 : _b.content, (_c = data.cell) === null || _c === void 0 ? void 0 : _c.format).formattedValue,
+ content: evaluateLiteral(data.cell?.content, data.cell?.format).formattedValue,
},
}
: undefined,
@@ -4606,7 +4665,7 @@
if (x === cell) {
found = true;
}
- const cellValue = evaluateLiteral(x === null || x === void 0 ? void 0 : x.content);
+ const cellValue = evaluateLiteral(x?.content);
if (filter(cellValue)) {
group.push(cellValue);
}
@@ -4645,8 +4704,7 @@
autofillRulesRegistry
.add("simple_value_copy", {
condition: (cell, cells) => {
- var _a;
- return cells.length === 1 && !cell.isFormula && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
+ return (cells.length === 1 && !cell.isFormula && !(cell.format && isDateTimeFormat(cell.format)));
},
generateRule: () => {
return { type: "COPY_MODIFIER" };
@@ -4718,10 +4776,10 @@
}
`;
class ErrorToolTip extends owl.Component {
+ static maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
+ static template = "o-spreadsheet-ErrorToolTip";
+ static components = {};
}
- ErrorToolTip.maxSize = { maxHeight: ERROR_TOOLTIP_MAX_HEIGHT };
- ErrorToolTip.template = "o-spreadsheet-ErrorToolTip";
- ErrorToolTip.components = {};
ErrorToolTip.props = {
text: String,
onClosed: { type: Function, optional: true },
@@ -4752,10 +4810,8 @@
}
`;
class FilterMenuValueItem extends owl.Component {
- constructor() {
- super(...arguments);
- this.itemRef = owl.useRef("menuValueItem");
- }
+ static template = "o-spreadsheet-FilterMenuValueItem";
+ itemRef = owl.useRef("menuValueItem");
setup() {
owl.onWillPatch(() => {
if (this.props.scrolledTo) {
@@ -4764,16 +4820,14 @@
});
}
scrollListToSelectedValue() {
- var _a, _b;
if (!this.itemRef.el) {
return;
}
- (_b = (_a = this.itemRef.el).scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, {
+ this.itemRef.el.scrollIntoView?.({
block: this.props.scrolledTo === "bottom" ? "end" : "start",
});
}
}
- FilterMenuValueItem.template = "o-spreadsheet-FilterMenuValueItem";
FilterMenuValueItem.props = {
value: String,
isChecked: Boolean,
@@ -4880,15 +4934,16 @@
}
`;
class FilterMenu extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- values: [],
- textFilter: "",
- selectedValue: undefined,
- });
- this.searchBar = owl.useRef("filterMenuSearchBar");
- }
+ static size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };
+ static template = "o-spreadsheet-FilterMenu";
+ static style = CSS$2;
+ static components = { FilterMenuValueItem };
+ state = owl.useState({
+ values: [],
+ textFilter: "",
+ selectedValue: undefined,
+ });
+ searchBar = owl.useRef("filterMenuSearchBar");
setup() {
owl.onWillUpdateProps((nextProps) => {
if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {
@@ -4922,10 +4977,9 @@
});
}
checkValue(value) {
- var _a;
this.state.selectedValue = value.string;
value.checked = !value.checked;
- (_a = this.searchBar.el) === null || _a === void 0 ? void 0 : _a.focus();
+ this.searchBar.el?.focus();
}
onMouseMove(value) {
this.state.selectedValue = value.string;
@@ -4948,18 +5002,16 @@
return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
}
confirm() {
- var _a, _b;
const position = this.props.filterPosition;
this.env.model.dispatch("UPDATE_FILTER", {
...position,
sheetId: this.env.model.getters.getActiveSheetId(),
values: this.state.values.filter((val) => !val.checked).map((val) => val.string),
});
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onClosed?.();
}
cancel() {
- var _a, _b;
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onClosed?.();
}
onKeyDown(ev) {
const displayedValues = this.displayedValues;
@@ -5016,7 +5068,6 @@
}
}
sortFilterZone(sortDirection) {
- var _a, _b;
const filterPosition = this.props.filterPosition;
const filterTable = this.filterTable;
if (!filterPosition || !filterTable || !filterTable.contentZone) {
@@ -5031,13 +5082,9 @@
sortDirection,
sortOptions: { emptyCellAsZero: true, sortHeaders: true },
});
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onClosed?.();
}
}
- FilterMenu.size = { width: MENU_WIDTH, height: FILTER_MENU_HEIGHT };
- FilterMenu.template = "o-spreadsheet-FilterMenu";
- FilterMenu.style = CSS$2;
- FilterMenu.components = { FilterMenuValueItem };
FilterMenu.props = {
filterPosition: Object,
onClosed: { type: Function, optional: true },
@@ -5063,7 +5110,7 @@
function gridOverlayPosition() {
const spreadsheetElement = document.querySelector(".o-grid-overlay");
if (spreadsheetElement) {
- const { top, left } = spreadsheetElement === null || spreadsheetElement === void 0 ? void 0 : spreadsheetElement.getBoundingClientRect();
+ const { top, left } = spreadsheetElement?.getBoundingClientRect();
return { top, left };
}
throw new Error("Can't find spreadsheet position");
@@ -5089,7 +5136,7 @@
}
}
function getElementScrollTop(el) {
- return (el === null || el === void 0 ? void 0 : el.scrollTop) || 0;
+ return el?.scrollTop || 0;
}
function setElementScrollTop(el, scroll) {
if (!el)
@@ -5213,26 +5260,32 @@
}
`;
class Popover extends owl.Component {
- constructor() {
- super(...arguments);
- this.popoverRef = owl.useRef("popover");
- this.currentPosition = undefined;
- this.currentDisplayValue = undefined;
- this.spreadsheetRect = useSpreadsheetRect();
- }
+ static template = "o-spreadsheet-Popover";
+ static defaultProps = {
+ positioning: "BottomLeft",
+ verticalOffset: 0,
+ onMouseWheel: () => { },
+ onPopoverMoved: () => { },
+ onPopoverHidden: () => { },
+ zIndex: ComponentsImportance.Popover,
+ };
+ popoverRef = owl.useRef("popover");
+ currentPosition = undefined;
+ currentDisplayValue = undefined;
+ spreadsheetRect = useSpreadsheetRect();
+ containerRect;
setup() {
this.containerRect = usePopoverContainer();
// useEffect occurs after the DOM is created and the element width/height are computed, but before
// the element in rendered, so we can still set its position
owl.useEffect(() => {
- var _a, _b, _c, _d;
if (!this.containerRect)
throw new Error("Popover container is not defined");
const el = this.popoverRef.el;
const anchor = rectIntersection(this.props.anchorRect, this.containerRect);
const newDisplay = anchor ? "block" : "none";
if (this.currentDisplayValue !== "none" && newDisplay === "none") {
- (_b = (_a = this.props).onPopoverHidden) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onPopoverHidden?.();
}
el.style.display = newDisplay;
this.currentDisplayValue = newDisplay;
@@ -5253,7 +5306,7 @@
}
const newPosition = popoverPositionHelper.getCurrentPosition(elDims);
if (this.currentPosition && newPosition !== this.currentPosition) {
- (_d = (_c = this.props).onPopoverMoved) === null || _d === void 0 ? void 0 : _d.call(_c);
+ this.props.onPopoverMoved?.();
}
this.currentPosition = newPosition;
});
@@ -5264,15 +5317,6 @@
});
}
}
- Popover.template = "o-spreadsheet-Popover";
- Popover.defaultProps = {
- positioning: "BottomLeft",
- verticalOffset: 0,
- onMouseWheel: () => { },
- onPopoverMoved: () => { },
- onPopoverHidden: () => { },
- zIndex: ComponentsImportance.Popover,
- };
Popover.props = {
anchorRect: Object,
containerRect: { type: Object, optional: true },
@@ -5287,6 +5331,10 @@
slots: Object,
};
class PopoverPositionContext {
+ anchorRect;
+ containerRect;
+ propsMaxSize;
+ spreadsheetOffset;
constructor(anchorRect, containerRect, propsMaxSize, spreadsheetOffset) {
this.anchorRect = anchorRect;
this.containerRect = containerRect;
@@ -5444,7 +5492,7 @@
.o-menu-item-icon {
display: inline-block;
- margin-right: calc(${MENU_ITEM_PADDING_HORIZONTAL}px * 3 / 5);
+ margin: 0px 8px 0px 0px;
width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
@@ -5473,17 +5521,19 @@
}
`;
class Menu extends owl.Component {
- constructor() {
- super(...arguments);
- this.subMenu = owl.useState({
- isOpen: false,
- position: null,
- scrollOffset: 0,
- menuItems: [],
- });
- this.menuRef = owl.useRef("menu");
- this.position = useAbsoluteBoundingRect(this.menuRef);
- }
+ static template = "o-spreadsheet-Menu";
+ static components = { Menu, Popover };
+ static defaultProps = {
+ depth: 1,
+ };
+ subMenu = owl.useState({
+ isOpen: false,
+ position: null,
+ scrollOffset: 0,
+ menuItems: [],
+ });
+ menuRef = owl.useRef("menu");
+ position = useAbsoluteBoundingRect(this.menuRef);
setup() {
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
owl.useExternalListener(window, "contextmenu", this.onExternalClick, { capture: true });
@@ -5507,6 +5557,9 @@
menuItemsAndSeparators.push("separator");
}
}
+ if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
+ menuItemsAndSeparators.pop();
+ }
if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
return [];
}
@@ -5533,11 +5586,13 @@
};
}
get childrenHaveIcon() {
- return this.props.menuItems.some((menuItem) => !menuItem.icon && !!menuItem.isActive);
+ return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
}
getIconName(menu) {
- var _a;
- if ((_a = menu.isActive) === null || _a === void 0 ? void 0 : _a.call(menu, this.env)) {
+ if (menu.icon) {
+ return menu.icon;
+ }
+ if (menu.isActive?.(this.env)) {
return "o-spreadsheet-Icon.CHECK";
}
return "";
@@ -5546,10 +5601,9 @@
return menu.textColor ? `color: ${menu.textColor}` : undefined;
}
async activateMenu(menu) {
- var _a, _b, _c;
- const result = await ((_a = menu.execute) === null || _a === void 0 ? void 0 : _a.call(menu, this.env));
+ const result = await menu.execute?.(this.env);
this.close();
- (_c = (_b = this.props).onMenuClicked) === null || _c === void 0 ? void 0 : _c.call(_b, { detail: result });
+ this.props.onMenuClicked?.({ detail: result });
}
close() {
this.closeSubMenu();
@@ -5597,8 +5651,7 @@
this.subMenu.parentMenu = menu;
}
isParentMenu(subMenu, menuItem) {
- var _a;
- return ((_a = subMenu.parentMenu) === null || _a === void 0 ? void 0 : _a.id) === menuItem.id;
+ return subMenu.parentMenu?.id === menuItem.id;
}
closeSubMenu() {
this.subMenu.isOpen = false;
@@ -5625,11 +5678,6 @@
}
}
}
- Menu.template = "o-spreadsheet-Menu";
- Menu.components = { Menu, Popover };
- Menu.defaultProps = {
- depth: 1,
- };
Menu.props = {
position: Object,
menuItems: Array,
@@ -5691,6 +5739,8 @@
}
`;
class LinkDisplay extends owl.Component {
+ static components = { Menu };
+ static template = "o-spreadsheet-LinkDisplay";
get cell() {
const { col, row } = this.props.cellPosition;
const sheetId = this.env.model.getters.getActiveSheetId();
@@ -5721,7 +5771,7 @@
const sheetId = this.env.model.getters.getActiveSheetId();
const { col, row } = this.props.cellPosition;
const style = this.env.model.getters.getCellComputedStyle({ sheetId, col, row });
- const textColor = (style === null || style === void 0 ? void 0 : style.textColor) === LINK_COLOR ? undefined : style === null || style === void 0 ? void 0 : style.textColor;
+ const textColor = style?.textColor === LINK_COLOR ? undefined : style?.textColor;
this.env.model.dispatch("UPDATE_CELL", {
col,
row,
@@ -5731,8 +5781,6 @@
});
}
}
- LinkDisplay.components = { Menu };
- LinkDisplay.template = "o-spreadsheet-LinkDisplay";
const LinkCellPopoverBuilder = {
onHover: (position, getters) => {
const cell = getters.getEvaluatedCell(position);
@@ -5993,19 +6041,18 @@
}
`;
class LinkEditor extends owl.Component {
- constructor() {
- super(...arguments);
- this.menuItems = linkMenuRegistry.getMenuItems();
- this.link = owl.useState(this.defaultState);
- this.menu = owl.useState({
- isOpen: false,
- });
- this.linkEditorRef = owl.useRef("linkEditor");
- this.position = useAbsoluteBoundingRect(this.linkEditorRef);
- this.urlInput = owl.useRef("urlInput");
- }
+ static template = "o-spreadsheet-LinkEditor";
+ static components = { Menu };
+ menuItems = linkMenuRegistry.getMenuItems();
+ link = owl.useState(this.defaultState);
+ menu = owl.useState({
+ isOpen: false,
+ });
+ linkEditorRef = owl.useRef("linkEditor");
+ position = useAbsoluteBoundingRect(this.linkEditorRef);
+ urlInput = owl.useRef("urlInput");
setup() {
- owl.onMounted(() => { var _a; return (_a = this.urlInput.el) === null || _a === void 0 ? void 0 : _a.focus(); });
+ owl.onMounted(() => this.urlInput.el?.focus());
}
get defaultState() {
const { col, row } = this.props.cellPosition;
@@ -6051,7 +6098,6 @@
this.link.isUrlEditable = true;
}
save() {
- var _a, _b;
const { col, row } = this.props.cellPosition;
const label = this.link.label || this.link.url;
this.env.model.dispatch("UPDATE_CELL", {
@@ -6060,11 +6106,10 @@
sheetId: this.env.model.getters.getActiveSheetId(),
content: markdownLink(label, this.link.url),
});
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onClosed?.();
}
cancel() {
- var _a, _b;
- (_b = (_a = this.props).onClosed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onClosed?.();
}
onKeyDown(ev) {
switch (ev.key) {
@@ -6081,8 +6126,6 @@
}
}
}
- LinkEditor.template = "o-spreadsheet-LinkEditor";
- LinkEditor.components = { Menu };
const LinkEditorPopoverBuilder = {
onOpen: (position, getters) => {
return {
@@ -6124,6 +6167,9 @@
* The role of this class is to maintain the state of each chart.
*/
class AbstractChart {
+ sheetId;
+ title;
+ getters;
constructor(definition, sheetId, getters) {
this.title = definition.title;
this.sheetId = sheetId;
@@ -6319,8 +6365,7 @@
* Transform a dataSet to a ExcelDataSet
*/
function toExcelDataset(getters, ds) {
- var _a;
- const labelZone = (_a = ds.labelCell) === null || _a === void 0 ? void 0 : _a.zone;
+ const labelZone = ds.labelCell?.zone;
let dataZone = ds.dataRange.zone;
if (labelZone) {
const { numberOfRows, numberOfCols } = zoneToDimension(dataZone);
@@ -6394,9 +6439,7 @@
"rgb(158,218,229)",
];
class ChartColors {
- constructor() {
- this.graphColorIndex = 0;
- }
+ graphColorIndex = 0;
next() {
return GraphColors[this.graphColorIndex++ % GraphColors.length];
}
@@ -6455,7 +6498,7 @@
return "";
}
else if (baselineMode === "text" ||
- (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number ||
+ keyValue?.type !== CellValueType.number ||
baseline.type !== CellValueType.number) {
return baseline.formattedValue;
}
@@ -6473,8 +6516,8 @@
}
function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
if (baselineMode === "text" ||
- (baseline === null || baseline === void 0 ? void 0 : baseline.type) !== CellValueType.number ||
- (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number) {
+ baseline?.type !== CellValueType.number ||
+ keyValue?.type !== CellValueType.number) {
return undefined;
}
const diff = keyValue.value - baseline.value;
@@ -6488,8 +6531,8 @@
}
function getBaselineArrowDirection(baseline, keyValue, baselineMode) {
if (baselineMode === "text" ||
- (baseline === null || baseline === void 0 ? void 0 : baseline.type) !== CellValueType.number ||
- (keyValue === null || keyValue === void 0 ? void 0 : keyValue.type) !== CellValueType.number) {
+ baseline?.type !== CellValueType.number ||
+ keyValue?.type !== CellValueType.number) {
return "neutral";
}
const diff = keyValue.value - baseline.value;
@@ -6578,6 +6621,14 @@
Custom: _lt("Custom"),
};
const MergeErrorMessage = _lt("Merged cells are preventing this operation. Unmerge those cells and try again.");
+ const SplitToColumnsTerms = {
+ Errors: {
+ Unexpected: _lt("Cannot split the selection for an unknown reason"),
+ [91 /* CommandResult.NoSplitSeparatorInSelection */]: _lt("There is no match for the selected separator in the selection"),
+ [88 /* CommandResult.MoreThanOneColumnSelected */]: _lt("Only a selection from a single column can be split"),
+ [90 /* CommandResult.SplitWillOverwriteContent */]: _lt("Splitting will overwrite existing content"),
+ },
+ };
/**
* This file contains helpers that are common to different runtime charts (mainly
@@ -6599,10 +6650,10 @@
return [];
}
function filterEmptyDataPoints(labels, datasets) {
- const numberOfDataPoints = Math.max(labels.length, ...datasets.map((dataset) => { var _a; return ((_a = dataset.data) === null || _a === void 0 ? void 0 : _a.length) || 0; }));
+ const numberOfDataPoints = Math.max(labels.length, ...datasets.map((dataset) => dataset.data?.length || 0));
const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
const label = labels[dataPointIndex];
- const values = datasets.map((dataset) => { var _a; return (_a = dataset.data) === null || _a === void 0 ? void 0 : _a[dataPointIndex]; });
+ const values = datasets.map((dataset) => dataset.data?.[dataPointIndex]);
return label || values.some((value) => value === 0 || Boolean(value));
});
return {
@@ -6689,14 +6740,13 @@
tooltips: {
callbacks: {
label: function (tooltipItem, data) {
- var _a, _b, _c, _d, _e;
- let xLabel = (_b = (_a = data.datasets) === null || _a === void 0 ? void 0 : _a[tooltipItem.datasetIndex || 0]) === null || _b === void 0 ? void 0 : _b.label;
+ let xLabel = data.datasets?.[tooltipItem.datasetIndex || 0]?.label;
const yLabel = tooltipItem.yLabel !== ""
? tooltipItem.yLabel
- : (_e = (_d = (_c = data.datasets) === null || _c === void 0 ? void 0 : _c[tooltipItem.datasetIndex || 0]) === null || _d === void 0 ? void 0 : _d.data) === null || _e === void 0 ? void 0 : _e[tooltipItem.index || 0];
+ : data.datasets?.[tooltipItem.datasetIndex || 0]?.data?.[tooltipItem.index || 0];
const yLabelStr = dataSetsFormat && typeof yLabel === "number"
? formatValue(yLabel, dataSetsFormat)
- : (yLabel === null || yLabel === void 0 ? void 0 : yLabel.toLocaleString()) || "";
+ : yLabel?.toLocaleString() || "";
return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
},
},
@@ -6794,9 +6844,17 @@
}
class BarChart extends AbstractChart {
+ dataSets;
+ labelRange;
+ background;
+ verticalAxisPosition;
+ legendPosition;
+ stacked;
+ aggregated;
+ type = "bar";
+ dataSetsHaveTitle;
constructor(definition, sheetId, getters) {
super(definition, sheetId, getters);
- this.type = "bar";
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
this.labelRange = createRange(getters, sheetId, definition.labelRange);
this.background = definition.background;
@@ -6891,7 +6949,6 @@
}
}
function getBarConfiguration(chart, labels, dataSetFormat) {
- var _a;
const fontColor = chartFontColor(chart.background);
const config = getDefaultChartJsRuntime(chart, labels, fontColor, dataSetFormat);
const legend = {
@@ -6903,7 +6960,7 @@
else {
legend.position = chart.legendPosition;
}
- config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };
+ config.options.legend = { ...config.options?.legend, ...legend };
config.options.layout = {
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
};
@@ -6930,7 +6987,7 @@
callback: (value) => {
return dataSetFormat
? formatValue(value, dataSetFormat)
- : (value === null || value === void 0 ? void 0 : value.toLocaleString()) || value;
+ : value?.toLocaleString() || value;
},
},
},
@@ -7037,9 +7094,12 @@
return 0 /* CommandResult.Success */;
}
class GaugeChart extends AbstractChart {
+ dataRange;
+ sectionRule;
+ background;
+ type = "gauge";
constructor(definition, sheetId, getters) {
super(definition, sheetId, getters);
- this.type = "gauge";
this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);
this.sectionRule = definition.sectionRule;
this.background = definition.background;
@@ -7336,7 +7396,7 @@
*
*/
function getBestTimeUnitForScale(labels, format) {
- const labelDates = labels.map((label) => { var _a; return (_a = parseDateTime(label)) === null || _a === void 0 ? void 0 : _a.jsDate; });
+ const labelDates = labels.map((label) => parseDateTime(label)?.jsDate);
if (labelDates.some((date) => date === undefined) || labels.length < 2) {
return undefined;
}
@@ -7362,9 +7422,18 @@
}
class LineChart extends AbstractChart {
+ dataSets;
+ labelRange;
+ background;
+ verticalAxisPosition;
+ legendPosition;
+ labelsAsText;
+ stacked;
+ aggregated;
+ type = "line";
+ dataSetsHaveTitle;
constructor(definition, sheetId, getters) {
super(definition, sheetId, getters);
- this.type = "line";
this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
this.background = definition.background;
@@ -7520,7 +7589,6 @@
return true;
}
function getLineConfiguration(chart, labels, dataSetFormat) {
- var _a;
const fontColor = chartFontColor(chart.background);
const config = getDefaultChartJsRuntime(chart, labels, fontColor, dataSetFormat);
const legend = {
@@ -7542,7 +7610,7 @@
else {
legend.position = chart.legendPosition;
}
- config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };
+ config.options.legend = { ...config.options?.legend, ...legend };
config.options.layout = {
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
};
@@ -7569,7 +7637,7 @@
callback: (value) => {
return dataSetFormat
? formatValue(value, dataSetFormat)
- : (value === null || value === void 0 ? void 0 : value.toLocaleString()) || value;
+ : value?.toLocaleString() || value;
},
},
},
@@ -7609,8 +7677,7 @@
config.options.scales.xAxes[0].type = "linear";
config.options.scales.xAxes[0].ticks.callback = (value) => formatValue(value, labelFormat);
config.options.tooltips.callbacks.title = (tooltipItem) => {
- var _a;
- return formatValue(((_a = tooltipItem[0]) === null || _a === void 0 ? void 0 : _a.xLabel) || "", labelFormat);
+ return formatValue(tooltipItem[0]?.xLabel || "", labelFormat);
};
}
const colors = new ChartColors();
@@ -7640,9 +7707,15 @@
}
class PieChart extends AbstractChart {
+ dataSets;
+ labelRange;
+ background;
+ legendPosition;
+ type = "pie";
+ aggregated;
+ dataSetsHaveTitle;
constructor(definition, sheetId, getters) {
super(definition, sheetId, getters);
- this.type = "pie";
this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
this.labelRange = createRange(getters, sheetId, definition.labelRange);
this.background = definition.background;
@@ -7732,7 +7805,6 @@
}
}
function getPieConfiguration(chart, labels, dataSetFormat) {
- var _a;
const fontColor = chartFontColor(chart.background);
const config = getDefaultChartJsRuntime(chart, labels, fontColor, dataSetFormat);
const legend = {
@@ -7744,7 +7816,7 @@
else {
legend.position = chart.legendPosition;
}
- config.options.legend = { ...(_a = config.options) === null || _a === void 0 ? void 0 : _a.legend, ...legend };
+ config.options.legend = { ...config.options?.legend, ...legend };
config.options.layout = {
padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
};
@@ -7801,9 +7873,17 @@
: 0 /* CommandResult.Success */;
}
class ScorecardChart extends AbstractChart {
+ keyValue;
+ baseline;
+ baselineMode;
+ baselineDescr;
+ background;
+ baselineColorUp;
+ baselineColorDown;
+ fontColor;
+ type = "scorecard";
constructor(definition, sheetId, getters) {
super(definition, sheetId, getters);
- this.type = "scorecard";
this.keyValue = createRange(getters, sheetId, definition.keyValue);
this.baseline = createRange(getters, sheetId, definition.baseline);
this.baselineMode = definition.baselineMode;
@@ -8026,6 +8106,8 @@
}
`;
class ChartFigure extends owl.Component {
+ static template = "o-spreadsheet-ChartFigure";
+ static components = {};
onDoubleClick() {
this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
this.env.openSidePanel("ChartPanel");
@@ -8042,14 +8124,14 @@
return component;
}
}
- ChartFigure.template = "o-spreadsheet-ChartFigure";
- ChartFigure.components = {};
ChartFigure.props = {
figure: Object,
onFigureDeleted: Function,
};
class ImageFigure extends owl.Component {
+ static template = "o-spreadsheet-ImageFigure";
+ static components = {};
// ---------------------------------------------------------------------------
// Getters
// ---------------------------------------------------------------------------
@@ -8060,8 +8142,6 @@
return this.env.model.getters.getImagePath(this.figureId);
}
}
- ImageFigure.template = "o-spreadsheet-ImageFigure";
- ImageFigure.components = {};
ImageFigure.props = {
figure: Object,
onFigureDeleted: Function,
@@ -8381,7 +8461,6 @@
* - Else returns a bar chart
*/
function getSmartChartDefinition(zone, getters) {
- var _a;
let dataSetZone = zone;
if (zone.left !== zone.right) {
dataSetZone = { ...zone, left: zone.left + 1 };
@@ -8389,11 +8468,11 @@
const dataSets = [zoneToXc(dataSetZone)];
const sheetId = getters.getActiveSheetId();
const topLeftCell = getters.getCell({ sheetId, col: zone.left, row: zone.top });
- if (getZoneArea(zone) === 1 && (topLeftCell === null || topLeftCell === void 0 ? void 0 : topLeftCell.content)) {
+ if (getZoneArea(zone) === 1 && topLeftCell?.content) {
return {
type: "scorecard",
title: "",
- background: ((_a = topLeftCell.style) === null || _a === void 0 ? void 0 : _a.fillColor) || undefined,
+ background: topLeftCell.style?.fillColor || undefined,
keyValue: zoneToXc(zone),
baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
@@ -8816,6 +8895,11 @@
}
return _lt("Delete columns %s - %s", numberToLetters(first), numberToLetters(last));
};
+ const NOT_ALL_VISIBLE_ROWS_SELECTED = (env) => {
+ const sheetId = env.model.getters.getActiveSheetId();
+ const selectedRows = env.model.getters.getElementsFromSelection("ROW");
+ return env.model.getters.canRemoveHeaders(sheetId, "ROW", selectedRows);
+ };
const REMOVE_COLUMNS_ACTION = (env) => {
let columns = [...env.model.getters.getActiveCols()];
if (!columns.length) {
@@ -8830,6 +8914,11 @@
elements: columns,
});
};
+ const NOT_ALL_VISIBLE_COLS_SELECTED = (env) => {
+ const sheetId = env.model.getters.getActiveSheetId();
+ const selectedCols = env.model.getters.getElementsFromSelection("COL");
+ return env.model.getters.canRemoveHeaders(sheetId, "COL", selectedCols);
+ };
const INSERT_CELL_SHIFT_DOWN = (env) => {
const zone = env.model.getters.getSelectedZone();
const result = env.model.dispatch("INSERT_CELL", { zone, shiftDimension: "ROW" });
@@ -8850,6 +8939,10 @@
const result = env.model.dispatch("DELETE_CELL", { zone, shiftDimension: "COL" });
handlePasteResult(env, result);
};
+ const MENU_INSERT_ROWS_NAME = (env) => {
+ const number = getColumnsNumber(env);
+ return number === 1 ? _lt("Insert row") : _lt("Insert %s rows", number.toString());
+ };
const MENU_INSERT_ROWS_BEFORE_NAME = (env) => {
const number = getRowsNumber(env);
if (number === 1) {
@@ -8928,6 +9021,10 @@
}
return _lt("%s Columns left", number.toString());
};
+ const MENU_INSERT_COLUMNS_NAME = (env) => {
+ const number = getColumnsNumber(env);
+ return number === 1 ? _lt("Insert column") : _lt("Insert %s columns", number.toString());
+ };
const COLUMN_INSERT_COLUMNS_BEFORE_NAME = (env) => {
const number = getColumnsNumber(env);
return number === 1
@@ -9235,23 +9332,28 @@
description: "Ctrl+C",
isReadonlyAllowed: true,
execute: COPY_ACTION,
+ icon: "o-spreadsheet-Icon.COPY",
};
const cut = {
name: _lt("Cut"),
description: "Ctrl+X",
execute: CUT_ACTION,
+ icon: "o-spreadsheet-Icon.CUT",
};
const paste = {
name: _lt("Paste"),
description: "Ctrl+V",
execute: PASTE_ACTION,
+ icon: "o-spreadsheet-Icon.PASTE",
};
const pasteSpecial = {
name: _lt("Paste special"),
isVisible: IS_NOT_CUT_OPERATION,
+ icon: "o-spreadsheet-Icon.PASTE",
};
const pasteSpecialValue = {
name: _lt("Paste value only"),
+ description: "Ctrl+Shift+V",
execute: PASTE_VALUE_ACTION,
};
const pasteSpecialFormat = {
@@ -9263,6 +9365,7 @@
description: "Ctrl+H",
isReadonlyAllowed: true,
execute: OPEN_FAR_SIDEPANEL_ACTION,
+ icon: "o-spreadsheet-Icon.FIND_AND_REPLACE",
};
const deleteValues = {
name: _lt("Delete values"),
@@ -9271,6 +9374,7 @@
const deleteRows = {
name: REMOVE_ROWS_NAME,
execute: REMOVE_ROWS_ACTION,
+ isVisible: NOT_ALL_VISIBLE_ROWS_SELECTED,
};
const deleteRow = {
...deleteRows,
@@ -9283,6 +9387,7 @@
const deleteCols = {
name: REMOVE_COLUMNS_NAME,
execute: REMOVE_COLUMNS_ACTION,
+ isVisible: NOT_ALL_VISIBLE_COLS_SELECTED,
};
const deleteCol = {
...deleteCols,
@@ -9547,7 +9652,7 @@
const value = Math.abs(toNumber(arg.value));
const format = arg.format;
if (unit !== undefined) {
- const postFix = unit === null || unit === void 0 ? void 0 : unit.value;
+ const postFix = unit?.value;
switch (postFix) {
case "k":
return createLargeNumberFormat(format, 1e3, "k");
@@ -9584,6 +9689,7 @@
const DEFAULT_MODE = 0;
const DEFAULT_PLACES = 0;
const DEFAULT_SIGNIFICANCE = 1;
+ const DECIMAL_REPRESENTATION = /^-?[a-z0-9]+$/i;
// -----------------------------------------------------------------------------
// ABS
// -----------------------------------------------------------------------------
@@ -9747,7 +9853,7 @@
arg(`factor (number, default=${DEFAULT_FACTOR})`, _lt("The number to whose multiples value will be rounded.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, factor = DEFAULT_FACTOR) {
const _value = toNumber(value);
const _factor = toNumber(factor);
@@ -9767,7 +9873,7 @@
arg(`mode (number, default=${DEFAULT_MODE})`, _lt("If number is negative, specifies the rounding direction. If 0 or blank, it is rounded towards zero. Otherwise, it is rounded away from zero.")),
],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (number, significance = DEFAULT_SIGNIFICANCE, mode = DEFAULT_MODE) {
let _significance = toNumber(significance);
if (_significance === 0) {
@@ -9796,7 +9902,7 @@
arg(`significance (number, default=${DEFAULT_SIGNIFICANCE})`, _lt("The number to whose multiples number will be rounded.")),
],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (number, significance) {
return CEILING_MATH.compute(number, significance, 0);
},
@@ -10010,7 +10116,7 @@
* Return error if 'value' is positive.
* Remove '-?' in the next regex to catch this error.
*/
- assert(() => !!_value.match(/^-?[a-z0-9]+$/i), _lt("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
+ assert(() => !!DECIMAL_REPRESENTATION.test(_value), _lt("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
const deci = parseInt(_value, _base);
assert(() => !isNaN(deci), _lt("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
return deci;
@@ -10051,7 +10157,7 @@
arg(`factor (number, default=${DEFAULT_FACTOR})`, _lt("The number to whose multiples value will be rounded.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, factor = DEFAULT_FACTOR) {
const _value = toNumber(value);
const _factor = toNumber(factor);
@@ -10071,7 +10177,7 @@
arg(`mode (number, default=${DEFAULT_MODE})`, _lt("If number is negative, specifies the rounding direction. If 0 or blank, it is rounded away from zero. Otherwise, it is rounded towards zero.")),
],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (number, significance = DEFAULT_SIGNIFICANCE, mode = DEFAULT_MODE) {
let _significance = toNumber(significance);
if (_significance === 0) {
@@ -10100,7 +10206,7 @@
arg(`significance (number, default=${DEFAULT_SIGNIFICANCE})`, _lt("The number to whose multiples number will be rounded.")),
],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (number, significance = DEFAULT_SIGNIFICANCE) {
return FLOOR_MATH.compute(number, significance, 0);
},
@@ -10129,7 +10235,7 @@
arg(`significance (number, default=${DEFAULT_SIGNIFICANCE})`, _lt("The number to whose multiples number will be rounded.")),
],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (number, significance = DEFAULT_SIGNIFICANCE) {
return CEILING_MATH.compute(number, significance, 0);
},
@@ -10172,7 +10278,7 @@
arg("divisor (number)", _lt("The number to divide by.")),
],
returns: ["NUMBER"],
- computeFormat: (dividend) => dividend === null || dividend === void 0 ? void 0 : dividend.format,
+ computeFormat: (dividend) => dividend?.format,
compute: function (dividend, divisor) {
const _divisor = toNumber(divisor);
assert(() => _divisor !== 0, _lt("The divisor must be different from 0."));
@@ -10193,7 +10299,7 @@
description: _lt(`Rounds a number up to the nearest odd integer.`),
args: [arg("value (number)", _lt("The value to round to the next greatest odd number."))],
returns: ["NUMBER"],
- computeFormat: (number) => number === null || number === void 0 ? void 0 : number.format,
+ computeFormat: (number) => number?.format,
compute: function (value) {
const _value = toNumber(value);
let temp = Math.ceil(Math.abs(_value));
@@ -10224,7 +10330,7 @@
arg("exponent (number)", _lt("The exponent to raise base to.")),
],
returns: ["NUMBER"],
- computeFormat: (base) => base === null || base === void 0 ? void 0 : base.format,
+ computeFormat: (base) => base?.format,
compute: function (base, exponent) {
const _base = toNumber(base);
const _exponent = toNumber(exponent);
@@ -10244,8 +10350,7 @@
],
returns: ["NUMBER"],
computeFormat: (factor1) => {
- var _a;
- return Array.isArray(factor1) ? (_a = factor1[0][0]) === null || _a === void 0 ? void 0 : _a.format : factor1 === null || factor1 === void 0 ? void 0 : factor1.format;
+ return Array.isArray(factor1) ? factor1[0][0]?.format : factor1?.format;
},
compute: function (...factors) {
let count = 0;
@@ -10295,7 +10400,7 @@
arg("high (number)", _lt("The high end of the random range.")),
],
returns: ["NUMBER"],
- computeFormat: (low) => low === null || low === void 0 ? void 0 : low.format,
+ computeFormat: (low) => low?.format,
compute: function (low, high) {
let _low = toNumber(low);
if (!Number.isInteger(_low)) {
@@ -10320,7 +10425,7 @@
arg(`places (number, default=${DEFAULT_PLACES})`, _lt("The number of decimal places to which to round.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, places = DEFAULT_PLACES) {
const _value = toNumber(value);
let _places = toNumber(places);
@@ -10349,7 +10454,7 @@
arg(`places (number, default=${DEFAULT_PLACES})`, _lt("The number of decimal places to which to round.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, places = DEFAULT_PLACES) {
const _value = toNumber(value);
let _places = toNumber(places);
@@ -10378,7 +10483,7 @@
arg(`places (number, default=${DEFAULT_PLACES})`, _lt("The number of decimal places to which to round.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, places = DEFAULT_PLACES) {
const _value = toNumber(value);
let _places = toNumber(places);
@@ -10452,7 +10557,7 @@
description: _lt("Positive square root of a positive number."),
args: [arg("value (number)", _lt("The number for which to calculate the positive square root."))],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value) {
const _value = toNumber(value);
assert(() => _value >= 0, _lt("The value (%s) must be positive or null.", _value.toString()));
@@ -10471,8 +10576,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
return reduceNumbers(values, (acc, a) => acc + a, 0);
@@ -10564,7 +10668,7 @@
arg(`places (number, default=${DEFAULT_PLACES})`, _lt("The number of significant digits to the right of the decimal point to retain.")),
],
returns: ["NUMBER"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value, places = DEFAULT_PLACES) {
const _value = toNumber(value);
let _places = toNumber(places);
@@ -10744,8 +10848,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
let count = 0;
@@ -10773,8 +10876,7 @@
],
returns: ["NUMBER"],
computeFormat: (values) => {
- var _a;
- return Array.isArray(values) ? (_a = values[0][0]) === null || _a === void 0 ? void 0 : _a.format : values === null || values === void 0 ? void 0 : values.format;
+ return Array.isArray(values) ? values[0][0]?.format : values?.format;
},
compute: function (...values) {
let sum = 0;
@@ -10832,8 +10934,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
let count = 0;
@@ -11006,8 +11107,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, n) {
const _n = Math.trunc(toNumber(n));
@@ -11043,8 +11143,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity);
@@ -11063,8 +11162,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
const maxa = reduceNumbersTextAs0(values, (acc, a) => {
@@ -11110,8 +11208,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
let data = [];
@@ -11133,8 +11230,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity);
@@ -11153,8 +11249,7 @@
],
returns: ["NUMBER"],
computeFormat: (value1) => {
- var _a;
- return Array.isArray(value1) ? (_a = value1[0][0]) === null || _a === void 0 ? void 0 : _a.format : value1 === null || value1 === void 0 ? void 0 : value1.format;
+ return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
},
compute: function (...values) {
const mina = reduceNumbersTextAs0(values, (acc, a) => {
@@ -11200,8 +11295,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, percentile) {
return PERCENTILE_INC.compute(data, percentile);
@@ -11219,8 +11313,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, percentile) {
return centile([data], percentile, false);
@@ -11238,8 +11331,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, percentile) {
return centile([data], percentile, true);
@@ -11257,8 +11349,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, quartileNumber) {
return QUARTILE_INC.compute(data, quartileNumber);
@@ -11276,8 +11367,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, quartileNumber) {
const _quartileNumber = Math.trunc(toNumber(quartileNumber));
@@ -11296,8 +11386,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, quartileNumber) {
const _quartileNumber = Math.trunc(toNumber(quartileNumber));
@@ -11316,8 +11405,7 @@
],
returns: ["NUMBER"],
computeFormat: (data) => {
- var _a;
- return Array.isArray(data) ? (_a = data[0][0]) === null || _a === void 0 ? void 0 : _a.format : data === null || data === void 0 ? void 0 : data.format;
+ return Array.isArray(data) ? data[0][0]?.format : data?.format;
},
compute: function (data, n) {
const _n = Math.trunc(toNumber(n));
@@ -11567,7 +11655,6 @@
function getMatchingCells(database, field, criteria) {
// Example
- var _a;
// # DATABASE # CRITERIA # field = "C"
//
// | A | B | C | | A | C |
@@ -11598,7 +11685,7 @@
}
else {
const colName = toString(field).toUpperCase();
- index = (_a = indexColNameDB.get(colName)) !== null && _a !== void 0 ? _a : -1;
+ index = indexColNameDB.get(colName) ?? -1;
if (index === -1) {
throw new Error(_lt("The field (%s) must be one of %s.", toString(field), [...indexColNameDB.keys()].toString()));
}
@@ -14738,7 +14825,7 @@
return false;
}
catch (e) {
- return (e === null || e === void 0 ? void 0 : e.errorType) != CellErrorType.NotAvailable;
+ return e?.errorType != CellErrorType.NotAvailable;
}
},
isExported: true,
@@ -14791,7 +14878,7 @@
return false;
}
catch (e) {
- return (e === null || e === void 0 ? void 0 : e.errorType) == CellErrorType.NotAvailable;
+ return e?.errorType == CellErrorType.NotAvailable;
}
},
isExported: true,
@@ -14944,12 +15031,11 @@
],
returns: ["ANY"],
computeFormat: (value, valueIfError = () => ({ value: "" })) => {
- var _a;
try {
return value().format;
}
catch (e) {
- return (_a = valueIfError()) === null || _a === void 0 ? void 0 : _a.format;
+ return valueIfError()?.format;
}
},
compute: function (value, valueIfError = () => "") {
@@ -15148,8 +15234,7 @@
],
returns: ["NUMBER"],
compute: function (cellReference) {
- var _a;
- const _cellReference = cellReference || ((_a = this.__originCellXC) === null || _a === void 0 ? void 0 : _a.call(this));
+ const _cellReference = cellReference || this.__originCellXC?.();
assert(() => !!_cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
const zone = toZone(_cellReference);
return zone.left + 1;
@@ -15284,8 +15369,7 @@
],
returns: ["NUMBER"],
compute: function (cellReference) {
- var _a;
- cellReference = cellReference || ((_a = this.__originCellXC) === null || _a === void 0 ? void 0 : _a.call(this));
+ cellReference = cellReference || this.__originCellXC?.();
assert(() => !!cellReference, "In this context, the function [[FUNCTION_NAME]] needs to have a cell or range in parameter.");
const zone = toZone(cellReference);
return zone.top + 1;
@@ -15378,7 +15462,7 @@
if (index !== -1) {
return (lookupRange.length === 1 ? returnRange[0][index] : returnRange[index][0]);
}
- const _defaultValue = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue();
+ const _defaultValue = defaultValue?.();
assertAvailable(_defaultValue, searchKey);
return _defaultValue;
},
@@ -15409,7 +15493,7 @@
arg("value2 (number)", _lt("The second addend.")),
],
returns: ["NUMBER"],
- computeFormat: (value1, value2) => (value1 === null || value1 === void 0 ? void 0 : value1.format) || (value2 === null || value2 === void 0 ? void 0 : value2.format),
+ computeFormat: (value1, value2) => value1?.format || value2?.format,
compute: function (value1, value2) {
return toNumber(value1) + toNumber(value2);
},
@@ -15439,7 +15523,7 @@
arg("divisor (number)", _lt("The number to divide by.")),
],
returns: ["NUMBER"],
- computeFormat: (dividend, divisor) => (dividend === null || dividend === void 0 ? void 0 : dividend.format) || (divisor === null || divisor === void 0 ? void 0 : divisor.format),
+ computeFormat: (dividend, divisor) => dividend?.format || divisor?.format,
compute: function (dividend, divisor) {
const _divisor = toNumber(divisor);
assert(() => _divisor !== 0, _lt("The divisor must be different from zero."));
@@ -15561,7 +15645,7 @@
arg("value2 (number)", _lt("The subtrahend, or number to subtract from value1.")),
],
returns: ["NUMBER"],
- computeFormat: (value1, value2) => (value1 === null || value1 === void 0 ? void 0 : value1.format) || (value2 === null || value2 === void 0 ? void 0 : value2.format),
+ computeFormat: (value1, value2) => value1?.format || value2?.format,
compute: function (value1, value2) {
return toNumber(value1) - toNumber(value2);
},
@@ -15576,7 +15660,7 @@
arg("factor2 (number)", _lt("The second multiplicand.")),
],
returns: ["NUMBER"],
- computeFormat: (factor1, factor2) => (factor1 === null || factor1 === void 0 ? void 0 : factor1.format) || (factor2 === null || factor2 === void 0 ? void 0 : factor2.format),
+ computeFormat: (factor1, factor2) => factor1?.format || factor2?.format,
compute: function (factor1, factor2) {
return toNumber(factor1) * toNumber(factor2);
},
@@ -15604,7 +15688,7 @@
arg("base (number)", _lt("The number to raise to the exponent power.")),
arg("exponent (number)", _lt("The exponent to raise base to.")),
],
- returns: ["BOOLEAN"],
+ returns: ["NUMBER"],
compute: function (base, exponent) {
return POWER.compute(base, exponent);
},
@@ -15617,7 +15701,7 @@
args: [
arg("value (number)", _lt("The number to have its sign reversed. Equivalently, the number to multiply by -1.")),
],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
returns: ["NUMBER"],
compute: function (value) {
return -toNumber(value);
@@ -15641,7 +15725,7 @@
description: _lt(`A specified number, unchanged.`),
args: [arg("value (any)", _lt("The number to return."))],
returns: ["ANY"],
- computeFormat: (value) => value === null || value === void 0 ? void 0 : value.format,
+ computeFormat: (value) => value?.format,
compute: function (value) {
return value === null ? "" : value;
},
@@ -16077,13 +16161,10 @@
// Function registry
//------------------------------------------------------------------------------
class FunctionRegistry extends Registry {
- constructor() {
- super(...arguments);
- this.mapping = {};
- }
+ mapping = {};
add(name, addDescr) {
name = name.toUpperCase();
- if (!name.match(functionNameRegex)) {
+ if (!functionNameRegex.test(name)) {
throw new Error(_lt("Invalid function name %s. Function names can exclusively contain alphanumerical values separated by dots (.) or underscore (_)", name));
}
const descr = addMetaInfoFromArg(addDescr);
@@ -16114,9 +16195,9 @@
}
function _extractArgValuesFromArgs(arg) {
if (Array.isArray(arg)) {
- return arg.map((col) => col.map((simpleArg) => simpleArg === null || simpleArg === void 0 ? void 0 : simpleArg.value));
+ return arg.map((col) => col.map((simpleArg) => simpleArg?.value));
}
- return arg === null || arg === void 0 ? void 0 : arg.value;
+ return arg?.value;
}
const functionRegistry = new FunctionRegistry();
for (let category of categories) {
@@ -16129,6 +16210,11 @@
}
}
+ const insertRow = {
+ name: MENU_INSERT_ROWS_NAME,
+ isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.INSERT_ROW",
+ };
const rowInsertRowBefore = {
name: ROW_INSERT_ROWS_BEFORE_NAME,
execute: INSERT_ROWS_BEFORE_ACTION,
@@ -16142,6 +16228,7 @@
...rowInsertRowBefore,
name: CELL_INSERT_ROWS_BEFORE_NAME,
isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.INSERT_ROW",
};
const rowInsertRowsAfter = {
execute: INSERT_ROWS_AFTER_ACTION,
@@ -16152,6 +16239,11 @@
name: MENU_INSERT_ROWS_AFTER_NAME,
isVisible: (env) => env.model.getters.getActiveCols().size === 0,
};
+ const insertCol = {
+ name: MENU_INSERT_COLUMNS_NAME,
+ isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.INSERT_COL",
+ };
const colInsertColsBefore = {
name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,
execute: INSERT_COLUMNS_BEFORE_ACTION,
@@ -16165,6 +16257,7 @@
...colInsertColsBefore,
name: CELL_INSERT_COLUMNS_BEFORE_NAME,
isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.INSERT_COL",
};
const colInsertColsAfter = {
name: COLUMN_INSERT_COLUMNS_AFTER_NAME,
@@ -16179,6 +16272,7 @@
const insertCell = {
name: _lt("Insert cells"),
isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.INSERT_CELL",
};
const insertCellShiftDown = {
name: _lt("Insert cells and shift down"),
@@ -16191,14 +16285,18 @@
const insertChart = {
name: _lt("Chart"),
execute: CREATE_CHART,
+ icon: "o-spreadsheet-Icon.INSERT_CHART",
};
const insertImage = {
name: _lt("Image"),
+ description: "Ctrl+O",
execute: CREATE_IMAGE,
isVisible: (env) => env.imageProvider !== undefined,
+ icon: "o-spreadsheet-Icon.INSERT_IMAGE",
};
const insertFunction = {
name: _lt("Function"),
+ icon: "o-spreadsheet-Icon.SHOW_HIDE_FORMULA",
};
const insertFunctionSum = {
name: _lt("SUM"),
@@ -16242,10 +16340,12 @@
const insertLink = {
name: _lt("Link"),
execute: INSERT_LINK,
+ icon: "o-spreadsheet-Icon.INSERT_LINK",
};
const insertSheet = {
- name: _lt("New sheet"),
+ name: _lt("Insert sheet"),
execute: CREATE_SHEET_ACTION,
+ icon: "o-spreadsheet-Icon.INSERT_SHEET",
};
function createFormulaFunctions(fnNames) {
return fnNames.sort().map((fnName, i) => {
@@ -16313,24 +16413,30 @@
.add("delete_row", {
...deleteRow,
sequence: 110,
+ icon: "o-spreadsheet-Icon.DELETE",
})
.add("delete_column", {
...deleteCol,
sequence: 120,
+ icon: "o-spreadsheet-Icon.DELETE",
})
.add("delete_cell", {
...deleteCells,
sequence: 130,
+ separator: true,
+ icon: "o-spreadsheet-Icon.DELETE",
})
.addChild("delete_cell_up", ["delete_cell"], {
...deleteCellShiftUp,
name: _lt("Shift up"),
sequence: 10,
+ icon: "o-spreadsheet-Icon.DELETE_CELL_SHIFT_UP",
})
.addChild("delete_cell_left", ["delete_cell"], {
...deleteCellShiftLeft,
name: _lt("Shift left"),
sequence: 20,
+ icon: "o-spreadsheet-Icon.DELETE_CELL_SHIFT_LEFT",
})
.add("insert_link", {
...insertLink,
@@ -16342,27 +16448,36 @@
const sortRange = {
name: _lt("Sort range"),
isVisible: IS_ONLY_ONE_RANGE,
+ icon: "o-spreadsheet-Icon.SORT_RANGE",
};
const sortAscending = {
name: _lt("Ascending (A ⟶ Z)"),
execute: SORT_CELLS_ASCENDING,
+ icon: "o-spreadsheet-Icon.SORT_ASCENDING",
};
const sortDescending = {
name: _lt("Descending (Z ⟶ A)"),
execute: SORT_CELLS_DESCENDING,
+ icon: "o-spreadsheet-Icon.SORT_DESCENDING",
};
const addDataFilter = {
name: _lt("Create filter"),
execute: FILTERS_CREATE_FILTER_TABLE,
isVisible: (env) => !SELECTION_CONTAINS_FILTER(env),
isEnabled: (env) => SELECTION_IS_CONTINUOUS(env),
- icon: "o-spreadsheet-Icon.FILTER_ICON_INACTIVE",
+ icon: "o-spreadsheet-Icon.MENU_FILTER_ICON",
};
const removeDataFilter = {
name: _lt("Remove filter"),
execute: FILTERS_REMOVE_FILTER_TABLE,
isVisible: SELECTION_CONTAINS_FILTER,
- icon: "o-spreadsheet-Icon.FILTER_ICON_INACTIVE",
+ icon: "o-spreadsheet-Icon.MENU_FILTER_ICON",
+ };
+ const splitToColumns = {
+ name: _lt("Split text to columns"),
+ sequence: 1,
+ execute: (env) => env.openSidePanel("SplitToColumns", {}),
+ isEnabled: (env) => env.model.getters.isSingleColSelected(),
};
const formatNumberAutomatic = {
@@ -16476,6 +16591,7 @@
const formatFontSize = {
name: _lt("Font size"),
children: fontSizeMenuBuilder(),
+ icon: "o-spreadsheet-Icon.FONT_SIZE",
};
const formatAlignment = {
name: _lt("Alignment"),
@@ -16487,18 +16603,21 @@
};
const formatAlignmentLeft = {
name: _lt("Left"),
+ description: "Ctrl+Shift+L",
execute: (env) => setStyle(env, { align: "left" }),
isActive: (env) => getHorizontalAlign(env) === "left",
icon: "o-spreadsheet-Icon.ALIGN_LEFT",
};
const formatAlignmentCenter = {
name: _lt("Center"),
+ description: "Ctrl+Shift+E",
execute: (env) => setStyle(env, { align: "center" }),
isActive: (env) => getHorizontalAlign(env) === "center",
icon: "o-spreadsheet-Icon.ALIGN_CENTER",
};
const formatAlignmentRight = {
name: _lt("Right"),
+ description: "Ctrl+Shift+R",
execute: (env) => setStyle(env, { align: "right" }),
isActive: (env) => getHorizontalAlign(env) === "right",
icon: "o-spreadsheet-Icon.ALIGN_RIGHT",
@@ -16558,6 +16677,7 @@
const formatCF = {
name: _lt("Conditional formatting"),
execute: OPEN_CF_SIDEPANEL_ACTION,
+ icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
};
const paintFormat = {
name: _lt("Paint Format"),
@@ -16569,6 +16689,7 @@
};
const clearFormat = {
name: _lt("Clear formatting"),
+ description: "Ctrl+<",
execute: FORMAT_CLEARFORMAT_ACTION,
icon: "o-spreadsheet-Icon.CLEAR_FORMAT",
};
@@ -16728,12 +16849,7 @@
const hideCols = {
name: HIDE_COLUMNS_NAME,
execute: HIDE_COLUMNS_ACTION,
- isVisible: (env) => {
- const sheetId = env.model.getters.getActiveSheetId();
- const hiddenCols = env.model.getters.getHiddenColsGroups(sheetId).flat();
- return (env.model.getters.getNumberCols(sheetId) >
- hiddenCols.length + env.model.getters.getElementsFromSelection("COL").length);
- },
+ isVisible: NOT_ALL_VISIBLE_COLS_SELECTED,
};
const unhideCols = {
name: _lt("Unhide columns"),
@@ -16754,12 +16870,7 @@
const hideRows = {
name: HIDE_ROWS_NAME,
execute: HIDE_ROWS_ACTION,
- isVisible: (env) => {
- const sheetId = env.model.getters.getActiveSheetId();
- const hiddenRows = env.model.getters.getHiddenRowsGroups(sheetId).flat();
- return (env.model.getters.getNumberRows(sheetId) >
- hiddenRows.length + env.model.getters.getElementsFromSelection("ROW").length);
- },
+ isVisible: NOT_ALL_VISIBLE_ROWS_SELECTED,
};
const unhideRows = {
name: _lt("Unhide rows"),
@@ -16789,6 +16900,7 @@
};
const freezePane = {
name: _lt("Freeze"),
+ icon: "o-spreadsheet-Icon.FREEZE",
};
const unFreezeRows = {
name: _lt("No rows"),
@@ -16847,11 +16959,13 @@
? _lt("Hide gridlines")
: _lt("Show gridlines"),
execute: SET_GRID_LINES_VISIBILITY_ACTION,
+ icon: "o-spreadsheet-Icon.SHOW_HIDE_GRID",
};
const viewFormulas = {
name: (env) => env.model.getters.shouldShowFormulas() ? _lt("Hide formulas") : _lt("Show formulas"),
execute: SET_FORMULA_VISIBILITY_ACTION,
isReadonlyAllowed: true,
+ icon: "o-spreadsheet-Icon.SHOW_HIDE_FORMULA",
};
const createRemoveFilter = {
name: (env) => selectionContainsFilter(env) ? _lt("Remove selected filters") : _lt("Create filter"),
@@ -17175,33 +17289,38 @@
sequence: 65,
separator: true,
})
- .addChild("edit_delete_cell_values", ["edit"], {
- ...deleteValues,
+ .addChild("delete", ["edit"], {
+ name: _lt("Delete"),
+ icon: "o-spreadsheet-Icon.DELETE",
sequence: 70,
})
- .addChild("edit_delete_row", ["edit"], {
+ .addChild("edit_delete_cell_values", ["edit", "delete"], {
+ ...deleteValues,
+ sequence: 10,
+ })
+ .addChild("edit_delete_row", ["edit", "delete"], {
...deleteRows,
- sequence: 80,
+ sequence: 20,
})
- .addChild("edit_delete_column", ["edit"], {
+ .addChild("edit_delete_column", ["edit", "delete"], {
...deleteCols,
- sequence: 90,
+ sequence: 30,
})
- .addChild("edit_delete_cell_shift_up", ["edit"], {
+ .addChild("edit_delete_cell_shift_up", ["edit", "delete"], {
...deleteCellShiftUp,
- sequence: 93,
+ sequence: 40,
})
- .addChild("edit_delete_cell_shift_left", ["edit"], {
+ .addChild("edit_delete_cell_shift_left", ["edit", "delete"], {
...deleteCellShiftLeft,
- sequence: 97,
+ sequence: 50,
})
.addChild("edit_unhide_columns", ["edit"], {
...unhideAllCols,
- sequence: 100,
+ sequence: 80,
})
.addChild("edit_unhide_rows", ["edit"], {
...unhideAllRows,
- sequence: 100,
+ sequence: 80,
})
// ---------------------------------------------------------------------
// VIEW MENU ITEMS
@@ -17267,31 +17386,47 @@
name: _lt("Insert"),
sequence: 40,
})
- .addChild("insert_row_before", ["insert"], {
+ .addChild("insert_row", ["insert"], {
+ ...insertRow,
+ sequence: 10,
+ })
+ .addChild("insert_row_before", ["insert", "insert_row"], {
...topBarInsertRowsBefore,
sequence: 10,
})
- .addChild("insert_row_after", ["insert"], {
+ .addChild("insert_row_after", ["insert", "insert_row"], {
...topBarInsertRowsAfter,
sequence: 20,
- separator: true,
})
- .addChild("insert_column_before", ["insert"], {
+ .addChild("insert_column", ["insert"], {
+ ...insertCol,
+ sequence: 20,
+ })
+ .addChild("insert_column_before", ["insert", "insert_column"], {
...topBarInsertColsBefore,
- sequence: 30,
+ sequence: 10,
})
- .addChild("insert_column_after", ["insert"], {
+ .addChild("insert_column_after", ["insert", "insert_column"], {
...topBarInsertColsAfter,
- sequence: 40,
- separator: true,
+ sequence: 20,
})
- .addChild("insert_insert_cell_shift_down", ["insert"], {
- ...insertCellShiftDown,
+ .addChild("insert_cell", ["insert"], {
+ ...insertCell,
sequence: 43,
})
- .addChild("insert_insert_cell_shift_right", ["insert"], {
+ .addChild("insert_cell_down", ["insert", "insert_cell"], {
+ ...insertCellShiftDown,
+ name: _lt("Shift down"),
+ sequence: 10,
+ })
+ .addChild("insert_cell_right", ["insert", "insert_cell"], {
...insertCellShiftRight,
- sequence: 47,
+ name: _lt("Shift right"),
+ sequence: 20,
+ })
+ .addChild("insert_sheet", ["insert"], {
+ ...insertSheet,
+ sequence: 80,
separator: true,
})
.addChild("insert_chart", ["insert"], {
@@ -17336,11 +17471,6 @@
...insertLink,
separator: true,
sequence: 70,
- })
- .addChild("insert_sheet", ["insert"], {
- ...insertSheet,
- sequence: 80,
- separator: true,
})
// ---------------------------------------------------------------------
// FORMAT MENU ITEMS
@@ -17450,6 +17580,11 @@
.addChild("sort_descending", ["data", "sort_range"], {
...sortDescending,
sequence: 20,
+ })
+ .addChild("split_to_columns", ["data"], {
+ ...splitToColumns,
+ sequence: 10,
+ separator: true,
})
.addChild("add_data_filter", ["data"], {
...addDataFilter,
@@ -17556,17 +17691,15 @@
* changes.
*/
class SelectionInput extends owl.Component {
- constructor() {
- super(...arguments);
- this.id = uuidGenerator$1.uuidv4();
- this.previousRanges = this.props.ranges || [];
- this.originSheet = this.env.model.getters.getActiveSheetId();
- this.state = owl.useState({
- isMissing: false,
- mode: "select-range",
- });
- this.focusedInput = owl.useRef("focusedInput");
- }
+ static template = "o-spreadsheet-SelectionInput";
+ id = uuidGenerator$1.uuidv4();
+ previousRanges = this.props.ranges || [];
+ originSheet = this.env.model.getters.getActiveSheetId();
+ state = owl.useState({
+ isMissing: false,
+ mode: "select-range",
+ });
+ focusedInput = owl.useRef("focusedInput");
get ranges() {
const existingSelectionRange = this.env.model.getters.getSelectionInput(this.id);
const ranges = existingSelectionRange.length
@@ -17593,7 +17726,7 @@
return this.props.isInvalid || this.state.isMissing;
}
setup() {
- owl.useEffect(() => { var _a; return (_a = this.focusedInput.el) === null || _a === void 0 ? void 0 : _a.focus(); }, () => [this.focusedInput.el]);
+ owl.useEffect(() => this.focusedInput.el?.focus(), () => [this.focusedInput.el]);
owl.onMounted(() => this.enableNewSelectionInput());
owl.onWillUnmount(async () => this.disableNewSelectionInput());
owl.onPatched(() => this.checkChange());
@@ -17619,9 +17752,8 @@
return "color: " + color + ";";
}
triggerChange() {
- var _a, _b;
const ranges = this.env.model.getters.getSelectionInputValue(this.id);
- (_b = (_a = this.props).onSelectionChanged) === null || _b === void 0 ? void 0 : _b.call(_a, ranges);
+ this.props.onSelectionChanged?.(ranges);
this.previousRanges = ranges;
}
onKeydown(ev) {
@@ -17655,10 +17787,9 @@
this.env.model.dispatch("ADD_EMPTY_RANGE", { id: this.id });
}
removeInput(rangeId) {
- var _a, _b;
this.env.model.dispatch("REMOVE_RANGE", { id: this.id, rangeId });
this.triggerChange();
- (_b = (_a = this.props).onSelectionConfirmed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onSelectionConfirmed?.();
}
onInputChanged(rangeId, ev) {
const target = ev.target;
@@ -17670,7 +17801,6 @@
this.triggerChange();
}
confirm() {
- var _a, _b;
this.env.model.dispatch("UNFOCUS_SELECTION_INPUT");
const ranges = this.env.model.getters.getSelectionInputValue(this.id);
if (this.props.required && ranges.length === 0) {
@@ -17683,10 +17813,9 @@
sheetIdTo: this.originSheet,
});
}
- (_b = (_a = this.props).onSelectionConfirmed) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onSelectionConfirmed?.();
}
}
- SelectionInput.template = "o-spreadsheet-SelectionInput";
SelectionInput.props = {
ranges: Array,
hasSingleRange: { type: Boolean, optional: true },
@@ -17698,33 +17827,30 @@
};
class LineBarPieConfigPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- datasetDispatchResult: undefined,
- labelsDispatchResult: undefined,
- });
- this.dataSeriesRanges = [];
- }
+ static template = "o-spreadsheet-LineBarPieConfigPanel";
+ static components = { SelectionInput };
+ state = owl.useState({
+ datasetDispatchResult: undefined,
+ labelsDispatchResult: undefined,
+ });
+ dataSeriesRanges = [];
+ labelRange;
setup() {
this.dataSeriesRanges = this.props.definition.dataSets;
this.labelRange = this.props.definition.labelRange;
}
get errorMessages() {
- var _a, _b;
const cancelledReasons = [
- ...(((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || []),
- ...(((_b = this.state.labelsDispatchResult) === null || _b === void 0 ? void 0 : _b.reasons) || []),
+ ...(this.state.datasetDispatchResult?.reasons || []),
+ ...(this.state.labelsDispatchResult?.reasons || []),
];
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
}
get isDatasetInvalid() {
- var _a;
- return !!((_a = this.state.datasetDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(32 /* CommandResult.InvalidDataSet */));
+ return !!this.state.datasetDispatchResult?.isCancelledBecause(32 /* CommandResult.InvalidDataSet */);
}
get isLabelInvalid() {
- var _a;
- return !!((_a = this.state.labelsDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(33 /* CommandResult.InvalidLabelRange */));
+ return !!this.state.labelsDispatchResult?.isCancelledBecause(33 /* CommandResult.InvalidLabelRange */);
}
onUpdateDataSetsHaveTitle(ev) {
this.props.updateChart({
@@ -17777,8 +17903,6 @@
return undefined;
}
}
- LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
- LineBarPieConfigPanel.components = { SelectionInput };
LineBarPieConfigPanel.props = {
figureId: String,
definition: Object,
@@ -17786,6 +17910,7 @@
};
class BarConfigPanel extends LineBarPieConfigPanel {
+ static template = "o-spreadsheet-BarConfigPanel";
onUpdateStacked(ev) {
this.props.updateChart({
stacked: ev.target.checked,
@@ -17797,7 +17922,6 @@
});
}
}
- BarConfigPanel.template = "o-spreadsheet-BarConfigPanel";
const PICKER_PADDING = 6;
const LINE_VERTICAL_PADDING = 1;
@@ -17944,21 +18068,23 @@
}));
}
class ColorPicker extends owl.Component {
- constructor() {
- super(...arguments);
- this.COLORS = COLOR_PICKER_DEFAULTS;
- this.state = owl.useState({
- showGradient: false,
- currentColor: isColorValid(this.props.currentColor) ? this.props.currentColor : "",
- isCurrentColorInvalid: false,
- style: {
- display: "none",
- background: "#ffffff",
- left: "0",
- top: "0",
- },
- });
- }
+ static template = "o-spreadsheet-ColorPicker";
+ static defaultProps = {
+ currentColor: "", //TODO Change it to false instead of empty string
+ };
+ static components = { Popover };
+ COLORS = COLOR_PICKER_DEFAULTS;
+ state = owl.useState({
+ showGradient: false,
+ currentColor: isColorValid(this.props.currentColor) ? this.props.currentColor : "",
+ isCurrentColorInvalid: false,
+ style: {
+ display: "none",
+ background: "#ffffff",
+ left: "0",
+ top: "0",
+ },
+ });
get colorPickerStyle() {
if (this.props.maxHeight !== undefined && this.props.maxHeight <= 0) {
return cssPropertiesToCss({ display: "none" });
@@ -18027,11 +18153,6 @@
return isSameColor(color1, color2);
}
}
- ColorPicker.template = "o-spreadsheet-ColorPicker";
- ColorPicker.defaultProps = {
- currentColor: "", //TODO Change it to false instead of empty string
- };
- ColorPicker.components = { Popover };
ColorPicker.props = {
onColorPicked: Function,
currentColor: { type: String, optional: true },
@@ -18074,10 +18195,9 @@
}
`;
class ColorPickerWidget extends owl.Component {
- constructor() {
- super(...arguments);
- this.colorPickerButtonRef = owl.useRef("colorPickerButton");
- }
+ static template = "o-spreadsheet-ColorPickerWidget";
+ static components = { ColorPicker };
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
get iconStyle() {
return this.props.currentColor
? `border-color: ${this.props.currentColor}`
@@ -18094,8 +18214,6 @@
};
}
}
- ColorPickerWidget.template = "o-spreadsheet-ColorPickerWidget";
- ColorPickerWidget.components = { ColorPicker };
ColorPickerWidget.props = {
currentColor: { type: String, optional: true },
toggleColorPicker: Function,
@@ -18109,12 +18227,11 @@
};
class LineBarPieDesignPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- fillColorTool: false,
- });
- }
+ static template = "o-spreadsheet-LineBarPieDesignPanel";
+ static components = { ColorPickerWidget };
+ state = owl.useState({
+ fillColorTool: false,
+ });
onClick(ev) {
this.state.fillColorTool = false;
}
@@ -18140,8 +18257,6 @@
});
}
}
- LineBarPieDesignPanel.template = "o-spreadsheet-LineBarPieDesignPanel";
- LineBarPieDesignPanel.components = { ColorPickerWidget };
LineBarPieDesignPanel.props = {
figureId: String,
definition: Object,
@@ -18149,25 +18264,22 @@
};
class BarChartDesignPanel extends LineBarPieDesignPanel {
+ static template = "o-spreadsheet-BarChartDesignPanel";
}
- BarChartDesignPanel.template = "o-spreadsheet-BarChartDesignPanel";
class GaugeChartConfigPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- dataRangeDispatchResult: undefined,
- });
- this.dataRange = this.props.definition.dataRange;
- }
+ static template = "o-spreadsheet-GaugeChartConfigPanel";
+ static components = { SelectionInput };
+ state = owl.useState({
+ dataRangeDispatchResult: undefined,
+ });
+ dataRange = this.props.definition.dataRange;
get configurationErrorMessages() {
- var _a;
- const cancelledReasons = [...(((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || [])];
+ const cancelledReasons = [...(this.state.dataRangeDispatchResult?.reasons || [])];
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
}
get isDataRangeInvalid() {
- var _a;
- return !!((_a = this.state.dataRangeDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(36 /* CommandResult.InvalidGaugeDataRange */));
+ return !!this.state.dataRangeDispatchResult?.isCancelledBecause(36 /* CommandResult.InvalidGaugeDataRange */);
}
onDataRangeChanged(ranges) {
this.dataRange = ranges[0];
@@ -18178,8 +18290,6 @@
});
}
}
- GaugeChartConfigPanel.template = "o-spreadsheet-GaugeChartConfigPanel";
- GaugeChartConfigPanel.components = { SelectionInput };
GaugeChartConfigPanel.props = {
figureId: String,
definition: Object,
@@ -18218,19 +18328,17 @@
}
`;
class GaugeChartDesignPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- openedMenu: undefined,
- sectionRuleDispatchResult: undefined,
- });
- }
+ static template = "o-spreadsheet-GaugeChartDesignPanel";
+ static components = { ColorPickerWidget };
+ state = owl.useState({
+ openedMenu: undefined,
+ sectionRuleDispatchResult: undefined,
+ });
setup() {
owl.useExternalListener(window, "click", this.closeMenus);
}
get designErrorMessages() {
- var _a;
- const cancelledReasons = [...(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || [])];
+ const cancelledReasons = [...(this.state.sectionRuleDispatchResult?.reasons || [])];
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
}
updateBackgroundColor(color) {
@@ -18245,29 +18353,25 @@
});
}
isRangeMinInvalid() {
- var _a, _b, _c;
- return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(37 /* CommandResult.EmptyGaugeRangeMin */)) ||
- ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(38 /* CommandResult.GaugeRangeMinNaN */)) ||
- ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */)));
+ return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause(37 /* CommandResult.EmptyGaugeRangeMin */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(38 /* CommandResult.GaugeRangeMinNaN */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */));
}
isRangeMaxInvalid() {
- var _a, _b, _c;
- return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(39 /* CommandResult.EmptyGaugeRangeMax */)) ||
- ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(40 /* CommandResult.GaugeRangeMaxNaN */)) ||
- ((_c = this.state.sectionRuleDispatchResult) === null || _c === void 0 ? void 0 : _c.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */)));
+ return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause(39 /* CommandResult.EmptyGaugeRangeMax */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(40 /* CommandResult.GaugeRangeMaxNaN */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(41 /* CommandResult.GaugeRangeMinBiggerThanRangeMax */));
}
// ---------------------------------------------------------------------------
// COLOR_SECTION_TEMPLATE
// ---------------------------------------------------------------------------
get isLowerInflectionPointInvalid() {
- var _a, _b;
- return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(42 /* CommandResult.GaugeLowerInflectionPointNaN */)) ||
- ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */)));
+ return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause(42 /* CommandResult.GaugeLowerInflectionPointNaN */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */));
}
get isUpperInflectionPointInvalid() {
- var _a, _b;
- return !!(((_a = this.state.sectionRuleDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(43 /* CommandResult.GaugeUpperInflectionPointNaN */)) ||
- ((_b = this.state.sectionRuleDispatchResult) === null || _b === void 0 ? void 0 : _b.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */)));
+ return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause(43 /* CommandResult.GaugeUpperInflectionPointNaN */) ||
+ this.state.sectionRuleDispatchResult?.isCancelledBecause(44 /* CommandResult.GaugeLowerBiggerThanUpper */));
}
updateInflectionPointValue(attr, ev) {
const sectionRule = deepCopy(this.props.definition.sectionRule);
@@ -18317,8 +18421,6 @@
this.state.openedMenu = undefined;
}
}
- GaugeChartDesignPanel.template = "o-spreadsheet-GaugeChartDesignPanel";
- GaugeChartDesignPanel.components = { ColorPickerWidget };
GaugeChartDesignPanel.props = {
figureId: String,
definition: Object,
@@ -18326,6 +18428,7 @@
};
class LineConfigPanel extends LineBarPieConfigPanel {
+ static template = "o-spreadsheet-LineConfigPanel";
get canTreatLabelsAsText() {
const chart = this.env.model.getters.getChart(this.props.figureId);
if (chart && chart instanceof LineChart) {
@@ -18349,37 +18452,32 @@
});
}
}
- LineConfigPanel.template = "o-spreadsheet-LineConfigPanel";
class LineChartDesignPanel extends LineBarPieDesignPanel {
+ static template = "o-spreadsheet-LineChartDesignPanel";
}
- LineChartDesignPanel.template = "o-spreadsheet-LineChartDesignPanel";
class ScorecardChartConfigPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- keyValueDispatchResult: undefined,
- baselineDispatchResult: undefined,
- });
- this.keyValue = this.props.definition.keyValue;
- this.baseline = this.props.definition.baseline;
- }
+ static template = "o-spreadsheet-ScorecardChartConfigPanel";
+ static components = { SelectionInput };
+ state = owl.useState({
+ keyValueDispatchResult: undefined,
+ baselineDispatchResult: undefined,
+ });
+ keyValue = this.props.definition.keyValue;
+ baseline = this.props.definition.baseline;
get errorMessages() {
- var _a, _b;
const cancelledReasons = [
- ...(((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.reasons) || []),
- ...(((_b = this.state.baselineDispatchResult) === null || _b === void 0 ? void 0 : _b.reasons) || []),
+ ...(this.state.keyValueDispatchResult?.reasons || []),
+ ...(this.state.baselineDispatchResult?.reasons || []),
];
return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
}
get isKeyValueInvalid() {
- var _a;
- return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(34 /* CommandResult.InvalidScorecardKeyValue */));
+ return !!this.state.keyValueDispatchResult?.isCancelledBecause(34 /* CommandResult.InvalidScorecardKeyValue */);
}
get isBaselineInvalid() {
- var _a;
- return !!((_a = this.state.keyValueDispatchResult) === null || _a === void 0 ? void 0 : _a.isCancelledBecause(35 /* CommandResult.InvalidScorecardBaseline */));
+ return !!this.state.keyValueDispatchResult?.isCancelledBecause(35 /* CommandResult.InvalidScorecardBaseline */);
}
onKeyValueRangeChanged(ranges) {
this.keyValue = ranges[0];
@@ -18401,8 +18499,6 @@
this.props.updateChart({ baselineMode: ev.target.value });
}
}
- ScorecardChartConfigPanel.template = "o-spreadsheet-ScorecardChartConfigPanel";
- ScorecardChartConfigPanel.components = { SelectionInput };
ScorecardChartConfigPanel.props = {
figureId: String,
definition: Object,
@@ -18410,12 +18506,11 @@
};
class ScorecardChartDesignPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- openedColorPicker: undefined,
- });
- }
+ static template = "o-spreadsheet-ScorecardChartDesignPanel";
+ static components = { ColorPickerWidget };
+ state = owl.useState({
+ openedColorPicker: undefined,
+ });
setup() {
owl.useExternalListener(window, "click", this.closeMenus);
}
@@ -18453,8 +18548,6 @@
this.state.openedColorPicker = undefined;
}
}
- ScorecardChartDesignPanel.template = "o-spreadsheet-ScorecardChartDesignPanel";
- ScorecardChartDesignPanel.components = { ColorPickerWidget };
ScorecardChartDesignPanel.props = {
figureId: String,
definition: Object,
@@ -18509,10 +18602,9 @@
}
`;
class ChartPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.shouldUpdateChart = true;
- }
+ static template = "o-spreadsheet-ChartPanel";
+ state;
+ shouldUpdateChart = true;
get figureId() {
return this.state.figureId;
}
@@ -18522,7 +18614,8 @@
setup() {
const selectedFigureId = this.env.model.getters.getSelectedFigureId();
if (!selectedFigureId) {
- throw new Error(_lt("Cannot open the chart side panel while no chart are selected"));
+ this.props.onCloseSidePanel();
+ return;
}
this.state = owl.useState({
panel: "configuration",
@@ -18592,7 +18685,6 @@
this.state.panel = panel;
}
}
- ChartPanel.template = "o-spreadsheet-ChartPanel";
ChartPanel.props = {
onCloseSidePanel: Function,
};
@@ -18693,18 +18785,15 @@
}
`;
class IconPicker extends owl.Component {
- constructor() {
- super(...arguments);
- this.icons = ICONS;
- this.iconSets = ICON_SETS;
- }
+ static template = "o-spreadsheet-IconPicker";
+ icons = ICONS;
+ iconSets = ICON_SETS;
onIconClick(icon) {
if (icon) {
this.props.onIconPicked(icon);
}
}
}
- IconPicker.template = "o-spreadsheet-IconPicker";
IconPicker.props = {
onIconPicked: Function,
};
@@ -18982,14 +19071,15 @@
}
`;
class ConditionalFormattingPanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.icons = ICONS;
- this.cellIsOperators = CellIsOperators;
- this.iconSets = ICON_SETS;
- this.getTextDecoration = getTextDecoration;
- this.colorNumberString = colorNumberString;
- }
+ static template = "o-spreadsheet-ConditionalFormattingPanel";
+ static components = { SelectionInput, IconPicker, ColorPickerWidget };
+ icons = ICONS;
+ cellIsOperators = CellIsOperators;
+ iconSets = ICON_SETS;
+ getTextDecoration = getTextDecoration;
+ colorNumberString = colorNumberString;
+ activeSheetId;
+ state;
setup() {
this.activeSheetId = this.env.model.getters.getActiveSheetId();
this.state = owl.useState({
@@ -19245,12 +19335,10 @@
* Cell Is Rule
****************************************************************************/
get isValue1Invalid() {
- var _a;
- return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(51 /* CommandResult.FirstArgMissing */));
+ return !!this.state.errors?.includes(51 /* CommandResult.FirstArgMissing */);
}
get isValue2Invalid() {
- var _a;
- return !!((_a = this.state.errors) === null || _a === void 0 ? void 0 : _a.includes(52 /* CommandResult.SecondArgMissing */));
+ return !!this.state.errors?.includes(52 /* CommandResult.SecondArgMissing */);
}
toggleStyle(tool) {
const style = this.state.rules.cellIs.style;
@@ -19290,10 +19378,9 @@
this.closeMenus();
}
getPreviewGradient() {
- var _a;
const rule = this.state.rules.colorScale;
const minColor = colorNumberString(rule.minimum.color);
- const midColor = colorNumberString(((_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.color) || DEFAULT_COLOR_SCALE_MIDPOINT_COLOR);
+ const midColor = colorNumberString(rule.midpoint?.color || DEFAULT_COLOR_SCALE_MIDPOINT_COLOR);
const maxColor = colorNumberString(rule.maximum.color);
const baseString = "background-image: linear-gradient(to right, ";
return rule.midpoint === undefined
@@ -19353,8 +19440,6 @@
this.state.rules.iconSet.icons[target] = icon;
}
}
- ConditionalFormattingPanel.template = "o-spreadsheet-ConditionalFormattingPanel";
- ConditionalFormattingPanel.components = { SelectionInput, IconPicker, ColorPickerWidget };
ConditionalFormattingPanel.props = {
selection: { type: Object, optional: true },
onCloseSidePanel: Function,
@@ -19368,6 +19453,9 @@
}
`;
class CustomCurrencyPanel extends owl.Component {
+ static template = "o-spreadsheet-CustomCurrencyPanel";
+ availableCurrencies;
+ state;
setup() {
this.availableCurrencies = [];
this.state = owl.useState({
@@ -19397,9 +19485,8 @@
return selectedFormat ? selectedFormat.format === this.getCommonFormat() : false;
}
async updateAvailableCurrencies() {
- var _a, _b;
if (currenciesRegistry.getAll().length === 0) {
- const currencies = (await ((_b = (_a = this.env).loadCurrencies) === null || _b === void 0 ? void 0 : _b.call(_a))) || [];
+ const currencies = (await this.env.loadCurrencies?.()) || [];
currencies.forEach((currency, index) => {
currenciesRegistry.add(index.toString(), currency);
});
@@ -19496,7 +19583,6 @@
return currency.name + (currency.code ? ` (${currency.code})` : "");
}
}
- CustomCurrencyPanel.template = "o-spreadsheet-CustomCurrencyPanel";
CustomCurrencyPanel.props = {
onCloseSidePanel: Function,
};
@@ -19536,12 +19622,11 @@
}
`;
class FindAndReplacePanel extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState(this.initialState());
- this.showFormulaState = false;
- this.findAndReplaceRef = owl.useRef("findAndReplace");
- }
+ static template = "o-spreadsheet-FindAndReplacePanel";
+ state = owl.useState(this.initialState());
+ debounceTimeoutId;
+ showFormulaState = false;
+ findAndReplaceRef = owl.useRef("findAndReplace");
get hasSearchResult() {
return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
}
@@ -19635,11 +19720,119 @@
};
}
}
- FindAndReplacePanel.template = "o-spreadsheet-FindAndReplacePanel";
FindAndReplacePanel.props = {
onCloseSidePanel: Function,
};
+ const SplitToColumnsInteractiveContent = {
+ SplitIsDestructive: _lt("This will overwrite data in the subsequent columns. Split anyway?"),
+ };
+ function interactiveSplitToColumns(env, separator, addNewColumns) {
+ let result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", { separator, addNewColumns });
+ if (result.isCancelledBecause(90 /* CommandResult.SplitWillOverwriteContent */)) {
+ env.askConfirmation(SplitToColumnsInteractiveContent.SplitIsDestructive, () => {
+ result = env.model.dispatch("SPLIT_TEXT_INTO_COLUMNS", {
+ separator,
+ addNewColumns,
+ force: true,
+ });
+ });
+ }
+ return result;
+ }
+
+ const SEPARATORS = [
+ { name: _lt("Detect automatically"), value: "auto" },
+ { name: _lt("Custom separator"), value: "custom" },
+ { name: _lt("Space"), value: " " },
+ { name: _lt("Comma"), value: "," },
+ { name: _lt("Semicolon"), value: ";" },
+ { name: _lt("Line Break"), value: NEWLINE },
+ ];
+ class SplitIntoColumnsPanel extends owl.Component {
+ static template = "o-spreadsheet-SplitIntoColumnsPanel";
+ state = owl.useState({ separatorValue: "auto", addNewColumns: false, customSeparator: "" });
+ setup() {
+ owl.onWillUpdateProps(() => {
+ // The feature makes no sense if we are editing a cell, because then the selection isn't active
+ // Stop the edition when the panel is mounted, and close the panel if the user start editing a cell
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
+ this.props.onCloseSidePanel();
+ }
+ });
+ owl.onMounted(() => {
+ this.env.model.dispatch("STOP_EDITION");
+ });
+ }
+ onSeparatorChange(value) {
+ this.state.separatorValue = value;
+ }
+ updateCustomSeparator(ev) {
+ if (!ev.target)
+ return;
+ this.state.customSeparator = ev.target.value;
+ }
+ updateAddNewColumnsCheckbox(ev) {
+ if (!ev.target)
+ return;
+ this.state.addNewColumns = ev.target.checked;
+ }
+ confirm() {
+ const result = interactiveSplitToColumns(this.env, this.separatorValue, this.state.addNewColumns);
+ if (result.isSuccessful) {
+ this.props.onCloseSidePanel();
+ }
+ }
+ get errorMessages() {
+ const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
+ separator: this.separatorValue,
+ addNewColumns: this.state.addNewColumns,
+ force: true,
+ }).reasons;
+ const errors = new Set();
+ for (const reason of cancelledReasons) {
+ switch (reason) {
+ case 90 /* CommandResult.SplitWillOverwriteContent */:
+ case 89 /* CommandResult.EmptySplitSeparator */:
+ break;
+ default:
+ errors.add(SplitToColumnsTerms.Errors[reason] || SplitToColumnsTerms.Errors.Unexpected);
+ }
+ }
+ return Array.from(errors);
+ }
+ get warningMessages() {
+ const warnings = [];
+ const cancelledReasons = this.env.model.canDispatch("SPLIT_TEXT_INTO_COLUMNS", {
+ separator: this.separatorValue,
+ addNewColumns: this.state.addNewColumns,
+ force: false,
+ }).reasons;
+ if (cancelledReasons.includes(90 /* CommandResult.SplitWillOverwriteContent */)) {
+ warnings.push(SplitToColumnsTerms.Errors[90 /* CommandResult.SplitWillOverwriteContent */]);
+ }
+ return warnings;
+ }
+ get separatorValue() {
+ if (this.state.separatorValue === "custom") {
+ return this.state.customSeparator;
+ }
+ else if (this.state.separatorValue === "auto") {
+ return this.env.model.getters.getAutomaticSeparator();
+ }
+ return this.state.separatorValue;
+ }
+ get separators() {
+ return SEPARATORS;
+ }
+ get isConfirmDisabled() {
+ return !this.separatorValue || this.errorMessages.length > 0;
+ }
+ }
+ SplitIntoColumnsPanel.props = {
+ onCloseSidePanel: Function,
+ };
+
const sidePanelRegistry = new Registry();
sidePanelRegistry.add("ConditionalFormatting", {
title: _lt("Conditional formatting"),
@@ -19657,13 +19850,14 @@
title: _lt("Custom currency format"),
Body: CustomCurrencyPanel,
});
+ sidePanelRegistry.add("SplitToColumns", {
+ title: _lt("Split text into columns"),
+ Body: SplitIntoColumnsPanel,
+ });
class TopBarComponentRegistry extends Registry {
- constructor() {
- super(...arguments);
- this.mapping = {};
- this.uuidGenerator = new UuidGenerator();
- }
+ mapping = {};
+ uuidGenerator = new UuidGenerator();
add(name, value) {
const component = { ...value, id: this.uuidGenerator.uuidv4() };
return super.add(name, component);
@@ -19752,13 +19946,18 @@
}
`;
class FigureComponent extends owl.Component {
- constructor() {
- super(...arguments);
- this.menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
- this.figureRef = owl.useRef("figure");
- this.menuButtonRef = owl.useRef("menuButton");
- this.menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
- }
+ static template = "o-spreadsheet-FigureComponent";
+ static components = { Menu };
+ static defaultProps = {
+ onFigureDeleted: () => { },
+ onMouseDown: () => { },
+ onClickAnchor: () => { },
+ };
+ menuState = owl.useState({ isOpen: false, position: null, menuItems: [] });
+ figureRef = owl.useRef("figure");
+ menuButtonRef = owl.useRef("menuButton");
+ menuButtonRect = useAbsoluteBoundingRect(this.menuButtonRef);
+ borderWidth;
get isSelected() {
return this.env.model.getters.getSelectedFigureId() === this.props.figure.id;
}
@@ -19821,7 +20020,7 @@
* TODO: When implementing proper mobile, we will need to scroll the viewport
* correctly (and render?) before focusing the element.
*/
- el === null || el === void 0 ? void 0 : el.focus({ preventScroll: true });
+ el?.focus({ preventScroll: true });
}
}, () => [this.env.model.getters.getSelectedFigureId(), this.props.figure.id, this.figureRef.el]);
}
@@ -19890,13 +20089,6 @@
.menuBuilder(this.props.figure.id, this.props.onFigureDeleted, this.env);
}
}
- FigureComponent.template = "o-spreadsheet-FigureComponent";
- FigureComponent.components = { Menu };
- FigureComponent.defaultProps = {
- onFigureDeleted: () => { },
- onMouseDown: () => { },
- onClickAnchor: () => { },
- };
FigureComponent.props = {
figure: Object,
style: { type: String, optional: true },
@@ -20089,13 +20281,11 @@
}
`;
class Autofill extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- position: { left: 0, top: 0 },
- handler: false,
- });
- }
+ static template = "o-spreadsheet-Autofill";
+ state = owl.useState({
+ position: { left: 0, top: 0 },
+ handler: false,
+ });
get style() {
const { left, top } = this.props.position;
return cssPropertiesToCss({
@@ -20160,16 +20350,15 @@
this.env.model.dispatch("AUTOFILL_AUTO");
}
}
- Autofill.template = "o-spreadsheet-Autofill";
Autofill.props = {
position: Object,
isVisible: Boolean,
};
class TooltipComponent extends owl.Component {
- }
- TooltipComponent.template = owl.xml /* xml */ `
+ static template = owl.xml /* xml */ `
`;
+ }
TooltipComponent.props = {
content: String,
};
@@ -20186,6 +20375,7 @@
}
`;
class ClientTag extends owl.Component {
+ static template = "o-spreadsheet-ClientTag";
get tagStyle() {
const { col, row, color } = this.props;
const { height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
@@ -20204,7 +20394,6 @@
});
}
}
- ClientTag.template = "o-spreadsheet-ClientTag";
ClientTag.props = {
active: Boolean,
name: String,
@@ -20234,8 +20423,8 @@
}
`;
class TextValueProvider extends owl.Component {
+ static template = "o-spreadsheet-TextValueProvider";
}
- TextValueProvider.template = "o-spreadsheet-TextValueProvider";
TextValueProvider.props = {
values: Array,
selectedIndex: Number,
@@ -20243,6 +20432,8 @@
};
class ContentEditableHelper {
+ // todo make el private and expose dedicated methods
+ el;
constructor(el) {
this.el = el;
}
@@ -20349,12 +20540,11 @@
}
}
scrollSelectionIntoView() {
- var _a;
- const focusedNode = (_a = document.getSelection()) === null || _a === void 0 ? void 0 : _a.focusNode;
+ const focusedNode = document.getSelection()?.focusNode;
if (!focusedNode || !this.el.contains(focusedNode))
return;
const element = focusedNode instanceof HTMLElement ? focusedNode : focusedNode.parentElement;
- element === null || element === void 0 ? void 0 : element.scrollIntoView({ block: "nearest" });
+ element?.scrollIntoView({ block: "nearest" });
}
/**
* remove the current selection of the user
@@ -20479,13 +20669,11 @@
}
`;
class FunctionDescriptionProvider extends owl.Component {
- constructor() {
- super(...arguments);
- this.assistantState = owl.useState({
- allowCellSelectionBehind: false,
- });
- this.timeOutId = 0;
- }
+ static template = "o-spreadsheet-FunctionDescriptionProvider";
+ assistantState = owl.useState({
+ allowCellSelectionBehind: false,
+ });
+ timeOutId = 0;
setup() {
owl.onWillUnmount(() => {
if (this.timeOutId) {
@@ -20506,7 +20694,6 @@
}, 2000);
}
}
- FunctionDescriptionProvider.template = "o-spreadsheet-FunctionDescriptionProvider";
FunctionDescriptionProvider.props = {
functionName: String,
functionDescription: Object,
@@ -20572,42 +20759,30 @@
}
`;
class Composer extends owl.Component {
- constructor() {
- super(...arguments);
- this.composerRef = owl.useRef("o_composer");
- this.contentHelper = new ContentEditableHelper(this.composerRef.el);
- this.composerState = owl.useState({
- positionStart: 0,
- positionEnd: 0,
- });
- this.autoCompleteState = owl.useState({
- showProvider: false,
- values: [],
- selectedIndex: 0,
- });
- this.functionDescriptionState = owl.useState({
- showDescription: false,
- functionName: "",
- functionDescription: {},
- argToFocus: 0,
- });
- this.isKeyStillDown = false;
- this.compositionActive = false;
- // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
- this.shouldProcessInputEvents = false;
- this.tokens = [];
- this.keyMapping = {
- ArrowUp: this.processArrowKeys,
- ArrowDown: this.processArrowKeys,
- ArrowLeft: this.processArrowKeys,
- ArrowRight: this.processArrowKeys,
- Enter: this.processEnterKey,
- Escape: this.processEscapeKey,
- F2: () => console.warn("Not implemented"),
- F4: this.processF4Key,
- Tab: (ev) => this.processTabKey(ev),
- };
- }
+ static template = "o-spreadsheet-Composer";
+ static components = { TextValueProvider, FunctionDescriptionProvider };
+ static defaultProps = {
+ inputStyle: "",
+ };
+ composerRef = owl.useRef("o_composer");
+ contentHelper = new ContentEditableHelper(this.composerRef.el);
+ composerState = owl.useState({
+ positionStart: 0,
+ positionEnd: 0,
+ });
+ autoCompleteState = owl.useState({
+ showProvider: false,
+ values: [],
+ selectedIndex: 0,
+ });
+ functionDescriptionState = owl.useState({
+ showDescription: false,
+ functionName: "",
+ functionDescription: {},
+ argToFocus: 0,
+ });
+ isKeyStillDown = false;
+ compositionActive = false;
get assistantStyle() {
if (this.props.delimitation && this.props.rect) {
const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
@@ -20628,6 +20803,20 @@
}
return cssPropertiesToCss({ width: `${ASSISTANT_WIDTH}px` });
}
+ // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
+ shouldProcessInputEvents = false;
+ tokens = [];
+ keyMapping = {
+ ArrowUp: this.processArrowKeys,
+ ArrowDown: this.processArrowKeys,
+ ArrowLeft: this.processArrowKeys,
+ ArrowRight: this.processArrowKeys,
+ Enter: this.processEnterKey,
+ Escape: this.processEscapeKey,
+ F2: () => console.warn("Not implemented"),
+ F4: this.processF4Key,
+ Tab: (ev) => this.processTabKey(ev),
+ };
setup() {
owl.onMounted(() => {
const el = this.composerRef.el;
@@ -20636,8 +20825,7 @@
this.contentHelper.scrollSelectionIntoView();
});
owl.onWillUnmount(() => {
- var _a, _b;
- (_b = (_a = this.props).onComposerUnmounted) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.props.onComposerUnmounted?.();
});
owl.onPatched(() => {
if (!this.isKeyStillDown) {
@@ -20686,11 +20874,10 @@
}
}
processTabKey(ev) {
- var _a;
ev.preventDefault();
ev.stopPropagation();
if (this.autoCompleteState.showProvider) {
- const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
+ const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
if (autoCompleteValue) {
this.autoComplete(autoCompleteValue);
return;
@@ -20707,7 +20894,6 @@
this.env.model.selection.moveAnchorCell(direction, 1);
}
processEnterKey(ev) {
- var _a;
ev.preventDefault();
ev.stopPropagation();
if (ev.altKey || ev.ctrlKey) {
@@ -20724,7 +20910,7 @@
}
this.isKeyStillDown = false;
if (this.autoCompleteState.showProvider) {
- const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
+ const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
if (autoCompleteValue) {
this.autoComplete(autoCompleteValue);
return;
@@ -21059,11 +21245,6 @@
this.processTokenAtCursor();
}
}
- Composer.template = "o-spreadsheet-Composer";
- Composer.components = { TextValueProvider, FunctionDescriptionProvider };
- Composer.defaultProps = {
- inputStyle: "",
- };
Composer.props = {
focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
onComposerContentFocused: Function,
@@ -21102,6 +21283,13 @@
* It also applies the style of the cell to the composer input.
*/
class GridComposer extends owl.Component {
+ static template = "o-spreadsheet-GridComposer";
+ static components = { Composer };
+ gridComposerRef;
+ zone;
+ rect;
+ isCellReferenceVisible;
+ composerState;
setup() {
this.gridComposerRef = owl.useRef("gridComposer");
this.composerState = owl.useState({
@@ -21202,8 +21390,6 @@
});
}
}
- GridComposer.template = "o-spreadsheet-GridComposer";
- GridComposer.components = { Composer };
GridComposer.props = {
focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
onComposerUnmounted: Function,
@@ -21213,13 +21399,13 @@
const CSS$1 = css /* scss */ `
.o-filter-icon {
- color: ${FILTERS_COLOR};
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: ${FILTER_ICON_EDGE_LENGTH}px;
height: ${FILTER_ICON_EDGE_LENGTH}px;
+ color: #4a4f59;
}
.o-filter-icon:hover {
background: ${FILTERS_COLOR};
@@ -21227,13 +21413,13 @@
}
`;
class FilterIcon extends owl.Component {
+ static style = CSS$1;
+ static template = "o-spreadsheet-FilterIcon";
get style() {
const { x, y } = this.props.position;
return `top:${y}px;left:${x}px`;
}
}
- FilterIcon.style = CSS$1;
- FilterIcon.template = "o-spreadsheet-FilterIcon";
FilterIcon.props = {
position: Object,
isActive: Boolean,
@@ -21242,6 +21428,14 @@
const CSS = css /* scss */ ``;
class FilterIconsOverlay extends owl.Component {
+ static style = CSS;
+ static template = "o-spreadsheet-FilterIconsOverlay";
+ static components = {
+ FilterIcon,
+ };
+ static defaultProps = {
+ gridPosition: { x: 0, y: 0 },
+ };
getVisibleFilterHeaders() {
const sheetId = this.env.model.getters.getActiveSheetId();
const headerPositions = this.env.model.getters.getFilterHeaders(sheetId);
@@ -21260,9 +21454,8 @@
}
// Calculates the vertical position of the filter icon based on the row dimensions and cell styles.
getIconVerticalPosition(rowDims, cell) {
- var _a;
const centeringOffset = Math.floor((rowDims.size - FILTER_ICON_EDGE_LENGTH) / 2);
- switch ((_a = cell === null || cell === void 0 ? void 0 : cell.style) === null || _a === void 0 ? void 0 : _a.verticalAlign) {
+ switch (cell?.style?.verticalAlign) {
case "bottom":
return rowDims.end - FILTER_ICON_MARGIN - FILTER_ICON_EDGE_LENGTH;
case "top":
@@ -21298,14 +21491,6 @@
return !(rect.width === 0 || rect.height === 0);
}
}
- FilterIconsOverlay.style = CSS;
- FilterIconsOverlay.template = "o-spreadsheet-FilterIconsOverlay";
- FilterIconsOverlay.components = {
- FilterIcon,
- };
- FilterIconsOverlay.defaultProps = {
- gridPosition: { x: 0, y: 0 },
- };
FilterIconsOverlay.props = {
gridPosition: { type: Object, optional: true },
};
@@ -21634,14 +21819,13 @@
*
*/
class FiguresContainer extends owl.Component {
- constructor() {
- super(...arguments);
- this.dnd = owl.useState({
- draggedFigure: undefined,
- horizontalSnap: undefined,
- verticalSnap: undefined,
- });
- }
+ static template = "o-spreadsheet-FiguresContainer";
+ static components = { FigureComponent };
+ dnd = owl.useState({
+ draggedFigure: undefined,
+ horizontalSnap: undefined,
+ verticalSnap: undefined,
+ });
setup() {
owl.onMounted(() => {
// horrible, but necessary
@@ -21655,11 +21839,10 @@
});
}
getVisibleFigures() {
- var _a;
const visibleFigures = this.env.model.getters.getVisibleFigures();
if (this.dnd.draggedFigure &&
- !visibleFigures.some((figure) => { var _a; return figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); })) {
- visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), (_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id));
+ !visibleFigures.some((figure) => figure.id === this.dnd.draggedFigure?.id)) {
+ visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id));
}
return visibleFigures;
}
@@ -21723,9 +21906,8 @@
});
}
getFigureContainer(figure) {
- var _a;
const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
- if (figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id)) {
+ if (figure.id === this.dnd.draggedFigure?.id) {
return "dnd";
}
else if (figure.x < viewportX && figure.y < viewportY) {
@@ -21833,7 +22015,7 @@
return this.getVisibleFigures().filter((f) => f.id !== figId);
}
getDndFigure() {
- const figure = this.getVisibleFigures().find((fig) => { var _a; return fig.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); });
+ const figure = this.getVisibleFigures().find((fig) => fig.id === this.dnd.draggedFigure?.id);
if (!figure)
throw new Error("Dnd figure not found");
return {
@@ -21842,8 +22024,7 @@
};
}
getFigureStyle(figure) {
- var _a;
- if (figure.id !== ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id))
+ if (figure.id !== this.dnd.draggedFigure?.id)
return "";
return cssPropertiesToCss({
opacity: "0.9",
@@ -21888,8 +22069,6 @@
}
}
}
- FiguresContainer.template = "o-spreadsheet-FiguresContainer";
- FiguresContainer.components = { FigureComponent };
FiguresContainer.props = {
onFigureDeleted: Function,
};
@@ -22020,6 +22199,17 @@
});
}
class GridOverlay extends owl.Component {
+ static template = "o-spreadsheet-GridOverlay";
+ static components = { FiguresContainer };
+ static defaultProps = {
+ onCellHovered: () => { },
+ onCellDoubleClicked: () => { },
+ onCellClicked: () => { },
+ onCellRightClicked: () => { },
+ onGridResized: () => { },
+ onFigureDeleted: () => { },
+ };
+ gridOverlay;
setup() {
this.gridOverlay = owl.useRef("gridOverlay");
useCellHovered(this.env, this.gridOverlay, this.props.onCellHovered);
@@ -22069,16 +22259,6 @@
return [colIndex, rowIndex];
}
}
- GridOverlay.template = "o-spreadsheet-GridOverlay";
- GridOverlay.components = { FiguresContainer };
- GridOverlay.defaultProps = {
- onCellHovered: () => { },
- onCellDoubleClicked: () => { },
- onCellClicked: () => { },
- onCellRightClicked: () => { },
- onGridResized: () => { },
- onFigureDeleted: () => { },
- };
GridOverlay.props = {
onCellHovered: { type: Function, optional: true },
onCellDoubleClicked: { type: Function, optional: true },
@@ -22091,10 +22271,9 @@
};
class GridPopover extends owl.Component {
- constructor() {
- super(...arguments);
- this.zIndex = ComponentsImportance.GridPopover;
- }
+ static template = "o-spreadsheet-GridPopover";
+ static components = { Popover };
+ zIndex = ComponentsImportance.GridPopover;
get cellPopover() {
const popover = this.env.model.getters.getCellPopover(this.props.hoveredCell);
if (!popover.isOpen) {
@@ -22112,8 +22291,6 @@
};
}
}
- GridPopover.template = "o-spreadsheet-GridPopover";
- GridPopover.components = { Popover };
GridPopover.props = {
hoveredCell: Object,
onClosePopover: Function,
@@ -22122,26 +22299,23 @@
};
class AbstractResizer extends owl.Component {
- constructor() {
- super(...arguments);
- this.PADDING = 0;
- this.MAX_SIZE_MARGIN = 0;
- this.MIN_ELEMENT_SIZE = 0;
- this.lastSelectedElementIndex = null;
- this.state = owl.useState({
- resizerIsActive: false,
- isResizing: false,
- isMoving: false,
- isSelecting: false,
- waitingForMove: false,
- activeElement: 0,
- draggerLinePosition: 0,
- draggerShadowPosition: 0,
- draggerShadowThickness: 0,
- delta: 0,
- base: 0,
- });
- }
+ PADDING = 0;
+ MAX_SIZE_MARGIN = 0;
+ MIN_ELEMENT_SIZE = 0;
+ lastSelectedElementIndex = null;
+ state = owl.useState({
+ resizerIsActive: false,
+ isResizing: false,
+ isMoving: false,
+ isSelecting: false,
+ waitingForMove: false,
+ activeElement: 0,
+ draggerLinePosition: 0,
+ draggerShadowPosition: 0,
+ draggerShadowThickness: 0,
+ delta: 0,
+ base: 0,
+ });
_computeHandleDisplay(ev) {
const position = this._getEvOffset(ev);
const elementIndex = this._getElementIndex(position);
@@ -22383,6 +22557,8 @@
onOpenContextMenu: Function,
};
class ColResizer extends AbstractResizer {
+ static template = "o-spreadsheet-ColResizer";
+ colResizerRef;
setup() {
super.setup();
this.colResizerRef = owl.useRef("colResizer");
@@ -22488,7 +22664,6 @@
return this._getDimensionsInViewport(hiddenIndex).start;
}
}
- ColResizer.template = "o-spreadsheet-ColResizer";
css /* scss */ `
.o-row-resizer {
position: absolute;
@@ -22555,6 +22730,7 @@
onOpenContextMenu: Function,
};
class RowResizer extends AbstractResizer {
+ static template = "o-spreadsheet-RowResizer";
setup() {
super.setup();
this.rowResizerRef = owl.useRef("rowResizer");
@@ -22562,6 +22738,7 @@
this.MAX_SIZE_MARGIN = 60;
this.MIN_ELEMENT_SIZE = MIN_ROW_HEIGHT;
}
+ rowResizerRef;
_getEvOffset(ev) {
return ev.offsetY;
}
@@ -22660,7 +22837,6 @@
return this._getDimensionsInViewport(hiddenIndex).start;
}
}
- RowResizer.template = "o-spreadsheet-RowResizer";
css /* scss */ `
.o-overlay {
.all {
@@ -22677,12 +22853,12 @@
onOpenContextMenu: Function,
};
class HeadersOverlay extends owl.Component {
+ static template = "o-spreadsheet-HeadersOverlay";
+ static components = { ColResizer, RowResizer };
selectAll() {
this.env.model.selection.selectAll();
}
}
- HeadersOverlay.template = "o-spreadsheet-HeadersOverlay";
- HeadersOverlay.components = { ColResizer, RowResizer };
HeadersOverlay.props = {
onOpenContextMenu: Function,
};
@@ -22742,6 +22918,7 @@
}
`;
class Border extends owl.Component {
+ static template = "o-spreadsheet-Border";
get style() {
const isTop = ["n", "w", "e"].includes(this.props.orientation);
const isLeft = ["n", "w", "s"].includes(this.props.orientation);
@@ -22770,7 +22947,6 @@
this.props.onMoveHighlight(ev.clientX, ev.clientY);
}
}
- Border.template = "o-spreadsheet-Border";
Border.props = {
zone: Object,
orientation: String,
@@ -22802,11 +22978,9 @@
}
`;
class Corner extends owl.Component {
- constructor() {
- super(...arguments);
- this.isTop = this.props.orientation[0] === "n";
- this.isLeft = this.props.orientation[1] === "w";
- }
+ static template = "o-spreadsheet-Corner";
+ isTop = this.props.orientation[0] === "n";
+ isLeft = this.props.orientation[1] === "w";
get style() {
const z = this.props.zone;
const col = this.isLeft ? z.left : z.right;
@@ -22833,7 +23007,6 @@
this.props.onResizeHighlight(this.isLeft, this.isTop);
}
}
- Corner.template = "o-spreadsheet-Corner";
Corner.props = {
zone: Object,
color: String,
@@ -22848,12 +23021,14 @@
}
`;
class Highlight extends owl.Component {
- constructor() {
- super(...arguments);
- this.highlightState = owl.useState({
- shiftingMode: "none",
- });
- }
+ static template = "o-spreadsheet-Highlight";
+ static components = {
+ Corner,
+ Border,
+ };
+ highlightState = owl.useState({
+ shiftingMode: "none",
+ });
onResizeHighlight(isLeft, isTop) {
const activeSheet = this.env.model.getters.getActiveSheet();
this.highlightState.shiftingMode = "isResizing";
@@ -22943,17 +23118,14 @@
dragAndDropBeyondTheViewport(this.env, mouseMove, mouseUp);
}
}
- Highlight.template = "o-spreadsheet-Highlight";
- Highlight.components = {
- Corner,
- Border,
- };
Highlight.props = {
zone: Object,
color: String,
};
class ScrollBar$1 {
+ direction;
+ el;
constructor(el, direction) {
this.el = el;
this.direction = direction;
@@ -22989,6 +23161,21 @@
}
`;
class ScrollBar extends owl.Component {
+ static template = owl.xml /*xml*/ `
+
+ `;
+ static defaultProps = {
+ width: 1,
+ height: 1,
+ };
+ scrollbarRef;
+ scrollbar;
setup() {
this.scrollbarRef = owl.useRef("scrollbar");
this.scrollbar = new ScrollBar$1(this.scrollbarRef.el, this.props.direction);
@@ -23017,19 +23204,6 @@
}
}
}
- ScrollBar.template = owl.xml /*xml*/ `
-
- `;
- ScrollBar.defaultProps = {
- width: 1,
- height: 1,
- };
ScrollBar.props = {
width: { type: Number, optional: true },
height: { type: Number, optional: true },
@@ -23040,6 +23214,19 @@
};
class HorizontalScrollBar extends owl.Component {
+ static components = { ScrollBar };
+ static template = owl.xml /*xml*/ `
+ `;
+ static defaultProps = {
+ leftOffset: 0,
+ };
get offset() {
return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollX;
}
@@ -23067,24 +23254,24 @@
});
}
}
- HorizontalScrollBar.components = { ScrollBar };
- HorizontalScrollBar.template = owl.xml /*xml*/ `
- `;
- HorizontalScrollBar.defaultProps = {
- leftOffset: 0,
- };
HorizontalScrollBar.props = {
leftOffset: { type: Number, optional: true },
};
class VerticalScrollBar extends owl.Component {
+ static components = { ScrollBar };
+ static template = owl.xml /*xml*/ `
+ `;
+ static defaultProps = {
+ topOffset: 0,
+ };
get offset() {
return this.env.model.getters.getActiveSheetDOMScrollInfo().scrollY;
}
@@ -23112,19 +23299,6 @@
});
}
}
- VerticalScrollBar.components = { ScrollBar };
- VerticalScrollBar.template = owl.xml /*xml*/ `
- `;
- VerticalScrollBar.defaultProps = {
- topOffset: 0,
- };
VerticalScrollBar.props = {
topOffset: { type: Number, optional: true },
};
@@ -23138,136 +23312,29 @@
// JS
// -----------------------------------------------------------------------------
class Grid extends owl.Component {
- constructor() {
- super(...arguments);
- this.HEADER_HEIGHT = HEADER_HEIGHT;
- this.HEADER_WIDTH = HEADER_WIDTH;
- // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
- // down itself
- this.keyDownMapping = {
- ENTER: () => {
- const cell = this.env.model.getters.getActiveCell();
- cell.type === CellValueType.empty
- ? this.props.onGridComposerCellFocused()
- : this.props.onComposerContentFocused();
- },
- TAB: () => this.env.model.selection.moveAnchorCell("right", 1),
- "SHIFT+TAB": () => this.env.model.selection.moveAnchorCell("left", 1),
- F2: () => {
- const cell = this.env.model.getters.getActiveCell();
- cell.type === CellValueType.empty
- ? this.props.onGridComposerCellFocused()
- : this.props.onComposerContentFocused();
- },
- DELETE: () => {
- this.env.model.dispatch("DELETE_CONTENT", {
- sheetId: this.env.model.getters.getActiveSheetId(),
- target: this.env.model.getters.getSelectedZones(),
- });
- },
- BACKSPACE: () => {
- this.env.model.dispatch("DELETE_CONTENT", {
- sheetId: this.env.model.getters.getActiveSheetId(),
- target: this.env.model.getters.getSelectedZones(),
- });
- },
- ESCAPE: () => {
- /** TODO: Clean once we introduce proper focus on sub components. Grid should not have to handle all this logic */
- if (this.env.model.getters.hasOpenedPopover()) {
- this.closeOpenedPopover();
- }
- else if (this.menuState.isOpen) {
- this.closeMenu();
- }
- else {
- this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
- }
- },
- "CTRL+A": () => this.env.model.selection.loopSelection(),
- "CTRL+Z": () => this.env.model.dispatch("REQUEST_UNDO"),
- "CTRL+Y": () => this.env.model.dispatch("REQUEST_REDO"),
- F4: () => this.env.model.dispatch("REQUEST_REDO"),
- "CTRL+B": () => this.env.model.dispatch("SET_FORMATTING", {
- sheetId: this.env.model.getters.getActiveSheetId(),
- target: this.env.model.getters.getSelectedZones(),
- style: { bold: !this.env.model.getters.getCurrentStyle().bold },
- }),
- "CTRL+I": () => this.env.model.dispatch("SET_FORMATTING", {
- sheetId: this.env.model.getters.getActiveSheetId(),
- target: this.env.model.getters.getSelectedZones(),
- style: { italic: !this.env.model.getters.getCurrentStyle().italic },
- }),
- "CTRL+U": () => this.env.model.dispatch("SET_FORMATTING", {
- sheetId: this.env.model.getters.getActiveSheetId(),
- target: this.env.model.getters.getSelectedZones(),
- style: { underline: !this.env.model.getters.getCurrentStyle().underline },
- }),
- "ALT+=": () => {
- var _a;
- const sheetId = this.env.model.getters.getActiveSheetId();
- const mainSelectedZone = this.env.model.getters.getSelectedZone();
- const { anchor } = this.env.model.getters.getSelection();
- const sums = this.env.model.getters.getAutomaticSums(sheetId, mainSelectedZone, anchor.cell);
- if (this.env.model.getters.isSingleCellOrMerge(sheetId, mainSelectedZone) ||
- (this.env.model.getters.isEmpty(sheetId, mainSelectedZone) && sums.length <= 1)) {
- const zone = (_a = sums[0]) === null || _a === void 0 ? void 0 : _a.zone;
- const zoneXc = zone ? this.env.model.getters.zoneToXC(sheetId, sums[0].zone) : "";
- const formula = `=SUM(${zoneXc})`;
- this.props.onGridComposerCellFocused(formula, { start: 5, end: 5 + zoneXc.length });
- }
- else {
- this.env.model.dispatch("SUM_SELECTION");
- }
- },
- "CTRL+HOME": () => {
- const sheetId = this.env.model.getters.getActiveSheetId();
- const { col, row } = this.env.model.getters.getNextVisibleCellPosition({
- sheetId,
- col: 0,
- row: 0,
- });
- this.env.model.selection.selectCell(col, row);
- },
- "CTRL+END": () => {
- const sheetId = this.env.model.getters.getActiveSheetId();
- const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", this.env.model.getters.getNumberCols(sheetId) - 1, 0);
- const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", this.env.model.getters.getNumberRows(sheetId) - 1, 0);
- this.env.model.selection.selectCell(col, row);
- },
- "SHIFT+ ": () => {
- const sheetId = this.env.model.getters.getActiveSheetId();
- const newZone = {
- ...this.env.model.getters.getSelectedZone(),
- left: 0,
- right: this.env.model.getters.getNumberCols(sheetId) - 1,
- };
- const position = this.env.model.getters.getActivePosition();
- this.env.model.selection.selectZone({ cell: position, zone: newZone });
- },
- "CTRL+ ": () => {
- const sheetId = this.env.model.getters.getActiveSheetId();
- const newZone = {
- ...this.env.model.getters.getSelectedZone(),
- top: 0,
- bottom: this.env.model.getters.getNumberRows(sheetId) - 1,
- };
- const position = this.env.model.getters.getActivePosition();
- this.env.model.selection.selectZone({ cell: position, zone: newZone });
- },
- "CTRL+SHIFT+ ": () => {
- this.env.model.selection.selectAll();
- },
- "SHIFT+PAGEDOWN": () => {
- this.env.model.dispatch("ACTIVATE_NEXT_SHEET");
- },
- "SHIFT+PAGEUP": () => {
- this.env.model.dispatch("ACTIVATE_PREVIOUS_SHEET");
- },
- PAGEDOWN: () => this.env.model.dispatch("SHIFT_VIEWPORT_DOWN"),
- PAGEUP: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
- "CTRL+K": () => INSERT_LINK(this.env),
- };
- }
+ static template = "o-spreadsheet-Grid";
+ static components = {
+ GridComposer,
+ GridOverlay,
+ GridPopover,
+ HeadersOverlay,
+ Menu,
+ Autofill,
+ ClientTag,
+ Highlight,
+ Popover,
+ VerticalScrollBar,
+ HorizontalScrollBar,
+ FilterIconsOverlay,
+ };
+ HEADER_HEIGHT = HEADER_HEIGHT;
+ HEADER_WIDTH = HEADER_WIDTH;
+ menuState;
+ gridRef;
+ hiddenInput;
+ onMouseWheel;
+ canvasPosition;
+ hoveredCell;
setup() {
this.menuState = owl.useState({
isOpen: false,
@@ -23310,11 +23377,178 @@
}
this.focus();
}
+ // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
+ // down itself
+ keyDownMapping = {
+ ENTER: () => {
+ const cell = this.env.model.getters.getActiveCell();
+ cell.type === CellValueType.empty
+ ? this.props.onGridComposerCellFocused()
+ : this.props.onComposerContentFocused();
+ },
+ TAB: () => this.env.model.selection.moveAnchorCell("right", 1),
+ "SHIFT+TAB": () => this.env.model.selection.moveAnchorCell("left", 1),
+ F2: () => {
+ const cell = this.env.model.getters.getActiveCell();
+ cell.type === CellValueType.empty
+ ? this.props.onGridComposerCellFocused()
+ : this.props.onComposerContentFocused();
+ },
+ DELETE: () => {
+ this.env.model.dispatch("DELETE_CONTENT", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ });
+ },
+ BACKSPACE: () => {
+ this.env.model.dispatch("DELETE_CONTENT", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ });
+ },
+ ESCAPE: () => {
+ /** TODO: Clean once we introduce proper focus on sub components. Grid should not have to handle all this logic */
+ if (this.env.model.getters.hasOpenedPopover()) {
+ this.closeOpenedPopover();
+ }
+ else if (this.menuState.isOpen) {
+ this.closeMenu();
+ }
+ else {
+ this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
+ }
+ },
+ "CTRL+A": () => this.env.model.selection.loopSelection(),
+ "CTRL+Z": () => this.env.model.dispatch("REQUEST_UNDO"),
+ "CTRL+Y": () => this.env.model.dispatch("REQUEST_REDO"),
+ F4: () => this.env.model.dispatch("REQUEST_REDO"),
+ "CTRL+B": () => this.env.model.dispatch("SET_FORMATTING", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ style: { bold: !this.env.model.getters.getCurrentStyle().bold },
+ }),
+ "CTRL+I": () => this.env.model.dispatch("SET_FORMATTING", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ style: { italic: !this.env.model.getters.getCurrentStyle().italic },
+ }),
+ "CTRL+U": () => this.env.model.dispatch("SET_FORMATTING", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ style: { underline: !this.env.model.getters.getCurrentStyle().underline },
+ }),
+ "CTRL+O": () => CREATE_IMAGE(this.env),
+ "ALT+=": () => {
+ const sheetId = this.env.model.getters.getActiveSheetId();
+ const mainSelectedZone = this.env.model.getters.getSelectedZone();
+ const { anchor } = this.env.model.getters.getSelection();
+ const sums = this.env.model.getters.getAutomaticSums(sheetId, mainSelectedZone, anchor.cell);
+ if (this.env.model.getters.isSingleCellOrMerge(sheetId, mainSelectedZone) ||
+ (this.env.model.getters.isEmpty(sheetId, mainSelectedZone) && sums.length <= 1)) {
+ const zone = sums[0]?.zone;
+ const zoneXc = zone ? this.env.model.getters.zoneToXC(sheetId, sums[0].zone) : "";
+ const formula = `=SUM(${zoneXc})`;
+ this.props.onGridComposerCellFocused(formula, { start: 5, end: 5 + zoneXc.length });
+ }
+ else {
+ this.env.model.dispatch("SUM_SELECTION");
+ }
+ },
+ "ALT+ENTER": () => {
+ const cell = this.env.model.getters.getActiveCell();
+ if (cell.link) {
+ openLink(cell.link, this.env);
+ }
+ },
+ "CTRL+HOME": () => {
+ const sheetId = this.env.model.getters.getActiveSheetId();
+ const { col, row } = this.env.model.getters.getNextVisibleCellPosition({
+ sheetId,
+ col: 0,
+ row: 0,
+ });
+ this.env.model.selection.selectCell(col, row);
+ },
+ "CTRL+END": () => {
+ const sheetId = this.env.model.getters.getActiveSheetId();
+ const col = this.env.model.getters.findVisibleHeader(sheetId, "COL", this.env.model.getters.getNumberCols(sheetId) - 1, 0);
+ const row = this.env.model.getters.findVisibleHeader(sheetId, "ROW", this.env.model.getters.getNumberRows(sheetId) - 1, 0);
+ this.env.model.selection.selectCell(col, row);
+ },
+ "SHIFT+ ": () => {
+ const sheetId = this.env.model.getters.getActiveSheetId();
+ const newZone = {
+ ...this.env.model.getters.getSelectedZone(),
+ left: 0,
+ right: this.env.model.getters.getNumberCols(sheetId) - 1,
+ };
+ const position = this.env.model.getters.getActivePosition();
+ this.env.model.selection.selectZone({ cell: position, zone: newZone });
+ },
+ "CTRL+ ": () => {
+ const sheetId = this.env.model.getters.getActiveSheetId();
+ const newZone = {
+ ...this.env.model.getters.getSelectedZone(),
+ top: 0,
+ bottom: this.env.model.getters.getNumberRows(sheetId) - 1,
+ };
+ const position = this.env.model.getters.getActivePosition();
+ this.env.model.selection.selectZone({ cell: position, zone: newZone });
+ },
+ "CTRL+SHIFT+E": () => this.setHorizontalAlign("center"),
+ "CTRL+SHIFT+L": () => this.setHorizontalAlign("left"),
+ "CTRL+SHIFT+R": () => this.setHorizontalAlign("right"),
+ "CTRL+SHIFT+V": () => PASTE_VALUE_ACTION(this.env),
+ "CTRL+SHIFT+<": () => this.clearFormatting(),
+ "CTRL+<": () => this.clearFormatting(),
+ "CTRL+SHIFT+ ": () => {
+ this.env.model.selection.selectAll();
+ },
+ "CTRL+ALT+=": () => {
+ const activeCols = this.env.model.getters.getActiveCols();
+ const activeRows = this.env.model.getters.getActiveRows();
+ const isSingleSelection = this.env.model.getters.getSelectedZones().length === 1;
+ const areFullCols = activeCols.size > 0 && isSingleSelection;
+ const areFullRows = activeRows.size > 0 && isSingleSelection;
+ if (areFullCols && !areFullRows) {
+ INSERT_COLUMNS_BEFORE_ACTION(this.env);
+ }
+ else if (areFullRows && !areFullCols) {
+ INSERT_ROWS_BEFORE_ACTION(this.env);
+ }
+ },
+ "CTRL+ALT+-": () => {
+ const columns = [...this.env.model.getters.getActiveCols()];
+ const rows = [...this.env.model.getters.getActiveRows()];
+ if (columns.length > 0 && rows.length === 0) {
+ this.env.model.dispatch("REMOVE_COLUMNS_ROWS", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ dimension: "COL",
+ elements: columns,
+ });
+ }
+ else if (rows.length > 0 && columns.length === 0) {
+ this.env.model.dispatch("REMOVE_COLUMNS_ROWS", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ dimension: "ROW",
+ elements: rows,
+ });
+ }
+ },
+ "SHIFT+PAGEDOWN": () => {
+ this.env.model.dispatch("ACTIVATE_NEXT_SHEET");
+ },
+ "SHIFT+PAGEUP": () => {
+ this.env.model.dispatch("ACTIVATE_PREVIOUS_SHEET");
+ },
+ PAGEDOWN: () => this.env.model.dispatch("SHIFT_VIEWPORT_DOWN"),
+ PAGEUP: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
+ "CTRL+K": () => INSERT_LINK(this.env),
+ };
focus() {
- var _a;
if (!this.env.model.getters.getSelectedFigureId() &&
this.env.model.getters.getEditionMode() === "inactive") {
- (_a = this.hiddenInput.el) === null || _a === void 0 ? void 0 : _a.focus();
+ this.hiddenInput.el?.focus();
}
}
get gridEl() {
@@ -23357,8 +23591,7 @@
});
}
getClientPositionKey(client) {
- var _a, _b, _c;
- return `${client.id}-${(_a = client.position) === null || _a === void 0 ? void 0 : _a.sheetId}-${(_b = client.position) === null || _b === void 0 ? void 0 : _b.col}-${(_c = client.position) === null || _c === void 0 ? void 0 : _c.row}`;
+ return `${client.id}-${client.position?.sheetId}-${client.position?.col}-${client.position?.row}`;
}
isCellHovered(col, row) {
return this.hoveredCell.col === col && this.hoveredCell.row === row;
@@ -23567,26 +23800,24 @@
displayWarningCopyPasteNotSupported() {
this.env.raiseError(_lt("Copy/Paste is not supported in this browser."));
}
+ clearFormatting() {
+ this.env.model.dispatch("CLEAR_FORMATTING", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ });
+ }
+ setHorizontalAlign(align) {
+ this.env.model.dispatch("SET_FORMATTING", {
+ sheetId: this.env.model.getters.getActiveSheetId(),
+ target: this.env.model.getters.getSelectedZones(),
+ style: { align },
+ });
+ }
closeMenu() {
this.menuState.isOpen = false;
this.focus();
}
}
- Grid.template = "o-spreadsheet-Grid";
- Grid.components = {
- GridComposer,
- GridOverlay,
- GridPopover,
- HeadersOverlay,
- Menu,
- Autofill,
- ClientTag,
- Highlight,
- Popover,
- VerticalScrollBar,
- HorizontalScrollBar,
- FilterIconsOverlay,
- };
Grid.props = {
sidePanelIsOpen: Boolean,
exposeFocus: Function,
@@ -23599,6 +23830,7 @@
* Represent a raw XML string
*/
class XMLString {
+ xmlString;
/**
* @param xmlString should be a well formed, properly escaped XML string
*/
@@ -23839,10 +24071,8 @@
WarningTypes["NumFmtIdNotSupported"] = "Number format";
})(WarningTypes || (WarningTypes = {}));
class XLSXImportWarningManager {
- constructor() {
- this._parsingWarnings = new Set();
- this._conversionWarnings = new Set();
- }
+ _parsingWarnings = new Set();
+ _conversionWarnings = new Set();
addParsingWarning(warning) {
this._parsingWarnings.add(warning);
}
@@ -23871,7 +24101,13 @@
}
const SUPPORTED_BORDER_STYLES = ["thin"];
- const SUPPORTED_HORIZONTAL_ALIGNMENTS = ["general", "left", "center", "right"];
+ const SUPPORTED_HORIZONTAL_ALIGNMENTS = [
+ "general",
+ "left",
+ "center",
+ "right",
+ ];
+ const SUPPORTED_VERTICAL_ALIGNMENTS = ["top", "center", "bottom"];
const SUPPORTED_FONTS = ["Arial"];
const SUPPORTED_FILL_PATTERNS = ["solid"];
const SUPPORTED_CF_TYPES = [
@@ -23924,6 +24160,20 @@
centerContinuous: "center",
distributed: "center",
};
+ /** Conversion map Vertical Alignment in XLSX => Vertical Alignment in o_spreadsheet */
+ const V_ALIGNMENT_CONVERSION_MAP = {
+ top: "top",
+ center: "middle",
+ bottom: "bottom",
+ justify: "middle",
+ distributed: "middle",
+ };
+ /** Conversion map Vertical Alignment in o-spreadsheet => Vertical Alignment in XLSX */
+ const V_ALIGNMENT_EXPORT_CONVERSION_MAP = {
+ top: "top",
+ middle: "center",
+ bottom: "bottom",
+ };
/** Convert the "CellIs" cf operator.
* We have all the operators that the xlsx have, but ours begin with a uppercase character */
function convertCFCellIsOperator(xlsxCfOperator) {
@@ -24320,20 +24570,19 @@
* Excel format are defined in openXML §18.8.31
*/
function convertXlsxFormat(numFmtId, formats, warningManager) {
- var _a, _b, _c;
if (numFmtId === 0) {
return undefined;
}
// Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
- let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
+ let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || formats.find((f) => f.id === numFmtId)?.format;
if (format) {
try {
let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
// Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
- const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
- const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
+ const numberOfQuotes = convertedFormat.match(/"/g)?.length || 0;
+ const numberOfOpenBrackets = convertedFormat.match(/\[/g)?.length || 0;
if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
throw new Error("Multiple escaped blocks in format");
}
@@ -24364,7 +24613,7 @@
}
}
function isXlsxDateFormat(format) {
- return format.match(XLSX_DATE_FORMAT_REGEX) !== null;
+ return XLSX_DATE_FORMAT_REGEX.test(format);
}
function convertDateFormat$1(format) {
// Some of these aren't defined neither in the OpenXML spec not the Xlsx extension of OpenXML,
@@ -24409,24 +24658,27 @@
return arrayToObject(stylesArray, 1);
}
function convertStyle(styleStruct, warningManager) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
- addStyleWarnings(styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.fontStyle, styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.fillStyle, warningManager);
- addHorizontalAlignmentWarnings((_a = styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.alignment) === null || _a === void 0 ? void 0 : _a.horizontal, warningManager);
- addVerticalAlignmentWarnings((_b = styleStruct === null || styleStruct === void 0 ? void 0 : styleStruct.alignment) === null || _b === void 0 ? void 0 : _b.vertical, warningManager);
+ addStyleWarnings(styleStruct?.fontStyle, styleStruct?.fillStyle, warningManager);
+ addHorizontalAlignmentWarnings(styleStruct?.alignment?.horizontal, warningManager);
+ addVerticalAlignmentWarnings(styleStruct?.alignment?.vertical, warningManager);
return {
- bold: (_c = styleStruct.fontStyle) === null || _c === void 0 ? void 0 : _c.bold,
- italic: (_d = styleStruct.fontStyle) === null || _d === void 0 ? void 0 : _d.italic,
- strikethrough: (_e = styleStruct.fontStyle) === null || _e === void 0 ? void 0 : _e.strike,
- underline: (_f = styleStruct.fontStyle) === null || _f === void 0 ? void 0 : _f.underline,
- align: ((_g = styleStruct.alignment) === null || _g === void 0 ? void 0 : _g.horizontal)
+ bold: styleStruct.fontStyle?.bold,
+ italic: styleStruct.fontStyle?.italic,
+ strikethrough: styleStruct.fontStyle?.strike,
+ underline: styleStruct.fontStyle?.underline,
+ verticalAlign: styleStruct.alignment?.vertical
+ ? V_ALIGNMENT_CONVERSION_MAP[styleStruct.alignment.vertical]
+ : undefined,
+ align: styleStruct.alignment?.horizontal
? H_ALIGNMENT_CONVERSION_MAP[styleStruct.alignment.horizontal]
: undefined,
// In xlsx fills, bgColor is the color of the fill, and fgColor is the color of the pattern above the background, except in solid fills
- fillColor: ((_h = styleStruct.fillStyle) === null || _h === void 0 ? void 0 : _h.patternType) === "solid"
- ? convertColor((_j = styleStruct.fillStyle) === null || _j === void 0 ? void 0 : _j.fgColor)
- : convertColor((_k = styleStruct.fillStyle) === null || _k === void 0 ? void 0 : _k.bgColor),
- textColor: convertColor((_l = styleStruct.fontStyle) === null || _l === void 0 ? void 0 : _l.color),
- fontSize: (_m = styleStruct.fontStyle) === null || _m === void 0 ? void 0 : _m.size,
+ fillColor: styleStruct.fillStyle?.patternType === "solid"
+ ? convertColor(styleStruct.fillStyle?.fgColor)
+ : convertColor(styleStruct.fillStyle?.bgColor),
+ textColor: convertColor(styleStruct.fontStyle?.color),
+ fontSize: styleStruct.fontStyle?.size,
+ wrapping: styleStruct.alignment?.wrapText ? "wrap" : "overflow",
};
}
function convertFormats(data, warningManager) {
@@ -24466,8 +24718,8 @@
}
}
function addVerticalAlignmentWarnings(alignment, warningManager) {
- if (alignment) {
- warningManager.generateNotSupportedWarning(WarningTypes.VerticalAlignmentNotSupported);
+ if (alignment && !SUPPORTED_VERTICAL_ALIGNMENTS.includes(alignment)) {
+ warningManager.generateNotSupportedWarning(WarningTypes.VerticalAlignmentNotSupported, alignment, SUPPORTED_VERTICAL_ALIGNMENTS);
}
}
@@ -24693,3596 +24945,11 @@
}
}
- // -------------------------------------
- // CF HELPERS
- // -------------------------------------
- /**
- * Convert the conditional formatting o-spreadsheet operator to
- * the corresponding excel operator.
- * */
- function convertOperator(operator) {
- switch (operator) {
- case "IsNotEmpty":
- return "notContainsBlanks";
- case "IsEmpty":
- return "containsBlanks";
- case "NotContains":
- return "notContainsBlanks";
- default:
- return operator.charAt(0).toLowerCase() + operator.slice(1);
- }
- }
- // -------------------------------------
- // WORKSHEET HELPERS
- // -------------------------------------
- function getCellType(value) {
- switch (typeof value) {
- case "boolean":
- return "b";
- case "string":
- return "str";
- case "number":
- return "n";
- }
- }
- function convertHeightToExcel(height) {
- return Math.round(HEIGHT_FACTOR * height * 100) / 100;
- }
- function convertWidthToExcel(width) {
- return Math.round(WIDTH_FACTOR * width * 100) / 100;
- }
- function convertHeightFromExcel(height) {
- if (!height)
- return height;
- return Math.round((height / HEIGHT_FACTOR) * 100) / 100;
- }
- function convertWidthFromExcel(width) {
- if (!width)
- return width;
- return Math.round((width / WIDTH_FACTOR) * 100) / 100;
- }
- function convertBorderDescr(descr) {
- if (!descr) {
- return undefined;
- }
- return {
- style: descr[0],
- color: { rgb: descr[1] },
- };
- }
- function extractStyle(cell, data) {
- let style = {};
- if (cell.style) {
- style = data.styles[cell.style];
- }
- const format = cell.format ? data.formats[cell.format] : undefined;
- const exportedBorder = {};
- if (cell.border) {
- const border = data.borders[cell.border];
- exportedBorder.left = convertBorderDescr(border.left);
- exportedBorder.right = convertBorderDescr(border.right);
- exportedBorder.bottom = convertBorderDescr(border.bottom);
- exportedBorder.top = convertBorderDescr(border.top);
- }
- const styles = {
- font: {
- size: (style === null || style === void 0 ? void 0 : style.fontSize) || DEFAULT_FONT_SIZE,
- color: { rgb: (style === null || style === void 0 ? void 0 : style.textColor) ? style.textColor : "000000" },
- family: 2,
- name: "Arial",
- },
- fill: (style === null || style === void 0 ? void 0 : style.fillColor)
- ? {
- fgColor: { rgb: style.fillColor },
- }
- : { reservedAttribute: "none" },
- numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
- border: exportedBorder || {},
- alignment: {
- vertical: "center",
- horizontal: style.align,
- },
- };
- styles.font["strike"] = !!(style === null || style === void 0 ? void 0 : style.strikethrough) || undefined;
- styles.font["underline"] = !!(style === null || style === void 0 ? void 0 : style.underline) || undefined;
- styles.font["bold"] = !!(style === null || style === void 0 ? void 0 : style.bold) || undefined;
- styles.font["italic"] = !!(style === null || style === void 0 ? void 0 : style.italic) || undefined;
- return styles;
- }
- function normalizeStyle(construct, styles) {
- const { id: fontId } = pushElement(styles["font"], construct.fonts);
- const { id: fillId } = pushElement(styles["fill"], construct.fills);
- const { id: borderId } = pushElement(styles["border"], construct.borders);
- // Normalize this
- const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
- const style = {
- fontId,
- fillId,
- borderId,
- numFmtId,
- alignment: {
- vertical: styles.alignment.vertical,
- horizontal: styles.alignment.horizontal,
- },
- };
- const { id } = pushElement(style, construct.styles);
- return id;
- }
- function convertFormat(format, numFmtStructure) {
- if (!format) {
- return 0;
- }
- let formatId = XLSX_FORMAT_MAP[format.format];
- if (!formatId) {
- const { id } = pushElement(format, numFmtStructure);
- formatId = id + FIRST_NUMFMT_ID;
- }
- return formatId;
- }
- /**
- * Add a relation to the given file and return its id.
- */
- function addRelsToFile(relsFiles, path, rel) {
- let relsFile = relsFiles.find((file) => file.path === path);
- // the id is a one-based int casted as string
- let id;
- if (!relsFile) {
- id = "rId1";
- relsFiles.push({ path, rels: [{ ...rel, id }] });
- }
- else {
- id = `rId${(relsFile.rels.length + 1).toString()}`;
- relsFile.rels.push({
- ...rel,
- id,
- });
- }
- return id;
- }
- function pushElement(property, propertyList) {
- for (let [key, value] of Object.entries(propertyList)) {
- if (JSON.stringify(value) === JSON.stringify(property)) {
- return { id: parseInt(key, 10), list: propertyList };
- }
- }
- let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));
- if (elemId === -1) {
- propertyList.push(property);
- elemId = propertyList.length - 1;
- }
- return {
- id: elemId,
- list: propertyList,
- };
- }
- const chartIds = [];
- /**
- * Convert a chart o-spreadsheet id to a xlsx id which
- * are unsigned integers (starting from 1).
- */
- function convertChartId(chartId) {
- const xlsxId = chartIds.findIndex((id) => id === chartId);
- if (xlsxId === -1) {
- chartIds.push(chartId);
- return chartIds.length;
- }
- return xlsxId + 1;
- }
- const imageIds = [];
- /**
- * Convert a image o-spreadsheet id to a xlsx id which
- * are unsigned integers (starting from 1).
- */
- function convertImageId(imageId) {
- const xlsxId = imageIds.findIndex((id) => id === imageId);
- if (xlsxId === -1) {
- imageIds.push(imageId);
- return imageIds.length;
- }
- return xlsxId + 1;
- }
- /**
- * Convert a value expressed in dot to EMU.
- * EMU = English Metrical Unit
- * There are 914400 EMU per inch.
- *
- * /!\ A value expressed in EMU cannot be fractional.
- * See https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement
- */
- function convertDotValueToEMU(value) {
- const DPI = 96;
- return Math.round((value * 914400) / DPI);
- }
- function getRangeSize(reference, defaultSheetIndex, data) {
- let xc = reference;
- let sheetName = undefined;
- ({ xc, sheetName } = splitReference(reference));
- let rangeSheetIndex;
- if (sheetName) {
- const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
- if (index < 0) {
- throw new Error("Unable to find a sheet with the name " + sheetName);
- }
- rangeSheetIndex = index;
- }
- else {
- rangeSheetIndex = Number(defaultSheetIndex);
- }
- const zone = toUnboundedZone(xc);
- if (zone.right === undefined) {
- zone.right = data.sheets[rangeSheetIndex].colNumber;
- }
- if (zone.bottom === undefined) {
- zone.bottom = data.sheets[rangeSheetIndex].rowNumber;
- }
- return (zone.right - zone.left + 1) * (zone.bottom - zone.top + 1);
- }
- function convertEMUToDotValue(value) {
- const DPI = 96;
- return Math.round((value * DPI) / 914400);
- }
- /**
- * Get the position of the start of a column in Excel (in px).
- */
- function getColPosition(colIndex, sheetData) {
- var _a;
- let position = 0;
- for (let i = 0; i < colIndex; i++) {
- const colAtIndex = sheetData.cols.find((col) => i >= col.min && i <= col.max);
- if (colAtIndex === null || colAtIndex === void 0 ? void 0 : colAtIndex.width) {
- position += colAtIndex.width;
- }
- else if ((_a = sheetData.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultColWidth) {
- position += sheetData.sheetFormat.defaultColWidth;
- }
- else {
- position += EXCEL_DEFAULT_COL_WIDTH;
- }
- }
- return position / WIDTH_FACTOR;
- }
- /**
- * Get the position of the start of a row in Excel (in px).
- */
- function getRowPosition(rowIndex, sheetData) {
- var _a;
- let position = 0;
- for (let i = 0; i < rowIndex; i++) {
- const rowAtIndex = sheetData.rows[i];
- if (rowAtIndex === null || rowAtIndex === void 0 ? void 0 : rowAtIndex.height) {
- position += rowAtIndex.height;
- }
- else if ((_a = sheetData.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultRowHeight) {
- position += sheetData.sheetFormat.defaultRowHeight;
- }
- else {
- position += EXCEL_DEFAULT_ROW_HEIGHT;
- }
- }
- return position / HEIGHT_FACTOR;
- }
-
- function convertFigures(sheetData) {
- let id = 1;
- return sheetData.figures
- .map((figure) => convertFigure(figure, (id++).toString(), sheetData))
- .filter(isDefined$1);
- }
- function convertFigure(figure, id, sheetData) {
- const x1 = getColPosition(figure.anchors[0].col, sheetData) +
- convertEMUToDotValue(figure.anchors[0].colOffset);
- const x2 = getColPosition(figure.anchors[1].col, sheetData) +
- convertEMUToDotValue(figure.anchors[1].colOffset);
- const y1 = getRowPosition(figure.anchors[0].row, sheetData) +
- convertEMUToDotValue(figure.anchors[0].rowOffset);
- const y2 = getRowPosition(figure.anchors[1].row, sheetData) +
- convertEMUToDotValue(figure.anchors[1].rowOffset);
- const width = x2 - x1;
- const height = y2 - y1;
- const chartData = convertChartData(figure.data);
- if (!chartData)
- return undefined;
- return {
- id: id,
- x: x1,
- y: y1,
- width: width,
- height: height,
- tag: "chart",
- data: convertChartData(figure.data),
- };
- }
- function convertChartData(chartData) {
- const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
- const labelRange = chartData.labelRange
- ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
- : undefined;
- let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
- // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
- if (chartData.type === "pie") {
- dataSets.reverse();
- }
- return {
- dataSets,
- dataSetsHaveTitle,
- labelRange,
- title: chartData.title || "",
- type: chartData.type,
- background: convertColor({ rgb: chartData.backgroundColor }) || "#FFFFFF",
- verticalAxisPosition: chartData.verticalAxisPosition,
- legendPosition: chartData.legendPosition,
- stacked: chartData.stacked || false,
- aggregated: false,
- labelsAsText: false,
- };
- }
- function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
- let { sheetName, xc } = splitReference(range);
- if (sheetName) {
- sheetName = getCanonicalSheetName(sheetName) + "!";
- }
- else {
- sheetName = "";
- }
- let zone = toUnboundedZone(xc);
- if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
- const height = zone.bottom - zone.top + 1;
- const width = zone.right - zone.left + 1;
- if (height === 1) {
- zone = { ...zone, left: zone.left - 1 };
- }
- else if (width === 1) {
- zone = { ...zone, top: zone.top - 1 };
- }
- }
- const dataXC = zoneToXc(zone);
- return sheetName + dataXC;
- }
-
- /**
- * Match external reference (ex. '[1]Sheet 3'!$B$4)
- *
- * First match group is the external reference id
- * Second match group is the sheet id
- * Third match group is the reference of the cell
- */
- const externalReferenceRegex = new RegExp(/'?\[([0-9]*)\](.*)'?!(\$?[a-zA-Z]*\$?[0-9]*)/g);
- const subtotalRegex = new RegExp(/SUBTOTAL\(([0-9]*),/g);
- const cellRegex = new RegExp(cellReference.source, "ig");
- function convertFormulasContent(sheet, data) {
- const sfMap = getSharedFormulasMap(sheet);
- for (let cell of sheet.rows.map((row) => row.cells).flat()) {
- if (cell === null || cell === void 0 ? void 0 : cell.formula) {
- cell.formula.content =
- cell.formula.sharedIndex !== undefined && !cell.formula.content
- ? "=" + adaptFormula(cell.xc, sfMap[cell.formula.sharedIndex])
- : "=" + cell.formula.content;
- cell.formula.content = convertFormula(cell.formula.content, data);
- }
- }
- }
- function getSharedFormulasMap(sheet) {
- const formulas = {};
- for (let row of sheet.rows) {
- for (let cell of row.cells) {
- if (cell.formula && cell.formula.sharedIndex !== undefined && cell.formula.content) {
- formulas[cell.formula.sharedIndex] = { refCellXc: cell.xc, formula: cell.formula.content };
- }
- }
- }
- return formulas;
- }
- /**
- * Convert an XLSX formula into something we can evaluate.
- * - remove _xlfn. flags before function names
- * - convert the SUBTOTAL(index, formula) function to the function given by its index
- * - change #REF! into #REF
- * - convert external references into their value
- */
- function convertFormula(formula, data) {
- formula = formula.replace("_xlfn.", "");
- formula = formula.replace(/#REF!/g, "#REF");
- // SUBOTOTAL function, eg. =SUBTOTAL(3, {formula})
- formula = formula.replace(subtotalRegex, (match, functionId) => {
- const convertedFunction = SUBTOTAL_FUNCTION_CONVERSION_MAP[functionId];
- return convertedFunction ? convertedFunction + "(" : match;
- });
- // External references, eg. ='[1]Sheet 3'!$B$4
- formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
- var _a;
- externalRefId = Number(externalRefId) - 1;
- cellRef = cellRef.replace(/\$/g, "");
- const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
- if (sheetIndex === -1) {
- return match;
- }
- const externalDataset = (_a = data.externalBooks[externalRefId].datasets.find((dataset) => dataset.sheetId === sheetIndex)) === null || _a === void 0 ? void 0 : _a.data;
- if (!externalDataset) {
- return match;
- }
- const datasetValue = externalDataset && externalDataset[cellRef];
- const convertedValue = Number(datasetValue) ? datasetValue : `"${datasetValue}"`;
- return convertedValue || match;
- });
- return formula;
- }
- /**
- * Transform a shared formula for the given target.
- *
- * This will compute the offset between the original cell of the shared formula and the target cell,
- * then apply this offset to all the ranges in the formula (taking fixed references into account)
- */
- function adaptFormula(targetCell, sf) {
- const refPosition = toCartesian(sf.refCellXc);
- let newFormula = sf.formula.slice();
- let match;
- do {
- match = cellRegex.exec(newFormula);
- if (match) {
- const formulaPosition = toCartesian(match[0].replace("$", ""));
- const targetPosition = toCartesian(targetCell);
- const rangePart = {
- colFixed: match[0].startsWith("$"),
- rowFixed: match[0].includes("$", 1),
- };
- const offset = {
- col: targetPosition.col - refPosition.col,
- row: targetPosition.row - refPosition.row,
- };
- const offsettedPosition = {
- col: rangePart.colFixed ? formulaPosition.col : formulaPosition.col + offset.col,
- row: rangePart.rowFixed ? formulaPosition.row : formulaPosition.row + offset.row,
- };
- newFormula =
- newFormula.slice(0, match.index) +
- toXC(offsettedPosition.col, offsettedPosition.row, rangePart) +
- newFormula.slice(match.index + match[0].length);
- }
- } while (match);
- return newFormula;
- }
-
- function convertSheets(data, warningManager) {
- return data.sheets.map((sheet) => {
- convertFormulasContent(sheet, data);
- const sheetDims = getSheetDims(sheet);
- const sheetOptions = sheet.sheetViews[0];
- return {
- id: sheet.sheetName,
- areGridLinesVisible: sheetOptions ? sheetOptions.showGridLines : true,
- name: sheet.sheetName,
- colNumber: sheetDims[0],
- rowNumber: sheetDims[1],
- cells: convertCells(sheet, data, sheetDims, warningManager),
- merges: sheet.merges,
- cols: convertCols(sheet, sheetDims[0]),
- rows: convertRows(sheet, sheetDims[1]),
- conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),
- figures: convertFigures(sheet),
- isVisible: sheet.isVisible,
- panes: sheetOptions
- ? { xSplit: sheetOptions.pane.xSplit, ySplit: sheetOptions.pane.ySplit }
- : { xSplit: 0, ySplit: 0 },
- filterTables: [],
- };
- });
- }
- function convertCols(sheet, numberOfCols) {
- var _a;
- const cols = {};
- // Excel begins indexes at 1
- for (let i = 1; i < numberOfCols + 1; i++) {
- const col = sheet.cols.find((col) => col.min <= i && i <= col.max);
- let colSize;
- if (col && col.width)
- colSize = col.width;
- else if ((_a = sheet.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultColWidth)
- colSize = sheet.sheetFormat.defaultColWidth;
- else
- colSize = EXCEL_DEFAULT_COL_WIDTH;
- cols[i - 1] = { size: convertWidthFromExcel(colSize), isHidden: col === null || col === void 0 ? void 0 : col.hidden };
- }
- return cols;
- }
- function convertRows(sheet, numberOfRows) {
- var _a;
- const rows = {};
- // Excel begins indexes at 1
- for (let i = 1; i < numberOfRows + 1; i++) {
- const row = sheet.rows.find((row) => row.index === i);
- let rowSize;
- if (row && row.height)
- rowSize = row.height;
- else if ((_a = sheet.sheetFormat) === null || _a === void 0 ? void 0 : _a.defaultRowHeight)
- rowSize = sheet.sheetFormat.defaultRowHeight;
- else
- rowSize = EXCEL_DEFAULT_ROW_HEIGHT;
- rows[i - 1] = { size: convertHeightFromExcel(rowSize), isHidden: row === null || row === void 0 ? void 0 : row.hidden };
- }
- return rows;
- }
- /** Remove newlines (\n) in shared strings, We do not support them */
- function convertSharedStrings(xlsxSharedStrings) {
- return xlsxSharedStrings.map((str) => str.replace(/\n/g, ""));
- }
- function convertCells(sheet, data, sheetDims, warningManager) {
- const cells = {};
- const sharedStrings = convertSharedStrings(data.sharedStrings);
- const hyperlinkMap = sheet.hyperlinks.reduce((map, link) => {
- map[link.xc] = link;
- return map;
- }, {});
- for (let row of sheet.rows) {
- for (let cell of row.cells) {
- cells[cell.xc] = {
- content: getCellValue(cell, hyperlinkMap, sharedStrings, warningManager),
- // + 1 : our indexes for normalized values begin at 1 and not 0
- style: cell.styleIndex ? cell.styleIndex + 1 : undefined,
- border: cell.styleIndex ? data.styles[cell.styleIndex].borderId + 1 : undefined,
- format: cell.styleIndex ? data.styles[cell.styleIndex].numFmtId + 1 : undefined,
- };
- }
- }
- // Apply row style
- for (let row of sheet.rows.filter((row) => row.styleIndex)) {
- for (let colIndex = 1; colIndex <= sheetDims[0]; colIndex++) {
- const xc = toXC(colIndex - 1, row.index - 1); // Excel indexes start at 1
- let cell = cells[xc];
- if (!cell) {
- cell = {};
- cells[xc] = cell;
- }
- cell.style = cell.style ? cell.style : row.styleIndex + 1;
- cell.border = cell.border ? cell.border : data.styles[row.styleIndex].borderId + 1;
- cell.format = cell.format ? cell.format : data.styles[row.styleIndex].numFmtId + 1;
- }
- }
- // Apply col style
- for (let col of sheet.cols.filter((col) => col.styleIndex)) {
- for (let colIndex = col.min; colIndex <= Math.min(col.max, sheetDims[0]); colIndex++) {
- for (let rowIndex = 1; rowIndex <= sheetDims[1]; rowIndex++) {
- const xc = toXC(colIndex - 1, rowIndex - 1); // Excel indexes start at 1
- let cell = cells[xc];
- if (!cell) {
- cell = {};
- cells[xc] = cell;
- }
- cell.style = cell.style ? cell.style : col.styleIndex + 1;
- cell.border = cell.border ? cell.border : data.styles[col.styleIndex].borderId + 1;
- cell.format = cell.format ? cell.format : data.styles[col.styleIndex].numFmtId + 1;
- }
- }
- }
- return cells;
- }
- function getCellValue(cell, hyperLinksMap, sharedStrings, warningManager) {
- let cellValue;
- switch (cell.type) {
- case "sharedString":
- const ssIndex = parseInt(cell.value, 10);
- cellValue = sharedStrings[ssIndex];
- break;
- case "boolean":
- cellValue = Number(cell.value) ? "TRUE" : "FALSE";
- break;
- case "date": // I'm not sure where this is used rather than a number with a format
- case "error": // I don't think Excel really uses this
- case "inlineStr":
- case "number":
- case "str":
- cellValue = cell.value;
- break;
- }
- if (cellValue && hyperLinksMap[cell.xc]) {
- cellValue = convertHyperlink(hyperLinksMap[cell.xc], cellValue, warningManager);
- }
- if (cell.formula) {
- cellValue = cell.formula.content;
- }
- return cellValue;
- }
- function convertHyperlink(link, cellValue, warningManager) {
- const label = link.display || cellValue;
- if (!link.relTarget && !link.location) {
- warningManager.generateNotSupportedWarning(WarningTypes.BadlyFormattedHyperlink);
- }
- const url = link.relTarget
- ? link.relTarget
- : buildSheetLink(splitReference(link.location).sheetName);
- return markdownLink(label, url);
- }
- function getSheetDims(sheet) {
- const dims = [0, 0];
- for (let row of sheet.rows) {
- dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));
- dims[1] = Math.max(dims[1], row.index);
- }
- dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);
- dims[1] = Math.max(dims[1], EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS);
- return dims;
- }
-
- const TABLE_HEADER_STYLE = {
- fillColor: "#000000",
- textColor: "#ffffff",
- bold: true,
- };
- const TABLE_HIGHLIGHTED_CELL_STYLE = {
- bold: true,
- };
- const TABLE_BORDER_STYLE = ["thin", "#000000FF"];
- /**
- * Convert the imported XLSX tables.
- *
- * We will create a FilterTable if the imported table have filters, then apply a style in all the cells of the table
- * and convert the table-specific formula references into standard references.
- *
- * Change the converted data in-place.
- */
- function convertTables(convertedData, xlsxData) {
- for (const xlsxSheet of xlsxData.sheets) {
- for (const table of xlsxSheet.tables) {
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
- if (!sheet || !table.autoFilter)
- continue;
- if (!sheet.filterTables)
- sheet.filterTables = [];
- sheet.filterTables.push({ range: table.ref });
- }
- }
- applyTableStyle(convertedData, xlsxData);
- convertTableFormulaReferences(convertedData.sheets, xlsxData.sheets);
- }
- /**
- * Apply a style to all the cells that are in a table, and add the created styles in the converted data.
- *
- * In XLSXs, the style of the cells of a table are not directly in the sheet, but rather deduced from the style of
- * the table that is defined in the table's XML file. The style of the table is a string referencing a standard style
- * defined in the OpenXML specifications. As there are 80+ different styles, we won't implement every one of them but
- * we will just define a style that will be used for all the imported tables.
- */
- function applyTableStyle(convertedData, xlsxData) {
- var _a, _b, _c, _d;
- const styles = objectToArray(convertedData.styles);
- const borders = objectToArray(convertedData.borders);
- for (let xlsxSheet of xlsxData.sheets) {
- for (let table of xlsxSheet.tables) {
- const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
- if (!sheet)
- continue;
- const tableZone = toZone(table.ref);
- // Table style
- for (let i = 0; i < table.headerRowCount; i++) {
- applyStyleToZone(TABLE_HEADER_STYLE, { ...tableZone, bottom: tableZone.top + i }, sheet.cells, styles);
- }
- for (let i = 0; i < table.totalsRowCount; i++) {
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, top: tableZone.bottom - i }, sheet.cells, styles);
- }
- if ((_a = table.style) === null || _a === void 0 ? void 0 : _a.showFirstColumn) {
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, right: tableZone.left }, sheet.cells, styles);
- }
- if ((_b = table.style) === null || _b === void 0 ? void 0 : _b.showLastColumn) {
- applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, left: tableZone.right }, sheet.cells, styles);
- }
- // Table borders
- // Borders at : table outline + col(/row) if showColumnStripes(/showRowStripes) + border above totalRow
- for (let col = tableZone.left; col <= tableZone.right; col++) {
- for (let row = tableZone.top; row <= tableZone.bottom; row++) {
- const xc = toXC(col, row);
- const cell = sheet.cells[xc];
- const border = {
- left: col === tableZone.left || ((_c = table.style) === null || _c === void 0 ? void 0 : _c.showColumnStripes)
- ? TABLE_BORDER_STYLE
- : undefined,
- right: col === tableZone.right ? TABLE_BORDER_STYLE : undefined,
- top: row === tableZone.top ||
- ((_d = table.style) === null || _d === void 0 ? void 0 : _d.showRowStripes) ||
- row > tableZone.bottom - table.totalsRowCount
- ? TABLE_BORDER_STYLE
- : undefined,
- bottom: row === tableZone.bottom ? TABLE_BORDER_STYLE : undefined,
- };
- const newBorder = (cell === null || cell === void 0 ? void 0 : cell.border) ? { ...borders[cell.border], ...border } : border;
- let borderIndex = borders.findIndex((border) => deepEquals(border, newBorder));
- if (borderIndex === -1) {
- borderIndex = borders.length;
- borders.push(newBorder);
- }
- if (cell) {
- cell.border = borderIndex;
- }
- else {
- sheet.cells[xc] = { border: borderIndex };
- }
- }
- }
- }
- }
- convertedData.styles = arrayToObject(styles);
- convertedData.borders = arrayToObject(borders);
- }
- /**
- * Apply a style to all the cells in the zone. The applied style WILL NOT overwrite values in existing style of the cell.
- *
- * If a style that was not in the styles array was applied, push it into the style array.
- */
- function applyStyleToZone(appliedStyle, zone, cells, styles) {
- for (let col = zone.left; col <= zone.right; col++) {
- for (let row = zone.top; row <= zone.bottom; row++) {
- const xc = toXC(col, row);
- const cell = cells[xc];
- const newStyle = (cell === null || cell === void 0 ? void 0 : cell.style) ? { ...styles[cell.style], ...appliedStyle } : appliedStyle;
- let styleIndex = styles.findIndex((style) => deepEquals(style, newStyle));
- if (styleIndex === -1) {
- styleIndex = styles.length;
- styles.push(newStyle);
- }
- if (cell) {
- cell.style = styleIndex;
- }
- else {
- cells[xc] = { style: styleIndex };
- }
- }
- }
- }
- /**
- * In all the sheets, replace the table-only references in the formula cells with standard references.
- */
- function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
- for (let sheet of convertedSheets) {
- const tables = xlsxSheets.find((s) => s.sheetName === sheet.name).tables;
- for (let table of tables) {
- const tabRef = table.name + "[";
- for (let position of positions(toZone(table.ref))) {
- const xc = toXC(position.col, position.row);
- const cell = sheet.cells[xc];
- if (cell && cell.content && cell.content.startsWith("=")) {
- let refIndex;
- while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {
- let reference = cell.content.slice(refIndex + tabRef.length);
- // Expression can either be tableName[colName] or tableName[[#This Row], [colName]]
- let endIndex = reference.indexOf("]");
- if (reference.startsWith(`[`)) {
- endIndex = reference.indexOf("]", endIndex + 1);
- endIndex = reference.indexOf("]", endIndex + 1);
- }
- reference = reference.slice(0, endIndex);
- const convertedRef = convertTableReference(reference, table, xc);
- cell.content =
- cell.content.slice(0, refIndex) +
- convertedRef +
- cell.content.slice(tabRef.length + refIndex + endIndex + 1);
- }
- }
- }
- }
- }
- }
- /**
- * Convert table-specific references in formulas into standard references.
- *
- * A reference in a table can have the form (only the part between brackets should be given to this function):
- * - tableName[colName] : reference to the whole column "colName"
- * - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
- *
- * The available keywords are :
- * - #All : all the column (including totals)
- * - #Data : only the column data (no headers/totals)
- * - #Headers : only the header of the column
- * - #Totals : only the totals of the column
- * - #This Row : only the element in the same row as the cell
- */
- function convertTableReference(expr, table, cellXc) {
- const refElements = expr.split(",");
- const tableZone = toZone(table.ref);
- const refZone = { ...tableZone };
- let isReferencedZoneValid = true;
- // Single column reference
- if (refElements.length === 1) {
- const colRelativeIndex = table.cols.findIndex((col) => col.name === refElements[0]);
- refZone.left = refZone.right = colRelativeIndex + tableZone.left;
- if (table.headerRowCount) {
- refZone.top += table.headerRowCount;
- }
- if (table.totalsRowCount) {
- refZone.bottom -= 1;
- }
- }
- // Other references
- else {
- switch (refElements[0].slice(1, refElements[0].length - 1)) {
- case "#All":
- refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
- refZone.bottom = tableZone.bottom;
- break;
- case "#Data":
- refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
- refZone.bottom = table.totalsRowCount ? tableZone.bottom + 1 : tableZone.bottom;
- break;
- case "#This Row":
- refZone.top = refZone.bottom = toCartesian(cellXc).row;
- break;
- case "#Headers":
- refZone.top = refZone.bottom = tableZone.top;
- if (!table.headerRowCount) {
- isReferencedZoneValid = false;
- }
- break;
- case "#Totals":
- refZone.top = refZone.bottom = tableZone.bottom;
- if (!table.totalsRowCount) {
- isReferencedZoneValid = false;
- }
- break;
- }
- const colRef = refElements[1].slice(1, refElements[1].length - 1);
- const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
- refZone.left = refZone.right = colRelativeIndex + tableZone.left;
- }
- if (!isReferencedZoneValid) {
- return INCORRECT_RANGE_STRING;
- }
- return refZone.top !== refZone.bottom ? zoneToXc(refZone) : toXC(refZone.left, refZone.top);
- }
-
- // -------------------------------------
- // XML HELPERS
- // -------------------------------------
- function createXMLFile(doc, path, contentType) {
- return {
- content: new XMLSerializer().serializeToString(doc),
- path,
- contentType,
- };
- }
- function xmlEscape(str) {
- return String(str)
- .replace(/\&/g, "&")
- .replace(/\/g, ">")
- .replace(/\"/g, """)
- .replace(/\'/g, "'");
- }
- function formatAttributes(attrs) {
- return new XMLString(attrs.map(([key, val]) => `${key}="${xmlEscape(val)}"`).join(" "));
- }
- function parseXML(xmlString, mimeType = "text/xml") {
- const document = new DOMParser().parseFromString(xmlString.toString(), mimeType);
- const parserError = document.querySelector("parsererror");
- if (parserError) {
- const errorString = parserError.innerHTML;
- const lineNumber = parseInt(errorString.split(":")[0], 10);
- const xmlStringArray = xmlString.toString().trim().split("\n");
- const xmlPreview = xmlStringArray
- .slice(Math.max(lineNumber - 3, 0), Math.min(lineNumber + 2, xmlStringArray.length))
- .join("\n");
- throw new Error(`XML string could not be parsed: ${errorString}\n${xmlPreview}`);
- }
- return document;
- }
- function getDefaultXLSXStructure() {
- return {
- relsFiles: [],
- sharedStrings: [],
- // default Values that will always be part of the style sheet
- styles: [
- {
- fontId: 0,
- fillId: 0,
- numFmtId: 0,
- borderId: 0,
- alignment: { vertical: "center" },
- },
- ],
- fonts: [
- {
- size: DEFAULT_FONT_SIZE,
- family: 2,
- color: { rgb: "000000" },
- name: "Calibri",
- },
- ],
- fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
- borders: [{}],
- numFmts: [],
- dxfs: [],
- };
- }
- function createOverride(partName, contentType) {
- return escapeXml /*xml*/ `
-
- `;
- }
- function joinXmlNodes(xmlNodes) {
- return new XMLString(xmlNodes.join("\n"));
- }
- /**
- * Escape interpolated values except if the value is already
- * a properly escaped XML string.
- *
- * ```
- * escapeXml`${"This will be escaped"}`
- * ```
- */
- function escapeXml(strings, ...expressions) {
- let str = [strings[0]];
- for (let i = 0; i < expressions.length; i++) {
- const value = expressions[i] instanceof XMLString ? expressions[i] : xmlEscape(expressions[i]);
- str.push(value + strings[i + 1]);
- }
- return new XMLString(concat(str));
- }
- /**
- * Removes the namespace of all the xml tags in the string.
- *
- * Eg. : "ns:test a" => "test a"
- */
- function removeNamespaces(query) {
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
- }
- /**
- * Escape the namespace's colons of all the xml tags in the string.
- *
- * Eg. : "ns:test a" => "ns\\:test a"
- */
- function escapeNamespaces(query) {
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
- }
- /**
- * 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;
- }
-
- class AttributeValue {
- constructor(value) {
- this.value = value;
- }
- asString() {
- return fixXlsxUnicode(String(this.value));
- }
- asBool() {
- return Boolean(Number(this.value));
- }
- asNum() {
- return Number(this.value);
- }
- }
- class XlsxBaseExtractor {
- constructor(rootFile, xlsxStructure, warningManager) {
- // 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
- this.currentFile = undefined;
- 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) => {
- this.relationships[rel.id] = rel;
- });
- }
- }
- /**
- * Extract all the relationships inside a .xml.rels file
- */
- extractRelationships(relFile) {
- return this.mapOnElements({ parent: relFile.xml, query: "Relationship" }, (relationshipElement) => {
- return {
- id: this.extractAttr(relationshipElement, "Id", { required: true }).asString(),
- target: this.extractAttr(relationshipElement, "Target", { required: true }).asString(),
- type: this.extractAttr(relationshipElement, "Type", { required: true }).asString(),
- };
- });
- }
- /**
- * Get the list of all the XLSX files in the XLSX file structure
- */
- getListOfFiles() {
- const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);
- return files;
- }
- /**
- * Return an array containing the return value of the given function applied to all the XML elements
- * found using the MapOnElementArgs.
- *
- * The arguments contains :
- * - query : a QuerySelector string to find the elements to apply the function to
- * - parent : an XML element or XML document in which to find the queried elements
- * - children : if true, the function is applied on the direct children of the queried element
- *
- * This method will also handle the errors thrown in the argument function.
- */
- mapOnElements(args, fct) {
- var _a;
- const ret = [];
- const oldWorkingDocument = this.currentFile;
- let elements;
- if (args.children) {
- const children = (_a = this.querySelector(args.parent, args.query)) === null || _a === void 0 ? void 0 : _a.children;
- elements = children ? children : [];
- }
- else {
- elements = this.querySelectorAll(args.parent, args.query);
- }
- if (elements) {
- for (let element of elements) {
- try {
- ret.push(fct(element));
- }
- catch (e) {
- this.catchErrorOnElement(e, element);
- }
- }
- }
- this.currentFile = oldWorkingDocument;
- return ret;
- }
- /**
- * Log an error caught when parsing an element in the warningManager.
- */
- catchErrorOnElement(error, onElement) {
- const errorMsg = onElement
- ? `Error when parsing an element <${onElement.tagName}> of file ${this.currentFile}, skip this element. \n${error.stack}`
- : `Error when parsing file ${this.currentFile}.`;
- this.warningManager.addParsingWarning([errorMsg, error.message].join("\n"));
- }
- /**
- * Extract an attribute from an Element.
- *
- * If the attribute is required but was not found, will add a warning in the warningManager if it was given a default
- * value, and throw an error if no default value was given.
- *
- * Can only return undefined value for non-required attributes without default value.
- */
- extractAttr(e, attName, optionalArgs) {
- const attribute = e.attributes[attName];
- if (!attribute)
- this.handleMissingValue(e, `attribute "${attName}"`, optionalArgs);
- const value = (attribute === null || attribute === void 0 ? void 0 : attribute.value) ? attribute.value : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default;
- return (value === undefined ? undefined : new AttributeValue(value));
- }
- /**
- * Extract the text content of an Element.
- *
- * If the text content is required but was not found, will add a warning in the warningManager if it was given a default
- * value, and throw an error if no default value was given.
- *
- * Can only return undefined value for non-required text content without default value.
- */
- extractTextContent(element, optionalArgs) {
- var _a;
- if ((optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) !== undefined && typeof optionalArgs.default !== "string") {
- throw new Error("extractTextContent default value should be a string");
- }
- const shouldPreserveSpaces = ((_a = element === null || element === void 0 ? void 0 : element.attributes["xml:space"]) === null || _a === void 0 ? void 0 : _a.value) === "preserve";
- let textContent = element === null || element === void 0 ? void 0 : element.textContent;
- if (!element || textContent === null) {
- this.handleMissingValue(element, `text content`, optionalArgs);
- }
- if (textContent) {
- textContent = shouldPreserveSpaces ? textContent : textContent.trim();
- }
- return (textContent ? fixXlsxUnicode(textContent) : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default);
- }
- /**
- * Extract an attribute of a child of the given element.
- *
- * The reference of a child can be a string (tag of the child) or an number (index in the list of children of the element)
- *
- * If the attribute is required but either the attribute or the referenced child element was not found, it will
- * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.
- *
- * Can only return undefined value for non-required attributes without default value.
- */
- extractChildAttr(e, childRef, attName, optionalArgs) {
- var _a;
- let child;
- if (typeof childRef === "number") {
- child = e.children[childRef];
- }
- else {
- child = this.querySelector(e, childRef);
- }
- if (!child) {
- this.handleMissingValue(e, typeof childRef === "number" ? `child at index ${childRef}` : `child <${childRef}>`, optionalArgs);
- }
- const value = child
- ? (_a = this.extractAttr(child, attName, optionalArgs)) === null || _a === void 0 ? void 0 : _a.asString()
- : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default;
- return (value !== undefined ? new AttributeValue(value) : undefined);
- }
- /**
- * Extract the text content of a child of the given element.
- *
- * If the text content is required but either the text content or the referenced child element was not found, it will
- * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.
- *
- * Can only return undefined value for non-required text content without default value.
- */
- extractChildTextContent(e, childRef, optionalArgs) {
- if ((optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) !== undefined && typeof optionalArgs.default !== "string") {
- throw new Error("extractTextContent default value should be a string");
- }
- let child = this.querySelector(e, childRef);
- if (!child) {
- this.handleMissingValue(e, `child <${childRef}>`, optionalArgs);
- }
- return (child ? this.extractTextContent(child, optionalArgs) : optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default);
- }
- /**
- * Should be called if a extractAttr/extractTextContent doesn't find the element it needs to extract.
- *
- * If the extractable was required, this function will add a warning in the warningManager if there was a default value,
- * and throw an error if no default value was given.
- */
- handleMissingValue(parentElement, missingElementName, optionalArgs) {
- if (optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.required) {
- if (optionalArgs === null || optionalArgs === void 0 ? void 0 : optionalArgs.default) {
- this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
- }
- else {
- throw new Error(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, and no default value was set`);
- }
- }
- }
- /**
- * Extract a color, extracting it from the theme if needed.
- *
- * Will throw an error if the element references a theme, but no theme was provided or the theme it doesn't contain the color.
- */
- extractColor(colorElement, theme, defaultColor) {
- var _a, _b, _c, _d, _e;
- if (!colorElement) {
- return defaultColor ? { rgb: defaultColor } : undefined;
- }
- const themeIndex = (_a = this.extractAttr(colorElement, "theme")) === null || _a === void 0 ? void 0 : _a.asString();
- let rgb;
- if (themeIndex !== undefined) {
- if (!theme || !theme.clrScheme) {
- throw new Error("Color referencing a theme but no theme was provided");
- }
- rgb = this.getThemeColor(themeIndex, theme.clrScheme);
- }
- else {
- rgb = (_b = this.extractAttr(colorElement, "rgb")) === null || _b === void 0 ? void 0 : _b.asString();
- }
- const color = {
- rgb,
- auto: (_c = this.extractAttr(colorElement, "auto")) === null || _c === void 0 ? void 0 : _c.asBool(),
- indexed: (_d = this.extractAttr(colorElement, "indexed")) === null || _d === void 0 ? void 0 : _d.asNum(),
- tint: (_e = this.extractAttr(colorElement, "tint")) === null || _e === void 0 ? void 0 : _e.asNum(),
- };
- return color;
- }
- /**
- * Returns the xlsx file targeted by a relationship.
- */
- getTargetXmlFile(relationship) {
- if (!relationship)
- throw new Error("Undefined target file");
- let target = relationship.target;
- target = target.replace("../", "");
- target = target.replace("./", "");
- // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
- const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));
- if (!f || !f.file)
- 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);
- }
- /**
- * 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);
- }
- /**
- * Get a color from its id in the Theme's colorScheme.
- *
- * Note that Excel don't use the colors from the theme but from its own internal theme, so the displayed
- * colors will be different in the import than in excel.
- * .
- */
- getThemeColor(colorId, clrScheme) {
- switch (colorId) {
- case "0": // 0 : sysColor window text
- return "FFFFFF";
- case "1": // 1 : sysColor window background
- return "000000";
- // Don't ask me why these 2 are inverted, I cannot find any documentation for it but everyone does it
- case "2":
- return clrScheme["3"].value;
- case "3":
- return clrScheme["2"].value;
- default:
- return clrScheme[colorId].value;
- }
- }
- }
-
- /**
- * XLSX Extractor class that can be used for either sharedString XML files or theme XML files.
- *
- * Since they both are quite simple, it make sense to make a single class to manage them all, to avoid unnecessary file
- * cluttering.
- */
- class XlsxMiscExtractor extends XlsxBaseExtractor {
- getTheme() {
- const clrScheme = this.mapOnElements({ query: "a:clrScheme", parent: this.rootFile.file.xml, children: true }, (element) => {
- return {
- name: element.tagName,
- value: this.extractChildAttr(element, 0, "val", {
- required: true,
- default: AUTO_COLOR,
- }).asString(),
- lastClr: this.extractChildAttr(element, 0, "lastClr", {
- default: AUTO_COLOR,
- }).asString(),
- };
- });
- return { clrScheme };
- }
- /**
- * Get the array of shared strings of the XLSX.
- *
- * Worth noting that running a prettier on the xml can mess up some strings, since there is an option in the
- * xmls to keep the spacing and not trim the string.
- */
- getSharedStrings() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "si" }, (ssElement) => {
- // Shared string can either be a simple text, or a rich text (text with formatting, possibly in multiple parts)
- if (ssElement.children[0].tagName === "t") {
- return this.extractTextContent(ssElement) || "";
- }
- // We don't support rich text formatting, we'll only extract the text
- else {
- return this.mapOnElements({ parent: ssElement, query: "t" }, (textElement) => {
- return this.extractTextContent(textElement) || "";
- }).join("");
- }
- });
- }
- }
-
- class XlsxCfExtractor extends XlsxBaseExtractor {
- constructor(sheetFile, xlsxStructure, warningManager, theme) {
- super(sheetFile, xlsxStructure, warningManager);
- this.theme = theme;
- }
- extractConditionalFormattings() {
- const cfs = this.mapOnElements({ parent: this.rootFile.file.xml, query: "worksheet > conditionalFormatting" }, (cfElement) => {
- var _a;
- return {
- // sqref = ranges on which the cf applies, separated by spaces
- sqref: this.extractAttr(cfElement, "sqref", { required: true }).asString().split(" "),
- pivot: (_a = this.extractAttr(cfElement, "pivot")) === null || _a === void 0 ? void 0 : _a.asBool(),
- cfRules: this.extractCFRules(cfElement, this.theme),
- };
- });
- // XLSX extension to OpenXml
- cfs.push(...this.mapOnElements({ parent: this.rootFile.file.xml, query: "extLst x14:conditionalFormatting" }, (cfElement) => {
- var _a;
- return {
- sqref: this.extractChildTextContent(cfElement, "xm:sqref", { required: true }).split(" "),
- pivot: (_a = this.extractAttr(cfElement, "xm:pivot")) === null || _a === void 0 ? void 0 : _a.asBool(),
- cfRules: this.extractCFRules(cfElement, this.theme),
- };
- }));
- return cfs;
- }
- extractCFRules(cfElement, theme) {
- return this.mapOnElements({ parent: cfElement, query: "cfRule, x14:cfRule" }, (cfRuleElement) => {
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
- const cfType = this.extractAttr(cfRuleElement, "type", {
- required: true,
- }).asString();
- if (cfType === "dataBar") {
- // Databars are an extension to OpenXml and have a different format (XLSX §2.6.30). Do'nt bother
- // extracting them as we don't support them.
- throw new Error("Databars conditional formats are not supported.");
- }
- return {
- type: cfType,
- priority: this.extractAttr(cfRuleElement, "priority", { required: true }).asNum(),
- colorScale: this.extractCfColorScale(cfRuleElement, theme),
- formula: this.extractCfFormula(cfRuleElement),
- iconSet: this.extractCfIconSet(cfRuleElement),
- dxfId: (_a = this.extractAttr(cfRuleElement, "dxfId")) === null || _a === void 0 ? void 0 : _a.asNum(),
- stopIfTrue: (_b = this.extractAttr(cfRuleElement, "stopIfTrue")) === null || _b === void 0 ? void 0 : _b.asBool(),
- aboveAverage: (_c = this.extractAttr(cfRuleElement, "aboveAverage")) === null || _c === void 0 ? void 0 : _c.asBool(),
- percent: (_d = this.extractAttr(cfRuleElement, "percent")) === null || _d === void 0 ? void 0 : _d.asBool(),
- bottom: (_e = this.extractAttr(cfRuleElement, "bottom")) === null || _e === void 0 ? void 0 : _e.asBool(),
- operator: (_f = this.extractAttr(cfRuleElement, "operator")) === null || _f === void 0 ? void 0 : _f.asString(),
- text: (_g = this.extractAttr(cfRuleElement, "text")) === null || _g === void 0 ? void 0 : _g.asString(),
- timePeriod: (_h = this.extractAttr(cfRuleElement, "timePeriod")) === null || _h === void 0 ? void 0 : _h.asString(),
- rank: (_j = this.extractAttr(cfRuleElement, "rank")) === null || _j === void 0 ? void 0 : _j.asNum(),
- stdDev: (_k = this.extractAttr(cfRuleElement, "stdDev")) === null || _k === void 0 ? void 0 : _k.asNum(),
- equalAverage: (_l = this.extractAttr(cfRuleElement, "equalAverage")) === null || _l === void 0 ? void 0 : _l.asBool(),
- };
- });
- }
- extractCfFormula(cfRulesElement) {
- return this.mapOnElements({ parent: cfRulesElement, query: "formula" }, (cfFormulaElements) => {
- return this.extractTextContent(cfFormulaElements, { required: true });
- });
- }
- extractCfColorScale(cfRulesElement, theme) {
- const colorScaleElement = this.querySelector(cfRulesElement, "colorScale");
- if (!colorScaleElement)
- return undefined;
- return {
- colors: this.mapOnElements({ parent: colorScaleElement, query: "color" }, (colorElement) => {
- return this.extractColor(colorElement, theme, "ffffff");
- }),
- cfvos: this.extractCFVos(colorScaleElement),
- };
- }
- extractCfIconSet(cfRulesElement) {
- var _a, _b;
- const iconSetElement = this.querySelector(cfRulesElement, "iconSet, x14:iconSet");
- if (!iconSetElement)
- return undefined;
- return {
- iconSet: this.extractAttr(iconSetElement, "iconSet", {
- default: "3TrafficLights1",
- }).asString(),
- showValue: this.extractAttr(iconSetElement, "showValue", { default: true }).asBool(),
- percent: this.extractAttr(iconSetElement, "percent", { default: true }).asBool(),
- reverse: (_a = this.extractAttr(iconSetElement, "reverse")) === null || _a === void 0 ? void 0 : _a.asBool(),
- custom: (_b = this.extractAttr(iconSetElement, "custom")) === null || _b === void 0 ? void 0 : _b.asBool(),
- cfvos: this.extractCFVos(iconSetElement),
- cfIcons: this.extractCfIcons(iconSetElement),
- };
- }
- extractCfIcons(iconSetElement) {
- const icons = this.mapOnElements({ parent: iconSetElement, query: "cfIcon, x14:cfIcon" }, (cfIconElement) => {
- return {
- iconSet: this.extractAttr(cfIconElement, "iconSet", {
- required: true,
- }).asString(),
- iconId: this.extractAttr(cfIconElement, "iconId", { required: true }).asNum(),
- };
- });
- return icons.length === 0 ? undefined : icons;
- }
- extractCFVos(parent) {
- return this.mapOnElements({ parent, query: "cfvo, x14:cfvo" }, (cfVoElement) => {
- var _a, _b;
- return {
- type: this.extractAttr(cfVoElement, "type", {
- required: true,
- }).asString(),
- gte: (_a = this.extractAttr(cfVoElement, "gte", { default: true })) === null || _a === void 0 ? void 0 : _a.asBool(),
- value: cfVoElement.attributes["val"]
- ? (_b = this.extractAttr(cfVoElement, "val")) === null || _b === void 0 ? void 0 : _b.asString()
- : this.extractChildTextContent(cfVoElement, "f, xm:f"),
- };
- });
- }
- }
-
- class XlsxChartExtractor extends XlsxBaseExtractor {
- extractChart() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "c:chartSpace" }, (rootChartElement) => {
- const chartType = this.getChartType(rootChartElement);
- if (!CHART_TYPE_CONVERSION_MAP[chartType]) {
- throw new Error(`Unsupported chart type ${chartType}`);
- }
- // Title can be separated into multiple xml elements (for styling and such), we only import the text
- const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
- return textElement.textContent || "";
- }).join("");
- const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
- default: "clustered",
- }).asString();
- return {
- title: chartTitle,
- type: CHART_TYPE_CONVERSION_MAP[chartType],
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
- labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
- backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
- default: "ffffff",
- }).asString(),
- verticalAxisPosition: this.extractChildAttr(rootChartElement, "c:valAx > c:axPos", "val", {
- default: "l",
- }).asString() === "r"
- ? "right"
- : "left",
- legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
- default: "b",
- }).asString()],
- stacked: barChartGrouping === "stacked",
- fontColor: "000000",
- };
- })[0];
- }
- extractChartDatasets(chartElement) {
- return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
- return {
- label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
- range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
- };
- });
- }
- /**
- * The chart type in the XML isn't explicitly defined, but there is an XML element that define the
- * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.
- */
- getChartType(chartElement) {
- const plotAreaElement = this.querySelector(chartElement, "c:plotArea");
- if (!plotAreaElement) {
- throw new Error("Missing plot area in the chart definition.");
- }
- for (let child of plotAreaElement.children) {
- const tag = removeNamespaces(child.tagName);
- if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
- return tag;
- }
- }
- throw new Error("Unknown chart type");
- }
- }
-
- class XlsxFigureExtractor extends XlsxBaseExtractor {
- extractFigures() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
- const anchorType = removeNamespaces(figureElement.tagName);
- if (anchorType !== "twoCellAnchor") {
- throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
- }
- const chartElement = this.querySelector(figureElement, "c:chart");
- if (!chartElement) {
- throw new Error("Only chart figures are currently supported.");
- }
- return {
- anchors: [
- this.extractFigureAnchor("xdr:from", figureElement),
- this.extractFigureAnchor("xdr:to", figureElement),
- ],
- data: this.extractChart(chartElement),
- };
- });
- }
- extractFigureAnchor(anchorTag, figureElement) {
- const anchor = this.querySelector(figureElement, anchorTag);
- if (!anchor) {
- throw new Error(`Missing anchor element ${anchorTag}`);
- }
- return {
- col: Number(this.extractChildTextContent(anchor, "xdr:col", { required: true })),
- colOffset: Number(this.extractChildTextContent(anchor, "xdr:colOff", { required: true })),
- row: Number(this.extractChildTextContent(anchor, "xdr:row", { required: true })),
- rowOffset: Number(this.extractChildTextContent(anchor, "xdr:rowOff", { required: true })),
- };
- }
- extractChart(chartElement) {
- const chartId = this.extractAttr(chartElement, "r:id", { required: true }).asString();
- const chartFile = this.getTargetXmlFile(this.relationships[chartId]);
- const chartDefinition = new XlsxChartExtractor(chartFile, this.xlsxFileStructure, this.warningManager).extractChart();
- if (!chartDefinition) {
- throw new Error("Unable to extract chart definition");
- }
- return chartDefinition;
- }
- }
-
- /**
- * We don't really support pivot tables, we'll just extract them as Tables.
- */
- class XlsxPivotExtractor extends XlsxBaseExtractor {
- getPivotTable() {
- return this.mapOnElements(
- // Use :root instead of "pivotTableDefinition" because others pivotTableDefinition elements are present inside the root
- // pivotTableDefinition elements.
- { query: ":root", parent: this.rootFile.file.xml }, (pivotElement) => {
- return {
- displayName: this.extractAttr(pivotElement, "name", { required: true }).asString(),
- id: this.extractAttr(pivotElement, "name", { required: true }).asString(),
- ref: this.extractChildAttr(pivotElement, "location", "ref", {
- required: true,
- }).asString(),
- headerRowCount: this.extractChildAttr(pivotElement, "location", "firstDataRow", {
- default: 0,
- }).asNum(),
- totalsRowCount: 1,
- cols: [],
- style: {
- showFirstColumn: true,
- showRowStripes: true,
- },
- };
- })[0];
- }
- }
-
- class XlsxTableExtractor extends XlsxBaseExtractor {
- getTable() {
- return this.mapOnElements({ query: "table", parent: this.rootFile.file.xml }, (tableElement) => {
- var _a;
- return {
- displayName: this.extractAttr(tableElement, "displayName", {
- required: true,
- }).asString(),
- name: (_a = this.extractAttr(tableElement, "name")) === null || _a === void 0 ? void 0 : _a.asString(),
- id: this.extractAttr(tableElement, "id", { required: true }).asString(),
- ref: this.extractAttr(tableElement, "ref", { required: true }).asString(),
- headerRowCount: this.extractAttr(tableElement, "headerRowCount", {
- default: 1,
- }).asNum(),
- totalsRowCount: this.extractAttr(tableElement, "totalsRowCount", {
- default: 0,
- }).asNum(),
- cols: this.extractTableCols(tableElement),
- style: this.extractTableStyleInfo(tableElement),
- autoFilter: this.extractTableAutoFilter(tableElement),
- };
- })[0];
- }
- extractTableCols(tableElement) {
- return this.mapOnElements({ query: "tableColumn", parent: tableElement }, (tableColElement) => {
- return {
- id: this.extractAttr(tableColElement, "id", { required: true }).asString(),
- name: this.extractAttr(tableColElement, "name", { required: true }).asString(),
- colFormula: this.extractChildTextContent(tableColElement, "calculatedColumnFormula"),
- };
- });
- }
- extractTableStyleInfo(tableElement) {
- return this.mapOnElements({ query: "tableStyleInfo", parent: tableElement }, (tableStyleElement) => {
- var _a, _b, _c, _d, _e;
- return {
- name: (_a = this.extractAttr(tableStyleElement, "name")) === null || _a === void 0 ? void 0 : _a.asString(),
- showFirstColumn: (_b = this.extractAttr(tableStyleElement, "showFirstColumn")) === null || _b === void 0 ? void 0 : _b.asBool(),
- showLastColumn: (_c = this.extractAttr(tableStyleElement, "showLastColumn")) === null || _c === void 0 ? void 0 : _c.asBool(),
- showRowStripes: (_d = this.extractAttr(tableStyleElement, "showRowStripes")) === null || _d === void 0 ? void 0 : _d.asBool(),
- showColumnStripes: (_e = this.extractAttr(tableStyleElement, "showColumnStripes")) === null || _e === void 0 ? void 0 : _e.asBool(),
- };
- })[0];
- }
- extractTableAutoFilter(tableElement) {
- return this.mapOnElements({ query: "autoFilter", parent: tableElement }, (autoFilterElement) => {
- return {
- columns: this.extractFilterColumns(autoFilterElement),
- zone: this.extractAttr(autoFilterElement, "ref", { required: true }).asString(),
- };
- })[0];
- }
- extractFilterColumns(autoFilterElement) {
- return this.mapOnElements({ query: "tableColumn", parent: autoFilterElement }, (filterColumnElement) => {
- return {
- colId: this.extractAttr(autoFilterElement, "colId", { required: true }).asNum(),
- hiddenButton: this.extractAttr(autoFilterElement, "hiddenButton", {
- default: false,
- }).asBool(),
- filters: this.extractSimpleFilter(filterColumnElement),
- };
- });
- }
- extractSimpleFilter(filterColumnElement) {
- return this.mapOnElements({ query: "filter", parent: filterColumnElement }, (filterColumnElement) => {
- return {
- val: this.extractAttr(filterColumnElement, "val", { required: true }).asString(),
- };
- });
- }
- }
-
- class XlsxSheetExtractor extends XlsxBaseExtractor {
- constructor(sheetFile, xlsxStructure, warningManager, theme) {
- super(sheetFile, xlsxStructure, warningManager);
- this.theme = theme;
- }
- getSheet() {
- return this.mapOnElements({ query: "worksheet", parent: this.rootFile.file.xml }, (sheetElement) => {
- const sheetWorkbookInfo = this.getSheetWorkbookInfo();
- return {
- sheetName: this.extractSheetName(),
- sheetViews: this.extractSheetViews(sheetElement),
- sheetFormat: this.extractSheetFormat(sheetElement),
- cols: this.extractCols(sheetElement),
- rows: this.extractRows(sheetElement),
- sharedFormulas: this.extractSharedFormulas(sheetElement),
- merges: this.extractMerges(sheetElement),
- cfs: this.extractConditionalFormats(),
- figures: this.extractFigures(sheetElement),
- hyperlinks: this.extractHyperLinks(sheetElement),
- tables: [...this.extractTables(sheetElement), ...this.extractPivotTables()],
- isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
- };
- })[0];
- }
- extractSheetViews(worksheet) {
- return this.mapOnElements({ parent: worksheet, query: "sheetView" }, (sheetViewElement) => {
- const paneElement = this.querySelector(sheetViewElement, "pane");
- return {
- tabSelected: this.extractAttr(sheetViewElement, "tabSelected", {
- default: false,
- }).asBool(),
- showFormulas: this.extractAttr(sheetViewElement, "showFormulas", {
- default: false,
- }).asBool(),
- showGridLines: this.extractAttr(sheetViewElement, "showGridLines", {
- default: true,
- }).asBool(),
- showRowColHeaders: this.extractAttr(sheetViewElement, "showRowColHeaders", {
- default: true,
- }).asBool(),
- pane: {
- xSplit: paneElement
- ? this.extractAttr(paneElement, "xSplit", { default: 0 }).asNum()
- : 0,
- ySplit: paneElement
- ? this.extractAttr(paneElement, "ySplit", { default: 0 }).asNum()
- : 0,
- },
- };
- });
- }
- extractSheetName() {
- const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);
- const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);
- const relId = workbookRels.find((rel) => rel.target === relativePath).id;
- // Having a namespace in the attributes names mess with the querySelector, and the behavior is not the same
- // for every XML parser. So we'll search manually instead of using a querySelector to search for an attribute value.
- for (let sheetElement of this.querySelectorAll(this.xlsxFileStructure.workbook.file.xml, "sheet")) {
- if (sheetElement.attributes["r:id"].value === relId) {
- return sheetElement.attributes["name"].value;
- }
- }
- throw new Error("Missing sheet name");
- }
- getSheetWorkbookInfo() {
- const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);
- const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);
- const relId = workbookRels.find((rel) => rel.target === relativePath).id;
- const workbookSheets = this.mapOnElements({ parent: this.xlsxFileStructure.workbook.file.xml, query: "sheet" }, (sheetElement) => {
- return {
- relationshipId: this.extractAttr(sheetElement, "r:id", { required: true }).asString(),
- sheetId: this.extractAttr(sheetElement, "sheetId", { required: true }).asString(),
- sheetName: this.extractAttr(sheetElement, "name", { required: true }).asString(),
- state: this.extractAttr(sheetElement, "state", {
- default: "visible",
- }).asString(),
- };
- });
- const info = workbookSheets.find((info) => info.relationshipId === relId);
- if (!info) {
- throw new Error("Cannot find corresponding workbook sheet");
- }
- return info;
- }
- extractConditionalFormats() {
- return new XlsxCfExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractConditionalFormattings();
- }
- extractFigures(worksheet) {
- const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
- var _a;
- const drawingId = (_a = this.extractAttr(drawingElement, "r:id", { required: true })) === null || _a === void 0 ? void 0 : _a.asString();
- const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);
- const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
- return figures;
- })[0];
- return figures || [];
- }
- extractTables(worksheet) {
- return this.mapOnElements({ query: "tablePart", parent: worksheet }, (tablePartElement) => {
- var _a;
- const tableId = (_a = this.extractAttr(tablePartElement, "r:id", { required: true })) === null || _a === void 0 ? void 0 : _a.asString();
- const tableFile = this.getTargetXmlFile(this.relationships[tableId]);
- const tableExtractor = new XlsxTableExtractor(tableFile, this.xlsxFileStructure, this.warningManager);
- return tableExtractor.getTable();
- });
- }
- extractPivotTables() {
- try {
- return Object.values(this.relationships)
- .filter((relationship) => relationship.type.endsWith("pivotTable"))
- .map((pivotRelationship) => {
- const pivotFile = this.getTargetXmlFile(pivotRelationship);
- const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
- return pivot;
- });
- }
- catch (e) {
- this.catchErrorOnElement(e);
- return [];
- }
- }
- extractMerges(worksheet) {
- return this.mapOnElements({ parent: worksheet, query: "mergeCell" }, (mergeElement) => {
- return this.extractAttr(mergeElement, "ref", { required: true }).asString();
- });
- }
- extractSheetFormat(worksheet) {
- const formatElement = this.querySelector(worksheet, "sheetFormatPr");
- if (!formatElement)
- return undefined;
- return {
- defaultColWidth: this.extractAttr(formatElement, "defaultColWidth", {
- default: EXCEL_DEFAULT_COL_WIDTH.toString(),
- }).asNum(),
- defaultRowHeight: this.extractAttr(formatElement, "defaultRowHeight", {
- default: EXCEL_DEFAULT_ROW_HEIGHT.toString(),
- }).asNum(),
- };
- }
- extractCols(worksheet) {
- return this.mapOnElements({ parent: worksheet, query: "cols col" }, (colElement) => {
- var _a, _b, _c, _d, _e, _f, _g;
- return {
- width: (_a = this.extractAttr(colElement, "width")) === null || _a === void 0 ? void 0 : _a.asNum(),
- customWidth: (_b = this.extractAttr(colElement, "customWidth")) === null || _b === void 0 ? void 0 : _b.asBool(),
- bestFit: (_c = this.extractAttr(colElement, "bestFit")) === null || _c === void 0 ? void 0 : _c.asBool(),
- hidden: (_d = this.extractAttr(colElement, "hidden")) === null || _d === void 0 ? void 0 : _d.asBool(),
- min: (_e = this.extractAttr(colElement, "min", { required: true })) === null || _e === void 0 ? void 0 : _e.asNum(),
- max: (_f = this.extractAttr(colElement, "max", { required: true })) === null || _f === void 0 ? void 0 : _f.asNum(),
- styleIndex: (_g = this.extractAttr(colElement, "style")) === null || _g === void 0 ? void 0 : _g.asNum(),
- };
- });
- }
- extractRows(worksheet) {
- return this.mapOnElements({ parent: worksheet, query: "sheetData row" }, (rowElement) => {
- var _a, _b, _c, _d, _e;
- return {
- index: (_a = this.extractAttr(rowElement, "r", { required: true })) === null || _a === void 0 ? void 0 : _a.asNum(),
- cells: this.extractCells(rowElement),
- height: (_b = this.extractAttr(rowElement, "ht")) === null || _b === void 0 ? void 0 : _b.asNum(),
- customHeight: (_c = this.extractAttr(rowElement, "customHeight")) === null || _c === void 0 ? void 0 : _c.asBool(),
- hidden: (_d = this.extractAttr(rowElement, "hidden")) === null || _d === void 0 ? void 0 : _d.asBool(),
- styleIndex: (_e = this.extractAttr(rowElement, "s")) === null || _e === void 0 ? void 0 : _e.asNum(),
- };
- });
- }
- extractCells(row) {
- return this.mapOnElements({ parent: row, query: "c" }, (cellElement) => {
- var _a, _b, _c;
- return {
- xc: (_a = this.extractAttr(cellElement, "r", { required: true })) === null || _a === void 0 ? void 0 : _a.asString(),
- styleIndex: (_b = this.extractAttr(cellElement, "s")) === null || _b === void 0 ? void 0 : _b.asNum(),
- type: CELL_TYPE_CONVERSION_MAP[(_c = this.extractAttr(cellElement, "t", { default: "n" })) === null || _c === void 0 ? void 0 : _c.asString()],
- value: this.extractChildTextContent(cellElement, "v"),
- formula: this.extractCellFormula(cellElement),
- };
- });
- }
- extractCellFormula(cellElement) {
- var _a, _b;
- const formulaElement = this.querySelector(cellElement, "f");
- if (!formulaElement)
- return undefined;
- return {
- content: this.extractTextContent(formulaElement),
- sharedIndex: (_a = this.extractAttr(formulaElement, "si")) === null || _a === void 0 ? void 0 : _a.asNum(),
- ref: (_b = this.extractAttr(formulaElement, "ref")) === null || _b === void 0 ? void 0 : _b.asString(),
- };
- }
- extractHyperLinks(worksheet) {
- return this.mapOnElements({ parent: worksheet, query: "hyperlink" }, (linkElement) => {
- var _a, _b, _c, _d;
- const relId = (_a = this.extractAttr(linkElement, "r:id")) === null || _a === void 0 ? void 0 : _a.asString();
- return {
- xc: (_b = this.extractAttr(linkElement, "ref", { required: true })) === null || _b === void 0 ? void 0 : _b.asString(),
- location: (_c = this.extractAttr(linkElement, "location")) === null || _c === void 0 ? void 0 : _c.asString(),
- display: (_d = this.extractAttr(linkElement, "display")) === null || _d === void 0 ? void 0 : _d.asString(),
- relTarget: relId ? this.relationships[relId].target : undefined,
- };
- });
- }
- extractSharedFormulas(worksheet) {
- const sfElements = this.querySelectorAll(worksheet, `f[si][ref]`);
- const sfMap = {};
- for (let sfElement of sfElements) {
- const index = this.extractAttr(sfElement, "si", { required: true }).asNum();
- const formula = this.extractTextContent(sfElement, { required: true });
- sfMap[index] = formula;
- }
- const sfs = [];
- for (let i = 0; i < Object.keys(sfMap).length; i++) {
- if (!sfMap[i]) {
- this.warningManager.addParsingWarning(`Missing shared formula ${i}, replacing it by empty formula`);
- sfs.push("");
- }
- else {
- sfs.push(sfMap[i]);
- }
- }
- return sfs;
- }
- }
-
- class XlsxStyleExtractor extends XlsxBaseExtractor {
- constructor(xlsxStructure, warningManager, theme) {
- super(xlsxStructure.styles, xlsxStructure, warningManager);
- this.theme = theme;
- }
- getNumFormats() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "numFmt" }, (numFmtElement) => {
- return this.extractNumFormats(numFmtElement);
- });
- }
- extractNumFormats(numFmtElement) {
- return {
- id: this.extractAttr(numFmtElement, "numFmtId", {
- required: true,
- }).asNum(),
- format: this.extractAttr(numFmtElement, "formatCode", {
- required: true,
- default: "",
- }).asString(),
- };
- }
- getFonts() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "font" }, (font) => {
- return this.extractFont(font);
- });
- }
- extractFont(fontElement) {
- var _a, _b, _c, _d;
- const name = this.extractChildAttr(fontElement, "name", "val", {
- default: "Arial",
- }).asString();
- const size = this.extractChildAttr(fontElement, "sz", "val", {
- default: DEFAULT_FONT_SIZE.toString(),
- }).asNum();
- const color = this.extractColor(this.querySelector(fontElement, `color`), this.theme);
- // The behavior for these is kinda strange. The text is italic if there is either a "italic" tag with no "val"
- // attribute, or a tag with a "val" attribute = "1" (boolean).
- const italicElement = this.querySelector(fontElement, `i`) || undefined;
- const italic = italicElement && ((_a = italicElement.attributes["val"]) === null || _a === void 0 ? void 0 : _a.value) !== "0";
- const boldElement = this.querySelector(fontElement, `b`) || undefined;
- const bold = boldElement && ((_b = boldElement.attributes["val"]) === null || _b === void 0 ? void 0 : _b.value) !== "0";
- const strikeElement = this.querySelector(fontElement, `strike`) || undefined;
- const strike = strikeElement && ((_c = strikeElement.attributes["val"]) === null || _c === void 0 ? void 0 : _c.value) !== "0";
- const underlineElement = this.querySelector(fontElement, `u`) || undefined;
- const underline = underlineElement && ((_d = underlineElement.attributes["val"]) === null || _d === void 0 ? void 0 : _d.value) !== "none";
- return { name, size, color, italic, bold, underline, strike };
- }
- getFills() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "fill" }, (fillElement) => {
- return this.extractFill(fillElement);
- });
- }
- extractFill(fillElement) {
- var _a;
- // Fills are either patterns of gradients
- const fillChild = fillElement.children[0];
- if (fillChild.tagName === "patternFill") {
- return {
- patternType: (_a = fillChild.attributes["patternType"]) === null || _a === void 0 ? void 0 : _a.value,
- bgColor: this.extractColor(this.querySelector(fillChild, "bgColor"), this.theme),
- fgColor: this.extractColor(this.querySelector(fillChild, "fgColor"), this.theme),
- };
- }
- else {
- // We don't support gradients. Take the second gradient color as fill color
- return {
- patternType: "solid",
- fgColor: this.extractColor(this.querySelectorAll(fillChild, "color")[1], this.theme),
- };
- }
- }
- getBorders() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "border" }, (borderElement) => {
- return this.extractBorder(borderElement);
- });
- }
- extractBorder(borderElement) {
- var _a, _b;
- const border = {
- left: this.extractSingleBorder(borderElement, "left", this.theme),
- right: this.extractSingleBorder(borderElement, "right", this.theme),
- top: this.extractSingleBorder(borderElement, "top", this.theme),
- bottom: this.extractSingleBorder(borderElement, "bottom", this.theme),
- diagonal: this.extractSingleBorder(borderElement, "diagonal", this.theme),
- };
- if (border.diagonal) {
- border.diagonalUp = (_a = this.extractAttr(borderElement, "diagonalUp")) === null || _a === void 0 ? void 0 : _a.asBool();
- border.diagonalDown = (_b = this.extractAttr(borderElement, "diagonalDown")) === null || _b === void 0 ? void 0 : _b.asBool();
- }
- return border;
- }
- extractSingleBorder(borderElement, direction, theme) {
- const directionElement = this.querySelector(borderElement, direction);
- if (!directionElement || !directionElement.attributes["style"])
- return undefined;
- return {
- style: this.extractAttr(directionElement, "style", {
- required: true,
- default: "thin",
- }).asString(),
- color: this.extractColor(directionElement.children[0], theme, "000000"),
- };
- }
- extractAlignment(alignmentElement) {
- var _a, _b, _c, _d, _e, _f, _g;
- return {
- horizontal: this.extractAttr(alignmentElement, "horizontal", {
- default: "general",
- }).asString(),
- vertical: this.extractAttr(alignmentElement, "vertical", {
- default: "center",
- }).asString(),
- textRotation: (_a = this.extractAttr(alignmentElement, "textRotation")) === null || _a === void 0 ? void 0 : _a.asNum(),
- wrapText: (_b = this.extractAttr(alignmentElement, "wrapText")) === null || _b === void 0 ? void 0 : _b.asBool(),
- indent: (_c = this.extractAttr(alignmentElement, "indent")) === null || _c === void 0 ? void 0 : _c.asNum(),
- relativeIndent: (_d = this.extractAttr(alignmentElement, "relativeIndent")) === null || _d === void 0 ? void 0 : _d.asNum(),
- justifyLastLine: (_e = this.extractAttr(alignmentElement, "justifyLastLine")) === null || _e === void 0 ? void 0 : _e.asBool(),
- shrinkToFit: (_f = this.extractAttr(alignmentElement, "shrinkToFit")) === null || _f === void 0 ? void 0 : _f.asBool(),
- readingOrder: (_g = this.extractAttr(alignmentElement, "readingOrder")) === null || _g === void 0 ? void 0 : _g.asNum(),
- };
- }
- getDxfs() {
- return this.mapOnElements({ query: "dxf", parent: this.rootFile.file.xml }, (dxfElement) => {
- const fontElement = this.querySelector(dxfElement, "font");
- const fillElement = this.querySelector(dxfElement, "fill");
- const borderElement = this.querySelector(dxfElement, "border");
- const numFmtElement = this.querySelector(dxfElement, "numFmt");
- const alignmentElement = this.querySelector(dxfElement, "alignment");
- return {
- font: fontElement ? this.extractFont(fontElement) : undefined,
- fill: fillElement ? this.extractFill(fillElement) : undefined,
- numFmt: numFmtElement ? this.extractNumFormats(numFmtElement) : undefined,
- alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,
- border: borderElement ? this.extractBorder(borderElement) : undefined,
- };
- });
- }
- getStyles() {
- return this.mapOnElements({ query: "cellXfs xf", parent: this.rootFile.file.xml }, (styleElement) => {
- const alignmentElement = this.querySelector(styleElement, "alignment");
- return {
- fontId: this.extractAttr(styleElement, "fontId", {
- required: true,
- default: 0,
- }).asNum(),
- fillId: this.extractAttr(styleElement, "fillId", {
- required: true,
- default: 0,
- }).asNum(),
- borderId: this.extractAttr(styleElement, "borderId", {
- required: true,
- default: 0,
- }).asNum(),
- numFmtId: this.extractAttr(styleElement, "numFmtId", {
- required: true,
- default: 0,
- }).asNum(),
- alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,
- };
- });
- }
- }
-
- class XlsxExternalBookExtractor extends XlsxBaseExtractor {
- getExternalBook() {
- return this.mapOnElements({ parent: this.rootFile.file.xml, query: "externalBook" }, (bookElement) => {
- return {
- rId: this.extractAttr(bookElement, "r:id", { required: true }).asString(),
- sheetNames: this.mapOnElements({ parent: bookElement, query: "sheetName" }, (sheetNameElement) => {
- return this.extractAttr(sheetNameElement, "val", { required: true }).asString();
- }),
- datasets: this.extractExternalSheetData(bookElement),
- };
- })[0];
- }
- extractExternalSheetData(externalBookElement) {
- return this.mapOnElements({ parent: externalBookElement, query: "sheetData" }, (sheetDataElement) => {
- const cellsData = this.mapOnElements({ parent: sheetDataElement, query: "cell" }, (cellElement) => {
- return {
- xc: this.extractAttr(cellElement, "r", { required: true }).asString(),
- value: this.extractChildTextContent(cellElement, "v", { required: true }),
- };
- });
- const dataMap = {};
- for (let cell of cellsData) {
- dataMap[cell.xc] = cell.value;
- }
- return {
- sheetId: this.extractAttr(sheetDataElement, "sheetId", { required: true }).asNum(),
- data: dataMap,
- };
- });
- }
- }
-
- /**
- * Return all the xmls converted to XLSXImportFile corresponding to the given content type.
- */
- function getXLSXFilesOfType(contentType, xmls) {
- const paths = getPathsOfContent(contentType, xmls);
- return getXlsxFile(paths, xmls);
- }
- /**
- * From an array of file path, return the equivalents XLSXFiles. An XLSX File is composed of an XML,
- * and optionally of a relationships XML.
- */
- function getXlsxFile(files, xmls) {
- const ret = [];
- for (let file of files) {
- const rels = getRelationFile(file, xmls);
- ret.push({
- file: { fileName: file, xml: xmls[file] },
- rels: rels ? { fileName: rels, xml: xmls[rels] } : undefined,
- });
- }
- return ret;
- }
- /**
- * Return all the path of the files in a XLSX directory that have content of the given type.
- */
- function getPathsOfContent(contentType, xmls) {
- const xml = xmls[CONTENT_TYPES_FILE];
- const sheetItems = xml.querySelectorAll(`Override[ContentType="${contentType}"]`);
- const paths = [];
- for (let item of sheetItems) {
- const file = item === null || item === void 0 ? void 0 : item.attributes["PartName"].value;
- paths.push(file.substring(1)); // Remove the heading "/"
- }
- return paths;
- }
- /**
- * Get the corresponding relationship file for a given xml file in a XLSX directory.
- */
- function getRelationFile(file, xmls) {
- if (file === CONTENT_TYPES_FILE) {
- return "_rels/.rels";
- }
- let relsFile = "";
- const pathParts = file.split("/");
- for (let i = 0; i < pathParts.length - 1; i++) {
- relsFile += pathParts[i] + "/";
- }
- relsFile += "_rels/";
- relsFile += pathParts[pathParts.length - 1] + ".rels";
- if (!xmls[relsFile]) {
- relsFile = undefined;
- }
- return relsFile;
- }
-
- const EXCEL_IMPORT_VERSION = 12;
- class XlsxReader {
- constructor(files) {
- this.warningManager = new XLSXImportWarningManager();
- this.xmls = {};
- 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]));
- }
- }
- }
- convertXlsx() {
- const xlsxData = this.getXlsxData();
- const convertedData = this.convertImportedData(xlsxData);
- return convertedData;
- }
- // ---------------------------------------------------------------------------
- // Parsing XMLs
- // ---------------------------------------------------------------------------
- getXlsxData() {
- const xlsxFileStructure = this.buildXlsxFileStructure();
- const theme = xlsxFileStructure.theme
- ? new XlsxMiscExtractor(xlsxFileStructure.theme, xlsxFileStructure, this.warningManager).getTheme()
- : undefined;
- const sharedStrings = xlsxFileStructure.sharedStrings
- ? new XlsxMiscExtractor(xlsxFileStructure.sharedStrings, xlsxFileStructure, this.warningManager).getSharedStrings()
- : [];
- // Sort sheets by file name : the sheets will always be named sheet1.xml, sheet2.xml, ... in order
- const sheets = xlsxFileStructure.sheets
- .sort((a, b) => a.file.fileName.localeCompare(b.file.fileName, undefined, { numeric: true }))
- .map((sheetFile) => {
- return new XlsxSheetExtractor(sheetFile, xlsxFileStructure, this.warningManager, theme).getSheet();
- });
- const externalBooks = xlsxFileStructure.externalLinks.map((externalLinkFile) => {
- return new XlsxExternalBookExtractor(externalLinkFile, xlsxFileStructure, this.warningManager).getExternalBook();
- });
- const styleExtractor = new XlsxStyleExtractor(xlsxFileStructure, this.warningManager, theme);
- return {
- fonts: styleExtractor.getFonts(),
- fills: styleExtractor.getFills(),
- borders: styleExtractor.getBorders(),
- dxfs: styleExtractor.getDxfs(),
- numFmts: styleExtractor.getNumFormats(),
- styles: styleExtractor.getStyles(),
- sheets: sheets,
- sharedStrings,
- externalBooks,
- };
- }
- buildXlsxFileStructure() {
- const xlsxFileStructure = {
- sheets: getXLSXFilesOfType(CONTENT_TYPES.sheet, this.xmls),
- workbook: getXLSXFilesOfType(CONTENT_TYPES.workbook, this.xmls)[0],
- styles: getXLSXFilesOfType(CONTENT_TYPES.styles, this.xmls)[0],
- sharedStrings: getXLSXFilesOfType(CONTENT_TYPES.sharedStrings, this.xmls)[0],
- theme: getXLSXFilesOfType(CONTENT_TYPES.themes, this.xmls)[0],
- charts: getXLSXFilesOfType(CONTENT_TYPES.chart, this.xmls),
- figures: getXLSXFilesOfType(CONTENT_TYPES.drawing, this.xmls),
- tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),
- pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),
- externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),
- };
- if (!xlsxFileStructure.workbook.rels) {
- throw Error(_lt("Cannot find workbook relations file"));
- }
- return xlsxFileStructure;
- }
- // ---------------------------------------------------------------------------
- // Conversion
- // ---------------------------------------------------------------------------
- convertImportedData(data) {
- const convertedData = {
- version: EXCEL_IMPORT_VERSION,
- sheets: convertSheets(data, this.warningManager),
- styles: convertStyles(data, this.warningManager),
- formats: convertFormats(data, this.warningManager),
- borders: convertBorders(data, this.warningManager),
- entities: {},
- revisionId: DEFAULT_REVISION_ID,
- };
- convertTables(convertedData, data);
- // Remove falsy attributes in styles. Not mandatory, but make objects more readable when debugging
- Object.keys(data.styles).map((key) => {
- data.styles[key] = removeFalsyAttributes(data.styles[key]);
- });
- return convertedData;
- }
- }
-
- /**
- * Tokenizer
- *
- * A tokenizer is a piece of code whose job is to transform a string into a list
- * of "tokens". For example, "(12+" is converted into:
- * [{type: "LEFT_PAREN", value: "("},
- * {type: "NUMBER", value: "12"},
- * {type: "OPERATOR", value: "+"}]
- *
- * As the example shows, a tokenizer does not care about the meaning behind those
- * tokens. It only cares about the structure.
- *
- * The tokenizer is usually the first step in a compilation pipeline. Also, it
- * is useful for the composer, which needs to be able to work with incomplete
- * formulas.
- */
- const functions$1 = functionRegistry.content;
- const POSTFIX_UNARY_OPERATORS = ["%"];
- const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
- function tokenize(str) {
- str = replaceSpecialSpaces(str);
- const chars = str.split("");
- const result = [];
- while (chars.length) {
- let token = tokenizeSpace(chars) ||
- tokenizeMisc(chars) ||
- tokenizeOperator(chars) ||
- tokenizeString(chars) ||
- tokenizeDebugger(chars) ||
- tokenizeInvalidRange(chars) ||
- tokenizeNumber(chars) ||
- tokenizeSymbol(chars);
- if (!token) {
- token = { type: "UNKNOWN", value: chars.shift() };
- }
- result.push(token);
- }
- return result;
- }
- function tokenizeDebugger(chars) {
- if (chars[0] === "?") {
- chars.shift();
- return { type: "DEBUGGER", value: "?" };
- }
- return null;
- }
- const misc = {
- ",": "COMMA",
- "(": "LEFT_PAREN",
- ")": "RIGHT_PAREN",
- };
- function tokenizeMisc(chars) {
- if (chars[0] in misc) {
- const value = chars.shift();
- const type = misc[value];
- return { type, value };
- }
- return null;
- }
- function startsWith(chars, op) {
- for (let i = 0; i < op.length; i++) {
- if (op[i] !== chars[i]) {
- return false;
- }
- }
- return true;
- }
- function tokenizeOperator(chars) {
- for (let op of OPERATORS) {
- if (startsWith(chars, op)) {
- chars.splice(0, op.length);
- return { type: "OPERATOR", value: op };
- }
- }
- return null;
- }
- function tokenizeNumber(chars) {
- const match = concat(chars).match(formulaNumberRegexp);
- if (match) {
- chars.splice(0, match[0].length);
- return { type: "NUMBER", value: match[0] };
- }
- return null;
- }
- function tokenizeString(chars) {
- if (chars[0] === '"') {
- const startChar = chars.shift();
- let letters = startChar;
- while (chars[0] && (chars[0] !== startChar || letters[letters.length - 1] === "\\")) {
- letters += chars.shift();
- }
- if (chars[0] === '"') {
- letters += chars.shift();
- }
- return {
- type: "STRING",
- value: letters,
- };
- }
- return null;
- }
- const separatorRegexp = /\w|\.|!|\$/;
- /**
- * A "Symbol" is just basically any word-like element that can appear in a
- * formula, which is not a string. So:
- * A1
- * SUM
- * CEILING.MATH
- * A$1
- * Sheet2!A2
- * 'Sheet 2'!A2
- *
- * are examples of symbols
- */
- function tokenizeSymbol(chars) {
- let result = "";
- // there are two main cases to manage: either something which starts with
- // a ', like 'Sheet 2'A2, or a word-like element.
- if (chars[0] === "'") {
- let lastChar = chars.shift();
- result += lastChar;
- while (chars[0]) {
- lastChar = chars.shift();
- result += lastChar;
- if (lastChar === "'") {
- if (chars[0] && chars[0] === "'") {
- lastChar = chars.shift();
- result += lastChar;
- }
- else {
- break;
- }
- }
- }
- if (lastChar !== "'") {
- return {
- type: "UNKNOWN",
- value: result,
- };
- }
- }
- while (chars[0] && chars[0].match(separatorRegexp)) {
- result += chars.shift();
- }
- if (result.length) {
- const value = result;
- const isFunction = value.toUpperCase() in functions$1;
- if (isFunction) {
- return { type: "FUNCTION", value };
- }
- const isReference = value.match(rangeReference);
- if (isReference) {
- return { type: "REFERENCE", value };
- }
- else {
- return { type: "SYMBOL", value };
- }
- }
- return null;
- }
- function tokenizeSpace(chars) {
- let length = 0;
- while (chars[0] === NEWLINE) {
- length++;
- chars.shift();
- }
- if (length) {
- return { type: "SPACE", value: NEWLINE.repeat(length) };
- }
- while (chars[0] === " ") {
- length++;
- chars.shift();
- }
- if (length) {
- return { type: "SPACE", value: " ".repeat(length) };
- }
- return null;
- }
- function tokenizeInvalidRange(chars) {
- if (startsWith(chars, INCORRECT_RANGE_STRING)) {
- chars.splice(0, INCORRECT_RANGE_STRING.length);
- return { type: "INVALID_REFERENCE", value: INCORRECT_RANGE_STRING };
- }
- return null;
- }
-
- var State;
- (function (State) {
- /**
- * Initial state.
- * Expecting any reference for the left part of a range
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
- */
- State[State["LeftRef"] = 0] = "LeftRef";
- /**
- * Expecting any reference for the right part of a range
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
- */
- State[State["RightRef"] = 1] = "RightRef";
- /**
- * Expecting the separator without any constraint on the right part
- */
- State[State["Separator"] = 2] = "Separator";
- /**
- * Expecting the separator for a full column range
- */
- State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
- /**
- * Expecting the separator for a full row range
- */
- State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
- /**
- * Expecting the right part of a full column range
- * e.g. "1", "A1"
- */
- State[State["RightColumnRef"] = 5] = "RightColumnRef";
- /**
- * Expecting the right part of a full row range
- * e.g. "A", "A1"
- */
- State[State["RightRowRef"] = 6] = "RightRowRef";
- /**
- * Final state. A range has been matched
- */
- State[State["Found"] = 7] = "Found";
- })(State || (State = {}));
- const goTo = (state, guard = () => true) => [
- {
- goTo: state,
- guard,
- },
- ];
- const goToMulti = (state, guard = () => true) => ({
- goTo: state,
- guard,
- });
- const machine = {
- [State.LeftRef]: {
- REFERENCE: goTo(State.Separator),
- NUMBER: goTo(State.FullRowSeparator),
- SYMBOL: [
- goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
- goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
- ],
- },
- [State.FullColumnSeparator]: {
- SPACE: goTo(State.FullColumnSeparator),
- OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
- },
- [State.FullRowSeparator]: {
- SPACE: goTo(State.FullRowSeparator),
- OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
- },
- [State.Separator]: {
- SPACE: goTo(State.Separator),
- OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
- },
- [State.RightRef]: {
- SPACE: goTo(State.RightRef),
- NUMBER: goTo(State.Found),
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
- },
- [State.RightColumnRef]: {
- SPACE: goTo(State.RightColumnRef),
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
- },
- [State.RightRowRef]: {
- SPACE: goTo(State.RightRowRef),
- NUMBER: goTo(State.Found),
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
- },
- [State.Found]: {},
- };
- /**
- * Check if the list of tokens starts with a sequence of tokens representing
- * a range.
- * If a range is found, the sequence is removed from the list and is returned
- * as a single token.
- */
- function matchReference(tokens) {
- var _a;
- let head = 0;
- let transitions = machine[State.LeftRef];
- const matchedTokens = [];
- while (transitions !== undefined) {
- const token = tokens[head++];
- if (!token) {
- return null;
- }
- const transition = (_a = transitions[token.type]) === null || _a === void 0 ? void 0 : _a.find((transition) => transition.guard(token));
- const nextState = transition ? transition.goTo : undefined;
- switch (nextState) {
- case undefined:
- return null;
- case State.Found:
- matchedTokens.push(token);
- tokens.splice(0, head);
- return {
- type: "REFERENCE",
- value: concat(matchedTokens.map((token) => token.value)),
- };
- default:
- transitions = machine[nextState];
- matchedTokens.push(token);
- break;
- }
- }
- return null;
- }
- /**
- * Take the result of the tokenizer and transform it to be usable in the
- * manipulations of range
- *
- * @param formula
- */
- function rangeTokenize(formula) {
- const tokens = tokenize(formula);
- const result = [];
- while (tokens.length) {
- result.push(matchReference(tokens) || tokens.shift());
- }
- return result;
- }
-
- /**
- * parses a formula (as a string) into the same formula,
- * but with the references to other cells extracted
- *
- * =sum(a3:b1) + c3 --> =sum(|0|) + |1|
- *
- * @param formula
- */
- function normalizeV9(formula) {
- const tokens = rangeTokenize(formula);
- let dependencies = [];
- let noRefFormula = "".concat(...tokens.map((token) => {
- if (token.type === "REFERENCE" && cellReference.test(token.value)) {
- const value = token.value.trim();
- if (!dependencies.includes(value)) {
- dependencies.push(value);
- }
- return `${FORMULA_REF_IDENTIFIER}${dependencies.indexOf(value)}${FORMULA_REF_IDENTIFIER}`;
- }
- else {
- return token.value;
- }
- }));
- return { text: noRefFormula, dependencies };
- }
-
- /**
- * This is the current state version number. It should be incremented each time
- * a breaking change is made in the way the state is handled, and an upgrade
- * function should be defined
- */
- const CURRENT_VERSION = 12;
- const INITIAL_SHEET_ID = "Sheet1";
- /**
- * This function tries to load anything that could look like a valid
- * workbookData object. It applies any migrations, if needed, and return a
- * current, complete workbookData object.
- *
- * It also ensures that there is at least one sheet.
- */
- function load(data, verboseImport) {
- if (!data) {
- return createEmptyWorkbookData();
- }
- if (data["[Content_Types].xml"]) {
- const reader = new XlsxReader(data);
- data = reader.convertXlsx();
- if (verboseImport) {
- for (let parsingError of reader.warningManager.warnings.sort()) {
- console.warn(parsingError);
- }
- }
- }
- data = deepCopy(data);
- // apply migrations, if needed
- if ("version" in data) {
- if (data.version < CURRENT_VERSION) {
- data = migrate(data);
- }
- }
- data = repairData(data);
- return data;
- }
- function migrate(data) {
- const index = MIGRATIONS.findIndex((m) => m.from === data.version);
- for (let i = index; i < MIGRATIONS.length; i++) {
- data = MIGRATIONS[i].applyMigration(data);
- }
- return data;
- }
- const MIGRATIONS = [
- {
- description: "add the `activeSheet` field on data",
- from: 1,
- to: 2,
- applyMigration(data) {
- if (data.sheets && data.sheets[0]) {
- data.activeSheet = data.sheets[0].name;
- }
- return data;
- },
- },
- {
- description: "add an id field in each sheet",
- from: 2,
- to: 3,
- applyMigration(data) {
- if (data.sheets && data.sheets.length) {
- for (let sheet of data.sheets) {
- sheet.id = sheet.id || sheet.name;
- }
- }
- return data;
- },
- },
- {
- description: "activeSheet is now an id, not the name of a sheet",
- from: 3,
- to: 4,
- applyMigration(data) {
- if (data.sheets && data.activeSheet) {
- const activeSheet = data.sheets.find((s) => s.name === data.activeSheet);
- data.activeSheet = activeSheet.id;
- }
- return data;
- },
- },
- {
- description: "add figures object in each sheets",
- from: 4,
- to: 5,
- applyMigration(data) {
- for (let sheet of data.sheets || []) {
- sheet.figures = sheet.figures || [];
- }
- return data;
- },
- },
- {
- description: "normalize the content of the cell if it is a formula to avoid parsing all the formula that vary only by the cells they use",
- from: 5,
- to: 6,
- applyMigration(data) {
- for (let sheet of data.sheets || []) {
- for (let xc in sheet.cells || []) {
- const cell = sheet.cells[xc];
- if (cell.content && cell.content.startsWith("=")) {
- cell.formula = normalizeV9(cell.content);
- }
- }
- }
- return data;
- },
- },
- {
- description: "transform chart data structure",
- from: 6,
- to: 7,
- applyMigration(data) {
- for (let sheet of data.sheets || []) {
- for (let f in sheet.figures || []) {
- const { dataSets, ...newData } = sheet.figures[f].data;
- const newDataSets = [];
- for (let ds of dataSets) {
- if (ds.labelCell) {
- const dataRange = toZone(ds.dataRange);
- const newRange = ds.labelCell + ":" + toXC(dataRange.right, dataRange.bottom);
- newDataSets.push(newRange);
- }
- else {
- newDataSets.push(ds.dataRange);
- }
- }
- newData.dataSetsHaveTitle = Boolean(dataSets[0].labelCell);
- newData.dataSets = newDataSets;
- sheet.figures[f].data = newData;
- }
- }
- return data;
- },
- },
- {
- description: "remove single quotes in sheet names",
- from: 7,
- to: 8,
- applyMigration(data) {
- var _a;
- const namesTaken = [];
- const globalForbiddenInExcel = new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g");
- for (let sheet of data.sheets || []) {
- if (!sheet.name) {
- continue;
- }
- const oldName = sheet.name;
- const escapedName = oldName.replace(globalForbiddenInExcel, "_");
- let i = 1;
- let newName = escapedName;
- while (namesTaken.includes(newName)) {
- newName = `${escapedName}${i}`;
- i++;
- }
- sheet.name = newName;
- namesTaken.push(newName);
- const replaceName = (str) => {
- if (str === undefined) {
- return str;
- }
- // replaceAll is only available in next Typescript version
- let newString = str.replace(oldName, newName);
- let currentString = str;
- while (currentString !== newString) {
- currentString = newString;
- newString = currentString.replace(oldName, newName);
- }
- return currentString;
- };
- //cells
- for (let xc in sheet.cells) {
- const cell = sheet.cells[xc];
- if (cell.formula) {
- cell.formula.dependencies = cell.formula.dependencies.map(replaceName);
- }
- }
- //charts
- for (let figure of sheet.figures || []) {
- if (figure.type === "chart") {
- const dataSets = figure.data.dataSets.map(replaceName);
- const labelRange = replaceName(figure.data.labelRange);
- figure.data = { ...figure.data, dataSets, labelRange };
- }
- }
- //ConditionalFormats
- for (let cf of sheet.conditionalFormats || []) {
- cf.ranges = cf.ranges.map(replaceName);
- for (const thresholdName of [
- "minimum",
- "maximum",
- "midpoint",
- "upperInflectionPoint",
- "lowerInflectionPoint",
- ]) {
- if (((_a = cf.rule[thresholdName]) === null || _a === void 0 ? void 0 : _a.type) === "formula") {
- cf.rule[thresholdName].value = replaceName(cf.rule[thresholdName].value);
- }
- }
- }
- }
- return data;
- },
- },
- {
- description: "transform chart data structure with design attributes",
- from: 8,
- to: 9,
- applyMigration(data) {
- for (const sheet of data.sheets || []) {
- for (const chart of sheet.figures || []) {
- chart.data.background = BACKGROUND_CHART_COLOR;
- chart.data.verticalAxisPosition = "left";
- chart.data.legendPosition = "top";
- chart.data.stacked = false;
- }
- }
- return data;
- },
- },
- {
- description: "de-normalize formula to reduce exported json size (~30%)",
- from: 9,
- to: 10,
- applyMigration(data) {
- for (let sheet of data.sheets || []) {
- for (let xc in sheet.cells || []) {
- const cell = sheet.cells[xc];
- if (cell.formula) {
- let { text, dependencies } = cell.formula;
- for (let [index, d] of Object.entries(dependencies)) {
- const stringPosition = `\\${FORMULA_REF_IDENTIFIER}${index}\\${FORMULA_REF_IDENTIFIER}`;
- text = text.replace(new RegExp(stringPosition, "g"), d);
- }
- cell.content = text;
- delete cell.formula;
- }
- }
- }
- return data;
- },
- },
- {
- description: "normalize the formats of the cells",
- from: 10,
- to: 11,
- applyMigration(data) {
- const formats = {};
- for (let sheet of data.sheets || []) {
- for (let xc in sheet.cells || []) {
- const cell = sheet.cells[xc];
- if (cell.format) {
- cell.format = getItemId(cell.format, formats);
- }
- }
- }
- data.formats = formats;
- return data;
- },
- },
- {
- description: "Add isVisible to sheets",
- from: 11,
- to: 12,
- applyMigration(data) {
- for (let sheet of data.sheets || []) {
- sheet.isVisible = true;
- }
- return data;
- },
- },
- ];
- /**
- * This function is used to repair faulty data independently of the migration.
- */
- function repairData(data) {
- data = forceUnicityOfFigure(data);
- data = setDefaults(data);
- return data;
- }
- /**
- * Force the unicity of figure ids accross sheets
- */
- function forceUnicityOfFigure(data) {
- if (data.uniqueFigureIds) {
- return data;
- }
- const figureIds = new Set();
- const uuidGenerator = new UuidGenerator();
- for (const sheet of data.sheets || []) {
- for (const figure of sheet.figures || []) {
- if (figureIds.has(figure.id)) {
- figure.id += uuidGenerator.uuidv4();
- }
- figureIds.add(figure.id);
- }
- }
- data.uniqueFigureIds = true;
- return data;
- }
- /**
- * sanity check: try to fix missing fields/corrupted state by providing
- * sensible default values
- */
- function setDefaults(data) {
- data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
- data.sheets = data.sheets
- ? data.sheets.map((s, i) => Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s))
- : [];
- if (data.sheets.length === 0) {
- data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
- }
- return data;
- }
- /**
- * The goal of this function is to repair corrupted/wrong initial messages caused by
- * a bug.
- * The bug should obviously be fixed, but it's too late for existing spreadsheet.
- */
- function repairInitialMessages(data, initialMessages) {
- initialMessages = fixTranslatedSheetIds(data, initialMessages);
- initialMessages = dropCommands(initialMessages, "SORT_CELLS");
- initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
- initialMessages = fixChartDefinitions(data, initialMessages);
- return initialMessages;
- }
- /**
- * When the workbook data is originally empty, a new one is generated on-the-fly.
- * A bug caused the sheet id to be non-deterministic. The sheet id was propagated in
- * commands.
- * This function repairs initial commands with a wrong sheetId.
- */
- function fixTranslatedSheetIds(data, initialMessages) {
- // the fix is only needed when the workbook is generated on-the-fly
- if (Object.keys(data).length !== 0) {
- return initialMessages;
- }
- const sheetIds = [];
- const messages = [];
- const fixSheetId = (cmd) => {
- if (cmd.type === "CREATE_SHEET") {
- sheetIds.push(cmd.sheetId);
- }
- else if ("sheetId" in cmd && !sheetIds.includes(cmd.sheetId)) {
- return { ...cmd, sheetId: INITIAL_SHEET_ID };
- }
- return cmd;
- };
- for (const message of initialMessages) {
- if (message.type === "REMOTE_REVISION") {
- messages.push({
- ...message,
- commands: message.commands.map(fixSheetId),
- });
- }
- else {
- messages.push(message);
- }
- }
- return messages;
- }
- function dropCommands(initialMessages, commandType) {
- const messages = [];
- for (const message of initialMessages) {
- if (message.type === "REMOTE_REVISION") {
- messages.push({
- ...message,
- commands: message.commands.filter((command) => command.type !== commandType),
- });
- }
- else {
- messages.push(message);
- }
- }
- return messages;
- }
- function fixChartDefinitions(data, initialMessages) {
- var _a;
- const messages = [];
- const map = {};
- for (const sheet of data.sheets || []) {
- (_a = sheet.figures) === null || _a === void 0 ? void 0 : _a.forEach((figure) => {
- if (figure.tag === "chart") {
- // chart definition
- map[figure.id] = figure.data;
- }
- });
- }
- for (const message of initialMessages) {
- if (message.type === "REMOTE_REVISION") {
- const commands = [];
- for (const cmd of message.commands) {
- let command = cmd;
- switch (cmd.type) {
- case "CREATE_CHART":
- map[cmd.id] = cmd.definition;
- break;
- case "UPDATE_CHART":
- if (!map[cmd.id]) {
- /** the chart does not exist on the map, it might have been created after a duplicate sheet.
- * We don't have access to the definition, so we skip the command.
- */
- console.log(`Fix chart definition: chart with id ${cmd.id} not found.`);
- continue;
- }
- const definition = map[cmd.id];
- const newDefinition = { ...definition, ...cmd.definition };
- command = { ...cmd, definition: newDefinition };
- map[cmd.id] = newDefinition;
- break;
- }
- commands.push(command);
- }
- messages.push({
- ...message,
- commands,
- });
- }
- else {
- messages.push(message);
- }
- }
- return messages;
- }
- // -----------------------------------------------------------------------------
- // Helpers
- // -----------------------------------------------------------------------------
- function createEmptySheet(sheetId, name) {
- return {
- id: sheetId,
- name,
- colNumber: 26,
- rowNumber: 100,
- cells: {},
- cols: {},
- rows: {},
- merges: [],
- conditionalFormats: [],
- figures: [],
- filterTables: [],
- isVisible: true,
- };
- }
- function createEmptyWorkbookData(sheetName = "Sheet1") {
- const data = {
- version: CURRENT_VERSION,
- sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],
- entities: {},
- styles: {},
- formats: {},
- borders: {},
- revisionId: DEFAULT_REVISION_ID,
- uniqueFigureIds: true,
- };
- return data;
- }
- function createEmptyExcelSheet(sheetId, name) {
- return {
- ...createEmptySheet(sheetId, name),
- charts: [],
- images: [],
- };
- }
- function createEmptyExcelWorkbookData() {
- return {
- ...createEmptyWorkbookData(),
- sheets: [createEmptyExcelSheet(INITIAL_SHEET_ID, "Sheet1")],
- };
- }
-
- /**
- * BasePlugin
- *
- * Since the spreadsheet internal state is quite complex, it is split into
- * multiple parts, each managing a specific concern.
- *
- * This file introduce the BasePlugin, which is the common class that defines
- * how each of these model sub parts should interact with each other.
- * There are two kind of plugins: core plugins handling persistent data
- * and UI plugins handling transient data.
- */
- class BasePlugin {
- constructor(stateObserver, dispatch) {
- this.history = Object.assign(Object.create(stateObserver), {
- update: stateObserver.addChange.bind(stateObserver, this),
- selectCell: () => { },
- });
- this.dispatch = dispatch;
- }
- /**
- * Export for excel should be available for all plugins, even for the UI.
- * In some case, we need to export evaluated value, which is available from
- * UI plugin only.
- */
- exportForExcel(data) { }
- // ---------------------------------------------------------------------------
- // Command handling
- // ---------------------------------------------------------------------------
- /**
- * Before a command is accepted, the model will ask each plugin if the command
- * is allowed. If all of then return true, then we can proceed. Otherwise,
- * the command is cancelled.
- *
- * There should not be any side effects in this method.
- */
- allowDispatch(command) {
- return 0 /* CommandResult.Success */;
- }
- /**
- * This method is useful when a plugin need to perform some action before a
- * command is handled in another plugin. This should only be used if it is not
- * possible to do the work in the handle method.
- */
- beforeHandle(command) { }
- /**
- * This is the standard place to handle any command. Most of the plugin
- * command handling work should take place here.
- */
- handle(command) { }
- /**
- * Sometimes, it is useful to perform some work after a command (and all its
- * subcommands) has been completely handled. For example, when we paste
- * multiple cells, we only want to reevaluate the cell values once at the end.
- */
- finalize() { }
- /**
- * Combine multiple validation functions into a single function
- * returning the list of result of every validation.
- */
- batchValidations(...validations) {
- return (toValidate) => validations.map((validation) => validation.call(this, toValidate)).flat();
- }
- /**
- * Combine multiple validation functions. Every validation is executed one after
- * the other. As soon as one validation fails, it stops and the cancelled reason
- * is returned.
- */
- chainValidations(...validations) {
- return (toValidate) => {
- for (const validation of validations) {
- let results = validation.call(this, toValidate);
- if (!Array.isArray(results)) {
- results = [results];
- }
- const cancelledReasons = results.filter((result) => result !== 0 /* CommandResult.Success */);
- if (cancelledReasons.length) {
- return cancelledReasons;
- }
- }
- return 0 /* CommandResult.Success */;
- };
- }
- checkValidations(command, ...validations) {
- return this.batchValidations(...validations)(command);
- }
- }
- BasePlugin.getters = [];
-
- /**
- * Core plugins handle spreadsheet data.
- * They are responsible to import, export and maintain the spreadsheet
- * persisted state.
- * They should not be concerned about UI parts or transient state.
- */
- class CorePlugin extends BasePlugin {
- constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
- super(stateObserver, dispatch);
- this.range = range;
- range.addRangeProvider(this.adaptRanges.bind(this));
- this.getters = getters;
- this.uuidGenerator = uuidGenerator;
- }
- // ---------------------------------------------------------------------------
- // Import/Export
- // ---------------------------------------------------------------------------
- import(data) { }
- export(data) { }
- /**
- * This method can be implemented in any plugin, to loop over the plugin's data structure and adapt the plugin's ranges.
- * To adapt them, the implementation of the function must have a perfect knowledge of the data structure, thus
- * implementing the loops over it makes sense in the plugin itself.
- * When calling the method applyChange, the range will be adapted if necessary, then a copy will be returned along with
- * the type of change that occurred.
- *
- * @param applyChange a function that, when called, will adapt the range according to the change on the grid
- * @param sheetId an optional sheetId to adapt either range of that sheet specifically, or ranges pointing to that sheet
- */
- adaptRanges(applyChange, sheetId) { }
- /**
- * Implement this method to clean unused external resources, such as images
- * stored on a server which have been deleted.
- */
- garbageCollectExternalResources() { }
- }
-
- /**
- * Formatting plugin.
- *
- * This plugin manages all things related to a cell look:
- * - borders
- */
- class BordersPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.borders = {};
- }
- // ---------------------------------------------------------------------------
- // Command Handling
- // ---------------------------------------------------------------------------
- handle(cmd) {
- switch (cmd.type) {
- case "ADD_MERGE":
- for (const zone of cmd.target) {
- this.addBordersToMerge(cmd.sheetId, zone);
- }
- break;
- case "DUPLICATE_SHEET":
- const borders = this.borders[cmd.sheetId];
- if (borders) {
- // borders is a sparse 2D array.
- // map and slice preserve empty values and do not set `undefined` instead
- const bordersCopy = borders
- .slice()
- .map((col) => col === null || col === void 0 ? void 0 : col.slice().map((border) => ({ ...border })));
- this.history.update("borders", cmd.sheetIdTo, bordersCopy);
- }
- break;
- case "DELETE_SHEET":
- const allBorders = { ...this.borders };
- delete allBorders[cmd.sheetId];
- this.history.update("borders", allBorders);
- break;
- case "SET_BORDER":
- this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
- break;
- case "SET_FORMATTING":
- if (cmd.border) {
- const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
- this.setBorders(cmd.sheetId, target, cmd.border);
- }
- break;
- case "CLEAR_FORMATTING":
- this.clearBorders(cmd.sheetId, cmd.target);
- break;
- case "REMOVE_COLUMNS_ROWS":
- for (let el of cmd.elements) {
- if (cmd.dimension === "COL") {
- this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
- }
- else {
- this.shiftBordersVertically(cmd.sheetId, el + 1, -1);
- }
- }
- break;
- case "ADD_COLUMNS_ROWS":
- if (cmd.dimension === "COL") {
- this.handleAddColumns(cmd);
- }
- else {
- this.handleAddRows(cmd);
- }
- break;
- }
- }
- /**
- * Move borders according to the inserted columns.
- * Ensure borders continuity.
- */
- handleAddColumns(cmd) {
- // The new columns have already been inserted in the sheet at this point.
- let colLeftOfInsertion;
- let colRightOfInsertion;
- if (cmd.position === "before") {
- this.shiftBordersHorizontally(cmd.sheetId, cmd.base, cmd.quantity, {
- moveFirstLeftBorder: true,
- });
- colLeftOfInsertion = cmd.base - 1;
- colRightOfInsertion = cmd.base + cmd.quantity;
- }
- else {
- this.shiftBordersHorizontally(cmd.sheetId, cmd.base + 1, cmd.quantity, {
- moveFirstLeftBorder: false,
- });
- colLeftOfInsertion = cmd.base;
- colRightOfInsertion = cmd.base + cmd.quantity + 1;
- }
- this.ensureColumnBorderContinuity(cmd.sheetId, colLeftOfInsertion, colRightOfInsertion);
- }
- /**
- * Move borders according to the inserted rows.
- * Ensure borders continuity.
- */
- handleAddRows(cmd) {
- // The new rows have already been inserted at this point.
- let rowAboveInsertion;
- let rowBelowInsertion;
- if (cmd.position === "before") {
- this.shiftBordersVertically(cmd.sheetId, cmd.base, cmd.quantity, {
- moveFirstTopBorder: true,
- });
- rowAboveInsertion = cmd.base - 1;
- rowBelowInsertion = cmd.base + cmd.quantity;
- }
- else {
- this.shiftBordersVertically(cmd.sheetId, cmd.base + 1, cmd.quantity, {
- moveFirstTopBorder: false,
- });
- rowAboveInsertion = cmd.base;
- rowBelowInsertion = cmd.base + cmd.quantity + 1;
- }
- this.ensureRowBorderContinuity(cmd.sheetId, rowAboveInsertion, rowBelowInsertion);
- }
- // ---------------------------------------------------------------------------
- // Getters
- // ---------------------------------------------------------------------------
- getCellBorder({ sheetId, col, row }) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
- const border = {
- top: (_c = (_b = (_a = this.borders[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row]) === null || _c === void 0 ? void 0 : _c.horizontal,
- bottom: (_f = (_e = (_d = this.borders[sheetId]) === null || _d === void 0 ? void 0 : _d[col]) === null || _e === void 0 ? void 0 : _e[row + 1]) === null || _f === void 0 ? void 0 : _f.horizontal,
- left: (_j = (_h = (_g = this.borders[sheetId]) === null || _g === void 0 ? void 0 : _g[col]) === null || _h === void 0 ? void 0 : _h[row]) === null || _j === void 0 ? void 0 : _j.vertical,
- right: (_m = (_l = (_k = this.borders[sheetId]) === null || _k === void 0 ? void 0 : _k[col + 1]) === null || _l === void 0 ? void 0 : _l[row]) === null || _m === void 0 ? void 0 : _m.vertical,
- };
- if (!border.bottom && !border.left && !border.right && !border.top) {
- return null;
- }
- return border;
- }
- // ---------------------------------------------------------------------------
- // Private
- // ---------------------------------------------------------------------------
- /**
- * Ensure border continuity between two columns.
- * If the two columns have the same borders (at each row respectively),
- * the same borders are applied to each cell in between.
- */
- ensureColumnBorderContinuity(sheetId, leftColumn, rightColumn) {
- const targetCols = range(leftColumn + 1, rightColumn);
- for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
- const leftBorder = this.getCellBorder({ sheetId, col: leftColumn, row });
- const rightBorder = this.getCellBorder({ sheetId, col: rightColumn, row });
- if (leftBorder && rightBorder) {
- const commonSides = this.getCommonSides(leftBorder, rightBorder);
- for (let col of targetCols) {
- this.addBorder(sheetId, col, row, commonSides);
- }
- }
- }
- }
- /**
- * Ensure border continuity between two rows.
- * If the two rows have the same borders (at each column respectively),
- * the same borders are applied to each cell in between.
- */
- ensureRowBorderContinuity(sheetId, topRow, bottomRow) {
- const targetRows = range(topRow + 1, bottomRow);
- for (let col = 0; col < this.getters.getNumberCols(sheetId); col++) {
- const aboveBorder = this.getCellBorder({ sheetId, col, row: topRow });
- const belowBorder = this.getCellBorder({ sheetId, col, row: bottomRow });
- if (aboveBorder && belowBorder) {
- const commonSides = this.getCommonSides(aboveBorder, belowBorder);
- for (let row of targetRows) {
- this.addBorder(sheetId, col, row, commonSides);
- }
- }
- }
- }
- /**
- * From two borders, return a new border with sides defined in both borders.
- * i.e. the intersection of two borders.
- */
- getCommonSides(border1, border2) {
- const commonBorder = {};
- for (let side of ["top", "bottom", "left", "right"]) {
- if (border1[side] && border1[side] === border2[side]) {
- commonBorder[side] = border1[side];
- }
- }
- return commonBorder;
- }
- /**
- * Get all the columns which contains at least a border
- */
- getColumnsWithBorders(sheetId) {
- const sheetBorders = this.borders[sheetId];
- if (!sheetBorders)
- return [];
- return Object.keys(sheetBorders).map((index) => parseInt(index, 10));
- }
- /**
- * Get the range of all the rows in the sheet
- */
- getRowsRange(sheetId) {
- const sheetBorders = this.borders[sheetId];
- if (!sheetBorders)
- return [];
- return range(0, this.getters.getNumberRows(sheetId) + 1);
- }
- /**
- * Move borders of a sheet horizontally.
- * @param sheetId
- * @param start starting column (included)
- * @param delta how much borders will be moved (negative if moved to the left)
- */
- shiftBordersHorizontally(sheetId, start, delta, { moveFirstLeftBorder } = {}) {
- const borders = this.borders[sheetId];
- if (!borders)
- return;
- if (delta < 0) {
- this.moveBordersOfColumn(sheetId, start, delta, "vertical", {
- destructive: false,
- });
- }
- this.getColumnsWithBorders(sheetId)
- .filter((col) => col >= start)
- .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
- .forEach((col) => {
- if ((col === start && moveFirstLeftBorder) || col !== start) {
- this.moveBordersOfColumn(sheetId, col, delta, "vertical");
- }
- this.moveBordersOfColumn(sheetId, col, delta, "horizontal");
- });
- }
- /**
- * Move borders of a sheet vertically.
- * @param sheetId
- * @param start starting row (included)
- * @param delta how much borders will be moved (negative if moved to the above)
- */
- shiftBordersVertically(sheetId, start, delta, { moveFirstTopBorder } = {}) {
- const borders = this.borders[sheetId];
- if (!borders)
- return;
- if (delta < 0) {
- this.moveBordersOfRow(sheetId, start, delta, "horizontal", {
- destructive: false,
- });
- }
- this.getRowsRange(sheetId)
- .filter((row) => row >= start)
- .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
- .forEach((row) => {
- if ((row === start && moveFirstTopBorder) || row !== start) {
- this.moveBordersOfRow(sheetId, row, delta, "horizontal");
- }
- this.moveBordersOfRow(sheetId, row, delta, "vertical");
- });
- }
- /**
- * Moves the borders (left if `vertical` or top if `horizontal` depending on
- * `borderDirection`) of all cells in an entire row `delta` rows to the right
- * (`delta` > 0) or to the left (`delta` < 0).
- * Note that as the left of a cell is the right of the cell-1, if the left is
- * moved the right is also moved. However, if `horizontal`, the bottom border
- * is not moved.
- * It does it by replacing the target border by the moved border. If the
- * argument `destructive` is given false, the target border is preserved if
- * the moved border is empty
- */
- moveBordersOfRow(sheetId, row, delta, borderDirection, { destructive } = { destructive: true }) {
- const borders = this.borders[sheetId];
- if (!borders)
- return;
- this.getColumnsWithBorders(sheetId).forEach((col) => {
- var _a, _b, _c, _d;
- const targetBorder = (_b = (_a = borders[col]) === null || _a === void 0 ? void 0 : _a[row + delta]) === null || _b === void 0 ? void 0 : _b[borderDirection];
- const movedBorder = (_d = (_c = borders[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d[borderDirection];
- this.history.update("borders", sheetId, col, row + delta, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
- this.history.update("borders", sheetId, col, row, borderDirection, undefined);
- });
- }
- /**
- * Moves the borders (left if `vertical` or top if `horizontal` depending on
- * `borderDirection`) of all cells in an entire column `delta` columns below
- * (`delta` > 0) or above (`delta` < 0).
- * Note that as the top of a cell is the bottom of the cell-1, if the top is
- * moved the bottom is also moved. However, if `vertical`, the right border
- * is not moved.
- * It does it by replacing the target border by the moved border. If the
- * argument `destructive` is given false, the target border is preserved if
- * the moved border is empty
- */
- moveBordersOfColumn(sheetId, col, delta, borderDirection, { destructive } = { destructive: true }) {
- const borders = this.borders[sheetId];
- if (!borders)
- return;
- this.getRowsRange(sheetId).forEach((row) => {
- var _a, _b, _c, _d;
- const targetBorder = (_b = (_a = borders[col + delta]) === null || _a === void 0 ? void 0 : _a[row]) === null || _b === void 0 ? void 0 : _b[borderDirection];
- const movedBorder = (_d = (_c = borders[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d[borderDirection];
- this.history.update("borders", sheetId, col + delta, row, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
- this.history.update("borders", sheetId, col, row, borderDirection, undefined);
- });
- }
- /**
- * Set the borders of a cell.
- * It overrides the current border if override == true.
- */
- setBorder(sheetId, col, row, border, override = true) {
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
- if (override || !((_d = (_c = (_b = (_a = this.borders) === null || _a === void 0 ? void 0 : _a[sheetId]) === null || _b === void 0 ? void 0 : _b[col]) === null || _c === void 0 ? void 0 : _c[row]) === null || _d === void 0 ? void 0 : _d.vertical)) {
- this.history.update("borders", sheetId, col, row, "vertical", border === null || border === void 0 ? void 0 : border.left);
- }
- if (override || !((_h = (_g = (_f = (_e = this.borders) === null || _e === void 0 ? void 0 : _e[sheetId]) === null || _f === void 0 ? void 0 : _f[col]) === null || _g === void 0 ? void 0 : _g[row]) === null || _h === void 0 ? void 0 : _h.horizontal)) {
- this.history.update("borders", sheetId, col, row, "horizontal", border === null || border === void 0 ? void 0 : border.top);
- }
- if (override || !((_m = (_l = (_k = (_j = this.borders) === null || _j === void 0 ? void 0 : _j[sheetId]) === null || _k === void 0 ? void 0 : _k[col + 1]) === null || _l === void 0 ? void 0 : _l[row]) === null || _m === void 0 ? void 0 : _m.vertical)) {
- this.history.update("borders", sheetId, col + 1, row, "vertical", border === null || border === void 0 ? void 0 : border.right);
- }
- if (override || !((_r = (_q = (_p = (_o = this.borders) === null || _o === void 0 ? void 0 : _o[sheetId]) === null || _p === void 0 ? void 0 : _p[col]) === null || _q === void 0 ? void 0 : _q[row + 1]) === null || _r === void 0 ? void 0 : _r.horizontal)) {
- this.history.update("borders", sheetId, col, row + 1, "horizontal", border === null || border === void 0 ? void 0 : border.bottom);
- }
- }
- /**
- * Remove the borders of a zone
- */
- clearBorders(sheetId, zones) {
- for (let zone of zones) {
- for (let row = zone.top; row <= zone.bottom; row++) {
- this.history.update("borders", sheetId, zone.right + 1, row, "vertical", undefined);
- for (let col = zone.left; col <= zone.right; col++) {
- this.history.update("borders", sheetId, col, row, undefined);
- }
- }
- for (let col = zone.left; col <= zone.right; col++) {
- this.history.update("borders", sheetId, col, zone.bottom + 1, "horizontal", undefined);
- }
- }
- }
- /**
- * Add a border to the existing one to a cell
- */
- addBorder(sheetId, col, row, border) {
- this.setBorder(sheetId, col, row, {
- ...this.getCellBorder({ sheetId, col, row }),
- ...border,
- });
- }
- /**
- * Set the borders of a zone by computing the borders to add from the given
- * command
- */
- setBorders(sheetId, zones, command) {
- if (command === "clear") {
- return this.clearBorders(sheetId, zones);
- }
- for (let zone of zones) {
- if (command === "h" || command === "hv" || command === "all") {
- for (let row = zone.top + 1; row <= zone.bottom; row++) {
- for (let col = zone.left; col <= zone.right; col++) {
- this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });
- }
- }
- }
- if (command === "v" || command === "hv" || command === "all") {
- for (let row = zone.top; row <= zone.bottom; row++) {
- for (let col = zone.left + 1; col <= zone.right; col++) {
- this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });
- }
- }
- }
- if (command === "left" || command === "all" || command === "external") {
- for (let row = zone.top; row <= zone.bottom; row++) {
- this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });
- }
- }
- if (command === "right" || command === "all" || command === "external") {
- for (let row = zone.top; row <= zone.bottom; row++) {
- this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });
- }
- }
- if (command === "top" || command === "all" || command === "external") {
- for (let col = zone.left; col <= zone.right; col++) {
- this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });
- }
- }
- if (command === "bottom" || command === "all" || command === "external") {
- for (let col = zone.left; col <= zone.right; col++) {
- this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });
- }
- }
- }
- }
- /**
- * Compute the borders to add to the given zone merged.
- */
- addBordersToMerge(sheetId, zone) {
- const { left, right, top, bottom } = zone;
- const bordersTopLeft = this.getCellBorder({ sheetId, col: left, row: top });
- const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });
- this.clearBorders(sheetId, [zone]);
- if (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.top) {
- this.setBorders(sheetId, [{ ...zone, bottom: top }], "top");
- }
- if (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.left) {
- this.setBorders(sheetId, [{ ...zone, right: left }], "left");
- }
- if ((bordersBottomRight === null || bordersBottomRight === void 0 ? void 0 : bordersBottomRight.bottom) || (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.bottom)) {
- this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom");
- }
- if ((bordersBottomRight === null || bordersBottomRight === void 0 ? void 0 : bordersBottomRight.right) || (bordersTopLeft === null || bordersTopLeft === void 0 ? void 0 : bordersTopLeft.right)) {
- this.setBorders(sheetId, [{ ...zone, left: right }], "right");
- }
- }
- // ---------------------------------------------------------------------------
- // Import/Export
- // ---------------------------------------------------------------------------
- import(data) {
- // Borders
- if (data.borders) {
- for (let sheet of data.sheets) {
- for (let [xc, cell] of Object.entries(sheet.cells)) {
- if (cell === null || cell === void 0 ? void 0 : cell.border) {
- const border = data.borders[cell.border];
- const { col, row } = toCartesian(xc);
- this.setBorder(sheet.id, col, row, border, false);
- }
- }
- }
- }
- // Merges
- for (let sheetData of data.sheets) {
- if (sheetData.merges) {
- for (let merge of sheetData.merges) {
- this.addBordersToMerge(sheetData.id, toZone(merge));
- }
- }
- }
- }
- export(data) {
- // Borders
- let borderId = 0;
- const borders = {};
- /**
- * Get the id of the given border. If the border does not exist, it creates
- * one.
- */
- function getBorderId(border) {
- for (let [key, value] of Object.entries(borders)) {
- if (stringify(value) === stringify(border)) {
- return parseInt(key, 10);
- }
- }
- borders[++borderId] = border;
- return borderId;
- }
- for (let sheet of data.sheets) {
- for (let col = 0; col < sheet.colNumber; col++) {
- for (let row = 0; row < sheet.rowNumber; row++) {
- const border = this.getCellBorder({ sheetId: sheet.id, col, row });
- if (border) {
- const xc = toXC(col, row);
- const cell = sheet.cells[xc];
- const borderId = getBorderId(border);
- if (cell) {
- cell.border = borderId;
- }
- else {
- sheet.cells[xc] = { border: borderId };
- }
- }
- }
- }
- }
- data.borders = borders;
- }
- exportForExcel(data) {
- this.export(data);
- }
- }
- BordersPlugin.getters = ["getCellBorder"];
-
class FunctionCodeBuilder {
+ scope;
+ code = "";
constructor(scope = new Scope()) {
this.scope = scope;
- this.code = "";
}
append(...lines) {
this.code += lines.map((line) => line.toString()).join("\n") + "\n";
@@ -28295,6 +24962,9 @@
}
}
class FunctionCodeImpl {
+ scope;
+ returnExpression;
+ code;
constructor(scope, code, returnExpression) {
this.scope = scope;
this.returnExpression = returnExpression;
@@ -28324,10 +24994,8 @@
}
}
class Scope {
- constructor() {
- this.nextId = 1;
- this.declaredVariables = new Set();
- }
+ nextId = 1;
+ declaredVariables = new Set();
nextVariableName() {
const name = `_${this.nextId++}`;
this.declaredVariables.add(name);
@@ -28363,6 +25031,215 @@
return result.trim();
}
+ /**
+ * Tokenizer
+ *
+ * A tokenizer is a piece of code whose job is to transform a string into a list
+ * of "tokens". For example, "(12+" is converted into:
+ * [{type: "LEFT_PAREN", value: "("},
+ * {type: "NUMBER", value: "12"},
+ * {type: "OPERATOR", value: "+"}]
+ *
+ * As the example shows, a tokenizer does not care about the meaning behind those
+ * tokens. It only cares about the structure.
+ *
+ * The tokenizer is usually the first step in a compilation pipeline. Also, it
+ * is useful for the composer, which needs to be able to work with incomplete
+ * formulas.
+ */
+ const functions$1 = functionRegistry.content;
+ const POSTFIX_UNARY_OPERATORS = ["%"];
+ const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
+ function tokenize(str) {
+ str = replaceSpecialSpaces(str);
+ const chars = new TokenizingChars(str);
+ const result = [];
+ while (!chars.isOver()) {
+ let token = tokenizeSpace(chars) ||
+ tokenizeMisc(chars) ||
+ tokenizeOperator(chars) ||
+ tokenizeString(chars) ||
+ tokenizeDebugger(chars) ||
+ tokenizeInvalidRange(chars) ||
+ tokenizeNumber(chars) ||
+ tokenizeSymbol(chars);
+ if (!token) {
+ token = { type: "UNKNOWN", value: chars.shift() };
+ }
+ result.push(token);
+ }
+ return result;
+ }
+ function tokenizeDebugger(chars) {
+ if (chars.current() === "?") {
+ chars.shift();
+ return { type: "DEBUGGER", value: "?" };
+ }
+ return null;
+ }
+ const misc = {
+ ",": "COMMA",
+ "(": "LEFT_PAREN",
+ ")": "RIGHT_PAREN",
+ };
+ function tokenizeMisc(chars) {
+ if (chars.current() in misc) {
+ const value = chars.shift();
+ const type = misc[value];
+ return { type, value };
+ }
+ return null;
+ }
+ function tokenizeOperator(chars) {
+ for (let op of OPERATORS) {
+ if (chars.currentStartsWith(op)) {
+ chars.advanceBy(op.length);
+ return { type: "OPERATOR", value: op };
+ }
+ }
+ return null;
+ }
+ function tokenizeNumber(chars) {
+ const match = chars.remaining().match(formulaNumberRegexp);
+ if (match) {
+ chars.advanceBy(match[0].length);
+ return { type: "NUMBER", value: match[0] };
+ }
+ return null;
+ }
+ function tokenizeString(chars) {
+ if (chars.current() === '"') {
+ const startChar = chars.shift();
+ let letters = startChar;
+ while (chars.current() &&
+ (chars.current() !== startChar || letters[letters.length - 1] === "\\")) {
+ letters += chars.shift();
+ }
+ if (chars.current() === '"') {
+ letters += chars.shift();
+ }
+ return {
+ type: "STRING",
+ value: letters,
+ };
+ }
+ return null;
+ }
+ const separatorRegexp = /\w|\.|!|\$/;
+ /**
+ * A "Symbol" is just basically any word-like element that can appear in a
+ * formula, which is not a string. So:
+ * A1
+ * SUM
+ * CEILING.MATH
+ * A$1
+ * Sheet2!A2
+ * 'Sheet 2'!A2
+ *
+ * are examples of symbols
+ */
+ function tokenizeSymbol(chars) {
+ let result = "";
+ // there are two main cases to manage: either something which starts with
+ // a ', like 'Sheet 2'A2, or a word-like element.
+ if (chars.current() === "'") {
+ let lastChar = chars.shift();
+ result += lastChar;
+ while (chars.current()) {
+ lastChar = chars.shift();
+ result += lastChar;
+ if (lastChar === "'") {
+ if (chars.current() && chars.current() === "'") {
+ lastChar = chars.shift();
+ result += lastChar;
+ }
+ else {
+ break;
+ }
+ }
+ }
+ if (lastChar !== "'") {
+ return {
+ type: "UNKNOWN",
+ value: result,
+ };
+ }
+ }
+ while (chars.current() && separatorRegexp.test(chars.current())) {
+ result += chars.shift();
+ }
+ if (result.length) {
+ const value = result;
+ const isFunction = value.toUpperCase() in functions$1;
+ if (isFunction) {
+ return { type: "FUNCTION", value };
+ }
+ const isReference = rangeReference.test(value);
+ if (isReference) {
+ return { type: "REFERENCE", value };
+ }
+ else {
+ return { type: "SYMBOL", value };
+ }
+ }
+ return null;
+ }
+ function tokenizeSpace(chars) {
+ let length = 0;
+ while (chars.current() === NEWLINE) {
+ length++;
+ chars.shift();
+ }
+ if (length) {
+ return { type: "SPACE", value: NEWLINE.repeat(length) };
+ }
+ while (chars.current() === " ") {
+ length++;
+ chars.shift();
+ }
+ if (length) {
+ return { type: "SPACE", value: " ".repeat(length) };
+ }
+ return null;
+ }
+ function tokenizeInvalidRange(chars) {
+ if (chars.currentStartsWith(INCORRECT_RANGE_STRING)) {
+ chars.advanceBy(INCORRECT_RANGE_STRING.length);
+ return { type: "INVALID_REFERENCE", value: INCORRECT_RANGE_STRING };
+ }
+ return null;
+ }
+ class TokenizingChars {
+ text;
+ currentIndex = 0;
+ constructor(text) {
+ this.text = text;
+ }
+ current() {
+ return this.text[this.currentIndex];
+ }
+ shift() {
+ return this.text[this.currentIndex++];
+ }
+ advanceBy(length) {
+ this.currentIndex += length;
+ }
+ isOver() {
+ return this.currentIndex >= this.text.length;
+ }
+ remaining() {
+ return this.text.substring(this.currentIndex);
+ }
+ currentStartsWith(str) {
+ for (let j = 0; j < str.length; j++) {
+ if (this.text[this.currentIndex + j] !== str[j]) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
const functionRegex = /[a-zA-Z0-9\_]+(\.[a-zA-Z0-9\_]+)*/;
const UNARY_OPERATORS_PREFIX = ["-", "+"];
const UNARY_OPERATORS_POSTFIX = ["%"];
@@ -28390,7 +25267,6 @@
* for SUM(1,2)+3, the next operand is SUM(1,2)
*/
function parseOperand(tokens) {
- var _a, _b, _c;
const current = tokens.shift();
if (!current) {
throw new BadExpressionError(DEFAULT_ERROR_MESSAGE);
@@ -28410,12 +25286,12 @@
case "INVALID_REFERENCE":
throw new InvalidReferenceError();
case "REFERENCE":
- if (((_a = tokens[0]) === null || _a === void 0 ? void 0 : _a.value) === ":" && ((_b = tokens[1]) === null || _b === void 0 ? void 0 : _b.type) === "REFERENCE") {
+ if (tokens[0]?.value === ":" && tokens[1]?.type === "REFERENCE") {
tokens.shift();
const rightReference = tokens.shift();
return {
type: "REFERENCE",
- value: `${current.value}:${rightReference === null || rightReference === void 0 ? void 0 : rightReference.value}`,
+ value: `${current.value}:${rightReference?.value}`,
};
}
return {
@@ -28427,7 +25303,7 @@
return { type: "BOOLEAN", value: current.value.toUpperCase() === "TRUE" };
}
if (current.value) {
- if (functionRegex.test(current.value) && ((_c = tokens[0]) === null || _c === void 0 ? void 0 : _c.type) === "LEFT_PAREN") {
+ if (functionRegex.test(current.value) && tokens[0]?.type === "LEFT_PAREN") {
throw new UnknownFunctionError(current.value);
}
}
@@ -28451,16 +25327,15 @@
}
}
function parseFunctionArgs(tokens) {
- var _a;
consumeOrThrow(tokens, "LEFT_PAREN", _lt("Missing opening parenthesis"));
const nextToken = tokens[0];
- if ((nextToken === null || nextToken === void 0 ? void 0 : nextToken.type) === "RIGHT_PAREN") {
+ if (nextToken?.type === "RIGHT_PAREN") {
consumeOrThrow(tokens, "RIGHT_PAREN");
return [];
}
const args = [];
args.push(parseOneFunctionArg(tokens));
- while (((_a = tokens[0]) === null || _a === void 0 ? void 0 : _a.type) !== "RIGHT_PAREN") {
+ while (tokens[0]?.type !== "RIGHT_PAREN") {
consumeOrThrow(tokens, "COMMA", _lt("Wrong function call"));
args.push(parseOneFunctionArg(tokens));
}
@@ -28469,7 +25344,7 @@
}
function parseOneFunctionArg(tokens) {
const nextToken = tokens[0];
- if ((nextToken === null || nextToken === void 0 ? void 0 : nextToken.type) === "COMMA" || (nextToken === null || nextToken === void 0 ? void 0 : nextToken.type) === "RIGHT_PAREN") {
+ if (nextToken?.type === "COMMA" || nextToken?.type === "RIGHT_PAREN") {
// arg is empty: "sum(1,,2)" "sum(,1)" "sum(1,)"
return { type: "EMPTY", value: "" };
}
@@ -28482,14 +25357,13 @@
}
}
function parseExpression(tokens, parent_priority = 0) {
- var _a;
if (tokens.length === 0) {
throw new BadExpressionError(DEFAULT_ERROR_MESSAGE);
}
let left = parseOperand(tokens);
// as long as we have operators with higher priority than the parent one,
// continue parsing the expression because it is a child sub-expression
- while (((_a = tokens[0]) === null || _a === void 0 ? void 0 : _a.type) === "OPERATOR" && OP_PRIORITY[tokens[0].value] > parent_priority) {
+ while (tokens[0]?.type === "OPERATOR" && OP_PRIORITY[tokens[0].value] > parent_priority) {
const operator = tokens.shift().value;
if (UNARY_OPERATORS_POSTFIX.includes(operator)) {
left = {
@@ -28627,6 +25501,145 @@
return needParenthesis ? `(${astToFormula(rightOperation)})` : astToFormula(rightOperation);
}
+ var State;
+ (function (State) {
+ /**
+ * Initial state.
+ * Expecting any reference for the left part of a range
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
+ */
+ State[State["LeftRef"] = 0] = "LeftRef";
+ /**
+ * Expecting any reference for the right part of a range
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
+ */
+ State[State["RightRef"] = 1] = "RightRef";
+ /**
+ * Expecting the separator without any constraint on the right part
+ */
+ State[State["Separator"] = 2] = "Separator";
+ /**
+ * Expecting the separator for a full column range
+ */
+ State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
+ /**
+ * Expecting the separator for a full row range
+ */
+ State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
+ /**
+ * Expecting the right part of a full column range
+ * e.g. "1", "A1"
+ */
+ State[State["RightColumnRef"] = 5] = "RightColumnRef";
+ /**
+ * Expecting the right part of a full row range
+ * e.g. "A", "A1"
+ */
+ State[State["RightRowRef"] = 6] = "RightRowRef";
+ /**
+ * Final state. A range has been matched
+ */
+ State[State["Found"] = 7] = "Found";
+ })(State || (State = {}));
+ const goTo = (state, guard = () => true) => [
+ {
+ goTo: state,
+ guard,
+ },
+ ];
+ const goToMulti = (state, guard = () => true) => ({
+ goTo: state,
+ guard,
+ });
+ const machine = {
+ [State.LeftRef]: {
+ REFERENCE: goTo(State.Separator),
+ NUMBER: goTo(State.FullRowSeparator),
+ SYMBOL: [
+ goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
+ goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
+ ],
+ },
+ [State.FullColumnSeparator]: {
+ SPACE: goTo(State.FullColumnSeparator),
+ OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
+ },
+ [State.FullRowSeparator]: {
+ SPACE: goTo(State.FullRowSeparator),
+ OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
+ },
+ [State.Separator]: {
+ SPACE: goTo(State.Separator),
+ OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
+ },
+ [State.RightRef]: {
+ SPACE: goTo(State.RightRef),
+ NUMBER: goTo(State.Found),
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
+ },
+ [State.RightColumnRef]: {
+ SPACE: goTo(State.RightColumnRef),
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
+ },
+ [State.RightRowRef]: {
+ SPACE: goTo(State.RightRowRef),
+ NUMBER: goTo(State.Found),
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
+ },
+ [State.Found]: {},
+ };
+ /**
+ * Check if the list of tokens starts with a sequence of tokens representing
+ * a range.
+ * If a range is found, the sequence is removed from the list and is returned
+ * as a single token.
+ */
+ function matchReference(tokens) {
+ let head = 0;
+ let transitions = machine[State.LeftRef];
+ const matchedTokens = [];
+ while (transitions !== undefined) {
+ const token = tokens[head++];
+ if (!token) {
+ return null;
+ }
+ const transition = transitions[token.type]?.find((transition) => transition.guard(token));
+ const nextState = transition ? transition.goTo : undefined;
+ switch (nextState) {
+ case undefined:
+ return null;
+ case State.Found:
+ matchedTokens.push(token);
+ tokens.splice(0, head);
+ return {
+ type: "REFERENCE",
+ value: concat(matchedTokens.map((token) => token.value)),
+ };
+ default:
+ transitions = machine[nextState];
+ matchedTokens.push(token);
+ break;
+ }
+ }
+ return null;
+ }
+ /**
+ * Take the result of the tokenizer and transform it to be usable in the
+ * manipulations of range
+ *
+ * @param formula
+ */
+ function rangeTokenize(formula) {
+ const tokens = tokenize(formula);
+ const result = [];
+ while (tokens.length) {
+ result.push(matchReference(tokens) || tokens.shift());
+ }
+ return result;
+ }
+
const functions = functionRegistry.content;
const OPERATOR_MAP = {
"=": "EQ",
@@ -28977,6 +25990,3270 @@
return mapParentFunction(mapParenthesis(enrichTokens(tokens)));
}
+ // -------------------------------------
+ // CF HELPERS
+ // -------------------------------------
+ /**
+ * Convert the conditional formatting o-spreadsheet operator to
+ * the corresponding excel operator.
+ * */
+ function convertOperator(operator) {
+ switch (operator) {
+ case "IsNotEmpty":
+ return "notContainsBlanks";
+ case "IsEmpty":
+ return "containsBlanks";
+ case "NotContains":
+ return "notContainsBlanks";
+ default:
+ return operator.charAt(0).toLowerCase() + operator.slice(1);
+ }
+ }
+ // -------------------------------------
+ // WORKSHEET HELPERS
+ // -------------------------------------
+ function getCellType(value) {
+ switch (typeof value) {
+ case "boolean":
+ return "b";
+ case "string":
+ return "str";
+ case "number":
+ return "n";
+ }
+ }
+ function convertHeightToExcel(height) {
+ return Math.round(HEIGHT_FACTOR * height * 100) / 100;
+ }
+ function convertWidthToExcel(width) {
+ return Math.round(WIDTH_FACTOR * width * 100) / 100;
+ }
+ function convertHeightFromExcel(height) {
+ if (!height)
+ return height;
+ return Math.round((height / HEIGHT_FACTOR) * 100) / 100;
+ }
+ function convertWidthFromExcel(width) {
+ if (!width)
+ return width;
+ return Math.round((width / WIDTH_FACTOR) * 100) / 100;
+ }
+ function convertBorderDescr(descr) {
+ if (!descr) {
+ return undefined;
+ }
+ return {
+ style: descr[0],
+ color: { rgb: descr[1] },
+ };
+ }
+ function extractStyle(cell, data) {
+ let style = {};
+ if (cell.style) {
+ style = data.styles[cell.style];
+ }
+ const format = extractFormat(cell, data);
+ const exportedBorder = {};
+ if (cell.border) {
+ const border = data.borders[cell.border];
+ exportedBorder.left = convertBorderDescr(border.left);
+ exportedBorder.right = convertBorderDescr(border.right);
+ exportedBorder.bottom = convertBorderDescr(border.bottom);
+ exportedBorder.top = convertBorderDescr(border.top);
+ }
+ const styles = {
+ font: {
+ size: style?.fontSize || DEFAULT_FONT_SIZE,
+ color: { rgb: style?.textColor ? style.textColor : "000000" },
+ family: 2,
+ name: "Arial",
+ },
+ fill: style?.fillColor
+ ? {
+ fgColor: { rgb: style.fillColor },
+ }
+ : { reservedAttribute: "none" },
+ numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
+ border: exportedBorder || {},
+ alignment: {
+ horizontal: style.align,
+ vertical: style.verticalAlign
+ ? V_ALIGNMENT_EXPORT_CONVERSION_MAP[style.verticalAlign]
+ : undefined,
+ wrapText: style.wrapping === "wrap",
+ },
+ };
+ styles.font["strike"] = !!style?.strikethrough || undefined;
+ styles.font["underline"] = !!style?.underline || undefined;
+ styles.font["bold"] = !!style?.bold || undefined;
+ styles.font["italic"] = !!style?.italic || undefined;
+ return styles;
+ }
+ function extractFormat(cell, data) {
+ if (cell.format) {
+ return data.formats[cell.format];
+ }
+ if (cell.isFormula) {
+ const tokens = tokenize(cell.content || "");
+ const functions = functionRegistry.content;
+ const isExported = tokens
+ .filter((tk) => tk.type === "FUNCTION")
+ .every((tk) => functions[tk.value.toUpperCase()].isExported);
+ if (!isExported) {
+ return cell.computedFormat;
+ }
+ }
+ return undefined;
+ }
+ function normalizeStyle(construct, styles) {
+ const { id: fontId } = pushElement(styles["font"], construct.fonts);
+ const { id: fillId } = pushElement(styles["fill"], construct.fills);
+ const { id: borderId } = pushElement(styles["border"], construct.borders);
+ // Normalize this
+ const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
+ const style = {
+ fontId,
+ fillId,
+ borderId,
+ numFmtId,
+ alignment: {
+ vertical: styles.alignment.vertical,
+ horizontal: styles.alignment.horizontal,
+ wrapText: styles.alignment.wrapText,
+ },
+ };
+ const { id } = pushElement(style, construct.styles);
+ return id;
+ }
+ function convertFormat(format, numFmtStructure) {
+ if (!format) {
+ return 0;
+ }
+ let formatId = XLSX_FORMAT_MAP[format.format];
+ if (!formatId) {
+ const { id } = pushElement(format, numFmtStructure);
+ formatId = id + FIRST_NUMFMT_ID;
+ }
+ return formatId;
+ }
+ /**
+ * Add a relation to the given file and return its id.
+ */
+ function addRelsToFile(relsFiles, path, rel) {
+ let relsFile = relsFiles.find((file) => file.path === path);
+ // the id is a one-based int casted as string
+ let id;
+ if (!relsFile) {
+ id = "rId1";
+ relsFiles.push({ path, rels: [{ ...rel, id }] });
+ }
+ else {
+ id = `rId${(relsFile.rels.length + 1).toString()}`;
+ relsFile.rels.push({
+ ...rel,
+ id,
+ });
+ }
+ return id;
+ }
+ function pushElement(property, propertyList) {
+ for (let [key, value] of Object.entries(propertyList)) {
+ if (JSON.stringify(value) === JSON.stringify(property)) {
+ return { id: parseInt(key, 10), list: propertyList };
+ }
+ }
+ let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));
+ if (elemId === -1) {
+ propertyList.push(property);
+ elemId = propertyList.length - 1;
+ }
+ return {
+ id: elemId,
+ list: propertyList,
+ };
+ }
+ const chartIds = [];
+ /**
+ * Convert a chart o-spreadsheet id to a xlsx id which
+ * are unsigned integers (starting from 1).
+ */
+ function convertChartId(chartId) {
+ const xlsxId = chartIds.findIndex((id) => id === chartId);
+ if (xlsxId === -1) {
+ chartIds.push(chartId);
+ return chartIds.length;
+ }
+ return xlsxId + 1;
+ }
+ const imageIds = [];
+ /**
+ * Convert a image o-spreadsheet id to a xlsx id which
+ * are unsigned integers (starting from 1).
+ */
+ function convertImageId(imageId) {
+ const xlsxId = imageIds.findIndex((id) => id === imageId);
+ if (xlsxId === -1) {
+ imageIds.push(imageId);
+ return imageIds.length;
+ }
+ return xlsxId + 1;
+ }
+ /**
+ * Convert a value expressed in dot to EMU.
+ * EMU = English Metrical Unit
+ * There are 914400 EMU per inch.
+ *
+ * /!\ A value expressed in EMU cannot be fractional.
+ * See https://docs.microsoft.com/en-us/windows/win32/vml/msdn-online-vml-units#other-units-of-measurement
+ */
+ function convertDotValueToEMU(value) {
+ const DPI = 96;
+ return Math.round((value * 914400) / DPI);
+ }
+ function getRangeSize(reference, defaultSheetIndex, data) {
+ let xc = reference;
+ let sheetName = undefined;
+ ({ xc, sheetName } = splitReference(reference));
+ let rangeSheetIndex;
+ if (sheetName) {
+ const index = data.sheets.findIndex((sheet) => sheet.name === sheetName);
+ if (index < 0) {
+ throw new Error("Unable to find a sheet with the name " + sheetName);
+ }
+ rangeSheetIndex = index;
+ }
+ else {
+ rangeSheetIndex = Number(defaultSheetIndex);
+ }
+ const zone = toUnboundedZone(xc);
+ if (zone.right === undefined) {
+ zone.right = data.sheets[rangeSheetIndex].colNumber;
+ }
+ if (zone.bottom === undefined) {
+ zone.bottom = data.sheets[rangeSheetIndex].rowNumber;
+ }
+ return (zone.right - zone.left + 1) * (zone.bottom - zone.top + 1);
+ }
+ function convertEMUToDotValue(value) {
+ const DPI = 96;
+ return Math.round((value * DPI) / 914400);
+ }
+ /**
+ * Get the position of the start of a column in Excel (in px).
+ */
+ function getColPosition(colIndex, sheetData) {
+ let position = 0;
+ for (let i = 0; i < colIndex; i++) {
+ const colAtIndex = sheetData.cols.find((col) => i >= col.min && i <= col.max);
+ if (colAtIndex?.width) {
+ position += colAtIndex.width;
+ }
+ else if (sheetData.sheetFormat?.defaultColWidth) {
+ position += sheetData.sheetFormat.defaultColWidth;
+ }
+ else {
+ position += EXCEL_DEFAULT_COL_WIDTH;
+ }
+ }
+ return position / WIDTH_FACTOR;
+ }
+ /**
+ * Get the position of the start of a row in Excel (in px).
+ */
+ function getRowPosition(rowIndex, sheetData) {
+ let position = 0;
+ for (let i = 0; i < rowIndex; i++) {
+ const rowAtIndex = sheetData.rows[i];
+ if (rowAtIndex?.height) {
+ position += rowAtIndex.height;
+ }
+ else if (sheetData.sheetFormat?.defaultRowHeight) {
+ position += sheetData.sheetFormat.defaultRowHeight;
+ }
+ else {
+ position += EXCEL_DEFAULT_ROW_HEIGHT;
+ }
+ }
+ return position / HEIGHT_FACTOR;
+ }
+
+ function convertFigures(sheetData) {
+ let id = 1;
+ return sheetData.figures
+ .map((figure) => convertFigure(figure, (id++).toString(), sheetData))
+ .filter(isDefined$1);
+ }
+ function convertFigure(figure, id, sheetData) {
+ const x1 = getColPosition(figure.anchors[0].col, sheetData) +
+ convertEMUToDotValue(figure.anchors[0].colOffset);
+ const x2 = getColPosition(figure.anchors[1].col, sheetData) +
+ convertEMUToDotValue(figure.anchors[1].colOffset);
+ const y1 = getRowPosition(figure.anchors[0].row, sheetData) +
+ convertEMUToDotValue(figure.anchors[0].rowOffset);
+ const y2 = getRowPosition(figure.anchors[1].row, sheetData) +
+ convertEMUToDotValue(figure.anchors[1].rowOffset);
+ const width = x2 - x1;
+ const height = y2 - y1;
+ const chartData = convertChartData(figure.data);
+ if (!chartData)
+ return undefined;
+ return {
+ id: id,
+ x: x1,
+ y: y1,
+ width: width,
+ height: height,
+ tag: "chart",
+ data: convertChartData(figure.data),
+ };
+ }
+ function convertChartData(chartData) {
+ const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
+ const labelRange = chartData.labelRange
+ ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
+ : undefined;
+ let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
+ // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
+ if (chartData.type === "pie") {
+ dataSets.reverse();
+ }
+ return {
+ dataSets,
+ dataSetsHaveTitle,
+ labelRange,
+ title: chartData.title || "",
+ type: chartData.type,
+ background: convertColor({ rgb: chartData.backgroundColor }) || "#FFFFFF",
+ verticalAxisPosition: chartData.verticalAxisPosition,
+ legendPosition: chartData.legendPosition,
+ stacked: chartData.stacked || false,
+ aggregated: false,
+ labelsAsText: false,
+ };
+ }
+ function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
+ let { sheetName, xc } = splitReference(range);
+ if (sheetName) {
+ sheetName = getCanonicalSheetName(sheetName) + "!";
+ }
+ else {
+ sheetName = "";
+ }
+ let zone = toUnboundedZone(xc);
+ if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
+ const height = zone.bottom - zone.top + 1;
+ const width = zone.right - zone.left + 1;
+ if (height === 1) {
+ zone = { ...zone, left: zone.left - 1 };
+ }
+ else if (width === 1) {
+ zone = { ...zone, top: zone.top - 1 };
+ }
+ }
+ const dataXC = zoneToXc(zone);
+ return sheetName + dataXC;
+ }
+
+ /**
+ * Match external reference (ex. '[1]Sheet 3'!$B$4)
+ *
+ * First match group is the external reference id
+ * Second match group is the sheet id
+ * Third match group is the reference of the cell
+ */
+ const externalReferenceRegex = new RegExp(/'?\[([0-9]*)\](.*)'?!(\$?[a-zA-Z]*\$?[0-9]*)/g);
+ const subtotalRegex = new RegExp(/SUBTOTAL\(([0-9]*),/g);
+ const cellRegex = new RegExp(cellReference.source, "ig");
+ function convertFormulasContent(sheet, data) {
+ const sfMap = getSharedFormulasMap(sheet);
+ for (let cell of sheet.rows.map((row) => row.cells).flat()) {
+ if (cell?.formula) {
+ cell.formula.content =
+ cell.formula.sharedIndex !== undefined && !cell.formula.content
+ ? "=" + adaptFormula(cell.xc, sfMap[cell.formula.sharedIndex])
+ : "=" + cell.formula.content;
+ cell.formula.content = convertFormula(cell.formula.content, data);
+ }
+ }
+ }
+ function getSharedFormulasMap(sheet) {
+ const formulas = {};
+ for (let row of sheet.rows) {
+ for (let cell of row.cells) {
+ if (cell.formula && cell.formula.sharedIndex !== undefined && cell.formula.content) {
+ formulas[cell.formula.sharedIndex] = { refCellXc: cell.xc, formula: cell.formula.content };
+ }
+ }
+ }
+ return formulas;
+ }
+ /**
+ * Convert an XLSX formula into something we can evaluate.
+ * - remove _xlfn. flags before function names
+ * - convert the SUBTOTAL(index, formula) function to the function given by its index
+ * - change #REF! into #REF
+ * - convert external references into their value
+ */
+ function convertFormula(formula, data) {
+ formula = formula.replace("_xlfn.", "");
+ formula = formula.replace(/#REF!/g, "#REF");
+ // SUBOTOTAL function, eg. =SUBTOTAL(3, {formula})
+ formula = formula.replace(subtotalRegex, (match, functionId) => {
+ const convertedFunction = SUBTOTAL_FUNCTION_CONVERSION_MAP[functionId];
+ return convertedFunction ? convertedFunction + "(" : match;
+ });
+ // External references, eg. ='[1]Sheet 3'!$B$4
+ formula = formula.replace(externalReferenceRegex, (match, externalRefId, sheetName, cellRef) => {
+ externalRefId = Number(externalRefId) - 1;
+ cellRef = cellRef.replace(/\$/g, "");
+ const sheetIndex = data.externalBooks[externalRefId].sheetNames.findIndex((name) => name === sheetName);
+ if (sheetIndex === -1) {
+ return match;
+ }
+ const externalDataset = data.externalBooks[externalRefId].datasets.find((dataset) => dataset.sheetId === sheetIndex)?.data;
+ if (!externalDataset) {
+ return match;
+ }
+ const datasetValue = externalDataset && externalDataset[cellRef];
+ const convertedValue = Number(datasetValue) ? datasetValue : `"${datasetValue}"`;
+ return convertedValue || match;
+ });
+ return formula;
+ }
+ /**
+ * Transform a shared formula for the given target.
+ *
+ * This will compute the offset between the original cell of the shared formula and the target cell,
+ * then apply this offset to all the ranges in the formula (taking fixed references into account)
+ */
+ function adaptFormula(targetCell, sf) {
+ const refPosition = toCartesian(sf.refCellXc);
+ let newFormula = sf.formula.slice();
+ let match;
+ do {
+ match = cellRegex.exec(newFormula);
+ if (match) {
+ const formulaPosition = toCartesian(match[0].replace("$", ""));
+ const targetPosition = toCartesian(targetCell);
+ const rangePart = {
+ colFixed: match[0].startsWith("$"),
+ rowFixed: match[0].includes("$", 1),
+ };
+ const offset = {
+ col: targetPosition.col - refPosition.col,
+ row: targetPosition.row - refPosition.row,
+ };
+ const offsettedPosition = {
+ col: rangePart.colFixed ? formulaPosition.col : formulaPosition.col + offset.col,
+ row: rangePart.rowFixed ? formulaPosition.row : formulaPosition.row + offset.row,
+ };
+ newFormula =
+ newFormula.slice(0, match.index) +
+ toXC(offsettedPosition.col, offsettedPosition.row, rangePart) +
+ newFormula.slice(match.index + match[0].length);
+ }
+ } while (match);
+ return newFormula;
+ }
+
+ function convertSheets(data, warningManager) {
+ return data.sheets.map((sheet) => {
+ convertFormulasContent(sheet, data);
+ const sheetDims = getSheetDims(sheet);
+ const sheetOptions = sheet.sheetViews[0];
+ return {
+ id: sheet.sheetName,
+ areGridLinesVisible: sheetOptions ? sheetOptions.showGridLines : true,
+ name: sheet.sheetName,
+ colNumber: sheetDims[0],
+ rowNumber: sheetDims[1],
+ cells: convertCells(sheet, data, sheetDims, warningManager),
+ merges: sheet.merges,
+ cols: convertCols(sheet, sheetDims[0]),
+ rows: convertRows(sheet, sheetDims[1]),
+ conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),
+ figures: convertFigures(sheet),
+ isVisible: sheet.isVisible,
+ panes: sheetOptions
+ ? { xSplit: sheetOptions.pane.xSplit, ySplit: sheetOptions.pane.ySplit }
+ : { xSplit: 0, ySplit: 0 },
+ filterTables: [],
+ };
+ });
+ }
+ function convertCols(sheet, numberOfCols) {
+ const cols = {};
+ // Excel begins indexes at 1
+ for (let i = 1; i < numberOfCols + 1; i++) {
+ const col = sheet.cols.find((col) => col.min <= i && i <= col.max);
+ let colSize;
+ if (col && col.width)
+ colSize = col.width;
+ else if (sheet.sheetFormat?.defaultColWidth)
+ colSize = sheet.sheetFormat.defaultColWidth;
+ else
+ colSize = EXCEL_DEFAULT_COL_WIDTH;
+ cols[i - 1] = { size: convertWidthFromExcel(colSize), isHidden: col?.hidden };
+ }
+ return cols;
+ }
+ function convertRows(sheet, numberOfRows) {
+ const rows = {};
+ // Excel begins indexes at 1
+ for (let i = 1; i < numberOfRows + 1; i++) {
+ const row = sheet.rows.find((row) => row.index === i);
+ let rowSize;
+ if (row && row.height)
+ rowSize = row.height;
+ else if (sheet.sheetFormat?.defaultRowHeight)
+ rowSize = sheet.sheetFormat.defaultRowHeight;
+ else
+ rowSize = EXCEL_DEFAULT_ROW_HEIGHT;
+ rows[i - 1] = { size: convertHeightFromExcel(rowSize), isHidden: row?.hidden };
+ }
+ return rows;
+ }
+ /** Remove newlines (\n) in shared strings, We do not support them */
+ function convertSharedStrings(xlsxSharedStrings) {
+ return xlsxSharedStrings.map((str) => str.replace(/\n/g, ""));
+ }
+ function convertCells(sheet, data, sheetDims, warningManager) {
+ const cells = {};
+ const sharedStrings = convertSharedStrings(data.sharedStrings);
+ const hyperlinkMap = sheet.hyperlinks.reduce((map, link) => {
+ map[link.xc] = link;
+ return map;
+ }, {});
+ for (let row of sheet.rows) {
+ for (let cell of row.cells) {
+ cells[cell.xc] = {
+ content: getCellValue(cell, hyperlinkMap, sharedStrings, warningManager),
+ // + 1 : our indexes for normalized values begin at 1 and not 0
+ style: cell.styleIndex ? cell.styleIndex + 1 : undefined,
+ border: cell.styleIndex ? data.styles[cell.styleIndex].borderId + 1 : undefined,
+ format: cell.styleIndex ? data.styles[cell.styleIndex].numFmtId + 1 : undefined,
+ };
+ }
+ }
+ // Apply row style
+ for (let row of sheet.rows.filter((row) => row.styleIndex)) {
+ for (let colIndex = 1; colIndex <= sheetDims[0]; colIndex++) {
+ const xc = toXC(colIndex - 1, row.index - 1); // Excel indexes start at 1
+ let cell = cells[xc];
+ if (!cell) {
+ cell = {};
+ cells[xc] = cell;
+ }
+ cell.style = cell.style ? cell.style : row.styleIndex + 1;
+ cell.border = cell.border ? cell.border : data.styles[row.styleIndex].borderId + 1;
+ cell.format = cell.format ? cell.format : data.styles[row.styleIndex].numFmtId + 1;
+ }
+ }
+ // Apply col style
+ for (let col of sheet.cols.filter((col) => col.styleIndex)) {
+ for (let colIndex = col.min; colIndex <= Math.min(col.max, sheetDims[0]); colIndex++) {
+ for (let rowIndex = 1; rowIndex <= sheetDims[1]; rowIndex++) {
+ const xc = toXC(colIndex - 1, rowIndex - 1); // Excel indexes start at 1
+ let cell = cells[xc];
+ if (!cell) {
+ cell = {};
+ cells[xc] = cell;
+ }
+ cell.style = cell.style ? cell.style : col.styleIndex + 1;
+ cell.border = cell.border ? cell.border : data.styles[col.styleIndex].borderId + 1;
+ cell.format = cell.format ? cell.format : data.styles[col.styleIndex].numFmtId + 1;
+ }
+ }
+ }
+ return cells;
+ }
+ function getCellValue(cell, hyperLinksMap, sharedStrings, warningManager) {
+ let cellValue;
+ switch (cell.type) {
+ case "sharedString":
+ const ssIndex = parseInt(cell.value, 10);
+ cellValue = sharedStrings[ssIndex];
+ break;
+ case "boolean":
+ cellValue = Number(cell.value) ? "TRUE" : "FALSE";
+ break;
+ case "date": // I'm not sure where this is used rather than a number with a format
+ case "error": // I don't think Excel really uses this
+ case "inlineStr":
+ case "number":
+ case "str":
+ cellValue = cell.value;
+ break;
+ }
+ if (cellValue && hyperLinksMap[cell.xc]) {
+ cellValue = convertHyperlink(hyperLinksMap[cell.xc], cellValue, warningManager);
+ }
+ if (cell.formula) {
+ cellValue = cell.formula.content;
+ }
+ return cellValue;
+ }
+ function convertHyperlink(link, cellValue, warningManager) {
+ const label = link.display || cellValue;
+ if (!link.relTarget && !link.location) {
+ warningManager.generateNotSupportedWarning(WarningTypes.BadlyFormattedHyperlink);
+ }
+ const url = link.relTarget
+ ? link.relTarget
+ : buildSheetLink(splitReference(link.location).sheetName);
+ return markdownLink(label, url);
+ }
+ function getSheetDims(sheet) {
+ const dims = [0, 0];
+ for (let row of sheet.rows) {
+ dims[0] = Math.max(dims[0], ...row.cells.map((cell) => toCartesian(cell.xc).col));
+ dims[1] = Math.max(dims[1], row.index);
+ }
+ dims[0] = Math.max(dims[0], EXCEL_IMPORT_DEFAULT_NUMBER_OF_COLS);
+ dims[1] = Math.max(dims[1], EXCEL_IMPORT_DEFAULT_NUMBER_OF_ROWS);
+ return dims;
+ }
+
+ const TABLE_HEADER_STYLE = {
+ fillColor: "#000000",
+ textColor: "#ffffff",
+ bold: true,
+ };
+ const TABLE_HIGHLIGHTED_CELL_STYLE = {
+ bold: true,
+ };
+ const TABLE_BORDER_STYLE = ["thin", "#000000FF"];
+ /**
+ * Convert the imported XLSX tables.
+ *
+ * We will create a FilterTable if the imported table have filters, then apply a style in all the cells of the table
+ * and convert the table-specific formula references into standard references.
+ *
+ * Change the converted data in-place.
+ */
+ function convertTables(convertedData, xlsxData) {
+ for (const xlsxSheet of xlsxData.sheets) {
+ for (const table of xlsxSheet.tables) {
+ const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
+ if (!sheet || !table.autoFilter)
+ continue;
+ if (!sheet.filterTables)
+ sheet.filterTables = [];
+ sheet.filterTables.push({ range: table.ref });
+ }
+ }
+ applyTableStyle(convertedData, xlsxData);
+ convertTableFormulaReferences(convertedData.sheets, xlsxData.sheets);
+ }
+ /**
+ * Apply a style to all the cells that are in a table, and add the created styles in the converted data.
+ *
+ * In XLSXs, the style of the cells of a table are not directly in the sheet, but rather deduced from the style of
+ * the table that is defined in the table's XML file. The style of the table is a string referencing a standard style
+ * defined in the OpenXML specifications. As there are 80+ different styles, we won't implement every one of them but
+ * we will just define a style that will be used for all the imported tables.
+ */
+ function applyTableStyle(convertedData, xlsxData) {
+ const styles = objectToArray(convertedData.styles);
+ const borders = objectToArray(convertedData.borders);
+ for (let xlsxSheet of xlsxData.sheets) {
+ for (let table of xlsxSheet.tables) {
+ const sheet = convertedData.sheets.find((sheet) => sheet.name === xlsxSheet.sheetName);
+ if (!sheet)
+ continue;
+ const tableZone = toZone(table.ref);
+ // Table style
+ for (let i = 0; i < table.headerRowCount; i++) {
+ applyStyleToZone(TABLE_HEADER_STYLE, { ...tableZone, bottom: tableZone.top + i }, sheet.cells, styles);
+ }
+ for (let i = 0; i < table.totalsRowCount; i++) {
+ applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, top: tableZone.bottom - i }, sheet.cells, styles);
+ }
+ if (table.style?.showFirstColumn) {
+ applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, right: tableZone.left }, sheet.cells, styles);
+ }
+ if (table.style?.showLastColumn) {
+ applyStyleToZone(TABLE_HIGHLIGHTED_CELL_STYLE, { ...tableZone, left: tableZone.right }, sheet.cells, styles);
+ }
+ // Table borders
+ // Borders at : table outline + col(/row) if showColumnStripes(/showRowStripes) + border above totalRow
+ for (let col = tableZone.left; col <= tableZone.right; col++) {
+ for (let row = tableZone.top; row <= tableZone.bottom; row++) {
+ const xc = toXC(col, row);
+ const cell = sheet.cells[xc];
+ const border = {
+ left: col === tableZone.left || table.style?.showColumnStripes
+ ? TABLE_BORDER_STYLE
+ : undefined,
+ right: col === tableZone.right ? TABLE_BORDER_STYLE : undefined,
+ top: row === tableZone.top ||
+ table.style?.showRowStripes ||
+ row > tableZone.bottom - table.totalsRowCount
+ ? TABLE_BORDER_STYLE
+ : undefined,
+ bottom: row === tableZone.bottom ? TABLE_BORDER_STYLE : undefined,
+ };
+ const newBorder = cell?.border ? { ...borders[cell.border], ...border } : border;
+ let borderIndex = borders.findIndex((border) => deepEquals(border, newBorder));
+ if (borderIndex === -1) {
+ borderIndex = borders.length;
+ borders.push(newBorder);
+ }
+ if (cell) {
+ cell.border = borderIndex;
+ }
+ else {
+ sheet.cells[xc] = { border: borderIndex };
+ }
+ }
+ }
+ }
+ }
+ convertedData.styles = arrayToObject(styles);
+ convertedData.borders = arrayToObject(borders);
+ }
+ /**
+ * Apply a style to all the cells in the zone. The applied style WILL NOT overwrite values in existing style of the cell.
+ *
+ * If a style that was not in the styles array was applied, push it into the style array.
+ */
+ function applyStyleToZone(appliedStyle, zone, cells, styles) {
+ for (let col = zone.left; col <= zone.right; col++) {
+ for (let row = zone.top; row <= zone.bottom; row++) {
+ const xc = toXC(col, row);
+ const cell = cells[xc];
+ const newStyle = cell?.style ? { ...styles[cell.style], ...appliedStyle } : appliedStyle;
+ let styleIndex = styles.findIndex((style) => deepEquals(style, newStyle));
+ if (styleIndex === -1) {
+ styleIndex = styles.length;
+ styles.push(newStyle);
+ }
+ if (cell) {
+ cell.style = styleIndex;
+ }
+ else {
+ cells[xc] = { style: styleIndex };
+ }
+ }
+ }
+ }
+ /**
+ * In all the sheets, replace the table-only references in the formula cells with standard references.
+ */
+ function convertTableFormulaReferences(convertedSheets, xlsxSheets) {
+ for (let sheet of convertedSheets) {
+ const tables = xlsxSheets.find((s) => s.sheetName === sheet.name).tables;
+ for (let table of tables) {
+ const tabRef = table.name + "[";
+ for (let position of positions(toZone(table.ref))) {
+ const xc = toXC(position.col, position.row);
+ const cell = sheet.cells[xc];
+ if (cell && cell.content && cell.content.startsWith("=")) {
+ let refIndex;
+ while ((refIndex = cell.content.indexOf(tabRef)) !== -1) {
+ let reference = cell.content.slice(refIndex + tabRef.length);
+ // Expression can either be tableName[colName] or tableName[[#This Row], [colName]]
+ let endIndex = reference.indexOf("]");
+ if (reference.startsWith(`[`)) {
+ endIndex = reference.indexOf("]", endIndex + 1);
+ endIndex = reference.indexOf("]", endIndex + 1);
+ }
+ reference = reference.slice(0, endIndex);
+ const convertedRef = convertTableReference(reference, table, xc);
+ cell.content =
+ cell.content.slice(0, refIndex) +
+ convertedRef +
+ cell.content.slice(tabRef.length + refIndex + endIndex + 1);
+ }
+ }
+ }
+ }
+ }
+ }
+ /**
+ * Convert table-specific references in formulas into standard references.
+ *
+ * A reference in a table can have the form (only the part between brackets should be given to this function):
+ * - tableName[colName] : reference to the whole column "colName"
+ * - tableName[[#keyword], [colName]] : reference to some of the element(s) of the column colName
+ *
+ * The available keywords are :
+ * - #All : all the column (including totals)
+ * - #Data : only the column data (no headers/totals)
+ * - #Headers : only the header of the column
+ * - #Totals : only the totals of the column
+ * - #This Row : only the element in the same row as the cell
+ */
+ function convertTableReference(expr, table, cellXc) {
+ const refElements = expr.split(",");
+ const tableZone = toZone(table.ref);
+ const refZone = { ...tableZone };
+ let isReferencedZoneValid = true;
+ // Single column reference
+ if (refElements.length === 1) {
+ const colRelativeIndex = table.cols.findIndex((col) => col.name === refElements[0]);
+ refZone.left = refZone.right = colRelativeIndex + tableZone.left;
+ if (table.headerRowCount) {
+ refZone.top += table.headerRowCount;
+ }
+ if (table.totalsRowCount) {
+ refZone.bottom -= 1;
+ }
+ }
+ // Other references
+ else {
+ switch (refElements[0].slice(1, refElements[0].length - 1)) {
+ case "#All":
+ refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
+ refZone.bottom = tableZone.bottom;
+ break;
+ case "#Data":
+ refZone.top = table.headerRowCount ? tableZone.top + table.headerRowCount : tableZone.top;
+ refZone.bottom = table.totalsRowCount ? tableZone.bottom + 1 : tableZone.bottom;
+ break;
+ case "#This Row":
+ refZone.top = refZone.bottom = toCartesian(cellXc).row;
+ break;
+ case "#Headers":
+ refZone.top = refZone.bottom = tableZone.top;
+ if (!table.headerRowCount) {
+ isReferencedZoneValid = false;
+ }
+ break;
+ case "#Totals":
+ refZone.top = refZone.bottom = tableZone.bottom;
+ if (!table.totalsRowCount) {
+ isReferencedZoneValid = false;
+ }
+ break;
+ }
+ const colRef = refElements[1].slice(1, refElements[1].length - 1);
+ const colRelativeIndex = table.cols.findIndex((col) => col.name === colRef);
+ refZone.left = refZone.right = colRelativeIndex + tableZone.left;
+ }
+ if (!isReferencedZoneValid) {
+ return INCORRECT_RANGE_STRING;
+ }
+ return refZone.top !== refZone.bottom ? zoneToXc(refZone) : toXC(refZone.left, refZone.top);
+ }
+
+ // -------------------------------------
+ // XML HELPERS
+ // -------------------------------------
+ function createXMLFile(doc, path, contentType) {
+ return {
+ content: new XMLSerializer().serializeToString(doc),
+ path,
+ contentType,
+ };
+ }
+ function xmlEscape(str) {
+ return String(str)
+ .replace(/\&/g, "&")
+ .replace(/\/g, ">")
+ .replace(/\"/g, """)
+ .replace(/\'/g, "'");
+ }
+ function formatAttributes(attrs) {
+ return new XMLString(attrs.map(([key, val]) => `${key}="${xmlEscape(val)}"`).join(" "));
+ }
+ function parseXML(xmlString, mimeType = "text/xml") {
+ const document = new DOMParser().parseFromString(xmlString.toString(), mimeType);
+ const parserError = document.querySelector("parsererror");
+ if (parserError) {
+ const errorString = parserError.innerHTML;
+ const lineNumber = parseInt(errorString.split(":")[0], 10);
+ const xmlStringArray = xmlString.toString().trim().split("\n");
+ const xmlPreview = xmlStringArray
+ .slice(Math.max(lineNumber - 3, 0), Math.min(lineNumber + 2, xmlStringArray.length))
+ .join("\n");
+ throw new Error(`XML string could not be parsed: ${errorString}\n${xmlPreview}`);
+ }
+ return document;
+ }
+ function getDefaultXLSXStructure() {
+ return {
+ relsFiles: [],
+ sharedStrings: [],
+ // default Values that will always be part of the style sheet
+ styles: [
+ {
+ fontId: 0,
+ fillId: 0,
+ numFmtId: 0,
+ borderId: 0,
+ alignment: { vertical: "bottom" },
+ },
+ ],
+ fonts: [
+ {
+ size: DEFAULT_FONT_SIZE,
+ family: 2,
+ color: { rgb: "000000" },
+ name: "Calibri",
+ },
+ ],
+ fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
+ borders: [{}],
+ numFmts: [],
+ dxfs: [],
+ };
+ }
+ function createOverride(partName, contentType) {
+ return escapeXml /*xml*/ `
+
+ `;
+ }
+ function joinXmlNodes(xmlNodes) {
+ return new XMLString(xmlNodes.join("\n"));
+ }
+ /**
+ * Escape interpolated values except if the value is already
+ * a properly escaped XML string.
+ *
+ * ```
+ * escapeXml`${"This will be escaped"}`
+ * ```
+ */
+ function escapeXml(strings, ...expressions) {
+ let str = [strings[0]];
+ for (let i = 0; i < expressions.length; i++) {
+ const value = expressions[i] instanceof XMLString ? expressions[i] : xmlEscape(expressions[i]);
+ str.push(value + strings[i + 1]);
+ }
+ return new XMLString(concat(str));
+ }
+ /**
+ * Removes the namespace of all the xml tags in the string.
+ *
+ * Eg. : "ns:test a" => "test a"
+ */
+ function removeNamespaces(query) {
+ return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
+ }
+ /**
+ * Escape the namespace's colons of all the xml tags in the string.
+ *
+ * Eg. : "ns:test a" => "ns\\:test a"
+ */
+ function escapeNamespaces(query) {
+ return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
+ }
+ /**
+ * 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;
+ }
+
+ class AttributeValue {
+ value;
+ constructor(value) {
+ this.value = value;
+ }
+ asString() {
+ return fixXlsxUnicode(String(this.value));
+ }
+ asBool() {
+ if (this.value === "true")
+ return true; // for files exported from Libre Office
+ if (this.value === "false")
+ return false;
+ return Boolean(Number(this.value));
+ }
+ asNum() {
+ return Number(this.value);
+ }
+ }
+ class XlsxBaseExtractor {
+ rootFile;
+ xlsxFileStructure;
+ warningManager;
+ relationships;
+ // 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;
+ 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) => {
+ this.relationships[rel.id] = rel;
+ });
+ }
+ }
+ /**
+ * Extract all the relationships inside a .xml.rels file
+ */
+ extractRelationships(relFile) {
+ return this.mapOnElements({ parent: relFile.xml, query: "Relationship" }, (relationshipElement) => {
+ return {
+ id: this.extractAttr(relationshipElement, "Id", { required: true }).asString(),
+ target: this.extractAttr(relationshipElement, "Target", { required: true }).asString(),
+ type: this.extractAttr(relationshipElement, "Type", { required: true }).asString(),
+ };
+ });
+ }
+ /**
+ * Get the list of all the XLSX files in the XLSX file structure
+ */
+ getListOfFiles() {
+ const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);
+ return files;
+ }
+ /**
+ * Return an array containing the return value of the given function applied to all the XML elements
+ * found using the MapOnElementArgs.
+ *
+ * The arguments contains :
+ * - query : a QuerySelector string to find the elements to apply the function to
+ * - parent : an XML element or XML document in which to find the queried elements
+ * - children : if true, the function is applied on the direct children of the queried element
+ *
+ * This method will also handle the errors thrown in the argument function.
+ */
+ mapOnElements(args, fct) {
+ const ret = [];
+ const oldWorkingDocument = this.currentFile;
+ let elements;
+ if (args.children) {
+ const children = this.querySelector(args.parent, args.query)?.children;
+ elements = children ? children : [];
+ }
+ else {
+ elements = this.querySelectorAll(args.parent, args.query);
+ }
+ if (elements) {
+ for (let element of elements) {
+ try {
+ ret.push(fct(element));
+ }
+ catch (e) {
+ this.catchErrorOnElement(e, element);
+ }
+ }
+ }
+ this.currentFile = oldWorkingDocument;
+ return ret;
+ }
+ /**
+ * Log an error caught when parsing an element in the warningManager.
+ */
+ catchErrorOnElement(error, onElement) {
+ const errorMsg = onElement
+ ? `Error when parsing an element <${onElement.tagName}> of file ${this.currentFile}, skip this element. \n${error.stack}`
+ : `Error when parsing file ${this.currentFile}.`;
+ this.warningManager.addParsingWarning([errorMsg, error.message].join("\n"));
+ }
+ /**
+ * Extract an attribute from an Element.
+ *
+ * If the attribute is required but was not found, will add a warning in the warningManager if it was given a default
+ * value, and throw an error if no default value was given.
+ *
+ * Can only return undefined value for non-required attributes without default value.
+ */
+ extractAttr(e, attName, optionalArgs) {
+ const attribute = e.attributes[attName];
+ if (!attribute)
+ this.handleMissingValue(e, `attribute "${attName}"`, optionalArgs);
+ const value = attribute?.value ? attribute.value : optionalArgs?.default;
+ return (value === undefined ? undefined : new AttributeValue(value));
+ }
+ /**
+ * Extract the text content of an Element.
+ *
+ * If the text content is required but was not found, will add a warning in the warningManager if it was given a default
+ * value, and throw an error if no default value was given.
+ *
+ * Can only return undefined value for non-required text content without default value.
+ */
+ extractTextContent(element, optionalArgs) {
+ if (optionalArgs?.default !== undefined && typeof optionalArgs.default !== "string") {
+ throw new Error("extractTextContent default value should be a string");
+ }
+ const shouldPreserveSpaces = element?.attributes["xml:space"]?.value === "preserve";
+ let textContent = element?.textContent;
+ if (!element || textContent === null) {
+ this.handleMissingValue(element, `text content`, optionalArgs);
+ }
+ if (textContent) {
+ textContent = shouldPreserveSpaces ? textContent : textContent.trim();
+ }
+ return (textContent ? fixXlsxUnicode(textContent) : optionalArgs?.default);
+ }
+ /**
+ * Extract an attribute of a child of the given element.
+ *
+ * The reference of a child can be a string (tag of the child) or an number (index in the list of children of the element)
+ *
+ * If the attribute is required but either the attribute or the referenced child element was not found, it will
+ * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.
+ *
+ * Can only return undefined value for non-required attributes without default value.
+ */
+ extractChildAttr(e, childRef, attName, optionalArgs) {
+ let child;
+ if (typeof childRef === "number") {
+ child = e.children[childRef];
+ }
+ else {
+ child = this.querySelector(e, childRef);
+ }
+ if (!child) {
+ this.handleMissingValue(e, typeof childRef === "number" ? `child at index ${childRef}` : `child <${childRef}>`, optionalArgs);
+ }
+ const value = child
+ ? this.extractAttr(child, attName, optionalArgs)?.asString()
+ : optionalArgs?.default;
+ return (value !== undefined ? new AttributeValue(value) : undefined);
+ }
+ /**
+ * Extract the text content of a child of the given element.
+ *
+ * If the text content is required but either the text content or the referenced child element was not found, it will
+ * will add a warning in the warningManager if it was given a default value, and throw an error if no default value was given.
+ *
+ * Can only return undefined value for non-required text content without default value.
+ */
+ extractChildTextContent(e, childRef, optionalArgs) {
+ if (optionalArgs?.default !== undefined && typeof optionalArgs.default !== "string") {
+ throw new Error("extractTextContent default value should be a string");
+ }
+ let child = this.querySelector(e, childRef);
+ if (!child) {
+ this.handleMissingValue(e, `child <${childRef}>`, optionalArgs);
+ }
+ return (child ? this.extractTextContent(child, optionalArgs) : optionalArgs?.default);
+ }
+ /**
+ * Should be called if a extractAttr/extractTextContent doesn't find the element it needs to extract.
+ *
+ * If the extractable was required, this function will add a warning in the warningManager if there was a default value,
+ * and throw an error if no default value was given.
+ */
+ handleMissingValue(parentElement, missingElementName, optionalArgs) {
+ if (optionalArgs?.required) {
+ if (optionalArgs?.default) {
+ this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
+ }
+ else {
+ throw new Error(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, and no default value was set`);
+ }
+ }
+ }
+ /**
+ * Extract a color, extracting it from the theme if needed.
+ *
+ * Will throw an error if the element references a theme, but no theme was provided or the theme it doesn't contain the color.
+ */
+ extractColor(colorElement, theme, defaultColor) {
+ if (!colorElement) {
+ return defaultColor ? { rgb: defaultColor } : undefined;
+ }
+ const themeIndex = this.extractAttr(colorElement, "theme")?.asString();
+ let rgb;
+ if (themeIndex !== undefined) {
+ if (!theme || !theme.clrScheme) {
+ throw new Error("Color referencing a theme but no theme was provided");
+ }
+ rgb = this.getThemeColor(themeIndex, theme.clrScheme);
+ }
+ else {
+ rgb = this.extractAttr(colorElement, "rgb")?.asString();
+ }
+ const color = {
+ rgb,
+ auto: this.extractAttr(colorElement, "auto")?.asBool(),
+ indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
+ tint: this.extractAttr(colorElement, "tint")?.asNum(),
+ };
+ return color;
+ }
+ /**
+ * Returns the xlsx file targeted by a relationship.
+ */
+ getTargetXmlFile(relationship) {
+ if (!relationship)
+ throw new Error("Undefined target file");
+ let target = relationship.target;
+ target = target.replace("../", "");
+ target = target.replace("./", "");
+ // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
+ const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));
+ if (!f || !f.file)
+ 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);
+ }
+ /**
+ * 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);
+ }
+ /**
+ * Get a color from its id in the Theme's colorScheme.
+ *
+ * Note that Excel don't use the colors from the theme but from its own internal theme, so the displayed
+ * colors will be different in the import than in excel.
+ * .
+ */
+ getThemeColor(colorId, clrScheme) {
+ switch (colorId) {
+ case "0": // 0 : sysColor window text
+ return "FFFFFF";
+ case "1": // 1 : sysColor window background
+ return "000000";
+ // Don't ask me why these 2 are inverted, I cannot find any documentation for it but everyone does it
+ case "2":
+ return clrScheme["3"].value;
+ case "3":
+ return clrScheme["2"].value;
+ default:
+ return clrScheme[colorId].value;
+ }
+ }
+ }
+
+ /**
+ * XLSX Extractor class that can be used for either sharedString XML files or theme XML files.
+ *
+ * Since they both are quite simple, it make sense to make a single class to manage them all, to avoid unnecessary file
+ * cluttering.
+ */
+ class XlsxMiscExtractor extends XlsxBaseExtractor {
+ getTheme() {
+ const clrScheme = this.mapOnElements({ query: "a:clrScheme", parent: this.rootFile.file.xml, children: true }, (element) => {
+ return {
+ name: element.tagName,
+ value: this.extractChildAttr(element, 0, "val", {
+ required: true,
+ default: AUTO_COLOR,
+ }).asString(),
+ lastClr: this.extractChildAttr(element, 0, "lastClr", {
+ default: AUTO_COLOR,
+ }).asString(),
+ };
+ });
+ return { clrScheme };
+ }
+ /**
+ * Get the array of shared strings of the XLSX.
+ *
+ * Worth noting that running a prettier on the xml can mess up some strings, since there is an option in the
+ * xmls to keep the spacing and not trim the string.
+ */
+ getSharedStrings() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "si" }, (ssElement) => {
+ // Shared string can either be a simple text, or a rich text (text with formatting, possibly in multiple parts)
+ if (ssElement.children[0].tagName === "t") {
+ return this.extractTextContent(ssElement) || "";
+ }
+ // We don't support rich text formatting, we'll only extract the text
+ else {
+ return this.mapOnElements({ parent: ssElement, query: "t" }, (textElement) => {
+ return this.extractTextContent(textElement) || "";
+ }).join("");
+ }
+ });
+ }
+ }
+
+ class XlsxCfExtractor extends XlsxBaseExtractor {
+ theme;
+ constructor(sheetFile, xlsxStructure, warningManager, theme) {
+ super(sheetFile, xlsxStructure, warningManager);
+ this.theme = theme;
+ }
+ extractConditionalFormattings() {
+ const cfs = this.mapOnElements({ parent: this.rootFile.file.xml, query: "worksheet > conditionalFormatting" }, (cfElement) => {
+ return {
+ // sqref = ranges on which the cf applies, separated by spaces
+ sqref: this.extractAttr(cfElement, "sqref", { required: true }).asString().split(" "),
+ pivot: this.extractAttr(cfElement, "pivot")?.asBool(),
+ cfRules: this.extractCFRules(cfElement, this.theme),
+ };
+ });
+ // XLSX extension to OpenXml
+ cfs.push(...this.mapOnElements({ parent: this.rootFile.file.xml, query: "extLst x14:conditionalFormatting" }, (cfElement) => {
+ return {
+ sqref: this.extractChildTextContent(cfElement, "xm:sqref", { required: true }).split(" "),
+ pivot: this.extractAttr(cfElement, "xm:pivot")?.asBool(),
+ cfRules: this.extractCFRules(cfElement, this.theme),
+ };
+ }));
+ return cfs;
+ }
+ extractCFRules(cfElement, theme) {
+ return this.mapOnElements({ parent: cfElement, query: "cfRule, x14:cfRule" }, (cfRuleElement) => {
+ const cfType = this.extractAttr(cfRuleElement, "type", {
+ required: true,
+ }).asString();
+ if (cfType === "dataBar") {
+ // Databars are an extension to OpenXml and have a different format (XLSX §2.6.30). Do'nt bother
+ // extracting them as we don't support them.
+ throw new Error("Databars conditional formats are not supported.");
+ }
+ return {
+ type: cfType,
+ priority: this.extractAttr(cfRuleElement, "priority", { required: true }).asNum(),
+ colorScale: this.extractCfColorScale(cfRuleElement, theme),
+ formula: this.extractCfFormula(cfRuleElement),
+ iconSet: this.extractCfIconSet(cfRuleElement),
+ dxfId: this.extractAttr(cfRuleElement, "dxfId")?.asNum(),
+ stopIfTrue: this.extractAttr(cfRuleElement, "stopIfTrue")?.asBool(),
+ aboveAverage: this.extractAttr(cfRuleElement, "aboveAverage")?.asBool(),
+ percent: this.extractAttr(cfRuleElement, "percent")?.asBool(),
+ bottom: this.extractAttr(cfRuleElement, "bottom")?.asBool(),
+ operator: this.extractAttr(cfRuleElement, "operator")?.asString(),
+ text: this.extractAttr(cfRuleElement, "text")?.asString(),
+ timePeriod: this.extractAttr(cfRuleElement, "timePeriod")?.asString(),
+ rank: this.extractAttr(cfRuleElement, "rank")?.asNum(),
+ stdDev: this.extractAttr(cfRuleElement, "stdDev")?.asNum(),
+ equalAverage: this.extractAttr(cfRuleElement, "equalAverage")?.asBool(),
+ };
+ });
+ }
+ extractCfFormula(cfRulesElement) {
+ return this.mapOnElements({ parent: cfRulesElement, query: "formula" }, (cfFormulaElements) => {
+ return this.extractTextContent(cfFormulaElements, { required: true });
+ });
+ }
+ extractCfColorScale(cfRulesElement, theme) {
+ const colorScaleElement = this.querySelector(cfRulesElement, "colorScale");
+ if (!colorScaleElement)
+ return undefined;
+ return {
+ colors: this.mapOnElements({ parent: colorScaleElement, query: "color" }, (colorElement) => {
+ return this.extractColor(colorElement, theme, "ffffff");
+ }),
+ cfvos: this.extractCFVos(colorScaleElement),
+ };
+ }
+ extractCfIconSet(cfRulesElement) {
+ const iconSetElement = this.querySelector(cfRulesElement, "iconSet, x14:iconSet");
+ if (!iconSetElement)
+ return undefined;
+ return {
+ iconSet: this.extractAttr(iconSetElement, "iconSet", {
+ default: "3TrafficLights1",
+ }).asString(),
+ showValue: this.extractAttr(iconSetElement, "showValue", { default: true }).asBool(),
+ percent: this.extractAttr(iconSetElement, "percent", { default: true }).asBool(),
+ reverse: this.extractAttr(iconSetElement, "reverse")?.asBool(),
+ custom: this.extractAttr(iconSetElement, "custom")?.asBool(),
+ cfvos: this.extractCFVos(iconSetElement),
+ cfIcons: this.extractCfIcons(iconSetElement),
+ };
+ }
+ extractCfIcons(iconSetElement) {
+ const icons = this.mapOnElements({ parent: iconSetElement, query: "cfIcon, x14:cfIcon" }, (cfIconElement) => {
+ return {
+ iconSet: this.extractAttr(cfIconElement, "iconSet", {
+ required: true,
+ }).asString(),
+ iconId: this.extractAttr(cfIconElement, "iconId", { required: true }).asNum(),
+ };
+ });
+ return icons.length === 0 ? undefined : icons;
+ }
+ extractCFVos(parent) {
+ return this.mapOnElements({ parent, query: "cfvo, x14:cfvo" }, (cfVoElement) => {
+ return {
+ type: this.extractAttr(cfVoElement, "type", {
+ required: true,
+ }).asString(),
+ gte: this.extractAttr(cfVoElement, "gte", { default: true })?.asBool(),
+ value: cfVoElement.attributes["val"]
+ ? this.extractAttr(cfVoElement, "val")?.asString()
+ : this.extractChildTextContent(cfVoElement, "f, xm:f"),
+ };
+ });
+ }
+ }
+
+ class XlsxChartExtractor extends XlsxBaseExtractor {
+ extractChart() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "c:chartSpace" }, (rootChartElement) => {
+ const chartType = this.getChartType(rootChartElement);
+ if (!CHART_TYPE_CONVERSION_MAP[chartType]) {
+ throw new Error(`Unsupported chart type ${chartType}`);
+ }
+ // Title can be separated into multiple xml elements (for styling and such), we only import the text
+ const chartTitle = this.mapOnElements({ parent: rootChartElement, query: "c:title a:t" }, (textElement) => {
+ return textElement.textContent || "";
+ }).join("");
+ const barChartGrouping = this.extractChildAttr(rootChartElement, "c:grouping", "val", {
+ default: "clustered",
+ }).asString();
+ return {
+ title: chartTitle,
+ type: CHART_TYPE_CONVERSION_MAP[chartType],
+ dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
+ labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
+ backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
+ default: "ffffff",
+ }).asString(),
+ verticalAxisPosition: this.extractChildAttr(rootChartElement, "c:valAx > c:axPos", "val", {
+ default: "l",
+ }).asString() === "r"
+ ? "right"
+ : "left",
+ legendPosition: DRAWING_LEGEND_POSITION_CONVERSION_MAP[this.extractChildAttr(rootChartElement, "c:legendPos", "val", {
+ default: "b",
+ }).asString()],
+ stacked: barChartGrouping === "stacked",
+ fontColor: "000000",
+ };
+ })[0];
+ }
+ extractChartDatasets(chartElement) {
+ return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
+ return {
+ label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
+ range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
+ };
+ });
+ }
+ /**
+ * The chart type in the XML isn't explicitly defined, but there is an XML element that define the
+ * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.
+ */
+ getChartType(chartElement) {
+ const plotAreaElement = this.querySelector(chartElement, "c:plotArea");
+ if (!plotAreaElement) {
+ throw new Error("Missing plot area in the chart definition.");
+ }
+ for (let child of plotAreaElement.children) {
+ const tag = removeNamespaces(child.tagName);
+ if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
+ return tag;
+ }
+ }
+ throw new Error("Unknown chart type");
+ }
+ }
+
+ class XlsxFigureExtractor extends XlsxBaseExtractor {
+ extractFigures() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
+ const anchorType = removeNamespaces(figureElement.tagName);
+ if (anchorType !== "twoCellAnchor") {
+ throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
+ }
+ const chartElement = this.querySelector(figureElement, "c:chart");
+ if (!chartElement) {
+ throw new Error("Only chart figures are currently supported.");
+ }
+ return {
+ anchors: [
+ this.extractFigureAnchor("xdr:from", figureElement),
+ this.extractFigureAnchor("xdr:to", figureElement),
+ ],
+ data: this.extractChart(chartElement),
+ };
+ });
+ }
+ extractFigureAnchor(anchorTag, figureElement) {
+ const anchor = this.querySelector(figureElement, anchorTag);
+ if (!anchor) {
+ throw new Error(`Missing anchor element ${anchorTag}`);
+ }
+ return {
+ col: Number(this.extractChildTextContent(anchor, "xdr:col", { required: true })),
+ colOffset: Number(this.extractChildTextContent(anchor, "xdr:colOff", { required: true })),
+ row: Number(this.extractChildTextContent(anchor, "xdr:row", { required: true })),
+ rowOffset: Number(this.extractChildTextContent(anchor, "xdr:rowOff", { required: true })),
+ };
+ }
+ extractChart(chartElement) {
+ const chartId = this.extractAttr(chartElement, "r:id", { required: true }).asString();
+ const chartFile = this.getTargetXmlFile(this.relationships[chartId]);
+ const chartDefinition = new XlsxChartExtractor(chartFile, this.xlsxFileStructure, this.warningManager).extractChart();
+ if (!chartDefinition) {
+ throw new Error("Unable to extract chart definition");
+ }
+ return chartDefinition;
+ }
+ }
+
+ /**
+ * We don't really support pivot tables, we'll just extract them as Tables.
+ */
+ class XlsxPivotExtractor extends XlsxBaseExtractor {
+ getPivotTable() {
+ return this.mapOnElements(
+ // Use :root instead of "pivotTableDefinition" because others pivotTableDefinition elements are present inside the root
+ // pivotTableDefinition elements.
+ { query: ":root", parent: this.rootFile.file.xml }, (pivotElement) => {
+ return {
+ displayName: this.extractAttr(pivotElement, "name", { required: true }).asString(),
+ id: this.extractAttr(pivotElement, "name", { required: true }).asString(),
+ ref: this.extractChildAttr(pivotElement, "location", "ref", {
+ required: true,
+ }).asString(),
+ headerRowCount: this.extractChildAttr(pivotElement, "location", "firstDataRow", {
+ default: 0,
+ }).asNum(),
+ totalsRowCount: 1,
+ cols: [],
+ style: {
+ showFirstColumn: true,
+ showRowStripes: true,
+ },
+ };
+ })[0];
+ }
+ }
+
+ class XlsxTableExtractor extends XlsxBaseExtractor {
+ getTable() {
+ return this.mapOnElements({ query: "table", parent: this.rootFile.file.xml }, (tableElement) => {
+ return {
+ displayName: this.extractAttr(tableElement, "displayName", {
+ required: true,
+ }).asString(),
+ name: this.extractAttr(tableElement, "name")?.asString(),
+ id: this.extractAttr(tableElement, "id", { required: true }).asString(),
+ ref: this.extractAttr(tableElement, "ref", { required: true }).asString(),
+ headerRowCount: this.extractAttr(tableElement, "headerRowCount", {
+ default: 1,
+ }).asNum(),
+ totalsRowCount: this.extractAttr(tableElement, "totalsRowCount", {
+ default: 0,
+ }).asNum(),
+ cols: this.extractTableCols(tableElement),
+ style: this.extractTableStyleInfo(tableElement),
+ autoFilter: this.extractTableAutoFilter(tableElement),
+ };
+ })[0];
+ }
+ extractTableCols(tableElement) {
+ return this.mapOnElements({ query: "tableColumn", parent: tableElement }, (tableColElement) => {
+ return {
+ id: this.extractAttr(tableColElement, "id", { required: true }).asString(),
+ name: this.extractAttr(tableColElement, "name", { required: true }).asString(),
+ colFormula: this.extractChildTextContent(tableColElement, "calculatedColumnFormula"),
+ };
+ });
+ }
+ extractTableStyleInfo(tableElement) {
+ return this.mapOnElements({ query: "tableStyleInfo", parent: tableElement }, (tableStyleElement) => {
+ return {
+ name: this.extractAttr(tableStyleElement, "name")?.asString(),
+ showFirstColumn: this.extractAttr(tableStyleElement, "showFirstColumn")?.asBool(),
+ showLastColumn: this.extractAttr(tableStyleElement, "showLastColumn")?.asBool(),
+ showRowStripes: this.extractAttr(tableStyleElement, "showRowStripes")?.asBool(),
+ showColumnStripes: this.extractAttr(tableStyleElement, "showColumnStripes")?.asBool(),
+ };
+ })[0];
+ }
+ extractTableAutoFilter(tableElement) {
+ return this.mapOnElements({ query: "autoFilter", parent: tableElement }, (autoFilterElement) => {
+ return {
+ columns: this.extractFilterColumns(autoFilterElement),
+ zone: this.extractAttr(autoFilterElement, "ref", { required: true }).asString(),
+ };
+ })[0];
+ }
+ extractFilterColumns(autoFilterElement) {
+ return this.mapOnElements({ query: "tableColumn", parent: autoFilterElement }, (filterColumnElement) => {
+ return {
+ colId: this.extractAttr(autoFilterElement, "colId", { required: true }).asNum(),
+ hiddenButton: this.extractAttr(autoFilterElement, "hiddenButton", {
+ default: false,
+ }).asBool(),
+ filters: this.extractSimpleFilter(filterColumnElement),
+ };
+ });
+ }
+ extractSimpleFilter(filterColumnElement) {
+ return this.mapOnElements({ query: "filter", parent: filterColumnElement }, (filterColumnElement) => {
+ return {
+ val: this.extractAttr(filterColumnElement, "val", { required: true }).asString(),
+ };
+ });
+ }
+ }
+
+ class XlsxSheetExtractor extends XlsxBaseExtractor {
+ theme;
+ constructor(sheetFile, xlsxStructure, warningManager, theme) {
+ super(sheetFile, xlsxStructure, warningManager);
+ this.theme = theme;
+ }
+ getSheet() {
+ return this.mapOnElements({ query: "worksheet", parent: this.rootFile.file.xml }, (sheetElement) => {
+ const sheetWorkbookInfo = this.getSheetWorkbookInfo();
+ return {
+ sheetName: this.extractSheetName(),
+ sheetViews: this.extractSheetViews(sheetElement),
+ sheetFormat: this.extractSheetFormat(sheetElement),
+ cols: this.extractCols(sheetElement),
+ rows: this.extractRows(sheetElement),
+ sharedFormulas: this.extractSharedFormulas(sheetElement),
+ merges: this.extractMerges(sheetElement),
+ cfs: this.extractConditionalFormats(),
+ figures: this.extractFigures(sheetElement),
+ hyperlinks: this.extractHyperLinks(sheetElement),
+ tables: [...this.extractTables(sheetElement), ...this.extractPivotTables()],
+ isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
+ };
+ })[0];
+ }
+ extractSheetViews(worksheet) {
+ return this.mapOnElements({ parent: worksheet, query: "sheetView" }, (sheetViewElement) => {
+ const paneElement = this.querySelector(sheetViewElement, "pane");
+ return {
+ tabSelected: this.extractAttr(sheetViewElement, "tabSelected", {
+ default: false,
+ }).asBool(),
+ showFormulas: this.extractAttr(sheetViewElement, "showFormulas", {
+ default: false,
+ }).asBool(),
+ showGridLines: this.extractAttr(sheetViewElement, "showGridLines", {
+ default: true,
+ }).asBool(),
+ showRowColHeaders: this.extractAttr(sheetViewElement, "showRowColHeaders", {
+ default: true,
+ }).asBool(),
+ pane: {
+ xSplit: paneElement
+ ? this.extractAttr(paneElement, "xSplit", { default: 0 }).asNum()
+ : 0,
+ ySplit: paneElement
+ ? this.extractAttr(paneElement, "ySplit", { default: 0 }).asNum()
+ : 0,
+ },
+ };
+ });
+ }
+ extractSheetName() {
+ const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);
+ const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);
+ const relId = workbookRels.find((rel) => rel.target === relativePath).id;
+ // Having a namespace in the attributes names mess with the querySelector, and the behavior is not the same
+ // for every XML parser. So we'll search manually instead of using a querySelector to search for an attribute value.
+ for (let sheetElement of this.querySelectorAll(this.xlsxFileStructure.workbook.file.xml, "sheet")) {
+ if (sheetElement.attributes["r:id"].value === relId) {
+ return sheetElement.attributes["name"].value;
+ }
+ }
+ throw new Error("Missing sheet name");
+ }
+ getSheetWorkbookInfo() {
+ const relativePath = getRelativePath(this.xlsxFileStructure.workbook.file.fileName, this.rootFile.file.fileName);
+ const workbookRels = this.extractRelationships(this.xlsxFileStructure.workbook.rels);
+ const relId = workbookRels.find((rel) => rel.target === relativePath).id;
+ const workbookSheets = this.mapOnElements({ parent: this.xlsxFileStructure.workbook.file.xml, query: "sheet" }, (sheetElement) => {
+ return {
+ relationshipId: this.extractAttr(sheetElement, "r:id", { required: true }).asString(),
+ sheetId: this.extractAttr(sheetElement, "sheetId", { required: true }).asString(),
+ sheetName: this.extractAttr(sheetElement, "name", { required: true }).asString(),
+ state: this.extractAttr(sheetElement, "state", {
+ default: "visible",
+ }).asString(),
+ };
+ });
+ const info = workbookSheets.find((info) => info.relationshipId === relId);
+ if (!info) {
+ throw new Error("Cannot find corresponding workbook sheet");
+ }
+ return info;
+ }
+ extractConditionalFormats() {
+ return new XlsxCfExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractConditionalFormattings();
+ }
+ extractFigures(worksheet) {
+ const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
+ const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
+ const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);
+ const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
+ return figures;
+ })[0];
+ return figures || [];
+ }
+ extractTables(worksheet) {
+ return this.mapOnElements({ query: "tablePart", parent: worksheet }, (tablePartElement) => {
+ const tableId = this.extractAttr(tablePartElement, "r:id", { required: true })?.asString();
+ const tableFile = this.getTargetXmlFile(this.relationships[tableId]);
+ const tableExtractor = new XlsxTableExtractor(tableFile, this.xlsxFileStructure, this.warningManager);
+ return tableExtractor.getTable();
+ });
+ }
+ extractPivotTables() {
+ try {
+ return Object.values(this.relationships)
+ .filter((relationship) => relationship.type.endsWith("pivotTable"))
+ .map((pivotRelationship) => {
+ const pivotFile = this.getTargetXmlFile(pivotRelationship);
+ const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
+ return pivot;
+ });
+ }
+ catch (e) {
+ this.catchErrorOnElement(e);
+ return [];
+ }
+ }
+ extractMerges(worksheet) {
+ return this.mapOnElements({ parent: worksheet, query: "mergeCell" }, (mergeElement) => {
+ return this.extractAttr(mergeElement, "ref", { required: true }).asString();
+ });
+ }
+ extractSheetFormat(worksheet) {
+ const formatElement = this.querySelector(worksheet, "sheetFormatPr");
+ if (!formatElement)
+ return undefined;
+ return {
+ defaultColWidth: this.extractAttr(formatElement, "defaultColWidth", {
+ default: EXCEL_DEFAULT_COL_WIDTH.toString(),
+ }).asNum(),
+ defaultRowHeight: this.extractAttr(formatElement, "defaultRowHeight", {
+ default: EXCEL_DEFAULT_ROW_HEIGHT.toString(),
+ }).asNum(),
+ };
+ }
+ extractCols(worksheet) {
+ return this.mapOnElements({ parent: worksheet, query: "cols col" }, (colElement) => {
+ return {
+ width: this.extractAttr(colElement, "width")?.asNum(),
+ customWidth: this.extractAttr(colElement, "customWidth")?.asBool(),
+ bestFit: this.extractAttr(colElement, "bestFit")?.asBool(),
+ hidden: this.extractAttr(colElement, "hidden")?.asBool(),
+ min: this.extractAttr(colElement, "min", { required: true })?.asNum(),
+ max: this.extractAttr(colElement, "max", { required: true })?.asNum(),
+ styleIndex: this.extractAttr(colElement, "style")?.asNum(),
+ };
+ });
+ }
+ extractRows(worksheet) {
+ return this.mapOnElements({ parent: worksheet, query: "sheetData row" }, (rowElement) => {
+ return {
+ index: this.extractAttr(rowElement, "r", { required: true })?.asNum(),
+ cells: this.extractCells(rowElement),
+ height: this.extractAttr(rowElement, "ht")?.asNum(),
+ customHeight: this.extractAttr(rowElement, "customHeight")?.asBool(),
+ hidden: this.extractAttr(rowElement, "hidden")?.asBool(),
+ styleIndex: this.extractAttr(rowElement, "s")?.asNum(),
+ };
+ });
+ }
+ extractCells(row) {
+ return this.mapOnElements({ parent: row, query: "c" }, (cellElement) => {
+ return {
+ xc: this.extractAttr(cellElement, "r", { required: true })?.asString(),
+ styleIndex: this.extractAttr(cellElement, "s")?.asNum(),
+ type: CELL_TYPE_CONVERSION_MAP[this.extractAttr(cellElement, "t", { default: "n" })?.asString()],
+ value: this.extractChildTextContent(cellElement, "v"),
+ formula: this.extractCellFormula(cellElement),
+ };
+ });
+ }
+ extractCellFormula(cellElement) {
+ const formulaElement = this.querySelector(cellElement, "f");
+ if (!formulaElement)
+ return undefined;
+ return {
+ content: this.extractTextContent(formulaElement),
+ sharedIndex: this.extractAttr(formulaElement, "si")?.asNum(),
+ ref: this.extractAttr(formulaElement, "ref")?.asString(),
+ };
+ }
+ extractHyperLinks(worksheet) {
+ return this.mapOnElements({ parent: worksheet, query: "hyperlink" }, (linkElement) => {
+ const relId = this.extractAttr(linkElement, "r:id")?.asString();
+ return {
+ xc: this.extractAttr(linkElement, "ref", { required: true })?.asString(),
+ location: this.extractAttr(linkElement, "location")?.asString(),
+ display: this.extractAttr(linkElement, "display")?.asString(),
+ relTarget: relId ? this.relationships[relId].target : undefined,
+ };
+ });
+ }
+ extractSharedFormulas(worksheet) {
+ const sfElements = this.querySelectorAll(worksheet, `f[si][ref]`);
+ const sfMap = {};
+ for (let sfElement of sfElements) {
+ const index = this.extractAttr(sfElement, "si", { required: true }).asNum();
+ const formula = this.extractTextContent(sfElement, { required: true });
+ sfMap[index] = formula;
+ }
+ const sfs = [];
+ for (let i = 0; i < Object.keys(sfMap).length; i++) {
+ if (!sfMap[i]) {
+ this.warningManager.addParsingWarning(`Missing shared formula ${i}, replacing it by empty formula`);
+ sfs.push("");
+ }
+ else {
+ sfs.push(sfMap[i]);
+ }
+ }
+ return sfs;
+ }
+ }
+
+ class XlsxStyleExtractor extends XlsxBaseExtractor {
+ theme;
+ constructor(xlsxStructure, warningManager, theme) {
+ super(xlsxStructure.styles, xlsxStructure, warningManager);
+ this.theme = theme;
+ }
+ getNumFormats() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "numFmt" }, (numFmtElement) => {
+ return this.extractNumFormats(numFmtElement);
+ });
+ }
+ extractNumFormats(numFmtElement) {
+ return {
+ id: this.extractAttr(numFmtElement, "numFmtId", {
+ required: true,
+ }).asNum(),
+ format: this.extractAttr(numFmtElement, "formatCode", {
+ required: true,
+ default: "",
+ }).asString(),
+ };
+ }
+ getFonts() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "font" }, (font) => {
+ return this.extractFont(font);
+ });
+ }
+ extractFont(fontElement) {
+ const name = this.extractChildAttr(fontElement, "name", "val", {
+ default: "Arial",
+ }).asString();
+ const size = this.extractChildAttr(fontElement, "sz", "val", {
+ default: DEFAULT_FONT_SIZE.toString(),
+ }).asNum();
+ const color = this.extractColor(this.querySelector(fontElement, `color`), this.theme);
+ // The behavior for these is kinda strange. The text is italic if there is either a "italic" tag with no "val"
+ // attribute, or a tag with a "val" attribute = "1" (boolean).
+ const italicElement = this.querySelector(fontElement, `i`) || undefined;
+ const italic = italicElement && italicElement.attributes["val"]?.value !== "0";
+ const boldElement = this.querySelector(fontElement, `b`) || undefined;
+ const bold = boldElement && boldElement.attributes["val"]?.value !== "0";
+ const strikeElement = this.querySelector(fontElement, `strike`) || undefined;
+ const strike = strikeElement && strikeElement.attributes["val"]?.value !== "0";
+ const underlineElement = this.querySelector(fontElement, `u`) || undefined;
+ const underline = underlineElement && underlineElement.attributes["val"]?.value !== "none";
+ return { name, size, color, italic, bold, underline, strike };
+ }
+ getFills() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "fill" }, (fillElement) => {
+ return this.extractFill(fillElement);
+ });
+ }
+ extractFill(fillElement) {
+ // Fills are either patterns of gradients
+ const fillChild = fillElement.children[0];
+ if (fillChild.tagName === "patternFill") {
+ return {
+ patternType: fillChild.attributes["patternType"]?.value,
+ bgColor: this.extractColor(this.querySelector(fillChild, "bgColor"), this.theme),
+ fgColor: this.extractColor(this.querySelector(fillChild, "fgColor"), this.theme),
+ };
+ }
+ else {
+ // We don't support gradients. Take the second gradient color as fill color
+ return {
+ patternType: "solid",
+ fgColor: this.extractColor(this.querySelectorAll(fillChild, "color")[1], this.theme),
+ };
+ }
+ }
+ getBorders() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "border" }, (borderElement) => {
+ return this.extractBorder(borderElement);
+ });
+ }
+ extractBorder(borderElement) {
+ const border = {
+ left: this.extractSingleBorder(borderElement, "left", this.theme),
+ right: this.extractSingleBorder(borderElement, "right", this.theme),
+ top: this.extractSingleBorder(borderElement, "top", this.theme),
+ bottom: this.extractSingleBorder(borderElement, "bottom", this.theme),
+ diagonal: this.extractSingleBorder(borderElement, "diagonal", this.theme),
+ };
+ if (border.diagonal) {
+ border.diagonalUp = this.extractAttr(borderElement, "diagonalUp")?.asBool();
+ border.diagonalDown = this.extractAttr(borderElement, "diagonalDown")?.asBool();
+ }
+ return border;
+ }
+ extractSingleBorder(borderElement, direction, theme) {
+ const directionElement = this.querySelector(borderElement, direction);
+ if (!directionElement || !directionElement.attributes["style"])
+ return undefined;
+ return {
+ style: this.extractAttr(directionElement, "style", {
+ required: true,
+ default: "thin",
+ }).asString(),
+ color: this.extractColor(directionElement.children[0], theme, "000000"),
+ };
+ }
+ extractAlignment(alignmentElement) {
+ return {
+ horizontal: this.extractAttr(alignmentElement, "horizontal", {
+ default: "general",
+ }).asString(),
+ vertical: this.extractAttr(alignmentElement, "vertical", {
+ default: "bottom",
+ }).asString(),
+ textRotation: this.extractAttr(alignmentElement, "textRotation")?.asNum(),
+ wrapText: this.extractAttr(alignmentElement, "wrapText")?.asBool(),
+ indent: this.extractAttr(alignmentElement, "indent")?.asNum(),
+ relativeIndent: this.extractAttr(alignmentElement, "relativeIndent")?.asNum(),
+ justifyLastLine: this.extractAttr(alignmentElement, "justifyLastLine")?.asBool(),
+ shrinkToFit: this.extractAttr(alignmentElement, "shrinkToFit")?.asBool(),
+ readingOrder: this.extractAttr(alignmentElement, "readingOrder")?.asNum(),
+ };
+ }
+ getDxfs() {
+ return this.mapOnElements({ query: "dxf", parent: this.rootFile.file.xml }, (dxfElement) => {
+ const fontElement = this.querySelector(dxfElement, "font");
+ const fillElement = this.querySelector(dxfElement, "fill");
+ const borderElement = this.querySelector(dxfElement, "border");
+ const numFmtElement = this.querySelector(dxfElement, "numFmt");
+ const alignmentElement = this.querySelector(dxfElement, "alignment");
+ return {
+ font: fontElement ? this.extractFont(fontElement) : undefined,
+ fill: fillElement ? this.extractFill(fillElement) : undefined,
+ numFmt: numFmtElement ? this.extractNumFormats(numFmtElement) : undefined,
+ alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,
+ border: borderElement ? this.extractBorder(borderElement) : undefined,
+ };
+ });
+ }
+ getStyles() {
+ return this.mapOnElements({ query: "cellXfs xf", parent: this.rootFile.file.xml }, (styleElement) => {
+ const alignmentElement = this.querySelector(styleElement, "alignment");
+ return {
+ fontId: this.extractAttr(styleElement, "fontId", {
+ required: true,
+ default: 0,
+ }).asNum(),
+ fillId: this.extractAttr(styleElement, "fillId", {
+ required: true,
+ default: 0,
+ }).asNum(),
+ borderId: this.extractAttr(styleElement, "borderId", {
+ required: true,
+ default: 0,
+ }).asNum(),
+ numFmtId: this.extractAttr(styleElement, "numFmtId", {
+ required: true,
+ default: 0,
+ }).asNum(),
+ alignment: alignmentElement ? this.extractAlignment(alignmentElement) : undefined,
+ };
+ });
+ }
+ }
+
+ class XlsxExternalBookExtractor extends XlsxBaseExtractor {
+ getExternalBook() {
+ return this.mapOnElements({ parent: this.rootFile.file.xml, query: "externalBook" }, (bookElement) => {
+ return {
+ rId: this.extractAttr(bookElement, "r:id", { required: true }).asString(),
+ sheetNames: this.mapOnElements({ parent: bookElement, query: "sheetName" }, (sheetNameElement) => {
+ return this.extractAttr(sheetNameElement, "val", { required: true }).asString();
+ }),
+ datasets: this.extractExternalSheetData(bookElement),
+ };
+ })[0];
+ }
+ extractExternalSheetData(externalBookElement) {
+ return this.mapOnElements({ parent: externalBookElement, query: "sheetData" }, (sheetDataElement) => {
+ const cellsData = this.mapOnElements({ parent: sheetDataElement, query: "cell" }, (cellElement) => {
+ return {
+ xc: this.extractAttr(cellElement, "r", { required: true }).asString(),
+ value: this.extractChildTextContent(cellElement, "v", { required: true }),
+ };
+ });
+ const dataMap = {};
+ for (let cell of cellsData) {
+ dataMap[cell.xc] = cell.value;
+ }
+ return {
+ sheetId: this.extractAttr(sheetDataElement, "sheetId", { required: true }).asNum(),
+ data: dataMap,
+ };
+ });
+ }
+ }
+
+ /**
+ * Return all the xmls converted to XLSXImportFile corresponding to the given content type.
+ */
+ function getXLSXFilesOfType(contentType, xmls) {
+ const paths = getPathsOfContent(contentType, xmls);
+ return getXlsxFile(paths, xmls);
+ }
+ /**
+ * From an array of file path, return the equivalents XLSXFiles. An XLSX File is composed of an XML,
+ * and optionally of a relationships XML.
+ */
+ function getXlsxFile(files, xmls) {
+ const ret = [];
+ for (let file of files) {
+ const rels = getRelationFile(file, xmls);
+ ret.push({
+ file: { fileName: file, xml: xmls[file] },
+ rels: rels ? { fileName: rels, xml: xmls[rels] } : undefined,
+ });
+ }
+ return ret;
+ }
+ /**
+ * Return all the path of the files in a XLSX directory that have content of the given type.
+ */
+ function getPathsOfContent(contentType, xmls) {
+ const xml = xmls[CONTENT_TYPES_FILE];
+ const sheetItems = xml.querySelectorAll(`Override[ContentType="${contentType}"]`);
+ const paths = [];
+ for (let item of sheetItems) {
+ const file = item?.attributes["PartName"].value;
+ paths.push(file.substring(1)); // Remove the heading "/"
+ }
+ return paths;
+ }
+ /**
+ * Get the corresponding relationship file for a given xml file in a XLSX directory.
+ */
+ function getRelationFile(file, xmls) {
+ if (file === CONTENT_TYPES_FILE) {
+ return "_rels/.rels";
+ }
+ let relsFile = "";
+ const pathParts = file.split("/");
+ for (let i = 0; i < pathParts.length - 1; i++) {
+ relsFile += pathParts[i] + "/";
+ }
+ relsFile += "_rels/";
+ relsFile += pathParts[pathParts.length - 1] + ".rels";
+ if (!xmls[relsFile]) {
+ relsFile = undefined;
+ }
+ return relsFile;
+ }
+
+ const EXCEL_IMPORT_VERSION = 12;
+ class XlsxReader {
+ warningManager;
+ xmls;
+ constructor(files) {
+ this.warningManager = new XLSXImportWarningManager();
+ this.xmls = {};
+ 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]));
+ }
+ }
+ }
+ convertXlsx() {
+ const xlsxData = this.getXlsxData();
+ const convertedData = this.convertImportedData(xlsxData);
+ return convertedData;
+ }
+ // ---------------------------------------------------------------------------
+ // Parsing XMLs
+ // ---------------------------------------------------------------------------
+ getXlsxData() {
+ const xlsxFileStructure = this.buildXlsxFileStructure();
+ const theme = xlsxFileStructure.theme
+ ? new XlsxMiscExtractor(xlsxFileStructure.theme, xlsxFileStructure, this.warningManager).getTheme()
+ : undefined;
+ const sharedStrings = xlsxFileStructure.sharedStrings
+ ? new XlsxMiscExtractor(xlsxFileStructure.sharedStrings, xlsxFileStructure, this.warningManager).getSharedStrings()
+ : [];
+ // Sort sheets by file name : the sheets will always be named sheet1.xml, sheet2.xml, ... in order
+ const sheets = xlsxFileStructure.sheets
+ .sort((a, b) => a.file.fileName.localeCompare(b.file.fileName, undefined, { numeric: true }))
+ .map((sheetFile) => {
+ return new XlsxSheetExtractor(sheetFile, xlsxFileStructure, this.warningManager, theme).getSheet();
+ });
+ const externalBooks = xlsxFileStructure.externalLinks.map((externalLinkFile) => {
+ return new XlsxExternalBookExtractor(externalLinkFile, xlsxFileStructure, this.warningManager).getExternalBook();
+ });
+ const styleExtractor = new XlsxStyleExtractor(xlsxFileStructure, this.warningManager, theme);
+ return {
+ fonts: styleExtractor.getFonts(),
+ fills: styleExtractor.getFills(),
+ borders: styleExtractor.getBorders(),
+ dxfs: styleExtractor.getDxfs(),
+ numFmts: styleExtractor.getNumFormats(),
+ styles: styleExtractor.getStyles(),
+ sheets: sheets,
+ sharedStrings,
+ externalBooks,
+ };
+ }
+ buildXlsxFileStructure() {
+ const xlsxFileStructure = {
+ sheets: getXLSXFilesOfType(CONTENT_TYPES.sheet, this.xmls),
+ workbook: getXLSXFilesOfType(CONTENT_TYPES.workbook, this.xmls)[0],
+ styles: getXLSXFilesOfType(CONTENT_TYPES.styles, this.xmls)[0],
+ sharedStrings: getXLSXFilesOfType(CONTENT_TYPES.sharedStrings, this.xmls)[0],
+ theme: getXLSXFilesOfType(CONTENT_TYPES.themes, this.xmls)[0],
+ charts: getXLSXFilesOfType(CONTENT_TYPES.chart, this.xmls),
+ figures: getXLSXFilesOfType(CONTENT_TYPES.drawing, this.xmls),
+ tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),
+ pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),
+ externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),
+ };
+ if (!xlsxFileStructure.workbook.rels) {
+ throw Error(_lt("Cannot find workbook relations file"));
+ }
+ return xlsxFileStructure;
+ }
+ // ---------------------------------------------------------------------------
+ // Conversion
+ // ---------------------------------------------------------------------------
+ convertImportedData(data) {
+ const convertedData = {
+ version: EXCEL_IMPORT_VERSION,
+ sheets: convertSheets(data, this.warningManager),
+ styles: convertStyles(data, this.warningManager),
+ formats: convertFormats(data, this.warningManager),
+ borders: convertBorders(data, this.warningManager),
+ entities: {},
+ revisionId: DEFAULT_REVISION_ID,
+ };
+ convertTables(convertedData, data);
+ // Remove falsy attributes in styles. Not mandatory, but make objects more readable when debugging
+ Object.keys(data.styles).map((key) => {
+ data.styles[key] = removeFalsyAttributes(data.styles[key]);
+ });
+ return convertedData;
+ }
+ }
+
+ /**
+ * parses a formula (as a string) into the same formula,
+ * but with the references to other cells extracted
+ *
+ * =sum(a3:b1) + c3 --> =sum(|0|) + |1|
+ *
+ * @param formula
+ */
+ function normalizeV9(formula) {
+ const tokens = rangeTokenize(formula);
+ let dependencies = [];
+ let noRefFormula = "".concat(...tokens.map((token) => {
+ if (token.type === "REFERENCE" && cellReference.test(token.value)) {
+ const value = token.value.trim();
+ if (!dependencies.includes(value)) {
+ dependencies.push(value);
+ }
+ return `${FORMULA_REF_IDENTIFIER}${dependencies.indexOf(value)}${FORMULA_REF_IDENTIFIER}`;
+ }
+ else {
+ return token.value;
+ }
+ }));
+ return { text: noRefFormula, dependencies };
+ }
+
+ /**
+ * This is the current state version number. It should be incremented each time
+ * a breaking change is made in the way the state is handled, and an upgrade
+ * function should be defined
+ */
+ const CURRENT_VERSION = 12;
+ const INITIAL_SHEET_ID = "Sheet1";
+ /**
+ * This function tries to load anything that could look like a valid
+ * workbookData object. It applies any migrations, if needed, and return a
+ * current, complete workbookData object.
+ *
+ * It also ensures that there is at least one sheet.
+ */
+ function load(data, verboseImport) {
+ if (!data) {
+ return createEmptyWorkbookData();
+ }
+ if (data["[Content_Types].xml"]) {
+ const reader = new XlsxReader(data);
+ data = reader.convertXlsx();
+ if (verboseImport) {
+ for (let parsingError of reader.warningManager.warnings.sort()) {
+ console.warn(parsingError);
+ }
+ }
+ }
+ // apply migrations, if needed
+ if ("version" in data) {
+ if (data.version < CURRENT_VERSION) {
+ data = migrate(data);
+ }
+ }
+ data = repairData(data);
+ return data;
+ }
+ function migrate(data) {
+ const index = MIGRATIONS.findIndex((m) => m.from === data.version);
+ for (let i = index; i < MIGRATIONS.length; i++) {
+ data = MIGRATIONS[i].applyMigration(data);
+ }
+ return data;
+ }
+ const MIGRATIONS = [
+ {
+ description: "add the `activeSheet` field on data",
+ from: 1,
+ to: 2,
+ applyMigration(data) {
+ if (data.sheets && data.sheets[0]) {
+ data.activeSheet = data.sheets[0].name;
+ }
+ return data;
+ },
+ },
+ {
+ description: "add an id field in each sheet",
+ from: 2,
+ to: 3,
+ applyMigration(data) {
+ if (data.sheets && data.sheets.length) {
+ for (let sheet of data.sheets) {
+ sheet.id = sheet.id || sheet.name;
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "activeSheet is now an id, not the name of a sheet",
+ from: 3,
+ to: 4,
+ applyMigration(data) {
+ if (data.sheets && data.activeSheet) {
+ const activeSheet = data.sheets.find((s) => s.name === data.activeSheet);
+ data.activeSheet = activeSheet.id;
+ }
+ return data;
+ },
+ },
+ {
+ description: "add figures object in each sheets",
+ from: 4,
+ to: 5,
+ applyMigration(data) {
+ for (let sheet of data.sheets || []) {
+ sheet.figures = sheet.figures || [];
+ }
+ return data;
+ },
+ },
+ {
+ description: "normalize the content of the cell if it is a formula to avoid parsing all the formula that vary only by the cells they use",
+ from: 5,
+ to: 6,
+ applyMigration(data) {
+ for (let sheet of data.sheets || []) {
+ for (let xc in sheet.cells || []) {
+ const cell = sheet.cells[xc];
+ if (cell.content && cell.content.startsWith("=")) {
+ cell.formula = normalizeV9(cell.content);
+ }
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "transform chart data structure",
+ from: 6,
+ to: 7,
+ applyMigration(data) {
+ for (let sheet of data.sheets || []) {
+ for (let f in sheet.figures || []) {
+ const { dataSets, ...newData } = sheet.figures[f].data;
+ const newDataSets = [];
+ for (let ds of dataSets) {
+ if (ds.labelCell) {
+ const dataRange = toZone(ds.dataRange);
+ const newRange = ds.labelCell + ":" + toXC(dataRange.right, dataRange.bottom);
+ newDataSets.push(newRange);
+ }
+ else {
+ newDataSets.push(ds.dataRange);
+ }
+ }
+ newData.dataSetsHaveTitle = Boolean(dataSets[0].labelCell);
+ newData.dataSets = newDataSets;
+ sheet.figures[f].data = newData;
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "remove single quotes in sheet names",
+ from: 7,
+ to: 8,
+ applyMigration(data) {
+ const namesTaken = [];
+ const globalForbiddenInExcel = new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g");
+ for (let sheet of data.sheets || []) {
+ if (!sheet.name) {
+ continue;
+ }
+ const oldName = sheet.name;
+ const escapedName = oldName.replace(globalForbiddenInExcel, "_");
+ let i = 1;
+ let newName = escapedName;
+ while (namesTaken.includes(newName)) {
+ newName = `${escapedName}${i}`;
+ i++;
+ }
+ sheet.name = newName;
+ namesTaken.push(newName);
+ const replaceName = (str) => {
+ if (str === undefined) {
+ return str;
+ }
+ // replaceAll is only available in next Typescript version
+ let newString = str.replace(oldName, newName);
+ let currentString = str;
+ while (currentString !== newString) {
+ currentString = newString;
+ newString = currentString.replace(oldName, newName);
+ }
+ return currentString;
+ };
+ //cells
+ for (let xc in sheet.cells) {
+ const cell = sheet.cells[xc];
+ if (cell.formula) {
+ cell.formula.dependencies = cell.formula.dependencies.map(replaceName);
+ }
+ }
+ //charts
+ for (let figure of sheet.figures || []) {
+ if (figure.type === "chart") {
+ const dataSets = figure.data.dataSets.map(replaceName);
+ const labelRange = replaceName(figure.data.labelRange);
+ figure.data = { ...figure.data, dataSets, labelRange };
+ }
+ }
+ //ConditionalFormats
+ for (let cf of sheet.conditionalFormats || []) {
+ cf.ranges = cf.ranges.map(replaceName);
+ for (const thresholdName of [
+ "minimum",
+ "maximum",
+ "midpoint",
+ "upperInflectionPoint",
+ "lowerInflectionPoint",
+ ]) {
+ if (cf.rule[thresholdName]?.type === "formula") {
+ cf.rule[thresholdName].value = replaceName(cf.rule[thresholdName].value);
+ }
+ }
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "transform chart data structure with design attributes",
+ from: 8,
+ to: 9,
+ applyMigration(data) {
+ for (const sheet of data.sheets || []) {
+ for (const chart of sheet.figures || []) {
+ chart.data.background = BACKGROUND_CHART_COLOR;
+ chart.data.verticalAxisPosition = "left";
+ chart.data.legendPosition = "top";
+ chart.data.stacked = false;
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "de-normalize formula to reduce exported json size (~30%)",
+ from: 9,
+ to: 10,
+ applyMigration(data) {
+ for (let sheet of data.sheets || []) {
+ for (let xc in sheet.cells || []) {
+ const cell = sheet.cells[xc];
+ if (cell.formula) {
+ let { text, dependencies } = cell.formula;
+ for (let [index, d] of Object.entries(dependencies)) {
+ const stringPosition = `\\${FORMULA_REF_IDENTIFIER}${index}\\${FORMULA_REF_IDENTIFIER}`;
+ text = text.replace(new RegExp(stringPosition, "g"), d);
+ }
+ cell.content = text;
+ delete cell.formula;
+ }
+ }
+ }
+ return data;
+ },
+ },
+ {
+ description: "normalize the formats of the cells",
+ from: 10,
+ to: 11,
+ applyMigration(data) {
+ const formats = {};
+ for (let sheet of data.sheets || []) {
+ for (let xc in sheet.cells || []) {
+ const cell = sheet.cells[xc];
+ if (cell.format) {
+ cell.format = getItemId(cell.format, formats);
+ }
+ }
+ }
+ data.formats = formats;
+ return data;
+ },
+ },
+ {
+ description: "Add isVisible to sheets",
+ from: 11,
+ to: 12,
+ applyMigration(data) {
+ for (let sheet of data.sheets || []) {
+ sheet.isVisible = true;
+ }
+ return data;
+ },
+ },
+ ];
+ /**
+ * This function is used to repair faulty data independently of the migration.
+ */
+ function repairData(data) {
+ data = forceUnicityOfFigure(data);
+ data = setDefaults(data);
+ return data;
+ }
+ /**
+ * Force the unicity of figure ids accross sheets
+ */
+ function forceUnicityOfFigure(data) {
+ if (data.uniqueFigureIds) {
+ return data;
+ }
+ const figureIds = new Set();
+ const uuidGenerator = new UuidGenerator();
+ for (const sheet of data.sheets || []) {
+ for (const figure of sheet.figures || []) {
+ if (figureIds.has(figure.id)) {
+ figure.id += uuidGenerator.uuidv4();
+ }
+ figureIds.add(figure.id);
+ }
+ }
+ data.uniqueFigureIds = true;
+ return data;
+ }
+ /**
+ * sanity check: try to fix missing fields/corrupted state by providing
+ * sensible default values
+ */
+ function setDefaults(data) {
+ data = Object.assign(createEmptyWorkbookData(), data, { version: CURRENT_VERSION });
+ data.sheets = data.sheets
+ ? data.sheets.map((s, i) => Object.assign(createEmptySheet(`Sheet${i + 1}`, `Sheet${i + 1}`), s))
+ : [];
+ if (data.sheets.length === 0) {
+ data.sheets.push(createEmptySheet(INITIAL_SHEET_ID, "Sheet1"));
+ }
+ return data;
+ }
+ /**
+ * The goal of this function is to repair corrupted/wrong initial messages caused by
+ * a bug.
+ * The bug should obviously be fixed, but it's too late for existing spreadsheet.
+ */
+ function repairInitialMessages(data, initialMessages) {
+ initialMessages = fixTranslatedSheetIds(data, initialMessages);
+ initialMessages = dropCommands(initialMessages, "SORT_CELLS");
+ initialMessages = dropCommands(initialMessages, "SET_DECIMAL");
+ initialMessages = fixChartDefinitions(data, initialMessages);
+ return initialMessages;
+ }
+ /**
+ * When the workbook data is originally empty, a new one is generated on-the-fly.
+ * A bug caused the sheet id to be non-deterministic. The sheet id was propagated in
+ * commands.
+ * This function repairs initial commands with a wrong sheetId.
+ */
+ function fixTranslatedSheetIds(data, initialMessages) {
+ // the fix is only needed when the workbook is generated on-the-fly
+ if (Object.keys(data).length !== 0) {
+ return initialMessages;
+ }
+ const sheetIds = [];
+ const messages = [];
+ const fixSheetId = (cmd) => {
+ if (cmd.type === "CREATE_SHEET") {
+ sheetIds.push(cmd.sheetId);
+ }
+ else if ("sheetId" in cmd && !sheetIds.includes(cmd.sheetId)) {
+ return { ...cmd, sheetId: INITIAL_SHEET_ID };
+ }
+ return cmd;
+ };
+ for (const message of initialMessages) {
+ if (message.type === "REMOTE_REVISION") {
+ messages.push({
+ ...message,
+ commands: message.commands.map(fixSheetId),
+ });
+ }
+ else {
+ messages.push(message);
+ }
+ }
+ return messages;
+ }
+ function dropCommands(initialMessages, commandType) {
+ const messages = [];
+ for (const message of initialMessages) {
+ if (message.type === "REMOTE_REVISION") {
+ messages.push({
+ ...message,
+ commands: message.commands.filter((command) => command.type !== commandType),
+ });
+ }
+ else {
+ messages.push(message);
+ }
+ }
+ return messages;
+ }
+ function fixChartDefinitions(data, initialMessages) {
+ const messages = [];
+ const map = {};
+ for (const sheet of data.sheets || []) {
+ sheet.figures?.forEach((figure) => {
+ if (figure.tag === "chart") {
+ // chart definition
+ map[figure.id] = figure.data;
+ }
+ });
+ }
+ for (const message of initialMessages) {
+ if (message.type === "REMOTE_REVISION") {
+ const commands = [];
+ for (const cmd of message.commands) {
+ let command = cmd;
+ switch (cmd.type) {
+ case "CREATE_CHART":
+ map[cmd.id] = cmd.definition;
+ break;
+ case "UPDATE_CHART":
+ if (!map[cmd.id]) {
+ /** the chart does not exist on the map, it might have been created after a duplicate sheet.
+ * We don't have access to the definition, so we skip the command.
+ */
+ console.log(`Fix chart definition: chart with id ${cmd.id} not found.`);
+ continue;
+ }
+ const definition = map[cmd.id];
+ const newDefinition = { ...definition, ...cmd.definition };
+ command = { ...cmd, definition: newDefinition };
+ map[cmd.id] = newDefinition;
+ break;
+ }
+ commands.push(command);
+ }
+ messages.push({
+ ...message,
+ commands,
+ });
+ }
+ else {
+ messages.push(message);
+ }
+ }
+ return messages;
+ }
+ // -----------------------------------------------------------------------------
+ // Helpers
+ // -----------------------------------------------------------------------------
+ function createEmptySheet(sheetId, name) {
+ return {
+ id: sheetId,
+ name,
+ colNumber: 26,
+ rowNumber: 100,
+ cells: {},
+ cols: {},
+ rows: {},
+ merges: [],
+ conditionalFormats: [],
+ figures: [],
+ filterTables: [],
+ isVisible: true,
+ };
+ }
+ function createEmptyWorkbookData(sheetName = "Sheet1") {
+ const data = {
+ version: CURRENT_VERSION,
+ sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],
+ entities: {},
+ styles: {},
+ formats: {},
+ borders: {},
+ revisionId: DEFAULT_REVISION_ID,
+ uniqueFigureIds: true,
+ };
+ return data;
+ }
+ function createEmptyExcelSheet(sheetId, name) {
+ return {
+ ...createEmptySheet(sheetId, name),
+ charts: [],
+ images: [],
+ };
+ }
+ function createEmptyExcelWorkbookData() {
+ return {
+ ...createEmptyWorkbookData(),
+ sheets: [createEmptyExcelSheet(INITIAL_SHEET_ID, "Sheet1")],
+ };
+ }
+
+ /**
+ * BasePlugin
+ *
+ * Since the spreadsheet internal state is quite complex, it is split into
+ * multiple parts, each managing a specific concern.
+ *
+ * This file introduce the BasePlugin, which is the common class that defines
+ * how each of these model sub parts should interact with each other.
+ * There are two kind of plugins: core plugins handling persistent data
+ * and UI plugins handling transient data.
+ */
+ class BasePlugin {
+ static getters = [];
+ history;
+ dispatch;
+ constructor(stateObserver, dispatch) {
+ this.history = Object.assign(Object.create(stateObserver), {
+ update: stateObserver.addChange.bind(stateObserver, this),
+ selectCell: () => { },
+ });
+ this.dispatch = dispatch;
+ }
+ /**
+ * Export for excel should be available for all plugins, even for the UI.
+ * In some case, we need to export evaluated value, which is available from
+ * UI plugin only.
+ */
+ exportForExcel(data) { }
+ // ---------------------------------------------------------------------------
+ // Command handling
+ // ---------------------------------------------------------------------------
+ /**
+ * Before a command is accepted, the model will ask each plugin if the command
+ * is allowed. If all of then return true, then we can proceed. Otherwise,
+ * the command is cancelled.
+ *
+ * There should not be any side effects in this method.
+ */
+ allowDispatch(command) {
+ return 0 /* CommandResult.Success */;
+ }
+ /**
+ * This method is useful when a plugin need to perform some action before a
+ * command is handled in another plugin. This should only be used if it is not
+ * possible to do the work in the handle method.
+ */
+ beforeHandle(command) { }
+ /**
+ * This is the standard place to handle any command. Most of the plugin
+ * command handling work should take place here.
+ */
+ handle(command) { }
+ /**
+ * Sometimes, it is useful to perform some work after a command (and all its
+ * subcommands) has been completely handled. For example, when we paste
+ * multiple cells, we only want to reevaluate the cell values once at the end.
+ */
+ finalize() { }
+ /**
+ * Combine multiple validation functions into a single function
+ * returning the list of result of every validation.
+ */
+ batchValidations(...validations) {
+ return (toValidate) => validations.map((validation) => validation.call(this, toValidate)).flat();
+ }
+ /**
+ * Combine multiple validation functions. Every validation is executed one after
+ * the other. As soon as one validation fails, it stops and the cancelled reason
+ * is returned.
+ */
+ chainValidations(...validations) {
+ return (toValidate) => {
+ for (const validation of validations) {
+ let results = validation.call(this, toValidate);
+ if (!Array.isArray(results)) {
+ results = [results];
+ }
+ const cancelledReasons = results.filter((result) => result !== 0 /* CommandResult.Success */);
+ if (cancelledReasons.length) {
+ return cancelledReasons;
+ }
+ }
+ return 0 /* CommandResult.Success */;
+ };
+ }
+ checkValidations(command, ...validations) {
+ return this.batchValidations(...validations)(command);
+ }
+ }
+
+ /**
+ * Core plugins handle spreadsheet data.
+ * They are responsible to import, export and maintain the spreadsheet
+ * persisted state.
+ * They should not be concerned about UI parts or transient state.
+ */
+ class CorePlugin extends BasePlugin {
+ getters;
+ range;
+ uuidGenerator;
+ constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
+ super(stateObserver, dispatch);
+ this.range = range;
+ range.addRangeProvider(this.adaptRanges.bind(this));
+ this.getters = getters;
+ this.uuidGenerator = uuidGenerator;
+ }
+ // ---------------------------------------------------------------------------
+ // Import/Export
+ // ---------------------------------------------------------------------------
+ import(data) { }
+ export(data) { }
+ /**
+ * This method can be implemented in any plugin, to loop over the plugin's data structure and adapt the plugin's ranges.
+ * To adapt them, the implementation of the function must have a perfect knowledge of the data structure, thus
+ * implementing the loops over it makes sense in the plugin itself.
+ * When calling the method applyChange, the range will be adapted if necessary, then a copy will be returned along with
+ * the type of change that occurred.
+ *
+ * @param applyChange a function that, when called, will adapt the range according to the change on the grid
+ * @param sheetId an optional sheetId to adapt either range of that sheet specifically, or ranges pointing to that sheet
+ */
+ adaptRanges(applyChange, sheetId) { }
+ /**
+ * Implement this method to clean unused external resources, such as images
+ * stored on a server which have been deleted.
+ */
+ garbageCollectExternalResources() { }
+ }
+
+ /**
+ * Formatting plugin.
+ *
+ * This plugin manages all things related to a cell look:
+ * - borders
+ */
+ class BordersPlugin extends CorePlugin {
+ static getters = ["getCellBorder"];
+ borders = {};
+ // ---------------------------------------------------------------------------
+ // Command Handling
+ // ---------------------------------------------------------------------------
+ handle(cmd) {
+ switch (cmd.type) {
+ case "ADD_MERGE":
+ for (const zone of cmd.target) {
+ this.addBordersToMerge(cmd.sheetId, zone);
+ }
+ break;
+ case "DUPLICATE_SHEET":
+ const borders = this.borders[cmd.sheetId];
+ if (borders) {
+ // borders is a sparse 2D array.
+ // map and slice preserve empty values and do not set `undefined` instead
+ const bordersCopy = borders
+ .slice()
+ .map((col) => col?.slice().map((border) => ({ ...border })));
+ this.history.update("borders", cmd.sheetIdTo, bordersCopy);
+ }
+ break;
+ case "DELETE_SHEET":
+ const allBorders = { ...this.borders };
+ delete allBorders[cmd.sheetId];
+ this.history.update("borders", allBorders);
+ break;
+ case "SET_BORDER":
+ this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
+ break;
+ case "SET_FORMATTING":
+ if (cmd.border) {
+ const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
+ this.setBorders(cmd.sheetId, target, cmd.border);
+ }
+ break;
+ case "CLEAR_FORMATTING":
+ this.clearBorders(cmd.sheetId, cmd.target);
+ break;
+ case "REMOVE_COLUMNS_ROWS":
+ for (let el of cmd.elements) {
+ if (cmd.dimension === "COL") {
+ this.shiftBordersHorizontally(cmd.sheetId, el + 1, -1);
+ }
+ else {
+ this.shiftBordersVertically(cmd.sheetId, el + 1, -1);
+ }
+ }
+ break;
+ case "ADD_COLUMNS_ROWS":
+ if (cmd.dimension === "COL") {
+ this.handleAddColumns(cmd);
+ }
+ else {
+ this.handleAddRows(cmd);
+ }
+ break;
+ }
+ }
+ /**
+ * Move borders according to the inserted columns.
+ * Ensure borders continuity.
+ */
+ handleAddColumns(cmd) {
+ // The new columns have already been inserted in the sheet at this point.
+ let colLeftOfInsertion;
+ let colRightOfInsertion;
+ if (cmd.position === "before") {
+ this.shiftBordersHorizontally(cmd.sheetId, cmd.base, cmd.quantity, {
+ moveFirstLeftBorder: true,
+ });
+ colLeftOfInsertion = cmd.base - 1;
+ colRightOfInsertion = cmd.base + cmd.quantity;
+ }
+ else {
+ this.shiftBordersHorizontally(cmd.sheetId, cmd.base + 1, cmd.quantity, {
+ moveFirstLeftBorder: false,
+ });
+ colLeftOfInsertion = cmd.base;
+ colRightOfInsertion = cmd.base + cmd.quantity + 1;
+ }
+ this.ensureColumnBorderContinuity(cmd.sheetId, colLeftOfInsertion, colRightOfInsertion);
+ }
+ /**
+ * Move borders according to the inserted rows.
+ * Ensure borders continuity.
+ */
+ handleAddRows(cmd) {
+ // The new rows have already been inserted at this point.
+ let rowAboveInsertion;
+ let rowBelowInsertion;
+ if (cmd.position === "before") {
+ this.shiftBordersVertically(cmd.sheetId, cmd.base, cmd.quantity, {
+ moveFirstTopBorder: true,
+ });
+ rowAboveInsertion = cmd.base - 1;
+ rowBelowInsertion = cmd.base + cmd.quantity;
+ }
+ else {
+ this.shiftBordersVertically(cmd.sheetId, cmd.base + 1, cmd.quantity, {
+ moveFirstTopBorder: false,
+ });
+ rowAboveInsertion = cmd.base;
+ rowBelowInsertion = cmd.base + cmd.quantity + 1;
+ }
+ this.ensureRowBorderContinuity(cmd.sheetId, rowAboveInsertion, rowBelowInsertion);
+ }
+ // ---------------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------------
+ getCellBorder({ sheetId, col, row }) {
+ const border = {
+ top: this.borders[sheetId]?.[col]?.[row]?.horizontal,
+ bottom: this.borders[sheetId]?.[col]?.[row + 1]?.horizontal,
+ left: this.borders[sheetId]?.[col]?.[row]?.vertical,
+ right: this.borders[sheetId]?.[col + 1]?.[row]?.vertical,
+ };
+ if (!border.bottom && !border.left && !border.right && !border.top) {
+ return null;
+ }
+ return border;
+ }
+ // ---------------------------------------------------------------------------
+ // Private
+ // ---------------------------------------------------------------------------
+ /**
+ * Ensure border continuity between two columns.
+ * If the two columns have the same borders (at each row respectively),
+ * the same borders are applied to each cell in between.
+ */
+ ensureColumnBorderContinuity(sheetId, leftColumn, rightColumn) {
+ const targetCols = range(leftColumn + 1, rightColumn);
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
+ const leftBorder = this.getCellBorder({ sheetId, col: leftColumn, row });
+ const rightBorder = this.getCellBorder({ sheetId, col: rightColumn, row });
+ if (leftBorder && rightBorder) {
+ const commonSides = this.getCommonSides(leftBorder, rightBorder);
+ for (let col of targetCols) {
+ this.addBorder(sheetId, col, row, commonSides);
+ }
+ }
+ }
+ }
+ /**
+ * Ensure border continuity between two rows.
+ * If the two rows have the same borders (at each column respectively),
+ * the same borders are applied to each cell in between.
+ */
+ ensureRowBorderContinuity(sheetId, topRow, bottomRow) {
+ const targetRows = range(topRow + 1, bottomRow);
+ for (let col = 0; col < this.getters.getNumberCols(sheetId); col++) {
+ const aboveBorder = this.getCellBorder({ sheetId, col, row: topRow });
+ const belowBorder = this.getCellBorder({ sheetId, col, row: bottomRow });
+ if (aboveBorder && belowBorder) {
+ const commonSides = this.getCommonSides(aboveBorder, belowBorder);
+ for (let row of targetRows) {
+ this.addBorder(sheetId, col, row, commonSides);
+ }
+ }
+ }
+ }
+ /**
+ * From two borders, return a new border with sides defined in both borders.
+ * i.e. the intersection of two borders.
+ */
+ getCommonSides(border1, border2) {
+ const commonBorder = {};
+ for (let side of ["top", "bottom", "left", "right"]) {
+ if (border1[side] && border1[side] === border2[side]) {
+ commonBorder[side] = border1[side];
+ }
+ }
+ return commonBorder;
+ }
+ /**
+ * Get all the columns which contains at least a border
+ */
+ getColumnsWithBorders(sheetId) {
+ const sheetBorders = this.borders[sheetId];
+ if (!sheetBorders)
+ return [];
+ return Object.keys(sheetBorders).map((index) => parseInt(index, 10));
+ }
+ /**
+ * Get the range of all the rows in the sheet
+ */
+ getRowsRange(sheetId) {
+ const sheetBorders = this.borders[sheetId];
+ if (!sheetBorders)
+ return [];
+ return range(0, this.getters.getNumberRows(sheetId) + 1);
+ }
+ /**
+ * Move borders of a sheet horizontally.
+ * @param sheetId
+ * @param start starting column (included)
+ * @param delta how much borders will be moved (negative if moved to the left)
+ */
+ shiftBordersHorizontally(sheetId, start, delta, { moveFirstLeftBorder } = {}) {
+ const borders = this.borders[sheetId];
+ if (!borders)
+ return;
+ if (delta < 0) {
+ this.moveBordersOfColumn(sheetId, start, delta, "vertical", {
+ destructive: false,
+ });
+ }
+ this.getColumnsWithBorders(sheetId)
+ .filter((col) => col >= start)
+ .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
+ .forEach((col) => {
+ if ((col === start && moveFirstLeftBorder) || col !== start) {
+ this.moveBordersOfColumn(sheetId, col, delta, "vertical");
+ }
+ this.moveBordersOfColumn(sheetId, col, delta, "horizontal");
+ });
+ }
+ /**
+ * Move borders of a sheet vertically.
+ * @param sheetId
+ * @param start starting row (included)
+ * @param delta how much borders will be moved (negative if moved to the above)
+ */
+ shiftBordersVertically(sheetId, start, delta, { moveFirstTopBorder } = {}) {
+ const borders = this.borders[sheetId];
+ if (!borders)
+ return;
+ if (delta < 0) {
+ this.moveBordersOfRow(sheetId, start, delta, "horizontal", {
+ destructive: false,
+ });
+ }
+ this.getRowsRange(sheetId)
+ .filter((row) => row >= start)
+ .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
+ .forEach((row) => {
+ if ((row === start && moveFirstTopBorder) || row !== start) {
+ this.moveBordersOfRow(sheetId, row, delta, "horizontal");
+ }
+ this.moveBordersOfRow(sheetId, row, delta, "vertical");
+ });
+ }
+ /**
+ * Moves the borders (left if `vertical` or top if `horizontal` depending on
+ * `borderDirection`) of all cells in an entire row `delta` rows to the right
+ * (`delta` > 0) or to the left (`delta` < 0).
+ * Note that as the left of a cell is the right of the cell-1, if the left is
+ * moved the right is also moved. However, if `horizontal`, the bottom border
+ * is not moved.
+ * It does it by replacing the target border by the moved border. If the
+ * argument `destructive` is given false, the target border is preserved if
+ * the moved border is empty
+ */
+ moveBordersOfRow(sheetId, row, delta, borderDirection, { destructive } = { destructive: true }) {
+ const borders = this.borders[sheetId];
+ if (!borders)
+ return;
+ this.getColumnsWithBorders(sheetId).forEach((col) => {
+ const targetBorder = borders[col]?.[row + delta]?.[borderDirection];
+ const movedBorder = borders[col]?.[row]?.[borderDirection];
+ this.history.update("borders", sheetId, col, row + delta, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
+ this.history.update("borders", sheetId, col, row, borderDirection, undefined);
+ });
+ }
+ /**
+ * Moves the borders (left if `vertical` or top if `horizontal` depending on
+ * `borderDirection`) of all cells in an entire column `delta` columns below
+ * (`delta` > 0) or above (`delta` < 0).
+ * Note that as the top of a cell is the bottom of the cell-1, if the top is
+ * moved the bottom is also moved. However, if `vertical`, the right border
+ * is not moved.
+ * It does it by replacing the target border by the moved border. If the
+ * argument `destructive` is given false, the target border is preserved if
+ * the moved border is empty
+ */
+ moveBordersOfColumn(sheetId, col, delta, borderDirection, { destructive } = { destructive: true }) {
+ const borders = this.borders[sheetId];
+ if (!borders)
+ return;
+ this.getRowsRange(sheetId).forEach((row) => {
+ const targetBorder = borders[col + delta]?.[row]?.[borderDirection];
+ const movedBorder = borders[col]?.[row]?.[borderDirection];
+ this.history.update("borders", sheetId, col + delta, row, borderDirection, destructive ? movedBorder : movedBorder || targetBorder);
+ this.history.update("borders", sheetId, col, row, borderDirection, undefined);
+ });
+ }
+ /**
+ * Set the borders of a cell.
+ * It overrides the current border if override == true.
+ */
+ setBorder(sheetId, col, row, border, override = true) {
+ if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
+ this.history.update("borders", sheetId, col, row, "vertical", border?.left);
+ }
+ if (override || !this.borders?.[sheetId]?.[col]?.[row]?.horizontal) {
+ this.history.update("borders", sheetId, col, row, "horizontal", border?.top);
+ }
+ if (override || !this.borders?.[sheetId]?.[col + 1]?.[row]?.vertical) {
+ this.history.update("borders", sheetId, col + 1, row, "vertical", border?.right);
+ }
+ if (override || !this.borders?.[sheetId]?.[col]?.[row + 1]?.horizontal) {
+ this.history.update("borders", sheetId, col, row + 1, "horizontal", border?.bottom);
+ }
+ }
+ /**
+ * Remove the borders of a zone
+ */
+ clearBorders(sheetId, zones) {
+ for (let zone of zones) {
+ for (let row = zone.top; row <= zone.bottom; row++) {
+ this.history.update("borders", sheetId, zone.right + 1, row, "vertical", undefined);
+ for (let col = zone.left; col <= zone.right; col++) {
+ this.history.update("borders", sheetId, col, row, undefined);
+ }
+ }
+ for (let col = zone.left; col <= zone.right; col++) {
+ this.history.update("borders", sheetId, col, zone.bottom + 1, "horizontal", undefined);
+ }
+ }
+ }
+ /**
+ * Add a border to the existing one to a cell
+ */
+ addBorder(sheetId, col, row, border) {
+ this.setBorder(sheetId, col, row, {
+ ...this.getCellBorder({ sheetId, col, row }),
+ ...border,
+ });
+ }
+ /**
+ * Set the borders of a zone by computing the borders to add from the given
+ * command
+ */
+ setBorders(sheetId, zones, command) {
+ if (command === "clear") {
+ return this.clearBorders(sheetId, zones);
+ }
+ for (let zone of zones) {
+ if (command === "h" || command === "hv" || command === "all") {
+ for (let row = zone.top + 1; row <= zone.bottom; row++) {
+ for (let col = zone.left; col <= zone.right; col++) {
+ this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });
+ }
+ }
+ }
+ if (command === "v" || command === "hv" || command === "all") {
+ for (let row = zone.top; row <= zone.bottom; row++) {
+ for (let col = zone.left + 1; col <= zone.right; col++) {
+ this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });
+ }
+ }
+ }
+ if (command === "left" || command === "all" || command === "external") {
+ for (let row = zone.top; row <= zone.bottom; row++) {
+ this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });
+ }
+ }
+ if (command === "right" || command === "all" || command === "external") {
+ for (let row = zone.top; row <= zone.bottom; row++) {
+ this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });
+ }
+ }
+ if (command === "top" || command === "all" || command === "external") {
+ for (let col = zone.left; col <= zone.right; col++) {
+ this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });
+ }
+ }
+ if (command === "bottom" || command === "all" || command === "external") {
+ for (let col = zone.left; col <= zone.right; col++) {
+ this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });
+ }
+ }
+ }
+ }
+ /**
+ * Compute the borders to add to the given zone merged.
+ */
+ addBordersToMerge(sheetId, zone) {
+ const { left, right, top, bottom } = zone;
+ const bordersTopLeft = this.getCellBorder({ sheetId, col: left, row: top });
+ const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });
+ this.clearBorders(sheetId, [zone]);
+ if (bordersTopLeft?.top) {
+ this.setBorders(sheetId, [{ ...zone, bottom: top }], "top");
+ }
+ if (bordersTopLeft?.left) {
+ this.setBorders(sheetId, [{ ...zone, right: left }], "left");
+ }
+ if (bordersBottomRight?.bottom || bordersTopLeft?.bottom) {
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom");
+ }
+ if (bordersBottomRight?.right || bordersTopLeft?.right) {
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right");
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // Import/Export
+ // ---------------------------------------------------------------------------
+ import(data) {
+ // Borders
+ if (Object.keys(data.borders || {}).length) {
+ for (let sheet of data.sheets) {
+ for (const xc in sheet.cells) {
+ const cell = sheet.cells[xc];
+ if (cell?.border) {
+ const border = data.borders[cell.border];
+ const { col, row } = toCartesian(xc);
+ this.setBorder(sheet.id, col, row, border, false);
+ }
+ }
+ }
+ }
+ // Merges
+ for (let sheetData of data.sheets) {
+ if (sheetData.merges) {
+ for (let merge of sheetData.merges) {
+ this.addBordersToMerge(sheetData.id, toZone(merge));
+ }
+ }
+ }
+ }
+ export(data) {
+ // Borders
+ let borderId = 0;
+ const borders = {};
+ /**
+ * Get the id of the given border. If the border does not exist, it creates
+ * one.
+ */
+ function getBorderId(border) {
+ for (let [key, value] of Object.entries(borders)) {
+ if (stringify(value) === stringify(border)) {
+ return parseInt(key, 10);
+ }
+ }
+ borders[++borderId] = border;
+ return borderId;
+ }
+ for (let sheet of data.sheets) {
+ for (let col = 0; col < sheet.colNumber; col++) {
+ for (let row = 0; row < sheet.rowNumber; row++) {
+ const border = this.getCellBorder({ sheetId: sheet.id, col, row });
+ if (border) {
+ const xc = toXC(col, row);
+ const cell = sheet.cells[xc];
+ const borderId = getBorderId(border);
+ if (cell) {
+ cell.border = borderId;
+ }
+ else {
+ sheet.cells[xc] = { border: borderId };
+ }
+ }
+ }
+ }
+ }
+ data.borders = borders;
+ }
+ exportForExcel(data) {
+ this.export(data);
+ }
+ }
+
/**
* Core Plugin
*
@@ -28984,11 +29261,16 @@
* cell and sheet content.
*/
class CellPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.nextId = 1;
- this.cells = {};
- }
+ static getters = [
+ "zoneToXC",
+ "getCells",
+ "getFormulaCellContent",
+ "getCellStyle",
+ "buildFormulaContent",
+ "getCellById",
+ ];
+ nextId = 1;
+ cells = {};
adaptRanges(applyChange, sheetId) {
for (const sheet of Object.keys(this.cells)) {
for (const cell of Object.values(this.cells[sheet] || {})) {
@@ -29115,7 +29397,7 @@
for (let xc in sheet.cells) {
const cellData = sheet.cells[xc];
const { col, row } = toCartesian(xc);
- if ((cellData === null || cellData === void 0 ? void 0 : cellData.content) || (cellData === null || cellData === void 0 ? void 0 : cellData.format) || (cellData === null || cellData === void 0 ? void 0 : cellData.style)) {
+ if (cellData?.content || cellData?.format || cellData?.style) {
const cell = this.importCell(sheet.id, cellData, data.styles, data.formats);
this.history.update("cells", sheet.id, cell.id, cell);
this.dispatch("UPDATE_CELL_POSITION", {
@@ -29154,7 +29436,7 @@
const style = (cellData.style && normalizedStyles[cellData.style]) || undefined;
const format = (cellData.format && normalizedFormats[cellData.format]) || undefined;
const cellId = this.getNextUid();
- return this.createCell(cellId, (cellData === null || cellData === void 0 ? void 0 : cellData.content) || "", format, style, sheetId);
+ return this.createCell(cellId, cellData?.content || "", format, style, sheetId);
}
exportForExcel(data) {
this.export(data);
@@ -29197,8 +29479,7 @@
return this.buildFormulaContent(sheetId, cell);
}
getCellStyle(position) {
- var _a;
- return ((_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.style) || {};
+ return this.getters.getCell(position)?.style || {};
}
/**
* Converts a zone to a XC coordinate system
@@ -29245,7 +29526,7 @@
sheetId,
col,
row,
- style: style ? { ...cell === null || cell === void 0 ? void 0 : cell.style, ...style } : undefined,
+ style: style ? { ...cell?.style, ...style } : undefined,
});
}
}
@@ -29303,7 +29584,7 @@
const before = this.getters.getCell({ sheetId, col, row });
const hasContent = "content" in after || "formula" in after;
// Compute the new cell properties
- const afterContent = hasContent ? replaceSpecialSpaces(after === null || after === void 0 ? void 0 : after.content) : (before === null || before === void 0 ? void 0 : before.content) || "";
+ const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || "";
let style;
if (after.style !== undefined) {
style = after.style || undefined;
@@ -29334,7 +29615,7 @@
}
return;
}
- const cellId = (before === null || before === void 0 ? void 0 : before.id) || this.getNextUid();
+ const cellId = before?.id || this.getNextUid();
const cell = this.createCell(cellId, afterContent, format, style, sheetId);
this.history.update("cells", sheetId, cell.id, cell);
this.dispatch("UPDATE_CELL_POSITION", { cellId: cell.id, col, row, sheetId });
@@ -29423,25 +29704,22 @@
return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;
}
}
- CellPlugin.getters = [
- "zoneToXC",
- "getCells",
- "getFormulaCellContent",
- "getCellStyle",
- "buildFormulaContent",
- "getCellById",
- ];
class ChartPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.charts = {};
- this.createChart = chartFactory(this.getters);
- this.validateChartDefinition = (cmd) => validateChartDefinition(this, cmd.definition);
- }
+ static getters = [
+ "isChartDefined",
+ "getChartDefinition",
+ "getChartType",
+ "getChartIds",
+ "getChart",
+ "getContextCreationChart",
+ ];
+ charts = {};
+ createChart = chartFactory(this.getters);
+ validateChartDefinition = (cmd) => validateChartDefinition(this, cmd.definition);
adaptRanges(applyChange) {
for (const [chartId, chart] of Object.entries(this.charts)) {
- this.history.update("charts", chartId, chart === null || chart === void 0 ? void 0 : chart.updateRanges(applyChange));
+ this.history.update("charts", chartId, chart?.updateRanges(applyChange));
}
}
// ---------------------------------------------------------------------------
@@ -29458,7 +29736,6 @@
}
}
handle(cmd) {
- var _a;
switch (cmd.type) {
case "CREATE_CHART":
this.addFigure(cmd.id, cmd.sheetId, cmd.position, cmd.size);
@@ -29474,7 +29751,7 @@
if (fig.tag === "chart") {
const figureIdBase = fig.id.split(FIGURE_ID_SPLITTER).pop();
const duplicatedFigureId = `${cmd.sheetIdTo}${FIGURE_ID_SPLITTER}${figureIdBase}`;
- const chart = (_a = this.charts[fig.id]) === null || _a === void 0 ? void 0 : _a.copyForSheetId(cmd.sheetIdTo);
+ const chart = this.charts[fig.id]?.copyForSheetId(cmd.sheetIdTo);
if (chart) {
this.dispatch("CREATE_CHART", {
id: duplicatedFigureId,
@@ -29502,15 +29779,13 @@
// Getters
// ---------------------------------------------------------------------------
getContextCreationChart(figureId) {
- var _a;
- return (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getContextCreation();
+ return this.charts[figureId]?.getContextCreation();
}
getChart(figureId) {
return this.charts[figureId];
}
getChartType(figureId) {
- var _a;
- const type = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.type;
+ const type = this.charts[figureId]?.type;
if (!type) {
throw new Error("Chart not defined.");
}
@@ -29521,12 +29796,11 @@
}
getChartIds(sheetId) {
return Object.entries(this.charts)
- .filter(([, chart]) => (chart === null || chart === void 0 ? void 0 : chart.sheetId) === sheetId)
+ .filter(([, chart]) => chart?.sheetId === sheetId)
.map(([id]) => id);
}
getChartDefinition(figureId) {
- var _a;
- const definition = (_a = this.charts[figureId]) === null || _a === void 0 ? void 0 : _a.getDefinition();
+ const definition = this.charts[figureId]?.getDefinition();
if (!definition) {
throw new Error(`There is no chart with the given figureId: ${figureId}`);
}
@@ -29550,7 +29824,6 @@
}
}
export(data) {
- var _a;
if (data.sheets) {
for (let sheet of data.sheets) {
// TODO This code is false, if two plugins want ot insert figures on the sheet, it will crash !
@@ -29559,7 +29832,7 @@
for (let sheetFigure of sheetFigures) {
const figure = sheetFigure;
if (figure && figure.tag === "chart") {
- const data = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinition();
+ const data = this.charts[figure.id]?.getDefinition();
if (data) {
figure.data = data;
figures.push(figure);
@@ -29574,13 +29847,12 @@
}
}
exportForExcel(data) {
- var _a;
for (let sheet of data.sheets) {
const sheetFigures = this.getters.getFigures(sheet.id);
const figures = [];
for (let figure of sheetFigures) {
if (figure && figure.tag === "chart") {
- const figureData = (_a = this.charts[figure.id]) === null || _a === void 0 ? void 0 : _a.getDefinitionForExcel();
+ const figureData = this.charts[figure.id]?.getDefinitionForExcel();
if (figureData) {
figures.push({
...figure,
@@ -29636,14 +29908,6 @@
: 85 /* CommandResult.ChartDoesNotExist */;
}
}
- ChartPlugin.getters = [
- "isChartDefined",
- "getChartDefinition",
- "getChartType",
- "getChartIds",
- "getChart",
- "getContextCreationChart",
- ];
// -----------------------------------------------------------------------------
// Constants
@@ -29652,10 +29916,8 @@
return value === "" ? NaN : Number(value);
}
class ConditionalFormatPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.cfRules = {};
- }
+ static getters = ["getConditionalFormats", "getRulesSelection", "getRulesByCell"];
+ cfRules = {};
loopThroughRangesOfSheet(sheetId, applyChange) {
for (const rule of this.cfRules[sheetId]) {
for (const range of rule.ranges) {
@@ -29757,8 +30019,7 @@
* Returns all the conditional format rules defined for the current sheet to display the user
*/
getConditionalFormats(sheetId) {
- var _a;
- return ((_a = this.cfRules[sheetId]) === null || _a === void 0 ? void 0 : _a.map((cf) => this.mapToConditionalFormat(sheetId, cf))) || [];
+ return this.cfRules[sheetId]?.map((cf) => this.mapToConditionalFormat(sheetId, cf)) || [];
}
getRulesSelection(sheetId, selection) {
const ruleIds = new Set();
@@ -29956,8 +30217,7 @@
return 0 /* CommandResult.Success */;
}
checkMidBiggerThanMax(rule) {
- var _a;
- const midValue = (_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.value;
+ const midValue = rule.midpoint?.value;
const maxValue = rule.maximum.value;
if (rule.midpoint &&
["number", "percentage", "percentile"].includes(rule.midpoint.type) &&
@@ -29968,9 +30228,8 @@
return 0 /* CommandResult.Success */;
}
checkMinBiggerThanMid(rule) {
- var _a;
const minValue = rule.minimum.value;
- const midValue = (_a = rule.midpoint) === null || _a === void 0 ? void 0 : _a.value;
+ const midValue = rule.midpoint?.value;
if (rule.midpoint &&
["number", "percentage", "percentile"].includes(rule.midpoint.type) &&
rule.minimum.type === rule.midpoint.type &&
@@ -30001,13 +30260,10 @@
}
}
}
- ConditionalFormatPlugin.getters = ["getConditionalFormats", "getRulesSelection", "getRulesByCell"];
class FigurePlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.figures = {};
- }
+ static getters = ["getFigures", "getFigure", "getFigureSheetId"];
+ figures = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -30107,14 +30363,13 @@
this.history.update("figures", sheetId, id, undefined);
}
checkFigureExists(sheetId, figureId) {
- var _a;
- if (((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) === undefined) {
+ if (this.figures[sheetId]?.[figureId] === undefined) {
return 70 /* CommandResult.FigureDoesNotExist */;
}
return 0 /* CommandResult.Success */;
}
checkFigureDuplicate(figureId) {
- if (Object.values(this.figures).find((sheet) => sheet === null || sheet === void 0 ? void 0 : sheet[figureId])) {
+ if (Object.values(this.figures).find((sheet) => sheet?.[figureId])) {
return 82 /* CommandResult.DuplicatedFigureId */;
}
return 0 /* CommandResult.Success */;
@@ -30126,11 +30381,10 @@
return Object.values(this.figures[sheetId] || {}).filter(isDefined$1);
}
getFigure(sheetId, figureId) {
- var _a;
- return (_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId];
+ return this.figures[sheetId]?.[figureId];
}
getFigureSheetId(figureId) {
- return Object.keys(this.figures).find((sheetId) => { var _a; return ((_a = this.figures[sheetId]) === null || _a === void 0 ? void 0 : _a[figureId]) !== undefined; });
+ return Object.keys(this.figures).find((sheetId) => this.figures[sheetId]?.[figureId] !== undefined);
}
// ---------------------------------------------------------------------------
// Import/Export
@@ -30156,9 +30410,11 @@
this.export(data);
}
}
- FigurePlugin.getters = ["getFigures", "getFigure", "getFigureSheetId"];
class FilterTable {
+ id;
+ zone;
+ filters;
constructor(zone) {
this.filters = [];
this.zone = zone;
@@ -30177,14 +30433,15 @@
return { ...this.zone, top: this.zone.top + 1 };
}
getFilterId(col) {
- var _a;
- return (_a = this.filters.find((filter) => filter.col === col)) === null || _a === void 0 ? void 0 : _a.id;
+ return this.filters.find((filter) => filter.col === col)?.id;
}
clone() {
return new FilterTable(this.zone);
}
}
class Filter {
+ id;
+ zoneWithHeaders;
constructor(id, zone) {
if (zone.left !== zone.right) {
throw new Error("Can only define a filter on a single column");
@@ -30206,10 +30463,16 @@
}
class FiltersPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.tables = {};
- }
+ static getters = [
+ "doesZonesContainFilter",
+ "getFilter",
+ "getFilters",
+ "getFilterTable",
+ "getFilterTables",
+ "getFilterTablesInZone",
+ "getFilterId",
+ ];
+ tables = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -30303,12 +30566,10 @@
return this.tables[sheetId] ? Object.values(this.tables[sheetId]).filter(isDefined$1) : [];
}
getFilter(position) {
- var _a;
- return (_a = this.getFilterTable(position)) === null || _a === void 0 ? void 0 : _a.filters.find((filter) => filter.col === position.col);
+ return this.getFilterTable(position)?.filters.find((filter) => filter.col === position.col);
}
getFilterId(position) {
- var _a;
- return (_a = this.getFilter(position)) === null || _a === void 0 ? void 0 : _a.id;
+ return this.getFilter(position)?.id;
}
getFilterTable({ sheetId, col, row }) {
return this.getFilterTables(sheetId).find((filterTable) => isInside(col, row, filterTable.zone));
@@ -30400,7 +30661,6 @@
*
*/
canUpdateCellCmdExtendTable({ content: newCellContent, sheetId, col, row }, table) {
- var _a;
if (!newCellContent) {
return;
}
@@ -30411,7 +30671,7 @@
for (const col of range(zone.left, zone.right + 1)) {
const position = { sheetId, col, row };
// Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
- const cellContent = (_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.content;
+ const cellContent = this.getters.getCell(position)?.content;
if (cellContent) {
return false;
}
@@ -30448,23 +30708,11 @@
this.export(data);
}
}
- FiltersPlugin.getters = [
- "doesZonesContainFilter",
- "getFilter",
- "getFilters",
- "getFilterTable",
- "getFilterTables",
- "getFilterTablesInZone",
- "getFilterId",
- ];
class HeaderSizePlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.sizes = {};
- }
+ static getters = ["getRowSize", "getColSize"];
+ sizes = {};
handle(cmd) {
- var _a, _b, _c;
switch (cmd.type) {
case "CREATE_SHEET": {
const computedSizes = this.computeSheetSizes(cmd.sheetId);
@@ -30552,7 +30800,7 @@
}
break;
case "UPDATE_CELL":
- if (!((_c = (_b = (_a = this.sizes[cmd.sheetId]) === null || _a === void 0 ? void 0 : _a["ROW"]) === null || _b === void 0 ? void 0 : _b[cmd.row]) === null || _c === void 0 ? void 0 : _c.manualSize)) {
+ 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)));
}
@@ -30578,19 +30826,17 @@
return this.getHeaderSize(sheetId, "ROW", index);
}
getHeaderSize(sheetId, dimension, index) {
- var _a, _b, _c, _d;
- return Math.round(((_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a[dimension][index]) === null || _b === void 0 ? void 0 : _b.manualSize) ||
- ((_d = (_c = this.sizes[sheetId]) === null || _c === void 0 ? void 0 : _c[dimension][index]) === null || _d === void 0 ? void 0 : _d.computedSize()) ||
+ return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
+ this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
this.getDefaultHeaderSize(dimension));
}
computeSheetSizes(sheetId) {
- var _a, _b;
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 = (_b = (_a = this.sizes[sheetId]) === null || _a === void 0 ? void 0 : _a["ROW"]) === null || _b === void 0 ? void 0 : _b[row].manualSize;
+ let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
if (!rowSize) {
const height = this.getRowTallestCellSize(sheetId, row);
rowSize = height;
@@ -30677,14 +30923,13 @@
* @param exportDefaults : if true, export column/row sizes even if they have the default size
*/
exportData(data, exportDefaults = false) {
- var _a, _b;
for (let sheet of data.sheets) {
// Export row sizes
if (sheet.rows === undefined) {
sheet.rows = {};
}
for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
- if (exportDefaults || ((_a = this.sizes[sheet.id]["ROW"][row]) === null || _a === void 0 ? void 0 : _a.manualSize)) {
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
}
}
@@ -30693,24 +30938,27 @@
sheet.cols = {};
}
for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
- if (exportDefaults || ((_b = this.sizes[sheet.id]["COL"][col]) === null || _b === void 0 ? void 0 : _b.manualSize)) {
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
}
}
}
}
}
- HeaderSizePlugin.getters = ["getRowSize", "getColSize"];
class HeaderVisibilityPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.hiddenHeaders = {};
- }
+ static getters = [
+ "canRemoveHeaders",
+ "getHiddenColsGroups",
+ "getHiddenRowsGroups",
+ "isRowHiddenByUser",
+ "isColHiddenByUser",
+ ];
+ hiddenHeaders = {};
allowDispatch(cmd) {
switch (cmd.type) {
case "HIDE_COLUMNS_ROWS": {
- if (!this.hiddenHeaders[cmd.sheetId]) {
+ if (!this.getters.tryGetSheet(cmd.sheetId)) {
return 27 /* CommandResult.InvalidSheetId */;
}
const hiddenGroup = cmd.dimension === "COL"
@@ -30719,10 +30967,25 @@
const elements = cmd.dimension === "COL"
? this.getters.getNumberCols(cmd.sheetId)
: this.getters.getNumberRows(cmd.sheetId);
- return (hiddenGroup || []).flat().concat(cmd.elements).length < elements
- ? 0 /* CommandResult.Success */
- : 66 /* CommandResult.TooManyHiddenElements */;
+ const hiddenElements = new Set((hiddenGroup || []).flat().concat(cmd.elements));
+ if (hiddenElements.size >= elements) {
+ return 66 /* CommandResult.TooManyHiddenElements */;
+ }
+ else if (Math.min(...cmd.elements) < 0 || Math.max(...cmd.elements) > elements) {
+ return 86 /* CommandResult.InvalidHeaderIndex */;
+ }
+ else {
+ return 0 /* CommandResult.Success */;
+ }
}
+ case "REMOVE_COLUMNS_ROWS":
+ if (!this.getters.tryGetSheet(cmd.sheetId)) {
+ return 27 /* CommandResult.InvalidSheetId */;
+ }
+ if (!this.canRemoveHeaders(cmd.sheetId, cmd.dimension, cmd.elements)) {
+ return 8 /* CommandResult.NotEnoughElements */;
+ }
+ return 0 /* CommandResult.Success */;
}
return 0 /* CommandResult.Success */;
}
@@ -30769,6 +31032,10 @@
}
return;
}
+ canRemoveHeaders(sheetId, dimension, elements) {
+ const visibleHeaders = this.getAllVisibleHeaders(sheetId, dimension);
+ return !includesAll(elements, visibleHeaders);
+ }
isRowHiddenByUser(sheetId, index) {
return this.hiddenHeaders[sheetId].ROW[index];
}
@@ -30813,15 +31080,17 @@
}
return consecutiveIndexes;
}
+ getAllVisibleHeaders(sheetId, dimension) {
+ return range(0, this.hiddenHeaders[sheetId][dimension].length).filter((i) => !this.hiddenHeaders[sheetId][dimension][i]);
+ }
import(data) {
- var _a, _b;
for (let sheet of data.sheets) {
this.hiddenHeaders[sheet.id] = { COL: [], ROW: [] };
for (let row = 0; row < sheet.rowNumber; row++) {
- this.hiddenHeaders[sheet.id].ROW[row] = Boolean((_a = sheet.rows[row]) === null || _a === void 0 ? void 0 : _a.isHidden);
+ this.hiddenHeaders[sheet.id].ROW[row] = Boolean(sheet.rows[row]?.isHidden);
}
for (let col = 0; col < sheet.colNumber; col++) {
- this.hiddenHeaders[sheet.id].COL[col] = Boolean((_b = sheet.cols[col]) === null || _b === void 0 ? void 0 : _b.isHidden);
+ this.hiddenHeaders[sheet.id].COL[col] = Boolean(sheet.cols[col]?.isHidden);
}
}
return;
@@ -30859,21 +31128,17 @@
}
}
}
- HeaderVisibilityPlugin.getters = [
- "getHiddenColsGroups",
- "getHiddenRowsGroups",
- "isRowHiddenByUser",
- "isColHiddenByUser",
- ];
class ImagePlugin extends CorePlugin {
+ static getters = ["getImage", "getImagePath", "getImageSize"];
+ fileStore;
+ images = {};
+ /**
+ * paths of images synced with the file store server.
+ */
+ syncedImages = new Set();
constructor(config) {
super(config);
- this.images = {};
- /**
- * paths of images synced with the file store server.
- */
- this.syncedImages = new Set();
this.fileStore = config.external.fileStore;
}
// ---------------------------------------------------------------------------
@@ -30930,11 +31195,10 @@
* Delete unused images from the file store
*/
garbageCollectExternalResources() {
- var _a;
const images = new Set(this.getAllImages().map((image) => image.path));
for (const path of this.syncedImages) {
if (!images.has(path)) {
- (_a = this.fileStore) === null || _a === void 0 ? void 0 : _a.delete(path);
+ this.fileStore?.delete(path);
}
}
}
@@ -30979,11 +31243,10 @@
}
}
export(data) {
- var _a;
for (const sheet of data.sheets) {
const images = sheet.figures.filter((figure) => figure.tag === "image");
for (const image of images) {
- image.data = (_a = this.images[sheet.id]) === null || _a === void 0 ? void 0 : _a[image.id];
+ image.data = this.images[sheet.id]?.[image.id];
}
}
}
@@ -30992,7 +31255,7 @@
const figures = this.getters.getFigures(sheet.id);
const images = [];
for (const figure of figures) {
- if ((figure === null || figure === void 0 ? void 0 : figure.tag) === "image") {
+ if (figure?.tag === "image") {
const image = this.getImage(figure.id);
if (image) {
images.push({
@@ -31013,15 +31276,26 @@
return images;
}
}
- ImagePlugin.getters = ["getImage", "getImagePath", "getImageSize"];
class MergePlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.nextId = 1;
- this.merges = {};
- this.mergeCellMap = {};
- }
+ static getters = [
+ "isInMerge",
+ "isInSameMerge",
+ "isMergeHidden",
+ "getMainCellPosition",
+ "getBottomLeftCell",
+ "expandZone",
+ "doesIntersectMerge",
+ "doesColumnsHaveCommonMerges",
+ "doesRowsHaveCommonMerges",
+ "getMerges",
+ "getMerge",
+ "getMergesInZone",
+ "isSingleCellOrMerge",
+ ];
+ nextId = 1;
+ merges = {};
+ mergeCellMap = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -31086,19 +31360,17 @@
.filter(isDefined$1);
}
getMerge({ sheetId, col, row }) {
- var _a;
const sheetMap = this.mergeCellMap[sheetId];
- const mergeId = sheetMap ? col in sheetMap && ((_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row]) : undefined;
+ const mergeId = sheetMap ? col in sheetMap && sheetMap[col]?.[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];
+ const mergeId = sheetMap[col]?.[row];
if (mergeId) {
mergeIds.add(mergeId);
}
@@ -31161,9 +31433,8 @@
return isEqual(mergeA, mergeB);
}
isInMerge({ sheetId, col, row }) {
- var _a;
const sheetMap = this.mergeCellMap[sheetId];
- return sheetMap ? col in sheetMap && Boolean((_a = sheetMap[col]) === null || _a === void 0 ? void 0 : _a[row]) : false;
+ return sheetMap ? col in sheetMap && Boolean(sheetMap[col]?.[row]) : false;
}
getMainCellPosition(position) {
if (!this.isInMerge(position)) {
@@ -31230,8 +31501,7 @@
return false;
}
getMergeById(sheetId, mergeId) {
- var _a;
- const range = (_a = this.merges[sheetId]) === null || _a === void 0 ? void 0 : _a[mergeId];
+ const range = this.merges[sheetId]?.[mergeId];
return range !== undefined ? rangeToMerge(mergeId, range) : undefined;
}
checkDestructiveMerge({ sheetId, target }) {
@@ -31419,21 +31689,6 @@
this.export(data);
}
}
- MergePlugin.getters = [
- "isInMerge",
- "isInSameMerge",
- "isMergeHidden",
- "getMainCellPosition",
- "getBottomLeftCell",
- "expandZone",
- "doesIntersectMerge",
- "doesColumnsHaveCommonMerges",
- "doesRowsHaveCommonMerges",
- "getMerges",
- "getMerge",
- "getMergesInZone",
- "isSingleCellOrMerge",
- ];
function exportMerges(merges) {
return Object.entries(merges)
.map(([mergeId, range]) => (range ? rangeToMerge(parseInt(mergeId, 10), range) : undefined))
@@ -31449,10 +31704,20 @@
}
class RangeAdapter {
+ getters;
+ providers = [];
constructor(getters) {
- this.providers = [];
this.getters = getters;
}
+ static getters = [
+ "getRangeString",
+ "getSelectionRangeString",
+ "getRangeFromSheetXC",
+ "createAdaptedRanges",
+ "getRangeDataFromXc",
+ "getRangeDataFromZone",
+ "getRangeFromRangeData",
+ ];
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -31827,24 +32092,38 @@
};
}
}
- RangeAdapter.getters = [
- "getRangeString",
- "getSelectionRangeString",
- "getRangeFromSheetXC",
- "createAdaptedRanges",
- "getRangeDataFromXc",
- "getRangeDataFromZone",
- "getRangeFromRangeData",
- ];
class SheetPlugin extends CorePlugin {
- constructor() {
- super(...arguments);
- this.sheetIdsMapName = {};
- this.orderedSheetIds = [];
- this.sheets = {};
- this.cellPosition = {};
- }
+ static getters = [
+ "getSheetName",
+ "tryGetSheetName",
+ "getSheet",
+ "tryGetSheet",
+ "getSheetIdByName",
+ "getSheetIds",
+ "getVisibleSheetIds",
+ "isSheetVisible",
+ "getEvaluationSheets",
+ "doesHeaderExist",
+ "getCell",
+ "getCellPosition",
+ "getColsZone",
+ "getRowCells",
+ "getRowsZone",
+ "getNumberCols",
+ "getNumberRows",
+ "getNumberHeaders",
+ "getGridLinesVisibility",
+ "getNextSheetName",
+ "isEmpty",
+ "getSheetSize",
+ "getSheetZone",
+ "getPaneDivisions",
+ ];
+ sheetIdsMapName = {};
+ orderedSheetIds = [];
+ sheets = {};
+ cellPosition = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -31878,13 +32157,27 @@
return this.orderedSheetIds.length > 1
? 0 /* CommandResult.Success */
: 9 /* CommandResult.NotEnoughSheets */;
- case "REMOVE_COLUMNS_ROWS": {
- const length = cmd.dimension === "COL"
+ case "ADD_COLUMNS_ROWS":
+ const elements = cmd.dimension === "COL"
? this.getNumberCols(cmd.sheetId)
: this.getNumberRows(cmd.sheetId);
- return length > cmd.elements.length
- ? 0 /* CommandResult.Success */
- : 8 /* CommandResult.NotEnoughElements */;
+ if (cmd.base < 0 || cmd.base > elements) {
+ return 86 /* CommandResult.InvalidHeaderIndex */;
+ }
+ else if (cmd.quantity <= 0) {
+ return 87 /* CommandResult.InvalidQuantity */;
+ }
+ return 0 /* CommandResult.Success */;
+ case "REMOVE_COLUMNS_ROWS": {
+ const elements = cmd.dimension === "COL"
+ ? this.getNumberCols(cmd.sheetId)
+ : this.getNumberRows(cmd.sheetId);
+ if (Math.min(...cmd.elements) < 0 || Math.max(...cmd.elements) > elements) {
+ return 86 /* CommandResult.InvalidHeaderIndex */;
+ }
+ else {
+ return 0 /* CommandResult.Success */;
+ }
}
case "FREEZE_ROWS": {
return this.checkValidations(cmd, this.checkRowFreezeQuantity, this.checkRowFreezeOverlapMerge);
@@ -31966,7 +32259,6 @@
// Import/Export
// ---------------------------------------------------------------------------
import(data) {
- var _a, _b;
// we need to fill the sheetIds mapping first, because otherwise formulas
// that depends on a sheet not already imported will not be able to be
// compiled
@@ -31984,8 +32276,8 @@
areGridLinesVisible: sheetData.areGridLinesVisible === undefined ? true : sheetData.areGridLinesVisible,
isVisible: sheetData.isVisible,
panes: {
- xSplit: ((_a = sheetData.panes) === null || _a === void 0 ? void 0 : _a.xSplit) || 0,
- ySplit: ((_b = sheetData.panes) === null || _b === void 0 ? void 0 : _b.ySplit) || 0,
+ xSplit: sheetData.panes?.xSplit || 0,
+ ySplit: sheetData.panes?.ySplit || 0,
},
};
this.orderedSheetIds.push(sheet.id);
@@ -32051,8 +32343,7 @@
* Return the sheet name or undefined if the sheet doesn't exist.
*/
tryGetSheetName(sheetId) {
- var _a;
- return (_a = this.tryGetSheet(sheetId)) === null || _a === void 0 ? void 0 : _a.name;
+ return this.tryGetSheet(sheetId)?.name;
}
getSheetIdByName(name) {
if (name) {
@@ -32087,9 +32378,8 @@
return row;
}
getCell({ sheetId, col, row }) {
- var _a;
const sheet = this.tryGetSheet(sheetId);
- const cellId = (_a = sheet === null || sheet === void 0 ? void 0 : sheet.rows[row]) === null || _a === void 0 ? void 0 : _a.cells[col];
+ const cellId = sheet?.rows[row]?.cells[col];
if (cellId === undefined) {
return undefined;
}
@@ -32104,8 +32394,7 @@
};
}
getRowCells(sheetId, row) {
- var _a;
- return Object.values((_a = this.getSheet(sheetId).rows[row]) === null || _a === void 0 ? void 0 : _a.cells).filter(isDefined$1);
+ return Object.values(this.getSheet(sheetId).rows[row]?.cells).filter(isDefined$1);
}
getRowsZone(sheetId, start, end) {
return {
@@ -32207,8 +32496,7 @@
* Remove the cell at the given position (if there's one)
*/
clearPosition(sheetId, col, row) {
- var _a;
- const cellId = (_a = this.sheets[sheetId]) === null || _a === void 0 ? void 0 : _a.rows[row].cells[col];
+ const cellId = this.sheets[sheetId]?.rows[row].cells[col];
if (cellId) {
this.history.update("cellPosition", cellId, undefined);
this.history.update("sheets", sheetId, "rows", row, "cells", col, undefined);
@@ -32285,7 +32573,7 @@
checkSheetName(cmd) {
const { orderedSheetIds, sheets } = this;
const name = cmd.name && cmd.name.trim().toLowerCase();
- if (orderedSheetIds.find((id) => { var _a; return ((_a = sheets[id]) === null || _a === void 0 ? void 0 : _a.name.toLowerCase()) === name; })) {
+ if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name)) {
return 11 /* CommandResult.DuplicatedSheetName */;
}
if (FORBIDDEN_IN_EXCEL_REGEX.test(name)) {
@@ -32671,38 +32959,16 @@
return 0 /* CommandResult.Success */;
}
}
- SheetPlugin.getters = [
- "getSheetName",
- "tryGetSheetName",
- "getSheet",
- "tryGetSheet",
- "getSheetIdByName",
- "getSheetIds",
- "getVisibleSheetIds",
- "isSheetVisible",
- "getEvaluationSheets",
- "doesHeaderExist",
- "getCell",
- "getCellPosition",
- "getColsZone",
- "getRowCells",
- "getRowsZone",
- "getNumberCols",
- "getNumberRows",
- "getNumberHeaders",
- "getGridLinesVisibility",
- "getNextSheetName",
- "isEmpty",
- "getSheetSize",
- "getSheetZone",
- "getPaneDivisions",
- ];
/**
* UI plugins handle any transient data required to display a spreadsheet.
* They can draw on the grid canvas.
*/
class UIPlugin extends BasePlugin {
+ static layers = [];
+ getters;
+ ui;
+ selection;
constructor({ getters, stateObserver, dispatch, uiActions, selection }) {
super(stateObserver, dispatch);
this.getters = getters;
@@ -32714,7 +32980,6 @@
// ---------------------------------------------------------------------------
drawGrid(ctx, layer) { }
}
- UIPlugin.layers = [];
/**
* https://tomekdev.com/posts/sorting-colors-in-js
@@ -32762,11 +33027,9 @@
* current spreadsheet
*/
class CustomColorsPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.customColors = new Set();
- this.shouldUpdateColors = false;
- }
+ customColors = new Set();
+ shouldUpdateColors = false;
+ static getters = ["getCustomColors"];
handle(cmd) {
switch (cmd.type) {
case "UPDATE_CELL":
@@ -32799,13 +33062,12 @@
]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
}
getColorsFromCells(cells) {
- var _a, _b;
const colors = new Set();
for (const cell of cells) {
- if ((_a = cell.style) === null || _a === void 0 ? void 0 : _a.textColor) {
+ if (cell.style?.textColor) {
colors.add(cell.style.textColor);
}
- if ((_b = cell.style) === null || _b === void 0 ? void 0 : _b.fillColor) {
+ if (cell.style?.fillColor) {
colors.add(cell.style.fillColor);
}
}
@@ -32862,14 +33124,25 @@
}
}
}
- CustomColorsPlugin.getters = ["getCustomColors"];
const functionMap = functionRegistry.mapping;
class EvaluationPlugin extends UIPlugin {
+ static getters = [
+ "evaluateFormula",
+ "getRangeFormattedValues",
+ "getRangeValues",
+ "getRangeFormats",
+ "getEvaluatedCell",
+ "getEvaluatedCells",
+ "getColEvaluatedCells",
+ "getEvaluatedCellsInZone",
+ ];
+ isUpToDate = false;
+ evaluatedCells = {};
+ evalContext;
+ lazyEvaluation;
constructor(config) {
super(config);
- this.isUpToDate = false;
- this.evaluatedCells = {};
this.evalContext = config.custom;
this.lazyEvaluation = config.lazyEvaluation;
}
@@ -32939,14 +33212,13 @@
return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.format);
}
getEvaluatedCell({ sheetId, col, row }) {
- var _a, _b, _c;
const cell = this.getters.getCell({ sheetId, col, row });
if (cell === undefined) {
return createEvaluatedCell("");
}
// the cell might have been created by a command in the current
// dispatch but the evaluation is not done yet.
- return ((_c = (_b = (_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row]) === null || _c === void 0 ? void 0 : _c.call(_b)) || createEvaluatedCell("");
+ return this.evaluatedCells[sheetId]?.[col]?.[row]?.() || createEvaluatedCell("");
}
getEvaluatedCells(sheetId) {
const rawCells = this.getters.getCells(sheetId) || {};
@@ -32961,8 +33233,7 @@
* Returns all the evaluated cells of a col
*/
getColEvaluatedCells(sheetId, col) {
- var _a;
- return Object.values(((_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) || [])
+ return Object.values(this.evaluatedCells[sheetId]?.[col] || [])
.filter(isDefined$1)
.map((lazyCell) => lazyCell());
}
@@ -32998,10 +33269,9 @@
this.evaluatedCells = {};
const cellsBeingComputed = new Set();
const computeCell = (cell) => {
- var _a, _b;
const cellId = cell.id;
const { col, row, sheetId } = this.getters.getCellPosition(cellId);
- const lazyEvaluation = (_b = (_a = this.evaluatedCells[sheetId]) === null || _a === void 0 ? void 0 : _a[col]) === null || _b === void 0 ? void 0 : _b[row];
+ const lazyEvaluation = this.evaluatedCells[sheetId]?.[col]?.[row];
if (lazyEvaluation) {
return lazyEvaluation; // already computed
}
@@ -33023,7 +33293,7 @@
if (!(e instanceof Error)) {
e = new Error(e);
}
- const msg = (e === null || e === void 0 ? void 0 : e.errorType) || CellErrorType.GenericError;
+ const msg = e?.errorType || CellErrorType.GenericError;
// apply function name
const __lastFnCalled = compilationParameters[2].__lastFnCalled || "";
const error = new EvaluationError(msg, e.message.replace("[[FUNCTION_NAME]]", __lastFnCalled), e.logLevel !== undefined ? e.logLevel : CellErrorLevel.error);
@@ -33157,8 +33427,12 @@
const cell = this.getters.getCell(position);
if (cell) {
const exportedCellData = sheet.cells[xc];
- exportedCellData.value = this.getEvaluatedCell(position).value;
+ const evaluatedCell = this.getEvaluatedCell(position);
+ exportedCellData.value = evaluatedCell.value;
exportedCellData.isFormula = cell.isFormula && !this.isBadExpression(cell.content);
+ if (cell.format !== evaluatedCell.format) {
+ exportedCellData.computedFormat = evaluatedCell.format;
+ }
}
}
}
@@ -33173,23 +33447,11 @@
}
}
}
- EvaluationPlugin.getters = [
- "evaluateFormula",
- "getRangeFormattedValues",
- "getRangeValues",
- "getRangeFormats",
- "getEvaluatedCell",
- "getEvaluatedCells",
- "getColEvaluatedCells",
- "getEvaluatedCellsInZone",
- ];
class EvaluationChartPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.charts = {};
- this.createRuntimeChart = chartRuntimeFactory(this.getters);
- }
+ static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
+ charts = {};
+ createRuntimeChart = chartRuntimeFactory(this.getters);
handle(cmd) {
if (invalidateEvaluationCommands.has(cmd.type) ||
invalidateCFEvaluationCommands.has(cmd.type) ||
@@ -33246,75 +33508,16 @@
return style.fillColor || BACKGROUND_CHART_COLOR;
}
}
- EvaluationChartPlugin.getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
// -----------------------------------------------------------------------------
// Constants
// -----------------------------------------------------------------------------
class EvaluationConditionalFormatPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.isStale = true;
- // stores the computed styles in the format of computedStyles.sheetName[col][row] = Style
- this.computedStyles = {};
- this.computedIcons = {};
- /**
- * Execute the predicate to know if a conditional formatting rule should be applied to a cell
- */
- this.rulePredicate = {
- CellIsRule: (cell, rule) => {
- if (cell.type === CellValueType.error) {
- return false;
- }
- const values = rule.values.map(parseLiteral);
- switch (rule.operator) {
- case "IsEmpty":
- return cell.value.toString().trim() === "";
- case "IsNotEmpty":
- return cell.value.toString().trim() !== "";
- case "BeginsWith":
- if (values[0] === "") {
- return false;
- }
- return cell.value.toString().startsWith(values[0].toString());
- case "EndsWith":
- if (values[0] === "") {
- return false;
- }
- return cell.value.toString().endsWith(values[0].toString());
- case "Between":
- return cell.value >= values[0] && cell.value <= values[1];
- case "NotBetween":
- return !(cell.value >= values[0] && cell.value <= values[1]);
- case "ContainsText":
- return cell.value.toString().indexOf(values[0].toString()) > -1;
- case "NotContains":
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
- case "GreaterThan":
- return cell.value > values[0];
- case "GreaterThanOrEqual":
- return cell.value >= values[0];
- case "LessThan":
- return cell.value < values[0];
- case "LessThanOrEqual":
- return cell.value <= values[0];
- case "NotEqual":
- if (values[0] === "") {
- return false;
- }
- return cell.value !== values[0];
- case "Equal":
- if (values[0] === "") {
- return true;
- }
- return cell.value === values[0];
- default:
- console.warn(_lt("Not implemented operator %s for kind of conditional formatting: %s", rule.operator, rule.type));
- }
- return false;
- },
- };
- }
+ static getters = ["getConditionalIcon", "getCellComputedStyle"];
+ isStale = true;
+ // stores the computed styles in the format of computedStyles.sheetName[col][row] = Style
+ computedStyles = {};
+ computedIcons = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -33352,14 +33555,13 @@
// Getters
// ---------------------------------------------------------------------------
getCellComputedStyle(position) {
- var _a;
// TODO move this getter out of CF: it also depends on filters and link
const { sheetId, col, row } = position;
const cell = this.getters.getCell(position);
const styles = this.computedStyles[sheetId];
- const cfStyle = styles && ((_a = styles[col]) === null || _a === void 0 ? void 0 : _a[row]);
+ const cfStyle = styles && styles[col]?.[row];
const computedStyle = {
- ...cell === null || cell === void 0 ? void 0 : cell.style,
+ ...cell?.style,
...cfStyle,
};
const evaluatedCell = this.getters.getEvaluatedCell(position);
@@ -33372,10 +33574,9 @@
return computedStyle;
}
getConditionalIcon({ col, row }) {
- var _a;
const activeSheet = this.getters.getActiveSheetId();
const icon = this.computedIcons[activeSheet];
- return icon && ((_a = icon[col]) === null || _a === void 0 ? void 0 : _a[row]);
+ return icon && icon[col]?.[row];
}
// ---------------------------------------------------------------------------
// Private
@@ -33391,7 +33592,6 @@
* If multiple conditional formatting use the same style value, they will be applied in order so that the last applied wins
*/
computeStyles() {
- var _a;
const sheetId = this.getters.getActiveSheetId();
this.computedStyles[sheetId] = {};
this.computedIcons[sheetId] = {};
@@ -33420,7 +33620,7 @@
if (!computedStyle[col])
computedStyle[col] = [];
// we must combine all the properties of all the CF rules applied to the given cell
- computedStyle[col][row] = Object.assign(((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {}, cf.rule.style);
+ computedStyle[col][row] = Object.assign(computedStyle[col]?.[row] || {}, cf.rule.style);
}
}
}
@@ -33497,7 +33697,6 @@
return icons[2];
}
applyColorScale(range, rule) {
- var _a;
const minValue = this.parsePoint(range, rule.minimum, "min");
const midValue = rule.midpoint ? this.parsePoint(range, rule.midpoint) : null;
const maxValue = this.parsePoint(range, rule.maximum, "max");
@@ -33547,7 +33746,7 @@
}
if (!computedStyle[col])
computedStyle[col] = [];
- computedStyle[col][row] = ((_a = computedStyle[col]) === null || _a === void 0 ? void 0 : _a[row]) || {};
+ computedStyle[col][row] = computedStyle[col]?.[row] || {};
computedStyle[col][row].fillColor = colorNumberString(color);
}
}
@@ -33570,6 +33769,62 @@
const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
return (r << 16) | (g << 8) | b;
}
+ /**
+ * Execute the predicate to know if a conditional formatting rule should be applied to a cell
+ */
+ rulePredicate = {
+ CellIsRule: (cell, rule) => {
+ if (cell.type === CellValueType.error) {
+ return false;
+ }
+ const values = rule.values.map(parseLiteral);
+ switch (rule.operator) {
+ case "IsEmpty":
+ return cell.value.toString().trim() === "";
+ case "IsNotEmpty":
+ return cell.value.toString().trim() !== "";
+ case "BeginsWith":
+ if (values[0] === "") {
+ return false;
+ }
+ return cell.value.toString().startsWith(values[0].toString());
+ case "EndsWith":
+ if (values[0] === "") {
+ return false;
+ }
+ return cell.value.toString().endsWith(values[0].toString());
+ case "Between":
+ return cell.value >= values[0] && cell.value <= values[1];
+ case "NotBetween":
+ return !(cell.value >= values[0] && cell.value <= values[1]);
+ case "ContainsText":
+ return cell.value.toString().indexOf(values[0].toString()) > -1;
+ case "NotContains":
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
+ case "GreaterThan":
+ return cell.value > values[0];
+ case "GreaterThanOrEqual":
+ return cell.value >= values[0];
+ case "LessThan":
+ return cell.value < values[0];
+ case "LessThanOrEqual":
+ return cell.value <= values[0];
+ case "NotEqual":
+ if (values[0] === "") {
+ return false;
+ }
+ return cell.value !== values[0];
+ case "Equal":
+ if (values[0] === "") {
+ return true;
+ }
+ return cell.value === values[0];
+ default:
+ console.warn(_lt("Not implemented operator %s for kind of conditional formatting: %s", rule.operator, rule.type));
+ }
+ return false;
+ },
+ };
/**
* Add or remove cells to a given conditional formatting rule.
*/
@@ -33618,15 +33873,19 @@
}
}
}
- EvaluationConditionalFormatPlugin.getters = ["getConditionalIcon", "getCellComputedStyle"];
class FilterEvaluationPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.filterValues = {};
- this.hiddenRows = new Set();
- this.isEvaluationDirty = false;
- }
+ static getters = [
+ "getCellBorderWithFilterBorder",
+ "getFilterHeaders",
+ "getFilterValues",
+ "isFilterHeader",
+ "isRowFiltered",
+ "isFilterActive",
+ ];
+ filterValues = {};
+ hiddenRows = new Set();
+ isEvaluationDirty = false;
allowDispatch(cmd) {
switch (cmd.type) {
case "UPDATE_FILTER":
@@ -33743,10 +34002,9 @@
return headers.some((header) => header.col === col && header.row === row);
}
isFilterActive(position) {
- var _a, _b;
const id = this.getters.getFilterId(position);
const sheetId = position.sheetId;
- return Boolean(id && ((_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[id]) === null || _b === void 0 ? void 0 : _b.length));
+ return Boolean(id && this.filterValues[sheetId]?.[id]?.length);
}
intersectZoneWithViewport(sheetId, zone) {
return {
@@ -33765,7 +34023,6 @@
this.filterValues[sheetId][id] = values;
}
updateHiddenRows() {
- var _a, _b;
const sheetId = this.getters.getActiveSheetId();
const filters = this.getters
.getFilters(sheetId)
@@ -33777,7 +34034,7 @@
continue;
if (hiddenRows.has(filter.zoneWithHeaders.top))
continue;
- const filteredValues = (_b = (_a = this.filterValues[sheetId]) === null || _a === void 0 ? void 0 : _a[filter.id]) === null || _b === void 0 ? void 0 : _b.map(toLowerCase);
+ const filteredValues = this.filterValues[sheetId]?.[filter.id]?.map(toLowerCase);
if (!filteredValues || !filter.filteredZone)
continue;
for (let row = filter.filteredZone.top; row <= filter.filteredZone.bottom; row++) {
@@ -33855,16 +34112,25 @@
return currentColName;
}
}
- FilterEvaluationPlugin.getters = [
- "getCellBorderWithFilterBorder",
- "getFilterHeaders",
- "getFilterValues",
- "isFilterHeader",
- "isRowFiltered",
- "isFilterActive",
- ];
class InternalViewport {
+ getters;
+ sheetId;
+ boundaries;
+ top;
+ bottom;
+ left;
+ right;
+ offsetX;
+ offsetY;
+ offsetScrollbarX;
+ offsetScrollbarY;
+ canScrollVertically;
+ canScrollHorizontally;
+ viewportWidth;
+ viewportHeight;
+ offsetCorrectionX;
+ offsetCorrectionY;
constructor(getters, sheetId, boundaries, sizeInGrid, options, offsets) {
this.getters = getters;
this.sheetId = sheetId;
@@ -34200,21 +34466,39 @@
*
*/
class SheetViewPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.viewports = {};
- /**
- * The viewport dimensions are usually set by one of the components
- * (i.e. when grid component is mounted) to properly reflect its state in the DOM.
- * In the absence of a component (standalone model), is it mandatory to set reasonable default values
- * to ensure the correct operation of this plugin.
- */
- this.sheetViewWidth = getDefaultSheetViewSize();
- this.sheetViewHeight = getDefaultSheetViewSize();
- this.gridOffsetX = 0;
- this.gridOffsetY = 0;
- this.sheetsWithDirtyViewports = new Set();
- }
+ static getters = [
+ "getColIndex",
+ "getRowIndex",
+ "getActiveMainViewport",
+ "getSheetViewDimension",
+ "getSheetViewDimensionWithHeaders",
+ "getMainViewportRect",
+ "isVisibleInViewport",
+ "getEdgeScrollCol",
+ "getEdgeScrollRow",
+ "getVisibleFigures",
+ "getVisibleRect",
+ "getColRowOffsetInViewport",
+ "getMainViewportCoordinates",
+ "getActiveSheetScrollInfo",
+ "getActiveSheetDOMScrollInfo",
+ "getSheetViewVisibleCols",
+ "getSheetViewVisibleRows",
+ "getFrozenSheetViewRatio",
+ "isPositionVisible",
+ ];
+ viewports = {};
+ /**
+ * The viewport dimensions are usually set by one of the components
+ * (i.e. when grid component is mounted) to properly reflect its state in the DOM.
+ * In the absence of a component (standalone model), is it mandatory to set reasonable default values
+ * to ensure the correct operation of this plugin.
+ */
+ sheetViewWidth = getDefaultSheetViewSize();
+ sheetViewHeight = getDefaultSheetViewSize();
+ gridOffsetX = 0;
+ gridOffsetY = 0;
+ sheetsWithDirtyViewports = new Set();
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -34255,7 +34539,6 @@
}
}
handle(cmd) {
- var _a;
this.cleanViewports();
switch (cmd.type) {
case "START":
@@ -34297,7 +34580,7 @@
break;
case "UPDATE_CELL":
// update cell content or format can change hidden rows because of data filters
- if ("content" in cmd || "format" in cmd || ((_a = cmd.style) === null || _a === void 0 ? void 0 : _a.fontSize) !== undefined) {
+ if ("content" in cmd || "format" in cmd || cmd.style?.fontSize !== undefined) {
this.sheetsWithDirtyViewports.add(cmd.sheetId);
}
break;
@@ -34329,10 +34612,9 @@
this.setViewports();
}
setViewports() {
- var _a;
const sheetIds = this.getters.getSheetIds();
for (const sheetId of sheetIds) {
- if (!((_a = this.viewports[sheetId]) === null || _a === void 0 ? void 0 : _a.bottomRight)) {
+ if (!this.viewports[sheetId]?.bottomRight) {
this.resetViewports(sheetId);
}
}
@@ -34659,10 +34941,9 @@
return offsetY;
}
getViewportOffset(sheetId) {
- var _a, _b;
return {
- x: ((_a = this.viewports[sheetId]) === null || _a === void 0 ? void 0 : _a.bottomRight.offsetScrollbarX) || 0,
- y: ((_b = this.viewports[sheetId]) === null || _b === void 0 ? void 0 : _b.bottomRight.offsetScrollbarY) || 0,
+ x: this.viewports[sheetId]?.bottomRight.offsetScrollbarX || 0,
+ y: this.viewports[sheetId]?.bottomRight.offsetScrollbarY || 0,
};
}
resetViewports(sheetId) {
@@ -34766,27 +35047,6 @@
return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };
}
}
- SheetViewPlugin.getters = [
- "getColIndex",
- "getRowIndex",
- "getActiveMainViewport",
- "getSheetViewDimension",
- "getSheetViewDimensionWithHeaders",
- "getMainViewportRect",
- "isVisibleInViewport",
- "getEdgeScrollCol",
- "getEdgeScrollRow",
- "getVisibleFigures",
- "getVisibleRect",
- "getColRowOffsetInViewport",
- "getMainViewportCoordinates",
- "getActiveSheetScrollInfo",
- "getActiveSheetDOMScrollInfo",
- "getSheetViewVisibleCols",
- "getSheetViewVisibleRows",
- "getFrozenSheetViewRatio",
- "isPositionVisible",
- ];
/**
* This plugin manage the autofill.
@@ -34813,8 +35073,11 @@
* should be computed.
*/
class AutofillGenerator {
+ cells;
+ getters;
+ index = 0;
+ direction;
constructor(cells, getters, direction) {
- this.index = 0;
this.cells = cells;
this.getters = getters;
this.direction = direction;
@@ -34843,10 +35106,13 @@
*
*/
class AutofillPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.lastCellSelected = {};
- }
+ static layers = [5 /* LAYERS.Autofill */];
+ static getters = ["getAutofillTooltip"];
+ autofillZone;
+ steps;
+ lastCellSelected = {};
+ direction;
+ tooltip;
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -35145,7 +35411,7 @@
}
}
const originMerge = this.getters.getMerge(originPosition);
- if ((originMerge === null || originMerge === void 0 ? void 0 : originMerge.topLeft.col) === originCol && (originMerge === null || originMerge === void 0 ? void 0 : originMerge.topLeft.row) === originRow) {
+ if (originMerge?.topLeft.col === originCol && originMerge?.topLeft.row === originRow) {
this.dispatch("ADD_MERGE", {
sheetId,
target: [
@@ -35177,10 +35443,9 @@
}
}
}
- AutofillPlugin.layers = [5 /* LAYERS.Autofill */];
- AutofillPlugin.getters = ["getAutofillTooltip"];
class AutomaticSumPlugin extends UIPlugin {
+ static getters = ["getAutomaticSums"];
handle(cmd) {
switch (cmd.type) {
case "SUM_SELECTION":
@@ -35331,8 +35596,7 @@
return this.getters.isEmpty(sheetId, zone) || this.getters.isSingleCellOrMerge(sheetId, zone);
}
isNumber(cell) {
- var _a;
- return cell.type === CellValueType.number && !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT));
+ return cell.type === CellValueType.number && !(cell.format && isDateTimeFormat(cell.format));
}
isZoneValid(zone) {
return zone.bottom >= zone.top && zone.right >= zone.left;
@@ -35451,12 +35715,17 @@
return new Set([...dimensions.values()].map((dimension) => (dimension === "COL" ? "ROW" : "COL")));
}
}
- AutomaticSumPlugin.getters = ["getAutomaticSums"];
/**
* Plugin managing the display of components next to cells.
*/
class CellPopoverPlugin extends UIPlugin {
+ static getters = [
+ "getCellPopover",
+ "getPersistentPopoverTypeAtPosition",
+ "hasOpenedPopover",
+ ];
+ persistentPopover;
allowDispatch(cmd) {
switch (cmd.type) {
case "OPEN_CELL_POPOVER":
@@ -35490,13 +35759,13 @@
}
}
getCellPopover({ col, row }) {
- var _a, _b;
const sheetId = this.getters.getActiveSheetId();
if (this.persistentPopover && this.getters.isVisibleInViewport(this.persistentPopover)) {
const position = this.getters.getMainCellPosition(this.persistentPopover);
- const popover = (_b = (_a = cellPopoverRegistry
- .get(this.persistentPopover.type)).onOpen) === null || _b === void 0 ? void 0 : _b.call(_a, position, this.getters);
- return !(popover === null || popover === void 0 ? void 0 : popover.isOpen)
+ const popover = cellPopoverRegistry
+ .get(this.persistentPopover.type)
+ .onOpen?.(position, this.getters);
+ return !popover?.isOpen
? { isOpen: false }
: {
...popover,
@@ -35511,9 +35780,9 @@
const position = this.getters.getMainCellPosition({ sheetId, col, row });
const popover = cellPopoverRegistry
.getAll()
- .map((matcher) => { var _a; return (_a = matcher.onHover) === null || _a === void 0 ? void 0 : _a.call(matcher, position, this.getters); })
- .find((popover) => popover === null || popover === void 0 ? void 0 : popover.isOpen);
- return !(popover === null || popover === void 0 ? void 0 : popover.isOpen)
+ .map((matcher) => matcher.onHover?.(position, this.getters))
+ .find((popover) => popover?.isOpen);
+ return !popover?.isOpen
? { isOpen: false }
: {
...popover,
@@ -35540,1462 +35809,13 @@
return this.getters.getVisibleRect(positionToZone({ col, row }));
}
}
- CellPopoverPlugin.getters = [
- "getCellPopover",
- "getPersistentPopoverTypeAtPosition",
- "hasOpenedPopover",
- ];
-
- const BORDER_COLOR = "#8B008B";
- const BACKGROUND_COLOR = "#8B008B33";
- var Direction;
- (function (Direction) {
- Direction[Direction["previous"] = -1] = "previous";
- Direction[Direction["current"] = 0] = "current";
- Direction[Direction["next"] = 1] = "next";
- })(Direction || (Direction = {}));
- /**
- * Find and Replace Plugin
- *
- * This plugin is used in combination with the find_and_replace sidePanel
- * It is used to 'highlight' cells that match an input string according to
- * the given searchOptions. The second part of this plugin makes it possible
- * (again with the find_and_replace sidePanel), to replace the values that match
- * the search with a new value.
- */
- class FindAndReplacePlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.searchMatches = [];
- this.selectedMatchIndex = null;
- this.currentSearchRegex = null;
- this.searchOptions = {
- matchCase: false,
- exactMatch: false,
- searchFormulas: false,
- };
- this.toSearch = "";
- }
- // ---------------------------------------------------------------------------
- // Command Handling
- // ---------------------------------------------------------------------------
- handle(cmd) {
- switch (cmd.type) {
- case "UPDATE_SEARCH":
- this.updateSearch(cmd.toSearch, cmd.searchOptions);
- break;
- case "CLEAR_SEARCH":
- this.clearSearch();
- break;
- case "SELECT_SEARCH_PREVIOUS_MATCH":
- this.selectNextCell(Direction.previous);
- break;
- case "SELECT_SEARCH_NEXT_MATCH":
- this.selectNextCell(Direction.next);
- break;
- case "REPLACE_SEARCH":
- this.replace(cmd.replaceWith);
- break;
- case "REPLACE_ALL_SEARCH":
- this.replaceAll(cmd.replaceWith);
- break;
- case "UNDO":
- case "REDO":
- case "REMOVE_COLUMNS_ROWS":
- case "ADD_COLUMNS_ROWS":
- this.clearSearch();
- break;
- case "ACTIVATE_SHEET":
- case "REFRESH_SEARCH":
- this.refreshSearch();
- break;
- }
- }
- // ---------------------------------------------------------------------------
- // Getters
- // ---------------------------------------------------------------------------
- getSearchMatches() {
- return this.searchMatches;
- }
- getCurrentSelectedMatchIndex() {
- return this.selectedMatchIndex;
- }
- // ---------------------------------------------------------------------------
- // Search
- // ---------------------------------------------------------------------------
- /**
- * Will update the current searchOptions and accordingly update the regex.
- * It will then search for matches using the regex and store them.
- */
- updateSearch(toSearch, searchOptions) {
- this.searchOptions = searchOptions;
- if (toSearch !== this.toSearch) {
- this.selectedMatchIndex = null;
- }
- this.toSearch = toSearch;
- this.updateRegex();
- this.refreshSearch();
- }
- /**
- * refresh the matches according to the current search options
- */
- refreshSearch() {
- const matches = this.findMatches();
- this.searchMatches = matches;
- this.selectNextCell(Direction.current);
- }
- /**
- * Updates the regex based on the current searchOptions and
- * the value toSearch
- */
- updateRegex() {
- let searchValue = escapeRegExp(this.toSearch);
- const flags = !this.searchOptions.matchCase ? "i" : "";
- if (this.searchOptions.exactMatch) {
- searchValue = `^${searchValue}$`;
- }
- this.currentSearchRegex = RegExp(searchValue, flags);
- }
- /**
- * Find matches using the current regex
- */
- findMatches() {
- const sheetId = this.getters.getActiveSheetId();
- const cells = this.getters.getCells(sheetId);
- const matches = [];
- if (this.toSearch) {
- for (const cell of Object.values(cells)) {
- const { col, row } = this.getters.getCellPosition(cell.id);
- if (cell &&
- this.currentSearchRegex &&
- this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
- const match = { col, row, selected: false };
- matches.push(match);
- }
- }
- }
- return matches.sort(this.sortByRowThenColumn);
- }
- sortByRowThenColumn(a, b) {
- if (a.row === b.row) {
- return a.col - b.col;
- }
- return a.row > b.row ? 1 : -1;
- }
- /**
- * Changes the selected search cell. Given a direction it will
- * Change the selection to the previous, current or nextCell,
- * if it exists otherwise it will set the selectedMatchIndex to null.
- * It will also reset the index to 0 if the search has changed.
- * It is also used to keep coherence between the selected searchMatch
- * and selectedMatchIndex.
- */
- selectNextCell(indexChange) {
- const matches = this.searchMatches;
- if (!matches.length) {
- this.selectedMatchIndex = null;
- return;
- }
- let nextIndex;
- if (this.selectedMatchIndex === null) {
- nextIndex = 0;
- }
- else {
- nextIndex = this.selectedMatchIndex + indexChange;
- }
- //modulo of negative value to be able to cycle in both directions with previous and next
- nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;
- this.selectedMatchIndex = nextIndex;
- this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);
- for (let index = 0; index < this.searchMatches.length; index++) {
- this.searchMatches[index].selected = index === this.selectedMatchIndex;
- }
- }
- clearSearch() {
- this.toSearch = "";
- this.searchMatches = [];
- this.selectedMatchIndex = null;
- this.currentSearchRegex = null;
- this.searchOptions = {
- matchCase: false,
- exactMatch: false,
- searchFormulas: false,
- };
- }
- // ---------------------------------------------------------------------------
- // Replace
- // ---------------------------------------------------------------------------
- /**
- * Replace the value of the currently selected match
- */
- replace(replaceWith) {
- if (this.selectedMatchIndex === null || !this.currentSearchRegex) {
- return;
- }
- const matches = this.searchMatches;
- const selectedMatch = matches[this.selectedMatchIndex];
- const sheetId = this.getters.getActiveSheetId();
- const cell = this.getters.getCell({ sheetId, ...selectedMatch });
- if ((cell === null || cell === void 0 ? void 0 : cell.isFormula) && !this.searchOptions.searchFormulas) {
- this.selectNextCell(Direction.next);
- }
- else {
- const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + "g");
- const toReplace = this.getSearchableString({
- sheetId,
- col: selectedMatch.col,
- row: selectedMatch.row,
- });
- const newContent = toReplace.replace(replaceRegex, replaceWith);
- this.dispatch("UPDATE_CELL", {
- sheetId: this.getters.getActiveSheetId(),
- col: selectedMatch.col,
- row: selectedMatch.row,
- content: newContent,
- });
- this.searchMatches.splice(this.selectedMatchIndex, 1);
- this.selectNextCell(Direction.current);
- }
- }
- /**
- * Apply the replace function to all the matches one time.
- */
- replaceAll(replaceWith) {
- const matchCount = this.searchMatches.length;
- for (let i = 0; i < matchCount; i++) {
- this.replace(replaceWith);
- }
- }
- getSearchableString(position) {
- const cell = this.getters.getCell(position);
- if (this.searchOptions.searchFormulas && (cell === null || cell === void 0 ? void 0 : cell.isFormula)) {
- return cell.content;
- }
- return this.getters.getEvaluatedCell(position).formattedValue;
- }
- // ---------------------------------------------------------------------------
- // Grid rendering
- // ---------------------------------------------------------------------------
- drawGrid(renderingContext) {
- const { ctx } = renderingContext;
- const sheetId = this.getters.getActiveSheetId();
- for (const match of this.searchMatches) {
- const merge = this.getters.getMerge({ sheetId, col: match.col, row: match.row });
- const left = merge ? merge.left : match.col;
- const right = merge ? merge.right : match.col;
- const top = merge ? merge.top : match.row;
- const bottom = merge ? merge.bottom : match.row;
- const { x, y, width, height } = this.getters.getVisibleRect({ top, left, right, bottom });
- if (width > 0 && height > 0) {
- ctx.fillStyle = BACKGROUND_COLOR;
- ctx.fillRect(x, y, width, height);
- if (match.selected) {
- ctx.strokeStyle = BORDER_COLOR;
- ctx.strokeRect(x, y, width, height);
- }
- }
- }
- }
- }
- FindAndReplacePlugin.layers = [3 /* LAYERS.Search */];
- FindAndReplacePlugin.getters = ["getSearchMatches", "getCurrentSelectedMatchIndex"];
-
- class FormatPlugin extends UIPlugin {
- // ---------------------------------------------------------------------------
- // Command Handling
- // ---------------------------------------------------------------------------
- handle(cmd) {
- switch (cmd.type) {
- case "SET_DECIMAL":
- this.setDecimal(cmd.sheetId, cmd.target, cmd.step);
- break;
- }
- }
- /**
- * This function allows to adjust the quantity of decimal places after a decimal
- * point on cells containing number value. It does this by changing the cells
- * format. Values aren't modified.
- *
- * The change of the decimal quantity is done one by one, the sign of the step
- * variable indicates whether we are increasing or decreasing.
- *
- * If several cells are in the zone, each cell's format will be individually
- * evaluated and updated with the number type.
- */
- setDecimal(sheetId, zones, step) {
- // Find the each cell with a number value and get the format
- for (const zone of zones) {
- for (const position of positions(zone)) {
- const numberFormat = this.getCellNumberFormat({ sheetId, ...position });
- if (numberFormat !== undefined) {
- // Depending on the step sign, increase or decrease the decimal representation
- // of the format
- const newFormat = changeDecimalPlaces(numberFormat, step);
- // Apply the new format on the whole zone
- this.dispatch("SET_FORMATTING", {
- sheetId,
- target: [positionToZone(position)],
- format: newFormat,
- });
- }
- }
- }
- }
- /**
- * Take a range of cells and return the format of the first cell containing a
- * number value. Returns a default format if the cell hasn't format. Returns
- * undefined if no number value in the range.
- */
- getCellNumberFormat(position) {
- var _a;
- for (const pos of [position]) {
- const cell = this.getters.getEvaluatedCell(pos);
- if (cell.type === CellValueType.number &&
- !((_a = cell.format) === null || _a === void 0 ? void 0 : _a.match(DATETIME_FORMAT)) // reject dates
- ) {
- return cell.format || createDefaultFormat(cell.value);
- }
- }
- return undefined;
- }
- }
-
- class HeaderVisibilityUIPlugin extends UIPlugin {
- isRowHidden(sheetId, index) {
- return (this.getters.isRowHiddenByUser(sheetId, index) || this.getters.isRowFiltered(sheetId, index));
- }
- isColHidden(sheetId, index) {
- return this.getters.isColHiddenByUser(sheetId, index);
- }
- isHeaderHidden(sheetId, dimension, index) {
- return dimension === "COL"
- ? this.isColHidden(sheetId, index)
- : this.isRowHidden(sheetId, index);
- }
- getNextVisibleCellPosition({ sheetId, col, row }) {
- return {
- sheetId,
- col: this.findVisibleHeader(sheetId, "COL", col, this.getters.getNumberCols(sheetId) - 1),
- row: this.findVisibleHeader(sheetId, "ROW", row, this.getters.getNumberRows(sheetId) - 1),
- };
- }
- /**
- * Find the first visible header in the range [`from` => `to`].
- *
- * Both `from` and `to` are inclusive.
- */
- findVisibleHeader(sheetId, dimension, from, to) {
- if (from <= to) {
- for (let i = from; i <= to; i++) {
- if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
- !this.isHeaderHidden(sheetId, dimension, i)) {
- return i;
- }
- }
- }
- if (from > to) {
- for (let i = from; i >= to; i--) {
- if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
- !this.isHeaderHidden(sheetId, dimension, i)) {
- return i;
- }
- }
- }
- return undefined;
- }
- findLastVisibleColRowIndex(sheetId, dimension, { last, first }) {
- const lastVisibleIndex = range(last, first, -1).find((index) => !this.isHeaderHidden(sheetId, dimension, index));
- return lastVisibleIndex || first;
- }
- findFirstVisibleColRowIndex(sheetId, dimension) {
- const numberOfHeaders = this.getters.getNumberHeaders(sheetId, dimension);
- for (let i = 0; i < numberOfHeaders - 1; i++) {
- if (dimension === "COL" && !this.isColHidden(sheetId, i)) {
- return i;
- }
- if (dimension === "ROW" && !this.isRowHidden(sheetId, i)) {
- return i;
- }
- }
- return undefined;
- }
- exportForExcel(data) {
- for (const sheetData of data.sheets) {
- for (const [row, rowData] of Object.entries(sheetData.rows)) {
- const isHidden = this.isRowHidden(sheetData.id, Number(row));
- rowData.isHidden = isHidden;
- }
- }
- }
- }
- HeaderVisibilityUIPlugin.getters = [
- "getNextVisibleCellPosition",
- "findVisibleHeader",
- "findLastVisibleColRowIndex",
- "findFirstVisibleColRowIndex",
- "isRowHidden",
- "isColHidden",
- "isHeaderHidden",
- ];
-
- /**
- * HighlightPlugin
- */
- class HighlightPlugin extends UIPlugin {
- // ---------------------------------------------------------------------------
- // Getters
- // ---------------------------------------------------------------------------
- getHighlights() {
- return this.prepareHighlights(this.getters.getComposerHighlights().concat(this.getters.getSelectionInputHighlights()));
- }
- // ---------------------------------------------------------------------------
- // Other
- // ---------------------------------------------------------------------------
- prepareHighlights(highlights) {
- return highlights
- .filter((x) => x.zone.top >= 0 &&
- x.zone.left >= 0 &&
- x.zone.bottom < this.getters.getNumberRows(x.sheetId) &&
- x.zone.right < this.getters.getNumberCols(x.sheetId))
- .map((highlight) => {
- const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
- const zone = numberOfRows * numberOfCols === 1
- ? this.getters.expandZone(highlight.sheetId, highlight.zone)
- : highlight.zone;
- return {
- ...highlight,
- zone,
- };
- });
- }
- // ---------------------------------------------------------------------------
- // Grid rendering
- // ---------------------------------------------------------------------------
- drawGrid(renderingContext) {
- // rendering selection highlights
- const { ctx, thinLineWidth } = renderingContext;
- const sheetId = this.getters.getActiveSheetId();
- const lineWidth = 3 * thinLineWidth;
- ctx.lineWidth = lineWidth;
- /**
- * We only need to draw the highlights of the current sheet.
- *
- * Note that there can be several times the same highlight in 'this.highlights'.
- * In order to avoid superposing the same color layer and modifying the final
- * opacity, we filter highlights to remove duplicates.
- */
- const highlights = this.getHighlights();
- for (let h of highlights.filter((highlight, index) =>
- // For every highlight in the sheet, deduplicated by zone
- highlights.findIndex((h) => isEqual(h.zone, highlight.zone) && h.sheetId === sheetId) ===
- index)) {
- const { x, y, width, height } = this.getters.getVisibleRect(h.zone);
- if (width > 0 && height > 0) {
- ctx.strokeStyle = h.color;
- ctx.strokeRect(x + lineWidth / 2, y + lineWidth / 2, width - lineWidth, height - lineWidth);
- ctx.globalCompositeOperation = "source-over";
- ctx.fillStyle = h.color + "20";
- ctx.fillRect(x + lineWidth, y + lineWidth, width - 2 * lineWidth, height - 2 * lineWidth);
- }
- }
- }
- }
- HighlightPlugin.layers = [1 /* LAYERS.Highlights */];
- HighlightPlugin.getters = ["getHighlights"];
-
- class RendererPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.boxes = [];
- }
- // ---------------------------------------------------------------------------
- // Getters
- // ---------------------------------------------------------------------------
- /**
- * Returns the size, start and end coordinates of a column relative to the left
- * column of the current viewport
- */
- getColDimensionsInViewport(sheetId, col) {
- const left = Math.min(...this.getters.getSheetViewVisibleCols());
- const start = this.getters.getColRowOffsetInViewport("COL", left, col);
- const size = this.getters.getColSize(sheetId, col);
- const isColHidden = this.getters.isColHidden(sheetId, col);
- return {
- start,
- size: size,
- end: start + (isColHidden ? 0 : size),
- };
- }
- /**
- * Returns the size, start and end coordinates of a row relative to the top row
- * of the current viewport
- */
- getRowDimensionsInViewport(sheetId, row) {
- const top = Math.min(...this.getters.getSheetViewVisibleRows());
- const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
- const size = this.getters.getRowSize(sheetId, row);
- const isRowHidden = this.getters.isRowHidden(sheetId, row);
- return {
- start,
- size: size,
- end: start + (isRowHidden ? 0 : size),
- };
- }
- /**
- * Get the offset of a header (see getColRowOffsetInViewport), adjusted with the header
- * size (HEADER_HEIGHT and HEADER_WIDTH)
- */
- getHeaderOffset(dimension, start, index) {
- let size = this.getters.getColRowOffsetInViewport(dimension, start, index);
- if (!this.getters.isDashboard()) {
- size += dimension === "ROW" ? HEADER_HEIGHT : HEADER_WIDTH;
- }
- return size;
- }
- // ---------------------------------------------------------------------------
- // Grid rendering
- // ---------------------------------------------------------------------------
- drawGrid(renderingContext, layer) {
- switch (layer) {
- case 0 /* LAYERS.Background */:
- this.boxes = this.getGridBoxes();
- this.drawBackground(renderingContext);
- this.drawOverflowingCellBackground(renderingContext);
- this.drawCellBackground(renderingContext);
- this.drawBorders(renderingContext);
- this.drawTexts(renderingContext);
- this.drawIcon(renderingContext);
- this.drawFrozenPanes(renderingContext);
- break;
- case 7 /* LAYERS.Headers */:
- if (!this.getters.isDashboard()) {
- this.drawHeaders(renderingContext);
- this.drawFrozenPanesHeaders(renderingContext);
- }
- break;
- }
- }
- drawBackground(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
- // white background
- ctx.fillStyle = "#ffffff";
- ctx.fillRect(0, 0, width + CANVAS_SHIFT, height + CANVAS_SHIFT);
- const areGridLinesVisible = !this.getters.isDashboard() &&
- this.getters.getGridLinesVisibility(this.getters.getActiveSheetId());
- const inset = areGridLinesVisible ? 0.1 * thinLineWidth : 0;
- if (areGridLinesVisible) {
- for (const box of this.boxes) {
- ctx.strokeStyle = CELL_BORDER_COLOR;
- ctx.lineWidth = thinLineWidth;
- ctx.strokeRect(box.x + inset, box.y + inset, box.width - 2 * inset, box.height - 2 * inset);
- }
- }
- }
- drawCellBackground(renderingContext) {
- const { ctx } = renderingContext;
- for (const box of this.boxes) {
- let style = box.style;
- if (style.fillColor && style.fillColor !== "#ffffff") {
- ctx.fillStyle = style.fillColor || "#ffffff";
- ctx.fillRect(box.x, box.y, box.width, box.height);
- }
- if (box.error) {
- ctx.fillStyle = "red";
- ctx.beginPath();
- ctx.moveTo(box.x + box.width - 5, box.y);
- ctx.lineTo(box.x + box.width, box.y);
- ctx.lineTo(box.x + box.width, box.y + 5);
- ctx.fill();
- }
- }
- }
- drawOverflowingCellBackground(renderingContext) {
- var _a, _b;
- const { ctx, thinLineWidth } = renderingContext;
- for (const box of this.boxes) {
- if (box.content && box.isOverflow) {
- const align = box.content.align || "left";
- let x;
- let width;
- const y = box.y + thinLineWidth / 2;
- const height = box.height - thinLineWidth;
- const clipWidth = Math.min(((_a = box.clipRect) === null || _a === void 0 ? void 0 : _a.width) || Infinity, box.content.width);
- if (align === "left") {
- x = box.x + thinLineWidth / 2;
- width = clipWidth - 2 * thinLineWidth;
- }
- else if (align === "right") {
- x = box.x + box.width - thinLineWidth / 2;
- width = -clipWidth + 2 * thinLineWidth;
- }
- else {
- x =
- (((_b = box.clipRect) === null || _b === void 0 ? void 0 : _b.x) || box.x + box.width / 2 - box.content.width / 2) + thinLineWidth / 2;
- width = clipWidth - 2 * thinLineWidth;
- }
- ctx.fillStyle = "#ffffff";
- ctx.fillRect(x, y, width, height);
- }
- }
- }
- drawBorders(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- for (let box of this.boxes) {
- const border = box.border;
- if (border) {
- const { x, y, width, height } = box;
- if (border.left) {
- drawBorder(border.left, x, y, x, y + height);
- }
- if (border.top) {
- drawBorder(border.top, x, y, x + width, y);
- }
- if (border.right) {
- drawBorder(border.right, x + width, y, x + width, y + height);
- }
- if (border.bottom) {
- drawBorder(border.bottom, x, y + height, x + width, y + height);
- }
- }
- }
- function drawBorder([style, color], x1, y1, x2, y2) {
- ctx.strokeStyle = color;
- ctx.lineWidth = (style === "thin" ? 2 : 3) * thinLineWidth;
- ctx.beginPath();
- ctx.moveTo(x1, y1);
- ctx.lineTo(x2, y2);
- ctx.stroke();
- }
- }
- drawTexts(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- ctx.textBaseline = "top";
- let currentFont;
- for (let box of this.boxes) {
- if (box.content) {
- const style = box.style || {};
- const align = box.content.align || "left";
- // compute font and textColor
- const font = computeTextFont(style);
- if (font !== currentFont) {
- currentFont = font;
- ctx.font = font;
- }
- ctx.fillStyle = style.textColor || "#000";
- // compute horizontal align start point parameter
- let x = box.x;
- if (align === "left") {
- x += MIN_CELL_TEXT_MARGIN + (box.image ? box.image.size + MIN_CF_ICON_MARGIN : 0);
- }
- else if (align === "right") {
- x +=
- box.width -
- MIN_CELL_TEXT_MARGIN -
- (box.isFilterHeader ? ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0);
- }
- else {
- x += box.width / 2;
- }
- // horizontal align text direction
- ctx.textAlign = align;
- // clip rect if needed
- if (box.clipRect) {
- ctx.save();
- ctx.beginPath();
- const { x, y, width, height } = box.clipRect;
- ctx.rect(x, y, width, height);
- ctx.clip();
- }
- // compute vertical align start point parameter:
- const textLineHeight = computeTextFontSizeInPixels(style);
- const numberOfLines = box.content.textLines.length;
- let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
- // use the horizontal and the vertical start points to:
- // fill text / fill strikethrough / fill underline
- for (let brokenLine of box.content.textLines) {
- ctx.fillText(brokenLine, Math.round(x), Math.round(y));
- 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, y + textLineHeight / 2, lineWidth, 2.6 * thinLineWidth);
- }
- if (style.underline) {
- ctx.fillRect(_x, y + textLineHeight + 1, lineWidth, 1.3 * thinLineWidth);
- }
- }
- y += MIN_CELL_TEXT_MARGIN + textLineHeight;
- }
- if (box.clipRect) {
- ctx.restore();
- }
- }
- }
- }
- drawIcon(renderingContext) {
- const { ctx } = renderingContext;
- for (const box of this.boxes) {
- if (box.image) {
- const icon = box.image.image;
- if (box.image.clipIcon) {
- ctx.save();
- ctx.beginPath();
- const { x, y, width, height } = box.image.clipIcon;
- ctx.rect(x, y, width, height);
- ctx.clip();
- }
- const iconSize = box.image.size;
- const y = this.computeTextYCoordinate(box, iconSize);
- ctx.drawImage(icon, box.x + MIN_CF_ICON_MARGIN, y, iconSize, iconSize);
- if (box.image.clipIcon) {
- ctx.restore();
- }
- }
- }
- }
- /** Compute the vertical start point from which a text line should be draw.
- *
- * Note that in case the cell does not have enough spaces to display its text lines,
- * (wrapping cell case) then the vertical align should be at the top.
- * */
- computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {
- const y = box.y + 1;
- const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
- const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
- const verticalAlign = box.verticalAlign || "middle";
- if (hasEnoughSpaces) {
- if (verticalAlign === "middle") {
- return y + (box.height - textHeight) / 2;
- }
- if (verticalAlign === "bottom") {
- return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;
- }
- }
- return y + MIN_CELL_TEXT_MARGIN;
- }
- drawHeaders(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- const visibleCols = this.getters.getSheetViewVisibleCols();
- const left = visibleCols[0];
- const right = visibleCols[visibleCols.length - 1];
- const visibleRows = this.getters.getSheetViewVisibleRows();
- const top = visibleRows[0];
- const bottom = visibleRows[visibleRows.length - 1];
- const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
- const selection = this.getters.getSelectedZones();
- const selectedCols = getZonesCols(selection);
- const selectedRows = getZonesRows(selection);
- const sheetId = this.getters.getActiveSheetId();
- const numberOfCols = this.getters.getNumberCols(sheetId);
- const numberOfRows = this.getters.getNumberRows(sheetId);
- const activeCols = this.getters.getActiveCols();
- const activeRows = this.getters.getActiveRows();
- ctx.font = `400 ${HEADER_FONT_SIZE}px ${DEFAULT_FONT}`;
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.lineWidth = thinLineWidth;
- ctx.strokeStyle = "#333";
- // Columns headers background
- for (let col = left; col <= right; col++) {
- const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
- const { x, width } = this.getters.getVisibleRect(colZone);
- const colHasFilter = this.getters.doesZonesContainFilter(sheetId, [colZone]);
- const isColActive = activeCols.has(col);
- const isColSelected = selectedCols.has(col);
- if (isColActive) {
- ctx.fillStyle = colHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
- }
- else if (isColSelected) {
- ctx.fillStyle = colHasFilter
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
- : BACKGROUND_HEADER_SELECTED_COLOR;
- }
- else {
- ctx.fillStyle = colHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
- }
- ctx.fillRect(x, 0, width, HEADER_HEIGHT);
- }
- // Rows headers background
- for (let row = top; row <= bottom; row++) {
- const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
- const { y, height } = this.getters.getVisibleRect(rowZone);
- const rowHasFilter = this.getters.doesZonesContainFilter(sheetId, [rowZone]);
- const isRowActive = activeRows.has(row);
- const isRowSelected = selectedRows.has(row);
- if (isRowActive) {
- ctx.fillStyle = rowHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
- }
- else if (isRowSelected) {
- ctx.fillStyle = rowHasFilter
- ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
- : BACKGROUND_HEADER_SELECTED_COLOR;
- }
- else {
- ctx.fillStyle = rowHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
- }
- ctx.fillRect(0, y, HEADER_WIDTH, height);
- }
- // 2 main lines
- ctx.beginPath();
- ctx.moveTo(HEADER_WIDTH, 0);
- ctx.lineTo(HEADER_WIDTH, height);
- ctx.moveTo(0, HEADER_HEIGHT);
- ctx.lineTo(width, HEADER_HEIGHT);
- ctx.strokeStyle = HEADER_BORDER_COLOR;
- ctx.stroke();
- ctx.beginPath();
- // column text + separator
- for (const i of visibleCols) {
- const colSize = this.getters.getColSize(sheetId, i);
- const colName = numberToLetters(i);
- ctx.fillStyle = activeCols.has(i) ? "#fff" : TEXT_HEADER_COLOR;
- let colStart = this.getHeaderOffset("COL", left, i);
- ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);
- ctx.moveTo(colStart + colSize, 0);
- ctx.lineTo(colStart + colSize, HEADER_HEIGHT);
- }
- // row text + separator
- for (const i of visibleRows) {
- const rowSize = this.getters.getRowSize(sheetId, i);
- ctx.fillStyle = activeRows.has(i) ? "#fff" : TEXT_HEADER_COLOR;
- let rowStart = this.getHeaderOffset("ROW", top, i);
- ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
- ctx.moveTo(0, rowStart + rowSize);
- ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);
- }
- ctx.stroke();
- }
- drawFrozenPanesHeaders(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();
- const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;
- const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;
- ctx.lineWidth = 6 * thinLineWidth;
- ctx.strokeStyle = "#BCBCBC";
- ctx.beginPath();
- if (offsetCorrectionX) {
- ctx.moveTo(widthCorrection + offsetCorrectionX, 0);
- ctx.lineTo(widthCorrection + offsetCorrectionX, heightCorrection);
- }
- if (offsetCorrectionY) {
- ctx.moveTo(0, heightCorrection + offsetCorrectionY);
- ctx.lineTo(widthCorrection, heightCorrection + offsetCorrectionY);
- }
- ctx.stroke();
- }
- drawFrozenPanes(renderingContext) {
- const { ctx, thinLineWidth } = renderingContext;
- const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();
- const visibleCols = this.getters.getSheetViewVisibleCols();
- const left = visibleCols[0];
- const right = visibleCols[visibleCols.length - 1];
- const visibleRows = this.getters.getSheetViewVisibleRows();
- const top = visibleRows[0];
- const bottom = visibleRows[visibleRows.length - 1];
- const viewport = { left, right, top, bottom };
- const rect = this.getters.getVisibleRect(viewport);
- const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;
- const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;
- ctx.lineWidth = 6 * thinLineWidth;
- ctx.strokeStyle = "#DADFE8";
- ctx.beginPath();
- if (offsetCorrectionX) {
- ctx.moveTo(widthCorrection + offsetCorrectionX, heightCorrection);
- ctx.lineTo(widthCorrection + offsetCorrectionX, rect.height + heightCorrection);
- }
- if (offsetCorrectionY) {
- ctx.moveTo(widthCorrection, heightCorrection + offsetCorrectionY);
- ctx.lineTo(rect.width + widthCorrection, heightCorrection + offsetCorrectionY);
- }
- ctx.stroke();
- }
- findNextEmptyCol(base, max, row) {
- const sheetId = this.getters.getActiveSheetId();
- let col = base;
- while (col < max) {
- const position = { sheetId, col: col + 1, row };
- const nextCell = this.getters.getEvaluatedCell(position);
- const nextCellBorder = this.getters.getCellBorderWithFilterBorder(position);
- if (nextCell.type !== CellValueType.empty ||
- this.getters.isInMerge(position) ||
- (nextCellBorder === null || nextCellBorder === void 0 ? void 0 : nextCellBorder.left)) {
- return col;
- }
- col++;
- }
- return col;
- }
- findPreviousEmptyCol(base, min, row) {
- const sheetId = this.getters.getActiveSheetId();
- let col = base;
- while (col > min) {
- const position = { sheetId, col: col - 1, row };
- const previousCell = this.getters.getEvaluatedCell(position);
- const previousCellBorder = this.getters.getCellBorderWithFilterBorder(position);
- if (previousCell.type !== CellValueType.empty ||
- this.getters.isInMerge(position) ||
- (previousCellBorder === null || previousCellBorder === void 0 ? void 0 : previousCellBorder.right)) {
- return col;
- }
- col--;
- }
- return col;
- }
- computeCellAlignment(position, isOverflowing) {
- const cell = this.getters.getCell(position);
- if ((cell === null || cell === void 0 ? void 0 : cell.isFormula) && this.getters.shouldShowFormulas()) {
- return "left";
- }
- const { align } = this.getters.getCellStyle(position);
- const evaluatedCell = this.getters.getEvaluatedCell(position);
- if (isOverflowing && evaluatedCell.type === CellValueType.number) {
- return align !== "center" ? "left" : align;
- }
- return align || evaluatedCell.defaultAlign;
- }
- createZoneBox(sheetId, zone, viewport) {
- const { left, right } = viewport;
- const col = zone.left;
- const row = zone.top;
- const position = { sheetId, col, row };
- const cell = this.getters.getEvaluatedCell(position);
- const showFormula = this.getters.shouldShowFormulas();
- const { x, y, width, height } = this.getters.getVisibleRect(zone);
- const { verticalAlign } = this.getters.getCellStyle(position);
- const box = {
- x,
- y,
- width,
- height,
- border: this.getters.getCellBorderWithFilterBorder(position) || undefined,
- style: this.getters.getCellComputedStyle(position),
- verticalAlign,
- };
- if (cell.type === CellValueType.empty) {
- return box;
- }
- /** Icon CF */
- const cfIcon = this.getters.getConditionalIcon(position);
- const fontSizePX = computeTextFontSizeInPixels(box.style);
- const iconBoxWidth = cfIcon ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
- if (cfIcon) {
- box.image = {
- type: "icon",
- size: fontSizePX,
- clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },
- image: ICONS[cfIcon].img,
- };
- }
- /** Filter Header */
- box.isFilterHeader = this.getters.isFilterHeader(position);
- const headerIconWidth = box.isFilterHeader ? FILTER_ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0;
- /** Content */
- const style = this.getters.getCellComputedStyle(position);
- const wrapping = style.wrapping || "overflow";
- const maxWidth = wrapping === "wrap" && !showFormula ? width - 2 * MIN_CELL_TEXT_MARGIN : undefined;
- const multiLineText = this.getters.getCellMultiLineText(position, maxWidth);
- const textWidth = Math.max(...multiLineText.map((line) => this.getters.getTextWidth(line, style) + MIN_CELL_TEXT_MARGIN));
- const contentWidth = iconBoxWidth + textWidth + headerIconWidth;
- const align = this.computeCellAlignment(position, contentWidth > width);
- box.content = {
- textLines: multiLineText,
- width: wrapping === "overflow" ? textWidth : width,
- align,
- };
- /** Error */
- if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {
- box.error = cell.error.message;
- }
- /** ClipRect */
- const isOverflowing = contentWidth > width || fontSizePX > height;
- if (cfIcon || box.isFilterHeader) {
- box.clipRect = {
- x: box.x + iconBoxWidth,
- y: box.y,
- width: Math.max(0, width - iconBoxWidth - headerIconWidth),
- height,
- };
- }
- else if (isOverflowing && wrapping === "overflow") {
- let nextColIndex, previousColIndex;
- const isCellInMerge = this.getters.isInMerge(position);
- if (isCellInMerge) {
- // Always clip merges
- nextColIndex = this.getters.getMerge(position).right;
- previousColIndex = col;
- }
- else {
- nextColIndex = this.findNextEmptyCol(col, right, row);
- previousColIndex = this.findPreviousEmptyCol(col, left, row);
- box.isOverflow = true;
- }
- switch (align) {
- case "left": {
- const emptyZoneOnTheLeft = positionToZone({ col: nextColIndex, row });
- const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheLeft));
- if (width < contentWidth || fontSizePX > height || multiLineText.length > 1) {
- box.clipRect = { x, y, width, height };
- }
- break;
- }
- case "right": {
- const emptyZoneOnTheRight = positionToZone({ col: previousColIndex, row });
- const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheRight));
- if (width < contentWidth || fontSizePX > height || multiLineText.length > 1) {
- box.clipRect = { x, y, width, height };
- }
- break;
- }
- case "center": {
- const emptyZone = {
- ...zone,
- left: previousColIndex,
- right: nextColIndex,
- };
- const { x, y, height, width } = this.getters.getVisibleRect(emptyZone);
- const halfContentWidth = contentWidth / 2;
- const boxMiddle = box.x + box.width / 2;
- if (x + width < boxMiddle + halfContentWidth ||
- x > boxMiddle - halfContentWidth ||
- fontSizePX > height ||
- multiLineText.length > 1) {
- const clipX = x > boxMiddle - halfContentWidth ? x : boxMiddle - halfContentWidth;
- const clipWidth = x + width - clipX;
- box.clipRect = { x: clipX, y, width: clipWidth, height };
- }
- break;
- }
- }
- }
- else if (wrapping === "clip" || wrapping === "wrap" || multiLineText.length > 1) {
- box.clipRect = {
- x: box.x,
- y: box.y,
- width,
- height,
- };
- }
- return box;
- }
- getGridBoxes() {
- const boxes = [];
- const visibleCols = this.getters.getSheetViewVisibleCols();
- const left = visibleCols[0];
- const right = visibleCols[visibleCols.length - 1];
- const visibleRows = this.getters.getSheetViewVisibleRows();
- const top = visibleRows[0];
- const bottom = visibleRows[visibleRows.length - 1];
- const viewport = { left, right, top, bottom };
- const sheetId = this.getters.getActiveSheetId();
- for (const row of visibleRows) {
- for (const col of visibleCols) {
- const position = { sheetId, col, row };
- if (this.getters.isInMerge(position)) {
- continue;
- }
- boxes.push(this.createZoneBox(sheetId, positionToZone(position), viewport));
- }
- }
- for (const merge of this.getters.getMerges(sheetId)) {
- if (this.getters.isMergeHidden(sheetId, merge)) {
- continue;
- }
- if (overlap(merge, viewport)) {
- const box = this.createZoneBox(sheetId, merge, viewport);
- const borderBottomRight = this.getters.getCellBorder({
- sheetId,
- col: merge.right,
- row: merge.bottom,
- });
- box.border = {
- ...box.border,
- bottom: borderBottomRight ? borderBottomRight.bottom : undefined,
- right: borderBottomRight ? borderBottomRight.right : undefined,
- };
- box.isMerge = true;
- boxes.push(box);
- }
- }
- return boxes;
- }
- }
- RendererPlugin.layers = [0 /* LAYERS.Background */, 7 /* LAYERS.Headers */];
- RendererPlugin.getters = ["getColDimensionsInViewport", "getRowDimensionsInViewport"];
-
- /**
- * Selection input Plugin
- *
- * The SelectionInput component input and output are both arrays of strings, but
- * it requires an intermediary internal state to work.
- * This plugin handles this internal state.
- */
- class SelectionInputPlugin extends UIPlugin {
- constructor(config, initialRanges, inputHasSingleRange) {
- super(config);
- this.inputHasSingleRange = inputHasSingleRange;
- this.ranges = [];
- this.focusedRangeIndex = null;
- this.willAddNewRange = false;
- this.insertNewRange(0, initialRanges);
- this.activeSheet = this.getters.getActiveSheetId();
- if (this.ranges.length === 0) {
- this.insertNewRange(this.ranges.length, [""]);
- this.focusLast();
- }
- }
- // ---------------------------------------------------------------------------
- // Command Handling
- // ---------------------------------------------------------------------------
- allowDispatch(cmd) {
- switch (cmd.type) {
- case "ADD_EMPTY_RANGE":
- if (this.inputHasSingleRange && this.ranges.length === 1) {
- return 30 /* CommandResult.MaximumRangesReached */;
- }
- break;
- }
- return 0 /* CommandResult.Success */;
- }
- handleEvent(event) {
- const xc = zoneToXc(event.anchor.zone);
- const inputSheetId = this.activeSheet;
- const sheetId = this.getters.getActiveSheetId();
- const sheetName = this.getters.getSheetName(sheetId);
- this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
- }
- handle(cmd) {
- switch (cmd.type) {
- case "UNFOCUS_SELECTION_INPUT":
- this.unfocus();
- break;
- case "FOCUS_RANGE":
- this.focus(this.getIndex(cmd.rangeId));
- break;
- case "CHANGE_RANGE": {
- const index = this.getIndex(cmd.rangeId);
- if (index !== null && this.focusedRangeIndex !== index) {
- this.focus(index);
- }
- if (index !== null) {
- const valueWithoutLeadingComma = cmd.value.replace(/^,+/, "");
- const values = valueWithoutLeadingComma.split(",").map((reference) => reference.trim());
- this.setRange(index, values);
- }
- break;
- }
- case "ADD_EMPTY_RANGE":
- this.insertNewRange(this.ranges.length, [""]);
- this.focusLast();
- break;
- case "REMOVE_RANGE":
- const index = this.getIndex(cmd.rangeId);
- if (index !== null) {
- this.removeRange(index);
- }
- break;
- case "STOP_SELECTION_INPUT":
- this.willAddNewRange = false;
- break;
- case "PREPARE_SELECTION_INPUT_EXPANSION": {
- const index = this.focusedRangeIndex;
- if (index !== null && !this.inputHasSingleRange) {
- this.willAddNewRange = this.ranges[index].xc.trim() !== "";
- }
- break;
- }
- case "ACTIVATE_SHEET": {
- if (cmd.sheetIdFrom !== cmd.sheetIdTo) {
- const { col, row } = this.getters.getNextVisibleCellPosition({
- sheetId: cmd.sheetIdTo,
- col: 0,
- row: 0,
- });
- const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));
- this.selection.resetAnchor(this, { cell: { col, row }, zone });
- }
- }
- }
- }
- // ---------------------------------------------------------------------------
- // Getters || only callable by the parent
- // ---------------------------------------------------------------------------
- getSelectionInputValue() {
- return this.cleanInputs(this.ranges.map((range) => {
- return range.xc ? range.xc : "";
- }));
- }
- getSelectionInputHighlights() {
- return this.ranges.map((input) => this.inputToHighlights(input)).flat();
- }
- // ---------------------------------------------------------------------------
- // Other
- // ---------------------------------------------------------------------------
- /**
- * Focus a given range or remove the focus.
- */
- focus(index) {
- this.focusedRangeIndex = index;
- }
- focusLast() {
- this.focus(this.ranges.length - 1);
- }
- unfocus() {
- this.focusedRangeIndex = null;
- }
- add(newRanges) {
- if (this.focusedRangeIndex === null || newRanges.length === 0) {
- return;
- }
- if (this.willAddNewRange) {
- this.insertNewRange(this.ranges.length, newRanges);
- this.focusLast();
- this.willAddNewRange = false;
- }
- else {
- this.setRange(this.focusedRangeIndex, newRanges);
- }
- }
- setContent(index, xc) {
- this.ranges[index] = {
- ...this.ranges[index],
- xc,
- };
- }
- /**
- * Insert new inputs after the given index.
- */
- insertNewRange(index, values) {
- const currentMaxId = Math.max(0, ...this.ranges.map((range) => Number(range.id)));
- this.ranges.splice(index, 0, ...values.map((xc, i) => ({
- xc,
- id: currentMaxId + i + 1,
- color: colors$1[(currentMaxId + i) % colors$1.length],
- })));
- }
- /**
- * Set a new value in a given range input. If more than one value is provided,
- * new inputs will be added.
- */
- setRange(index, values) {
- const [, ...additionalValues] = values;
- this.setContent(index, values[0]);
- this.insertNewRange(index + 1, additionalValues);
- // focus the last newly added range
- if (additionalValues.length) {
- this.focus(index + additionalValues.length);
- }
- }
- removeRange(index) {
- this.ranges.splice(index, 1);
- if (this.focusedRangeIndex !== null) {
- this.focusLast();
- }
- }
- /**
- * Convert highlights input format to the command format.
- * The first xc in the input range will keep its color.
- * Invalid ranges and ranges from other sheets than the active sheets
- * are ignored.
- */
- inputToHighlights({ xc, color }) {
- const XCs = this.cleanInputs([xc])
- .filter((range) => this.getters.isRangeValid(range))
- .filter((reference) => this.shouldBeHighlighted(this.activeSheet, reference));
- return XCs.map((xc) => {
- const { sheetName } = splitReference(xc);
- return {
- zone: this.getters.getRangeFromSheetXC(this.activeSheet, xc).zone,
- sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.activeSheet,
- color,
- };
- });
- }
- cleanInputs(ranges) {
- return ranges
- .map((xc) => xc.split(","))
- .flat()
- .map((xc) => xc.trim())
- .filter((xc) => xc !== "");
- }
- /**
- * Check if a cell or range reference should be highlighted.
- * It should be highlighted if it references the current active sheet.
- * Note that if no sheet name is given in the reference ("A1"), it refers to the
- * active sheet when the selection input was enabled which might be different from
- * the current active sheet.
- */
- shouldBeHighlighted(inputSheetId, reference) {
- const { sheetName } = splitReference(reference);
- const sheetId = this.getters.getSheetIdByName(sheetName);
- const activeSheetId = this.getters.getActiveSheet().id;
- const valid = this.getters.isRangeValid(reference);
- return (valid &&
- (sheetId === activeSheetId || (sheetId === undefined && activeSheetId === inputSheetId)));
- }
- /**
- * Return the index of a range given its id
- * or `null` if the range is not found.
- */
- getIndex(rangeId) {
- const index = this.ranges.findIndex((range) => range.id === rangeId);
- return index >= 0 ? index : null;
- }
- }
- SelectionInputPlugin.layers = [1 /* LAYERS.Highlights */];
- SelectionInputPlugin.getters = [];
-
- /**
- * Selection input Plugin
- *
- * The SelectionInput component input and output are both arrays of strings, but
- * it requires an intermediary internal state to work.
- * This plugin handles this internal state.
- */
- class SelectionInputsManagerPlugin extends UIPlugin {
- constructor(config) {
- super(config);
- this.config = config;
- this.inputs = {};
- this.focusedInputId = null;
- }
- get currentInput() {
- return this.focusedInputId ? this.inputs[this.focusedInputId] : null;
- }
- // ---------------------------------------------------------------------------
- // Command Handling
- // ---------------------------------------------------------------------------
- allowDispatch(cmd) {
- var _a, _b;
- switch (cmd.type) {
- case "FOCUS_RANGE":
- const index = (_a = this.currentInput) === null || _a === void 0 ? void 0 : _a.getIndex(cmd.rangeId);
- if (this.focusedInputId === cmd.id && ((_b = this.currentInput) === null || _b === void 0 ? void 0 : _b.focusedRangeIndex) === index) {
- return 29 /* CommandResult.InputAlreadyFocused */;
- }
- break;
- }
- if (this.currentInput) {
- return this.currentInput.allowDispatch(cmd);
- }
- return 0 /* CommandResult.Success */;
- }
- handle(cmd) {
- var _a;
- switch (cmd.type) {
- case "ENABLE_NEW_SELECTION_INPUT":
- this.initInput(cmd.id, cmd.initialRanges || [], cmd.hasSingleRange);
- break;
- case "DISABLE_SELECTION_INPUT":
- if (this.focusedInputId === cmd.id) {
- this.unfocus();
- }
- delete this.inputs[cmd.id];
- break;
- case "UNFOCUS_SELECTION_INPUT":
- this.unfocus();
- break;
- case "ADD_EMPTY_RANGE":
- case "REMOVE_RANGE":
- if (cmd.id !== this.focusedInputId) {
- const input = this.inputs[cmd.id];
- this.selection.capture(input, { cell: { col: 0, row: 0 }, zone: positionToZone({ col: 0, row: 0 }) }, { handleEvent: input.handleEvent.bind(input) });
- this.focusedInputId = cmd.id;
- }
- break;
- case "FOCUS_RANGE":
- case "CHANGE_RANGE":
- if (cmd.id !== this.focusedInputId) {
- const input = this.inputs[cmd.id];
- const range = input.ranges.find((range) => range.id === cmd.rangeId);
- const sheetId = this.getters.getActiveSheetId();
- const zone = this.getters.getRangeFromSheetXC(sheetId, (range === null || range === void 0 ? void 0 : range.xc) || "A1").zone;
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
- this.focusedInputId = cmd.id;
- }
- break;
- }
- (_a = this.currentInput) === null || _a === void 0 ? void 0 : _a.handle(cmd);
- }
- // ---------------------------------------------------------------------------
- // Getters
- // ---------------------------------------------------------------------------
- /**
- * Return a list of all valid XCs.
- * e.g. ["A1", "Sheet2!B3", "E12"]
- */
- getSelectionInput(id) {
- if (!this.inputs[id]) {
- return [];
- }
- return this.inputs[id].ranges.map((input, index) => Object.assign({}, input, {
- color: this.focusedInputId === id &&
- this.inputs[id].focusedRangeIndex !== null &&
- this.isRangeValid(input.xc)
- ? input.color
- : null,
- isFocused: this.focusedInputId === id && this.inputs[id].focusedRangeIndex === index,
- }));
- }
- isRangeValid(reference) {
- if (!reference) {
- return false;
- }
- const { xc, sheetName } = splitReference(reference);
- return (xc.match(rangeReference) !== null &&
- (!sheetName || this.getters.getSheetIdByName(sheetName) !== undefined));
- }
- getSelectionInputValue(id) {
- return this.inputs[id].getSelectionInputValue();
- }
- getSelectionInputHighlights() {
- if (!this.focusedInputId) {
- return [];
- }
- return this.inputs[this.focusedInputId].getSelectionInputHighlights();
- }
- // ---------------------------------------------------------------------------
- // Other
- // ---------------------------------------------------------------------------
- initInput(id, initialRanges, inputHasSingleRange = false) {
- this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
- if (initialRanges.length === 0) {
- const input = this.inputs[id];
- const anchor = {
- zone: positionToZone({ col: 0, row: 0 }),
- cell: { col: 0, row: 0 },
- };
- this.selection.capture(input, anchor, { handleEvent: input.handleEvent.bind(input) });
- this.focusedInputId = id;
- }
- }
- unfocus() {
- this.selection.release(this.currentInput);
- this.focusedInputId = null;
- }
- }
- SelectionInputsManagerPlugin.layers = [1 /* LAYERS.Highlights */];
- SelectionInputsManagerPlugin.getters = [
- "getSelectionInput",
- "getSelectionInputValue",
- "isRangeValid",
- "getSelectionInputHighlights",
- ];
/**
* This is a generic event bus based on the Owl event bus.
* This bus however ensures type safety across events and subscription callbacks.
*/
class EventBus {
- constructor() {
- this.subscriptions = {};
- }
+ subscriptions = {};
/**
* Add a listener for the 'eventType' events.
*
@@ -37078,11 +35898,10 @@
};
}
function createSheetTransformation(cmd, executed) {
- var _a;
if (cmd.name === executed.name) {
return {
...cmd,
- name: ((_a = cmd.name) === null || _a === void 0 ? void 0 : _a.match(/\d+/))
+ name: cmd.name?.match(/\d+/)
? cmd.name.replace(/\d+/, (n) => (parseInt(n) + 1).toString())
: `${cmd.name}~`,
position: cmd.position + 1,
@@ -37353,6 +36172,11 @@
}
class Revision {
+ rootCommand;
+ id;
+ clientId;
+ _commands = [];
+ _changes = [];
/**
* A revision represents a whole client action (Create a sheet, merge a Zone, Undo, ...).
* A revision contains the following information:
@@ -37364,8 +36188,6 @@
*/
constructor(id, clientId, commands, rootCommand, changes) {
this.rootCommand = rootCommand;
- this._commands = [];
- this._changes = [];
this.id = id;
this.clientId = clientId;
this._commands = [...commands];
@@ -37385,6 +36207,28 @@
class ClientDisconnectedError extends Error {
}
class Session extends EventBus {
+ revisions;
+ transportService;
+ serverRevisionId;
+ /**
+ * Positions of the others client.
+ */
+ clients = {};
+ clientId = "local";
+ /**
+ * Id of the server revision
+ */
+ debouncedMove;
+ pendingMessages = [];
+ waitingAck = false;
+ /**
+ * Flag used to block all commands when an undo or redo is triggered, until
+ * it is accepted on the server
+ */
+ waitingUndoRedoAck = false;
+ isReplayingInitialRevisions = false;
+ processedRevisions = new Set();
+ uuidGenerator = new UuidGenerator();
/**
* Manages the collaboration between multiple users on the same spreadsheet.
* It can forward local state changes to other users to ensure they all eventually
@@ -37403,21 +36247,6 @@
this.revisions = revisions;
this.transportService = transportService;
this.serverRevisionId = serverRevisionId;
- /**
- * Positions of the others client.
- */
- this.clients = {};
- this.clientId = "local";
- this.pendingMessages = [];
- this.waitingAck = false;
- /**
- * Flag used to block all commands when an undo or redo is triggered, until
- * it is accepted on the server
- */
- this.waitingUndoRedoAck = false;
- this.isReplayingInitialRevisions = false;
- this.processedRevisions = new Set();
- this.uuidGenerator = new UuidGenerator();
this.debouncedMove = debounce(this._move.bind(this), DEBOUNCE_TIME);
}
canApplyOptimisticUpdate() {
@@ -37535,26 +36364,24 @@
* Get the last local revision whose root command isn't in the given list of ignored commands
* */
getLastLocalNonEmptyRevision(ignoredRootCommands) {
- var _a;
const revisions = this.revisions.getRevertedExecution();
for (const rev of revisions) {
if (rev.rootCommand === "SNAPSHOT")
return undefined;
if (!rev.rootCommand || rev.rootCommand === "REMOTE")
continue;
- if (!ignoredRootCommands.includes((_a = rev.rootCommand) === null || _a === void 0 ? void 0 : _a.type) && rev.commands.length)
+ if (!ignoredRootCommands.includes(rev.rootCommand?.type) && rev.commands.length)
return rev;
}
return undefined;
}
_move(position) {
- var _a;
// this method is debounced and might be called after the client
// left the session.
if (!this.clients[this.clientId])
return;
- const currentPosition = (_a = this.clients[this.clientId]) === null || _a === void 0 ? void 0 : _a.position;
- if ((currentPosition === null || currentPosition === void 0 ? void 0 : currentPosition.col) === position.col &&
+ const currentPosition = this.clients[this.clientId]?.position;
+ if (currentPosition?.col === position.col &&
currentPosition.row === position.row &&
currentPosition.sheetId === position.sheetId) {
return;
@@ -37759,11 +36586,20 @@
"#85144b",
"#001f3f",
];
- class SelectionMultiUserPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.availableColors = new Set(colors);
- this.colors = {};
+ class CollaborativePlugin extends UIPlugin {
+ static getters = [
+ "getClientsToDisplay",
+ "getClient",
+ "getConnectedClients",
+ "isFullySynchronized",
+ ];
+ static layers = [6 /* LAYERS.Selection */];
+ availableColors = new Set(colors);
+ colors = {};
+ session;
+ constructor(config) {
+ super(config);
+ this.session = config.session;
}
isPositionValid(position) {
return (position.row < this.getters.getNumberRows(position.sheetId) &&
@@ -37777,6 +36613,15 @@
this.availableColors.delete(color);
return color;
}
+ getClient() {
+ return this.session.getClient();
+ }
+ getConnectedClients() {
+ return this.session.getConnectedClients();
+ }
+ isFullySynchronized() {
+ return this.session.isFullySynchronized();
+ }
/**
* Get the list of others connected clients which are present in the same sheet
* and with a valid position
@@ -37844,10 +36689,1444 @@
}
}
}
- SelectionMultiUserPlugin.getters = ["getClientsToDisplay"];
- SelectionMultiUserPlugin.layers = [6 /* LAYERS.Selection */];
+
+ const BORDER_COLOR = "#8B008B";
+ const BACKGROUND_COLOR = "#8B008B33";
+ var Direction;
+ (function (Direction) {
+ Direction[Direction["previous"] = -1] = "previous";
+ Direction[Direction["current"] = 0] = "current";
+ Direction[Direction["next"] = 1] = "next";
+ })(Direction || (Direction = {}));
+ /**
+ * Find and Replace Plugin
+ *
+ * This plugin is used in combination with the find_and_replace sidePanel
+ * It is used to 'highlight' cells that match an input string according to
+ * the given searchOptions. The second part of this plugin makes it possible
+ * (again with the find_and_replace sidePanel), to replace the values that match
+ * the search with a new value.
+ */
+ class FindAndReplacePlugin extends UIPlugin {
+ static layers = [3 /* LAYERS.Search */];
+ static getters = ["getSearchMatches", "getCurrentSelectedMatchIndex"];
+ searchMatches = [];
+ selectedMatchIndex = null;
+ currentSearchRegex = null;
+ searchOptions = {
+ matchCase: false,
+ exactMatch: false,
+ searchFormulas: false,
+ };
+ toSearch = "";
+ // ---------------------------------------------------------------------------
+ // Command Handling
+ // ---------------------------------------------------------------------------
+ handle(cmd) {
+ switch (cmd.type) {
+ case "UPDATE_SEARCH":
+ this.updateSearch(cmd.toSearch, cmd.searchOptions);
+ break;
+ case "CLEAR_SEARCH":
+ this.clearSearch();
+ break;
+ case "SELECT_SEARCH_PREVIOUS_MATCH":
+ this.selectNextCell(Direction.previous);
+ break;
+ case "SELECT_SEARCH_NEXT_MATCH":
+ this.selectNextCell(Direction.next);
+ break;
+ case "REPLACE_SEARCH":
+ this.replace(cmd.replaceWith);
+ break;
+ case "REPLACE_ALL_SEARCH":
+ this.replaceAll(cmd.replaceWith);
+ break;
+ case "UNDO":
+ case "REDO":
+ case "REMOVE_COLUMNS_ROWS":
+ case "ADD_COLUMNS_ROWS":
+ this.clearSearch();
+ break;
+ case "ACTIVATE_SHEET":
+ case "REFRESH_SEARCH":
+ this.refreshSearch();
+ break;
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------------
+ getSearchMatches() {
+ return this.searchMatches;
+ }
+ getCurrentSelectedMatchIndex() {
+ return this.selectedMatchIndex;
+ }
+ // ---------------------------------------------------------------------------
+ // Search
+ // ---------------------------------------------------------------------------
+ /**
+ * Will update the current searchOptions and accordingly update the regex.
+ * It will then search for matches using the regex and store them.
+ */
+ updateSearch(toSearch, searchOptions) {
+ this.searchOptions = searchOptions;
+ if (toSearch !== this.toSearch) {
+ this.selectedMatchIndex = null;
+ }
+ this.toSearch = toSearch;
+ this.updateRegex();
+ this.refreshSearch();
+ }
+ /**
+ * refresh the matches according to the current search options
+ */
+ refreshSearch() {
+ const matches = this.findMatches();
+ this.searchMatches = matches;
+ this.selectNextCell(Direction.current);
+ }
+ /**
+ * Updates the regex based on the current searchOptions and
+ * the value toSearch
+ */
+ updateRegex() {
+ let searchValue = escapeRegExp(this.toSearch);
+ const flags = !this.searchOptions.matchCase ? "i" : "";
+ if (this.searchOptions.exactMatch) {
+ searchValue = `^${searchValue}$`;
+ }
+ this.currentSearchRegex = RegExp(searchValue, flags);
+ }
+ /**
+ * Find matches using the current regex
+ */
+ findMatches() {
+ const sheetId = this.getters.getActiveSheetId();
+ const cells = this.getters.getCells(sheetId);
+ const matches = [];
+ if (this.toSearch) {
+ for (const cell of Object.values(cells)) {
+ const { col, row } = this.getters.getCellPosition(cell.id);
+ if (cell &&
+ this.currentSearchRegex &&
+ this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
+ const match = { col, row, selected: false };
+ matches.push(match);
+ }
+ }
+ }
+ return matches.sort(this.sortByRowThenColumn);
+ }
+ sortByRowThenColumn(a, b) {
+ if (a.row === b.row) {
+ return a.col - b.col;
+ }
+ return a.row > b.row ? 1 : -1;
+ }
+ /**
+ * Changes the selected search cell. Given a direction it will
+ * Change the selection to the previous, current or nextCell,
+ * if it exists otherwise it will set the selectedMatchIndex to null.
+ * It will also reset the index to 0 if the search has changed.
+ * It is also used to keep coherence between the selected searchMatch
+ * and selectedMatchIndex.
+ */
+ selectNextCell(indexChange) {
+ const matches = this.searchMatches;
+ if (!matches.length) {
+ this.selectedMatchIndex = null;
+ return;
+ }
+ let nextIndex;
+ if (this.selectedMatchIndex === null) {
+ nextIndex = 0;
+ }
+ else {
+ nextIndex = this.selectedMatchIndex + indexChange;
+ }
+ //modulo of negative value to be able to cycle in both directions with previous and next
+ nextIndex = ((nextIndex % matches.length) + matches.length) % matches.length;
+ this.selectedMatchIndex = nextIndex;
+ this.selection.selectCell(matches[nextIndex].col, matches[nextIndex].row);
+ for (let index = 0; index < this.searchMatches.length; index++) {
+ this.searchMatches[index].selected = index === this.selectedMatchIndex;
+ }
+ }
+ clearSearch() {
+ this.toSearch = "";
+ this.searchMatches = [];
+ this.selectedMatchIndex = null;
+ this.currentSearchRegex = null;
+ this.searchOptions = {
+ matchCase: false,
+ exactMatch: false,
+ searchFormulas: false,
+ };
+ }
+ // ---------------------------------------------------------------------------
+ // Replace
+ // ---------------------------------------------------------------------------
+ /**
+ * Replace the value of the currently selected match
+ */
+ replace(replaceWith) {
+ if (this.selectedMatchIndex === null || !this.currentSearchRegex) {
+ return;
+ }
+ const matches = this.searchMatches;
+ const selectedMatch = matches[this.selectedMatchIndex];
+ const sheetId = this.getters.getActiveSheetId();
+ const cell = this.getters.getCell({ sheetId, ...selectedMatch });
+ if (cell?.isFormula && !this.searchOptions.searchFormulas) {
+ this.selectNextCell(Direction.next);
+ }
+ else {
+ const replaceRegex = new RegExp(this.currentSearchRegex.source, this.currentSearchRegex.flags + "g");
+ const toReplace = this.getSearchableString({
+ sheetId,
+ col: selectedMatch.col,
+ row: selectedMatch.row,
+ });
+ const newContent = toReplace.replace(replaceRegex, replaceWith);
+ this.dispatch("UPDATE_CELL", {
+ sheetId: this.getters.getActiveSheetId(),
+ col: selectedMatch.col,
+ row: selectedMatch.row,
+ content: newContent,
+ });
+ this.searchMatches.splice(this.selectedMatchIndex, 1);
+ this.selectNextCell(Direction.current);
+ }
+ }
+ /**
+ * Apply the replace function to all the matches one time.
+ */
+ replaceAll(replaceWith) {
+ const matchCount = this.searchMatches.length;
+ for (let i = 0; i < matchCount; i++) {
+ this.replace(replaceWith);
+ }
+ }
+ getSearchableString(position) {
+ const cell = this.getters.getCell(position);
+ if (this.searchOptions.searchFormulas && cell?.isFormula) {
+ return cell.content;
+ }
+ return this.getters.getEvaluatedCell(position).formattedValue;
+ }
+ // ---------------------------------------------------------------------------
+ // Grid rendering
+ // ---------------------------------------------------------------------------
+ drawGrid(renderingContext) {
+ const { ctx } = renderingContext;
+ const sheetId = this.getters.getActiveSheetId();
+ for (const match of this.searchMatches) {
+ const merge = this.getters.getMerge({ sheetId, col: match.col, row: match.row });
+ const left = merge ? merge.left : match.col;
+ const right = merge ? merge.right : match.col;
+ const top = merge ? merge.top : match.row;
+ const bottom = merge ? merge.bottom : match.row;
+ const { x, y, width, height } = this.getters.getVisibleRect({ top, left, right, bottom });
+ if (width > 0 && height > 0) {
+ ctx.fillStyle = BACKGROUND_COLOR;
+ ctx.fillRect(x, y, width, height);
+ if (match.selected) {
+ ctx.strokeStyle = BORDER_COLOR;
+ ctx.strokeRect(x, y, width, height);
+ }
+ }
+ }
+ }
+ }
+
+ class FormatPlugin extends UIPlugin {
+ // ---------------------------------------------------------------------------
+ // Command Handling
+ // ---------------------------------------------------------------------------
+ handle(cmd) {
+ switch (cmd.type) {
+ case "SET_DECIMAL":
+ this.setDecimal(cmd.sheetId, cmd.target, cmd.step);
+ break;
+ }
+ }
+ /**
+ * This function allows to adjust the quantity of decimal places after a decimal
+ * point on cells containing number value. It does this by changing the cells
+ * format. Values aren't modified.
+ *
+ * The change of the decimal quantity is done one by one, the sign of the step
+ * variable indicates whether we are increasing or decreasing.
+ *
+ * If several cells are in the zone, each cell's format will be individually
+ * evaluated and updated with the number type.
+ */
+ setDecimal(sheetId, zones, step) {
+ // Find the each cell with a number value and get the format
+ for (const zone of zones) {
+ for (const position of positions(zone)) {
+ const numberFormat = this.getCellNumberFormat({ sheetId, ...position });
+ if (numberFormat !== undefined) {
+ // Depending on the step sign, increase or decrease the decimal representation
+ // of the format
+ const newFormat = changeDecimalPlaces(numberFormat, step);
+ // Apply the new format on the whole zone
+ this.dispatch("SET_FORMATTING", {
+ sheetId,
+ target: [positionToZone(position)],
+ format: newFormat,
+ });
+ }
+ }
+ }
+ }
+ /**
+ * Take a range of cells and return the format of the first cell containing a
+ * number value. Returns a default format if the cell hasn't format. Returns
+ * undefined if no number value in the range.
+ */
+ getCellNumberFormat(position) {
+ for (const pos of [position]) {
+ const cell = this.getters.getEvaluatedCell(pos);
+ if (cell.type === CellValueType.number &&
+ !(cell.format && isDateTimeFormat(cell.format)) // reject dates
+ ) {
+ return cell.format || createDefaultFormat(cell.value);
+ }
+ }
+ return undefined;
+ }
+ }
+
+ class HeaderVisibilityUIPlugin extends UIPlugin {
+ static getters = [
+ "getNextVisibleCellPosition",
+ "findVisibleHeader",
+ "findLastVisibleColRowIndex",
+ "findFirstVisibleColRowIndex",
+ "isRowHidden",
+ "isColHidden",
+ "isHeaderHidden",
+ ];
+ isRowHidden(sheetId, index) {
+ return (this.getters.isRowHiddenByUser(sheetId, index) || this.getters.isRowFiltered(sheetId, index));
+ }
+ isColHidden(sheetId, index) {
+ return this.getters.isColHiddenByUser(sheetId, index);
+ }
+ isHeaderHidden(sheetId, dimension, index) {
+ return dimension === "COL"
+ ? this.isColHidden(sheetId, index)
+ : this.isRowHidden(sheetId, index);
+ }
+ getNextVisibleCellPosition({ sheetId, col, row }) {
+ return {
+ sheetId,
+ col: this.findVisibleHeader(sheetId, "COL", col, this.getters.getNumberCols(sheetId) - 1),
+ row: this.findVisibleHeader(sheetId, "ROW", row, this.getters.getNumberRows(sheetId) - 1),
+ };
+ }
+ /**
+ * Find the first visible header in the range [`from` => `to`].
+ *
+ * Both `from` and `to` are inclusive.
+ */
+ findVisibleHeader(sheetId, dimension, from, to) {
+ if (from <= to) {
+ for (let i = from; i <= to; i++) {
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
+ !this.isHeaderHidden(sheetId, dimension, i)) {
+ return i;
+ }
+ }
+ }
+ if (from > to) {
+ for (let i = from; i >= to; i--) {
+ if (this.getters.doesHeaderExist(sheetId, dimension, i) &&
+ !this.isHeaderHidden(sheetId, dimension, i)) {
+ return i;
+ }
+ }
+ }
+ return undefined;
+ }
+ findLastVisibleColRowIndex(sheetId, dimension, { last, first }) {
+ const lastVisibleIndex = range(last, first, -1).find((index) => !this.isHeaderHidden(sheetId, dimension, index));
+ return lastVisibleIndex || first;
+ }
+ findFirstVisibleColRowIndex(sheetId, dimension) {
+ const numberOfHeaders = this.getters.getNumberHeaders(sheetId, dimension);
+ for (let i = 0; i < numberOfHeaders; i++) {
+ if (dimension === "COL" && !this.isColHidden(sheetId, i)) {
+ return i;
+ }
+ if (dimension === "ROW" && !this.isRowHidden(sheetId, i)) {
+ return i;
+ }
+ }
+ return undefined;
+ }
+ exportForExcel(data) {
+ for (const sheetData of data.sheets) {
+ for (const [row, rowData] of Object.entries(sheetData.rows)) {
+ const isHidden = this.isRowHidden(sheetData.id, Number(row));
+ rowData.isHidden = isHidden;
+ }
+ }
+ }
+ }
+
+ /**
+ * HighlightPlugin
+ */
+ class HighlightPlugin extends UIPlugin {
+ static layers = [1 /* LAYERS.Highlights */];
+ static getters = ["getHighlights"];
+ // ---------------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------------
+ getHighlights() {
+ return this.prepareHighlights(this.getters.getComposerHighlights().concat(this.getters.getSelectionInputHighlights()));
+ }
+ // ---------------------------------------------------------------------------
+ // Other
+ // ---------------------------------------------------------------------------
+ prepareHighlights(highlights) {
+ return highlights
+ .filter((x) => x.zone.top >= 0 &&
+ x.zone.left >= 0 &&
+ x.zone.bottom < this.getters.getNumberRows(x.sheetId) &&
+ x.zone.right < this.getters.getNumberCols(x.sheetId))
+ .map((highlight) => {
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
+ const zone = numberOfRows * numberOfCols === 1
+ ? this.getters.expandZone(highlight.sheetId, highlight.zone)
+ : highlight.zone;
+ return {
+ ...highlight,
+ zone,
+ };
+ });
+ }
+ // ---------------------------------------------------------------------------
+ // Grid rendering
+ // ---------------------------------------------------------------------------
+ drawGrid(renderingContext) {
+ // rendering selection highlights
+ const { ctx, thinLineWidth } = renderingContext;
+ const sheetId = this.getters.getActiveSheetId();
+ const lineWidth = 3 * thinLineWidth;
+ ctx.lineWidth = lineWidth;
+ /**
+ * We only need to draw the highlights of the current sheet.
+ *
+ * Note that there can be several times the same highlight in 'this.highlights'.
+ * In order to avoid superposing the same color layer and modifying the final
+ * opacity, we filter highlights to remove duplicates.
+ */
+ const highlights = this.getHighlights();
+ for (let h of highlights.filter((highlight, index) =>
+ // For every highlight in the sheet, deduplicated by zone
+ highlights.findIndex((h) => isEqual(h.zone, highlight.zone) && h.sheetId === sheetId) ===
+ index)) {
+ const { x, y, width, height } = this.getters.getVisibleRect(h.zone);
+ if (width > 0 && height > 0) {
+ ctx.strokeStyle = h.color;
+ ctx.strokeRect(x + lineWidth / 2, y + lineWidth / 2, width - lineWidth, height - lineWidth);
+ ctx.globalCompositeOperation = "source-over";
+ ctx.fillStyle = h.color + "20";
+ ctx.fillRect(x + lineWidth, y + lineWidth, width - 2 * lineWidth, height - 2 * lineWidth);
+ }
+ }
+ }
+ }
+
+ class RendererPlugin extends UIPlugin {
+ static layers = [0 /* LAYERS.Background */, 7 /* LAYERS.Headers */];
+ static getters = ["getColDimensionsInViewport", "getRowDimensionsInViewport"];
+ boxes = [];
+ // ---------------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------------
+ /**
+ * Returns the size, start and end coordinates of a column relative to the left
+ * column of the current viewport
+ */
+ getColDimensionsInViewport(sheetId, col) {
+ const left = Math.min(...this.getters.getSheetViewVisibleCols());
+ const start = this.getters.getColRowOffsetInViewport("COL", left, col);
+ const size = this.getters.getColSize(sheetId, col);
+ const isColHidden = this.getters.isColHidden(sheetId, col);
+ return {
+ start,
+ size: size,
+ end: start + (isColHidden ? 0 : size),
+ };
+ }
+ /**
+ * Returns the size, start and end coordinates of a row relative to the top row
+ * of the current viewport
+ */
+ getRowDimensionsInViewport(sheetId, row) {
+ const top = Math.min(...this.getters.getSheetViewVisibleRows());
+ const start = this.getters.getColRowOffsetInViewport("ROW", top, row);
+ const size = this.getters.getRowSize(sheetId, row);
+ const isRowHidden = this.getters.isRowHidden(sheetId, row);
+ return {
+ start,
+ size: size,
+ end: start + (isRowHidden ? 0 : size),
+ };
+ }
+ /**
+ * Get the offset of a header (see getColRowOffsetInViewport), adjusted with the header
+ * size (HEADER_HEIGHT and HEADER_WIDTH)
+ */
+ getHeaderOffset(dimension, start, index) {
+ let size = this.getters.getColRowOffsetInViewport(dimension, start, index);
+ if (!this.getters.isDashboard()) {
+ size += dimension === "ROW" ? HEADER_HEIGHT : HEADER_WIDTH;
+ }
+ return size;
+ }
+ // ---------------------------------------------------------------------------
+ // Grid rendering
+ // ---------------------------------------------------------------------------
+ drawGrid(renderingContext, layer) {
+ switch (layer) {
+ case 0 /* LAYERS.Background */:
+ this.boxes = this.getGridBoxes();
+ this.drawBackground(renderingContext);
+ this.drawOverflowingCellBackground(renderingContext);
+ this.drawCellBackground(renderingContext);
+ this.drawBorders(renderingContext);
+ this.drawTexts(renderingContext);
+ this.drawIcon(renderingContext);
+ this.drawFrozenPanes(renderingContext);
+ break;
+ case 7 /* LAYERS.Headers */:
+ if (!this.getters.isDashboard()) {
+ this.drawHeaders(renderingContext);
+ this.drawFrozenPanesHeaders(renderingContext);
+ }
+ break;
+ }
+ }
+ drawBackground(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
+ // white background
+ ctx.fillStyle = "#ffffff";
+ ctx.fillRect(0, 0, width + CANVAS_SHIFT, height + CANVAS_SHIFT);
+ const areGridLinesVisible = !this.getters.isDashboard() &&
+ this.getters.getGridLinesVisibility(this.getters.getActiveSheetId());
+ const inset = areGridLinesVisible ? 0.1 * thinLineWidth : 0;
+ if (areGridLinesVisible) {
+ for (const box of this.boxes) {
+ ctx.strokeStyle = CELL_BORDER_COLOR;
+ ctx.lineWidth = thinLineWidth;
+ ctx.strokeRect(box.x + inset, box.y + inset, box.width - 2 * inset, box.height - 2 * inset);
+ }
+ }
+ }
+ drawCellBackground(renderingContext) {
+ const { ctx } = renderingContext;
+ for (const box of this.boxes) {
+ let style = box.style;
+ if (style.fillColor && style.fillColor !== "#ffffff") {
+ ctx.fillStyle = style.fillColor || "#ffffff";
+ ctx.fillRect(box.x, box.y, box.width, box.height);
+ }
+ if (box.error) {
+ ctx.fillStyle = "red";
+ ctx.beginPath();
+ ctx.moveTo(box.x + box.width - 5, box.y);
+ ctx.lineTo(box.x + box.width, box.y);
+ ctx.lineTo(box.x + box.width, box.y + 5);
+ ctx.fill();
+ }
+ }
+ }
+ drawOverflowingCellBackground(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ for (const box of this.boxes) {
+ if (box.content && box.isOverflow) {
+ const align = box.content.align || "left";
+ let x;
+ let width;
+ const y = box.y + thinLineWidth / 2;
+ const height = box.height - thinLineWidth;
+ const clipWidth = Math.min(box.clipRect?.width || Infinity, box.content.width);
+ if (align === "left") {
+ x = box.x + thinLineWidth / 2;
+ width = clipWidth - 2 * thinLineWidth;
+ }
+ else if (align === "right") {
+ x = box.x + box.width - thinLineWidth / 2;
+ width = -clipWidth + 2 * thinLineWidth;
+ }
+ else {
+ x =
+ (box.clipRect?.x || box.x + box.width / 2 - box.content.width / 2) + thinLineWidth / 2;
+ width = clipWidth - 2 * thinLineWidth;
+ }
+ ctx.fillStyle = "#ffffff";
+ ctx.fillRect(x, y, width, height);
+ }
+ }
+ }
+ drawBorders(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ for (let box of this.boxes) {
+ const border = box.border;
+ if (border) {
+ const { x, y, width, height } = box;
+ if (border.left) {
+ drawBorder(border.left, x, y, x, y + height);
+ }
+ if (border.top) {
+ drawBorder(border.top, x, y, x + width, y);
+ }
+ if (border.right) {
+ drawBorder(border.right, x + width, y, x + width, y + height);
+ }
+ if (border.bottom) {
+ drawBorder(border.bottom, x, y + height, x + width, y + height);
+ }
+ }
+ }
+ function drawBorder([style, color], x1, y1, x2, y2) {
+ ctx.strokeStyle = color;
+ ctx.lineWidth = (style === "thin" ? 2 : 3) * thinLineWidth;
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+ }
+ }
+ drawTexts(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ ctx.textBaseline = "top";
+ let currentFont;
+ for (let box of this.boxes) {
+ if (box.content) {
+ const style = box.style || {};
+ const align = box.content.align || "left";
+ // compute font and textColor
+ const font = computeTextFont(style);
+ if (font !== currentFont) {
+ currentFont = font;
+ ctx.font = font;
+ }
+ ctx.fillStyle = style.textColor || "#000";
+ // compute horizontal align start point parameter
+ let x = box.x;
+ if (align === "left") {
+ x += MIN_CELL_TEXT_MARGIN + (box.image ? box.image.size + MIN_CF_ICON_MARGIN : 0);
+ }
+ else if (align === "right") {
+ x +=
+ box.width -
+ MIN_CELL_TEXT_MARGIN -
+ (box.isFilterHeader ? ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0);
+ }
+ else {
+ x += box.width / 2;
+ }
+ // horizontal align text direction
+ ctx.textAlign = align;
+ // clip rect if needed
+ if (box.clipRect) {
+ ctx.save();
+ ctx.beginPath();
+ const { x, y, width, height } = box.clipRect;
+ ctx.rect(x, y, width, height);
+ ctx.clip();
+ }
+ // compute vertical align start point parameter:
+ const textLineHeight = computeTextFontSizeInPixels(style);
+ const numberOfLines = box.content.textLines.length;
+ let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
+ // use the horizontal and the vertical start points to:
+ // fill text / fill strikethrough / fill underline
+ for (let brokenLine of box.content.textLines) {
+ ctx.fillText(brokenLine, Math.round(x), Math.round(y));
+ 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, y + textLineHeight / 2, lineWidth, 2.6 * thinLineWidth);
+ }
+ if (style.underline) {
+ ctx.fillRect(_x, y + textLineHeight + 1, lineWidth, 1.3 * thinLineWidth);
+ }
+ }
+ y += MIN_CELL_TEXT_MARGIN + textLineHeight;
+ }
+ if (box.clipRect) {
+ ctx.restore();
+ }
+ }
+ }
+ }
+ drawIcon(renderingContext) {
+ const { ctx } = renderingContext;
+ for (const box of this.boxes) {
+ if (box.image) {
+ const icon = box.image.image;
+ if (box.image.clipIcon) {
+ ctx.save();
+ ctx.beginPath();
+ const { x, y, width, height } = box.image.clipIcon;
+ ctx.rect(x, y, width, height);
+ ctx.clip();
+ }
+ const iconSize = box.image.size;
+ const y = this.computeTextYCoordinate(box, iconSize);
+ ctx.drawImage(icon, box.x + MIN_CF_ICON_MARGIN, y, iconSize, iconSize);
+ if (box.image.clipIcon) {
+ ctx.restore();
+ }
+ }
+ }
+ }
+ /** Compute the vertical start point from which a text line should be draw.
+ *
+ * Note that in case the cell does not have enough spaces to display its text lines,
+ * (wrapping cell case) then the vertical align should be at the top.
+ * */
+ computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {
+ const y = box.y + 1;
+ const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
+ const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
+ const verticalAlign = box.verticalAlign || DEFAULT_VERTICAL_ALIGN;
+ if (hasEnoughSpaces) {
+ if (verticalAlign === "middle") {
+ return y + (box.height - textHeight) / 2;
+ }
+ if (verticalAlign === "bottom") {
+ return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;
+ }
+ }
+ return y + MIN_CELL_TEXT_MARGIN;
+ }
+ drawHeaders(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ const visibleCols = this.getters.getSheetViewVisibleCols();
+ const left = visibleCols[0];
+ const right = visibleCols[visibleCols.length - 1];
+ const visibleRows = this.getters.getSheetViewVisibleRows();
+ const top = visibleRows[0];
+ const bottom = visibleRows[visibleRows.length - 1];
+ const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
+ const selection = this.getters.getSelectedZones();
+ const selectedCols = getZonesCols(selection);
+ const selectedRows = getZonesRows(selection);
+ const sheetId = this.getters.getActiveSheetId();
+ const numberOfCols = this.getters.getNumberCols(sheetId);
+ const numberOfRows = this.getters.getNumberRows(sheetId);
+ const activeCols = this.getters.getActiveCols();
+ const activeRows = this.getters.getActiveRows();
+ ctx.font = `400 ${HEADER_FONT_SIZE}px ${DEFAULT_FONT}`;
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.lineWidth = thinLineWidth;
+ ctx.strokeStyle = "#333";
+ // Columns headers background
+ for (let col = left; col <= right; col++) {
+ const colZone = { left: col, right: col, top: 0, bottom: numberOfRows - 1 };
+ const { x, width } = this.getters.getVisibleRect(colZone);
+ const colHasFilter = this.getters.doesZonesContainFilter(sheetId, [colZone]);
+ const isColActive = activeCols.has(col);
+ const isColSelected = selectedCols.has(col);
+ if (isColActive) {
+ ctx.fillStyle = colHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
+ }
+ else if (isColSelected) {
+ ctx.fillStyle = colHasFilter
+ ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
+ : BACKGROUND_HEADER_SELECTED_COLOR;
+ }
+ else {
+ ctx.fillStyle = colHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
+ }
+ ctx.fillRect(x, 0, width, HEADER_HEIGHT);
+ }
+ // Rows headers background
+ for (let row = top; row <= bottom; row++) {
+ const rowZone = { top: row, bottom: row, left: 0, right: numberOfCols - 1 };
+ const { y, height } = this.getters.getVisibleRect(rowZone);
+ const rowHasFilter = this.getters.doesZonesContainFilter(sheetId, [rowZone]);
+ const isRowActive = activeRows.has(row);
+ const isRowSelected = selectedRows.has(row);
+ if (isRowActive) {
+ ctx.fillStyle = rowHasFilter ? FILTERS_COLOR : BACKGROUND_HEADER_ACTIVE_COLOR;
+ }
+ else if (isRowSelected) {
+ ctx.fillStyle = rowHasFilter
+ ? BACKGROUND_HEADER_SELECTED_FILTER_COLOR
+ : BACKGROUND_HEADER_SELECTED_COLOR;
+ }
+ else {
+ ctx.fillStyle = rowHasFilter ? BACKGROUND_HEADER_FILTER_COLOR : BACKGROUND_HEADER_COLOR;
+ }
+ ctx.fillRect(0, y, HEADER_WIDTH, height);
+ }
+ // 2 main lines
+ ctx.beginPath();
+ ctx.moveTo(HEADER_WIDTH, 0);
+ ctx.lineTo(HEADER_WIDTH, height);
+ ctx.moveTo(0, HEADER_HEIGHT);
+ ctx.lineTo(width, HEADER_HEIGHT);
+ ctx.strokeStyle = HEADER_BORDER_COLOR;
+ ctx.stroke();
+ ctx.beginPath();
+ // column text + separator
+ for (const i of visibleCols) {
+ const colSize = this.getters.getColSize(sheetId, i);
+ const colName = numberToLetters(i);
+ ctx.fillStyle = activeCols.has(i) ? "#fff" : TEXT_HEADER_COLOR;
+ let colStart = this.getHeaderOffset("COL", left, i);
+ ctx.fillText(colName, colStart + colSize / 2, HEADER_HEIGHT / 2);
+ ctx.moveTo(colStart + colSize, 0);
+ ctx.lineTo(colStart + colSize, HEADER_HEIGHT);
+ }
+ // row text + separator
+ for (const i of visibleRows) {
+ const rowSize = this.getters.getRowSize(sheetId, i);
+ ctx.fillStyle = activeRows.has(i) ? "#fff" : TEXT_HEADER_COLOR;
+ let rowStart = this.getHeaderOffset("ROW", top, i);
+ ctx.fillText(String(i + 1), HEADER_WIDTH / 2, rowStart + rowSize / 2);
+ ctx.moveTo(0, rowStart + rowSize);
+ ctx.lineTo(HEADER_WIDTH, rowStart + rowSize);
+ }
+ ctx.stroke();
+ }
+ drawFrozenPanesHeaders(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();
+ const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;
+ const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;
+ ctx.lineWidth = 6 * thinLineWidth;
+ ctx.strokeStyle = "#BCBCBC";
+ ctx.beginPath();
+ if (offsetCorrectionX) {
+ ctx.moveTo(widthCorrection + offsetCorrectionX, 0);
+ ctx.lineTo(widthCorrection + offsetCorrectionX, heightCorrection);
+ }
+ if (offsetCorrectionY) {
+ ctx.moveTo(0, heightCorrection + offsetCorrectionY);
+ ctx.lineTo(widthCorrection, heightCorrection + offsetCorrectionY);
+ }
+ ctx.stroke();
+ }
+ drawFrozenPanes(renderingContext) {
+ const { ctx, thinLineWidth } = renderingContext;
+ const { x: offsetCorrectionX, y: offsetCorrectionY } = this.getters.getMainViewportCoordinates();
+ const visibleCols = this.getters.getSheetViewVisibleCols();
+ const left = visibleCols[0];
+ const right = visibleCols[visibleCols.length - 1];
+ const visibleRows = this.getters.getSheetViewVisibleRows();
+ const top = visibleRows[0];
+ const bottom = visibleRows[visibleRows.length - 1];
+ const viewport = { left, right, top, bottom };
+ const rect = this.getters.getVisibleRect(viewport);
+ const widthCorrection = this.getters.isDashboard() ? 0 : HEADER_WIDTH;
+ const heightCorrection = this.getters.isDashboard() ? 0 : HEADER_HEIGHT;
+ ctx.lineWidth = 6 * thinLineWidth;
+ ctx.strokeStyle = "#DADFE8";
+ ctx.beginPath();
+ if (offsetCorrectionX) {
+ ctx.moveTo(widthCorrection + offsetCorrectionX, heightCorrection);
+ ctx.lineTo(widthCorrection + offsetCorrectionX, rect.height + heightCorrection);
+ }
+ if (offsetCorrectionY) {
+ ctx.moveTo(widthCorrection, heightCorrection + offsetCorrectionY);
+ ctx.lineTo(rect.width + widthCorrection, heightCorrection + offsetCorrectionY);
+ }
+ ctx.stroke();
+ }
+ findNextEmptyCol(base, max, row) {
+ const sheetId = this.getters.getActiveSheetId();
+ let col = base;
+ while (col < max) {
+ const position = { sheetId, col: col + 1, row };
+ const nextCell = this.getters.getEvaluatedCell(position);
+ const nextCellBorder = this.getters.getCellBorderWithFilterBorder(position);
+ if (nextCell.type !== CellValueType.empty ||
+ this.getters.isInMerge(position) ||
+ nextCellBorder?.left) {
+ return col;
+ }
+ col++;
+ }
+ return col;
+ }
+ findPreviousEmptyCol(base, min, row) {
+ const sheetId = this.getters.getActiveSheetId();
+ let col = base;
+ while (col > min) {
+ const position = { sheetId, col: col - 1, row };
+ const previousCell = this.getters.getEvaluatedCell(position);
+ const previousCellBorder = this.getters.getCellBorderWithFilterBorder(position);
+ if (previousCell.type !== CellValueType.empty ||
+ this.getters.isInMerge(position) ||
+ previousCellBorder?.right) {
+ return col;
+ }
+ col--;
+ }
+ return col;
+ }
+ computeCellAlignment(position, isOverflowing) {
+ const cell = this.getters.getCell(position);
+ if (cell?.isFormula && this.getters.shouldShowFormulas()) {
+ return "left";
+ }
+ const { align } = this.getters.getCellStyle(position);
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
+ if (isOverflowing && evaluatedCell.type === CellValueType.number) {
+ return align !== "center" ? "left" : align;
+ }
+ return align || evaluatedCell.defaultAlign;
+ }
+ createZoneBox(sheetId, zone, viewport) {
+ const { left, right } = viewport;
+ const col = zone.left;
+ const row = zone.top;
+ const position = { sheetId, col, row };
+ const cell = this.getters.getEvaluatedCell(position);
+ const showFormula = this.getters.shouldShowFormulas();
+ const { x, y, width, height } = this.getters.getVisibleRect(zone);
+ const { verticalAlign } = this.getters.getCellStyle(position);
+ const box = {
+ x,
+ y,
+ width,
+ height,
+ border: this.getters.getCellBorderWithFilterBorder(position) || undefined,
+ style: this.getters.getCellComputedStyle(position),
+ verticalAlign,
+ };
+ if (cell.type === CellValueType.empty) {
+ return box;
+ }
+ /** Icon CF */
+ const cfIcon = this.getters.getConditionalIcon(position);
+ const fontSizePX = computeTextFontSizeInPixels(box.style);
+ const iconBoxWidth = cfIcon ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
+ if (cfIcon) {
+ box.image = {
+ type: "icon",
+ size: fontSizePX,
+ clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },
+ image: ICONS[cfIcon].img,
+ };
+ }
+ /** Filter Header */
+ box.isFilterHeader = this.getters.isFilterHeader(position);
+ const headerIconWidth = box.isFilterHeader ? FILTER_ICON_EDGE_LENGTH + FILTER_ICON_MARGIN : 0;
+ /** Content */
+ const style = this.getters.getCellComputedStyle(position);
+ const wrapping = style.wrapping || "overflow";
+ const maxWidth = wrapping === "wrap" && !showFormula ? width - 2 * MIN_CELL_TEXT_MARGIN : undefined;
+ const multiLineText = this.getters.getCellMultiLineText(position, maxWidth);
+ const textWidth = Math.max(...multiLineText.map((line) => this.getters.getTextWidth(line, style) + MIN_CELL_TEXT_MARGIN));
+ const contentWidth = iconBoxWidth + textWidth + headerIconWidth;
+ const align = this.computeCellAlignment(position, contentWidth > width);
+ box.content = {
+ textLines: multiLineText,
+ width: wrapping === "overflow" ? textWidth : width,
+ align,
+ };
+ /** Error */
+ if (cell.type === CellValueType.error && cell.error.logLevel > CellErrorLevel.silent) {
+ box.error = cell.error.message;
+ }
+ /** ClipRect */
+ const isOverflowing = contentWidth > width || fontSizePX > height;
+ if (cfIcon || box.isFilterHeader) {
+ box.clipRect = {
+ x: box.x + iconBoxWidth,
+ y: box.y,
+ width: Math.max(0, width - iconBoxWidth - headerIconWidth),
+ height,
+ };
+ }
+ else if (isOverflowing && wrapping === "overflow") {
+ let nextColIndex, previousColIndex;
+ const isCellInMerge = this.getters.isInMerge(position);
+ if (isCellInMerge) {
+ // Always clip merges
+ nextColIndex = this.getters.getMerge(position).right;
+ previousColIndex = col;
+ }
+ else {
+ nextColIndex = this.findNextEmptyCol(col, right, row);
+ previousColIndex = this.findPreviousEmptyCol(col, left, row);
+ box.isOverflow = true;
+ }
+ switch (align) {
+ case "left": {
+ const emptyZoneOnTheLeft = positionToZone({ col: nextColIndex, row });
+ const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheLeft));
+ if (width < contentWidth || fontSizePX > height || multiLineText.length > 1) {
+ box.clipRect = { x, y, width, height };
+ }
+ break;
+ }
+ case "right": {
+ const emptyZoneOnTheRight = positionToZone({ col: previousColIndex, row });
+ const { x, y, width, height } = this.getters.getVisibleRect(union(zone, emptyZoneOnTheRight));
+ if (width < contentWidth || fontSizePX > height || multiLineText.length > 1) {
+ box.clipRect = { x, y, width, height };
+ }
+ break;
+ }
+ case "center": {
+ const emptyZone = {
+ ...zone,
+ left: previousColIndex,
+ right: nextColIndex,
+ };
+ const { x, y, height, width } = this.getters.getVisibleRect(emptyZone);
+ const halfContentWidth = contentWidth / 2;
+ const boxMiddle = box.x + box.width / 2;
+ if (x + width < boxMiddle + halfContentWidth ||
+ x > boxMiddle - halfContentWidth ||
+ fontSizePX > height ||
+ multiLineText.length > 1) {
+ const clipX = x > boxMiddle - halfContentWidth ? x : boxMiddle - halfContentWidth;
+ const clipWidth = x + width - clipX;
+ box.clipRect = { x: clipX, y, width: clipWidth, height };
+ }
+ break;
+ }
+ }
+ }
+ else if (wrapping === "clip" || wrapping === "wrap" || multiLineText.length > 1) {
+ box.clipRect = {
+ x: box.x,
+ y: box.y,
+ width,
+ height,
+ };
+ }
+ return box;
+ }
+ getGridBoxes() {
+ const boxes = [];
+ const visibleCols = this.getters.getSheetViewVisibleCols();
+ const left = visibleCols[0];
+ const right = visibleCols[visibleCols.length - 1];
+ const visibleRows = this.getters.getSheetViewVisibleRows();
+ const top = visibleRows[0];
+ const bottom = visibleRows[visibleRows.length - 1];
+ const viewport = { left, right, top, bottom };
+ const sheetId = this.getters.getActiveSheetId();
+ for (const row of visibleRows) {
+ for (const col of visibleCols) {
+ const position = { sheetId, col, row };
+ if (this.getters.isInMerge(position)) {
+ continue;
+ }
+ boxes.push(this.createZoneBox(sheetId, positionToZone(position), viewport));
+ }
+ }
+ for (const merge of this.getters.getMerges(sheetId)) {
+ if (this.getters.isMergeHidden(sheetId, merge)) {
+ continue;
+ }
+ if (overlap(merge, viewport)) {
+ const box = this.createZoneBox(sheetId, merge, viewport);
+ const borderBottomRight = this.getters.getCellBorder({
+ sheetId,
+ col: merge.right,
+ row: merge.bottom,
+ });
+ box.border = {
+ ...box.border,
+ bottom: borderBottomRight ? borderBottomRight.bottom : undefined,
+ right: borderBottomRight ? borderBottomRight.right : undefined,
+ };
+ box.isMerge = true;
+ boxes.push(box);
+ }
+ }
+ return boxes;
+ }
+ }
+
+ /**
+ * Selection input Plugin
+ *
+ * The SelectionInput component input and output are both arrays of strings, but
+ * it requires an intermediary internal state to work.
+ * This plugin handles this internal state.
+ */
+ class SelectionInputPlugin extends UIPlugin {
+ inputHasSingleRange;
+ static layers = [1 /* LAYERS.Highlights */];
+ static getters = [];
+ ranges = [];
+ focusedRangeIndex = null;
+ activeSheet;
+ willAddNewRange = false;
+ constructor(config, initialRanges, inputHasSingleRange) {
+ super(config);
+ this.inputHasSingleRange = inputHasSingleRange;
+ this.insertNewRange(0, initialRanges);
+ this.activeSheet = this.getters.getActiveSheetId();
+ if (this.ranges.length === 0) {
+ this.insertNewRange(this.ranges.length, [""]);
+ this.focusLast();
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // Command Handling
+ // ---------------------------------------------------------------------------
+ allowDispatch(cmd) {
+ switch (cmd.type) {
+ case "ADD_EMPTY_RANGE":
+ if (this.inputHasSingleRange && this.ranges.length === 1) {
+ return 30 /* CommandResult.MaximumRangesReached */;
+ }
+ break;
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ handleEvent(event) {
+ const xc = zoneToXc(event.anchor.zone);
+ const inputSheetId = this.activeSheet;
+ const sheetId = this.getters.getActiveSheetId();
+ const sheetName = this.getters.getSheetName(sheetId);
+ this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
+ }
+ handle(cmd) {
+ switch (cmd.type) {
+ case "UNFOCUS_SELECTION_INPUT":
+ this.unfocus();
+ break;
+ case "FOCUS_RANGE":
+ this.focus(this.getIndex(cmd.rangeId));
+ break;
+ case "CHANGE_RANGE": {
+ const index = this.getIndex(cmd.rangeId);
+ if (index !== null && this.focusedRangeIndex !== index) {
+ this.focus(index);
+ }
+ if (index !== null) {
+ const valueWithoutLeadingComma = cmd.value.replace(/^,+/, "");
+ const values = valueWithoutLeadingComma.split(",").map((reference) => reference.trim());
+ this.setRange(index, values);
+ }
+ break;
+ }
+ case "ADD_EMPTY_RANGE":
+ this.insertNewRange(this.ranges.length, [""]);
+ this.focusLast();
+ break;
+ case "REMOVE_RANGE":
+ const index = this.getIndex(cmd.rangeId);
+ if (index !== null) {
+ this.removeRange(index);
+ }
+ break;
+ case "STOP_SELECTION_INPUT":
+ this.willAddNewRange = false;
+ break;
+ case "PREPARE_SELECTION_INPUT_EXPANSION": {
+ const index = this.focusedRangeIndex;
+ if (index !== null && !this.inputHasSingleRange) {
+ this.willAddNewRange = this.ranges[index].xc.trim() !== "";
+ }
+ break;
+ }
+ case "ACTIVATE_SHEET": {
+ if (cmd.sheetIdFrom !== cmd.sheetIdTo) {
+ const { col, row } = this.getters.getNextVisibleCellPosition({
+ sheetId: cmd.sheetIdTo,
+ col: 0,
+ row: 0,
+ });
+ const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));
+ this.selection.resetAnchor(this, { cell: { col, row }, zone });
+ }
+ }
+ }
+ }
+ // ---------------------------------------------------------------------------
+ // Getters || only callable by the parent
+ // ---------------------------------------------------------------------------
+ getSelectionInputValue() {
+ return this.cleanInputs(this.ranges.map((range) => {
+ return range.xc ? range.xc : "";
+ }));
+ }
+ getSelectionInputHighlights() {
+ return this.ranges.map((input) => this.inputToHighlights(input)).flat();
+ }
+ // ---------------------------------------------------------------------------
+ // Other
+ // ---------------------------------------------------------------------------
+ /**
+ * Focus a given range or remove the focus.
+ */
+ focus(index) {
+ this.focusedRangeIndex = index;
+ }
+ focusLast() {
+ this.focus(this.ranges.length - 1);
+ }
+ unfocus() {
+ this.focusedRangeIndex = null;
+ }
+ add(newRanges) {
+ if (this.focusedRangeIndex === null || newRanges.length === 0) {
+ return;
+ }
+ if (this.willAddNewRange) {
+ this.insertNewRange(this.ranges.length, newRanges);
+ this.focusLast();
+ this.willAddNewRange = false;
+ }
+ else {
+ this.setRange(this.focusedRangeIndex, newRanges);
+ }
+ }
+ setContent(index, xc) {
+ this.ranges[index] = {
+ ...this.ranges[index],
+ xc,
+ };
+ }
+ /**
+ * Insert new inputs after the given index.
+ */
+ insertNewRange(index, values) {
+ const currentMaxId = Math.max(0, ...this.ranges.map((range) => Number(range.id)));
+ this.ranges.splice(index, 0, ...values.map((xc, i) => ({
+ xc,
+ id: currentMaxId + i + 1,
+ color: colors$1[(currentMaxId + i) % colors$1.length],
+ })));
+ }
+ /**
+ * Set a new value in a given range input. If more than one value is provided,
+ * new inputs will be added.
+ */
+ setRange(index, values) {
+ const [, ...additionalValues] = values;
+ this.setContent(index, values[0]);
+ this.insertNewRange(index + 1, additionalValues);
+ // focus the last newly added range
+ if (additionalValues.length) {
+ this.focus(index + additionalValues.length);
+ }
+ }
+ removeRange(index) {
+ this.ranges.splice(index, 1);
+ if (this.focusedRangeIndex !== null) {
+ this.focusLast();
+ }
+ }
+ /**
+ * Convert highlights input format to the command format.
+ * The first xc in the input range will keep its color.
+ * Invalid ranges and ranges from other sheets than the active sheets
+ * are ignored.
+ */
+ inputToHighlights({ xc, color }) {
+ const XCs = this.cleanInputs([xc])
+ .filter((range) => this.getters.isRangeValid(range))
+ .filter((reference) => this.shouldBeHighlighted(this.activeSheet, reference));
+ return XCs.map((xc) => {
+ const { sheetName } = splitReference(xc);
+ return {
+ zone: this.getters.getRangeFromSheetXC(this.activeSheet, xc).zone,
+ sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.activeSheet,
+ color,
+ };
+ });
+ }
+ cleanInputs(ranges) {
+ return ranges
+ .map((xc) => xc.split(","))
+ .flat()
+ .map((xc) => xc.trim())
+ .filter((xc) => xc !== "");
+ }
+ /**
+ * Check if a cell or range reference should be highlighted.
+ * It should be highlighted if it references the current active sheet.
+ * Note that if no sheet name is given in the reference ("A1"), it refers to the
+ * active sheet when the selection input was enabled which might be different from
+ * the current active sheet.
+ */
+ shouldBeHighlighted(inputSheetId, reference) {
+ const { sheetName } = splitReference(reference);
+ const sheetId = this.getters.getSheetIdByName(sheetName);
+ const activeSheetId = this.getters.getActiveSheet().id;
+ const valid = this.getters.isRangeValid(reference);
+ return (valid &&
+ (sheetId === activeSheetId || (sheetId === undefined && activeSheetId === inputSheetId)));
+ }
+ /**
+ * Return the index of a range given its id
+ * or `null` if the range is not found.
+ */
+ getIndex(rangeId) {
+ const index = this.ranges.findIndex((range) => range.id === rangeId);
+ return index >= 0 ? index : null;
+ }
+ }
+
+ /**
+ * Selection input Plugin
+ *
+ * The SelectionInput component input and output are both arrays of strings, but
+ * it requires an intermediary internal state to work.
+ * This plugin handles this internal state.
+ */
+ class SelectionInputsManagerPlugin extends UIPlugin {
+ config;
+ static layers = [1 /* LAYERS.Highlights */];
+ static getters = [
+ "getSelectionInput",
+ "getSelectionInputValue",
+ "isRangeValid",
+ "getSelectionInputHighlights",
+ ];
+ inputs = {};
+ focusedInputId = null;
+ get currentInput() {
+ return this.focusedInputId ? this.inputs[this.focusedInputId] : null;
+ }
+ constructor(config) {
+ super(config);
+ this.config = config;
+ }
+ // ---------------------------------------------------------------------------
+ // Command Handling
+ // ---------------------------------------------------------------------------
+ allowDispatch(cmd) {
+ switch (cmd.type) {
+ case "FOCUS_RANGE":
+ const index = this.currentInput?.getIndex(cmd.rangeId);
+ if (this.focusedInputId === cmd.id && this.currentInput?.focusedRangeIndex === index) {
+ return 29 /* CommandResult.InputAlreadyFocused */;
+ }
+ break;
+ }
+ if (this.currentInput) {
+ return this.currentInput.allowDispatch(cmd);
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ handle(cmd) {
+ switch (cmd.type) {
+ case "ENABLE_NEW_SELECTION_INPUT":
+ this.initInput(cmd.id, cmd.initialRanges || [], cmd.hasSingleRange);
+ break;
+ case "DISABLE_SELECTION_INPUT":
+ if (this.focusedInputId === cmd.id) {
+ this.unfocus();
+ }
+ delete this.inputs[cmd.id];
+ break;
+ case "UNFOCUS_SELECTION_INPUT":
+ this.unfocus();
+ break;
+ case "ADD_EMPTY_RANGE":
+ case "REMOVE_RANGE":
+ if (cmd.id !== this.focusedInputId) {
+ const input = this.inputs[cmd.id];
+ this.selection.capture(input, { cell: { col: 0, row: 0 }, zone: positionToZone({ col: 0, row: 0 }) }, { handleEvent: input.handleEvent.bind(input) });
+ this.focusedInputId = cmd.id;
+ }
+ break;
+ case "FOCUS_RANGE":
+ case "CHANGE_RANGE":
+ if (cmd.id !== this.focusedInputId) {
+ const input = this.inputs[cmd.id];
+ const range = input.ranges.find((range) => range.id === cmd.rangeId);
+ const sheetId = this.getters.getActiveSheetId();
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
+ this.focusedInputId = cmd.id;
+ }
+ break;
+ }
+ this.currentInput?.handle(cmd);
+ }
+ // ---------------------------------------------------------------------------
+ // Getters
+ // ---------------------------------------------------------------------------
+ /**
+ * Return a list of all valid XCs.
+ * e.g. ["A1", "Sheet2!B3", "E12"]
+ */
+ getSelectionInput(id) {
+ if (!this.inputs[id]) {
+ return [];
+ }
+ return this.inputs[id].ranges.map((input, index) => Object.assign({}, input, {
+ color: this.focusedInputId === id &&
+ this.inputs[id].focusedRangeIndex !== null &&
+ this.isRangeValid(input.xc)
+ ? input.color
+ : null,
+ isFocused: this.focusedInputId === id && this.inputs[id].focusedRangeIndex === index,
+ }));
+ }
+ isRangeValid(reference) {
+ if (!reference) {
+ return false;
+ }
+ const { xc, sheetName } = splitReference(reference);
+ return (xc.match(rangeReference) !== null &&
+ (!sheetName || this.getters.getSheetIdByName(sheetName) !== undefined));
+ }
+ getSelectionInputValue(id) {
+ return this.inputs[id].getSelectionInputValue();
+ }
+ getSelectionInputHighlights() {
+ if (!this.focusedInputId) {
+ return [];
+ }
+ return this.inputs[this.focusedInputId].getSelectionInputHighlights();
+ }
+ // ---------------------------------------------------------------------------
+ // Other
+ // ---------------------------------------------------------------------------
+ initInput(id, initialRanges, inputHasSingleRange = false) {
+ this.inputs[id] = new SelectionInputPlugin(this.config, initialRanges, inputHasSingleRange);
+ if (initialRanges.length === 0) {
+ const input = this.inputs[id];
+ const anchor = {
+ zone: positionToZone({ col: 0, row: 0 }),
+ cell: { col: 0, row: 0 },
+ };
+ this.selection.capture(input, anchor, { handleEvent: input.handleEvent.bind(input) });
+ this.focusedInputId = id;
+ }
+ }
+ unfocus() {
+ this.selection.release(this.currentInput);
+ this.focusedInputId = null;
+ }
+ }
class SortPlugin extends UIPlugin {
+ static getters = ["getContiguousZone"];
allowDispatch(cmd) {
switch (cmd.type) {
case "SORT_CELLS":
@@ -38140,13 +38419,10 @@
return cells;
}
}
- SortPlugin.getters = ["getContiguousZone"];
class UIOptionsPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.showFormulas = false;
- }
+ static getters = ["shouldShowFormulas"];
+ showFormulas = false;
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -38164,13 +38440,18 @@
return this.showFormulas;
}
}
- UIOptionsPlugin.getters = ["shouldShowFormulas"];
class SheetUIPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.ctx = document.createElement("canvas").getContext("2d");
- }
+ static getters = [
+ "getCellWidth",
+ "getTextWidth",
+ "getCellText",
+ "getCellMultiLineText",
+ "getColDimensions",
+ "getRowDimensions",
+ "getColRowOffset",
+ ];
+ ctx = document.createElement("canvas").getContext("2d");
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -38244,7 +38525,7 @@
}
getCellText(position, showFormula = false) {
const cell = this.getters.getCell(position);
- if (showFormula && (cell === null || cell === void 0 ? void 0 : cell.isFormula)) {
+ if (showFormula && cell?.isFormula) {
return cell.content;
}
else {
@@ -38387,15 +38668,6 @@
return splitWord;
}
}
- SheetUIPlugin.getters = [
- "getCellWidth",
- "getTextWidth",
- "getCellText",
- "getCellMultiLineText",
- "getColDimensions",
- "getRowDimensions",
- "getColRowOffset",
- ];
const genericRepeatsTransforms = [
repeatSheetDependantCommand,
@@ -38441,12 +38713,11 @@
return newCmd;
}
function repeatCreateSheetCommand(getters, cmd) {
- var _a;
const newCmd = deepCopy(cmd);
newCmd.sheetId = uuidGenerator.uuidv4();
const sheetName = cmd.name || getters.getSheet(getters.getActiveSheetId()).name;
// Extract the prefix of the sheet name (everything before the number at the end of the name)
- const namePrefix = ((_a = sheetName.match(/(.+?)\d*$/)) === null || _a === void 0 ? void 0 : _a[1]) || sheetName;
+ const namePrefix = sheetName.match(/(.+?)\d*$/)?.[1] || sheetName;
newCmd.name = getters.getNextSheetName(namePrefix);
return newCmd;
}
@@ -38610,16 +38881,18 @@
* changes
*/
class HistoryPlugin extends UIPlugin {
+ static getters = ["canUndo", "canRedo"];
+ /**
+ * Ids of the revisions which can be undone
+ */
+ undoStack = [];
+ /**
+ * Ids of the revisions which can be redone
+ */
+ redoStack = [];
+ session;
constructor(config) {
super(config);
- /**
- * Ids of the revisions which can be undone
- */
- this.undoStack = [];
- /**
- * Ids of the revisions which can be redone
- */
- this.redoStack = [];
this.session = config.session;
this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
@@ -38713,10 +38986,201 @@
return this.session.getLastLocalNonEmptyRevision(["REQUEST_REDO"]);
}
}
- HistoryPlugin.getters = ["canUndo", "canRedo"];
+
+ class SplitToColumnsPlugin extends UIPlugin {
+ static getters = ["getAutomaticSeparator"];
+ allowDispatch(cmd) {
+ switch (cmd.type) {
+ case "SPLIT_TEXT_INTO_COLUMNS":
+ return this.chainValidations(this.batchValidations(this.checkSingleColSelected, this.checkNonEmptySelector), this.batchValidations(this.checkNotOverwritingContent, this.checkSeparatorInSelection))(cmd);
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ handle(cmd) {
+ switch (cmd.type) {
+ case "SPLIT_TEXT_INTO_COLUMNS":
+ this.splitIntoColumns(cmd);
+ break;
+ }
+ }
+ getAutomaticSeparator() {
+ const cells = this.getters.getSelectedCells();
+ for (const cell of cells) {
+ if (cell.value && cell.type === CellValueType.text) {
+ const separator = this.getAutoSeparatorForString(cell.value);
+ if (separator) {
+ return separator;
+ }
+ }
+ }
+ return " ";
+ }
+ getAutoSeparatorForString(str) {
+ const separators = [NEWLINE, ";", ",", " ", "."];
+ for (const separator of separators) {
+ if (str.includes(separator)) {
+ return separator;
+ }
+ }
+ return;
+ }
+ splitIntoColumns({ separator, addNewColumns }) {
+ const selection = this.getters.getSelectedZone();
+ const sheetId = this.getters.getActiveSheetId();
+ const splitted = this.getSplittedCols(selection, separator);
+ if (addNewColumns) {
+ this.addColsToAvoidCollisions(selection, splitted);
+ }
+ this.removeMergesInSplitZone(selection, splitted);
+ this.addColumnsToNotOverflowSheet(selection, splitted);
+ for (let i = 0; i < splitted.length; i++) {
+ const row = selection.top + i;
+ const splittedContent = splitted[i];
+ const col = selection.left;
+ const mainCell = this.getters.getCell({ sheetId, col, row });
+ if (splittedContent.length === 1 && splittedContent[0] === mainCell?.content) {
+ continue;
+ }
+ for (const [index, content] of splittedContent.entries()) {
+ this.dispatch("UPDATE_CELL", {
+ sheetId,
+ col: col + index,
+ row,
+ content,
+ format: "",
+ style: mainCell?.style || null,
+ });
+ }
+ }
+ }
+ getSplittedCols(selection, separator) {
+ if (!separator) {
+ throw new Error("Separator cannot be empty");
+ }
+ const sheetId = this.getters.getActiveSheetId();
+ const splitted = [];
+ for (const row of range(selection.top, selection.bottom + 1)) {
+ const text = this.getters.getEvaluatedCell({
+ sheetId,
+ col: selection.left,
+ row,
+ }).formattedValue;
+ splitted.push(this.splitAndRemoveTrailingEmpty(text, separator));
+ }
+ return splitted;
+ }
+ splitAndRemoveTrailingEmpty(string, separator) {
+ const splitted = string.split(separator);
+ while (splitted.length > 1 && splitted[splitted.length - 1] === "") {
+ splitted.pop();
+ }
+ return splitted;
+ }
+ willSplittedColsOverwriteContent(selection, splittedCols) {
+ const sheetId = this.getters.getActiveSheetId();
+ for (const row of range(selection.top, selection.bottom + 1)) {
+ const splittedText = splittedCols[row - selection.top];
+ for (let i = 1; i < splittedText.length; i++) {
+ const cell = this.getters.getCell({ sheetId, col: selection.left + i, row });
+ if (cell && cell.content) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ removeMergesInSplitZone(selection, splittedCols) {
+ const sheetId = this.getters.getActiveSheetId();
+ const colsInSplitZone = Math.max(...splittedCols.map((s) => s.length));
+ const splitZone = { ...selection, right: selection.left + colsInSplitZone - 1 };
+ const merges = this.getters.getMergesInZone(sheetId, splitZone);
+ this.dispatch("REMOVE_MERGE", { sheetId, target: merges });
+ }
+ addColsToAvoidCollisions(selection, splittedCols) {
+ const sheetId = this.getters.getActiveSheetId();
+ let colsToAdd = 0;
+ for (const row of range(selection.top, selection.bottom + 1)) {
+ const cellPosition = { sheetId, col: selection.left, row };
+ const splittedText = splittedCols[row - selection.top];
+ const colsToAddInRow = this.getColsToAddToAvoidCollision(cellPosition, splittedText);
+ colsToAdd = Math.max(colsToAdd, colsToAddInRow);
+ }
+ if (colsToAdd) {
+ this.dispatch("ADD_COLUMNS_ROWS", {
+ dimension: "COL",
+ base: selection.left,
+ sheetId,
+ quantity: colsToAdd,
+ position: "after",
+ });
+ }
+ }
+ getColsToAddToAvoidCollision(cellPosition, splittedText) {
+ const maxColumnsToSpread = splittedText.length;
+ for (let i = 1; i < maxColumnsToSpread; i++) {
+ const col = cellPosition.col + i;
+ const cell = this.getters.getCell({ ...cellPosition, col });
+ if (cell && cell.content) {
+ return maxColumnsToSpread - i;
+ }
+ }
+ return 0;
+ }
+ addColumnsToNotOverflowSheet(selection, splittedCols) {
+ const sheetId = this.getters.getActiveSheetId();
+ const maxColumnsToSpread = Math.max(...splittedCols.map((s) => s.length - 1));
+ const maxColIndex = this.getters.getNumberCols(sheetId) - 1;
+ if (selection.left + maxColumnsToSpread > maxColIndex) {
+ this.dispatch("ADD_COLUMNS_ROWS", {
+ dimension: "COL",
+ base: maxColIndex,
+ sheetId,
+ quantity: selection.left + maxColumnsToSpread - maxColIndex,
+ position: "after",
+ });
+ }
+ }
+ checkSingleColSelected() {
+ if (!this.getters.isSingleColSelected()) {
+ return 88 /* CommandResult.MoreThanOneColumnSelected */;
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ checkNonEmptySelector(cmd) {
+ if (cmd.separator === "") {
+ return 89 /* CommandResult.EmptySplitSeparator */;
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ checkNotOverwritingContent(cmd) {
+ if (cmd.addNewColumns || cmd.force) {
+ return 0 /* CommandResult.Success */;
+ }
+ const selection = this.getters.getSelectedZones()[0];
+ const splitted = this.getSplittedCols(selection, cmd.separator);
+ if (this.willSplittedColsOverwriteContent(selection, splitted)) {
+ return 90 /* CommandResult.SplitWillOverwriteContent */;
+ }
+ return 0 /* CommandResult.Success */;
+ }
+ checkSeparatorInSelection({ separator }) {
+ const cells = this.getters.getSelectedCells();
+ for (const cell of cells) {
+ if (cell.formattedValue.includes(separator)) {
+ return 0 /* CommandResult.Success */;
+ }
+ }
+ return 91 /* CommandResult.NoSplitSeparatorInSelection */;
+ }
+ }
/** Abstract state of the clipboard when copying/cutting content that is pasted in cells of the sheet */
class ClipboardCellsAbstractState {
+ getters;
+ dispatch;
+ selection;
+ operation;
+ sheetId;
constructor(operation, getters, dispatch, selection) {
this.getters = getters;
this.dispatch = dispatch;
@@ -38765,6 +39229,9 @@
/** State of the clipboard when copying/cutting cells */
class ClipboardCellsState extends ClipboardCellsAbstractState {
+ cells;
+ copiedTables;
+ zones;
constructor(zones, operation, getters, dispatch, selection) {
super(operation, getters, dispatch, selection);
if (!zones.length) {
@@ -38824,7 +39291,7 @@
}
isPasteAllowed(target, clipboardOption) {
const sheetId = this.getters.getActiveSheetId();
- if (this.operation === "CUT" && (clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) !== undefined) {
+ if (this.operation === "CUT" && clipboardOption?.pasteOption !== undefined) {
// cannot paste only format or only value if the previous operation is a CUT
return 21 /* CommandResult.WrongPasteOption */;
}
@@ -38868,7 +39335,7 @@
const height = this.cells.length;
const width = this.cells[0].length;
const isCutOperation = this.operation === "CUT";
- if (options === null || options === void 0 ? void 0 : options.selectTarget) {
+ if (options?.selectTarget) {
this.selectPastedZone(width, height, isCutOperation, target);
}
}
@@ -38896,7 +39363,7 @@
}
}
}
- if ((options === null || options === void 0 ? void 0 : options.pasteOption) === undefined) {
+ if (options?.pasteOption === undefined) {
this.pasteCopiedTables(target);
}
}
@@ -39005,7 +39472,7 @@
// This condition is used to determine if we have to paste the CF or not.
// We have to do it when the command handled is "PASTE", not "INSERT_CELL"
// or "DELETE_CELL". So, the state should be the local state
- const shouldPasteCF = (clipboardOptions === null || clipboardOptions === void 0 ? void 0 : clipboardOptions.pasteOption) !== "onlyValue" && (clipboardOptions === null || clipboardOptions === void 0 ? void 0 : clipboardOptions.shouldPasteCF);
+ const shouldPasteCF = clipboardOptions?.pasteOption !== "onlyValue" && clipboardOptions?.shouldPasteCF;
const sheetId = this.getters.getActiveSheetId();
// first, add missing cols/rows if needed
this.addMissingDimensions(width, height, col, row);
@@ -39038,19 +39505,19 @@
pasteCell(origin, target, operation, clipboardOption) {
const { sheetId, col, row } = target;
const targetCell = this.getters.getEvaluatedCell(target);
- if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) !== "onlyValue") {
+ if (clipboardOption?.pasteOption !== "onlyValue") {
const targetBorders = this.getters.getCellBorder(target);
const originBorders = origin.border;
const border = {
- top: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.top) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.top),
- bottom: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.bottom) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.bottom),
- left: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.left) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.left),
- right: (targetBorders === null || targetBorders === void 0 ? void 0 : targetBorders.right) || (originBorders === null || originBorders === void 0 ? void 0 : originBorders.right),
+ top: targetBorders?.top || originBorders?.top,
+ bottom: targetBorders?.bottom || originBorders?.bottom,
+ left: targetBorders?.left || originBorders?.left,
+ right: targetBorders?.right || originBorders?.right,
};
this.dispatch("SET_BORDER", { sheetId, col, row, border });
}
if (origin.cell) {
- if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === "onlyFormat") {
+ if (clipboardOption?.pasteOption === "onlyFormat") {
this.dispatch("UPDATE_CELL", {
...target,
style: origin.cell.style,
@@ -39058,7 +39525,7 @@
});
return;
}
- if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === "onlyValue") {
+ if (clipboardOption?.pasteOption === "onlyValue") {
const content = formatValue(origin.evaluatedCell.value);
this.dispatch("UPDATE_CELL", { ...target, content });
return;
@@ -39077,10 +39544,10 @@
});
}
else if (targetCell) {
- if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === "onlyValue") {
+ if (clipboardOption?.pasteOption === "onlyValue") {
this.dispatch("UPDATE_CELL", { ...target, content: "" });
}
- else if ((clipboardOption === null || clipboardOption === void 0 ? void 0 : clipboardOption.pasteOption) === "onlyFormat") {
+ else if (clipboardOption?.pasteOption === "onlyFormat") {
this.dispatch("UPDATE_CELL", { ...target, style: null, format: "" });
}
else {
@@ -39208,6 +39675,12 @@
/** State of the clipboard when copying/cutting figures */
class ClipboardFigureState {
+ operation;
+ getters;
+ dispatch;
+ sheetId;
+ copiedFigure;
+ copiedFigureContent;
constructor(operation, getters, dispatch) {
this.operation = operation;
this.getters = getters;
@@ -39240,7 +39713,7 @@
if (target.length === 0) {
return 73 /* CommandResult.EmptyTarget */;
}
- if ((option === null || option === void 0 ? void 0 : option.pasteOption) !== undefined) {
+ if (option?.pasteOption !== undefined) {
return 22 /* CommandResult.WrongFigurePasteOption */;
}
return 0 /* CommandResult.Success */;
@@ -39280,6 +39753,9 @@
drawClipboard(renderingContext) { }
}
class ClipboardFigureChart {
+ dispatch;
+ sheetId;
+ copiedChart;
constructor(dispatch, getters, sheetId, copiedFigureId) {
this.dispatch = dispatch;
this.sheetId = sheetId;
@@ -39301,6 +39777,9 @@
}
}
class ClipboardFigureImage {
+ dispatch;
+ sheetId;
+ copiedImage;
constructor(dispatch, getters, sheetId, copiedFigureId) {
this.dispatch = dispatch;
this.sheetId = sheetId;
@@ -39321,6 +39800,7 @@
/** State of the clipboard when copying/cutting from the OS clipboard*/
class ClipboardOsState extends ClipboardCellsAbstractState {
+ values;
constructor(content, getters, dispatch, selection) {
super("COPY", getters, dispatch, selection);
this.values = content
@@ -39386,11 +39866,17 @@
* application, and with the OS clipboard as well.
*/
class ClipboardPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.status = "invisible";
- this._isPaintingFormat = false;
- }
+ static layers = [2 /* LAYERS.Clipboard */];
+ static getters = [
+ "getClipboardContent",
+ "getClipboardTextContent",
+ "isCutOperation",
+ "isPaintingFormat",
+ ];
+ status = "invisible";
+ state;
+ lastPasteState;
+ _isPaintingFormat = false;
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -39424,7 +39910,6 @@
return 0 /* CommandResult.Success */;
}
handle(cmd) {
- var _a, _b, _c;
switch (cmd.type) {
case "COPY":
case "CUT":
@@ -39469,7 +39954,7 @@
case "ADD_COLUMNS_ROWS": {
this.status = "invisible";
// If we add a col/row inside or before the cut area, we invalidate the clipboard
- if (((_a = this.state) === null || _a === void 0 ? void 0 : _a.operation) !== "CUT") {
+ if (this.state?.operation !== "CUT") {
return;
}
const isClipboardDirty = this.state.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -39481,7 +39966,7 @@
case "REMOVE_COLUMNS_ROWS": {
this.status = "invisible";
// If we remove a col/row inside or before the cut area, we invalidate the clipboard
- if (((_b = this.state) === null || _b === void 0 ? void 0 : _b.operation) !== "CUT") {
+ if (this.state?.operation !== "CUT") {
return;
}
for (let el of cmd.elements) {
@@ -39501,7 +39986,7 @@
this.status = "invisible";
break;
case "REPEAT_PASTE": {
- (_c = this.lastPasteState) === null || _c === void 0 ? void 0 : _c.paste(cmd.target, {
+ this.lastPasteState?.paste(cmd.target, {
pasteOption: cmd.pasteOption,
shouldPasteCF: true,
selectTarget: true,
@@ -39536,12 +40021,10 @@
* considered as a copy content.
*/
getClipboardContent() {
- var _a;
- return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()) || { [ClipboardMIMEType.PlainText]: "\t" };
+ return this.state?.getClipboardContent() || { [ClipboardMIMEType.PlainText]: "\t" };
}
getClipboardTextContent() {
- var _a;
- return ((_a = this.state) === null || _a === void 0 ? void 0 : _a.getClipboardContent()[ClipboardMIMEType.PlainText]) || "\t";
+ return this.state?.getClipboardContent()[ClipboardMIMEType.PlainText] || "\t";
}
isCutOperation() {
return this.state ? this.state.operation === "CUT" : false;
@@ -39618,13 +40101,6 @@
this.state.drawClipboard(renderingContext);
}
}
- ClipboardPlugin.layers = [2 /* LAYERS.Clipboard */];
- ClipboardPlugin.getters = [
- "getClipboardContent",
- "getClipboardTextContent",
- "isCutOperation",
- "isPaintingFormat",
- ];
/**
* Change the reference types inside the given token, if the token represent a range or a cell
@@ -39714,22 +40190,30 @@
const CELL_DELETED_MESSAGE = _lt("The cell you are trying to edit has been deleted.");
class EditionPlugin extends UIPlugin {
- constructor() {
- super(...arguments);
- this.col = 0;
- this.row = 0;
- this.mode = "inactive";
- this.sheetId = "";
- this.currentContent = "";
- this.currentTokens = [];
- this.selectionStart = 0;
- this.selectionEnd = 0;
- this.selectionInitialStart = 0;
- this.initialContent = "";
- this.previousRef = "";
- this.previousRange = undefined;
- this.colorIndexByRange = {};
- }
+ static getters = [
+ "getEditionMode",
+ "isSelectingForComposer",
+ "showSelectionIndicator",
+ "getCurrentContent",
+ "getComposerSelection",
+ "getCurrentTokens",
+ "getTokenAtCursor",
+ "getComposerHighlights",
+ "getCurrentEditedCell",
+ ];
+ col = 0;
+ row = 0;
+ mode = "inactive";
+ sheetId = "";
+ currentContent = "";
+ currentTokens = [];
+ selectionStart = 0;
+ selectionEnd = 0;
+ selectionInitialStart = 0;
+ initialContent = "";
+ previousRef = "";
+ previousRange = undefined;
+ colorIndexByRange = {};
// ---------------------------------------------------------------------------
// Command Handling
// ---------------------------------------------------------------------------
@@ -40015,9 +40499,8 @@
* @private
*/
startEdition(str, selection) {
- var _a;
const evaluatedCell = this.getters.getActiveCell();
- if (str && ((_a = evaluatedCell.format) === null || _a === void 0 ? void 0 : _a.includes("%")) && isNumber(str)) {
+ if (str && evaluatedCell.format?.includes("%") && isNumber(str)) {
selection = selection || { start: str.length, end: str.length };
str = `${str}%`;
}
@@ -40094,7 +40577,7 @@
}
getComposerContent(position) {
const cell = this.getters.getCell(position);
- if (cell === null || cell === void 0 ? void 0 : cell.isFormula) {
+ if (cell?.isFormula) {
return cell.content;
}
const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
@@ -40105,7 +40588,7 @@
case CellValueType.boolean:
return formattedValue;
case CellValueType.error:
- return (cell === null || cell === void 0 ? void 0 : cell.content) || "";
+ return cell?.content || "";
case CellValueType.number:
if (format && isDateTimeFormat(format)) {
return formattedValue;
@@ -40114,7 +40597,7 @@
}
}
numberComposerContent(value, format) {
- if (format === null || format === void 0 ? void 0 : format.includes("%")) {
+ if (format?.includes("%")) {
return `${value * 100}%`;
}
return numberToString(value);
@@ -40329,17 +40812,6 @@
return this.currentTokens.filter((t) => (t.start <= start && t.end >= start) || (t.start >= start && t.start < end));
}
}
- EditionPlugin.getters = [
- "getEditionMode",
- "isSelectingForComposer",
- "showSelectionIndicator",
- "getCurrentContent",
- "getComposerSelection",
- "getCurrentTokens",
- "getTokenAtCursor",
- "getComposerHighlights",
- "getCurrentEditedCell",
- ];
const selectionStatisticFunctions = [
{
@@ -40377,20 +40849,42 @@
* SelectionPlugin
*/
class GridSelectionPlugin extends UIPlugin {
+ static layers = [6 /* LAYERS.Selection */];
+ static getters = [
+ "getActiveSheet",
+ "getActiveSheetId",
+ "getActiveCell",
+ "getActiveCols",
+ "getActiveRows",
+ "getCurrentStyle",
+ "getSelectedZones",
+ "getSelectedZone",
+ "getSelectedCells",
+ "getStatisticFnResults",
+ "getAggregate",
+ "getSelectedFigureId",
+ "getSelection",
+ "getActivePosition",
+ "getSheetPosition",
+ "isSelected",
+ "isSingleColSelected",
+ "getElementsFromSelection",
+ ];
+ gridSelection = {
+ anchor: {
+ cell: { col: 0, row: 0 },
+ zone: { top: 0, left: 0, bottom: 0, right: 0 },
+ },
+ zones: [{ top: 0, left: 0, bottom: 0, right: 0 }],
+ };
+ selectedFigureId = null;
+ sheetsData = {};
+ moveClient;
+ // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
+ // the main command.
+ activeSheet = null;
constructor(config) {
super(config);
- this.gridSelection = {
- anchor: {
- cell: { col: 0, row: 0 },
- zone: { top: 0, left: 0, bottom: 0, right: 0 },
- },
- zones: [{ top: 0, left: 0, bottom: 0, right: 0 }],
- };
- this.selectedFigureId = null;
- this.sheetsData = {};
- // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
- // the main command.
- this.activeSheet = null;
this.moveClient = config.moveClient;
}
// ---------------------------------------------------------------------------
@@ -40631,6 +41125,14 @@
getSelection() {
return deepCopy(this.gridSelection);
}
+ getSelectedCells() {
+ const sheetId = this.getters.getActiveSheetId();
+ const cells = [];
+ for (const zone of this.gridSelection.zones) {
+ cells.push(...this.getters.getEvaluatedCellsInZone(sheetId, zone));
+ }
+ return cells;
+ }
getSelectedFigureId() {
return this.selectedFigureId;
}
@@ -40700,6 +41202,13 @@
isSelected(zone) {
return !!this.getters.getSelectedZones().find((z) => isEqual(z, zone));
}
+ isSingleColSelected() {
+ const selection = this.getters.getSelectedZones();
+ if (selection.length !== 1 || selection[0].left !== selection[0].right) {
+ return false;
+ }
+ return true;
+ }
/**
* Returns a sorted array of indexes of all columns (respectively rows depending
* on the dimension parameter) intersected by the currently selected zones.
@@ -40942,25 +41451,6 @@
}
}
}
- GridSelectionPlugin.layers = [6 /* LAYERS.Selection */];
- GridSelectionPlugin.getters = [
- "getActiveSheet",
- "getActiveSheetId",
- "getActiveCell",
- "getActiveCols",
- "getActiveRows",
- "getCurrentStyle",
- "getSelectedZones",
- "getSelectedZone",
- "getStatisticFnResults",
- "getAggregate",
- "getSelectedFigureId",
- "getSelection",
- "getActivePosition",
- "getSheetPosition",
- "isSelected",
- "getElementsFromSelection",
- ];
const corePluginRegistry = new Registry()
.add("sheet", SheetPlugin)
@@ -40987,8 +41477,9 @@
.add("sort", SortPlugin)
.add("automatic_sum", AutomaticSumPlugin)
.add("format", FormatPlugin)
+ .add("split_to_columns", SplitToColumnsPlugin)
.add("cell_popovers", CellPopoverPlugin)
- .add("selection_multiuser", SelectionMultiUserPlugin)
+ .add("collaborative", CollaborativePlugin)
.add("history", HistoryPlugin);
// Plugins which have a state, but which should not be shared in collaborative
const statefulUIPluginRegistry = new Registry()
@@ -41012,6 +41503,7 @@
});
class ImageProvider {
+ fileStore;
constructor(fileStore) {
this.fileStore = fileStore;
}
@@ -41061,10 +41553,8 @@
}
`;
class RippleEffect extends owl.Component {
- constructor() {
- super(...arguments);
- this.rippleRef = owl.useRef("ripple");
- }
+ static template = "o-spreadsheet-RippleEffect";
+ rippleRef = owl.useRef("ripple");
setup() {
let animation = undefined;
owl.onMounted(() => {
@@ -41078,7 +41568,7 @@
animation.addEventListener("finish", this.props.onAnimationEnd);
});
owl.onWillUnmount(() => {
- animation === null || animation === void 0 ? void 0 : animation.removeEventListener("finish", this.props.onAnimationEnd);
+ animation?.removeEventListener("finish", this.props.onAnimationEnd);
});
}
get rippleStyle() {
@@ -41099,7 +41589,6 @@
});
}
}
- RippleEffect.template = "o-spreadsheet-RippleEffect";
RippleEffect.props = {
x: String,
y: String,
@@ -41115,12 +41604,19 @@
style: String,
};
class Ripple extends owl.Component {
- constructor() {
- super(...arguments);
- this.childContainer = owl.useRef("childContainer");
- this.state = owl.useState({ ripples: [] });
- this.currentId = 1;
- }
+ static template = "o-spreadsheet-Ripple";
+ static components = { RippleEffect };
+ static defaultProps = {
+ color: "#aaaaaa",
+ opacity: 0.4,
+ duration: 800,
+ enabled: true,
+ onAnimationEnd: () => { },
+ class: "",
+ };
+ childContainer = owl.useRef("childContainer");
+ state = owl.useState({ ripples: [] });
+ currentId = 1;
onClick(ev) {
if (!this.props.enabled)
return;
@@ -41175,8 +41671,7 @@
this.state.ripples.splice(index, 1);
}
getRippleEffectProps(id) {
- var _a;
- const rect = (_a = this.state.ripples.find((r) => r.id === id)) === null || _a === void 0 ? void 0 : _a.rippleRect;
+ const rect = this.state.ripples.find((r) => r.id === id)?.rippleRect;
if (!rect)
throw new Error("Cannot find a ripple with the id " + id);
return {
@@ -41195,16 +41690,6 @@
};
}
}
- Ripple.template = "o-spreadsheet-Ripple";
- Ripple.components = { RippleEffect };
- Ripple.defaultProps = {
- color: "#aaaaaa",
- opacity: 0.4,
- duration: 800,
- enabled: true,
- onAnimationEnd: () => { },
- class: "",
- };
Ripple.props = {
color: { type: String, optional: true },
opacity: { type: Number, optional: true },
@@ -41279,13 +41764,16 @@
}
`;
class BottomBarSheet extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({ isEditing: false });
- this.sheetDivRef = owl.useRef("sheetDiv");
- this.sheetNameRef = owl.useRef("sheetNameSpan");
- this.editionState = "initializing";
- }
+ static template = "o-spreadsheet-BottomBarSheet";
+ static components = { Ripple };
+ static defaultProps = {
+ onMouseDown: () => { },
+ style: "",
+ };
+ state = owl.useState({ isEditing: false });
+ sheetDivRef = owl.useRef("sheetDiv");
+ sheetNameRef = owl.useRef("sheetNameSpan");
+ editionState = "initializing";
setup() {
owl.onMounted(() => {
if (this.isSheetActive) {
@@ -41300,18 +41788,16 @@
});
}
focusInputAndSelectContent() {
- var _a;
if (!this.state.isEditing || !this.sheetNameRef.el)
return;
this.sheetNameRef.el.focus();
const selection = window.getSelection();
if (selection && this.sheetNameRef.el.firstChild) {
- selection.setBaseAndExtent(this.sheetNameRef.el.firstChild, 0, this.sheetNameRef.el.firstChild, ((_a = this.sheetNameRef.el.textContent) === null || _a === void 0 ? void 0 : _a.length) || 0);
+ selection.setBaseAndExtent(this.sheetNameRef.el.firstChild, 0, this.sheetNameRef.el.firstChild, this.sheetNameRef.el.textContent?.length || 0);
}
}
scrollToSheet() {
- var _a, _b;
- (_b = (_a = this.sheetDivRef.el) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.sheetDivRef.el?.scrollIntoView?.();
}
onFocusOut() {
if (this.state.isEditing && this.editionState !== "initializing") {
@@ -41352,20 +41838,18 @@
this.editionState = "initializing";
}
stopEdition() {
- var _a;
if (!this.state.isEditing)
return;
this.state.isEditing = false;
this.editionState = "initializing";
- (_a = this.sheetNameRef.el) === null || _a === void 0 ? void 0 : _a.blur();
+ this.sheetNameRef.el?.blur();
const inputValue = this.getInputContent() || "";
interactiveRenameSheet(this.env, this.props.sheetId, inputValue, () => this.startEdition());
}
cancelEdition() {
- var _a;
this.state.isEditing = false;
this.editionState = "initializing";
- (_a = this.sheetNameRef.el) === null || _a === void 0 ? void 0 : _a.blur();
+ this.sheetNameRef.el?.blur();
this.setInputContent(this.sheetName);
}
onIconClick(ev) {
@@ -41381,8 +41865,7 @@
this.props.openContextMenu(this.contextMenuRegistry, ev);
}
getInputContent() {
- var _a;
- return (_a = this.sheetNameRef.el) === null || _a === void 0 ? void 0 : _a.textContent;
+ return this.sheetNameRef.el?.textContent;
}
setInputContent(content) {
if (this.sheetNameRef.el)
@@ -41403,12 +41886,6 @@
return this.env.model.getters.getSheetName(this.props.sheetId);
}
}
- BottomBarSheet.template = "o-spreadsheet-BottomBarSheet";
- BottomBarSheet.components = { Ripple };
- BottomBarSheet.defaultProps = {
- onMouseDown: () => { },
- style: "",
- };
BottomBarSheet.props = {
sheetId: String,
openContextMenu: Function,
@@ -41431,11 +41908,10 @@
}
`;
class BottomBarStatistic extends owl.Component {
- constructor() {
- super(...arguments);
- this.selectedStatisticFn = "";
- this.statisticFnResults = {};
- }
+ static template = "o-spreadsheet-BottomBarStatisic";
+ static components = { Ripple };
+ selectedStatisticFn = "";
+ statisticFnResults = {};
setup() {
this.statisticFnResults = this.env.model.getters.getStatisticFnResults();
owl.onWillUpdateProps(() => {
@@ -41478,16 +41954,34 @@
return fnName + ": " + (fnValue !== undefined ? formatValue(fnValue) : "__");
}
}
- BottomBarStatistic.template = "o-spreadsheet-BottomBarStatisic";
- BottomBarStatistic.components = { Ripple };
BottomBarStatistic.props = {
openContextMenu: Function,
closeContextMenu: Function,
};
class DOMDndHelper {
+ draggedItemId;
+ items;
+ containerEl;
+ initialMousePosition;
+ currentMousePosition;
+ minPosition;
+ maxPosition;
+ edgeScrollIntervalId;
+ edgeScrollOffset = 0;
+ onChange;
+ onCancel;
+ onDragEnd;
+ /**
+ * The dead zone is an area in which the mousemove events are ignored.
+ *
+ * This is useful when swapping the dragged item with a larger item. After the swap,
+ * the mouse is still hovering on the item we just swapped with. In this case, we don't want
+ * a mouse move to trigger another swap the other way around, so we create a dead zone. We will clear
+ * the dead zone when the mouse leaves the swapped item.
+ */
+ deadZone;
constructor(args) {
- this.edgeScrollOffset = 0;
this.items = args.items.map((item) => ({ ...item, positionAtStart: item.position }));
this.draggedItemId = args.draggedItemId;
this.containerEl = args.containerEl;
@@ -41668,28 +42162,28 @@
}
`;
class BottomBar extends owl.Component {
- constructor() {
- super(...arguments);
- this.bottomBarRef = owl.useRef("bottomBar");
- this.sheetListRef = owl.useRef("sheetList");
- this.targetScroll = undefined;
- this.state = owl.useState({
- isSheetListScrollableLeft: false,
- isSheetListScrollableRight: false,
- });
- this.menuMaxHeight = MENU_MAX_HEIGHT;
- this.menuState = owl.useState({
- isOpen: false,
- menuId: undefined,
- position: null,
- menuItems: [],
- });
- this.sheetState = owl.useState({
- sheetList: this.getVisibleSheets(),
- isDnd: false,
- sheetDndPositions: undefined,
- });
- }
+ static template = "o-spreadsheet-BottomBar";
+ static components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
+ bottomBarRef = owl.useRef("bottomBar");
+ sheetListRef = owl.useRef("sheetList");
+ dndHelper;
+ targetScroll = undefined;
+ state = owl.useState({
+ isSheetListScrollableLeft: false,
+ isSheetListScrollableRight: false,
+ });
+ menuMaxHeight = MENU_MAX_HEIGHT;
+ menuState = owl.useState({
+ isOpen: false,
+ menuId: undefined,
+ position: null,
+ menuItems: [],
+ });
+ sheetState = owl.useState({
+ sheetList: this.getVisibleSheets(),
+ isDnd: false,
+ sheetDndPositions: undefined,
+ });
setup() {
owl.onWillUpdateProps(() => {
this.updateScrollState();
@@ -41701,13 +42195,11 @@
this.sheetState.sheetList = visibleSheets;
});
owl.onWillUnmount(() => {
- var _a;
- (_a = this.dndHelper) === null || _a === void 0 ? void 0 : _a.destroy();
+ this.dndHelper?.destroy();
});
}
isDragged(sheetId) {
- var _a;
- return this.sheetState.isDnd && ((_a = this.dndHelper) === null || _a === void 0 ? void 0 : _a.draggedItemId) === sheetId;
+ return this.sheetState.isDnd && this.dndHelper?.draggedItemId === sheetId;
}
clickAddSheet(ev) {
const activeSheetId = this.env.model.getters.getActiveSheetId();
@@ -41849,11 +42341,10 @@
this.stopDragging();
}
getSheetStyle(sheetId) {
- var _a;
const style = {};
if (this.sheetState.isDnd) {
style.position = "relative";
- style.left = (((_a = this.sheetState.sheetDndPositions) === null || _a === void 0 ? void 0 : _a[sheetId]) || 0) + "px";
+ style.left = (this.sheetState.sheetDndPositions?.[sheetId] || 0) + "px";
style.transition = "left 0.5s";
style.cursor = "move";
}
@@ -41904,8 +42395,6 @@
return this.sheetListRef.el.scrollWidth - this.sheetListRef.el.clientWidth;
}
}
- BottomBar.template = "o-spreadsheet-BottomBar";
- BottomBar.components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
BottomBar.props = {
onClick: Function,
};
@@ -41918,6 +42407,18 @@
`;
let tKey = 1;
class SpreadsheetDashboard extends owl.Component {
+ static template = "o-spreadsheet-SpreadsheetDashboard";
+ static components = {
+ GridOverlay,
+ GridPopover,
+ Popover,
+ VerticalScrollBar,
+ HorizontalScrollBar,
+ FilterIconsOverlay,
+ };
+ onMouseWheel;
+ canvasPosition;
+ hoveredCell;
setup() {
const gridRef = owl.useRef("grid");
this.canvasPosition = useAbsoluteBoundingRect(gridRef);
@@ -42025,15 +42526,6 @@
return { ...this.canvasPosition, ...this.env.model.getters.getSheetViewDimensionWithHeaders() };
}
}
- SpreadsheetDashboard.template = "o-spreadsheet-SpreadsheetDashboard";
- SpreadsheetDashboard.components = {
- GridOverlay,
- GridPopover,
- Popover,
- VerticalScrollBar,
- HorizontalScrollBar,
- FilterIconsOverlay,
- };
SpreadsheetDashboard.props = {};
css /* scss */ `
@@ -42104,9 +42596,15 @@
}
}
- .o-sidepanel-error {
- color: red;
+ .o-sidepanel-error,
+ .o-sidepanel-warning {
margin-top: 10px;
+
+ .o-icon {
+ margin-right: 5px;
+ height: 1.2em;
+ width: 1.2em;
+ }
}
.o-sidePanelButtons {
@@ -42164,6 +42662,16 @@
text-align: left;
}
+ .o-checkbox {
+ label {
+ display: flex;
+ justify-items: center;
+ input {
+ margin-right: 5px;
+ }
+ }
+ }
+
.o-inflection {
table {
table-layout: fixed;
@@ -42203,6 +42711,8 @@
}
`;
class SidePanel extends owl.Component {
+ static template = "o-spreadsheet-SidePanel";
+ state;
setup() {
this.state = owl.useState({
panel: sidePanelRegistry.get(this.props.component),
@@ -42215,7 +42725,6 @@
: this.state.panel.title;
}
}
- SidePanel.template = "o-spreadsheet-SidePanel";
SidePanel.props = {
component: String,
panelProps: { type: Object, optional: true },
@@ -42238,10 +42747,8 @@
}
`;
class ActionButton extends owl.Component {
- constructor() {
- super(...arguments);
- this.actionButton = createAction(this.props.action);
- }
+ static template = "o-spreadsheet-ActionButton";
+ actionButton = createAction(this.props.action);
setup() {
owl.onWillUpdateProps(() => (this.actionButton = createAction(this.props.action)));
}
@@ -42252,8 +42759,7 @@
return this.actionButton.isEnabled(this.env);
}
get isActive() {
- var _a, _b;
- return (_b = (_a = this.actionButton).isActive) === null || _b === void 0 ? void 0 : _b.call(_a, this.env);
+ return this.actionButton.isActive?.(this.env);
}
get title() {
const name = this.actionButton.name(this.env);
@@ -42264,10 +42770,9 @@
return this.actionButton.icon;
}
onClick(ev) {
- var _a, _b, _c, _d;
if (this.isEnabled) {
- (_b = (_a = this.props).onClick) === null || _b === void 0 ? void 0 : _b.call(_a, ev);
- (_d = (_c = this.actionButton).execute) === null || _d === void 0 ? void 0 : _d.call(_c, this.env);
+ this.props.onClick?.(ev);
+ this.actionButton.execute?.(this.env);
}
}
get buttonStyle() {
@@ -42281,7 +42786,13 @@
return "";
}
}
- ActionButton.template = "o-spreadsheet-ActionButton";
+ ActionButton.props = {
+ action: Object,
+ hasTriangleDownIcon: { type: Boolean, optional: true },
+ selectedColor: { type: String, optional: true },
+ class: { type: String, optional: true },
+ onClick: { type: Function, optional: true },
+ };
const COMPOSER_MAX_HEIGHT = 100;
css /* scss */ `
@@ -42298,6 +42809,8 @@
}
`;
class TopBarComposer extends owl.Component {
+ static template = "o-spreadsheet-TopBarComposer";
+ static components = { Composer };
get composerStyle() {
const style = {
padding: "5px 0px 5px 8px",
@@ -42319,8 +42832,6 @@
});
}
}
- TopBarComposer.template = "o-spreadsheet-TopBarComposer";
- TopBarComposer.components = { Composer };
TopBarComposer.props = {
focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
onComposerContentFocused: Function,
@@ -42354,13 +42865,12 @@
}
`;
class FontSizeEditor extends owl.Component {
- constructor() {
- super(...arguments);
- this.fontSizes = FONT_SIZES;
- this.dropdown = owl.useState({ isOpen: false });
- this.inputRef = owl.useRef("inputFontSize");
- this.rootEditorRef = owl.useRef("FontSizeEditor");
- }
+ static template = "o-spreadsheet-FontSizeEditor";
+ static components = {};
+ fontSizes = FONT_SIZES;
+ dropdown = owl.useState({ isOpen: false });
+ inputRef = owl.useRef("inputFontSize");
+ rootEditorRef = owl.useRef("FontSizeEditor");
setup() {
owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
}
@@ -42412,8 +42922,6 @@
}
}
}
- FontSizeEditor.template = "o-spreadsheet-FontSizeEditor";
- FontSizeEditor.components = {};
FontSizeEditor.props = {
onToggle: Function,
dropdownStyle: String,
@@ -42529,26 +43037,32 @@
}
`;
class TopBar extends owl.Component {
- constructor() {
- super(...arguments);
- this.state = owl.useState({
- menuState: { isOpen: false, position: null, menuItems: [] },
- activeTool: "",
- fillColor: "#ffffff",
- textColor: "#000000",
- });
- this.isSelectingMenu = false;
- this.openedEl = null;
- this.menus = [];
- this.EDIT = ACTION_EDIT;
- this.FORMAT = ACTION_FORMAT;
- this.VIEW = ACTION_VIEW;
- this.formatNumberMenuItemSpec = formatNumberMenuItemSpec;
- this.isntToolbarMenu = false;
- }
+ static template = "o-spreadsheet-TopBar";
get dropdownStyle() {
return `max-height:${this.props.dropdownMaxHeight}px`;
}
+ static components = {
+ ColorPickerWidget,
+ ColorPicker,
+ Menu,
+ TopBarComposer,
+ FontSizeEditor,
+ ActionButton,
+ };
+ state = owl.useState({
+ menuState: { isOpen: false, position: null, menuItems: [] },
+ activeTool: "",
+ fillColor: "#ffffff",
+ textColor: "#000000",
+ });
+ isSelectingMenu = false;
+ openedEl = null;
+ menus = [];
+ EDIT = ACTION_EDIT;
+ FORMAT = ACTION_FORMAT;
+ VIEW = ACTION_VIEW;
+ formatNumberMenuItemSpec = formatNumberMenuItemSpec;
+ isntToolbarMenu = false;
setup() {
owl.useExternalListener(window, "click", this.onExternalClick);
owl.onWillStart(() => this.updateCellState());
@@ -42636,15 +43150,6 @@
this.onClick();
}
}
- TopBar.template = "o-spreadsheet-TopBar";
- TopBar.components = {
- ColorPickerWidget,
- ColorPicker,
- Menu,
- TopBarComposer,
- FontSizeEditor,
- ActionButton,
- };
TopBar.props = {
onClick: Function,
focusComposer: String,
@@ -42656,21 +43161,20 @@
return new WebClipboardWrapper(navigator.clipboard);
}
class WebClipboardWrapper {
+ clipboard;
// Can be undefined because navigator.clipboard doesn't exist in old browsers
constructor(clipboard) {
this.clipboard = clipboard;
}
async write(clipboardContent) {
- var _a;
try {
- (_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.write(this.getClipboardItems(clipboardContent));
+ this.clipboard?.write(this.getClipboardItems(clipboardContent));
}
catch (e) { }
}
async writeText(text) {
- var _a;
try {
- (_a = this.clipboard) === null || _a === void 0 ? void 0 : _a.writeText(text);
+ this.clipboard?.writeText(text);
}
catch (e) { }
}
@@ -42686,7 +43190,7 @@
return { status: "ok", content: clipboardContent };
}
catch (e) {
- const status = (permissionResult === null || permissionResult === void 0 ? void 0 : permissionResult.state) === "denied" ? "permissionDenied" : "notImplemented";
+ const status = permissionResult?.state === "denied" ? "permissionDenied" : "notImplemented";
return { status };
}
}
@@ -42794,10 +43298,14 @@
`;
const t = (s) => s;
class Spreadsheet extends owl.Component {
- constructor() {
- super(...arguments);
- this.isViewportTooSmall = false;
- }
+ static template = "o-spreadsheet-Spreadsheet";
+ static components = { TopBar, Grid, BottomBar, SidePanel, SpreadsheetDashboard };
+ static _t = t;
+ sidePanel;
+ composer;
+ _focusGrid;
+ keyDownMapping;
+ isViewportTooSmall = false;
get model() {
return this.props.model;
}
@@ -42887,10 +43395,9 @@
this.sidePanel.isOpen = true;
}
closeSidePanel() {
- var _a, _b;
this.sidePanel.isOpen = false;
this.focusGrid();
- (_b = (_a = this.sidePanel.panelProps) === null || _a === void 0 ? void 0 : _a.onCloseSidePanel) === null || _b === void 0 ? void 0 : _b.call(_a);
+ this.sidePanel.panelProps?.onCloseSidePanel?.();
}
toggleSidePanel(panel, panelProps) {
if (this.sidePanel.isOpen && panel === this.sidePanel.component) {
@@ -42964,17 +43471,12 @@
return height;
}
}
- Spreadsheet.template = "o-spreadsheet-Spreadsheet";
- Spreadsheet.components = { TopBar, Grid, BottomBar, SidePanel, SpreadsheetDashboard };
- Spreadsheet._t = t;
Spreadsheet.props = {
model: Object,
};
class LocalTransportService {
- constructor() {
- this.listeners = [];
- }
+ listeners = [];
sendMessage(message) {
for (const { callback } of this.listeners) {
callback(message);
@@ -43016,6 +43518,8 @@
* @param operations initial operations
*/
class Branch {
+ buildTransformation;
+ operations;
constructor(buildTransformation, operations = []) {
this.buildTransformation = buildTransformation;
this.operations = operations;
@@ -43031,8 +43535,7 @@
return operation;
}
getLastOperationId() {
- var _a;
- return (_a = this.operations[this.operations.length - 1]) === null || _a === void 0 ? void 0 : _a.id;
+ return this.operations[this.operations.length - 1]?.id;
}
/**
* Get the id of the operation appears first in the list of operations
@@ -43140,6 +43643,8 @@
* to revert it).
*/
class Operation {
+ id;
+ data;
constructor(id, data) {
this.id = id;
this.data = data;
@@ -43149,6 +43654,8 @@
}
}
class LazyOperation {
+ id;
+ lazyData;
constructor(id, lazyData) {
this.id = id;
this.lazyData = lazyData;
@@ -43172,6 +43679,7 @@
* ```
*/
class OperationSequence {
+ operations;
constructor(operations) {
this.operations = operations;
}
@@ -43253,9 +43761,11 @@
*
*/
class Tree {
+ buildTransformation;
+ branches;
+ branchingOperationIds = new Map();
constructor(buildTransformation, initialBranch) {
this.buildTransformation = buildTransformation;
- this.branchingOperationIds = new Map();
this.branches = [initialBranch];
}
/**
@@ -43294,8 +43804,7 @@
* ```
*/
insertOperationLast(branch, operation) {
- var _a;
- const insertAfter = branch.getLastOperationId() || ((_a = this.previousBranch(branch)) === null || _a === void 0 ? void 0 : _a.getLastOperationId());
+ const insertAfter = branch.getLastOperationId() || this.previousBranch(branch)?.getLastOperationId();
branch.append(operation);
if (insertAfter) {
this.insertPrevious(branch, operation, insertAfter);
@@ -43590,6 +44099,13 @@
}
class SelectiveHistory {
+ applyOperation;
+ revertOperation;
+ buildEmpty;
+ buildTransformation;
+ HEAD_BRANCH;
+ HEAD_OPERATION;
+ tree;
/**
* The selective history is a data structure used to register changes/updates of a state.
* Each change/update is called an "operation".
@@ -43809,9 +44325,12 @@
*
*/
class EventStream {
- constructor() {
- this.observers = [];
- }
+ observers = [];
+ /**
+ * the one we default to when someone releases the stream by themeselves
+ */
+ defaultSubscription;
+ mainSubscription;
registerAsDefault(owner, callbacks) {
this.defaultSubscription = { owner, callbacks };
if (!this.mainSubscription) {
@@ -43828,18 +44347,16 @@
* Capture the stream for yourself
*/
capture(owner, callbacks) {
- var _a, _b, _c;
if (this.observers.find((sub) => sub.owner === owner)) {
throw new Error("You are already subscribed forever");
}
- if (((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) && this.mainSubscription.owner !== owner) {
- (_c = (_b = this.mainSubscription.callbacks).release) === null || _c === void 0 ? void 0 : _c.call(_b);
+ if (this.mainSubscription?.owner && this.mainSubscription.owner !== owner) {
+ this.mainSubscription.callbacks.release?.();
}
this.mainSubscription = { owner, callbacks };
}
release(owner) {
- var _a;
- if (((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) !== owner ||
+ if (this.mainSubscription?.owner !== owner ||
this.observers.find((sub) => sub.owner === owner)) {
return;
}
@@ -43849,26 +44366,23 @@
* Release whichever subscription in charge and get back to the default subscription
*/
getBackToDefault() {
- var _a, _b, _c;
if (this.mainSubscription === this.defaultSubscription) {
return;
}
- (_c = (_a = this.mainSubscription) === null || _a === void 0 ? void 0 : (_b = _a.callbacks).release) === null || _c === void 0 ? void 0 : _c.call(_b);
+ this.mainSubscription?.callbacks.release?.();
this.mainSubscription = this.defaultSubscription;
}
/**
* Check if you are currently the main stream consumer
*/
isListening(owner) {
- var _a;
- return ((_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.owner) === owner;
+ return this.mainSubscription?.owner === owner;
}
/**
* Push an event to the stream and broadcast it to consumers
*/
send(event) {
- var _a;
- (_a = this.mainSubscription) === null || _a === void 0 ? void 0 : _a.callbacks.handleEvent(event);
+ this.mainSubscription?.callbacks.handleEvent(event);
[...this.observers].forEach((sub) => sub.callbacks.handleEvent(event));
}
}
@@ -43878,6 +44392,15 @@
* with the new selected anchor
*/
class SelectionStreamProcessorImpl {
+ getters;
+ stream;
+ /**
+ * "Active" anchor used as a reference to compute new anchors
+ * An new initial value is given each time the stream is
+ * captured. The value is updated with each new anchor.
+ */
+ anchor;
+ defaultAnchor;
constructor(getters) {
this.getters = getters;
this.stream = new EventStream();
@@ -44406,10 +44929,8 @@
}
class StateObserver {
- constructor() {
- this.changes = [];
- this.commands = [];
- }
+ changes = [];
+ commands = [];
/**
* Record the changes which could happen in the given callback, save them in a
* new revision with the given id and userId.
@@ -44835,12 +45356,9 @@
else {
// Shouldn't we always output the value then ?
const value = cell.value;
- // what if value = 0? Is this condition correct?
- if (value) {
- const type = getCellType(value);
- attrs.push(["t", type]);
- node = escapeXml /*xml*/ `${value}`;
- }
+ const type = getCellType(value);
+ attrs.push(["t", type]);
+ node = escapeXml /*xml*/ `${value}`;
return { attrs, node };
}
}
@@ -44919,10 +45437,10 @@
const internalDate = parseDateTime(value);
if (internalDate) {
let format = [];
- if (value.match(mdyDateRegexp) || value.match(ymdDateRegexp)) {
+ if (mdyDateRegexp.test(value) || ymdDateRegexp.test(value)) {
format.push("yyyy-mm-dd");
}
- if (value.match(timeRegexp)) {
+ if (timeRegexp.test(value)) {
format.push("hh:mm:ss");
}
return {
@@ -45191,7 +45709,7 @@
];
const figuresNodes = [];
for (const [figureIndex, figure] of Object.entries(figures)) {
- switch (figure === null || figure === void 0 ? void 0 : figure.tag) {
+ switch (figure?.tag) {
case "chart":
figuresNodes.push(createChartDrawing(figure, sheet, drawingRelIds[figureIndex]));
break;
@@ -45456,11 +45974,16 @@
if (style.alignment && style.alignment.horizontal) {
alignAttrs.push(["horizontal", style.alignment.horizontal]);
}
- styleNodes.push(escapeXml /*xml*/ `
-
- ${alignAttrs ? escapeXml /*xml*/ `` : ""}
-
- `);
+ if (style.alignment && style.alignment.wrapText) {
+ alignAttrs.push(["wrapText", "1"]);
+ }
+ if (alignAttrs.length > 0) {
+ attributes.push(["applyAlignment", "1"]); // for Libre Office
+ styleNodes.push(escapeXml /*xml*/ `${escapeXml /*xml*/ ``} `);
+ }
+ else {
+ styleNodes.push(escapeXml /*xml*/ ``);
+ }
}
return escapeXml /*xml*/ `
@@ -45558,12 +46081,11 @@
`;
}
function addTableColumns(table, sheetData) {
- var _a;
const tableZone = toZone(table.range);
const columns = [];
for (const i of range(0, zoneToDimension(tableZone).numberOfCols)) {
const colHeaderXc = toXC(tableZone.left + i, tableZone.top);
- const colName = ((_a = sheetData.cells[colHeaderXc]) === null || _a === void 0 ? void 0 : _a.content) || `col${i}`;
+ const colName = sheetData.cells[colHeaderXc]?.content || `col${i}`;
const colAttributes = [
["id", i + 1],
["name", colName],
@@ -45661,12 +46183,11 @@
});
}
function addHyperlinks(construct, data, sheetIndex) {
- var _a;
const sheet = data.sheets[sheetIndex];
const cells = sheet.cells;
const linkNodes = [];
for (const xc in cells) {
- const content = (_a = cells[xc]) === null || _a === void 0 ? void 0 : _a.content;
+ const content = cells[xc]?.content;
if (content && isMarkdownLink(content)) {
const { label, url } = parseMarkdownLink(content);
if (isSheetUrl(url)) {
@@ -45733,9 +46254,13 @@
${bottomRight}
`;
}
+ const sheetViewAttrs = [
+ ["showGridLines", sheet.areGridLinesVisible ? 1 : 0],
+ ["workbookViewId", 0],
+ ];
let sheetView = escapeXml /*xml*/ `
-
+
${splitPanes}
@@ -45957,10 +46482,18 @@
overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
}
}
+ const relsAttributes = [
+ ["Extension", "rels"],
+ ["ContentType", "application/vnd.openxmlformats-package.relationships+xml"],
+ ];
+ const xmlAttributes = [
+ ["Extension", "xml"],
+ ["ContentType", "application/xml"],
+ ];
const xml = escapeXml /*xml*/ `
-
-
+
+
${joinXmlNodes(overrideNodes)}
`;
@@ -45988,112 +46521,53 @@
Status[Status["Finalizing"] = 3] = "Finalizing";
})(Status || (Status = {}));
class Model extends EventBus {
+ corePlugins = [];
+ featurePlugins = [];
+ statefulUIPlugins = [];
+ coreViewsPlugins = [];
+ range;
+ session;
+ /**
+ * In a collaborative context, some commands can be replayed, we have to ensure
+ * that these commands are not replayed on the UI plugins.
+ */
+ isReplayingCommand = false;
+ /**
+ * A plugin can draw some contents on the canvas. But even better: it can do
+ * so multiple times. The order of the render calls will determine a list of
+ * "layers" (i.e., earlier calls will be obviously drawn below later calls).
+ * This list simply keeps the renderers+layer information so the drawing code
+ * can just iterate on it
+ */
+ renderers = [];
+ /**
+ * Internal status of the model. Important for command handling coordination
+ */
+ status = 0 /* Status.Ready */;
+ /**
+ * The config object contains some configuration flag and callbacks
+ */
+ config;
+ corePluginConfig;
+ uiPluginConfig;
+ state;
+ selection;
+ /**
+ * Getters are the main way the rest of the UI read data from the model. Also,
+ * it is shared between all plugins, so they can also communicate with each
+ * other.
+ */
+ getters;
+ /**
+ * Getters that are accessible from the core plugins. It's a subset of `getters`,
+ * without the UI getters
+ */
+ coreGetters;
+ uuidGenerator;
+ handlers = [];
+ coreHandlers = [];
constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
super();
- this.corePlugins = [];
- this.featurePlugins = [];
- this.statefulUIPlugins = [];
- this.coreViewsPlugins = [];
- /**
- * In a collaborative context, some commands can be replayed, we have to ensure
- * that these commands are not replayed on the UI plugins.
- */
- this.isReplayingCommand = false;
- /**
- * A plugin can draw some contents on the canvas. But even better: it can do
- * so multiple times. The order of the render calls will determine a list of
- * "layers" (i.e., earlier calls will be obviously drawn below later calls).
- * This list simply keeps the renderers+layer information so the drawing code
- * can just iterate on it
- */
- this.renderers = [];
- /**
- * Internal status of the model. Important for command handling coordination
- */
- this.status = 0 /* Status.Ready */;
- this.handlers = [];
- this.coreHandlers = [];
- /**
- * Check if a command can be dispatched, and returns a DispatchResult object with the possible
- * reasons the dispatch failed.
- */
- this.canDispatch = (type, payload) => {
- return this.checkDispatchAllowed({ ...payload, type });
- };
- /**
- * The dispatch method is the only entry point to manipulate data in the model.
- * This is through this method that commands are dispatched most of the time
- * recursively until no plugin want to react anymore.
- *
- * CoreCommands dispatched from this function are saved in the history.
- *
- * Small technical detail: it is defined as an arrow function. There are two
- * reasons for this:
- * 1. this means that the dispatch method can be "detached" from the model,
- * which is done when it is put in the environment (see the Spreadsheet
- * component)
- * 2. This allows us to define its type by using the interface CommandDispatcher
- */
- this.dispatch = (type, payload) => {
- const command = { ...payload, type };
- let status = this.status;
- if (this.getters.isReadonly() && !canExecuteInReadonly(command)) {
- return new DispatchResult(67 /* CommandResult.Readonly */);
- }
- if (!this.session.canApplyOptimisticUpdate()) {
- return new DispatchResult(64 /* CommandResult.WaitingSessionConfirmation */);
- }
- switch (status) {
- case 0 /* Status.Ready */:
- const result = this.checkDispatchAllowed(command);
- if (!result.isSuccessful) {
- return result;
- }
- this.status = 1 /* Status.Running */;
- const { changes, commands } = this.state.recordChanges(() => {
- if (isCoreCommand(command)) {
- this.state.addCommand(command);
- }
- this.dispatchToHandlers(this.handlers, command);
- this.finalize();
- });
- this.session.save(command, commands, changes);
- this.status = 0 /* Status.Ready */;
- this.trigger("update");
- break;
- case 1 /* Status.Running */:
- if (isCoreCommand(command)) {
- const dispatchResult = this.checkDispatchAllowed(command);
- if (!dispatchResult.isSuccessful) {
- return dispatchResult;
- }
- this.state.addCommand(command);
- }
- this.dispatchToHandlers(this.handlers, command);
- break;
- case 3 /* Status.Finalizing */:
- throw new Error("Cannot dispatch commands in the finalize state");
- case 2 /* Status.RunningCore */:
- if (isCoreCommand(command)) {
- throw new Error(`A UI plugin cannot dispatch ${type} while handling a core command`);
- }
- this.dispatchToHandlers(this.handlers, command);
- }
- return DispatchResult.Success;
- };
- /**
- * Dispatch a command from a Core Plugin (or the History).
- * A command dispatched from this function is not added to the history.
- */
- this.dispatchFromCorePlugin = (type, payload) => {
- const command = { ...payload, type };
- const previousStatus = this.status;
- this.status = 2 /* Status.RunningCore */;
- const handlers = this.isReplayingCommand ? this.coreHandlers : this.handlers;
- this.dispatchToHandlers(handlers, command);
- this.status = previousStatus;
- return DispatchResult.Success;
- };
stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
const workbookData = load(data, verboseImport);
this.state = new StateObserver();
@@ -46112,9 +46586,6 @@
this.getters = {
isReadonly: () => this.config.mode === "readonly" || this.config.mode === "dashboard",
isDashboard: () => this.config.mode === "dashboard",
- getClient: this.session.getClient.bind(this.session),
- getConnectedClients: this.session.getConnectedClients.bind(this.session),
- isFullySynchronized: this.session.isFullySynchronized.bind(this.session),
};
this.uuidGenerator.setIsFastStrategy(true);
// Initiate stream processor
@@ -46319,12 +46790,92 @@
}
this.status = 0 /* Status.Ready */;
}
+ /**
+ * Check if a command can be dispatched, and returns a DispatchResult object with the possible
+ * reasons the dispatch failed.
+ */
+ canDispatch = (type, payload) => {
+ return this.checkDispatchAllowed(createCommand(type, payload));
+ };
+ /**
+ * The dispatch method is the only entry point to manipulate data in the model.
+ * This is through this method that commands are dispatched most of the time
+ * recursively until no plugin want to react anymore.
+ *
+ * CoreCommands dispatched from this function are saved in the history.
+ *
+ * Small technical detail: it is defined as an arrow function. There are two
+ * reasons for this:
+ * 1. this means that the dispatch method can be "detached" from the model,
+ * which is done when it is put in the environment (see the Spreadsheet
+ * component)
+ * 2. This allows us to define its type by using the interface CommandDispatcher
+ */
+ dispatch = (type, payload) => {
+ const command = createCommand(type, payload);
+ let status = this.status;
+ if (this.getters.isReadonly() && !canExecuteInReadonly(command)) {
+ return new DispatchResult(67 /* CommandResult.Readonly */);
+ }
+ if (!this.session.canApplyOptimisticUpdate()) {
+ return new DispatchResult(64 /* CommandResult.WaitingSessionConfirmation */);
+ }
+ switch (status) {
+ case 0 /* Status.Ready */:
+ const result = this.checkDispatchAllowed(command);
+ if (!result.isSuccessful) {
+ return result;
+ }
+ this.status = 1 /* Status.Running */;
+ const { changes, commands } = this.state.recordChanges(() => {
+ if (isCoreCommand(command)) {
+ this.state.addCommand(command);
+ }
+ this.dispatchToHandlers(this.handlers, command);
+ this.finalize();
+ });
+ this.session.save(command, commands, changes);
+ this.status = 0 /* Status.Ready */;
+ this.trigger("update");
+ break;
+ case 1 /* Status.Running */:
+ if (isCoreCommand(command)) {
+ const dispatchResult = this.checkDispatchAllowed(command);
+ if (!dispatchResult.isSuccessful) {
+ return dispatchResult;
+ }
+ this.state.addCommand(command);
+ }
+ this.dispatchToHandlers(this.handlers, command);
+ break;
+ case 3 /* Status.Finalizing */:
+ throw new Error("Cannot dispatch commands in the finalize state");
+ case 2 /* Status.RunningCore */:
+ if (isCoreCommand(command)) {
+ throw new Error(`A UI plugin cannot dispatch ${type} while handling a core command`);
+ }
+ this.dispatchToHandlers(this.handlers, command);
+ }
+ return DispatchResult.Success;
+ };
+ /**
+ * Dispatch a command from a Core Plugin (or the History).
+ * A command dispatched from this function is not added to the history.
+ */
+ dispatchFromCorePlugin = (type, payload) => {
+ const command = createCommand(type, payload);
+ const previousStatus = this.status;
+ this.status = 2 /* Status.RunningCore */;
+ const handlers = this.isReplayingCommand ? this.coreHandlers : this.handlers;
+ this.dispatchToHandlers(handlers, command);
+ this.status = previousStatus;
+ return DispatchResult.Success;
+ };
/**
* Dispatch the given command to the given handlers.
* It will call `beforeHandle` and `handle`
*/
dispatchToHandlers(handlers, command) {
- command = deepCopy(command);
for (const handler of handlers) {
handler.beforeHandle(command);
}
@@ -46406,6 +46957,11 @@
}
}
}
+ function createCommand(type, payload = {}) {
+ const command = deepCopy(payload);
+ command.type = type;
+ return command;
+ }
/**
* We export here all entities that needs to be accessed publicly by Odoo.
@@ -46512,7 +47068,6 @@
exports.AbstractChart = AbstractChart;
exports.CorePlugin = CorePlugin;
- exports.DATETIME_FORMAT = DATETIME_FORMAT;
exports.DispatchResult = DispatchResult;
exports.EvaluationError = EvaluationError;
exports.Model = Model;
@@ -46544,9 +47099,9 @@
Object.defineProperty(exports, '__esModule', { value: true });
- __info__.version = '16.3.0-alpha.5';
- __info__.date = '2023-04-21T08:03:01.799Z';
- __info__.hash = '6304f41';
+ __info__.version = '16.3.0-alpha.7';
+ __info__.date = '2023-05-02T13:11:44.262Z';
+ __info__.hash = '77c8f5f';
})(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 ecf70b2d6b3..6c9b1d8fc20 100644
--- a/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
+++ b/addons/spreadsheet/static/src/o_spreadsheet/o_spreadsheet.xml
@@ -1102,12 +1102,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1115,8 +1163,138 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1129,12 +1307,19 @@
/>
+
+
+
@@ -1162,28 +1347,20 @@
@@ -1191,19 +1368,15 @@
-
@@ -1211,7 +1384,7 @@
@@ -1219,196 +1392,143 @@
-
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
-
-
+
+
-
-
-
+
-
-
-
-
-
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-
+
+
+
@@ -1416,10 +1536,7 @@
-
+
@@ -1589,6 +1706,38 @@
/>
+
+
+
+
+
+
+
+
+
+ A
+ Z
+
+
+
+
+
+
+ A
+ Z
+
+
@@ -1628,6 +1776,14 @@
/>
+
+
+
+
+
.00
- 123
+
+
+
+
+
+
+
+ A
+ A
+
+
+
+
+
+
@@ -3171,6 +3353,69 @@
+
+
+
+
Separator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+