@odoo/o-spreadsheet 16.4.0-alpha.6 → 16.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -235,6 +235,7 @@ const DEFAULT_FONT_SIZE = 10;
235
235
  const HEADER_FONT_SIZE = 11;
236
236
  const DEFAULT_FONT = "'Roboto', arial";
237
237
  const DEFAULT_VERTICAL_ALIGN = "bottom";
238
+ const DEFAULT_WRAPPING_MODE = "overflow";
238
239
  // Borders
239
240
  const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
240
241
  const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
@@ -392,82 +393,6 @@ function getCanonicalSheetName(sheetName) {
392
393
  function clip(val, min, max) {
393
394
  return val < min ? min : val > max ? max : val;
394
395
  }
395
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
396
- return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
397
- }
398
- /**
399
- * Get the default height of the cell given its style.
400
- */
401
- function getDefaultCellHeight(cell) {
402
- if (!cell || !cell.content) {
403
- return DEFAULT_CELL_HEIGHT;
404
- }
405
- const fontSize = computeTextFontSizeInPixels(cell.style);
406
- // the number of lines should be computed from the formula result, but it's not evaluated at this point
407
- const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
408
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
409
- }
410
- function computeTextWidth(context, text, style) {
411
- const font = computeTextFont(style);
412
- if (!textWidthCache[font]) {
413
- textWidthCache[font] = {};
414
- }
415
- if (textWidthCache[font][text] === undefined) {
416
- context.save();
417
- context.font = font;
418
- const textWidth = context.measureText(text).width;
419
- context.restore();
420
- textWidthCache[font][text] = textWidth;
421
- }
422
- return textWidthCache[font][text];
423
- }
424
- const textWidthCache = {};
425
- function fontSizeInPixels(fontSize) {
426
- return Math.round((fontSize * 96) / 72);
427
- }
428
- function computeTextFont(style) {
429
- const italic = style.italic ? "italic " : "";
430
- const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
431
- const size = computeTextFontSizeInPixels(style);
432
- return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
433
- }
434
- function computeTextFontSizeInPixels(style) {
435
- const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
436
- return fontSizeInPixels(sizeInPt);
437
- }
438
- /**
439
- * Return the font size that makes the width of a text match the given line width.
440
- * Minimum font size is 1.
441
- *
442
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
443
- */
444
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
445
- let minFontSize = 1;
446
- if (getTextWidth(minFontSize) > lineWidth)
447
- return minFontSize;
448
- if (getTextWidth(maxFontSize) < lineWidth)
449
- return maxFontSize;
450
- // Dichotomic search
451
- let fontSize = (minFontSize + maxFontSize) / 2;
452
- let currentTextWidth = getTextWidth(fontSize);
453
- // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
454
- let iterations = 0;
455
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
456
- if (currentTextWidth >= lineWidth) {
457
- maxFontSize = (minFontSize + maxFontSize) / 2;
458
- }
459
- else {
460
- minFontSize = (minFontSize + maxFontSize) / 2;
461
- }
462
- fontSize = (minFontSize + maxFontSize) / 2;
463
- currentTextWidth = getTextWidth(fontSize);
464
- iterations++;
465
- }
466
- return fontSize;
467
- }
468
- function computeIconWidth(style) {
469
- return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
470
- }
471
396
  /**
472
397
  * Create a range from start (included) to end (excluded).
473
398
  * range(10, 13) => [10, 11, 12]
@@ -722,10 +647,6 @@ function removeFalsyAttributes(obj) {
722
647
  Object.keys(cleanObject).forEach((key) => !cleanObject[key] && delete cleanObject[key]);
723
648
  return cleanObject;
724
649
  }
725
- /** Transform a string to lower case. If the string is undefined, return an empty string */
726
- function toLowerCase(str) {
727
- return str ? str.toLowerCase() : "";
728
- }
729
650
  function transpose2dArray(matrix, callback = (val) => val) {
730
651
  if (!matrix.length)
731
652
  return [];
@@ -814,6 +735,14 @@ function memoize(func) {
814
735
  return cache.get(args[0]);
815
736
  },
816
737
  }[funcName];
738
+ }
739
+ function removeIndexesFromArray(array, indexes) {
740
+ return array.filter((_, index) => !indexes.includes(index));
741
+ }
742
+ function insertItemsAtIndex(array, items, index) {
743
+ const newArray = [...array];
744
+ newArray.splice(index, 0, ...items);
745
+ return newArray;
817
746
  }
818
747
 
819
748
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -957,13 +886,13 @@ function rgbaToHex(rgba) {
957
886
  let g = rgba.g.toString(16);
958
887
  let b = rgba.b.toString(16);
959
888
  let a = Math.round(rgba.a * 255).toString(16);
960
- if (r.length == 1)
889
+ if (r.length === 1)
961
890
  r = "0" + r;
962
- if (g.length == 1)
891
+ if (g.length === 1)
963
892
  g = "0" + g;
964
- if (b.length == 1)
893
+ if (b.length === 1)
965
894
  b = "0" + b;
966
- if (a.length == 1)
895
+ if (a.length === 1)
967
896
  a = "0" + a;
968
897
  if (a === "ff")
969
898
  a = "";
@@ -1066,13 +995,13 @@ function rgbaToHSLA(rgba) {
1066
995
  let l = 0;
1067
996
  // Calculate hue
1068
997
  // No difference
1069
- if (delta == 0)
998
+ if (delta === 0)
1070
999
  h = 0;
1071
1000
  // Red is max
1072
- else if (cMax == r)
1001
+ else if (cMax === r)
1073
1002
  h = ((g - b) / delta) % 6;
1074
1003
  // Green is max
1075
- else if (cMax == g)
1004
+ else if (cMax === g)
1076
1005
  h = (b - r) / delta + 2;
1077
1006
  // Blue is max
1078
1007
  else
@@ -1083,7 +1012,7 @@ function rgbaToHSLA(rgba) {
1083
1012
  h += 360;
1084
1013
  l = (cMax + cMin) / 2;
1085
1014
  // Calculate saturation
1086
- s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1015
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1087
1016
  // Multiply l and s by 100
1088
1017
  s = +(s * 100).toFixed(1);
1089
1018
  l = +(l * 100).toFixed(1);
@@ -1520,7 +1449,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
1520
1449
  }
1521
1450
  function isLeapYear(year) {
1522
1451
  const _year = Math.trunc(year);
1523
- return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 == 0;
1452
+ return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 === 0;
1524
1453
  }
1525
1454
  function getYearFrac(startDate, endDate, _dayCountConvention) {
1526
1455
  if (startDate === endDate) {
@@ -2181,6 +2110,39 @@ function detectNumberFormat(content) {
2181
2110
  }
2182
2111
  return undefined;
2183
2112
  }
2113
+ function createCurrencyFormat(currency) {
2114
+ const decimalPlaces = currency.decimalPlaces ?? 2;
2115
+ const position = currency.position ?? "before";
2116
+ const code = currency.code ?? "";
2117
+ const symbol = currency.symbol ?? "";
2118
+ const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
2119
+ const numberFormat = "#,##0" + decimalRepresentation;
2120
+ let textExpression = `${code} ${symbol}`.trim();
2121
+ if (position === "after" && code) {
2122
+ textExpression = " " + textExpression;
2123
+ }
2124
+ return insertTextInFormat(textExpression, position, numberFormat);
2125
+ }
2126
+ function insertTextInFormat(text, position, format) {
2127
+ const textExpression = `[$${text}]`;
2128
+ return position === "before" ? textExpression + format : format + textExpression;
2129
+ }
2130
+ function roundFormat(format) {
2131
+ const internalFormat = parseFormat(format);
2132
+ const roundedFormat = internalFormat.map((formatPart) => {
2133
+ if (formatPart.type === "NUMBER") {
2134
+ return {
2135
+ type: formatPart.type,
2136
+ format: {
2137
+ ...formatPart.format,
2138
+ decimalPart: undefined,
2139
+ },
2140
+ };
2141
+ }
2142
+ return formatPart;
2143
+ });
2144
+ return convertInternalFormatToFormat(roundedFormat);
2145
+ }
2184
2146
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
2185
2147
  const internalFormat = parseFormat(format || "#,##0");
2186
2148
  const largeNumberFormat = internalFormat
@@ -3274,6 +3236,169 @@ function createDefaultRows(rowNumber) {
3274
3236
  return rows;
3275
3237
  }
3276
3238
 
3239
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
3240
+ return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
3241
+ }
3242
+ /**
3243
+ * Get the default height of the cell given its style.
3244
+ */
3245
+ function getDefaultCellHeight(ctx, cell, colSize) {
3246
+ if (!cell || !cell.content)
3247
+ return DEFAULT_CELL_HEIGHT;
3248
+ const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
3249
+ const numberOfLines = cell.isFormula
3250
+ ? 1
3251
+ : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
3252
+ const fontSize = computeTextFontSizeInPixels(cell.style);
3253
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
3254
+ }
3255
+ const textWidthCache = {};
3256
+ function computeTextWidth(context, text, style) {
3257
+ const font = computeTextFont(style);
3258
+ if (!textWidthCache[font]) {
3259
+ textWidthCache[font] = {};
3260
+ }
3261
+ if (textWidthCache[font][text] === undefined) {
3262
+ context.save();
3263
+ context.font = font;
3264
+ const textWidth = context.measureText(text).width;
3265
+ context.restore();
3266
+ textWidthCache[font][text] = textWidth;
3267
+ }
3268
+ return textWidthCache[font][text];
3269
+ }
3270
+ function fontSizeInPixels(fontSize) {
3271
+ return Math.round((fontSize * 96) / 72);
3272
+ }
3273
+ function computeTextFont(style) {
3274
+ const italic = style.italic ? "italic " : "";
3275
+ const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
3276
+ const size = computeTextFontSizeInPixels(style);
3277
+ return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
3278
+ }
3279
+ function computeTextFontSizeInPixels(style) {
3280
+ const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
3281
+ return fontSizeInPixels(sizeInPt);
3282
+ }
3283
+ function splitWordToSpecificWidth(ctx, word, width, style) {
3284
+ const wordWidth = computeTextWidth(ctx, word, style);
3285
+ if (wordWidth <= width) {
3286
+ return [word];
3287
+ }
3288
+ const splitWord = [];
3289
+ let wordPart = "";
3290
+ for (let l of word) {
3291
+ const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
3292
+ if (wordPartWidth > width) {
3293
+ splitWord.push(wordPart);
3294
+ wordPart = l;
3295
+ }
3296
+ else {
3297
+ wordPart += l;
3298
+ }
3299
+ }
3300
+ splitWord.push(wordPart);
3301
+ return splitWord;
3302
+ }
3303
+ /**
3304
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
3305
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
3306
+ */
3307
+ function splitTextToWidth(ctx, text, style, width) {
3308
+ if (!style)
3309
+ style = {};
3310
+ const brokenText = [];
3311
+ // Checking if text contains NEWLINE before split makes it very slightly slower if text contains it,
3312
+ // but 5-10x faster if it doesn't
3313
+ const lines = text.includes(NEWLINE) ? text.split(NEWLINE) : [text];
3314
+ for (const line of lines) {
3315
+ const words = line.includes(" ") ? line.split(" ") : [line];
3316
+ if (!width) {
3317
+ brokenText.push(line);
3318
+ continue;
3319
+ }
3320
+ let textLine = "";
3321
+ let availableWidth = width;
3322
+ for (let word of words) {
3323
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
3324
+ const lastPart = splitWord.pop();
3325
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
3326
+ // At this step: "splitWord" is an array composed of parts of word whose
3327
+ // length is at most equal to "width".
3328
+ // Last part contains the end of the word.
3329
+ // Note that: When word length is less than width, then lastPart is equal
3330
+ // to word and splitWord is empty
3331
+ if (splitWord.length) {
3332
+ if (textLine !== "") {
3333
+ brokenText.push(textLine);
3334
+ textLine = "";
3335
+ availableWidth = width;
3336
+ }
3337
+ splitWord.forEach((wordPart) => {
3338
+ brokenText.push(wordPart);
3339
+ });
3340
+ textLine = lastPart;
3341
+ availableWidth = width - lastPartWidth;
3342
+ }
3343
+ else {
3344
+ // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
3345
+ const _word = textLine === "" ? lastPart : " " + lastPart;
3346
+ const wordWidth = computeTextWidth(ctx, _word, style);
3347
+ if (wordWidth <= availableWidth) {
3348
+ textLine += _word;
3349
+ availableWidth -= wordWidth;
3350
+ }
3351
+ else {
3352
+ brokenText.push(textLine);
3353
+ textLine = lastPart;
3354
+ availableWidth = width - lastPartWidth;
3355
+ }
3356
+ }
3357
+ }
3358
+ if (textLine !== "") {
3359
+ brokenText.push(textLine);
3360
+ }
3361
+ }
3362
+ return brokenText;
3363
+ }
3364
+ /**
3365
+ * Return the font size that makes the width of a text match the given line width.
3366
+ * Minimum font size is 1.
3367
+ *
3368
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
3369
+ */
3370
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
3371
+ let minFontSize = 1;
3372
+ if (getTextWidth(minFontSize) > lineWidth)
3373
+ return minFontSize;
3374
+ if (getTextWidth(maxFontSize) < lineWidth)
3375
+ return maxFontSize;
3376
+ // Dichotomic search
3377
+ let fontSize = (minFontSize + maxFontSize) / 2;
3378
+ let currentTextWidth = getTextWidth(fontSize);
3379
+ // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
3380
+ let iterations = 0;
3381
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
3382
+ if (currentTextWidth >= lineWidth) {
3383
+ maxFontSize = (minFontSize + maxFontSize) / 2;
3384
+ }
3385
+ else {
3386
+ minFontSize = (minFontSize + maxFontSize) / 2;
3387
+ }
3388
+ fontSize = (minFontSize + maxFontSize) / 2;
3389
+ currentTextWidth = getTextWidth(fontSize);
3390
+ iterations++;
3391
+ }
3392
+ return fontSize;
3393
+ }
3394
+ function computeIconWidth(style) {
3395
+ return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
3396
+ }
3397
+ /** Transform a string to lower case. If the string is undefined, return an empty string */
3398
+ function toLowerCase(str) {
3399
+ return str ? str.toLowerCase() : "";
3400
+ }
3401
+
3277
3402
  /*
3278
3403
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
3279
3404
  * */
