@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.
@@ -239,6 +239,7 @@ const DEFAULT_FONT_SIZE = 10;
239
239
  const HEADER_FONT_SIZE = 11;
240
240
  const DEFAULT_FONT = "'Roboto', arial";
241
241
  const DEFAULT_VERTICAL_ALIGN = "bottom";
242
+ const DEFAULT_WRAPPING_MODE = "overflow";
242
243
  // Borders
243
244
  const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
244
245
  const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
@@ -396,82 +397,6 @@ function getCanonicalSheetName(sheetName) {
396
397
  function clip(val, min, max) {
397
398
  return val < min ? min : val > max ? max : val;
398
399
  }
399
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
400
- return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
401
- }
402
- /**
403
- * Get the default height of the cell given its style.
404
- */
405
- function getDefaultCellHeight(cell) {
406
- if (!cell || !cell.content) {
407
- return DEFAULT_CELL_HEIGHT;
408
- }
409
- const fontSize = computeTextFontSizeInPixels(cell.style);
410
- // the number of lines should be computed from the formula result, but it's not evaluated at this point
411
- const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
412
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
413
- }
414
- function computeTextWidth(context, text, style) {
415
- const font = computeTextFont(style);
416
- if (!textWidthCache[font]) {
417
- textWidthCache[font] = {};
418
- }
419
- if (textWidthCache[font][text] === undefined) {
420
- context.save();
421
- context.font = font;
422
- const textWidth = context.measureText(text).width;
423
- context.restore();
424
- textWidthCache[font][text] = textWidth;
425
- }
426
- return textWidthCache[font][text];
427
- }
428
- const textWidthCache = {};
429
- function fontSizeInPixels(fontSize) {
430
- return Math.round((fontSize * 96) / 72);
431
- }
432
- function computeTextFont(style) {
433
- const italic = style.italic ? "italic " : "";
434
- const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
435
- const size = computeTextFontSizeInPixels(style);
436
- return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
437
- }
438
- function computeTextFontSizeInPixels(style) {
439
- const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
440
- return fontSizeInPixels(sizeInPt);
441
- }
442
- /**
443
- * Return the font size that makes the width of a text match the given line width.
444
- * Minimum font size is 1.
445
- *
446
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
447
- */
448
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
449
- let minFontSize = 1;
450
- if (getTextWidth(minFontSize) > lineWidth)
451
- return minFontSize;
452
- if (getTextWidth(maxFontSize) < lineWidth)
453
- return maxFontSize;
454
- // Dichotomic search
455
- let fontSize = (minFontSize + maxFontSize) / 2;
456
- let currentTextWidth = getTextWidth(fontSize);
457
- // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
458
- let iterations = 0;
459
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
460
- if (currentTextWidth >= lineWidth) {
461
- maxFontSize = (minFontSize + maxFontSize) / 2;
462
- }
463
- else {
464
- minFontSize = (minFontSize + maxFontSize) / 2;
465
- }
466
- fontSize = (minFontSize + maxFontSize) / 2;
467
- currentTextWidth = getTextWidth(fontSize);
468
- iterations++;
469
- }
470
- return fontSize;
471
- }
472
- function computeIconWidth(style) {
473
- return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
474
- }
475
400
  /**
476
401
  * Create a range from start (included) to end (excluded).
477
402
  * range(10, 13) => [10, 11, 12]
@@ -726,10 +651,6 @@ function removeFalsyAttributes(obj) {
726
651
  Object.keys(cleanObject).forEach((key) => !cleanObject[key] && delete cleanObject[key]);
727
652
  return cleanObject;
728
653
  }
729
- /** Transform a string to lower case. If the string is undefined, return an empty string */
730
- function toLowerCase(str) {
731
- return str ? str.toLowerCase() : "";
732
- }
733
654
  function transpose2dArray(matrix, callback = (val) => val) {
734
655
  if (!matrix.length)
735
656
  return [];
@@ -818,6 +739,14 @@ function memoize(func) {
818
739
  return cache.get(args[0]);
819
740
  },
820
741
  }[funcName];
742
+ }
743
+ function removeIndexesFromArray(array, indexes) {
744
+ return array.filter((_, index) => !indexes.includes(index));
745
+ }
746
+ function insertItemsAtIndex(array, items, index) {
747
+ const newArray = [...array];
748
+ newArray.splice(index, 0, ...items);
749
+ return newArray;
821
750
  }
822
751
 
823
752
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -961,13 +890,13 @@ function rgbaToHex(rgba) {
961
890
  let g = rgba.g.toString(16);
962
891
  let b = rgba.b.toString(16);
963
892
  let a = Math.round(rgba.a * 255).toString(16);
964
- if (r.length == 1)
893
+ if (r.length === 1)
965
894
  r = "0" + r;
966
- if (g.length == 1)
895
+ if (g.length === 1)
967
896
  g = "0" + g;
968
- if (b.length == 1)
897
+ if (b.length === 1)
969
898
  b = "0" + b;
970
- if (a.length == 1)
899
+ if (a.length === 1)
971
900
  a = "0" + a;
972
901
  if (a === "ff")
973
902
  a = "";
@@ -1070,13 +999,13 @@ function rgbaToHSLA(rgba) {
1070
999
  let l = 0;
1071
1000
  // Calculate hue
1072
1001
  // No difference
1073
- if (delta == 0)
1002
+ if (delta === 0)
1074
1003
  h = 0;
1075
1004
  // Red is max
1076
- else if (cMax == r)
1005
+ else if (cMax === r)
1077
1006
  h = ((g - b) / delta) % 6;
1078
1007
  // Green is max
1079
- else if (cMax == g)
1008
+ else if (cMax === g)
1080
1009
  h = (b - r) / delta + 2;
1081
1010
  // Blue is max
1082
1011
  else
@@ -1087,7 +1016,7 @@ function rgbaToHSLA(rgba) {
1087
1016
  h += 360;
1088
1017
  l = (cMax + cMin) / 2;
1089
1018
  // Calculate saturation
1090
- s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1019
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1091
1020
  // Multiply l and s by 100
1092
1021
  s = +(s * 100).toFixed(1);
1093
1022
  l = +(l * 100).toFixed(1);
@@ -1524,7 +1453,7 @@ function addMonthsToDate(date, months, keepEndOfMonth) {
1524
1453
  }
1525
1454
  function isLeapYear(year) {
1526
1455
  const _year = Math.trunc(year);
1527
- return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 == 0;
1456
+ return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 === 0;
1528
1457
  }
1529
1458
  function getYearFrac(startDate, endDate, _dayCountConvention) {
1530
1459
  if (startDate === endDate) {
@@ -2185,6 +2114,39 @@ function detectNumberFormat(content) {
2185
2114
  }
2186
2115
  return undefined;
2187
2116
  }
2117
+ function createCurrencyFormat(currency) {
2118
+ const decimalPlaces = currency.decimalPlaces ?? 2;
2119
+ const position = currency.position ?? "before";
2120
+ const code = currency.code ?? "";
2121
+ const symbol = currency.symbol ?? "";
2122
+ const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
2123
+ const numberFormat = "#,##0" + decimalRepresentation;
2124
+ let textExpression = `${code} ${symbol}`.trim();
2125
+ if (position === "after" && code) {
2126
+ textExpression = " " + textExpression;
2127
+ }
2128
+ return insertTextInFormat(textExpression, position, numberFormat);
2129
+ }
2130
+ function insertTextInFormat(text, position, format) {
2131
+ const textExpression = `[$${text}]`;
2132
+ return position === "before" ? textExpression + format : format + textExpression;
2133
+ }
2134
+ function roundFormat(format) {
2135
+ const internalFormat = parseFormat(format);
2136
+ const roundedFormat = internalFormat.map((formatPart) => {
2137
+ if (formatPart.type === "NUMBER") {
2138
+ return {
2139
+ type: formatPart.type,
2140
+ format: {
2141
+ ...formatPart.format,
2142
+ decimalPart: undefined,
2143
+ },
2144
+ };
2145
+ }
2146
+ return formatPart;
2147
+ });
2148
+ return convertInternalFormatToFormat(roundedFormat);
2149
+ }
2188
2150
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
2189
2151
  const internalFormat = parseFormat(format || "#,##0");
2190
2152
  const largeNumberFormat = internalFormat
@@ -3278,6 +3240,169 @@ function createDefaultRows(rowNumber) {
3278
3240
  return rows;
3279
3241
  }
3280
3242
 
3243
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
3244
+ return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
3245
+ }
3246
+ /**
3247
+ * Get the default height of the cell given its style.
3248
+ */
3249
+ function getDefaultCellHeight(ctx, cell, colSize) {
3250
+ if (!cell || !cell.content)
3251
+ return DEFAULT_CELL_HEIGHT;
3252
+ const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
3253
+ const numberOfLines = cell.isFormula
3254
+ ? 1
3255
+ : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
3256
+ const fontSize = computeTextFontSizeInPixels(cell.style);
3257
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
3258
+ }
3259
+ const textWidthCache = {};
3260
+ function computeTextWidth(context, text, style) {
3261
+ const font = computeTextFont(style);
3262
+ if (!textWidthCache[font]) {
3263
+ textWidthCache[font] = {};
3264
+ }
3265
+ if (textWidthCache[font][text] === undefined) {
3266
+ context.save();
3267
+ context.font = font;
3268
+ const textWidth = context.measureText(text).width;
3269
+ context.restore();
3270
+ textWidthCache[font][text] = textWidth;
3271
+ }
3272
+ return textWidthCache[font][text];
3273
+ }
3274
+ function fontSizeInPixels(fontSize) {
3275
+ return Math.round((fontSize * 96) / 72);
3276
+ }
3277
+ function computeTextFont(style) {
3278
+ const italic = style.italic ? "italic " : "";
3279
+ const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
3280
+ const size = computeTextFontSizeInPixels(style);
3281
+ return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
3282
+ }
3283
+ function computeTextFontSizeInPixels(style) {
3284
+ const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
3285
+ return fontSizeInPixels(sizeInPt);
3286
+ }
3287
+ function splitWordToSpecificWidth(ctx, word, width, style) {
3288
+ const wordWidth = computeTextWidth(ctx, word, style);
3289
+ if (wordWidth <= width) {
3290
+ return [word];
3291
+ }
3292
+ const splitWord = [];
3293
+ let wordPart = "";
3294
+ for (let l of word) {
3295
+ const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
3296
+ if (wordPartWidth > width) {
3297
+ splitWord.push(wordPart);
3298
+ wordPart = l;
3299
+ }
3300
+ else {
3301
+ wordPart += l;
3302
+ }
3303
+ }
3304
+ splitWord.push(wordPart);
3305
+ return splitWord;
3306
+ }
3307
+ /**
3308
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
3309
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
3310
+ */
3311
+ function splitTextToWidth(ctx, text, style, width) {
3312
+ if (!style)
3313
+ style = {};
3314
+ const brokenText = [];
3315
+ // Checking if text contains NEWLINE before split makes it very slightly slower if text contains it,
3316
+ // but 5-10x faster if it doesn't
3317
+ const lines = text.includes(NEWLINE) ? text.split(NEWLINE) : [text];
3318
+ for (const line of lines) {
3319
+ const words = line.includes(" ") ? line.split(" ") : [line];
3320
+ if (!width) {
3321
+ brokenText.push(line);
3322
+ continue;
3323
+ }
3324
+ let textLine = "";
3325
+ let availableWidth = width;
3326
+ for (let word of words) {
3327
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
3328
+ const lastPart = splitWord.pop();
3329
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
3330
+ // At this step: "splitWord" is an array composed of parts of word whose
3331
+ // length is at most equal to "width".
3332
+ // Last part contains the end of the word.
3333
+ // Note that: When word length is less than width, then lastPart is equal
3334
+ // to word and splitWord is empty
3335
+ if (splitWord.length) {
3336
+ if (textLine !== "") {
3337
+ brokenText.push(textLine);
3338
+ textLine = "";
3339
+ availableWidth = width;
3340
+ }
3341
+ splitWord.forEach((wordPart) => {
3342
+ brokenText.push(wordPart);
3343
+ });
3344
+ textLine = lastPart;
3345
+ availableWidth = width - lastPartWidth;
3346
+ }
3347
+ else {
3348
+ // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
3349
+ const _word = textLine === "" ? lastPart : " " + lastPart;
3350
+ const wordWidth = computeTextWidth(ctx, _word, style);
3351
+ if (wordWidth <= availableWidth) {
3352
+ textLine += _word;
3353
+ availableWidth -= wordWidth;
3354
+ }
3355
+ else {
3356
+ brokenText.push(textLine);
3357
+ textLine = lastPart;
3358
+ availableWidth = width - lastPartWidth;
3359
+ }
3360
+ }
3361
+ }
3362
+ if (textLine !== "") {
3363
+ brokenText.push(textLine);
3364
+ }
3365
+ }
3366
+ return brokenText;
3367
+ }
3368
+ /**
3369
+ * Return the font size that makes the width of a text match the given line width.
3370
+ * Minimum font size is 1.
3371
+ *
3372
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
3373
+ */
3374
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
3375
+ let minFontSize = 1;
3376
+ if (getTextWidth(minFontSize) > lineWidth)
3377
+ return minFontSize;
3378
+ if (getTextWidth(maxFontSize) < lineWidth)
3379
+ return maxFontSize;
3380
+ // Dichotomic search
3381
+ let fontSize = (minFontSize + maxFontSize) / 2;
3382
+ let currentTextWidth = getTextWidth(fontSize);
3383
+ // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
3384
+ let iterations = 0;
3385
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
3386
+ if (currentTextWidth >= lineWidth) {
3387
+ maxFontSize = (minFontSize + maxFontSize) / 2;
3388
+ }
3389
+ else {
3390
+ minFontSize = (minFontSize + maxFontSize) / 2;
3391
+ }
3392
+ fontSize = (minFontSize + maxFontSize) / 2;
3393
+ currentTextWidth = getTextWidth(fontSize);
3394
+ iterations++;
3395
+ }
3396
+ return fontSize;
3397
+ }
3398
+ function computeIconWidth(style) {
3399
+ return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
3400
+ }
3401
+ /** Transform a string to lower case. If the string is undefined, return an empty string */
3402
+ function toLowerCase(str) {
3403
+ return str ? str.toLowerCase() : "";
3404
+ }
3405
+
3281
3406
  /*
3282
3407
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
3283
3408
  * */
@@ -3300,7 +3425,7 @@ class UuidGenerator {
3300
3425
  else {
3301
3426
  // mainly for jest and other browsers that do not have the crypto functionality
3302
3427
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
3303
- var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
3428
+ var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
3304
3429
  return v.toString(16);
3305
3430
  });
3306
3431
  }