@@ -3296,7 +3421,7 @@ class UuidGenerator {
3296
3421
  else {
3297
3422
  // mainly for jest and other browsers that do not have the crypto functionality
3298
3423
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
3299
- var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
3424
+ var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
3300
3425
  return v.toString(16);
3301
3426
  });
3302
3427
  }
@@ -4124,6 +4249,7 @@ var CommandResult;
4124
4249
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 92] = "NoSplitSeparatorInSelection";
4125
4250
  CommandResult[CommandResult["NoActiveSheet"] = 93] = "NoActiveSheet";
4126
4251
  CommandResult[CommandResult["InvalidLocale"] = 94] = "InvalidLocale";
4252
+ CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 95] = "AlreadyInPaintingFormatMode";
4127
4253
  })(CommandResult || (CommandResult = {}));
4128
4254
 
4129
4255
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5939,8 +6065,8 @@ class Menu extends Component {
5939
6065
  return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
5940
6066
  }
5941
6067
  getIconName(menu) {
5942
- if (menu.icon) {
5943
- return menu.icon;
6068
+ if (menu.icon(this.env)) {
6069
+ return menu.icon(this.env);
5944
6070
  }
5945
6071
  if (menu.isActive?.(this.env)) {
5946
6072
  return "o-spreadsheet-Icon.CHECK";
@@ -6374,7 +6500,7 @@ function invertMatrix(M) {
6374
6500
  for (let pivot = 0; pivot < dim; pivot++) {
6375
6501
  let diagonalElement = C[pivot][pivot];
6376
6502
  // if we have a 0 on the diagonal we'll need to swap with a lower row
6377
- if (diagonalElement == 0) {
6503
+ if (diagonalElement === 0) {
6378
6504
  //look through every row below the i'th row
6379
6505
  for (let row = pivot + 1; row < dim; row++) {
6380
6506
  //if the ii'th row has a non-0 in the i'th col, swap it with that row
@@ -6401,7 +6527,7 @@ function invertMatrix(M) {
6401
6527
  // the other rows so that there will be 0's in this column in the
6402
6528
  // rows above and below this one
6403
6529
  for (let row = 0; row < dim; row++) {
6404
- if (row == pivot) {
6530
+ if (row === pivot) {
6405
6531
  continue;
6406
6532
  }
6407
6533
  // We want to change this element to 0
@@ -12466,7 +12592,7 @@ const ISNA = {
12466
12592
  return false;
12467
12593
  }
12468
12594
  catch (e) {
12469
- return e?.errorType == CellErrorType.NotAvailable;
12595
+ return e?.errorType === CellErrorType.NotAvailable;
12470
12596
  }
12471
12597
  },
12472
12598
  isExported: true,
@@ -12592,6 +12718,18 @@ const AND = {
12592
12718
  isExported: true,
12593
12719
  };
12594
12720
  // -----------------------------------------------------------------------------
12721
+ // FALSE
12722
+ // -----------------------------------------------------------------------------
12723
+ const FALSE = {
12724
+ description: _lt("Logical value `false`."),
12725
+ args: [],
12726
+ returns: ["BOOLEAN"],
12727
+ compute: function () {
12728
+ return false;
12729
+ },
12730
+ isExported: true,
12731
+ };
12732
+ // -----------------------------------------------------------------------------
12595
12733
  // IF
12596
12734
  // -----------------------------------------------------------------------------
12597
12735
  const IF = {
@@ -12727,6 +12865,18 @@ const OR = {
12727
12865
  isExported: true,
12728
12866
  };
12729
12867
  // -----------------------------------------------------------------------------
12868
+ // TRUE
12869
+ // -----------------------------------------------------------------------------
12870
+ const TRUE = {
12871
+ description: _lt("Logical value `true`."),
12872
+ args: [],
12873
+ returns: ["BOOLEAN"],
12874
+ compute: function () {
12875
+ return true;
12876
+ },
12877
+ isExported: true,
12878
+ };
12879
+ // -----------------------------------------------------------------------------
12730
12880
  // XOR
12731
12881
  // -----------------------------------------------------------------------------
12732
12882
  const XOR = {
@@ -12753,12 +12903,14 @@ const XOR = {
12753
12903
  var logical = /*#__PURE__*/Object.freeze({
12754
12904
  __proto__: null,
12755
12905
  AND: AND,
12906
+ FALSE: FALSE,
12756
12907
  IF: IF,
12757
12908
  IFERROR: IFERROR,
12758
12909
  IFNA: IFNA,
12759
12910
  IFS: IFS,
12760
12911
  NOT: NOT,
12761
12912
  OR: OR,
12913
+ TRUE: TRUE,
12762
12914
  XOR: XOR
12763
12915
  });
12764
12916
 
@@ -13945,7 +14097,6 @@ function indentCode(code) {
13945
14097
  * is useful for the composer, which needs to be able to work with incomplete
13946
14098
  * formulas.
13947
14099
  */
13948
- const functions$3 = functionRegistry.content;
13949
14100
  const POSTFIX_UNARY_OPERATORS = ["%"];
13950
14101
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
13951
14102
  function tokenize(str, locale = DEFAULT_LOCALE) {
@@ -14076,17 +14227,11 @@ function tokenizeSymbol(chars) {
14076
14227
  }
14077
14228
  if (result.length) {
14078
14229
  const value = result;
14079
- const isFunction = value.toUpperCase() in functions$3;
14080
- if (isFunction) {
14081
- return { type: "FUNCTION", value };
14082
- }
14083
14230
  const isReference = rangeReference.test(value);
14084
14231
  if (isReference) {
14085
14232
  return { type: "REFERENCE", value };
14086
14233
  }
14087
- else {
14088
- return { type: "SYMBOL", value };
14089
- }
14234
+ return { type: "SYMBOL", value };
14090
14235
  }
14091
14236
  return null;
14092
14237
  }
@@ -14186,9 +14331,6 @@ function parseOperand(tokens) {
14186
14331
  return { type: "NUMBER", value: parseNumber(current.value, DEFAULT_LOCALE) };
14187
14332
  case "STRING":
14188
14333
  return { type: "STRING", value: removeStringQuotes(current.value) };
14189
- case "FUNCTION":
14190
- const args = parseFunctionArgs(tokens);
14191
- return { type: "FUNCALL", value: current.value, args };
14192
14334
  case "INVALID_REFERENCE":
14193
14335
  throw new InvalidReferenceError();
14194
14336
  case "REFERENCE":
@@ -14205,13 +14347,15 @@ function parseOperand(tokens) {
14205
14347
  value: current.value,
14206
14348
  };
14207
14349
  case "SYMBOL":
14208
- if (["TRUE", "FALSE"].includes(current.value.toUpperCase())) {
14209
- return { type: "BOOLEAN", value: current.value.toUpperCase() === "TRUE" };
14350
+ const value = current.value;
14351
+ const nextToken = tokens[0];
14352
+ if (nextToken?.type === "LEFT_PAREN" && functionRegex.test(current.value)) {
14353
+ const args = parseFunctionArgs(tokens);
14354
+ return { type: "FUNCALL", value: value, args };
14210
14355
  }
14211
- if (current.value) {
14212
- if (functionRegex.test(current.value) && tokens[0]?.type === "LEFT_PAREN") {
14213
- throw new UnknownFunctionError(current.value);
14214
- }
14356
+ const upperCaseValue = value.toUpperCase();
14357
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
14358
+ return { type: "BOOLEAN", value: upperCaseValue === "TRUE" };
14215
14359
  }
14216
14360
  throw new BadExpressionError(_lt("Invalid formula"));
14217
14361
  case "LEFT_PAREN":
@@ -14313,36 +14457,60 @@ function parseTokens(tokens) {
14313
14457
  * to nodes of a specific type.
14314
14458
  * Useful if you want to convert some part of a formula.
14315
14459
  *
14316
- * e.g.
14317
- * ```ts
14460
+ * @example
14318
14461
  * convertAstNodes(ast, "FUNCALL", convertFormulaToExcel)
14319
14462
  *
14320
14463
  * function convertFormulaToExcel(ast: ASTFuncall) {
14321
14464
  * // ...
14322
14465
  * return modifiedAst
14323
14466
  * }
14324
- * ```
14325
14467
  */
14326
14468
  function convertAstNodes(ast, type, fn) {
14327
- if (type === ast.type) {
14328
- ast = fn(ast);
14469
+ return mapAst(ast, (ast) => {
14470
+ if (ast.type === type) {
14471
+ return fn(ast);
14472
+ }
14473
+ return ast;
14474
+ });
14475
+ }
14476
+ function iterateAstNodes(ast) {
14477
+ return Array.from(astIterator(ast));
14478
+ }
14479
+ function* astIterator(ast) {
14480
+ yield ast;
14481
+ switch (ast.type) {
14482
+ case "FUNCALL":
14483
+ for (const arg of ast.args) {
14484
+ yield* astIterator(arg);
14485
+ }
14486
+ break;
14487
+ case "UNARY_OPERATION":
14488
+ yield* astIterator(ast.operand);
14489
+ break;
14490
+ case "BIN_OPERATION":
14491
+ yield* astIterator(ast.left);
14492
+ yield* astIterator(ast.right);
14493
+ break;
14329
14494
  }
14495
+ }
14496
+ function mapAst(ast, fn) {
14497
+ ast = fn(ast);
14330
14498
  switch (ast.type) {
14331
14499
  case "FUNCALL":
14332
14500
  return {
14333
14501
  ...ast,
14334
- args: ast.args.map((child) => convertAstNodes(child, type, fn)),
14502
+ args: ast.args.map((child) => mapAst(child, fn)),
14335
14503
  };
14336
14504
  case "UNARY_OPERATION":
14337
14505
  return {
14338
14506
  ...ast,
14339
- operand: convertAstNodes(ast.operand, type, fn),
14507
+ operand: mapAst(ast.operand, fn),
14340
14508
  };
14341
14509
  case "BIN_OPERATION":
14342
14510
  return {
14343
14511
  ...ast,
14344
- right: convertAstNodes(ast.right, type, fn),
14345
- left: convertAstNodes(ast.left, type, fn),
14512
+ right: mapAst(ast.right, fn),
14513
+ left: mapAst(ast.left, fn),
14346
14514
  };
14347
14515
  default:
14348
14516
  return ast;
@@ -14612,6 +14780,9 @@ function compile(formula) {
14612
14780
  const { args } = ast;
14613
14781
  const functionName = ast.value.toUpperCase();
14614
14782
  const functionDefinition = functions$2[functionName];
14783
+ if (!functionDefinition) {
14784
+ throw new UnknownFunctionError(ast.value);
14785
+ }
14615
14786
  assertEnoughArgs(ast);
14616
14787
  const compiledArgs = [];
14617
14788
  for (let i = 0; i < args.length; i++) {
@@ -14870,7 +15041,7 @@ function mapParentFunction(tokens) {
14870
15041
  functionStarted = "";
14871
15042
  }
14872
15043
  switch (token.type) {
14873
- case "FUNCTION":
15044
+ case "SYMBOL":
14874
15045
  functionStarted = token.value;
14875
15046
  break;
14876
15047
  case "LEFT_PAREN":
@@ -14907,6 +15078,17 @@ function composerTokenize(formula, locale) {
14907
15078
  return mapParentFunction(mapParenthesis(enrichTokens(tokens)));
14908
15079
  }
14909
15080
 
15081
+ const functions$1 = functionRegistry.content;
15082
+ function isExportableToExcel(tokens) {
15083
+ try {
15084
+ const nonExportableFunctions = iterateAstNodes(parseTokens(tokens)).filter((ast) => ast.type === "FUNCALL" && !functions$1[ast.value.toUpperCase()]?.isExported);
15085
+ return nonExportableFunctions.length === 0;
15086
+ }
15087
+ catch (error) {
15088
+ return false;
15089
+ }
15090
+ }
15091
+
14910
15092
  function isValidLocale(locale) {
14911
15093
  if (!(locale &&
14912
15094
  typeof locale === "object" &&
@@ -15126,6 +15308,8 @@ const uuidGenerator$2 = new UuidGenerator();
15126
15308
  function createAction(item) {
15127
15309
  const name = item.name;
15128
15310
  const children = item.children;
15311
+ const description = item.description;
15312
+ const icon = item.icon;
15129
15313
  return {
15130
15314
  id: item.id || uuidGenerator$2.uuidv4(),
15131
15315
  name: typeof name === "function" ? name : () => name,
@@ -15143,8 +15327,8 @@ function createAction(item) {
15143
15327
  : () => [],
15144
15328
  isReadonlyAllowed: item.isReadonlyAllowed || false,
15145
15329
  separator: item.separator || false,
15146
- icon: item.icon || "",
15147
- description: item.description || "",
15330
+ icon: typeof icon === "function" ? icon : () => icon || "",
15331
+ description: typeof description === "function" ? description : () => description || "",
15148
15332
  textColor: item.textColor,
15149
15333
  sequence: item.sequence || 0,
15150
15334
  };
@@ -16610,7 +16794,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
16610
16794
  function convertDateFormatForMoment(format) {
16611
16795
  format = format.replace(/y/g, "Y");
16612
16796
  format = format.replace(/d/g, "D");
16613
- // "m" before "h" == month, "m" after "h" == minute
16797
+ // "m" before "h" === month, "m" after "h" === minute
16614
16798
  const indexH = format.indexOf("h");
16615
16799
  if (indexH >= 0) {
16616
16800
  format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
@@ -17464,6 +17648,7 @@ function getChartMenu(figureId, onFigureDeleted, env) {
17464
17648
  env.model.dispatch("SELECT_FIGURE", { id: figureId });
17465
17649
  env.openSidePanel("ChartPanel");
17466
17650
  },
17651
+ icon: "o-spreadsheet-Icon.EDIT",
17467
17652
  },
17468
17653
  getCopyMenuItem(figureId, env),
17469
17654
  getCutMenuItem(figureId, env),
@@ -17479,8 +17664,14 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17479
17664
  id: "reset_size",
17480
17665
  name: _lt("Reset size"),
17481
17666
  sequence: 4,
17482
- execute: () => {
17483
- const size = env.model.getters.getImageSize(figureId);
17667
+ execute: async () => {
17668
+ const imagePath = env.model.getters.getImagePath(figureId);
17669
+ const size = env.model.getters.getImageSize(figureId) ??
17670
+ (await env.imageProvider?.getImageOriginalSize(imagePath));
17671
+ if (!env.model.getters.getImageSize(figureId)) {
17672
+ const image = env.model.getters.getImage(figureId);
17673
+ image.size = size;
17674
+ }
17484
17675
  const { height, width } = getMaxFigureSize(env.model.getters, size);
17485
17676
  env.model.dispatch("UPDATE_FIGURE", {
17486
17677
  sheetId: env.model.getters.getActiveSheetId(),
@@ -17489,6 +17680,7 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17489
17680
  width,
17490
17681
  });
17491
17682
  },
17683
+ icon: "o-spreadsheet-Icon.REFRESH",
17492
17684
  },
17493
17685
  getDeleteMenuItem(figureId, onFigureDeleted, env),
17494
17686
  ];
@@ -17505,6 +17697,7 @@ function getCopyMenuItem(figureId, env) {
17505
17697
  env.model.dispatch("COPY");
17506
17698
  await env.clipboard.write(env.model.getters.getClipboardContent());
17507
17699
  },
17700
+ icon: "o-spreadsheet-Icon.COPY",
17508
17701
  };
17509
17702
  }
17510
17703
  function getCutMenuItem(figureId, env) {
@@ -17518,6 +17711,7 @@ function getCutMenuItem(figureId, env) {
17518
17711
  env.model.dispatch("CUT");
17519
17712
  await env.clipboard.write(env.model.getters.getClipboardContent());
17520
17713
  },
17714
+ icon: "o-spreadsheet-Icon.CUT",
17521
17715
  };
17522
17716
  }
17523
17717
  function getDeleteMenuItem(figureId, onFigureDeleted, env) {
@@ -17532,6 +17726,7 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
17532
17726
  });
17533
17727
  onFigureDeleted();
17534
17728
  },
17729
+ icon: "o-spreadsheet-Icon.DELETE",
17535
17730
  };
17536
17731
  }
17537
17732
 
@@ -18599,7 +18794,7 @@ const insertFunctionMin = {
18599
18794
  };
18600
18795
  const categorieFunctionAll = {
18601
18796
  name: _lt("All"),
18602
- children: allFunctionListMenuBuilder(),
18797
+ children: [allFunctionListMenuBuilder],
18603
18798
  };
18604
18799
  function allFunctionListMenuBuilder() {
18605
18800
  const fnNames = functionRegistry.getKeys();
@@ -18931,7 +19126,10 @@ const formatNumberAutomatic = {
18931
19126
  };
18932
19127
  const formatNumberNumber = {
18933
19128
  name: _lt("Number"),
18934
- description: "1,000.12",
19129
+ description: (env) => formatValue(1000.12, {
19130
+ format: "#,##0.00",
19131
+ locale: env.model.getters.getLocale(),
19132
+ }),
18935
19133
  execute: (env) => setFormatter(env, "#,##0.00"),
18936
19134
  isActive: (env) => isFormatSelected(env, "#,##0.00"),
18937
19135
  };
@@ -18942,21 +19140,34 @@ const formatPercent = {
18942
19140
  };
18943
19141
  const formatNumberPercent = {
18944
19142
  name: _lt("Percent"),
18945
- description: "10.12%",
19143
+ description: (env) => formatValue(0.1012, {
19144
+ format: "0.00%",
19145
+ locale: env.model.getters.getLocale(),
19146
+ }),
18946
19147
  execute: FORMAT_PERCENT_ACTION,
18947
19148
  isActive: (env) => isFormatSelected(env, "0.00%"),
18948
19149
  };
18949
19150
  const formatNumberCurrency = {
18950
19151
  name: _lt("Currency"),
18951
- description: "$1,000.12",
18952
- execute: (env) => setFormatter(env, "[$$]#,##0.00"),
18953
- isActive: (env) => isFormatSelected(env, "[$$]#,##0.00"),
19152
+ description: (env) => formatValue(1000.12, {
19153
+ format: env.model.config.defaultCurrencyFormat,
19154
+ locale: env.model.getters.getLocale(),
19155
+ }),
19156
+ execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
19157
+ isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
18954
19158
  };
18955
19159
  const formatNumberCurrencyRounded = {
18956
19160
  name: _lt("Currency rounded"),
18957
- description: "$1,000",
18958
- execute: (env) => setFormatter(env, "[$$]#,##0"),
18959
- isActive: (env) => isFormatSelected(env, "[$$]#,##0"),
19161
+ description: (env) => formatValue(1000, {
19162
+ format: roundFormat(env.model.config.defaultCurrencyFormat),
19163
+ locale: env.model.getters.getLocale(),
19164
+ }),
19165
+ execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19166
+ isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19167
+ isVisible: (env) => {
19168
+ const currencyFormat = env.model.config.defaultCurrencyFormat;
19169
+ return currencyFormat !== roundFormat(currencyFormat);
19170
+ },
18960
19171
  };
18961
19172
  const formatCustomCurrency = {
18962
19173
  name: _lt("Custom currency"),
@@ -18965,19 +19176,37 @@ const formatCustomCurrency = {
18965
19176
  };
18966
19177
  const formatNumberDate = {
18967
19178
  name: _lt("Date"),
18968
- description: "9/26/2008",
19179
+ description: (env) => {
19180
+ const locale = env.model.getters.getLocale();
19181
+ return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
19182
+ format: locale.dateFormat,
19183
+ locale,
19184
+ });
19185
+ },
18969
19186
  execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
18970
19187
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
18971
19188
  };
18972
19189
  const formatNumberTime = {
18973
19190
  name: _lt("Time"),
18974
- description: "10:43:00 PM",
19191
+ description: (env) => {
19192
+ const locale = env.model.getters.getLocale();
19193
+ return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
19194
+ format: locale.timeFormat,
19195
+ locale,
19196
+ });
19197
+ },
18975
19198
  execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
18976
19199
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
18977
19200
  };
18978
19201
  const formatNumberDateTime = {
18979
19202
  name: _lt("Date time"),
18980
- description: "9/26/2008 22:43:00",
19203
+ description: (env) => {
19204
+ const locale = env.model.getters.getLocale();
19205
+ return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
19206
+ format: locale.dateFormat + " " + locale.timeFormat,
19207
+ locale,
19208
+ });
19209
+ },
18981
19210
  execute: (env) => {
18982
19211
  const locale = env.model.getters.getLocale();
18983
19212
  setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
@@ -19049,7 +19278,7 @@ const formatAlignment = {
19049
19278
  };
19050
19279
  const formatAlignmentHorizontal = {
19051
19280
  name: _lt("Horizontal align"),
19052
- icon: "o-spreadsheet-Icon.ALIGN_LEFT",
19281
+ icon: (env) => getHorizontalAlignmentIcon(env),
19053
19282
  };
19054
19283
  const formatAlignmentLeft = {
19055
19284
  name: _lt("Left"),
@@ -19074,46 +19303,50 @@ const formatAlignmentRight = {
19074
19303
  };
19075
19304
  const formatAlignmentVertical = {
19076
19305
  name: _lt("Vertical align"),
19077
- icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19306
+ icon: (env) => getVerticalAlignmentIcon(env),
19078
19307
  };
19079
19308
  const formatAlignmentTop = {
19080
19309
  name: _lt("Top"),
19081
19310
  execute: (env) => setStyle(env, { verticalAlign: "top" }),
19082
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "top",
19311
+ isActive: (env) => getVerticalAlign(env) === "top",
19083
19312
  icon: "o-spreadsheet-Icon.ALIGN_TOP",
19084
19313
  };
19085
19314
  const formatAlignmentMiddle = {
19086
19315
  name: _lt("Middle"),
19087
19316
  execute: (env) => setStyle(env, { verticalAlign: "middle" }),
19088
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "middle",
19317
+ isActive: (env) => getVerticalAlign(env) === "middle",
19089
19318
  icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19090
19319
  };
19091
19320
  const formatAlignmentBottom = {
19092
19321
  name: _lt("Bottom"),
19093
19322
  execute: (env) => setStyle(env, { verticalAlign: "bottom" }),
19094
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "bottom",
19323
+ isActive: (env) => getVerticalAlign(env) === "bottom",
19095
19324
  icon: "o-spreadsheet-Icon.ALIGN_BOTTOM",
19096
19325
  };
19097
- const formatWrapping = {
19326
+ const formatWrappingIcon = {
19098
19327
  name: _lt("Wrapping"),
19099
19328
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19100
19329
  };
19330
+ const formatWrapping = {
19331
+ name: _lt("Wrapping"),
19332
+ icon: (env) => getWrapModeIcon(env),
19333
+ };
19101
19334
  const formatWrappingOverflow = {
19102
19335
  name: _lt("Overflow"),
19103
19336
  execute: (env) => setStyle(env, { wrapping: "overflow" }),
19104
- isActive: (env) => (env.model.getters.getCurrentStyle().wrapping || "overflow") === "overflow",
19337
+ isActive: (env) => getWrappingMode(env) === "overflow",
19105
19338
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19106
19339
  };
19107
19340
  const formatWrappingWrap = {
19108
19341
  name: _lt("Wrap"),
19109
19342
  execute: (env) => setStyle(env, { wrapping: "wrap" }),
19110
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "wrap",
19343
+ isActive: (env) => getWrappingMode(env) === "wrap",
19111
19344
  icon: "o-spreadsheet-Icon.WRAPPING_WRAP",
19112
19345
  };
19113
19346
  const formatWrappingClip = {
19114
19347
  name: _lt("Clip"),
19115
19348
  execute: (env) => setStyle(env, { wrapping: "clip" }),
19116
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "clip",
19349
+ isActive: (env) => getWrappingMode(env) === "clip",
19117
19350
  icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
19118
19351
  };
19119
19352
  const textColor = {
@@ -19129,14 +19362,6 @@ const formatCF = {
19129
19362
  execute: OPEN_CF_SIDEPANEL_ACTION,
19130
19363
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
19131
19364
  };
19132
- const paintFormat = {
19133
- name: _lt("Paint Format"),
19134
- execute: (env) => env.model.dispatch("ACTIVATE_PAINT_FORMAT", {
19135
- target: env.model.getters.getSelectedZones(),
19136
- }),
19137
- icon: "o-spreadsheet-Icon.PAINT_FORMAT",
19138
- isActive: (env) => env.model.getters.isPaintingFormat(),
19139
- };
19140
19365
  const clearFormat = {
19141
19366
  name: _lt("Clear formatting"),
19142
19367
  description: "Ctrl+<",
@@ -19176,6 +19401,53 @@ function getHorizontalAlign(env) {
19176
19401
  }
19177
19402
  const cell = env.model.getters.getActiveCell();
19178
19403
  return cell.defaultAlign;
19404
+ }
19405
+ function getVerticalAlign(env) {
19406
+ const style = env.model.getters.getCurrentStyle();
19407
+ if (style.verticalAlign) {
19408
+ return style.verticalAlign;
19409
+ }
19410
+ return DEFAULT_VERTICAL_ALIGN;
19411
+ }
19412
+ function getWrappingMode(env) {
19413
+ const style = env.model.getters.getCurrentStyle();
19414
+ if (style.wrapping) {
19415
+ return style.wrapping;
19416
+ }
19417
+ return DEFAULT_WRAPPING_MODE;
19418
+ }
19419
+ function getHorizontalAlignmentIcon(env) {
19420
+ const horizontalAlign = getHorizontalAlign(env);
19421
+ switch (horizontalAlign) {
19422
+ case "right":
19423
+ return "o-spreadsheet-Icon.ALIGN_RIGHT";
19424
+ case "center":
19425
+ return "o-spreadsheet-Icon.ALIGN_CENTER";
19426
+ default:
19427
+ return "o-spreadsheet-Icon.ALIGN_LEFT";
19428
+ }
19429
+ }
19430
+ function getVerticalAlignmentIcon(env) {
19431
+ const verticalAlign = getVerticalAlign(env);
19432
+ switch (verticalAlign) {
19433
+ case "top":
19434
+ return "o-spreadsheet-Icon.ALIGN_TOP";
19435
+ case "middle":
19436
+ return "o-spreadsheet-Icon.ALIGN_MIDDLE";
19437
+ default:
19438
+ return "o-spreadsheet-Icon.ALIGN_BOTTOM";
19439
+ }
19440
+ }
19441
+ function getWrapModeIcon(env) {
19442
+ const wrapMode = getWrappingMode(env);
19443
+ switch (wrapMode) {
19444
+ case "wrap":
19445
+ return "o-spreadsheet-Icon.WRAPPING_WRAP";
19446
+ case "clip":
19447
+ return "o-spreadsheet-Icon.WRAPPING_CLIP";
19448
+ default:
19449
+ return "o-spreadsheet-Icon.WRAPPING_OVERFLOW";
19450
+ }
19179
19451
  }
19180
19452
 
19181
19453
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
@@ -19207,6 +19479,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19207
19479
  formatAlignmentTop: formatAlignmentTop,
19208
19480
  formatAlignmentMiddle: formatAlignmentMiddle,
19209
19481
  formatAlignmentBottom: formatAlignmentBottom,
19482
+ formatWrappingIcon: formatWrappingIcon,
19210
19483
  formatWrapping: formatWrapping,
19211
19484
  formatWrappingOverflow: formatWrappingOverflow,
19212
19485
  formatWrappingWrap: formatWrappingWrap,
@@ -19214,7 +19487,6 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19214
19487
  textColor: textColor,
19215
19488
  fillColor: fillColor,
19216
19489
  formatCF: formatCF,
19217
- paintFormat: paintFormat,
19218
19490
  clearFormat: clearFormat
19219
19491
  });
19220
19492
 
@@ -19977,7 +20249,7 @@ topbarMenuRegistry
19977
20249
  separator: true,
19978
20250
  })
19979
20251
  .addChild("format_wrapping", ["format"], {
19980
- ...formatWrapping,
20252
+ ...formatWrappingIcon,
19981
20253
  sequence: 80,
19982
20254
  separator: true,
19983
20255
  })
@@ -22267,17 +22539,45 @@ class CustomCurrencyPanel extends Component {
22267
22539
  }
22268
22540
  get formatProposals() {
22269
22541
  const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];
22270
- const proposalBases = this.initProposalBases(currency.decimalPlaces);
22271
- const firstPosition = currency.position;
22272
- const secondPosition = currency.position === "before" ? "after" : "before";
22542
+ const position = currency.position;
22543
+ const opposite = currency.position === "before" ? "after" : "before";
22273
22544
  const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : "";
22274
22545
  const code = this.state.currencyCode.trim() ? this.state.currencyCode : "";
22275
- return code || symbol
22276
- ? [
22277
- ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),
22278
- ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),
22279
- ]
22280
- : [];
22546
+ const decimalPlaces = currency.decimalPlaces;
22547
+ if (!symbol && !code) {
22548
+ return [];
22549
+ }
22550
+ const simple = symbol ? createCurrencyFormat({ symbol, position, decimalPlaces }) : "";
22551
+ const rounded = simple ? roundFormat(simple) : "";
22552
+ const simpleWithCode = createCurrencyFormat({ symbol, position, decimalPlaces, code });
22553
+ const roundedWithCode = roundFormat(simpleWithCode);
22554
+ const simpleOpposite = symbol
22555
+ ? createCurrencyFormat({ symbol, position: opposite, decimalPlaces })
22556
+ : "";
22557
+ const roundedOpposite = simpleOpposite ? roundFormat(simpleOpposite) : "";
22558
+ const simpleOppositeWithCode = createCurrencyFormat({
22559
+ symbol,
22560
+ position: opposite,
22561
+ decimalPlaces,
22562
+ code,
22563
+ });
22564
+ const roundedOppositeWithCode = roundFormat(simpleOppositeWithCode);
22565
+ const formats = new Set([
22566
+ rounded,
22567
+ simple,
22568
+ roundedWithCode,
22569
+ simpleWithCode,
22570
+ roundedOpposite,
22571
+ simpleOpposite,
22572
+ roundedOppositeWithCode,
22573
+ simpleOppositeWithCode,
22574
+ ]);
22575
+ return [...formats]
22576
+ .filter((format) => format !== "")
22577
+ .map((format) => ({
22578
+ format,
22579
+ example: formatValue(1000.0, { format, locale: this.env.model.getters.getLocale() }),
22580
+ }));
22281
22581
  }
22282
22582
  get isSameFormat() {
22283
22583
  const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];
@@ -22334,41 +22634,6 @@ class CustomCurrencyPanel extends Component {
22334
22634
  initAvailableCurrencies() {
22335
22635
  this.state.selectedCurrencyIndex = 0;
22336
22636
  }
22337
- initProposalBases(decimalPlaces) {
22338
- const result = [{ format: "#,##0", example: "1,000" }];
22339
- const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
22340
- if (decimalRepresentation) {
22341
- result.push({
22342
- format: "#,##0" + decimalRepresentation,
22343
- example: "1,000" + decimalRepresentation,
22344
- });
22345
- }
22346
- return result;
22347
- }
22348
- createFormatProposals(proposalBases, symbol, code, position) {
22349
- let formatProposals = [];
22350
- // 1 - add proposal with symbol and without code
22351
- if (symbol) {
22352
- for (let base of proposalBases) {
22353
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));
22354
- }
22355
- }
22356
- // 2 - if code exist --> add more proposal with symbol and with code
22357
- if (code) {
22358
- for (let base of proposalBases) {
22359
- const expression = (position === "after" ? " " : "") + code + " " + symbol;
22360
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));
22361
- }
22362
- }
22363
- return formatProposals;
22364
- }
22365
- createFormatProposal(position, baseExample, formatBase, expression) {
22366
- const formatExpression = "[$" + expression + "]";
22367
- return {
22368
- example: position === "before" ? expression + baseExample : baseExample + expression,
22369
- format: position === "before" ? formatExpression + formatBase : formatBase + formatExpression,
22370
- };
22371
- }
22372
22637
  getCommonFormat() {
22373
22638
  const selectedZones = this.env.model.getters.getSelectedZones();
22374
22639
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -23171,7 +23436,7 @@ class ContentEditableHelper {
23171
23436
  selection.removeAllRanges();
23172
23437
  selection.addRange(range);
23173
23438
  }
23174
- if (start == end && start === 0) {
23439
+ if (start === end && start === 0) {
23175
23440
  range.setStart(this.el, 0);
23176
23441
  range.setEnd(this.el, 0);
23177
23442
  }
@@ -23424,7 +23689,7 @@ class ContentEditableHelper {
23424
23689
  }, 0);
23425
23690
  }
23426
23691
  }
23427
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
23692
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
23428
23693
  usedCharacters++;
23429
23694
  }
23430
23695
  return usedCharacters;
@@ -23533,7 +23798,7 @@ FunctionDescriptionProvider.props = {
23533
23798
  argToFocus: Number,
23534
23799
  };
23535
23800
 
23536
- const functions$1 = functionRegistry.content;
23801
+ const functions = functionRegistry.content;
23537
23802
  const ASSISTANT_WIDTH = 300;
23538
23803
  const selectionIndicatorClass = "selector-flag";
23539
23804
  const selectionIndicatorColor = "#a9a9a9";
@@ -23567,10 +23832,6 @@ css /* scss */ `
23567
23832
  padding-right: 3px;
23568
23833
  outline: none;
23569
23834
 
23570
- &.unfocusable {
23571
- pointer-events: none;
23572
- }
23573
-
23574
23835
  p {
23575
23836
  margin-bottom: 0px;
23576
23837
 
@@ -23818,7 +24079,10 @@ class Composer extends Component {
23818
24079
  }
23819
24080
  }
23820
24081
  showAutocomplete(searchTerm) {
23821
- if (!this.env.model.getters.getCurrentContent().startsWith("=")) {
24082
+ const searchTermUpperCase = searchTerm.toUpperCase();
24083
+ if (!this.env.model.getters.getCurrentContent().startsWith("=") ||
24084
+ searchTermUpperCase === "TRUE" ||
24085
+ searchTermUpperCase === "FALSE") {
23822
24086
  return;
23823
24087
  }
23824
24088
  this.autoCompleteState.showProvider = true;
@@ -23936,7 +24200,6 @@ class Composer extends Component {
23936
24200
  switch (token.type) {
23937
24201
  case "OPERATOR":
23938
24202
  case "NUMBER":
23939
- case "FUNCTION":
23940
24203
  case "ARG_SEPARATOR":
23941
24204
  case "STRING":
23942
24205
  result.push({ value: token.value, color: tokenColors[token.type] || "#000" });
@@ -23946,10 +24209,14 @@ class Composer extends Component {
23946
24209
  result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
23947
24210
  break;
23948
24211
  case "SYMBOL":
23949
- let value = token.value;
23950
- if (["TRUE", "FALSE"].includes(value.toUpperCase())) {
24212
+ const value = token.value;
24213
+ const upperCaseValue = value.toUpperCase();
24214
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
23951
24215
  result.push({ value: token.value, color: tokenColors.NUMBER });
23952
24216
  }
24217
+ else if (upperCaseValue in functionRegistry.content) {
24218
+ result.push({ value: token.value, color: tokenColors.FUNCTION });
24219
+ }
23953
24220
  else {
23954
24221
  result.push({ value: token.value, color: "#000" });
23955
24222
  }
@@ -24045,25 +24312,28 @@ class Composer extends Component {
24045
24312
  this.autoCompleteState.showProvider = false;
24046
24313
  this.functionDescriptionState.showDescription = false;
24047
24314
  if (content.startsWith("=")) {
24048
- const tokenAtCursor = this.env.model.getters.getTokenAtCursor();
24049
- if (tokenAtCursor) {
24050
- const { xc } = splitReference(tokenAtCursor.value);
24051
- if (tokenAtCursor.type === "FUNCTION" ||
24052
- (tokenAtCursor.type === "SYMBOL" && !rangeReference.test(xc))) {
24053
- // initialize Autocomplete Dropdown
24054
- this.showAutocomplete(tokenAtCursor.value);
24055
- }
24056
- else if (tokenAtCursor.functionContext && tokenAtCursor.type !== "UNKNOWN") {
24057
- // initialize Formula Assistant
24058
- const tokenContext = tokenAtCursor.functionContext;
24059
- const parentFunction = tokenContext.parent.toUpperCase();
24060
- const description = functions$1[parentFunction];
24061
- const argPosition = tokenContext.argPosition;
24062
- this.functionDescriptionState.functionName = parentFunction;
24063
- this.functionDescriptionState.functionDescription = description;
24064
- this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24065
- this.functionDescriptionState.showDescription = true;
24066
- }
24315
+ const token = this.env.model.getters.getTokenAtCursor();
24316
+ if (!token) {
24317
+ return;
24318
+ }
24319
+ if (token.type === "SYMBOL") {
24320
+ // initialize Autocomplete Dropdown
24321
+ this.showAutocomplete(token.value);
24322
+ return;
24323
+ }
24324
+ const tokenContext = token.functionContext;
24325
+ const parentFunction = tokenContext?.parent.toUpperCase();
24326
+ if (tokenContext &&
24327
+ parentFunction &&
24328
+ parentFunction in functions &&
24329
+ token.type !== "UNKNOWN") {
24330
+ // initialize Formula Assistant
24331
+ const description = functions[parentFunction];
24332
+ const argPosition = tokenContext.argPosition;
24333
+ this.functionDescriptionState.functionName = parentFunction;
24334
+ this.functionDescriptionState.functionDescription = description;
24335
+ this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24336
+ this.functionDescriptionState.showDescription = true;
24067
24337
  }
24068
24338
  }
24069
24339
  }
@@ -24966,6 +25236,14 @@ function useInterval(callback, delay) {
24966
25236
  };
24967
25237
  }
24968
25238
 
25239
+ const CURSOR_SVG = /*xml*/ `
25240
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="16"><path d="M6.5.4c1.3-.8 2.9-.1 3.8 1.4l2.9 5.1c.2.4.9 1.6-.4 2.3l-1.6.9 1.8 3.1c.2.4.1 1-.2 1.2l-1.6 1c-.3.1-.9 0-1.1-.4l-1.8-3.1-1.6 1c-.6.4-1.7 0-2.2-.8L0 4.3"/><path fill="#fff" d="M9.1 2a1.4 1.1 60 0 0-1.7-.6L5.5 2.5l.9 1.6-1 .6-.9-1.6-.6.4 1.8 3.1-1.3.7-1.8-3.1-1 .6 3.8 6.6 6.8-3.98M3.9 8.8 10.82 5l.795 1.4-6.81 3.96"/></svg>
25241
+ `;
25242
+ css /* scss */ `
25243
+ .o-paint-format-cursor {
25244
+ cursor: url("data:image/svg+xml,${encodeURIComponent(CURSOR_SVG)}"), auto;
25245
+ }
25246
+ `;
24969
25247
  function useCellHovered(env, gridRef, callback) {
24970
25248
  let hoveredPosition = {
24971
25249
  col: undefined,
@@ -25089,6 +25367,12 @@ class GridOverlay extends Component {
25089
25367
  }
25090
25368
  return this.gridOverlay.el;
25091
25369
  }
25370
+ get style() {
25371
+ return this.props.gridOverlayDimensions;
25372
+ }
25373
+ get isPaintingFormat() {
25374
+ return this.env.model.getters.isPaintingFormat();
25375
+ }
25092
25376
  onMouseDown(ev) {
25093
25377
  if (ev.button > 0) {
25094
25378
  // not main button, probably a context menu
@@ -26266,6 +26550,9 @@ class Grid extends Component {
26266
26550
  else if (this.menuState.isOpen) {
26267
26551
  this.closeMenu();
26268
26552
  }
26553
+ else if (this.env.model.getters.isPaintingFormat()) {
26554
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
26555
+ }
26269
26556
  else {
26270
26557
  this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
26271
26558
  }
@@ -26763,7 +27050,12 @@ const XLSX_RELATION_TYPE = {
26763
27050
  };
26764
27051
  const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
26765
27052
  const HEIGHT_FACTOR = 0.75; // 100px => 75 u
26766
- const WIDTH_FACTOR = 0.1317; // 100px => 13.17 u
27053
+ /**
27054
+ * Excel says its default column width is 8.43 characters (64px)
27055
+ * which makes WIDTH_FACTOR = 0.1317, but it doesn't work well
27056
+ * 0.143 is a value from dev's experiments.
27057
+ */
27058
+ const WIDTH_FACTOR = 0.143;
26767
27059
  /** unit : maximum number of characters a column can hold at the standard font size. What. */
26768
27060
  const EXCEL_DEFAULT_COL_WIDTH = 8.43;
26769
27061
  /** unit : points */
@@ -27280,7 +27572,7 @@ const XLSX_INDEXED_COLORS = {
27280
27572
  64: "000000",
27281
27573
  65: "FFFFFF", // system background
27282
27574
  };
27283
- const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
27575
+ const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
27284
27576
  "image/avif": "avif",
27285
27577
  "image/bmp": "bmp",
27286
27578
  "image/gif": "gif",
@@ -27289,6 +27581,17 @@ const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
27289
27581
  "image/png": "png",
27290
27582
  "image/tiff": "tiff",
27291
27583
  "image/webp": "webp",
27584
+ };
27585
+ const IMAGE_EXTENSION_TO_MIMETYPE_MAPPING = {
27586
+ avif: "image/avif",
27587
+ bmp: "image/bmp",
27588
+ gif: "image/gif",
27589
+ ico: "image/vnd.microsoft.icon",
27590
+ jpeg: "image/jpeg",
27591
+ png: "image/png",
27592
+ tiff: "image/tiff",
27593
+ webp: "image/webp",
27594
+ jpg: "image/jpeg",
27292
27595
  };
27293
27596
 
27294
27597
  /**
@@ -27452,8 +27755,8 @@ function convertXlsxFormat(numFmtId, formats, warningManager) {
27452
27755
  throw new Error("Multiple escaped blocks in format");
27453
27756
  }
27454
27757
  convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
27455
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
27456
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
27758
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ === ignore width of next char for align purposes. Not supported ATM
27759
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * === repeat next character enough to fill the line. Not supported ATM
27457
27760
  convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
27458
27761
  convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
27459
27762
  if (isXlsxDateFormat(convertedFormat)) {
@@ -28092,20 +28395,35 @@ function convertFigure(figure, id, sheetData) {
28092
28395
  convertEMUToDotValue(figure.anchors[0].rowOffset);
28093
28396
  const y2 = getRowPosition(figure.anchors[1].row, sheetData) +
28094
28397
  convertEMUToDotValue(figure.anchors[1].rowOffset);
28095
- const width = x2 - x1;
28096
- const height = y2 - y1;
28097
- const chartData = convertChartData(figure.data);
28098
- if (!chartData)
28099
- return undefined;
28100
- return {
28101
- id: id,
28102
- x: x1,
28103
- y: y1,
28104
- width: width,
28105
- height: height,
28106
- tag: "chart",
28107
- data: convertChartData(figure.data),
28108
- };
28398
+ const figureData = { id, x: x1, y: y1 };
28399
+ if (isChartData(figure.data)) {
28400
+ return {
28401
+ ...figureData,
28402
+ width: x2 - x1,
28403
+ height: y2 - y1,
28404
+ tag: "chart",
28405
+ data: convertChartData(figure.data),
28406
+ };
28407
+ }
28408
+ else if (isImageData(figure.data)) {
28409
+ return {
28410
+ ...figureData,
28411
+ width: convertEMUToDotValue(figure.data.size.cx),
28412
+ height: convertEMUToDotValue(figure.data.size.cy),
28413
+ tag: "image",
28414
+ data: {
28415
+ path: figure.data.imageSrc,
28416
+ mimetype: figure.data.mimetype,
28417
+ },
28418
+ };
28419
+ }
28420
+ return undefined;
28421
+ }
28422
+ function isChartData(data) {
28423
+ return "dataSets" in data;
28424
+ }
28425
+ function isImageData(data) {
28426
+ return "imageSrc" in data;
28109
28427
  }
28110
28428
  function convertChartData(chartData) {
28111
28429
  const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
@@ -28730,29 +29048,26 @@ function escapeXml(strings, ...expressions) {
28730
29048
  return new XMLString(concat(str));
28731
29049
  }
28732
29050
  /**
28733
- * Removes the namespace of all the xml tags in the string.
29051
+ * Removes the escaped namespace of all the xml tags in the string.
28734
29052
  *
28735
- * Eg. : "ns:test a" => "test a"
29053
+ * Eg. : "NAMESPACEnsNAMESPACEtest a" => "test a"
28736
29054
  */
28737
- function removeNamespaces(query) {
28738
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
29055
+ function removeTagEscapedNamespaces(tag) {
29056
+ return tag.replace(/NAMESPACE.*NAMESPACE(.*)/, "$1");
28739
29057
  }
28740
29058
  /**
28741
- * Escape the namespace's colons of all the xml tags in the string.
29059
+ * Encase the namespaces in the element's tags with NAMESPACE string
28742
29060
  *
28743
- * Eg. : "ns:test a" => "ns\\:test a"
28744
- */
28745
- function escapeNamespaces(query) {
28746
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
28747
- }
28748
- /**
28749
- * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.
29061
+ * e.g. <x:foo> becomes <NAMESPACExNAMESPACEFoo>
28750
29062
  *
28751
- * Should return true if it's running on a browser, and false if it's running on jest (jsdom).
29063
+ * That's useful because namespaces aren't supported by the HTML specification, so it's arbitrary whether a HTML parser/querySelector
29064
+ * implementation will support namespaces in the tags or not.
28752
29065
  */
28753
- function areNamespaceIgnoredByQuerySelector() {
28754
- const doc = new DOMParser().parseFromString("<t:test xmlns:t='a'/>", "text/xml");
28755
- return doc.querySelector("test") !== null;
29066
+ function escapeTagNamespaces(str) {
29067
+ return str.replaceAll(/(<\/?)([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "$1" + "NAMESPACE" + "$2" + "NAMESPACE" + "$3");
29068
+ }
29069
+ function escapeQueryNameSpaces(query) {
29070
+ return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
28756
29071
  }
28757
29072
 
28758
29073
  class AttributeValue {
@@ -28782,14 +29097,20 @@ class XlsxBaseExtractor {
28782
29097
  // The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but
28783
29098
  // the XLSXImportFile contains both the main .xml file, and the .rels file
28784
29099
  currentFile = undefined;
28785
- // If the parser querySelector() implementation ignores tag namespaces or not
28786
- areNamespaceIgnored;
29100
+ /**
29101
+ * /!\ Important : There should be no namespaces in the tags of the XML files.
29102
+ *
29103
+ * This class use native querySelector and querySelectorAll, that's used for HTML (not XML). These aren't supposed to
29104
+ * handled namespaces, as they are not supported by the HTML specification. Some implementations (most browsers) do
29105
+ * actually support namespaces, but some don't (e.g. jsdom).
29106
+ *
29107
+ * The namespace should be escaped as with NAMESPACE string (eg. <t:foo> => <NAMESPACEtNAMESPACEfoo>).
29108
+ */
28787
29109
  constructor(rootFile, xlsxStructure, warningManager) {
28788
29110
  this.rootFile = rootFile;
28789
29111
  this.currentFile = rootFile.file.fileName;
28790
29112
  this.xlsxFileStructure = xlsxStructure;
28791
29113
  this.warningManager = warningManager;
28792
- this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();
28793
29114
  this.relationships = {};
28794
29115
  if (rootFile.rels) {
28795
29116
  this.extractRelationships(rootFile.rels).map((rel) => {
@@ -28812,9 +29133,13 @@ class XlsxBaseExtractor {
28812
29133
  /**
28813
29134
  * Get the list of all the XLSX files in the XLSX file structure
28814
29135
  */
28815
- getListOfFiles() {
28816
- const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);
28817
- return files;
29136
+ getListOfXMLFiles() {
29137
+ const XMLFiles = Object.entries(this.xlsxFileStructure)
29138
+ .filter(([key]) => key !== "images")
29139
+ .map(([_, value]) => value)
29140
+ .flat()
29141
+ .filter(isDefined$1);
29142
+ return XMLFiles;
28818
29143
  }
28819
29144
  /**
28820
29145
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -28986,43 +29311,38 @@ class XlsxBaseExtractor {
28986
29311
  return color;
28987
29312
  }
28988
29313
  /**
28989
- * Returns the xlsx file targeted by a relationship.
29314
+ * Returns the xml file targeted by a relationship.
28990
29315
  */
28991
29316
  getTargetXmlFile(relationship) {
28992
29317
  if (!relationship)
28993
29318
  throw new Error("Undefined target file");
28994
- let target = relationship.target;
28995
- target = target.replace("../", "");
28996
- target = target.replace("./", "");
29319
+ const target = this.processRelationshipTargetName(relationship.target);
28997
29320
  // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
28998
- const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));
29321
+ const f = this.getListOfXMLFiles().find((f) => f.file.fileName.endsWith(target));
28999
29322
  if (!f || !f.file)
29000
29323
  throw new Error("Cannot find target file");
29001
29324
  return f;
29002
29325
  }
29003
29326
  /**
29004
- * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29005
- *
29006
- * Why we need to do this :
29007
- * - For an XML "<t:test />"
29008
- * - on Jest(jsdom) : xml.querySelector("test") == null, xml.querySelector("t\\:test") == <t:test />
29009
- * - on Browser : xml.querySelector("test") == <t:test />, xml.querySelector("t\\:test") == null
29327
+ * Returns the image parameters targeted by a relationship.
29010
29328
  */
29329
+ getTargetImageFile(relationship) {
29330
+ if (!relationship)
29331
+ throw new Error("Undefined target file");
29332
+ const target = this.processRelationshipTargetName(relationship.target);
29333
+ // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
29334
+ const f = this.xlsxFileStructure.images.find((f) => f.fileName.endsWith(target));
29335
+ if (!f)
29336
+ throw new Error("Cannot find target file");
29337
+ return f;
29338
+ }
29011
29339
  querySelector(element, query) {
29012
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29013
- return element.querySelector(query);
29340
+ const escapedQuery = escapeQueryNameSpaces(query);
29341
+ return element.querySelector(escapedQuery);
29014
29342
  }
29015
- /**
29016
- * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29017
- *
29018
- * Why we need to do this :
29019
- * - For an XML "<t:test />"
29020
- * - on Jest(jsdom) : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == [<t:test />]
29021
- * - on Browser : xml.querySelectorAll("test") == [<t:test />], xml.querySelectorAll("t\\:test") == []
29022
- */
29023
29343
  querySelectorAll(element, query) {
29024
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29025
- return element.querySelectorAll(query);
29344
+ const escapedQuery = escapeQueryNameSpaces(query);
29345
+ return element.querySelectorAll(escapedQuery);
29026
29346
  }
29027
29347
  /**
29028
29348
  * Get a color from its id in the Theme's colorScheme.
@@ -29046,6 +29366,10 @@ class XlsxBaseExtractor {
29046
29366
  return clrScheme[colorId].value;
29047
29367
  }
29048
29368
  }
29369
+ /** Remove signs of relative path. */
29370
+ processRelationshipTargetName(targetName) {
29371
+ return targetName.replace(/\.+\//, "");
29372
+ }
29049
29373
  }
29050
29374
 
29051
29375
  /**
@@ -29258,7 +29582,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29258
29582
  throw new Error("Missing plot area in the chart definition.");
29259
29583
  }
29260
29584
  for (let child of plotAreaElement.children) {
29261
- const tag = removeNamespaces(child.tagName);
29585
+ const tag = removeTagEscapedNamespaces(child.tagName);
29262
29586
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
29263
29587
  return tag;
29264
29588
  }
@@ -29270,20 +29594,21 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29270
29594
  class XlsxFigureExtractor extends XlsxBaseExtractor {
29271
29595
  extractFigures() {
29272
29596
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
29273
- const anchorType = removeNamespaces(figureElement.tagName);
29597
+ const anchorType = removeTagEscapedNamespaces(figureElement.tagName);
29274
29598
  if (anchorType !== "twoCellAnchor") {
29275
29599
  throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
29276
29600
  }
29277
29601
  const chartElement = this.querySelector(figureElement, "c:chart");
29278
- if (!chartElement) {
29279
- throw new Error("Only chart figures are currently supported.");
29602
+ const imageElement = this.querySelector(figureElement, "a:blip");
29603
+ if (!chartElement && !imageElement) {
29604
+ throw new Error("Only chart and image figures are currently supported.");
29280
29605
  }
29281
29606
  return {
29282
29607
  anchors: [
29283
29608
  this.extractFigureAnchor("xdr:from", figureElement),
29284
29609
  this.extractFigureAnchor("xdr:to", figureElement),
29285
29610
  ],
29286
- data: this.extractChart(chartElement),
29611
+ data: chartElement ? this.extractChart(chartElement) : this.extractImage(figureElement),
29287
29612
  };
29288
29613
  });
29289
29614
  }
@@ -29308,6 +29633,24 @@ class XlsxFigureExtractor extends XlsxBaseExtractor {
29308
29633
  }
29309
29634
  return chartDefinition;
29310
29635
  }
29636
+ extractImage(figureElement) {
29637
+ const imageElement = this.querySelector(figureElement, "a:blip");
29638
+ const imageId = this.extractAttr(imageElement, "r:embed", { required: true }).asString();
29639
+ const image = this.getTargetImageFile(this.relationships[imageId]);
29640
+ if (!image) {
29641
+ throw new Error("Unable to extract image");
29642
+ }
29643
+ const shapePropertyElement = this.querySelector(figureElement, "a:xfrm");
29644
+ const extension = image.fileName.split(".").at(-1);
29645
+ return {
29646
+ imageSrc: image.imageSrc,
29647
+ mimetype: extension ? IMAGE_EXTENSION_TO_MIMETYPE_MAPPING[extension] : undefined,
29648
+ size: {
29649
+ cx: this.extractChildAttr(shapePropertyElement, "a:ext", "cx", { required: true }).asNum(),
29650
+ cy: this.extractChildAttr(shapePropertyElement, "a:ext", "cy", { required: true }).asNum(),
29651
+ },
29652
+ };
29653
+ }
29311
29654
  }
29312
29655
 
29313
29656
  /**
@@ -29877,13 +30220,22 @@ const EXCEL_IMPORT_VERSION = 12;
29877
30220
  class XlsxReader {
29878
30221
  warningManager;
29879
30222
  xmls;
30223
+ images;
29880
30224
  constructor(files) {
29881
30225
  this.warningManager = new XLSXImportWarningManager();
29882
30226
  this.xmls = {};
30227
+ this.images = [];
29883
30228
  for (let key of Object.keys(files)) {
29884
30229
  // Random files can be in xlsx (like a bin file for printer settings)
29885
30230
  if (key.endsWith(".xml") || key.endsWith(".rels")) {
29886
- this.xmls[key] = parseXML(new XMLString(files[key]));
30231
+ const contentString = escapeTagNamespaces(files[key]);
30232
+ this.xmls[key] = parseXML(new XMLString(contentString));
30233
+ }
30234
+ else if (key.includes("media/image")) {
30235
+ this.images.push({
30236
+ fileName: key,
30237
+ imageSrc: files[key]["imageSrc"],
30238
+ });
29887
30239
  }
29888
30240
  }
29889
30241
  }
@@ -29937,6 +30289,7 @@ class XlsxReader {
29937
30289
  tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),
29938
30290
  pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),
29939
30291
  externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),
30292
+ images: this.images,
29940
30293
  };
29941
30294
  if (!xlsxFileStructure.workbook.rels) {
29942
30295
  throw Error(_lt("Cannot find workbook relations file"));
@@ -30943,7 +31296,7 @@ class BordersPlugin extends CorePlugin {
30943
31296
  }
30944
31297
  /**
30945
31298
  * Set the borders of a cell.
30946
- * It overrides the current border if override == true.
31299
+ * It overrides the current border if override === true.
30947
31300
  */
30948
31301
  setBorder(sheetId, col, row, border, override = true) {
30949
31302
  if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
@@ -32213,7 +32566,9 @@ class FigurePlugin extends CorePlugin {
32213
32566
  const numHeader = this.getters.getNumberRows(sheetId);
32214
32567
  let gridHeight = 0;
32215
32568
  for (let i = 0; i < numHeader; i++) {
32216
- gridHeight += this.getters.getRowSize(sheetId, i);
32569
+ // TODO : since the row size is an UI value now, this doesn't work anymore. Using the default cell height is
32570
+ // a temporary solution at best, but is broken.
32571
+ gridHeight += this.getters.getUserRowSize(sheetId, i) || DEFAULT_CELL_HEIGHT;
32217
32572
  }
32218
32573
  const figures = this.getters.getFigures(sheetId);
32219
32574
  for (const figure of figures) {
@@ -32639,33 +32994,15 @@ class FiltersPlugin extends CorePlugin {
32639
32994
  }
32640
32995
 
32641
32996
  class HeaderSizePlugin extends CorePlugin {
32642
- static getters = ["getRowSize", "getColSize"];
32997
+ static getters = ["getUserRowSize", "getColSize"];
32643
32998
  sizes = {};
32644
32999
  handle(cmd) {
32645
33000
  switch (cmd.type) {
32646
33001
  case "CREATE_SHEET": {
32647
- const computedSizes = this.computeSheetSizes(cmd.sheetId);
32648
- const sizes = {
32649
- COL: computedSizes.COL.map((size) => ({
32650
- manualSize: undefined,
32651
- computedSize: lazy(size),
32652
- })),
32653
- ROW: computedSizes.ROW.map((size) => ({
32654
- manualSize: undefined,
32655
- computedSize: lazy(size),
32656
- })),
32657
- };
32658
- this.history.update("sizes", cmd.sheetId, sizes);
33002
+ this.history.update("sizes", cmd.sheetId, { COL: [], ROW: [] });
32659
33003
  break;
32660
33004
  }
32661
33005
  case "DUPLICATE_SHEET":
32662
- // make sure the values are computed in case the original sheet is deleted
32663
- for (const row of this.sizes[cmd.sheetId].ROW) {
32664
- row.computedSize();
32665
- }
32666
- for (const col of this.sizes[cmd.sheetId].COL) {
32667
- col.computedSize();
32668
- }
32669
33006
  this.history.update("sizes", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));
32670
33007
  break;
32671
33008
  case "DELETE_SHEET":
@@ -32674,21 +33011,8 @@ class HeaderSizePlugin extends CorePlugin {
32674
33011
  this.history.update("sizes", sizes);
32675
33012
  break;
32676
33013
  case "REMOVE_COLUMNS_ROWS": {
32677
- let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
32678
- for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {
32679
- sizes.splice(headerIndex, 1);
32680
- }
32681
- const min = Math.min(...cmd.elements);
32682
- sizes = sizes.map((size, row) => {
32683
- if (cmd.dimension === "ROW" && row >= min) {
32684
- // invalidate sizes
32685
- return {
32686
- manualSize: size.manualSize,
32687
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32688
- };
32689
- }
32690
- return size;
32691
- });
33014
+ const arr = this.sizes[cmd.sheetId][cmd.dimension];
33015
+ const sizes = removeIndexesFromArray(arr, cmd.elements);
32692
33016
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32693
33017
  break;
32694
33018
  }
@@ -32697,51 +33021,18 @@ class HeaderSizePlugin extends CorePlugin {
32697
33021
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
32698
33022
  const baseSize = sizes[cmd.base];
32699
33023
  sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
32700
- sizes = sizes.map((size, row) => {
32701
- if (cmd.dimension === "ROW" && row > cmd.base + cmd.quantity) {
32702
- // invalidate sizes
32703
- return {
32704
- manualSize: size.manualSize,
32705
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32706
- };
32707
- }
32708
- return size;
32709
- });
32710
33024
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32711
33025
  break;
32712
33026
  }
32713
33027
  case "RESIZE_COLUMNS_ROWS":
32714
- for (let el of cmd.elements) {
32715
- if (cmd.dimension === "ROW") {
32716
- const height = this.getRowTallestCellSize(cmd.sheetId, el);
32717
- const size = height;
32718
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32719
- manualSize: cmd.size || undefined,
32720
- computedSize: lazy(size),
32721
- });
33028
+ if (cmd.dimension === "ROW") {
33029
+ for (const el of cmd.elements) {
33030
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32722
33031
  }
32723
- else {
32724
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32725
- manualSize: cmd.size || undefined,
32726
- computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),
32727
- });
32728
- }
32729
- }
32730
- break;
32731
- case "UPDATE_CELL":
32732
- if (!this.sizes[cmd.sheetId]?.["ROW"]?.[cmd.row]?.manualSize) {
32733
- const { sheetId, row } = cmd;
32734
- this.history.update("sizes", sheetId, "ROW", row, "computedSize", lazy(() => this.getRowTallestCellSize(sheetId, row)));
32735
33032
  }
32736
- break;
32737
- case "ADD_MERGE":
32738
- case "REMOVE_MERGE":
32739
- for (let target of cmd.target) {
32740
- for (let row of range(target.top, target.bottom + 1)) {
32741
- const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);
32742
- if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {
32743
- this.history.update("sizes", cmd.sheetId, "ROW", row, "computedSize", lazy(rowHeight));
32744
- }
33033
+ else {
33034
+ for (const el of cmd.elements) {
33035
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32745
33036
  }
32746
33037
  }
32747
33038
  break;
@@ -32749,94 +33040,29 @@ class HeaderSizePlugin extends CorePlugin {
32749
33040
  return;
32750
33041
  }
32751
33042
  getColSize(sheetId, index) {
32752
- return this.getHeaderSize(sheetId, "COL", index);
32753
- }
32754
- getRowSize(sheetId, index) {
32755
- return this.getHeaderSize(sheetId, "ROW", index);
33043
+ return Math.round(this.sizes[sheetId]?.["COL"][index] || DEFAULT_CELL_WIDTH);
32756
33044
  }
32757
- getHeaderSize(sheetId, dimension, index) {
32758
- return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
32759
- this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
32760
- this.getDefaultHeaderSize(dimension));
32761
- }
32762
- computeSheetSizes(sheetId) {
32763
- const sizes = { COL: [], ROW: [] };
32764
- for (let col of range(0, this.getters.getNumberCols(sheetId))) {
32765
- sizes.COL.push(this.getHeaderSize(sheetId, "COL", col));
32766
- }
32767
- for (let row of range(0, this.getters.getNumberRows(sheetId))) {
32768
- let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
32769
- if (!rowSize) {
32770
- const height = this.getRowTallestCellSize(sheetId, row);
32771
- rowSize = height;
32772
- }
32773
- sizes.ROW.push(rowSize);
32774
- }
32775
- return sizes;
32776
- }
32777
- getDefaultHeaderSize(dimension) {
32778
- return dimension === "COL" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;
32779
- }
32780
- /**
32781
- * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
32782
- * merge, or the height of the cell computed based on its font size.
32783
- */
32784
- getCellHeight(position) {
32785
- const merge = this.getters.getMerge(position);
32786
- if (merge && merge.bottom !== merge.top) {
32787
- return DEFAULT_CELL_HEIGHT;
32788
- }
32789
- const cell = this.getters.getCell(position);
32790
- return getDefaultCellHeight(cell);
32791
- }
32792
- /**
32793
- * Get the tallest cell of a row and its size.
32794
- *
32795
- * The tallest cell of the row correspond to the cell with the biggest font size,
32796
- * and that is not part of a multi-line merge.
32797
- */
32798
- getRowTallestCellSize(sheetId, row) {
32799
- const cellIds = this.getters.getRowCells(sheetId, row);
32800
- let maxHeight = 0;
32801
- for (let i = 0; i < cellIds.length; i++) {
32802
- const cell = this.getters.getCellById(cellIds[i]);
32803
- if (!cell)
32804
- continue;
32805
- const position = this.getters.getCellPosition(cell.id);
32806
- const cellHeight = this.getCellHeight(position);
32807
- if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
32808
- maxHeight = cellHeight;
32809
- }
32810
- }
32811
- if (maxHeight <= DEFAULT_CELL_HEIGHT) {
32812
- return DEFAULT_CELL_HEIGHT;
32813
- }
32814
- return maxHeight;
33045
+ getUserRowSize(sheetId, index) {
33046
+ const rowSize = this.sizes[sheetId]?.["ROW"][index];
33047
+ return rowSize ? Math.round(rowSize) : undefined;
32815
33048
  }
32816
33049
  import(data) {
32817
33050
  for (let sheet of data.sheets) {
32818
- const manualSizes = { COL: [], ROW: [] };
33051
+ const sizes = {
33052
+ COL: Array(sheet.colNumber).fill(undefined),
33053
+ ROW: Array(sheet.rowNumber).fill(undefined),
33054
+ };
32819
33055
  for (let [rowIndex, row] of Object.entries(sheet.rows)) {
32820
33056
  if (row.size) {
32821
- manualSizes["ROW"][rowIndex] = row.size;
33057
+ sizes["ROW"][rowIndex] = row.size;
32822
33058
  }
32823
33059
  }
32824
33060
  for (let [colIndex, col] of Object.entries(sheet.cols)) {
32825
33061
  if (col.size) {
32826
- manualSizes["COL"][colIndex] = col.size;
32827
- }
32828
- }
32829
- const computedSizes = this.computeSheetSizes(sheet.id);
32830
- this.sizes[sheet.id] = {
32831
- COL: computedSizes.COL.map((size, i) => ({
32832
- manualSize: manualSizes.COL[i],
32833
- computedSize: lazy(size),
32834
- })),
32835
- ROW: computedSizes.ROW.map((size, i) => ({
32836
- manualSize: manualSizes.ROW[i],
32837
- computedSize: lazy(size),
32838
- })),
32839
- };
33062
+ sizes["COL"][colIndex] = col.size;
33063
+ }
33064
+ }
33065
+ this.sizes[sheet.id] = sizes;
32840
33066
  }
32841
33067
  return;
32842
33068
  }
@@ -32857,9 +33083,12 @@ class HeaderSizePlugin extends CorePlugin {
32857
33083
  if (sheet.rows === undefined) {
32858
33084
  sheet.rows = {};
32859
33085
  }
32860
- for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
32861
- if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
32862
- sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
33086
+ for (const row of range(0, this.getters.getNumberRows(sheet.id))) {
33087
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]) {
33088
+ sheet.rows[row] = {
33089
+ ...sheet.rows[row],
33090
+ size: this.getUserRowSize(sheet.id, row) ?? DEFAULT_CELL_HEIGHT,
33091
+ };
32863
33092
  }
32864
33093
  }
32865
33094
  // Export col sizes
@@ -32867,7 +33096,7 @@ class HeaderSizePlugin extends CorePlugin {
32867
33096
  sheet.cols = {};
32868
33097
  }
32869
33098
  for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
32870
- if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
33099
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]) {
32871
33100
  sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
32872
33101
  }
32873
33102
  }
@@ -33087,7 +33316,7 @@ class ImagePlugin extends CorePlugin {
33087
33316
  handle(cmd) {
33088
33317
  switch (cmd.type) {
33089
33318
  case "CREATE_IMAGE":
33090
- this.addFigure(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33319
+ this.addImage(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33091
33320
  this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
33092
33321
  this.syncedImages.add(cmd.definition.path);
33093
33322
  break;
@@ -33151,7 +33380,7 @@ class ImagePlugin extends CorePlugin {
33151
33380
  // ---------------------------------------------------------------------------
33152
33381
  // Private
33153
33382
  // ---------------------------------------------------------------------------
33154
- addFigure(id, sheetId, position, size) {
33383
+ addImage(id, sheetId, position, size) {
33155
33384
  const figure = {
33156
33385
  id,
33157
33386
  x: position.x,
@@ -35658,7 +35887,6 @@ class PositionBitsEncoder {
35658
35887
  }
35659
35888
  }
35660
35889
 
35661
- const functions = functionRegistry.content;
35662
35890
  //#region
35663
35891
  // ---------------------------------------------------------------------------
35664
35892
  // INTRODUCTION
@@ -35880,9 +36108,7 @@ class EvaluationPlugin extends UIPlugin {
35880
36108
  let isExported = true;
35881
36109
  const formulaCell = this.getCorrespondingFormulaCell(position);
35882
36110
  if (formulaCell) {
35883
- isExported = formulaCell.compiledFormula.tokens
35884
- .filter((tk) => tk.type === "FUNCTION")
35885
- .every((tk) => functions[tk.value.toUpperCase()].isExported);
36111
+ isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
35886
36112
  isFormula = isExported;
35887
36113
  if (!isExported) {
35888
36114
  newContent = value.toString();
@@ -36426,7 +36652,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36426
36652
  case "ContainsText":
36427
36653
  return cell.value.toString().indexOf(values[0].toString()) > -1;
36428
36654
  case "NotContains":
36429
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
36655
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) === -1;
36430
36656
  case "GreaterThan":
36431
36657
  return cell.value > values[0];
36432
36658
  case "GreaterThanOrEqual":
@@ -36453,6 +36679,156 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36453
36679
  };
36454
36680
  }
36455
36681
 
36682
+ class UIRowSizePlugin extends UIPlugin {
36683
+ static getters = ["getRowSize"];
36684
+ tallestCellInRow = {};
36685
+ ctx = document.createElement("canvas").getContext("2d");
36686
+ handle(cmd) {
36687
+ switch (cmd.type) {
36688
+ case "START":
36689
+ for (const sheetId of this.getters.getSheetIds()) {
36690
+ this.initializeSheet(sheetId);
36691
+ }
36692
+ break;
36693
+ case "CREATE_SHEET": {
36694
+ this.initializeSheet(cmd.sheetId);
36695
+ break;
36696
+ }
36697
+ case "DUPLICATE_SHEET": {
36698
+ const tallestCells = deepCopy(this.tallestCellInRow[cmd.sheetId]);
36699
+ this.history.update("tallestCellInRow", cmd.sheetIdTo, tallestCells);
36700
+ break;
36701
+ }
36702
+ case "DELETE_SHEET":
36703
+ const tallestCells = { ...this.tallestCellInRow };
36704
+ delete tallestCells[cmd.sheetId];
36705
+ this.history.update("tallestCellInRow", tallestCells);
36706
+ break;
36707
+ case "REMOVE_COLUMNS_ROWS": {
36708
+ if (cmd.dimension === "COL") {
36709
+ return;
36710
+ }
36711
+ const tallestCells = removeIndexesFromArray(this.tallestCellInRow[cmd.sheetId], cmd.elements);
36712
+ this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
36713
+ break;
36714
+ }
36715
+ case "ADD_COLUMNS_ROWS": {
36716
+ if (cmd.dimension === "COL") {
36717
+ return;
36718
+ }
36719
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
36720
+ const newCells = Array(cmd.quantity).fill(undefined);
36721
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
36722
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
36723
+ break;
36724
+ }
36725
+ case "RESIZE_COLUMNS_ROWS":
36726
+ {
36727
+ const sheetId = cmd.sheetId;
36728
+ if (cmd.dimension === "ROW") {
36729
+ for (const row of cmd.elements) {
36730
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36731
+ this.history.update("tallestCellInRow", sheetId, row, tallestCell);
36732
+ }
36733
+ }
36734
+ else {
36735
+ // Recompute row heights on col size change, they might have changed because of wrapped text
36736
+ for (const row of range(0, this.getters.getNumberRows(sheetId))) {
36737
+ for (const col of cmd.elements) {
36738
+ this.updateRowSizeForCellChange(sheetId, row, col);
36739
+ }
36740
+ }
36741
+ }
36742
+ }
36743
+ break;
36744
+ case "UPDATE_CELL":
36745
+ this.updateRowSizeForCellChange(cmd.sheetId, cmd.row, cmd.col);
36746
+ break;
36747
+ case "ADD_MERGE":
36748
+ case "REMOVE_MERGE":
36749
+ for (const target of cmd.target) {
36750
+ for (const position of positions(target)) {
36751
+ this.updateRowSizeForCellChange(cmd.sheetId, position.row, position.col);
36752
+ }
36753
+ }
36754
+ }
36755
+ return;
36756
+ }
36757
+ getRowSize(sheetId, row) {
36758
+ return Math.round(this.getters.getUserRowSize(sheetId, row) ??
36759
+ this.tallestCellInRow[sheetId][row]?.size ??
36760
+ DEFAULT_CELL_HEIGHT);
36761
+ }
36762
+ updateRowSizeForCellChange(sheetId, row, col) {
36763
+ const tallestCellInRow = this.tallestCellInRow[sheetId]?.[row];
36764
+ if (tallestCellInRow?.cell.col === col) {
36765
+ const newTallestCell = this.getRowTallestCell(sheetId, row);
36766
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36767
+ }
36768
+ const updatedCellHeight = this.getCellHeight({ sheetId, col, row });
36769
+ if (updatedCellHeight <= DEFAULT_CELL_HEIGHT) {
36770
+ return;
36771
+ }
36772
+ if ((!tallestCellInRow && updatedCellHeight > DEFAULT_CELL_HEIGHT) ||
36773
+ (tallestCellInRow && updatedCellHeight > tallestCellInRow.size)) {
36774
+ const newTallestCell = { cell: { sheetId, col, row }, size: updatedCellHeight };
36775
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36776
+ }
36777
+ }
36778
+ initializeSheet(sheetId) {
36779
+ const tallestCells = [];
36780
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
36781
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36782
+ tallestCells.push(tallestCell);
36783
+ }
36784
+ this.history.update("tallestCellInRow", sheetId, tallestCells);
36785
+ }
36786
+ /**
36787
+ * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
36788
+ * merge, or the height of the cell computed based on its style/content.
36789
+ */
36790
+ getCellHeight(position) {
36791
+ if (this.isInMultiRowMerge(position)) {
36792
+ return DEFAULT_CELL_HEIGHT;
36793
+ }
36794
+ const cell = this.getters.getCell(position);
36795
+ const colSize = this.getters.getColSize(position.sheetId, position.col);
36796
+ return getDefaultCellHeight(this.ctx, cell, colSize);
36797
+ }
36798
+ isInMultiRowMerge(position) {
36799
+ const merge = this.getters.getMerge(position);
36800
+ return !!merge && merge.bottom !== merge.top;
36801
+ }
36802
+ /**
36803
+ * Get the tallest cell of a row and its size.
36804
+ */
36805
+ getRowTallestCell(sheetId, row) {
36806
+ const userRowSize = this.getters.getUserRowSize(sheetId, row);
36807
+ if (userRowSize !== undefined) {
36808
+ return undefined;
36809
+ }
36810
+ const cellIds = this.getters.getRowCells(sheetId, row);
36811
+ let maxHeight = 0;
36812
+ let tallestCell = undefined;
36813
+ for (let i = 0; i < cellIds.length; i++) {
36814
+ const cell = this.getters.getCellById(cellIds[i]);
36815
+ if (!cell) {
36816
+ continue;
36817
+ }
36818
+ const position = this.getters.getCellPosition(cell.id);
36819
+ const cellHeight = this.getCellHeight(position);
36820
+ if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
36821
+ maxHeight = cellHeight;
36822
+ tallestCell = { cell: position, size: cellHeight };
36823
+ }
36824
+ }
36825
+ if (tallestCell && tallestCell.size > DEFAULT_CELL_HEIGHT) {
36826
+ return tallestCell;
36827
+ }
36828
+ return undefined;
36829
+ }
36830
+ }
36831
+
36456
36832
  /**
36457
36833
  * This plugin manage the autofill.
36458
36834
  *
@@ -39886,10 +40262,8 @@ class SortPlugin extends UIPlugin {
39886
40262
  }
39887
40263
  updateCellCommands.push(newCellValues);
39888
40264
  }
39889
- for (const cmd of updateCellCommands) {
39890
- this.dispatch("UPDATE_CELL", cmd);
39891
- }
39892
40265
  }
40266
+ updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
39893
40267
  }
39894
40268
  /**
39895
40269
  * Return the distances between main merge cells in the zone.
@@ -39990,24 +40364,28 @@ class SheetUIPlugin extends UIPlugin {
39990
40364
  // Getters
39991
40365
  // ---------------------------------------------------------------------------
39992
40366
  getCellWidth(position) {
39993
- const text = this.getCellText(position);
39994
40367
  const style = this.getters.getCellComputedStyle(position);
39995
- const multiLineText = text.split(NEWLINE);
39996
- let contentWidth = Math.max(...multiLineText.map((line) => this.getTextWidth(line, style)));
40368
+ let contentWidth = 0;
40369
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
40370
+ if (content) {
40371
+ const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
40372
+ contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
40373
+ }
39997
40374
  const icon = this.getters.getConditionalIcon(position);
39998
40375
  if (icon) {
39999
- contentWidth += computeIconWidth(this.getters.getCellStyle(position));
40376
+ contentWidth += computeIconWidth(style);
40000
40377
  }
40001
40378
  const isFilterHeader = this.getters.isFilterHeader(position);
40002
40379
  if (isFilterHeader) {
40003
40380
  contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;
40004
40381
  }
40005
- if (contentWidth > 0) {
40006
- contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40007
- if (this.getters.getCellStyle(position).wrapping === "wrap") {
40008
- const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40009
- return Math.min(colWidth, contentWidth);
40010
- }
40382
+ if (contentWidth === 0) {
40383
+ return 0;
40384
+ }
40385
+ contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40386
+ if (style.wrapping === "wrap") {
40387
+ const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40388
+ return Math.min(colWidth, contentWidth);
40011
40389
  }
40012
40390
  return contentWidth;
40013
40391
  }
@@ -40030,56 +40408,7 @@ class SheetUIPlugin extends UIPlugin {
40030
40408
  getCellMultiLineText(position, width) {
40031
40409
  const style = this.getters.getCellStyle(position);
40032
40410
  const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());
40033
- const brokenText = [];
40034
- for (const line of text.split("\n")) {
40035
- const words = line.split(" ");
40036
- if (!width) {
40037
- brokenText.push(line);
40038
- continue;
40039
- }
40040
- let textLine = "";
40041
- let availableWidth = width;
40042
- for (let word of words) {
40043
- const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);
40044
- const lastPart = splitWord.pop();
40045
- const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);
40046
- // At this step: "splitWord" is an array composed of parts of word whose
40047
- // length is at most equal to "width".
40048
- // Last part contains the end of the word.
40049
- // Note that: When word length is less than width, then lastPart is equal
40050
- // to word and splitWord is empty
40051
- if (splitWord.length) {
40052
- if (textLine !== "") {
40053
- brokenText.push(textLine);
40054
- textLine = "";
40055
- availableWidth = width;
40056
- }
40057
- splitWord.forEach((wordPart) => {
40058
- brokenText.push(wordPart);
40059
- });
40060
- textLine = lastPart;
40061
- availableWidth = width - lastPartWidth;
40062
- }
40063
- else {
40064
- // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
40065
- const _word = textLine === "" ? lastPart : " " + lastPart;
40066
- const wordWidth = computeTextWidth(this.ctx, _word, style);
40067
- if (wordWidth <= availableWidth) {
40068
- textLine += _word;
40069
- availableWidth -= wordWidth;
40070
- }
40071
- else {
40072
- brokenText.push(textLine);
40073
- textLine = lastPart;
40074
- availableWidth = width - lastPartWidth;
40075
- }
40076
- }
40077
- }
40078
- if (textLine !== "") {
40079
- brokenText.push(textLine);
40080
- }
40081
- }
40082
- return brokenText;
40411
+ return splitTextToWidth(this.ctx, text, style, width);
40083
40412
  }
40084
40413
  /**
40085
40414
  * Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -40138,26 +40467,6 @@ class SheetUIPlugin extends UIPlugin {
40138
40467
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
40139
40468
  return Math.max(0, ...sizes);
40140
40469
  }
40141
- splitWordToSpecificWidth(ctx, word, width, style) {
40142
- const wordWidth = computeTextWidth(ctx, word, style);
40143
- if (wordWidth <= width) {
40144
- return [word];
40145
- }
40146
- const splitWord = [];
40147
- let wordPart = "";
40148
- for (let l of word) {
40149
- const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
40150
- if (wordPartWidth > width) {
40151
- splitWord.push(wordPart);
40152
- wordPart = l;
40153
- }
40154
- else {
40155
- wordPart += l;
40156
- }
40157
- }
40158
- splitWord.push(wordPart);
40159
- return splitWord;
40160
- }
40161
40470
  /**
40162
40471
  * Check that any "sheetId" in the command matches an existing
40163
40472
  * sheet.
@@ -41149,7 +41458,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
41149
41458
  .join("\n") || "\t");
41150
41459
  }
41151
41460
  getHTMLContent() {
41152
- if (this.cells.length == 1 && this.cells[0].length == 1) {
41461
+ if (this.cells.length === 1 && this.cells[0].length === 1) {
41153
41462
  return this.getters.getCellText(this.cells[0][0].position);
41154
41463
  }
41155
41464
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
@@ -41474,7 +41783,7 @@ class ClipboardPlugin extends UIPlugin {
41474
41783
  status = "invisible";
41475
41784
  state;
41476
41785
  lastPasteState;
41477
- _isPaintingFormat = false;
41786
+ paintFormatStatus = "inactive";
41478
41787
  originSheetId;
41479
41788
  // ---------------------------------------------------------------------------
41480
41789
  // Command Handling
@@ -41489,7 +41798,7 @@ class ClipboardPlugin extends UIPlugin {
41489
41798
  if (!this.state) {
41490
41799
  return 24 /* CommandResult.EmptyClipboard */;
41491
41800
  }
41492
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41801
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41493
41802
  return this.state.isPasteAllowed(cmd.target, { pasteOption });
41494
41803
  case "PASTE_FROM_OS_CLIPBOARD": {
41495
41804
  const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
@@ -41505,6 +41814,11 @@ class ClipboardPlugin extends UIPlugin {
41505
41814
  const state = this.getClipboardStateForCopyCells(cut, "CUT");
41506
41815
  return state.isPasteAllowed(paste);
41507
41816
  }
41817
+ case "ACTIVATE_PAINT_FORMAT": {
41818
+ if (this.paintFormatStatus !== "inactive") {
41819
+ return 95 /* CommandResult.AlreadyInPaintingFormatMode */;
41820
+ }
41821
+ }
41508
41822
  }
41509
41823
  return 0 /* CommandResult.Success */;
41510
41824
  }
@@ -41521,11 +41835,13 @@ class ClipboardPlugin extends UIPlugin {
41521
41835
  if (!this.state) {
41522
41836
  break;
41523
41837
  }
41524
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41525
- this._isPaintingFormat = false;
41838
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41526
41839
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
41527
41840
  this.lastPasteState = this.state;
41528
- this.status = "invisible";
41841
+ if (this.paintFormatStatus === "oneOff") {
41842
+ this.paintFormatStatus = "inactive";
41843
+ this.status = "invisible";
41844
+ }
41529
41845
  break;
41530
41846
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
41531
41847
  this.status = "invisible";
@@ -41593,8 +41909,13 @@ class ClipboardPlugin extends UIPlugin {
41593
41909
  case "ACTIVATE_PAINT_FORMAT": {
41594
41910
  const zones = this.getters.getSelectedZones();
41595
41911
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
41596
- this._isPaintingFormat = true;
41597
41912
  this.status = "visible";
41913
+ if (cmd.persistent) {
41914
+ this.paintFormatStatus = "persistent";
41915
+ }
41916
+ else {
41917
+ this.paintFormatStatus = "oneOff";
41918
+ }
41598
41919
  break;
41599
41920
  }
41600
41921
  case "DELETE_SHEET":
@@ -41606,6 +41927,11 @@ class ClipboardPlugin extends UIPlugin {
41606
41927
  this.status = "invisible";
41607
41928
  }
41608
41929
  break;
41930
+ case "CANCEL_PAINT_FORMAT": {
41931
+ this.paintFormatStatus = "inactive";
41932
+ this.status = "invisible";
41933
+ break;
41934
+ }
41609
41935
  default:
41610
41936
  if (isCoreCommand(cmd)) {
41611
41937
  this.status = "invisible";
@@ -41636,7 +41962,7 @@ class ClipboardPlugin extends UIPlugin {
41636
41962
  return this.state ? this.state.operation === "CUT" : false;
41637
41963
  }
41638
41964
  isPaintingFormat() {
41639
- return this._isPaintingFormat;
41965
+ return this.paintFormatStatus !== "inactive";
41640
41966
  }
41641
41967
  // ---------------------------------------------------------------------------
41642
41968
  // Private methods
@@ -44271,6 +44597,7 @@ const coreViewsPluginRegistry = new Registry()
44271
44597
  .add("evaluation", EvaluationPlugin)
44272
44598
  .add("evaluation_chart", EvaluationChartPlugin)
44273
44599
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
44600
+ .add("row_size", UIRowSizePlugin)
44274
44601
  .add("custom_colors", CustomColorsPlugin);
44275
44602
 
44276
44603
  const clickableCellRegistry = new Registry();
@@ -44288,7 +44615,7 @@ class ImageProvider {
44288
44615
  async requestImage() {
44289
44616
  const file = await this.getImageFromUser();
44290
44617
  const path = await this.fileStore.upload(file);
44291
- const size = await this.getImageSize(path);
44618
+ const size = await this.getImageOriginalSize(path);
44292
44619
  return { path, size, mimetype: file.type };
44293
44620
  }
44294
44621
  getImageFromUser() {
@@ -44307,7 +44634,7 @@ class ImageProvider {
44307
44634
  input.click();
44308
44635
  });
44309
44636
  }
44310
- getImageSize(path) {
44637
+ getImageOriginalSize(path) {
44311
44638
  return new Promise((resolve, reject) => {
44312
44639
  const image = new Image();
44313
44640
  image.src = path;
@@ -45526,11 +45853,11 @@ class ActionButton extends Component {
45526
45853
  }
45527
45854
  get title() {
45528
45855
  const name = this.actionButton.name(this.env);
45529
- const description = this.actionButton.description;
45856
+ const description = this.actionButton.description(this.env);
45530
45857
  return name + (description ? ` (${description})` : "");
45531
45858
  }
45532
45859
  get iconTitle() {
45533
- return this.actionButton.icon;
45860
+ return this.actionButton.icon(this.env);
45534
45861
  }
45535
45862
  onClick(ev) {
45536
45863
  if (this.isEnabled) {
@@ -45792,6 +46119,10 @@ css /* scss */ `
45792
46119
  top: 20%;
45793
46120
  }
45794
46121
  }
46122
+
46123
+ .user-select-text {
46124
+ user-select: text;
46125
+ }
45795
46126
  `;
45796
46127
  class TopBarComposer extends Component {
45797
46128
  static template = "o-spreadsheet-TopBarComposer";
@@ -45911,6 +46242,27 @@ FontSizeEditor.props = {
45911
46242
  class: String,
45912
46243
  };
45913
46244
 
46245
+ class PaintFormatButton extends Component {
46246
+ static template = "o-spreadsheet-PaintFormatButton";
46247
+ get isActive() {
46248
+ return this.env.model.getters.isPaintingFormat();
46249
+ }
46250
+ onDblClick() {
46251
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: true });
46252
+ }
46253
+ togglePaintFormat() {
46254
+ if (this.isActive) {
46255
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
46256
+ }
46257
+ else {
46258
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: false });
46259
+ }
46260
+ }
46261
+ }
46262
+ PaintFormatButton.props = {
46263
+ class: { type: String, optional: true },
46264
+ };
46265
+
45914
46266
  // -----------------------------------------------------------------------------
45915
46267
  // TopBar
45916
46268
  // -----------------------------------------------------------------------------
@@ -46011,6 +46363,7 @@ class TopBar extends Component {
46011
46363
  TopBarComposer,
46012
46364
  FontSizeEditor,
46013
46365
  ActionButton,
46366
+ PaintFormatButton,
46014
46367
  BorderEditorWidget,
46015
46368
  };
46016
46369
  state = useState({
@@ -47863,7 +48216,7 @@ class SelectionStreamProcessorImpl {
47863
48216
  : "nextCluster";
47864
48217
  while (true) {
47865
48218
  const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);
47866
- // Break if nextPosition == currentPosition, which happens if there's no next valid position
48219
+ // Break if nextPosition === currentPosition, which happens if there's no next valid position
47867
48220
  if (currentPosition.col === nextCellPosition.col &&
47868
48221
  currentPosition.row === nextCellPosition.row) {
47869
48222
  break;
@@ -48830,6 +49183,8 @@ function createImageDrawing(figure, sheet, imageRelId) {
48830
49183
  ["name", `Image ${imageId}`],
48831
49184
  ["title", "Image"],
48832
49185
  ];
49186
+ const cx = convertDotValueToEMU(figure.width);
49187
+ const cy = convertDotValueToEMU(figure.height);
48833
49188
  return escapeXml /*xml*/ `
48834
49189
  <xdr:twoCellAnchor editAs="oneCell">
48835
49190
  <xdr:from>
@@ -48856,6 +49211,9 @@ function createImageDrawing(figure, sheet, imageRelId) {
48856
49211
  </a:stretch>
48857
49212
  </xdr:blipFill>
48858
49213
  <xdr:spPr>
49214
+ <a:xfrm>
49215
+ <a:ext cx="${cx}" cy="${cy}" />
49216
+ </a:xfrm>
48859
49217
  <a:prstGeom prst="rect">
48860
49218
  <a:avLst/>
48861
49219
  </a:prstGeom>
@@ -49375,7 +49733,7 @@ function createWorksheets(data, construct) {
49375
49733
  const mimeType = image.data.mimetype;
49376
49734
  if (mimeType === undefined)
49377
49735
  continue;
49378
- const extension = IMAGE_MIMETYPE_EXTENSION_MAPPING[mimeType];
49736
+ const extension = IMAGE_MIMETYPE_TO_EXTENSION_MAPPING[mimeType];
49379
49737
  // only support exporting images with mimetypes specified in the mapping
49380
49738
  if (extension === undefined)
49381
49739
  continue;
@@ -49511,7 +49869,7 @@ function createRelsFiles(relsFiles) {
49511
49869
  function createContentTypes(files) {
49512
49870
  const overrideNodes = [];
49513
49871
  // hard-code supported image mimetypes
49514
- const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49872
+ const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_TO_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49515
49873
  for (const file of files) {
49516
49874
  if ("contentType" in file && file.contentType) {
49517
49875
  overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
@@ -49772,6 +50130,7 @@ class Model extends EventBus {
49772
50130
  ...config,
49773
50131
  mode: config.mode || "normal",
49774
50132
  custom: config.custom || {},
50133
+ defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
49775
50134
  external: this.setupExternalConfig(config.external || {}),
49776
50135
  transportService,
49777
50136
  client,
@@ -50069,6 +50428,7 @@ const helpers = {
50069
50428
  numberToLetters,
50070
50429
  UuidGenerator,
50071
50430
  formatValue,
50431
+ createCurrencyFormat,
50072
50432
  computeTextWidth,
50073
50433
  createEmptyWorkbookData,
50074
50434
  createEmptySheet,
@@ -50117,9 +50477,9 @@ const constants = {
50117
50477
  DEFAULT_LOCALE,
50118
50478
  };
50119
50479
 
50120
- export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
50480
+ export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
50121
50481
 
50122
50482
 
50123
- __info__.version = '16.4.0-alpha.6';
50124
- __info__.date = '2023-06-28T11:40:12.377Z';
50125
- __info__.hash = '7d15d3c';
50483
+ __info__.version = '16.4.0';
50484
+ __info__.date = '2023-07-24T06:26:19.494Z';
50485
+ __info__.hash = '4eb614c';