@@ -4128,6 +4253,7 @@ exports.CommandResult = void 0;
4128
4253
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 92] = "NoSplitSeparatorInSelection";
4129
4254
  CommandResult[CommandResult["NoActiveSheet"] = 93] = "NoActiveSheet";
4130
4255
  CommandResult[CommandResult["InvalidLocale"] = 94] = "InvalidLocale";
4256
+ CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 95] = "AlreadyInPaintingFormatMode";
4131
4257
  })(exports.CommandResult || (exports.CommandResult = {}));
4132
4258
 
4133
4259
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5943,8 +6069,8 @@ class Menu extends owl.Component {
5943
6069
  return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
5944
6070
  }
5945
6071
  getIconName(menu) {
5946
- if (menu.icon) {
5947
- return menu.icon;
6072
+ if (menu.icon(this.env)) {
6073
+ return menu.icon(this.env);
5948
6074
  }
5949
6075
  if (menu.isActive?.(this.env)) {
5950
6076
  return "o-spreadsheet-Icon.CHECK";
@@ -6378,7 +6504,7 @@ function invertMatrix(M) {
6378
6504
  for (let pivot = 0; pivot < dim; pivot++) {
6379
6505
  let diagonalElement = C[pivot][pivot];
6380
6506
  // if we have a 0 on the diagonal we'll need to swap with a lower row
6381
- if (diagonalElement == 0) {
6507
+ if (diagonalElement === 0) {
6382
6508
  //look through every row below the i'th row
6383
6509
  for (let row = pivot + 1; row < dim; row++) {
6384
6510
  //if the ii'th row has a non-0 in the i'th col, swap it with that row
@@ -6405,7 +6531,7 @@ function invertMatrix(M) {
6405
6531
  // the other rows so that there will be 0's in this column in the
6406
6532
  // rows above and below this one
6407
6533
  for (let row = 0; row < dim; row++) {
6408
- if (row == pivot) {
6534
+ if (row === pivot) {
6409
6535
  continue;
6410
6536
  }
6411
6537
  // We want to change this element to 0
@@ -12470,7 +12596,7 @@ const ISNA = {
12470
12596
  return false;
12471
12597
  }
12472
12598
  catch (e) {
12473
- return e?.errorType == CellErrorType.NotAvailable;
12599
+ return e?.errorType === CellErrorType.NotAvailable;
12474
12600
  }
12475
12601
  },
12476
12602
  isExported: true,
@@ -12596,6 +12722,18 @@ const AND = {
12596
12722
  isExported: true,
12597
12723
  };
12598
12724
  // -----------------------------------------------------------------------------
12725
+ // FALSE
12726
+ // -----------------------------------------------------------------------------
12727
+ const FALSE = {
12728
+ description: _lt("Logical value `false`."),
12729
+ args: [],
12730
+ returns: ["BOOLEAN"],
12731
+ compute: function () {
12732
+ return false;
12733
+ },
12734
+ isExported: true,
12735
+ };
12736
+ // -----------------------------------------------------------------------------
12599
12737
  // IF
12600
12738
  // -----------------------------------------------------------------------------
12601
12739
  const IF = {
@@ -12731,6 +12869,18 @@ const OR = {
12731
12869
  isExported: true,
12732
12870
  };
12733
12871
  // -----------------------------------------------------------------------------
12872
+ // TRUE
12873
+ // -----------------------------------------------------------------------------
12874
+ const TRUE = {
12875
+ description: _lt("Logical value `true`."),
12876
+ args: [],
12877
+ returns: ["BOOLEAN"],
12878
+ compute: function () {
12879
+ return true;
12880
+ },
12881
+ isExported: true,
12882
+ };
12883
+ // -----------------------------------------------------------------------------
12734
12884
  // XOR
12735
12885
  // -----------------------------------------------------------------------------
12736
12886
  const XOR = {
@@ -12757,12 +12907,14 @@ const XOR = {
12757
12907
  var logical = /*#__PURE__*/Object.freeze({
12758
12908
  __proto__: null,
12759
12909
  AND: AND,
12910
+ FALSE: FALSE,
12760
12911
  IF: IF,
12761
12912
  IFERROR: IFERROR,
12762
12913
  IFNA: IFNA,
12763
12914
  IFS: IFS,
12764
12915
  NOT: NOT,
12765
12916
  OR: OR,
12917
+ TRUE: TRUE,
12766
12918
  XOR: XOR
12767
12919
  });
12768
12920
 
@@ -13949,7 +14101,6 @@ function indentCode(code) {
13949
14101
  * is useful for the composer, which needs to be able to work with incomplete
13950
14102
  * formulas.
13951
14103
  */
13952
- const functions$3 = functionRegistry.content;
13953
14104
  const POSTFIX_UNARY_OPERATORS = ["%"];
13954
14105
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
13955
14106
  function tokenize(str, locale = DEFAULT_LOCALE) {
@@ -14080,17 +14231,11 @@ function tokenizeSymbol(chars) {
14080
14231
  }
14081
14232
  if (result.length) {
14082
14233
  const value = result;
14083
- const isFunction = value.toUpperCase() in functions$3;
14084
- if (isFunction) {
14085
- return { type: "FUNCTION", value };
14086
- }
14087
14234
  const isReference = rangeReference.test(value);
14088
14235
  if (isReference) {
14089
14236
  return { type: "REFERENCE", value };
14090
14237
  }
14091
- else {
14092
- return { type: "SYMBOL", value };
14093
- }
14238
+ return { type: "SYMBOL", value };
14094
14239
  }
14095
14240
  return null;
14096
14241
  }
@@ -14190,9 +14335,6 @@ function parseOperand(tokens) {
14190
14335
  return { type: "NUMBER", value: parseNumber(current.value, DEFAULT_LOCALE) };
14191
14336
  case "STRING":
14192
14337
  return { type: "STRING", value: removeStringQuotes(current.value) };
14193
- case "FUNCTION":
14194
- const args = parseFunctionArgs(tokens);
14195
- return { type: "FUNCALL", value: current.value, args };
14196
14338
  case "INVALID_REFERENCE":
14197
14339
  throw new InvalidReferenceError();
14198
14340
  case "REFERENCE":
@@ -14209,13 +14351,15 @@ function parseOperand(tokens) {
14209
14351
  value: current.value,
14210
14352
  };
14211
14353
  case "SYMBOL":
14212
- if (["TRUE", "FALSE"].includes(current.value.toUpperCase())) {
14213
- return { type: "BOOLEAN", value: current.value.toUpperCase() === "TRUE" };
14354
+ const value = current.value;
14355
+ const nextToken = tokens[0];
14356
+ if (nextToken?.type === "LEFT_PAREN" && functionRegex.test(current.value)) {
14357
+ const args = parseFunctionArgs(tokens);
14358
+ return { type: "FUNCALL", value: value, args };
14214
14359
  }
14215
- if (current.value) {
14216
- if (functionRegex.test(current.value) && tokens[0]?.type === "LEFT_PAREN") {
14217
- throw new UnknownFunctionError(current.value);
14218
- }
14360
+ const upperCaseValue = value.toUpperCase();
14361
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
14362
+ return { type: "BOOLEAN", value: upperCaseValue === "TRUE" };
14219
14363
  }
14220
14364
  throw new BadExpressionError(_lt("Invalid formula"));
14221
14365
  case "LEFT_PAREN":
@@ -14317,36 +14461,60 @@ function parseTokens(tokens) {
14317
14461
  * to nodes of a specific type.
14318
14462
  * Useful if you want to convert some part of a formula.
14319
14463
  *
14320
- * e.g.
14321
- * ```ts
14464
+ * @example
14322
14465
  * convertAstNodes(ast, "FUNCALL", convertFormulaToExcel)
14323
14466
  *
14324
14467
  * function convertFormulaToExcel(ast: ASTFuncall) {
14325
14468
  * // ...
14326
14469
  * return modifiedAst
14327
14470
  * }
14328
- * ```
14329
14471
  */
14330
14472
  function convertAstNodes(ast, type, fn) {
14331
- if (type === ast.type) {
14332
- ast = fn(ast);
14473
+ return mapAst(ast, (ast) => {
14474
+ if (ast.type === type) {
14475
+ return fn(ast);
14476
+ }
14477
+ return ast;
14478
+ });
14479
+ }
14480
+ function iterateAstNodes(ast) {
14481
+ return Array.from(astIterator(ast));
14482
+ }
14483
+ function* astIterator(ast) {
14484
+ yield ast;
14485
+ switch (ast.type) {
14486
+ case "FUNCALL":
14487
+ for (const arg of ast.args) {
14488
+ yield* astIterator(arg);
14489
+ }
14490
+ break;
14491
+ case "UNARY_OPERATION":
14492
+ yield* astIterator(ast.operand);
14493
+ break;
14494
+ case "BIN_OPERATION":
14495
+ yield* astIterator(ast.left);
14496
+ yield* astIterator(ast.right);
14497
+ break;
14333
14498
  }
14499
+ }
14500
+ function mapAst(ast, fn) {
14501
+ ast = fn(ast);
14334
14502
  switch (ast.type) {
14335
14503
  case "FUNCALL":
14336
14504
  return {
14337
14505
  ...ast,
14338
- args: ast.args.map((child) => convertAstNodes(child, type, fn)),
14506
+ args: ast.args.map((child) => mapAst(child, fn)),
14339
14507
  };
14340
14508
  case "UNARY_OPERATION":
14341
14509
  return {
14342
14510
  ...ast,
14343
- operand: convertAstNodes(ast.operand, type, fn),
14511
+ operand: mapAst(ast.operand, fn),
14344
14512
  };
14345
14513
  case "BIN_OPERATION":
14346
14514
  return {
14347
14515
  ...ast,
14348
- right: convertAstNodes(ast.right, type, fn),
14349
- left: convertAstNodes(ast.left, type, fn),
14516
+ right: mapAst(ast.right, fn),
14517
+ left: mapAst(ast.left, fn),
14350
14518
  };
14351
14519
  default:
14352
14520
  return ast;
@@ -14616,6 +14784,9 @@ function compile(formula) {
14616
14784
  const { args } = ast;
14617
14785
  const functionName = ast.value.toUpperCase();
14618
14786
  const functionDefinition = functions$2[functionName];
14787
+ if (!functionDefinition) {
14788
+ throw new UnknownFunctionError(ast.value);
14789
+ }
14619
14790
  assertEnoughArgs(ast);
14620
14791
  const compiledArgs = [];
14621
14792
  for (let i = 0; i < args.length; i++) {
@@ -14874,7 +15045,7 @@ function mapParentFunction(tokens) {
14874
15045
  functionStarted = "";
14875
15046
  }
14876
15047
  switch (token.type) {
14877
- case "FUNCTION":
15048
+ case "SYMBOL":
14878
15049
  functionStarted = token.value;
14879
15050
  break;
14880
15051
  case "LEFT_PAREN":
@@ -14911,6 +15082,17 @@ function composerTokenize(formula, locale) {
14911
15082
  return mapParentFunction(mapParenthesis(enrichTokens(tokens)));
14912
15083
  }
14913
15084
 
15085
+ const functions$1 = functionRegistry.content;
15086
+ function isExportableToExcel(tokens) {
15087
+ try {
15088
+ const nonExportableFunctions = iterateAstNodes(parseTokens(tokens)).filter((ast) => ast.type === "FUNCALL" && !functions$1[ast.value.toUpperCase()]?.isExported);
15089
+ return nonExportableFunctions.length === 0;
15090
+ }
15091
+ catch (error) {
15092
+ return false;
15093
+ }
15094
+ }
15095
+
14914
15096
  function isValidLocale(locale) {
14915
15097
  if (!(locale &&
14916
15098
  typeof locale === "object" &&
@@ -15130,6 +15312,8 @@ const uuidGenerator$2 = new UuidGenerator();
15130
15312
  function createAction(item) {
15131
15313
  const name = item.name;
15132
15314
  const children = item.children;
15315
+ const description = item.description;
15316
+ const icon = item.icon;
15133
15317
  return {
15134
15318
  id: item.id || uuidGenerator$2.uuidv4(),
15135
15319
  name: typeof name === "function" ? name : () => name,
@@ -15147,8 +15331,8 @@ function createAction(item) {
15147
15331
  : () => [],
15148
15332
  isReadonlyAllowed: item.isReadonlyAllowed || false,
15149
15333
  separator: item.separator || false,
15150
- icon: item.icon || "",
15151
- description: item.description || "",
15334
+ icon: typeof icon === "function" ? icon : () => icon || "",
15335
+ description: typeof description === "function" ? description : () => description || "",
15152
15336
  textColor: item.textColor,
15153
15337
  sequence: item.sequence || 0,
15154
15338
  };
@@ -16614,7 +16798,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
16614
16798
  function convertDateFormatForMoment(format) {
16615
16799
  format = format.replace(/y/g, "Y");
16616
16800
  format = format.replace(/d/g, "D");
16617
- // "m" before "h" == month, "m" after "h" == minute
16801
+ // "m" before "h" === month, "m" after "h" === minute
16618
16802
  const indexH = format.indexOf("h");
16619
16803
  if (indexH >= 0) {
16620
16804
  format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
@@ -17468,6 +17652,7 @@ function getChartMenu(figureId, onFigureDeleted, env) {
17468
17652
  env.model.dispatch("SELECT_FIGURE", { id: figureId });
17469
17653
  env.openSidePanel("ChartPanel");
17470
17654
  },
17655
+ icon: "o-spreadsheet-Icon.EDIT",
17471
17656
  },
17472
17657
  getCopyMenuItem(figureId, env),
17473
17658
  getCutMenuItem(figureId, env),
@@ -17483,8 +17668,14 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17483
17668
  id: "reset_size",
17484
17669
  name: _lt("Reset size"),
17485
17670
  sequence: 4,
17486
- execute: () => {
17487
- const size = env.model.getters.getImageSize(figureId);
17671
+ execute: async () => {
17672
+ const imagePath = env.model.getters.getImagePath(figureId);
17673
+ const size = env.model.getters.getImageSize(figureId) ??
17674
+ (await env.imageProvider?.getImageOriginalSize(imagePath));
17675
+ if (!env.model.getters.getImageSize(figureId)) {
17676
+ const image = env.model.getters.getImage(figureId);
17677
+ image.size = size;
17678
+ }
17488
17679
  const { height, width } = getMaxFigureSize(env.model.getters, size);
17489
17680
  env.model.dispatch("UPDATE_FIGURE", {
17490
17681
  sheetId: env.model.getters.getActiveSheetId(),
@@ -17493,6 +17684,7 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17493
17684
  width,
17494
17685
  });
17495
17686
  },
17687
+ icon: "o-spreadsheet-Icon.REFRESH",
17496
17688
  },
17497
17689
  getDeleteMenuItem(figureId, onFigureDeleted, env),
17498
17690
  ];
@@ -17509,6 +17701,7 @@ function getCopyMenuItem(figureId, env) {
17509
17701
  env.model.dispatch("COPY");
17510
17702
  await env.clipboard.write(env.model.getters.getClipboardContent());
17511
17703
  },
17704
+ icon: "o-spreadsheet-Icon.COPY",
17512
17705
  };
17513
17706
  }
17514
17707
  function getCutMenuItem(figureId, env) {
@@ -17522,6 +17715,7 @@ function getCutMenuItem(figureId, env) {
17522
17715
  env.model.dispatch("CUT");
17523
17716
  await env.clipboard.write(env.model.getters.getClipboardContent());
17524
17717
  },
17718
+ icon: "o-spreadsheet-Icon.CUT",
17525
17719
  };
17526
17720
  }
17527
17721
  function getDeleteMenuItem(figureId, onFigureDeleted, env) {
@@ -17536,6 +17730,7 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
17536
17730
  });
17537
17731
  onFigureDeleted();
17538
17732
  },
17733
+ icon: "o-spreadsheet-Icon.DELETE",
17539
17734
  };
17540
17735
  }
17541
17736
 
@@ -18603,7 +18798,7 @@ const insertFunctionMin = {
18603
18798
  };
18604
18799
  const categorieFunctionAll = {
18605
18800
  name: _lt("All"),
18606
- children: allFunctionListMenuBuilder(),
18801
+ children: [allFunctionListMenuBuilder],
18607
18802
  };
18608
18803
  function allFunctionListMenuBuilder() {
18609
18804
  const fnNames = functionRegistry.getKeys();
@@ -18935,7 +19130,10 @@ const formatNumberAutomatic = {
18935
19130
  };
18936
19131
  const formatNumberNumber = {
18937
19132
  name: _lt("Number"),
18938
- description: "1,000.12",
19133
+ description: (env) => formatValue(1000.12, {
19134
+ format: "#,##0.00",
19135
+ locale: env.model.getters.getLocale(),
19136
+ }),
18939
19137
  execute: (env) => setFormatter(env, "#,##0.00"),
18940
19138
  isActive: (env) => isFormatSelected(env, "#,##0.00"),
18941
19139
  };
@@ -18946,21 +19144,34 @@ const formatPercent = {
18946
19144
  };
18947
19145
  const formatNumberPercent = {
18948
19146
  name: _lt("Percent"),
18949
- description: "10.12%",
19147
+ description: (env) => formatValue(0.1012, {
19148
+ format: "0.00%",
19149
+ locale: env.model.getters.getLocale(),
19150
+ }),
18950
19151
  execute: FORMAT_PERCENT_ACTION,
18951
19152
  isActive: (env) => isFormatSelected(env, "0.00%"),
18952
19153
  };
18953
19154
  const formatNumberCurrency = {
18954
19155
  name: _lt("Currency"),
18955
- description: "$1,000.12",
18956
- execute: (env) => setFormatter(env, "[$$]#,##0.00"),
18957
- isActive: (env) => isFormatSelected(env, "[$$]#,##0.00"),
19156
+ description: (env) => formatValue(1000.12, {
19157
+ format: env.model.config.defaultCurrencyFormat,
19158
+ locale: env.model.getters.getLocale(),
19159
+ }),
19160
+ execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
19161
+ isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
18958
19162
  };
18959
19163
  const formatNumberCurrencyRounded = {
18960
19164
  name: _lt("Currency rounded"),
18961
- description: "$1,000",
18962
- execute: (env) => setFormatter(env, "[$$]#,##0"),
18963
- isActive: (env) => isFormatSelected(env, "[$$]#,##0"),
19165
+ description: (env) => formatValue(1000, {
19166
+ format: roundFormat(env.model.config.defaultCurrencyFormat),
19167
+ locale: env.model.getters.getLocale(),
19168
+ }),
19169
+ execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19170
+ isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19171
+ isVisible: (env) => {
19172
+ const currencyFormat = env.model.config.defaultCurrencyFormat;
19173
+ return currencyFormat !== roundFormat(currencyFormat);
19174
+ },
18964
19175
  };
18965
19176
  const formatCustomCurrency = {
18966
19177
  name: _lt("Custom currency"),
@@ -18969,19 +19180,37 @@ const formatCustomCurrency = {
18969
19180
  };
18970
19181
  const formatNumberDate = {
18971
19182
  name: _lt("Date"),
18972
- description: "9/26/2008",
19183
+ description: (env) => {
19184
+ const locale = env.model.getters.getLocale();
19185
+ return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
19186
+ format: locale.dateFormat,
19187
+ locale,
19188
+ });
19189
+ },
18973
19190
  execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
18974
19191
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
18975
19192
  };
18976
19193
  const formatNumberTime = {
18977
19194
  name: _lt("Time"),
18978
- description: "10:43:00 PM",
19195
+ description: (env) => {
19196
+ const locale = env.model.getters.getLocale();
19197
+ return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
19198
+ format: locale.timeFormat,
19199
+ locale,
19200
+ });
19201
+ },
18979
19202
  execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
18980
19203
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
18981
19204
  };
18982
19205
  const formatNumberDateTime = {
18983
19206
  name: _lt("Date time"),
18984
- description: "9/26/2008 22:43:00",
19207
+ description: (env) => {
19208
+ const locale = env.model.getters.getLocale();
19209
+ return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
19210
+ format: locale.dateFormat + " " + locale.timeFormat,
19211
+ locale,
19212
+ });
19213
+ },
18985
19214
  execute: (env) => {
18986
19215
  const locale = env.model.getters.getLocale();
18987
19216
  setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
@@ -19053,7 +19282,7 @@ const formatAlignment = {
19053
19282
  };
19054
19283
  const formatAlignmentHorizontal = {
19055
19284
  name: _lt("Horizontal align"),
19056
- icon: "o-spreadsheet-Icon.ALIGN_LEFT",
19285
+ icon: (env) => getHorizontalAlignmentIcon(env),
19057
19286
  };
19058
19287
  const formatAlignmentLeft = {
19059
19288
  name: _lt("Left"),
@@ -19078,46 +19307,50 @@ const formatAlignmentRight = {
19078
19307
  };
19079
19308
  const formatAlignmentVertical = {
19080
19309
  name: _lt("Vertical align"),
19081
- icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19310
+ icon: (env) => getVerticalAlignmentIcon(env),
19082
19311
  };
19083
19312
  const formatAlignmentTop = {
19084
19313
  name: _lt("Top"),
19085
19314
  execute: (env) => setStyle(env, { verticalAlign: "top" }),
19086
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "top",
19315
+ isActive: (env) => getVerticalAlign(env) === "top",
19087
19316
  icon: "o-spreadsheet-Icon.ALIGN_TOP",
19088
19317
  };
19089
19318
  const formatAlignmentMiddle = {
19090
19319
  name: _lt("Middle"),
19091
19320
  execute: (env) => setStyle(env, { verticalAlign: "middle" }),
19092
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "middle",
19321
+ isActive: (env) => getVerticalAlign(env) === "middle",
19093
19322
  icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19094
19323
  };
19095
19324
  const formatAlignmentBottom = {
19096
19325
  name: _lt("Bottom"),
19097
19326
  execute: (env) => setStyle(env, { verticalAlign: "bottom" }),
19098
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "bottom",
19327
+ isActive: (env) => getVerticalAlign(env) === "bottom",
19099
19328
  icon: "o-spreadsheet-Icon.ALIGN_BOTTOM",
19100
19329
  };
19101
- const formatWrapping = {
19330
+ const formatWrappingIcon = {
19102
19331
  name: _lt("Wrapping"),
19103
19332
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19104
19333
  };
19334
+ const formatWrapping = {
19335
+ name: _lt("Wrapping"),
19336
+ icon: (env) => getWrapModeIcon(env),
19337
+ };
19105
19338
  const formatWrappingOverflow = {
19106
19339
  name: _lt("Overflow"),
19107
19340
  execute: (env) => setStyle(env, { wrapping: "overflow" }),
19108
- isActive: (env) => (env.model.getters.getCurrentStyle().wrapping || "overflow") === "overflow",
19341
+ isActive: (env) => getWrappingMode(env) === "overflow",
19109
19342
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19110
19343
  };
19111
19344
  const formatWrappingWrap = {
19112
19345
  name: _lt("Wrap"),
19113
19346
  execute: (env) => setStyle(env, { wrapping: "wrap" }),
19114
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "wrap",
19347
+ isActive: (env) => getWrappingMode(env) === "wrap",
19115
19348
  icon: "o-spreadsheet-Icon.WRAPPING_WRAP",
19116
19349
  };
19117
19350
  const formatWrappingClip = {
19118
19351
  name: _lt("Clip"),
19119
19352
  execute: (env) => setStyle(env, { wrapping: "clip" }),
19120
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "clip",
19353
+ isActive: (env) => getWrappingMode(env) === "clip",
19121
19354
  icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
19122
19355
  };
19123
19356
  const textColor = {
@@ -19133,14 +19366,6 @@ const formatCF = {
19133
19366
  execute: OPEN_CF_SIDEPANEL_ACTION,
19134
19367
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
19135
19368
  };
19136
- const paintFormat = {
19137
- name: _lt("Paint Format"),
19138
- execute: (env) => env.model.dispatch("ACTIVATE_PAINT_FORMAT", {
19139
- target: env.model.getters.getSelectedZones(),
19140
- }),
19141
- icon: "o-spreadsheet-Icon.PAINT_FORMAT",
19142
- isActive: (env) => env.model.getters.isPaintingFormat(),
19143
- };
19144
19369
  const clearFormat = {
19145
19370
  name: _lt("Clear formatting"),
19146
19371
  description: "Ctrl+<",
@@ -19180,6 +19405,53 @@ function getHorizontalAlign(env) {
19180
19405
  }
19181
19406
  const cell = env.model.getters.getActiveCell();
19182
19407
  return cell.defaultAlign;
19408
+ }
19409
+ function getVerticalAlign(env) {
19410
+ const style = env.model.getters.getCurrentStyle();
19411
+ if (style.verticalAlign) {
19412
+ return style.verticalAlign;
19413
+ }
19414
+ return DEFAULT_VERTICAL_ALIGN;
19415
+ }
19416
+ function getWrappingMode(env) {
19417
+ const style = env.model.getters.getCurrentStyle();
19418
+ if (style.wrapping) {
19419
+ return style.wrapping;
19420
+ }
19421
+ return DEFAULT_WRAPPING_MODE;
19422
+ }
19423
+ function getHorizontalAlignmentIcon(env) {
19424
+ const horizontalAlign = getHorizontalAlign(env);
19425
+ switch (horizontalAlign) {
19426
+ case "right":
19427
+ return "o-spreadsheet-Icon.ALIGN_RIGHT";
19428
+ case "center":
19429
+ return "o-spreadsheet-Icon.ALIGN_CENTER";
19430
+ default:
19431
+ return "o-spreadsheet-Icon.ALIGN_LEFT";
19432
+ }
19433
+ }
19434
+ function getVerticalAlignmentIcon(env) {
19435
+ const verticalAlign = getVerticalAlign(env);
19436
+ switch (verticalAlign) {
19437
+ case "top":
19438
+ return "o-spreadsheet-Icon.ALIGN_TOP";
19439
+ case "middle":
19440
+ return "o-spreadsheet-Icon.ALIGN_MIDDLE";
19441
+ default:
19442
+ return "o-spreadsheet-Icon.ALIGN_BOTTOM";
19443
+ }
19444
+ }
19445
+ function getWrapModeIcon(env) {
19446
+ const wrapMode = getWrappingMode(env);
19447
+ switch (wrapMode) {
19448
+ case "wrap":
19449
+ return "o-spreadsheet-Icon.WRAPPING_WRAP";
19450
+ case "clip":
19451
+ return "o-spreadsheet-Icon.WRAPPING_CLIP";
19452
+ default:
19453
+ return "o-spreadsheet-Icon.WRAPPING_OVERFLOW";
19454
+ }
19183
19455
  }
19184
19456
 
19185
19457
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
@@ -19211,6 +19483,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19211
19483
  formatAlignmentTop: formatAlignmentTop,
19212
19484
  formatAlignmentMiddle: formatAlignmentMiddle,
19213
19485
  formatAlignmentBottom: formatAlignmentBottom,
19486
+ formatWrappingIcon: formatWrappingIcon,
19214
19487
  formatWrapping: formatWrapping,
19215
19488
  formatWrappingOverflow: formatWrappingOverflow,
19216
19489
  formatWrappingWrap: formatWrappingWrap,
@@ -19218,7 +19491,6 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19218
19491
  textColor: textColor,
19219
19492
  fillColor: fillColor,
19220
19493
  formatCF: formatCF,
19221
- paintFormat: paintFormat,
19222
19494
  clearFormat: clearFormat
19223
19495
  });
19224
19496
 
@@ -19981,7 +20253,7 @@ topbarMenuRegistry
19981
20253
  separator: true,
19982
20254
  })
19983
20255
  .addChild("format_wrapping", ["format"], {
19984
- ...formatWrapping,
20256
+ ...formatWrappingIcon,
19985
20257
  sequence: 80,
19986
20258
  separator: true,
19987
20259
  })
@@ -22271,17 +22543,45 @@ class CustomCurrencyPanel extends owl.Component {
22271
22543
  }
22272
22544
  get formatProposals() {
22273
22545
  const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];
22274
- const proposalBases = this.initProposalBases(currency.decimalPlaces);
22275
- const firstPosition = currency.position;
22276
- const secondPosition = currency.position === "before" ? "after" : "before";
22546
+ const position = currency.position;
22547
+ const opposite = currency.position === "before" ? "after" : "before";
22277
22548
  const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : "";
22278
22549
  const code = this.state.currencyCode.trim() ? this.state.currencyCode : "";
22279
- return code || symbol
22280
- ? [
22281
- ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),
22282
- ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),
22283
- ]
22284
- : [];
22550
+ const decimalPlaces = currency.decimalPlaces;
22551
+ if (!symbol && !code) {
22552
+ return [];
22553
+ }
22554
+ const simple = symbol ? createCurrencyFormat({ symbol, position, decimalPlaces }) : "";
22555
+ const rounded = simple ? roundFormat(simple) : "";
22556
+ const simpleWithCode = createCurrencyFormat({ symbol, position, decimalPlaces, code });
22557
+ const roundedWithCode = roundFormat(simpleWithCode);
22558
+ const simpleOpposite = symbol
22559
+ ? createCurrencyFormat({ symbol, position: opposite, decimalPlaces })
22560
+ : "";
22561
+ const roundedOpposite = simpleOpposite ? roundFormat(simpleOpposite) : "";
22562
+ const simpleOppositeWithCode = createCurrencyFormat({
22563
+ symbol,
22564
+ position: opposite,
22565
+ decimalPlaces,
22566
+ code,
22567
+ });
22568
+ const roundedOppositeWithCode = roundFormat(simpleOppositeWithCode);
22569
+ const formats = new Set([
22570
+ rounded,
22571
+ simple,
22572
+ roundedWithCode,
22573
+ simpleWithCode,
22574
+ roundedOpposite,
22575
+ simpleOpposite,
22576
+ roundedOppositeWithCode,
22577
+ simpleOppositeWithCode,
22578
+ ]);
22579
+ return [...formats]
22580
+ .filter((format) => format !== "")
22581
+ .map((format) => ({
22582
+ format,
22583
+ example: formatValue(1000.0, { format, locale: this.env.model.getters.getLocale() }),
22584
+ }));
22285
22585
  }
22286
22586
  get isSameFormat() {
22287
22587
  const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];
@@ -22338,41 +22638,6 @@ class CustomCurrencyPanel extends owl.Component {
22338
22638
  initAvailableCurrencies() {
22339
22639
  this.state.selectedCurrencyIndex = 0;
22340
22640
  }
22341
- initProposalBases(decimalPlaces) {
22342
- const result = [{ format: "#,##0", example: "1,000" }];
22343
- const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
22344
- if (decimalRepresentation) {
22345
- result.push({
22346
- format: "#,##0" + decimalRepresentation,
22347
- example: "1,000" + decimalRepresentation,
22348
- });
22349
- }
22350
- return result;
22351
- }
22352
- createFormatProposals(proposalBases, symbol, code, position) {
22353
- let formatProposals = [];
22354
- // 1 - add proposal with symbol and without code
22355
- if (symbol) {
22356
- for (let base of proposalBases) {
22357
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));
22358
- }
22359
- }
22360
- // 2 - if code exist --> add more proposal with symbol and with code
22361
- if (code) {
22362
- for (let base of proposalBases) {
22363
- const expression = (position === "after" ? " " : "") + code + " " + symbol;
22364
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));
22365
- }
22366
- }
22367
- return formatProposals;
22368
- }
22369
- createFormatProposal(position, baseExample, formatBase, expression) {
22370
- const formatExpression = "[$" + expression + "]";
22371
- return {
22372
- example: position === "before" ? expression + baseExample : baseExample + expression,
22373
- format: position === "before" ? formatExpression + formatBase : formatBase + formatExpression,
22374
- };
22375
- }
22376
22641
  getCommonFormat() {
22377
22642
  const selectedZones = this.env.model.getters.getSelectedZones();
22378
22643
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -23175,7 +23440,7 @@ class ContentEditableHelper {
23175
23440
  selection.removeAllRanges();
23176
23441
  selection.addRange(range);
23177
23442
  }
23178
- if (start == end && start === 0) {
23443
+ if (start === end && start === 0) {
23179
23444
  range.setStart(this.el, 0);
23180
23445
  range.setEnd(this.el, 0);
23181
23446
  }
@@ -23428,7 +23693,7 @@ class ContentEditableHelper {
23428
23693
  }, 0);
23429
23694
  }
23430
23695
  }
23431
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
23696
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
23432
23697
  usedCharacters++;
23433
23698
  }
23434
23699
  return usedCharacters;
@@ -23537,7 +23802,7 @@ FunctionDescriptionProvider.props = {
23537
23802
  argToFocus: Number,
23538
23803
  };
23539
23804
 
23540
- const functions$1 = functionRegistry.content;
23805
+ const functions = functionRegistry.content;
23541
23806
  const ASSISTANT_WIDTH = 300;
23542
23807
  const selectionIndicatorClass = "selector-flag";
23543
23808
  const selectionIndicatorColor = "#a9a9a9";
@@ -23571,10 +23836,6 @@ css /* scss */ `
23571
23836
  padding-right: 3px;
23572
23837
  outline: none;
23573
23838
 
23574
- &.unfocusable {
23575
- pointer-events: none;
23576
- }
23577
-
23578
23839
  p {
23579
23840
  margin-bottom: 0px;
23580
23841
 
@@ -23822,7 +24083,10 @@ class Composer extends owl.Component {
23822
24083
  }
23823
24084
  }
23824
24085
  showAutocomplete(searchTerm) {
23825
- if (!this.env.model.getters.getCurrentContent().startsWith("=")) {
24086
+ const searchTermUpperCase = searchTerm.toUpperCase();
24087
+ if (!this.env.model.getters.getCurrentContent().startsWith("=") ||
24088
+ searchTermUpperCase === "TRUE" ||
24089
+ searchTermUpperCase === "FALSE") {
23826
24090
  return;
23827
24091
  }
23828
24092
  this.autoCompleteState.showProvider = true;
@@ -23940,7 +24204,6 @@ class Composer extends owl.Component {
23940
24204
  switch (token.type) {
23941
24205
  case "OPERATOR":
23942
24206
  case "NUMBER":
23943
- case "FUNCTION":
23944
24207
  case "ARG_SEPARATOR":
23945
24208
  case "STRING":
23946
24209
  result.push({ value: token.value, color: tokenColors[token.type] || "#000" });
@@ -23950,10 +24213,14 @@ class Composer extends owl.Component {
23950
24213
  result.push({ value: token.value, color: this.rangeColor(xc, sheetName) || "#000" });
23951
24214
  break;
23952
24215
  case "SYMBOL":
23953
- let value = token.value;
23954
- if (["TRUE", "FALSE"].includes(value.toUpperCase())) {
24216
+ const value = token.value;
24217
+ const upperCaseValue = value.toUpperCase();
24218
+ if (upperCaseValue === "TRUE" || upperCaseValue === "FALSE") {
23955
24219
  result.push({ value: token.value, color: tokenColors.NUMBER });
23956
24220
  }
24221
+ else if (upperCaseValue in functionRegistry.content) {
24222
+ result.push({ value: token.value, color: tokenColors.FUNCTION });
24223
+ }
23957
24224
  else {
23958
24225
  result.push({ value: token.value, color: "#000" });
23959
24226
  }
@@ -24049,25 +24316,28 @@ class Composer extends owl.Component {
24049
24316
  this.autoCompleteState.showProvider = false;
24050
24317
  this.functionDescriptionState.showDescription = false;
24051
24318
  if (content.startsWith("=")) {
24052
- const tokenAtCursor = this.env.model.getters.getTokenAtCursor();
24053
- if (tokenAtCursor) {
24054
- const { xc } = splitReference(tokenAtCursor.value);
24055
- if (tokenAtCursor.type === "FUNCTION" ||
24056
- (tokenAtCursor.type === "SYMBOL" && !rangeReference.test(xc))) {
24057
- // initialize Autocomplete Dropdown
24058
- this.showAutocomplete(tokenAtCursor.value);
24059
- }
24060
- else if (tokenAtCursor.functionContext && tokenAtCursor.type !== "UNKNOWN") {
24061
- // initialize Formula Assistant
24062
- const tokenContext = tokenAtCursor.functionContext;
24063
- const parentFunction = tokenContext.parent.toUpperCase();
24064
- const description = functions$1[parentFunction];
24065
- const argPosition = tokenContext.argPosition;
24066
- this.functionDescriptionState.functionName = parentFunction;
24067
- this.functionDescriptionState.functionDescription = description;
24068
- this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24069
- this.functionDescriptionState.showDescription = true;
24070
- }
24319
+ const token = this.env.model.getters.getTokenAtCursor();
24320
+ if (!token) {
24321
+ return;
24322
+ }
24323
+ if (token.type === "SYMBOL") {
24324
+ // initialize Autocomplete Dropdown
24325
+ this.showAutocomplete(token.value);
24326
+ return;
24327
+ }
24328
+ const tokenContext = token.functionContext;
24329
+ const parentFunction = tokenContext?.parent.toUpperCase();
24330
+ if (tokenContext &&
24331
+ parentFunction &&
24332
+ parentFunction in functions &&
24333
+ token.type !== "UNKNOWN") {
24334
+ // initialize Formula Assistant
24335
+ const description = functions[parentFunction];
24336
+ const argPosition = tokenContext.argPosition;
24337
+ this.functionDescriptionState.functionName = parentFunction;
24338
+ this.functionDescriptionState.functionDescription = description;
24339
+ this.functionDescriptionState.argToFocus = description.getArgToFocus(argPosition + 1) - 1;
24340
+ this.functionDescriptionState.showDescription = true;
24071
24341
  }
24072
24342
  }
24073
24343
  }
@@ -24970,6 +25240,14 @@ function useInterval(callback, delay) {
24970
25240
  };
24971
25241
  }
24972
25242
 
25243
+ const CURSOR_SVG = /*xml*/ `
25244
+ <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>
25245
+ `;
25246
+ css /* scss */ `
25247
+ .o-paint-format-cursor {
25248
+ cursor: url("data:image/svg+xml,${encodeURIComponent(CURSOR_SVG)}"), auto;
25249
+ }
25250
+ `;
24973
25251
  function useCellHovered(env, gridRef, callback) {
24974
25252
  let hoveredPosition = {
24975
25253
  col: undefined,
@@ -25093,6 +25371,12 @@ class GridOverlay extends owl.Component {
25093
25371
  }
25094
25372
  return this.gridOverlay.el;
25095
25373
  }
25374
+ get style() {
25375
+ return this.props.gridOverlayDimensions;
25376
+ }
25377
+ get isPaintingFormat() {
25378
+ return this.env.model.getters.isPaintingFormat();
25379
+ }
25096
25380
  onMouseDown(ev) {
25097
25381
  if (ev.button > 0) {
25098
25382
  // not main button, probably a context menu
@@ -26270,6 +26554,9 @@ class Grid extends owl.Component {
26270
26554
  else if (this.menuState.isOpen) {
26271
26555
  this.closeMenu();
26272
26556
  }
26557
+ else if (this.env.model.getters.isPaintingFormat()) {
26558
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
26559
+ }
26273
26560
  else {
26274
26561
  this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
26275
26562
  }
@@ -26767,7 +27054,12 @@ const XLSX_RELATION_TYPE = {
26767
27054
  };
26768
27055
  const RELATIONSHIP_NSR = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
26769
27056
  const HEIGHT_FACTOR = 0.75; // 100px => 75 u
26770
- const WIDTH_FACTOR = 0.1317; // 100px => 13.17 u
27057
+ /**
27058
+ * Excel says its default column width is 8.43 characters (64px)
27059
+ * which makes WIDTH_FACTOR = 0.1317, but it doesn't work well
27060
+ * 0.143 is a value from dev's experiments.
27061
+ */
27062
+ const WIDTH_FACTOR = 0.143;
26771
27063
  /** unit : maximum number of characters a column can hold at the standard font size. What. */
26772
27064
  const EXCEL_DEFAULT_COL_WIDTH = 8.43;
26773
27065
  /** unit : points */
@@ -27284,7 +27576,7 @@ const XLSX_INDEXED_COLORS = {
27284
27576
  64: "000000",
27285
27577
  65: "FFFFFF", // system background
27286
27578
  };
27287
- const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
27579
+ const IMAGE_MIMETYPE_TO_EXTENSION_MAPPING = {
27288
27580
  "image/avif": "avif",
27289
27581
  "image/bmp": "bmp",
27290
27582
  "image/gif": "gif",
@@ -27293,6 +27585,17 @@ const IMAGE_MIMETYPE_EXTENSION_MAPPING = {
27293
27585
  "image/png": "png",
27294
27586
  "image/tiff": "tiff",
27295
27587
  "image/webp": "webp",
27588
+ };
27589
+ const IMAGE_EXTENSION_TO_MIMETYPE_MAPPING = {
27590
+ avif: "image/avif",
27591
+ bmp: "image/bmp",
27592
+ gif: "image/gif",
27593
+ ico: "image/vnd.microsoft.icon",
27594
+ jpeg: "image/jpeg",
27595
+ png: "image/png",
27596
+ tiff: "image/tiff",
27597
+ webp: "image/webp",
27598
+ jpg: "image/jpeg",
27296
27599
  };
27297
27600
 
27298
27601
  /**
@@ -27456,8 +27759,8 @@ function convertXlsxFormat(numFmtId, formats, warningManager) {
27456
27759
  throw new Error("Multiple escaped blocks in format");
27457
27760
  }
27458
27761
  convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
27459
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
27460
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
27762
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ === ignore width of next char for align purposes. Not supported ATM
27763
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * === repeat next character enough to fill the line. Not supported ATM
27461
27764
  convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
27462
27765
  convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
27463
27766
  if (isXlsxDateFormat(convertedFormat)) {
@@ -28096,20 +28399,35 @@ function convertFigure(figure, id, sheetData) {
28096
28399
  convertEMUToDotValue(figure.anchors[0].rowOffset);
28097
28400
  const y2 = getRowPosition(figure.anchors[1].row, sheetData) +
28098
28401
  convertEMUToDotValue(figure.anchors[1].rowOffset);
28099
- const width = x2 - x1;
28100
- const height = y2 - y1;
28101
- const chartData = convertChartData(figure.data);
28102
- if (!chartData)
28103
- return undefined;
28104
- return {
28105
- id: id,
28106
- x: x1,
28107
- y: y1,
28108
- width: width,
28109
- height: height,
28110
- tag: "chart",
28111
- data: convertChartData(figure.data),
28112
- };
28402
+ const figureData = { id, x: x1, y: y1 };
28403
+ if (isChartData(figure.data)) {
28404
+ return {
28405
+ ...figureData,
28406
+ width: x2 - x1,
28407
+ height: y2 - y1,
28408
+ tag: "chart",
28409
+ data: convertChartData(figure.data),
28410
+ };
28411
+ }
28412
+ else if (isImageData(figure.data)) {
28413
+ return {
28414
+ ...figureData,
28415
+ width: convertEMUToDotValue(figure.data.size.cx),
28416
+ height: convertEMUToDotValue(figure.data.size.cy),
28417
+ tag: "image",
28418
+ data: {
28419
+ path: figure.data.imageSrc,
28420
+ mimetype: figure.data.mimetype,
28421
+ },
28422
+ };
28423
+ }
28424
+ return undefined;
28425
+ }
28426
+ function isChartData(data) {
28427
+ return "dataSets" in data;
28428
+ }
28429
+ function isImageData(data) {
28430
+ return "imageSrc" in data;
28113
28431
  }
28114
28432
  function convertChartData(chartData) {
28115
28433
  const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
@@ -28734,29 +29052,26 @@ function escapeXml(strings, ...expressions) {
28734
29052
  return new XMLString(concat(str));
28735
29053
  }
28736
29054
  /**
28737
- * Removes the namespace of all the xml tags in the string.
29055
+ * Removes the escaped namespace of all the xml tags in the string.
28738
29056
  *
28739
- * Eg. : "ns:test a" => "test a"
29057
+ * Eg. : "NAMESPACEnsNAMESPACEtest a" => "test a"
28740
29058
  */
28741
- function removeNamespaces(query) {
28742
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
29059
+ function removeTagEscapedNamespaces(tag) {
29060
+ return tag.replace(/NAMESPACE.*NAMESPACE(.*)/, "$1");
28743
29061
  }
28744
29062
  /**
28745
- * Escape the namespace's colons of all the xml tags in the string.
29063
+ * Encase the namespaces in the element's tags with NAMESPACE string
28746
29064
  *
28747
- * Eg. : "ns:test a" => "ns\\:test a"
28748
- */
28749
- function escapeNamespaces(query) {
28750
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
28751
- }
28752
- /**
28753
- * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.
29065
+ * e.g. <x:foo> becomes <NAMESPACExNAMESPACEFoo>
28754
29066
  *
28755
- * Should return true if it's running on a browser, and false if it's running on jest (jsdom).
29067
+ * That's useful because namespaces aren't supported by the HTML specification, so it's arbitrary whether a HTML parser/querySelector
29068
+ * implementation will support namespaces in the tags or not.
28756
29069
  */
28757
- function areNamespaceIgnoredByQuerySelector() {
28758
- const doc = new DOMParser().parseFromString("<t:test xmlns:t='a'/>", "text/xml");
28759
- return doc.querySelector("test") !== null;
29070
+ function escapeTagNamespaces(str) {
29071
+ return str.replaceAll(/(<\/?)([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "$1" + "NAMESPACE" + "$2" + "NAMESPACE" + "$3");
29072
+ }
29073
+ function escapeQueryNameSpaces(query) {
29074
+ return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
28760
29075
  }
28761
29076
 
28762
29077
  class AttributeValue {
@@ -28786,14 +29101,20 @@ class XlsxBaseExtractor {
28786
29101
  // The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but
28787
29102
  // the XLSXImportFile contains both the main .xml file, and the .rels file
28788
29103
  currentFile = undefined;
28789
- // If the parser querySelector() implementation ignores tag namespaces or not
28790
- areNamespaceIgnored;
29104
+ /**
29105
+ * /!\ Important : There should be no namespaces in the tags of the XML files.
29106
+ *
29107
+ * This class use native querySelector and querySelectorAll, that's used for HTML (not XML). These aren't supposed to
29108
+ * handled namespaces, as they are not supported by the HTML specification. Some implementations (most browsers) do
29109
+ * actually support namespaces, but some don't (e.g. jsdom).
29110
+ *
29111
+ * The namespace should be escaped as with NAMESPACE string (eg. <t:foo> => <NAMESPACEtNAMESPACEfoo>).
29112
+ */
28791
29113
  constructor(rootFile, xlsxStructure, warningManager) {
28792
29114
  this.rootFile = rootFile;
28793
29115
  this.currentFile = rootFile.file.fileName;
28794
29116
  this.xlsxFileStructure = xlsxStructure;
28795
29117
  this.warningManager = warningManager;
28796
- this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();
28797
29118
  this.relationships = {};
28798
29119
  if (rootFile.rels) {
28799
29120
  this.extractRelationships(rootFile.rels).map((rel) => {
@@ -28816,9 +29137,13 @@ class XlsxBaseExtractor {
28816
29137
  /**
28817
29138
  * Get the list of all the XLSX files in the XLSX file structure
28818
29139
  */
28819
- getListOfFiles() {
28820
- const files = Object.values(this.xlsxFileStructure).flat().filter(isDefined$1);
28821
- return files;
29140
+ getListOfXMLFiles() {
29141
+ const XMLFiles = Object.entries(this.xlsxFileStructure)
29142
+ .filter(([key]) => key !== "images")
29143
+ .map(([_, value]) => value)
29144
+ .flat()
29145
+ .filter(isDefined$1);
29146
+ return XMLFiles;
28822
29147
  }
28823
29148
  /**
28824
29149
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -28990,43 +29315,38 @@ class XlsxBaseExtractor {
28990
29315
  return color;
28991
29316
  }
28992
29317
  /**
28993
- * Returns the xlsx file targeted by a relationship.
29318
+ * Returns the xml file targeted by a relationship.
28994
29319
  */
28995
29320
  getTargetXmlFile(relationship) {
28996
29321
  if (!relationship)
28997
29322
  throw new Error("Undefined target file");
28998
- let target = relationship.target;
28999
- target = target.replace("../", "");
29000
- target = target.replace("./", "");
29323
+ const target = this.processRelationshipTargetName(relationship.target);
29001
29324
  // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
29002
- const f = this.getListOfFiles().find((f) => f.file.fileName.endsWith(target));
29325
+ const f = this.getListOfXMLFiles().find((f) => f.file.fileName.endsWith(target));
29003
29326
  if (!f || !f.file)
29004
29327
  throw new Error("Cannot find target file");
29005
29328
  return f;
29006
29329
  }
29007
29330
  /**
29008
- * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29009
- *
29010
- * Why we need to do this :
29011
- * - For an XML "<t:test />"
29012
- * - on Jest(jsdom) : xml.querySelector("test") == null, xml.querySelector("t\\:test") == <t:test />
29013
- * - on Browser : xml.querySelector("test") == <t:test />, xml.querySelector("t\\:test") == null
29331
+ * Returns the image parameters targeted by a relationship.
29014
29332
  */
29333
+ getTargetImageFile(relationship) {
29334
+ if (!relationship)
29335
+ throw new Error("Undefined target file");
29336
+ const target = this.processRelationshipTargetName(relationship.target);
29337
+ // Use "endsWith" because targets are relative paths, and we know the files by their absolute path.
29338
+ const f = this.xlsxFileStructure.images.find((f) => f.fileName.endsWith(target));
29339
+ if (!f)
29340
+ throw new Error("Cannot find target file");
29341
+ return f;
29342
+ }
29015
29343
  querySelector(element, query) {
29016
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29017
- return element.querySelector(query);
29344
+ const escapedQuery = escapeQueryNameSpaces(query);
29345
+ return element.querySelector(escapedQuery);
29018
29346
  }
29019
- /**
29020
- * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29021
- *
29022
- * Why we need to do this :
29023
- * - For an XML "<t:test />"
29024
- * - on Jest(jsdom) : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == [<t:test />]
29025
- * - on Browser : xml.querySelectorAll("test") == [<t:test />], xml.querySelectorAll("t\\:test") == []
29026
- */
29027
29347
  querySelectorAll(element, query) {
29028
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29029
- return element.querySelectorAll(query);
29348
+ const escapedQuery = escapeQueryNameSpaces(query);
29349
+ return element.querySelectorAll(escapedQuery);
29030
29350
  }
29031
29351
  /**
29032
29352
  * Get a color from its id in the Theme's colorScheme.
@@ -29050,6 +29370,10 @@ class XlsxBaseExtractor {
29050
29370
  return clrScheme[colorId].value;
29051
29371
  }
29052
29372
  }
29373
+ /** Remove signs of relative path. */
29374
+ processRelationshipTargetName(targetName) {
29375
+ return targetName.replace(/\.+\//, "");
29376
+ }
29053
29377
  }
29054
29378
 
29055
29379
  /**
@@ -29262,7 +29586,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29262
29586
  throw new Error("Missing plot area in the chart definition.");
29263
29587
  }
29264
29588
  for (let child of plotAreaElement.children) {
29265
- const tag = removeNamespaces(child.tagName);
29589
+ const tag = removeTagEscapedNamespaces(child.tagName);
29266
29590
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
29267
29591
  return tag;
29268
29592
  }
@@ -29274,20 +29598,21 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29274
29598
  class XlsxFigureExtractor extends XlsxBaseExtractor {
29275
29599
  extractFigures() {
29276
29600
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
29277
- const anchorType = removeNamespaces(figureElement.tagName);
29601
+ const anchorType = removeTagEscapedNamespaces(figureElement.tagName);
29278
29602
  if (anchorType !== "twoCellAnchor") {
29279
29603
  throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
29280
29604
  }
29281
29605
  const chartElement = this.querySelector(figureElement, "c:chart");
29282
- if (!chartElement) {
29283
- throw new Error("Only chart figures are currently supported.");
29606
+ const imageElement = this.querySelector(figureElement, "a:blip");
29607
+ if (!chartElement && !imageElement) {
29608
+ throw new Error("Only chart and image figures are currently supported.");
29284
29609
  }
29285
29610
  return {
29286
29611
  anchors: [
29287
29612
  this.extractFigureAnchor("xdr:from", figureElement),
29288
29613
  this.extractFigureAnchor("xdr:to", figureElement),
29289
29614
  ],
29290
- data: this.extractChart(chartElement),
29615
+ data: chartElement ? this.extractChart(chartElement) : this.extractImage(figureElement),
29291
29616
  };
29292
29617
  });
29293
29618
  }
@@ -29312,6 +29637,24 @@ class XlsxFigureExtractor extends XlsxBaseExtractor {
29312
29637
  }
29313
29638
  return chartDefinition;
29314
29639
  }
29640
+ extractImage(figureElement) {
29641
+ const imageElement = this.querySelector(figureElement, "a:blip");
29642
+ const imageId = this.extractAttr(imageElement, "r:embed", { required: true }).asString();
29643
+ const image = this.getTargetImageFile(this.relationships[imageId]);
29644
+ if (!image) {
29645
+ throw new Error("Unable to extract image");
29646
+ }
29647
+ const shapePropertyElement = this.querySelector(figureElement, "a:xfrm");
29648
+ const extension = image.fileName.split(".").at(-1);
29649
+ return {
29650
+ imageSrc: image.imageSrc,
29651
+ mimetype: extension ? IMAGE_EXTENSION_TO_MIMETYPE_MAPPING[extension] : undefined,
29652
+ size: {
29653
+ cx: this.extractChildAttr(shapePropertyElement, "a:ext", "cx", { required: true }).asNum(),
29654
+ cy: this.extractChildAttr(shapePropertyElement, "a:ext", "cy", { required: true }).asNum(),
29655
+ },
29656
+ };
29657
+ }
29315
29658
  }
29316
29659
 
29317
29660
  /**
@@ -29881,13 +30224,22 @@ const EXCEL_IMPORT_VERSION = 12;
29881
30224
  class XlsxReader {
29882
30225
  warningManager;
29883
30226
  xmls;
30227
+ images;
29884
30228
  constructor(files) {
29885
30229
  this.warningManager = new XLSXImportWarningManager();
29886
30230
  this.xmls = {};
30231
+ this.images = [];
29887
30232
  for (let key of Object.keys(files)) {
29888
30233
  // Random files can be in xlsx (like a bin file for printer settings)
29889
30234
  if (key.endsWith(".xml") || key.endsWith(".rels")) {
29890
- this.xmls[key] = parseXML(new XMLString(files[key]));
30235
+ const contentString = escapeTagNamespaces(files[key]);
30236
+ this.xmls[key] = parseXML(new XMLString(contentString));
30237
+ }
30238
+ else if (key.includes("media/image")) {
30239
+ this.images.push({
30240
+ fileName: key,
30241
+ imageSrc: files[key]["imageSrc"],
30242
+ });
29891
30243
  }
29892
30244
  }
29893
30245
  }
@@ -29941,6 +30293,7 @@ class XlsxReader {
29941
30293
  tables: getXLSXFilesOfType(CONTENT_TYPES.table, this.xmls),
29942
30294
  pivots: getXLSXFilesOfType(CONTENT_TYPES.pivot, this.xmls),
29943
30295
  externalLinks: getXLSXFilesOfType(CONTENT_TYPES.externalLink, this.xmls),
30296
+ images: this.images,
29944
30297
  };
29945
30298
  if (!xlsxFileStructure.workbook.rels) {
29946
30299
  throw Error(_lt("Cannot find workbook relations file"));
@@ -30947,7 +31300,7 @@ class BordersPlugin extends CorePlugin {
30947
31300
  }
30948
31301
  /**
30949
31302
  * Set the borders of a cell.
30950
- * It overrides the current border if override == true.
31303
+ * It overrides the current border if override === true.
30951
31304
  */
30952
31305
  setBorder(sheetId, col, row, border, override = true) {
30953
31306
  if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
@@ -32217,7 +32570,9 @@ class FigurePlugin extends CorePlugin {
32217
32570
  const numHeader = this.getters.getNumberRows(sheetId);
32218
32571
  let gridHeight = 0;
32219
32572
  for (let i = 0; i < numHeader; i++) {
32220
- gridHeight += this.getters.getRowSize(sheetId, i);
32573
+ // TODO : since the row size is an UI value now, this doesn't work anymore. Using the default cell height is
32574
+ // a temporary solution at best, but is broken.
32575
+ gridHeight += this.getters.getUserRowSize(sheetId, i) || DEFAULT_CELL_HEIGHT;
32221
32576
  }
32222
32577
  const figures = this.getters.getFigures(sheetId);
32223
32578
  for (const figure of figures) {
@@ -32643,33 +32998,15 @@ class FiltersPlugin extends CorePlugin {
32643
32998
  }
32644
32999
 
32645
33000
  class HeaderSizePlugin extends CorePlugin {
32646
- static getters = ["getRowSize", "getColSize"];
33001
+ static getters = ["getUserRowSize", "getColSize"];
32647
33002
  sizes = {};
32648
33003
  handle(cmd) {
32649
33004
  switch (cmd.type) {
32650
33005
  case "CREATE_SHEET": {
32651
- const computedSizes = this.computeSheetSizes(cmd.sheetId);
32652
- const sizes = {
32653
- COL: computedSizes.COL.map((size) => ({
32654
- manualSize: undefined,
32655
- computedSize: lazy(size),
32656
- })),
32657
- ROW: computedSizes.ROW.map((size) => ({
32658
- manualSize: undefined,
32659
- computedSize: lazy(size),
32660
- })),
32661
- };
32662
- this.history.update("sizes", cmd.sheetId, sizes);
33006
+ this.history.update("sizes", cmd.sheetId, { COL: [], ROW: [] });
32663
33007
  break;
32664
33008
  }
32665
33009
  case "DUPLICATE_SHEET":
32666
- // make sure the values are computed in case the original sheet is deleted
32667
- for (const row of this.sizes[cmd.sheetId].ROW) {
32668
- row.computedSize();
32669
- }
32670
- for (const col of this.sizes[cmd.sheetId].COL) {
32671
- col.computedSize();
32672
- }
32673
33010
  this.history.update("sizes", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));
32674
33011
  break;
32675
33012
  case "DELETE_SHEET":
@@ -32678,21 +33015,8 @@ class HeaderSizePlugin extends CorePlugin {
32678
33015
  this.history.update("sizes", sizes);
32679
33016
  break;
32680
33017
  case "REMOVE_COLUMNS_ROWS": {
32681
- let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
32682
- for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {
32683
- sizes.splice(headerIndex, 1);
32684
- }
32685
- const min = Math.min(...cmd.elements);
32686
- sizes = sizes.map((size, row) => {
32687
- if (cmd.dimension === "ROW" && row >= min) {
32688
- // invalidate sizes
32689
- return {
32690
- manualSize: size.manualSize,
32691
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32692
- };
32693
- }
32694
- return size;
32695
- });
33018
+ const arr = this.sizes[cmd.sheetId][cmd.dimension];
33019
+ const sizes = removeIndexesFromArray(arr, cmd.elements);
32696
33020
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32697
33021
  break;
32698
33022
  }
@@ -32701,51 +33025,18 @@ class HeaderSizePlugin extends CorePlugin {
32701
33025
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
32702
33026
  const baseSize = sizes[cmd.base];
32703
33027
  sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
32704
- sizes = sizes.map((size, row) => {
32705
- if (cmd.dimension === "ROW" && row > cmd.base + cmd.quantity) {
32706
- // invalidate sizes
32707
- return {
32708
- manualSize: size.manualSize,
32709
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32710
- };
32711
- }
32712
- return size;
32713
- });
32714
33028
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32715
33029
  break;
32716
33030
  }
32717
33031
  case "RESIZE_COLUMNS_ROWS":
32718
- for (let el of cmd.elements) {
32719
- if (cmd.dimension === "ROW") {
32720
- const height = this.getRowTallestCellSize(cmd.sheetId, el);
32721
- const size = height;
32722
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32723
- manualSize: cmd.size || undefined,
32724
- computedSize: lazy(size),
32725
- });
33032
+ if (cmd.dimension === "ROW") {
33033
+ for (const el of cmd.elements) {
33034
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32726
33035
  }
32727
- else {
32728
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32729
- manualSize: cmd.size || undefined,
32730
- computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),
32731
- });
32732
- }
32733
- }
32734
- break;
32735
- case "UPDATE_CELL":
32736
- if (!this.sizes[cmd.sheetId]?.["ROW"]?.[cmd.row]?.manualSize) {
32737
- const { sheetId, row } = cmd;
32738
- this.history.update("sizes", sheetId, "ROW", row, "computedSize", lazy(() => this.getRowTallestCellSize(sheetId, row)));
32739
33036
  }
32740
- break;
32741
- case "ADD_MERGE":
32742
- case "REMOVE_MERGE":
32743
- for (let target of cmd.target) {
32744
- for (let row of range(target.top, target.bottom + 1)) {
32745
- const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);
32746
- if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {
32747
- this.history.update("sizes", cmd.sheetId, "ROW", row, "computedSize", lazy(rowHeight));
32748
- }
33037
+ else {
33038
+ for (const el of cmd.elements) {
33039
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32749
33040
  }
32750
33041
  }
32751
33042
  break;
@@ -32753,94 +33044,29 @@ class HeaderSizePlugin extends CorePlugin {
32753
33044
  return;
32754
33045
  }
32755
33046
  getColSize(sheetId, index) {
32756
- return this.getHeaderSize(sheetId, "COL", index);
32757
- }
32758
- getRowSize(sheetId, index) {
32759
- return this.getHeaderSize(sheetId, "ROW", index);
33047
+ return Math.round(this.sizes[sheetId]?.["COL"][index] || DEFAULT_CELL_WIDTH);
32760
33048
  }
32761
- getHeaderSize(sheetId, dimension, index) {
32762
- return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
32763
- this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
32764
- this.getDefaultHeaderSize(dimension));
32765
- }
32766
- computeSheetSizes(sheetId) {
32767
- const sizes = { COL: [], ROW: [] };
32768
- for (let col of range(0, this.getters.getNumberCols(sheetId))) {
32769
- sizes.COL.push(this.getHeaderSize(sheetId, "COL", col));
32770
- }
32771
- for (let row of range(0, this.getters.getNumberRows(sheetId))) {
32772
- let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
32773
- if (!rowSize) {
32774
- const height = this.getRowTallestCellSize(sheetId, row);
32775
- rowSize = height;
32776
- }
32777
- sizes.ROW.push(rowSize);
32778
- }
32779
- return sizes;
32780
- }
32781
- getDefaultHeaderSize(dimension) {
32782
- return dimension === "COL" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;
32783
- }
32784
- /**
32785
- * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
32786
- * merge, or the height of the cell computed based on its font size.
32787
- */
32788
- getCellHeight(position) {
32789
- const merge = this.getters.getMerge(position);
32790
- if (merge && merge.bottom !== merge.top) {
32791
- return DEFAULT_CELL_HEIGHT;
32792
- }
32793
- const cell = this.getters.getCell(position);
32794
- return getDefaultCellHeight(cell);
32795
- }
32796
- /**
32797
- * Get the tallest cell of a row and its size.
32798
- *
32799
- * The tallest cell of the row correspond to the cell with the biggest font size,
32800
- * and that is not part of a multi-line merge.
32801
- */
32802
- getRowTallestCellSize(sheetId, row) {
32803
- const cellIds = this.getters.getRowCells(sheetId, row);
32804
- let maxHeight = 0;
32805
- for (let i = 0; i < cellIds.length; i++) {
32806
- const cell = this.getters.getCellById(cellIds[i]);
32807
- if (!cell)
32808
- continue;
32809
- const position = this.getters.getCellPosition(cell.id);
32810
- const cellHeight = this.getCellHeight(position);
32811
- if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
32812
- maxHeight = cellHeight;
32813
- }
32814
- }
32815
- if (maxHeight <= DEFAULT_CELL_HEIGHT) {
32816
- return DEFAULT_CELL_HEIGHT;
32817
- }
32818
- return maxHeight;
33049
+ getUserRowSize(sheetId, index) {
33050
+ const rowSize = this.sizes[sheetId]?.["ROW"][index];
33051
+ return rowSize ? Math.round(rowSize) : undefined;
32819
33052
  }
32820
33053
  import(data) {
32821
33054
  for (let sheet of data.sheets) {
32822
- const manualSizes = { COL: [], ROW: [] };
33055
+ const sizes = {
33056
+ COL: Array(sheet.colNumber).fill(undefined),
33057
+ ROW: Array(sheet.rowNumber).fill(undefined),
33058
+ };
32823
33059
  for (let [rowIndex, row] of Object.entries(sheet.rows)) {
32824
33060
  if (row.size) {
32825
- manualSizes["ROW"][rowIndex] = row.size;
33061
+ sizes["ROW"][rowIndex] = row.size;
32826
33062
  }
32827
33063
  }
32828
33064
  for (let [colIndex, col] of Object.entries(sheet.cols)) {
32829
33065
  if (col.size) {
32830
- manualSizes["COL"][colIndex] = col.size;
32831
- }
32832
- }
32833
- const computedSizes = this.computeSheetSizes(sheet.id);
32834
- this.sizes[sheet.id] = {
32835
- COL: computedSizes.COL.map((size, i) => ({
32836
- manualSize: manualSizes.COL[i],
32837
- computedSize: lazy(size),
32838
- })),
32839
- ROW: computedSizes.ROW.map((size, i) => ({
32840
- manualSize: manualSizes.ROW[i],
32841
- computedSize: lazy(size),
32842
- })),
32843
- };
33066
+ sizes["COL"][colIndex] = col.size;
33067
+ }
33068
+ }
33069
+ this.sizes[sheet.id] = sizes;
32844
33070
  }
32845
33071
  return;
32846
33072
  }
@@ -32861,9 +33087,12 @@ class HeaderSizePlugin extends CorePlugin {
32861
33087
  if (sheet.rows === undefined) {
32862
33088
  sheet.rows = {};
32863
33089
  }
32864
- for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
32865
- if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
32866
- sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
33090
+ for (const row of range(0, this.getters.getNumberRows(sheet.id))) {
33091
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]) {
33092
+ sheet.rows[row] = {
33093
+ ...sheet.rows[row],
33094
+ size: this.getUserRowSize(sheet.id, row) ?? DEFAULT_CELL_HEIGHT,
33095
+ };
32867
33096
  }
32868
33097
  }
32869
33098
  // Export col sizes
@@ -32871,7 +33100,7 @@ class HeaderSizePlugin extends CorePlugin {
32871
33100
  sheet.cols = {};
32872
33101
  }
32873
33102
  for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
32874
- if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
33103
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]) {
32875
33104
  sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
32876
33105
  }
32877
33106
  }
@@ -33091,7 +33320,7 @@ class ImagePlugin extends CorePlugin {
33091
33320
  handle(cmd) {
33092
33321
  switch (cmd.type) {
33093
33322
  case "CREATE_IMAGE":
33094
- this.addFigure(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33323
+ this.addImage(cmd.figureId, cmd.sheetId, cmd.position, cmd.size);
33095
33324
  this.history.update("images", cmd.sheetId, cmd.figureId, cmd.definition);
33096
33325
  this.syncedImages.add(cmd.definition.path);
33097
33326
  break;
@@ -33155,7 +33384,7 @@ class ImagePlugin extends CorePlugin {
33155
33384
  // ---------------------------------------------------------------------------
33156
33385
  // Private
33157
33386
  // ---------------------------------------------------------------------------
33158
- addFigure(id, sheetId, position, size) {
33387
+ addImage(id, sheetId, position, size) {
33159
33388
  const figure = {
33160
33389
  id,
33161
33390
  x: position.x,
@@ -35662,7 +35891,6 @@ class PositionBitsEncoder {
35662
35891
  }
35663
35892
  }
35664
35893
 
35665
- const functions = functionRegistry.content;
35666
35894
  //#region
35667
35895
  // ---------------------------------------------------------------------------
35668
35896
  // INTRODUCTION
@@ -35884,9 +36112,7 @@ class EvaluationPlugin extends UIPlugin {
35884
36112
  let isExported = true;
35885
36113
  const formulaCell = this.getCorrespondingFormulaCell(position);
35886
36114
  if (formulaCell) {
35887
- isExported = formulaCell.compiledFormula.tokens
35888
- .filter((tk) => tk.type === "FUNCTION")
35889
- .every((tk) => functions[tk.value.toUpperCase()].isExported);
36115
+ isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
35890
36116
  isFormula = isExported;
35891
36117
  if (!isExported) {
35892
36118
  newContent = value.toString();
@@ -36430,7 +36656,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36430
36656
  case "ContainsText":
36431
36657
  return cell.value.toString().indexOf(values[0].toString()) > -1;
36432
36658
  case "NotContains":
36433
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
36659
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) === -1;
36434
36660
  case "GreaterThan":
36435
36661
  return cell.value > values[0];
36436
36662
  case "GreaterThanOrEqual":
@@ -36457,6 +36683,156 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36457
36683
  };
36458
36684
  }
36459
36685
 
36686
+ class UIRowSizePlugin extends UIPlugin {
36687
+ static getters = ["getRowSize"];
36688
+ tallestCellInRow = {};
36689
+ ctx = document.createElement("canvas").getContext("2d");
36690
+ handle(cmd) {
36691
+ switch (cmd.type) {
36692
+ case "START":
36693
+ for (const sheetId of this.getters.getSheetIds()) {
36694
+ this.initializeSheet(sheetId);
36695
+ }
36696
+ break;
36697
+ case "CREATE_SHEET": {
36698
+ this.initializeSheet(cmd.sheetId);
36699
+ break;
36700
+ }
36701
+ case "DUPLICATE_SHEET": {
36702
+ const tallestCells = deepCopy(this.tallestCellInRow[cmd.sheetId]);
36703
+ this.history.update("tallestCellInRow", cmd.sheetIdTo, tallestCells);
36704
+ break;
36705
+ }
36706
+ case "DELETE_SHEET":
36707
+ const tallestCells = { ...this.tallestCellInRow };
36708
+ delete tallestCells[cmd.sheetId];
36709
+ this.history.update("tallestCellInRow", tallestCells);
36710
+ break;
36711
+ case "REMOVE_COLUMNS_ROWS": {
36712
+ if (cmd.dimension === "COL") {
36713
+ return;
36714
+ }
36715
+ const tallestCells = removeIndexesFromArray(this.tallestCellInRow[cmd.sheetId], cmd.elements);
36716
+ this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
36717
+ break;
36718
+ }
36719
+ case "ADD_COLUMNS_ROWS": {
36720
+ if (cmd.dimension === "COL") {
36721
+ return;
36722
+ }
36723
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
36724
+ const newCells = Array(cmd.quantity).fill(undefined);
36725
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
36726
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
36727
+ break;
36728
+ }
36729
+ case "RESIZE_COLUMNS_ROWS":
36730
+ {
36731
+ const sheetId = cmd.sheetId;
36732
+ if (cmd.dimension === "ROW") {
36733
+ for (const row of cmd.elements) {
36734
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36735
+ this.history.update("tallestCellInRow", sheetId, row, tallestCell);
36736
+ }
36737
+ }
36738
+ else {
36739
+ // Recompute row heights on col size change, they might have changed because of wrapped text
36740
+ for (const row of range(0, this.getters.getNumberRows(sheetId))) {
36741
+ for (const col of cmd.elements) {
36742
+ this.updateRowSizeForCellChange(sheetId, row, col);
36743
+ }
36744
+ }
36745
+ }
36746
+ }
36747
+ break;
36748
+ case "UPDATE_CELL":
36749
+ this.updateRowSizeForCellChange(cmd.sheetId, cmd.row, cmd.col);
36750
+ break;
36751
+ case "ADD_MERGE":
36752
+ case "REMOVE_MERGE":
36753
+ for (const target of cmd.target) {
36754
+ for (const position of positions(target)) {
36755
+ this.updateRowSizeForCellChange(cmd.sheetId, position.row, position.col);
36756
+ }
36757
+ }
36758
+ }
36759
+ return;
36760
+ }
36761
+ getRowSize(sheetId, row) {
36762
+ return Math.round(this.getters.getUserRowSize(sheetId, row) ??
36763
+ this.tallestCellInRow[sheetId][row]?.size ??
36764
+ DEFAULT_CELL_HEIGHT);
36765
+ }
36766
+ updateRowSizeForCellChange(sheetId, row, col) {
36767
+ const tallestCellInRow = this.tallestCellInRow[sheetId]?.[row];
36768
+ if (tallestCellInRow?.cell.col === col) {
36769
+ const newTallestCell = this.getRowTallestCell(sheetId, row);
36770
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36771
+ }
36772
+ const updatedCellHeight = this.getCellHeight({ sheetId, col, row });
36773
+ if (updatedCellHeight <= DEFAULT_CELL_HEIGHT) {
36774
+ return;
36775
+ }
36776
+ if ((!tallestCellInRow && updatedCellHeight > DEFAULT_CELL_HEIGHT) ||
36777
+ (tallestCellInRow && updatedCellHeight > tallestCellInRow.size)) {
36778
+ const newTallestCell = { cell: { sheetId, col, row }, size: updatedCellHeight };
36779
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36780
+ }
36781
+ }
36782
+ initializeSheet(sheetId) {
36783
+ const tallestCells = [];
36784
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
36785
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36786
+ tallestCells.push(tallestCell);
36787
+ }
36788
+ this.history.update("tallestCellInRow", sheetId, tallestCells);
36789
+ }
36790
+ /**
36791
+ * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
36792
+ * merge, or the height of the cell computed based on its style/content.
36793
+ */
36794
+ getCellHeight(position) {
36795
+ if (this.isInMultiRowMerge(position)) {
36796
+ return DEFAULT_CELL_HEIGHT;
36797
+ }
36798
+ const cell = this.getters.getCell(position);
36799
+ const colSize = this.getters.getColSize(position.sheetId, position.col);
36800
+ return getDefaultCellHeight(this.ctx, cell, colSize);
36801
+ }
36802
+ isInMultiRowMerge(position) {
36803
+ const merge = this.getters.getMerge(position);
36804
+ return !!merge && merge.bottom !== merge.top;
36805
+ }
36806
+ /**
36807
+ * Get the tallest cell of a row and its size.
36808
+ */
36809
+ getRowTallestCell(sheetId, row) {
36810
+ const userRowSize = this.getters.getUserRowSize(sheetId, row);
36811
+ if (userRowSize !== undefined) {
36812
+ return undefined;
36813
+ }
36814
+ const cellIds = this.getters.getRowCells(sheetId, row);
36815
+ let maxHeight = 0;
36816
+ let tallestCell = undefined;
36817
+ for (let i = 0; i < cellIds.length; i++) {
36818
+ const cell = this.getters.getCellById(cellIds[i]);
36819
+ if (!cell) {
36820
+ continue;
36821
+ }
36822
+ const position = this.getters.getCellPosition(cell.id);
36823
+ const cellHeight = this.getCellHeight(position);
36824
+ if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
36825
+ maxHeight = cellHeight;
36826
+ tallestCell = { cell: position, size: cellHeight };
36827
+ }
36828
+ }
36829
+ if (tallestCell && tallestCell.size > DEFAULT_CELL_HEIGHT) {
36830
+ return tallestCell;
36831
+ }
36832
+ return undefined;
36833
+ }
36834
+ }
36835
+
36460
36836
  /**
36461
36837
  * This plugin manage the autofill.
36462
36838
  *
@@ -39890,10 +40266,8 @@ class SortPlugin extends UIPlugin {
39890
40266
  }
39891
40267
  updateCellCommands.push(newCellValues);
39892
40268
  }
39893
- for (const cmd of updateCellCommands) {
39894
- this.dispatch("UPDATE_CELL", cmd);
39895
- }
39896
40269
  }
40270
+ updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
39897
40271
  }
39898
40272
  /**
39899
40273
  * Return the distances between main merge cells in the zone.
@@ -39994,24 +40368,28 @@ class SheetUIPlugin extends UIPlugin {
39994
40368
  // Getters
39995
40369
  // ---------------------------------------------------------------------------
39996
40370
  getCellWidth(position) {
39997
- const text = this.getCellText(position);
39998
40371
  const style = this.getters.getCellComputedStyle(position);
39999
- const multiLineText = text.split(NEWLINE);
40000
- let contentWidth = Math.max(...multiLineText.map((line) => this.getTextWidth(line, style)));
40372
+ let contentWidth = 0;
40373
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
40374
+ if (content) {
40375
+ const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
40376
+ contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
40377
+ }
40001
40378
  const icon = this.getters.getConditionalIcon(position);
40002
40379
  if (icon) {
40003
- contentWidth += computeIconWidth(this.getters.getCellStyle(position));
40380
+ contentWidth += computeIconWidth(style);
40004
40381
  }
40005
40382
  const isFilterHeader = this.getters.isFilterHeader(position);
40006
40383
  if (isFilterHeader) {
40007
40384
  contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;
40008
40385
  }
40009
- if (contentWidth > 0) {
40010
- contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40011
- if (this.getters.getCellStyle(position).wrapping === "wrap") {
40012
- const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40013
- return Math.min(colWidth, contentWidth);
40014
- }
40386
+ if (contentWidth === 0) {
40387
+ return 0;
40388
+ }
40389
+ contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40390
+ if (style.wrapping === "wrap") {
40391
+ const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40392
+ return Math.min(colWidth, contentWidth);
40015
40393
  }
40016
40394
  return contentWidth;
40017
40395
  }
@@ -40034,56 +40412,7 @@ class SheetUIPlugin extends UIPlugin {
40034
40412
  getCellMultiLineText(position, width) {
40035
40413
  const style = this.getters.getCellStyle(position);
40036
40414
  const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());
40037
- const brokenText = [];
40038
- for (const line of text.split("\n")) {
40039
- const words = line.split(" ");
40040
- if (!width) {
40041
- brokenText.push(line);
40042
- continue;
40043
- }
40044
- let textLine = "";
40045
- let availableWidth = width;
40046
- for (let word of words) {
40047
- const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);
40048
- const lastPart = splitWord.pop();
40049
- const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);
40050
- // At this step: "splitWord" is an array composed of parts of word whose
40051
- // length is at most equal to "width".
40052
- // Last part contains the end of the word.
40053
- // Note that: When word length is less than width, then lastPart is equal
40054
- // to word and splitWord is empty
40055
- if (splitWord.length) {
40056
- if (textLine !== "") {
40057
- brokenText.push(textLine);
40058
- textLine = "";
40059
- availableWidth = width;
40060
- }
40061
- splitWord.forEach((wordPart) => {
40062
- brokenText.push(wordPart);
40063
- });
40064
- textLine = lastPart;
40065
- availableWidth = width - lastPartWidth;
40066
- }
40067
- else {
40068
- // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
40069
- const _word = textLine === "" ? lastPart : " " + lastPart;
40070
- const wordWidth = computeTextWidth(this.ctx, _word, style);
40071
- if (wordWidth <= availableWidth) {
40072
- textLine += _word;
40073
- availableWidth -= wordWidth;
40074
- }
40075
- else {
40076
- brokenText.push(textLine);
40077
- textLine = lastPart;
40078
- availableWidth = width - lastPartWidth;
40079
- }
40080
- }
40081
- }
40082
- if (textLine !== "") {
40083
- brokenText.push(textLine);
40084
- }
40085
- }
40086
- return brokenText;
40415
+ return splitTextToWidth(this.ctx, text, style, width);
40087
40416
  }
40088
40417
  /**
40089
40418
  * Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -40142,26 +40471,6 @@ class SheetUIPlugin extends UIPlugin {
40142
40471
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
40143
40472
  return Math.max(0, ...sizes);
40144
40473
  }
40145
- splitWordToSpecificWidth(ctx, word, width, style) {
40146
- const wordWidth = computeTextWidth(ctx, word, style);
40147
- if (wordWidth <= width) {
40148
- return [word];
40149
- }
40150
- const splitWord = [];
40151
- let wordPart = "";
40152
- for (let l of word) {
40153
- const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
40154
- if (wordPartWidth > width) {
40155
- splitWord.push(wordPart);
40156
- wordPart = l;
40157
- }
40158
- else {
40159
- wordPart += l;
40160
- }
40161
- }
40162
- splitWord.push(wordPart);
40163
- return splitWord;
40164
- }
40165
40474
  /**
40166
40475
  * Check that any "sheetId" in the command matches an existing
40167
40476
  * sheet.
@@ -41153,7 +41462,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
41153
41462
  .join("\n") || "\t");
41154
41463
  }
41155
41464
  getHTMLContent() {
41156
- if (this.cells.length == 1 && this.cells[0].length == 1) {
41465
+ if (this.cells.length === 1 && this.cells[0].length === 1) {
41157
41466
  return this.getters.getCellText(this.cells[0][0].position);
41158
41467
  }
41159
41468
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
@@ -41478,7 +41787,7 @@ class ClipboardPlugin extends UIPlugin {
41478
41787
  status = "invisible";
41479
41788
  state;
41480
41789
  lastPasteState;
41481
- _isPaintingFormat = false;
41790
+ paintFormatStatus = "inactive";
41482
41791
  originSheetId;
41483
41792
  // ---------------------------------------------------------------------------
41484
41793
  // Command Handling
@@ -41493,7 +41802,7 @@ class ClipboardPlugin extends UIPlugin {
41493
41802
  if (!this.state) {
41494
41803
  return 24 /* CommandResult.EmptyClipboard */;
41495
41804
  }
41496
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41805
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41497
41806
  return this.state.isPasteAllowed(cmd.target, { pasteOption });
41498
41807
  case "PASTE_FROM_OS_CLIPBOARD": {
41499
41808
  const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
@@ -41509,6 +41818,11 @@ class ClipboardPlugin extends UIPlugin {
41509
41818
  const state = this.getClipboardStateForCopyCells(cut, "CUT");
41510
41819
  return state.isPasteAllowed(paste);
41511
41820
  }
41821
+ case "ACTIVATE_PAINT_FORMAT": {
41822
+ if (this.paintFormatStatus !== "inactive") {
41823
+ return 95 /* CommandResult.AlreadyInPaintingFormatMode */;
41824
+ }
41825
+ }
41512
41826
  }
41513
41827
  return 0 /* CommandResult.Success */;
41514
41828
  }
@@ -41525,11 +41839,13 @@ class ClipboardPlugin extends UIPlugin {
41525
41839
  if (!this.state) {
41526
41840
  break;
41527
41841
  }
41528
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41529
- this._isPaintingFormat = false;
41842
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41530
41843
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
41531
41844
  this.lastPasteState = this.state;
41532
- this.status = "invisible";
41845
+ if (this.paintFormatStatus === "oneOff") {
41846
+ this.paintFormatStatus = "inactive";
41847
+ this.status = "invisible";
41848
+ }
41533
41849
  break;
41534
41850
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
41535
41851
  this.status = "invisible";
@@ -41597,8 +41913,13 @@ class ClipboardPlugin extends UIPlugin {
41597
41913
  case "ACTIVATE_PAINT_FORMAT": {
41598
41914
  const zones = this.getters.getSelectedZones();
41599
41915
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
41600
- this._isPaintingFormat = true;
41601
41916
  this.status = "visible";
41917
+ if (cmd.persistent) {
41918
+ this.paintFormatStatus = "persistent";
41919
+ }
41920
+ else {
41921
+ this.paintFormatStatus = "oneOff";
41922
+ }
41602
41923
  break;
41603
41924
  }
41604
41925
  case "DELETE_SHEET":
@@ -41610,6 +41931,11 @@ class ClipboardPlugin extends UIPlugin {
41610
41931
  this.status = "invisible";
41611
41932
  }
41612
41933
  break;
41934
+ case "CANCEL_PAINT_FORMAT": {
41935
+ this.paintFormatStatus = "inactive";
41936
+ this.status = "invisible";
41937
+ break;
41938
+ }
41613
41939
  default:
41614
41940
  if (isCoreCommand(cmd)) {
41615
41941
  this.status = "invisible";
@@ -41640,7 +41966,7 @@ class ClipboardPlugin extends UIPlugin {
41640
41966
  return this.state ? this.state.operation === "CUT" : false;
41641
41967
  }
41642
41968
  isPaintingFormat() {
41643
- return this._isPaintingFormat;
41969
+ return this.paintFormatStatus !== "inactive";
41644
41970
  }
41645
41971
  // ---------------------------------------------------------------------------
41646
41972
  // Private methods
@@ -44275,6 +44601,7 @@ const coreViewsPluginRegistry = new Registry()
44275
44601
  .add("evaluation", EvaluationPlugin)
44276
44602
  .add("evaluation_chart", EvaluationChartPlugin)
44277
44603
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
44604
+ .add("row_size", UIRowSizePlugin)
44278
44605
  .add("custom_colors", CustomColorsPlugin);
44279
44606
 
44280
44607
  const clickableCellRegistry = new Registry();
@@ -44292,7 +44619,7 @@ class ImageProvider {
44292
44619
  async requestImage() {
44293
44620
  const file = await this.getImageFromUser();
44294
44621
  const path = await this.fileStore.upload(file);
44295
- const size = await this.getImageSize(path);
44622
+ const size = await this.getImageOriginalSize(path);
44296
44623
  return { path, size, mimetype: file.type };
44297
44624
  }
44298
44625
  getImageFromUser() {
@@ -44311,7 +44638,7 @@ class ImageProvider {
44311
44638
  input.click();
44312
44639
  });
44313
44640
  }
44314
- getImageSize(path) {
44641
+ getImageOriginalSize(path) {
44315
44642
  return new Promise((resolve, reject) => {
44316
44643
  const image = new Image();
44317
44644
  image.src = path;
@@ -45530,11 +45857,11 @@ class ActionButton extends owl.Component {
45530
45857
  }
45531
45858
  get title() {
45532
45859
  const name = this.actionButton.name(this.env);
45533
- const description = this.actionButton.description;
45860
+ const description = this.actionButton.description(this.env);
45534
45861
  return name + (description ? ` (${description})` : "");
45535
45862
  }
45536
45863
  get iconTitle() {
45537
- return this.actionButton.icon;
45864
+ return this.actionButton.icon(this.env);
45538
45865
  }
45539
45866
  onClick(ev) {
45540
45867
  if (this.isEnabled) {
@@ -45796,6 +46123,10 @@ css /* scss */ `
45796
46123
  top: 20%;
45797
46124
  }
45798
46125
  }
46126
+
46127
+ .user-select-text {
46128
+ user-select: text;
46129
+ }
45799
46130
  `;
45800
46131
  class TopBarComposer extends owl.Component {
45801
46132
  static template = "o-spreadsheet-TopBarComposer";
@@ -45915,6 +46246,27 @@ FontSizeEditor.props = {
45915
46246
  class: String,
45916
46247
  };
45917
46248
 
46249
+ class PaintFormatButton extends owl.Component {
46250
+ static template = "o-spreadsheet-PaintFormatButton";
46251
+ get isActive() {
46252
+ return this.env.model.getters.isPaintingFormat();
46253
+ }
46254
+ onDblClick() {
46255
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: true });
46256
+ }
46257
+ togglePaintFormat() {
46258
+ if (this.isActive) {
46259
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
46260
+ }
46261
+ else {
46262
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: false });
46263
+ }
46264
+ }
46265
+ }
46266
+ PaintFormatButton.props = {
46267
+ class: { type: String, optional: true },
46268
+ };
46269
+
45918
46270
  // -----------------------------------------------------------------------------
45919
46271
  // TopBar
45920
46272
  // -----------------------------------------------------------------------------
@@ -46015,6 +46367,7 @@ class TopBar extends owl.Component {
46015
46367
  TopBarComposer,
46016
46368
  FontSizeEditor,
46017
46369
  ActionButton,
46370
+ PaintFormatButton,
46018
46371
  BorderEditorWidget,
46019
46372
  };
46020
46373
  state = owl.useState({
@@ -47867,7 +48220,7 @@ class SelectionStreamProcessorImpl {
47867
48220
  : "nextCluster";
47868
48221
  while (true) {
47869
48222
  const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);
47870
- // Break if nextPosition == currentPosition, which happens if there's no next valid position
48223
+ // Break if nextPosition === currentPosition, which happens if there's no next valid position
47871
48224
  if (currentPosition.col === nextCellPosition.col &&
47872
48225
  currentPosition.row === nextCellPosition.row) {
47873
48226
  break;
@@ -48834,6 +49187,8 @@ function createImageDrawing(figure, sheet, imageRelId) {
48834
49187
  ["name", `Image ${imageId}`],
48835
49188
  ["title", "Image"],
48836
49189
  ];
49190
+ const cx = convertDotValueToEMU(figure.width);
49191
+ const cy = convertDotValueToEMU(figure.height);
48837
49192
  return escapeXml /*xml*/ `
48838
49193
  <xdr:twoCellAnchor editAs="oneCell">
48839
49194
  <xdr:from>
@@ -48860,6 +49215,9 @@ function createImageDrawing(figure, sheet, imageRelId) {
48860
49215
  </a:stretch>
48861
49216
  </xdr:blipFill>
48862
49217
  <xdr:spPr>
49218
+ <a:xfrm>
49219
+ <a:ext cx="${cx}" cy="${cy}" />
49220
+ </a:xfrm>
48863
49221
  <a:prstGeom prst="rect">
48864
49222
  <a:avLst/>
48865
49223
  </a:prstGeom>
@@ -49379,7 +49737,7 @@ function createWorksheets(data, construct) {
49379
49737
  const mimeType = image.data.mimetype;
49380
49738
  if (mimeType === undefined)
49381
49739
  continue;
49382
- const extension = IMAGE_MIMETYPE_EXTENSION_MAPPING[mimeType];
49740
+ const extension = IMAGE_MIMETYPE_TO_EXTENSION_MAPPING[mimeType];
49383
49741
  // only support exporting images with mimetypes specified in the mapping
49384
49742
  if (extension === undefined)
49385
49743
  continue;
@@ -49515,7 +49873,7 @@ function createRelsFiles(relsFiles) {
49515
49873
  function createContentTypes(files) {
49516
49874
  const overrideNodes = [];
49517
49875
  // hard-code supported image mimetypes
49518
- const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49876
+ const imageDefaultNodes = Object.entries(IMAGE_MIMETYPE_TO_EXTENSION_MAPPING).map(([mimetype, extension]) => createDefaultXMLElement(extension, mimetype));
49519
49877
  for (const file of files) {
49520
49878
  if ("contentType" in file && file.contentType) {
49521
49879
  overrideNodes.push(createOverride("/" + file.path, CONTENT_TYPES[file.contentType]));
@@ -49776,6 +50134,7 @@ class Model extends EventBus {
49776
50134
  ...config,
49777
50135
  mode: config.mode || "normal",
49778
50136
  custom: config.custom || {},
50137
+ defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
49779
50138
  external: this.setupExternalConfig(config.external || {}),
49780
50139
  transportService,
49781
50140
  client,
@@ -50073,6 +50432,7 @@ const helpers = {
50073
50432
  numberToLetters,
50074
50433
  UuidGenerator,
50075
50434
  formatValue,
50435
+ createCurrencyFormat,
50076
50436
  computeTextWidth,
50077
50437
  createEmptyWorkbookData,
50078
50438
  createEmptySheet,
@@ -50145,6 +50505,7 @@ exports.helpers = helpers;
50145
50505
  exports.invalidateCFEvaluationCommands = invalidateCFEvaluationCommands;
50146
50506
  exports.invalidateDependenciesCommands = invalidateDependenciesCommands;
50147
50507
  exports.invalidateEvaluationCommands = invalidateEvaluationCommands;
50508
+ exports.iterateAstNodes = iterateAstNodes;
50148
50509
  exports.links = links;
50149
50510
  exports.load = load;
50150
50511
  exports.parse = parse;
@@ -50155,6 +50516,6 @@ exports.setTranslationMethod = setTranslationMethod;
50155
50516
  exports.tokenize = tokenize;
50156
50517
 
50157
50518
 
50158
- __info__.version = '16.4.0-alpha.6';
50159
- __info__.date = '2023-06-28T11:40:12.377Z';
50160
- __info__.hash = '7d15d3c';
50519
+ __info__.version = '16.4.0';
50520
+ __info__.date = '2023-07-24T06:26:19.494Z';
50521
+ __info__.hash = '4eb614c';