@odoo/o-spreadsheet 16.4.0-alpha.7 → 16.4.1

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.
@@ -112,8 +112,8 @@ const SELECTION_BORDER_COLOR = "#3266ca";
112
112
  const HEADER_BORDER_COLOR = "#C0C0C0";
113
113
  const CELL_BORDER_COLOR = "#E2E3E3";
114
114
  const BACKGROUND_CHART_COLOR = "#FFFFFF";
115
- const MENU_ITEM_DISABLED_COLOR = "#CACACA";
116
115
  const BG_HOVER_COLOR = "#EBEBEB";
116
+ const DISABLED_TEXT_COLOR = "#CACACA";
117
117
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
118
118
  const LINK_COLOR = "#01666b";
119
119
  const FILTERS_COLOR = "#188038";
@@ -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
  }
@@ -3930,7 +4055,6 @@ const readonlyAllowedCommands = new Set([
3930
4055
  "SET_VIEWPORT_OFFSET",
3931
4056
  "SELECT_SEARCH_NEXT_MATCH",
3932
4057
  "SELECT_SEARCH_PREVIOUS_MATCH",
3933
- "REFRESH_SEARCH",
3934
4058
  "UPDATE_SEARCH",
3935
4059
  "CLEAR_SEARCH",
3936
4060
  "EVALUATE_CELLS",
@@ -4128,6 +4252,7 @@ exports.CommandResult = void 0;
4128
4252
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 92] = "NoSplitSeparatorInSelection";
4129
4253
  CommandResult[CommandResult["NoActiveSheet"] = 93] = "NoActiveSheet";
4130
4254
  CommandResult[CommandResult["InvalidLocale"] = 94] = "InvalidLocale";
4255
+ CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 95] = "AlreadyInPaintingFormatMode";
4131
4256
  })(exports.CommandResult || (exports.CommandResult = {}));
4132
4257
 
4133
4258
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5868,7 +5993,7 @@ css /* scss */ `
5868
5993
  }
5869
5994
  }
5870
5995
  &.disabled {
5871
- color: ${MENU_ITEM_DISABLED_COLOR};
5996
+ color: ${DISABLED_TEXT_COLOR};
5872
5997
  cursor: not-allowed;
5873
5998
  }
5874
5999
  }
@@ -5943,8 +6068,8 @@ class Menu extends owl.Component {
5943
6068
  return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
5944
6069
  }
5945
6070
  getIconName(menu) {
5946
- if (menu.icon) {
5947
- return menu.icon;
6071
+ if (menu.icon(this.env)) {
6072
+ return menu.icon(this.env);
5948
6073
  }
5949
6074
  if (menu.isActive?.(this.env)) {
5950
6075
  return "o-spreadsheet-Icon.CHECK";
@@ -6378,7 +6503,7 @@ function invertMatrix(M) {
6378
6503
  for (let pivot = 0; pivot < dim; pivot++) {
6379
6504
  let diagonalElement = C[pivot][pivot];
6380
6505
  // if we have a 0 on the diagonal we'll need to swap with a lower row
6381
- if (diagonalElement == 0) {
6506
+ if (diagonalElement === 0) {
6382
6507
  //look through every row below the i'th row
6383
6508
  for (let row = pivot + 1; row < dim; row++) {
6384
6509
  //if the ii'th row has a non-0 in the i'th col, swap it with that row
@@ -6405,7 +6530,7 @@ function invertMatrix(M) {
6405
6530
  // the other rows so that there will be 0's in this column in the
6406
6531
  // rows above and below this one
6407
6532
  for (let row = 0; row < dim; row++) {
6408
- if (row == pivot) {
6533
+ if (row === pivot) {
6409
6534
  continue;
6410
6535
  }
6411
6536
  // We want to change this element to 0
@@ -12470,7 +12595,7 @@ const ISNA = {
12470
12595
  return false;
12471
12596
  }
12472
12597
  catch (e) {
12473
- return e?.errorType == CellErrorType.NotAvailable;
12598
+ return e?.errorType === CellErrorType.NotAvailable;
12474
12599
  }
12475
12600
  },
12476
12601
  isExported: true,
@@ -15186,6 +15311,8 @@ const uuidGenerator$2 = new UuidGenerator();
15186
15311
  function createAction(item) {
15187
15312
  const name = item.name;
15188
15313
  const children = item.children;
15314
+ const description = item.description;
15315
+ const icon = item.icon;
15189
15316
  return {
15190
15317
  id: item.id || uuidGenerator$2.uuidv4(),
15191
15318
  name: typeof name === "function" ? name : () => name,
@@ -15203,8 +15330,8 @@ function createAction(item) {
15203
15330
  : () => [],
15204
15331
  isReadonlyAllowed: item.isReadonlyAllowed || false,
15205
15332
  separator: item.separator || false,
15206
- icon: item.icon || "",
15207
- description: item.description || "",
15333
+ icon: typeof icon === "function" ? icon : () => icon || "",
15334
+ description: typeof description === "function" ? description : () => description || "",
15208
15335
  textColor: item.textColor,
15209
15336
  sequence: item.sequence || 0,
15210
15337
  };
@@ -16670,7 +16797,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
16670
16797
  function convertDateFormatForMoment(format) {
16671
16798
  format = format.replace(/y/g, "Y");
16672
16799
  format = format.replace(/d/g, "D");
16673
- // "m" before "h" == month, "m" after "h" == minute
16800
+ // "m" before "h" === month, "m" after "h" === minute
16674
16801
  const indexH = format.indexOf("h");
16675
16802
  if (indexH >= 0) {
16676
16803
  format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
@@ -17524,6 +17651,7 @@ function getChartMenu(figureId, onFigureDeleted, env) {
17524
17651
  env.model.dispatch("SELECT_FIGURE", { id: figureId });
17525
17652
  env.openSidePanel("ChartPanel");
17526
17653
  },
17654
+ icon: "o-spreadsheet-Icon.EDIT",
17527
17655
  },
17528
17656
  getCopyMenuItem(figureId, env),
17529
17657
  getCutMenuItem(figureId, env),
@@ -17555,6 +17683,7 @@ function getImageMenuRegistry(figureId, onFigureDeleted, env) {
17555
17683
  width,
17556
17684
  });
17557
17685
  },
17686
+ icon: "o-spreadsheet-Icon.REFRESH",
17558
17687
  },
17559
17688
  getDeleteMenuItem(figureId, onFigureDeleted, env),
17560
17689
  ];
@@ -17571,6 +17700,7 @@ function getCopyMenuItem(figureId, env) {
17571
17700
  env.model.dispatch("COPY");
17572
17701
  await env.clipboard.write(env.model.getters.getClipboardContent());
17573
17702
  },
17703
+ icon: "o-spreadsheet-Icon.COPY",
17574
17704
  };
17575
17705
  }
17576
17706
  function getCutMenuItem(figureId, env) {
@@ -17584,6 +17714,7 @@ function getCutMenuItem(figureId, env) {
17584
17714
  env.model.dispatch("CUT");
17585
17715
  await env.clipboard.write(env.model.getters.getClipboardContent());
17586
17716
  },
17717
+ icon: "o-spreadsheet-Icon.CUT",
17587
17718
  };
17588
17719
  }
17589
17720
  function getDeleteMenuItem(figureId, onFigureDeleted, env) {
@@ -17598,6 +17729,7 @@ function getDeleteMenuItem(figureId, onFigureDeleted, env) {
17598
17729
  });
17599
17730
  onFigureDeleted();
17600
17731
  },
17732
+ icon: "o-spreadsheet-Icon.DELETE",
17601
17733
  };
17602
17734
  }
17603
17735
 
@@ -18665,7 +18797,7 @@ const insertFunctionMin = {
18665
18797
  };
18666
18798
  const categorieFunctionAll = {
18667
18799
  name: _lt("All"),
18668
- children: allFunctionListMenuBuilder(),
18800
+ children: [allFunctionListMenuBuilder],
18669
18801
  };
18670
18802
  function allFunctionListMenuBuilder() {
18671
18803
  const fnNames = functionRegistry.getKeys();
@@ -18997,7 +19129,10 @@ const formatNumberAutomatic = {
18997
19129
  };
18998
19130
  const formatNumberNumber = {
18999
19131
  name: _lt("Number"),
19000
- description: "1,000.12",
19132
+ description: (env) => formatValue(1000.12, {
19133
+ format: "#,##0.00",
19134
+ locale: env.model.getters.getLocale(),
19135
+ }),
19001
19136
  execute: (env) => setFormatter(env, "#,##0.00"),
19002
19137
  isActive: (env) => isFormatSelected(env, "#,##0.00"),
19003
19138
  };
@@ -19008,21 +19143,34 @@ const formatPercent = {
19008
19143
  };
19009
19144
  const formatNumberPercent = {
19010
19145
  name: _lt("Percent"),
19011
- description: "10.12%",
19146
+ description: (env) => formatValue(0.1012, {
19147
+ format: "0.00%",
19148
+ locale: env.model.getters.getLocale(),
19149
+ }),
19012
19150
  execute: FORMAT_PERCENT_ACTION,
19013
19151
  isActive: (env) => isFormatSelected(env, "0.00%"),
19014
19152
  };
19015
19153
  const formatNumberCurrency = {
19016
19154
  name: _lt("Currency"),
19017
- description: "$1,000.12",
19018
- execute: (env) => setFormatter(env, "[$$]#,##0.00"),
19019
- isActive: (env) => isFormatSelected(env, "[$$]#,##0.00"),
19155
+ description: (env) => formatValue(1000.12, {
19156
+ format: env.model.config.defaultCurrencyFormat,
19157
+ locale: env.model.getters.getLocale(),
19158
+ }),
19159
+ execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
19160
+ isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
19020
19161
  };
19021
19162
  const formatNumberCurrencyRounded = {
19022
19163
  name: _lt("Currency rounded"),
19023
- description: "$1,000",
19024
- execute: (env) => setFormatter(env, "[$$]#,##0"),
19025
- isActive: (env) => isFormatSelected(env, "[$$]#,##0"),
19164
+ description: (env) => formatValue(1000, {
19165
+ format: roundFormat(env.model.config.defaultCurrencyFormat),
19166
+ locale: env.model.getters.getLocale(),
19167
+ }),
19168
+ execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19169
+ isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19170
+ isVisible: (env) => {
19171
+ const currencyFormat = env.model.config.defaultCurrencyFormat;
19172
+ return currencyFormat !== roundFormat(currencyFormat);
19173
+ },
19026
19174
  };
19027
19175
  const formatCustomCurrency = {
19028
19176
  name: _lt("Custom currency"),
@@ -19031,19 +19179,37 @@ const formatCustomCurrency = {
19031
19179
  };
19032
19180
  const formatNumberDate = {
19033
19181
  name: _lt("Date"),
19034
- description: "9/26/2008",
19182
+ description: (env) => {
19183
+ const locale = env.model.getters.getLocale();
19184
+ return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
19185
+ format: locale.dateFormat,
19186
+ locale,
19187
+ });
19188
+ },
19035
19189
  execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
19036
19190
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
19037
19191
  };
19038
19192
  const formatNumberTime = {
19039
19193
  name: _lt("Time"),
19040
- description: "10:43:00 PM",
19194
+ description: (env) => {
19195
+ const locale = env.model.getters.getLocale();
19196
+ return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
19197
+ format: locale.timeFormat,
19198
+ locale,
19199
+ });
19200
+ },
19041
19201
  execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
19042
19202
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
19043
19203
  };
19044
19204
  const formatNumberDateTime = {
19045
19205
  name: _lt("Date time"),
19046
- description: "9/26/2008 22:43:00",
19206
+ description: (env) => {
19207
+ const locale = env.model.getters.getLocale();
19208
+ return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
19209
+ format: locale.dateFormat + " " + locale.timeFormat,
19210
+ locale,
19211
+ });
19212
+ },
19047
19213
  execute: (env) => {
19048
19214
  const locale = env.model.getters.getLocale();
19049
19215
  setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
@@ -19115,7 +19281,7 @@ const formatAlignment = {
19115
19281
  };
19116
19282
  const formatAlignmentHorizontal = {
19117
19283
  name: _lt("Horizontal align"),
19118
- icon: "o-spreadsheet-Icon.ALIGN_LEFT",
19284
+ icon: (env) => getHorizontalAlignmentIcon(env),
19119
19285
  };
19120
19286
  const formatAlignmentLeft = {
19121
19287
  name: _lt("Left"),
@@ -19140,46 +19306,50 @@ const formatAlignmentRight = {
19140
19306
  };
19141
19307
  const formatAlignmentVertical = {
19142
19308
  name: _lt("Vertical align"),
19143
- icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19309
+ icon: (env) => getVerticalAlignmentIcon(env),
19144
19310
  };
19145
19311
  const formatAlignmentTop = {
19146
19312
  name: _lt("Top"),
19147
19313
  execute: (env) => setStyle(env, { verticalAlign: "top" }),
19148
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "top",
19314
+ isActive: (env) => getVerticalAlign(env) === "top",
19149
19315
  icon: "o-spreadsheet-Icon.ALIGN_TOP",
19150
19316
  };
19151
19317
  const formatAlignmentMiddle = {
19152
19318
  name: _lt("Middle"),
19153
19319
  execute: (env) => setStyle(env, { verticalAlign: "middle" }),
19154
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "middle",
19320
+ isActive: (env) => getVerticalAlign(env) === "middle",
19155
19321
  icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19156
19322
  };
19157
19323
  const formatAlignmentBottom = {
19158
19324
  name: _lt("Bottom"),
19159
19325
  execute: (env) => setStyle(env, { verticalAlign: "bottom" }),
19160
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "bottom",
19326
+ isActive: (env) => getVerticalAlign(env) === "bottom",
19161
19327
  icon: "o-spreadsheet-Icon.ALIGN_BOTTOM",
19162
19328
  };
19163
- const formatWrapping = {
19329
+ const formatWrappingIcon = {
19164
19330
  name: _lt("Wrapping"),
19165
19331
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19166
19332
  };
19333
+ const formatWrapping = {
19334
+ name: _lt("Wrapping"),
19335
+ icon: (env) => getWrapModeIcon(env),
19336
+ };
19167
19337
  const formatWrappingOverflow = {
19168
19338
  name: _lt("Overflow"),
19169
19339
  execute: (env) => setStyle(env, { wrapping: "overflow" }),
19170
- isActive: (env) => (env.model.getters.getCurrentStyle().wrapping || "overflow") === "overflow",
19340
+ isActive: (env) => getWrappingMode(env) === "overflow",
19171
19341
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19172
19342
  };
19173
19343
  const formatWrappingWrap = {
19174
19344
  name: _lt("Wrap"),
19175
19345
  execute: (env) => setStyle(env, { wrapping: "wrap" }),
19176
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "wrap",
19346
+ isActive: (env) => getWrappingMode(env) === "wrap",
19177
19347
  icon: "o-spreadsheet-Icon.WRAPPING_WRAP",
19178
19348
  };
19179
19349
  const formatWrappingClip = {
19180
19350
  name: _lt("Clip"),
19181
19351
  execute: (env) => setStyle(env, { wrapping: "clip" }),
19182
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "clip",
19352
+ isActive: (env) => getWrappingMode(env) === "clip",
19183
19353
  icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
19184
19354
  };
19185
19355
  const textColor = {
@@ -19195,14 +19365,6 @@ const formatCF = {
19195
19365
  execute: OPEN_CF_SIDEPANEL_ACTION,
19196
19366
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
19197
19367
  };
19198
- const paintFormat = {
19199
- name: _lt("Paint Format"),
19200
- execute: (env) => env.model.dispatch("ACTIVATE_PAINT_FORMAT", {
19201
- target: env.model.getters.getSelectedZones(),
19202
- }),
19203
- icon: "o-spreadsheet-Icon.PAINT_FORMAT",
19204
- isActive: (env) => env.model.getters.isPaintingFormat(),
19205
- };
19206
19368
  const clearFormat = {
19207
19369
  name: _lt("Clear formatting"),
19208
19370
  description: "Ctrl+<",
@@ -19242,6 +19404,53 @@ function getHorizontalAlign(env) {
19242
19404
  }
19243
19405
  const cell = env.model.getters.getActiveCell();
19244
19406
  return cell.defaultAlign;
19407
+ }
19408
+ function getVerticalAlign(env) {
19409
+ const style = env.model.getters.getCurrentStyle();
19410
+ if (style.verticalAlign) {
19411
+ return style.verticalAlign;
19412
+ }
19413
+ return DEFAULT_VERTICAL_ALIGN;
19414
+ }
19415
+ function getWrappingMode(env) {
19416
+ const style = env.model.getters.getCurrentStyle();
19417
+ if (style.wrapping) {
19418
+ return style.wrapping;
19419
+ }
19420
+ return DEFAULT_WRAPPING_MODE;
19421
+ }
19422
+ function getHorizontalAlignmentIcon(env) {
19423
+ const horizontalAlign = getHorizontalAlign(env);
19424
+ switch (horizontalAlign) {
19425
+ case "right":
19426
+ return "o-spreadsheet-Icon.ALIGN_RIGHT";
19427
+ case "center":
19428
+ return "o-spreadsheet-Icon.ALIGN_CENTER";
19429
+ default:
19430
+ return "o-spreadsheet-Icon.ALIGN_LEFT";
19431
+ }
19432
+ }
19433
+ function getVerticalAlignmentIcon(env) {
19434
+ const verticalAlign = getVerticalAlign(env);
19435
+ switch (verticalAlign) {
19436
+ case "top":
19437
+ return "o-spreadsheet-Icon.ALIGN_TOP";
19438
+ case "middle":
19439
+ return "o-spreadsheet-Icon.ALIGN_MIDDLE";
19440
+ default:
19441
+ return "o-spreadsheet-Icon.ALIGN_BOTTOM";
19442
+ }
19443
+ }
19444
+ function getWrapModeIcon(env) {
19445
+ const wrapMode = getWrappingMode(env);
19446
+ switch (wrapMode) {
19447
+ case "wrap":
19448
+ return "o-spreadsheet-Icon.WRAPPING_WRAP";
19449
+ case "clip":
19450
+ return "o-spreadsheet-Icon.WRAPPING_CLIP";
19451
+ default:
19452
+ return "o-spreadsheet-Icon.WRAPPING_OVERFLOW";
19453
+ }
19245
19454
  }
19246
19455
 
19247
19456
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
@@ -19273,6 +19482,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19273
19482
  formatAlignmentTop: formatAlignmentTop,
19274
19483
  formatAlignmentMiddle: formatAlignmentMiddle,
19275
19484
  formatAlignmentBottom: formatAlignmentBottom,
19485
+ formatWrappingIcon: formatWrappingIcon,
19276
19486
  formatWrapping: formatWrapping,
19277
19487
  formatWrappingOverflow: formatWrappingOverflow,
19278
19488
  formatWrappingWrap: formatWrappingWrap,
@@ -19280,7 +19490,6 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
19280
19490
  textColor: textColor,
19281
19491
  fillColor: fillColor,
19282
19492
  formatCF: formatCF,
19283
- paintFormat: paintFormat,
19284
19493
  clearFormat: clearFormat
19285
19494
  });
19286
19495
 
@@ -20043,7 +20252,7 @@ topbarMenuRegistry
20043
20252
  separator: true,
20044
20253
  })
20045
20254
  .addChild("format_wrapping", ["format"], {
20046
- ...formatWrapping,
20255
+ ...formatWrappingIcon,
20047
20256
  sequence: 80,
20048
20257
  separator: true,
20049
20258
  })
@@ -22333,17 +22542,45 @@ class CustomCurrencyPanel extends owl.Component {
22333
22542
  }
22334
22543
  get formatProposals() {
22335
22544
  const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];
22336
- const proposalBases = this.initProposalBases(currency.decimalPlaces);
22337
- const firstPosition = currency.position;
22338
- const secondPosition = currency.position === "before" ? "after" : "before";
22545
+ const position = currency.position;
22546
+ const opposite = currency.position === "before" ? "after" : "before";
22339
22547
  const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : "";
22340
22548
  const code = this.state.currencyCode.trim() ? this.state.currencyCode : "";
22341
- return code || symbol
22342
- ? [
22343
- ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),
22344
- ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),
22345
- ]
22346
- : [];
22549
+ const decimalPlaces = currency.decimalPlaces;
22550
+ if (!symbol && !code) {
22551
+ return [];
22552
+ }
22553
+ const simple = symbol ? createCurrencyFormat({ symbol, position, decimalPlaces }) : "";
22554
+ const rounded = simple ? roundFormat(simple) : "";
22555
+ const simpleWithCode = createCurrencyFormat({ symbol, position, decimalPlaces, code });
22556
+ const roundedWithCode = roundFormat(simpleWithCode);
22557
+ const simpleOpposite = symbol
22558
+ ? createCurrencyFormat({ symbol, position: opposite, decimalPlaces })
22559
+ : "";
22560
+ const roundedOpposite = simpleOpposite ? roundFormat(simpleOpposite) : "";
22561
+ const simpleOppositeWithCode = createCurrencyFormat({
22562
+ symbol,
22563
+ position: opposite,
22564
+ decimalPlaces,
22565
+ code,
22566
+ });
22567
+ const roundedOppositeWithCode = roundFormat(simpleOppositeWithCode);
22568
+ const formats = new Set([
22569
+ rounded,
22570
+ simple,
22571
+ roundedWithCode,
22572
+ simpleWithCode,
22573
+ roundedOpposite,
22574
+ simpleOpposite,
22575
+ roundedOppositeWithCode,
22576
+ simpleOppositeWithCode,
22577
+ ]);
22578
+ return [...formats]
22579
+ .filter((format) => format !== "")
22580
+ .map((format) => ({
22581
+ format,
22582
+ example: formatValue(1000.0, { format, locale: this.env.model.getters.getLocale() }),
22583
+ }));
22347
22584
  }
22348
22585
  get isSameFormat() {
22349
22586
  const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];
@@ -22400,41 +22637,6 @@ class CustomCurrencyPanel extends owl.Component {
22400
22637
  initAvailableCurrencies() {
22401
22638
  this.state.selectedCurrencyIndex = 0;
22402
22639
  }
22403
- initProposalBases(decimalPlaces) {
22404
- const result = [{ format: "#,##0", example: "1,000" }];
22405
- const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
22406
- if (decimalRepresentation) {
22407
- result.push({
22408
- format: "#,##0" + decimalRepresentation,
22409
- example: "1,000" + decimalRepresentation,
22410
- });
22411
- }
22412
- return result;
22413
- }
22414
- createFormatProposals(proposalBases, symbol, code, position) {
22415
- let formatProposals = [];
22416
- // 1 - add proposal with symbol and without code
22417
- if (symbol) {
22418
- for (let base of proposalBases) {
22419
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));
22420
- }
22421
- }
22422
- // 2 - if code exist --> add more proposal with symbol and with code
22423
- if (code) {
22424
- for (let base of proposalBases) {
22425
- const expression = (position === "after" ? " " : "") + code + " " + symbol;
22426
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));
22427
- }
22428
- }
22429
- return formatProposals;
22430
- }
22431
- createFormatProposal(position, baseExample, formatBase, expression) {
22432
- const formatExpression = "[$" + expression + "]";
22433
- return {
22434
- example: position === "before" ? expression + baseExample : baseExample + expression,
22435
- format: position === "before" ? formatExpression + formatBase : formatBase + formatExpression,
22436
- };
22437
- }
22438
22640
  getCommonFormat() {
22439
22641
  const selectedZones = this.env.model.getters.getSelectedZones();
22440
22642
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -22491,6 +22693,7 @@ class FindAndReplacePanel extends owl.Component {
22491
22693
  state = owl.useState(this.initialState());
22492
22694
  debounceTimeoutId;
22493
22695
  showFormulaState = false;
22696
+ debouncedUpdateSearch;
22494
22697
  findAndReplaceRef = owl.useRef("findAndReplace");
22495
22698
  get hasSearchResult() {
22496
22699
  return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
@@ -22500,11 +22703,16 @@ class FindAndReplacePanel extends owl.Component {
22500
22703
  }
22501
22704
  setup() {
22502
22705
  this.showFormulaState = this.env.model.getters.shouldShowFormulas();
22706
+ this.debouncedUpdateSearch = debounce(this.updateSearch.bind(this), 200);
22503
22707
  owl.onMounted(() => this.focusInput());
22504
22708
  owl.onWillUnmount(() => {
22505
22709
  this.env.model.dispatch("CLEAR_SEARCH");
22506
22710
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
22507
22711
  });
22712
+ owl.useEffect(() => {
22713
+ this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
22714
+ this.searchFormulas();
22715
+ }, () => [this.env.model.getters.shouldShowFormulas()]);
22508
22716
  }
22509
22717
  onInput(ev) {
22510
22718
  this.state.toSearch = ev.target.value;
@@ -22524,10 +22732,6 @@ class FindAndReplacePanel extends owl.Component {
22524
22732
  this.replace();
22525
22733
  }
22526
22734
  }
22527
- onFocusSidePanel() {
22528
- this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
22529
- this.env.model.dispatch("REFRESH_SEARCH");
22530
- }
22531
22735
  searchFormulas() {
22532
22736
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
22533
22737
  show: this.state.searchOptions.searchFormulas,
@@ -22546,13 +22750,6 @@ class FindAndReplacePanel extends owl.Component {
22546
22750
  searchOptions: this.state.searchOptions,
22547
22751
  });
22548
22752
  }
22549
- debouncedUpdateSearch() {
22550
- clearTimeout(this.debounceTimeoutId);
22551
- this.debounceTimeoutId = setTimeout(() => {
22552
- this.updateSearch();
22553
- this.debounceTimeoutId = undefined;
22554
- }, 400);
22555
- }
22556
22753
  replace() {
22557
22754
  this.env.model.dispatch("REPLACE_SEARCH", {
22558
22755
  replaceWith: this.state.replaceWith,
@@ -23237,7 +23434,7 @@ class ContentEditableHelper {
23237
23434
  selection.removeAllRanges();
23238
23435
  selection.addRange(range);
23239
23436
  }
23240
- if (start == end && start === 0) {
23437
+ if (start === end && start === 0) {
23241
23438
  range.setStart(this.el, 0);
23242
23439
  range.setEnd(this.el, 0);
23243
23440
  }
@@ -23490,7 +23687,7 @@ class ContentEditableHelper {
23490
23687
  }, 0);
23491
23688
  }
23492
23689
  }
23493
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
23690
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
23494
23691
  usedCharacters++;
23495
23692
  }
23496
23693
  return usedCharacters;
@@ -25037,6 +25234,14 @@ function useInterval(callback, delay) {
25037
25234
  };
25038
25235
  }
25039
25236
 
25237
+ const CURSOR_SVG = /*xml*/ `
25238
+ <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>
25239
+ `;
25240
+ css /* scss */ `
25241
+ .o-paint-format-cursor {
25242
+ cursor: url("data:image/svg+xml,${encodeURIComponent(CURSOR_SVG)}"), auto;
25243
+ }
25244
+ `;
25040
25245
  function useCellHovered(env, gridRef, callback) {
25041
25246
  let hoveredPosition = {
25042
25247
  col: undefined,
@@ -25160,6 +25365,12 @@ class GridOverlay extends owl.Component {
25160
25365
  }
25161
25366
  return this.gridOverlay.el;
25162
25367
  }
25368
+ get style() {
25369
+ return this.props.gridOverlayDimensions;
25370
+ }
25371
+ get isPaintingFormat() {
25372
+ return this.env.model.getters.isPaintingFormat();
25373
+ }
25163
25374
  onMouseDown(ev) {
25164
25375
  if (ev.button > 0) {
25165
25376
  // not main button, probably a context menu
@@ -26337,6 +26548,9 @@ class Grid extends owl.Component {
26337
26548
  else if (this.menuState.isOpen) {
26338
26549
  this.closeMenu();
26339
26550
  }
26551
+ else if (this.env.model.getters.isPaintingFormat()) {
26552
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
26553
+ }
26340
26554
  else {
26341
26555
  this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
26342
26556
  }
@@ -27539,8 +27753,8 @@ function convertXlsxFormat(numFmtId, formats, warningManager) {
27539
27753
  throw new Error("Multiple escaped blocks in format");
27540
27754
  }
27541
27755
  convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
27542
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
27543
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
27756
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ === ignore width of next char for align purposes. Not supported ATM
27757
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * === repeat next character enough to fill the line. Not supported ATM
27544
27758
  convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
27545
27759
  convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
27546
27760
  if (isXlsxDateFormat(convertedFormat)) {
@@ -28832,29 +29046,26 @@ function escapeXml(strings, ...expressions) {
28832
29046
  return new XMLString(concat(str));
28833
29047
  }
28834
29048
  /**
28835
- * Removes the namespace of all the xml tags in the string.
29049
+ * Removes the escaped namespace of all the xml tags in the string.
28836
29050
  *
28837
- * Eg. : "ns:test a" => "test a"
29051
+ * Eg. : "NAMESPACEnsNAMESPACEtest a" => "test a"
28838
29052
  */
28839
- function removeNamespaces(query) {
28840
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
29053
+ function removeTagEscapedNamespaces(tag) {
29054
+ return tag.replace(/NAMESPACE.*NAMESPACE(.*)/, "$1");
28841
29055
  }
28842
29056
  /**
28843
- * Escape the namespace's colons of all the xml tags in the string.
29057
+ * Encase the namespaces in the element's tags with NAMESPACE string
28844
29058
  *
28845
- * Eg. : "ns:test a" => "ns\\:test a"
28846
- */
28847
- function escapeNamespaces(query) {
28848
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
28849
- }
28850
- /**
28851
- * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.
29059
+ * e.g. <x:foo> becomes <NAMESPACExNAMESPACEFoo>
28852
29060
  *
28853
- * Should return true if it's running on a browser, and false if it's running on jest (jsdom).
29061
+ * That's useful because namespaces aren't supported by the HTML specification, so it's arbitrary whether a HTML parser/querySelector
29062
+ * implementation will support namespaces in the tags or not.
28854
29063
  */
28855
- function areNamespaceIgnoredByQuerySelector() {
28856
- const doc = new DOMParser().parseFromString("<t:test xmlns:t='a'/>", "text/xml");
28857
- return doc.querySelector("test") !== null;
29064
+ function escapeTagNamespaces(str) {
29065
+ return str.replaceAll(/(<\/?)([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "$1" + "NAMESPACE" + "$2" + "NAMESPACE" + "$3");
29066
+ }
29067
+ function escapeQueryNameSpaces(query) {
29068
+ return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
28858
29069
  }
28859
29070
 
28860
29071
  class AttributeValue {
@@ -28884,14 +29095,20 @@ class XlsxBaseExtractor {
28884
29095
  // The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but
28885
29096
  // the XLSXImportFile contains both the main .xml file, and the .rels file
28886
29097
  currentFile = undefined;
28887
- // If the parser querySelector() implementation ignores tag namespaces or not
28888
- areNamespaceIgnored;
29098
+ /**
29099
+ * /!\ Important : There should be no namespaces in the tags of the XML files.
29100
+ *
29101
+ * This class use native querySelector and querySelectorAll, that's used for HTML (not XML). These aren't supposed to
29102
+ * handled namespaces, as they are not supported by the HTML specification. Some implementations (most browsers) do
29103
+ * actually support namespaces, but some don't (e.g. jsdom).
29104
+ *
29105
+ * The namespace should be escaped as with NAMESPACE string (eg. <t:foo> => <NAMESPACEtNAMESPACEfoo>).
29106
+ */
28889
29107
  constructor(rootFile, xlsxStructure, warningManager) {
28890
29108
  this.rootFile = rootFile;
28891
29109
  this.currentFile = rootFile.file.fileName;
28892
29110
  this.xlsxFileStructure = xlsxStructure;
28893
29111
  this.warningManager = warningManager;
28894
- this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();
28895
29112
  this.relationships = {};
28896
29113
  if (rootFile.rels) {
28897
29114
  this.extractRelationships(rootFile.rels).map((rel) => {
@@ -29117,29 +29334,13 @@ class XlsxBaseExtractor {
29117
29334
  throw new Error("Cannot find target file");
29118
29335
  return f;
29119
29336
  }
29120
- /**
29121
- * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29122
- *
29123
- * Why we need to do this :
29124
- * - For an XML "<t:test />"
29125
- * - on Jest(jsdom) : xml.querySelector("test") == null, xml.querySelector("t\\:test") == <t:test />
29126
- * - on Browser : xml.querySelector("test") == <t:test />, xml.querySelector("t\\:test") == null
29127
- */
29128
29337
  querySelector(element, query) {
29129
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29130
- return element.querySelector(query);
29338
+ const escapedQuery = escapeQueryNameSpaces(query);
29339
+ return element.querySelector(escapedQuery);
29131
29340
  }
29132
- /**
29133
- * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29134
- *
29135
- * Why we need to do this :
29136
- * - For an XML "<t:test />"
29137
- * - on Jest(jsdom) : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == [<t:test />]
29138
- * - on Browser : xml.querySelectorAll("test") == [<t:test />], xml.querySelectorAll("t\\:test") == []
29139
- */
29140
29341
  querySelectorAll(element, query) {
29141
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29142
- return element.querySelectorAll(query);
29342
+ const escapedQuery = escapeQueryNameSpaces(query);
29343
+ return element.querySelectorAll(escapedQuery);
29143
29344
  }
29144
29345
  /**
29145
29346
  * Get a color from its id in the Theme's colorScheme.
@@ -29379,7 +29580,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29379
29580
  throw new Error("Missing plot area in the chart definition.");
29380
29581
  }
29381
29582
  for (let child of plotAreaElement.children) {
29382
- const tag = removeNamespaces(child.tagName);
29583
+ const tag = removeTagEscapedNamespaces(child.tagName);
29383
29584
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
29384
29585
  return tag;
29385
29586
  }
@@ -29391,7 +29592,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
29391
29592
  class XlsxFigureExtractor extends XlsxBaseExtractor {
29392
29593
  extractFigures() {
29393
29594
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
29394
- const anchorType = removeNamespaces(figureElement.tagName);
29595
+ const anchorType = removeTagEscapedNamespaces(figureElement.tagName);
29395
29596
  if (anchorType !== "twoCellAnchor") {
29396
29597
  throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
29397
29598
  }
@@ -30025,7 +30226,8 @@ class XlsxReader {
30025
30226
  for (let key of Object.keys(files)) {
30026
30227
  // Random files can be in xlsx (like a bin file for printer settings)
30027
30228
  if (key.endsWith(".xml") || key.endsWith(".rels")) {
30028
- this.xmls[key] = parseXML(new XMLString(files[key]));
30229
+ const contentString = escapeTagNamespaces(files[key]);
30230
+ this.xmls[key] = parseXML(new XMLString(contentString));
30029
30231
  }
30030
30232
  else if (key.includes("media/image")) {
30031
30233
  this.images.push({
@@ -31092,7 +31294,7 @@ class BordersPlugin extends CorePlugin {
31092
31294
  }
31093
31295
  /**
31094
31296
  * Set the borders of a cell.
31095
- * It overrides the current border if override == true.
31297
+ * It overrides the current border if override === true.
31096
31298
  */
31097
31299
  setBorder(sheetId, col, row, border, override = true) {
31098
31300
  if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
@@ -32362,7 +32564,9 @@ class FigurePlugin extends CorePlugin {
32362
32564
  const numHeader = this.getters.getNumberRows(sheetId);
32363
32565
  let gridHeight = 0;
32364
32566
  for (let i = 0; i < numHeader; i++) {
32365
- gridHeight += this.getters.getRowSize(sheetId, i);
32567
+ // TODO : since the row size is an UI value now, this doesn't work anymore. Using the default cell height is
32568
+ // a temporary solution at best, but is broken.
32569
+ gridHeight += this.getters.getUserRowSize(sheetId, i) || DEFAULT_CELL_HEIGHT;
32366
32570
  }
32367
32571
  const figures = this.getters.getFigures(sheetId);
32368
32572
  for (const figure of figures) {
@@ -32788,33 +32992,15 @@ class FiltersPlugin extends CorePlugin {
32788
32992
  }
32789
32993
 
32790
32994
  class HeaderSizePlugin extends CorePlugin {
32791
- static getters = ["getRowSize", "getColSize"];
32995
+ static getters = ["getUserRowSize", "getColSize"];
32792
32996
  sizes = {};
32793
32997
  handle(cmd) {
32794
32998
  switch (cmd.type) {
32795
32999
  case "CREATE_SHEET": {
32796
- const computedSizes = this.computeSheetSizes(cmd.sheetId);
32797
- const sizes = {
32798
- COL: computedSizes.COL.map((size) => ({
32799
- manualSize: undefined,
32800
- computedSize: lazy(size),
32801
- })),
32802
- ROW: computedSizes.ROW.map((size) => ({
32803
- manualSize: undefined,
32804
- computedSize: lazy(size),
32805
- })),
32806
- };
32807
- this.history.update("sizes", cmd.sheetId, sizes);
33000
+ this.history.update("sizes", cmd.sheetId, { COL: [], ROW: [] });
32808
33001
  break;
32809
33002
  }
32810
33003
  case "DUPLICATE_SHEET":
32811
- // make sure the values are computed in case the original sheet is deleted
32812
- for (const row of this.sizes[cmd.sheetId].ROW) {
32813
- row.computedSize();
32814
- }
32815
- for (const col of this.sizes[cmd.sheetId].COL) {
32816
- col.computedSize();
32817
- }
32818
33004
  this.history.update("sizes", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));
32819
33005
  break;
32820
33006
  case "DELETE_SHEET":
@@ -32823,21 +33009,8 @@ class HeaderSizePlugin extends CorePlugin {
32823
33009
  this.history.update("sizes", sizes);
32824
33010
  break;
32825
33011
  case "REMOVE_COLUMNS_ROWS": {
32826
- let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
32827
- for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {
32828
- sizes.splice(headerIndex, 1);
32829
- }
32830
- const min = Math.min(...cmd.elements);
32831
- sizes = sizes.map((size, row) => {
32832
- if (cmd.dimension === "ROW" && row >= min) {
32833
- // invalidate sizes
32834
- return {
32835
- manualSize: size.manualSize,
32836
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32837
- };
32838
- }
32839
- return size;
32840
- });
33012
+ const arr = this.sizes[cmd.sheetId][cmd.dimension];
33013
+ const sizes = removeIndexesFromArray(arr, cmd.elements);
32841
33014
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32842
33015
  break;
32843
33016
  }
@@ -32846,51 +33019,18 @@ class HeaderSizePlugin extends CorePlugin {
32846
33019
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
32847
33020
  const baseSize = sizes[cmd.base];
32848
33021
  sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
32849
- sizes = sizes.map((size, row) => {
32850
- if (cmd.dimension === "ROW" && row > cmd.base + cmd.quantity) {
32851
- // invalidate sizes
32852
- return {
32853
- manualSize: size.manualSize,
32854
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32855
- };
32856
- }
32857
- return size;
32858
- });
32859
33022
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32860
33023
  break;
32861
33024
  }
32862
33025
  case "RESIZE_COLUMNS_ROWS":
32863
- for (let el of cmd.elements) {
32864
- if (cmd.dimension === "ROW") {
32865
- const height = this.getRowTallestCellSize(cmd.sheetId, el);
32866
- const size = height;
32867
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32868
- manualSize: cmd.size || undefined,
32869
- computedSize: lazy(size),
32870
- });
32871
- }
32872
- else {
32873
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32874
- manualSize: cmd.size || undefined,
32875
- computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),
32876
- });
33026
+ if (cmd.dimension === "ROW") {
33027
+ for (const el of cmd.elements) {
33028
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32877
33029
  }
32878
33030
  }
32879
- break;
32880
- case "UPDATE_CELL":
32881
- if (!this.sizes[cmd.sheetId]?.["ROW"]?.[cmd.row]?.manualSize) {
32882
- const { sheetId, row } = cmd;
32883
- this.history.update("sizes", sheetId, "ROW", row, "computedSize", lazy(() => this.getRowTallestCellSize(sheetId, row)));
32884
- }
32885
- break;
32886
- case "ADD_MERGE":
32887
- case "REMOVE_MERGE":
32888
- for (let target of cmd.target) {
32889
- for (let row of range(target.top, target.bottom + 1)) {
32890
- const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);
32891
- if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {
32892
- this.history.update("sizes", cmd.sheetId, "ROW", row, "computedSize", lazy(rowHeight));
32893
- }
33031
+ else {
33032
+ for (const el of cmd.elements) {
33033
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32894
33034
  }
32895
33035
  }
32896
33036
  break;
@@ -32898,94 +33038,29 @@ class HeaderSizePlugin extends CorePlugin {
32898
33038
  return;
32899
33039
  }
32900
33040
  getColSize(sheetId, index) {
32901
- return this.getHeaderSize(sheetId, "COL", index);
32902
- }
32903
- getRowSize(sheetId, index) {
32904
- return this.getHeaderSize(sheetId, "ROW", index);
32905
- }
32906
- getHeaderSize(sheetId, dimension, index) {
32907
- return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
32908
- this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
32909
- this.getDefaultHeaderSize(dimension));
32910
- }
32911
- computeSheetSizes(sheetId) {
32912
- const sizes = { COL: [], ROW: [] };
32913
- for (let col of range(0, this.getters.getNumberCols(sheetId))) {
32914
- sizes.COL.push(this.getHeaderSize(sheetId, "COL", col));
32915
- }
32916
- for (let row of range(0, this.getters.getNumberRows(sheetId))) {
32917
- let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
32918
- if (!rowSize) {
32919
- const height = this.getRowTallestCellSize(sheetId, row);
32920
- rowSize = height;
32921
- }
32922
- sizes.ROW.push(rowSize);
32923
- }
32924
- return sizes;
33041
+ return Math.round(this.sizes[sheetId]?.["COL"][index] || DEFAULT_CELL_WIDTH);
32925
33042
  }
32926
- getDefaultHeaderSize(dimension) {
32927
- return dimension === "COL" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;
32928
- }
32929
- /**
32930
- * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
32931
- * merge, or the height of the cell computed based on its font size.
32932
- */
32933
- getCellHeight(position) {
32934
- const merge = this.getters.getMerge(position);
32935
- if (merge && merge.bottom !== merge.top) {
32936
- return DEFAULT_CELL_HEIGHT;
32937
- }
32938
- const cell = this.getters.getCell(position);
32939
- return getDefaultCellHeight(cell);
32940
- }
32941
- /**
32942
- * Get the tallest cell of a row and its size.
32943
- *
32944
- * The tallest cell of the row correspond to the cell with the biggest font size,
32945
- * and that is not part of a multi-line merge.
32946
- */
32947
- getRowTallestCellSize(sheetId, row) {
32948
- const cellIds = this.getters.getRowCells(sheetId, row);
32949
- let maxHeight = 0;
32950
- for (let i = 0; i < cellIds.length; i++) {
32951
- const cell = this.getters.getCellById(cellIds[i]);
32952
- if (!cell)
32953
- continue;
32954
- const position = this.getters.getCellPosition(cell.id);
32955
- const cellHeight = this.getCellHeight(position);
32956
- if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
32957
- maxHeight = cellHeight;
32958
- }
32959
- }
32960
- if (maxHeight <= DEFAULT_CELL_HEIGHT) {
32961
- return DEFAULT_CELL_HEIGHT;
32962
- }
32963
- return maxHeight;
33043
+ getUserRowSize(sheetId, index) {
33044
+ const rowSize = this.sizes[sheetId]?.["ROW"][index];
33045
+ return rowSize ? Math.round(rowSize) : undefined;
32964
33046
  }
32965
33047
  import(data) {
32966
33048
  for (let sheet of data.sheets) {
32967
- const manualSizes = { COL: [], ROW: [] };
33049
+ const sizes = {
33050
+ COL: Array(sheet.colNumber).fill(undefined),
33051
+ ROW: Array(sheet.rowNumber).fill(undefined),
33052
+ };
32968
33053
  for (let [rowIndex, row] of Object.entries(sheet.rows)) {
32969
33054
  if (row.size) {
32970
- manualSizes["ROW"][rowIndex] = row.size;
33055
+ sizes["ROW"][rowIndex] = row.size;
32971
33056
  }
32972
33057
  }
32973
33058
  for (let [colIndex, col] of Object.entries(sheet.cols)) {
32974
33059
  if (col.size) {
32975
- manualSizes["COL"][colIndex] = col.size;
32976
- }
32977
- }
32978
- const computedSizes = this.computeSheetSizes(sheet.id);
32979
- this.sizes[sheet.id] = {
32980
- COL: computedSizes.COL.map((size, i) => ({
32981
- manualSize: manualSizes.COL[i],
32982
- computedSize: lazy(size),
32983
- })),
32984
- ROW: computedSizes.ROW.map((size, i) => ({
32985
- manualSize: manualSizes.ROW[i],
32986
- computedSize: lazy(size),
32987
- })),
32988
- };
33060
+ sizes["COL"][colIndex] = col.size;
33061
+ }
33062
+ }
33063
+ this.sizes[sheet.id] = sizes;
32989
33064
  }
32990
33065
  return;
32991
33066
  }
@@ -33006,9 +33081,12 @@ class HeaderSizePlugin extends CorePlugin {
33006
33081
  if (sheet.rows === undefined) {
33007
33082
  sheet.rows = {};
33008
33083
  }
33009
- for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
33010
- if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
33011
- sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
33084
+ for (const row of range(0, this.getters.getNumberRows(sheet.id))) {
33085
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]) {
33086
+ sheet.rows[row] = {
33087
+ ...sheet.rows[row],
33088
+ size: this.getUserRowSize(sheet.id, row) ?? DEFAULT_CELL_HEIGHT,
33089
+ };
33012
33090
  }
33013
33091
  }
33014
33092
  // Export col sizes
@@ -33016,7 +33094,7 @@ class HeaderSizePlugin extends CorePlugin {
33016
33094
  sheet.cols = {};
33017
33095
  }
33018
33096
  for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
33019
- if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
33097
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]) {
33020
33098
  sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
33021
33099
  }
33022
33100
  }
@@ -36572,7 +36650,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36572
36650
  case "ContainsText":
36573
36651
  return cell.value.toString().indexOf(values[0].toString()) > -1;
36574
36652
  case "NotContains":
36575
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
36653
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) === -1;
36576
36654
  case "GreaterThan":
36577
36655
  return cell.value > values[0];
36578
36656
  case "GreaterThanOrEqual":
@@ -36599,6 +36677,156 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
36599
36677
  };
36600
36678
  }
36601
36679
 
36680
+ class UIRowSizePlugin extends UIPlugin {
36681
+ static getters = ["getRowSize"];
36682
+ tallestCellInRow = {};
36683
+ ctx = document.createElement("canvas").getContext("2d");
36684
+ handle(cmd) {
36685
+ switch (cmd.type) {
36686
+ case "START":
36687
+ for (const sheetId of this.getters.getSheetIds()) {
36688
+ this.initializeSheet(sheetId);
36689
+ }
36690
+ break;
36691
+ case "CREATE_SHEET": {
36692
+ this.initializeSheet(cmd.sheetId);
36693
+ break;
36694
+ }
36695
+ case "DUPLICATE_SHEET": {
36696
+ const tallestCells = deepCopy(this.tallestCellInRow[cmd.sheetId]);
36697
+ this.history.update("tallestCellInRow", cmd.sheetIdTo, tallestCells);
36698
+ break;
36699
+ }
36700
+ case "DELETE_SHEET":
36701
+ const tallestCells = { ...this.tallestCellInRow };
36702
+ delete tallestCells[cmd.sheetId];
36703
+ this.history.update("tallestCellInRow", tallestCells);
36704
+ break;
36705
+ case "REMOVE_COLUMNS_ROWS": {
36706
+ if (cmd.dimension === "COL") {
36707
+ return;
36708
+ }
36709
+ const tallestCells = removeIndexesFromArray(this.tallestCellInRow[cmd.sheetId], cmd.elements);
36710
+ this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
36711
+ break;
36712
+ }
36713
+ case "ADD_COLUMNS_ROWS": {
36714
+ if (cmd.dimension === "COL") {
36715
+ return;
36716
+ }
36717
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
36718
+ const newCells = Array(cmd.quantity).fill(undefined);
36719
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
36720
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
36721
+ break;
36722
+ }
36723
+ case "RESIZE_COLUMNS_ROWS":
36724
+ {
36725
+ const sheetId = cmd.sheetId;
36726
+ if (cmd.dimension === "ROW") {
36727
+ for (const row of cmd.elements) {
36728
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36729
+ this.history.update("tallestCellInRow", sheetId, row, tallestCell);
36730
+ }
36731
+ }
36732
+ else {
36733
+ // Recompute row heights on col size change, they might have changed because of wrapped text
36734
+ for (const row of range(0, this.getters.getNumberRows(sheetId))) {
36735
+ for (const col of cmd.elements) {
36736
+ this.updateRowSizeForCellChange(sheetId, row, col);
36737
+ }
36738
+ }
36739
+ }
36740
+ }
36741
+ break;
36742
+ case "UPDATE_CELL":
36743
+ this.updateRowSizeForCellChange(cmd.sheetId, cmd.row, cmd.col);
36744
+ break;
36745
+ case "ADD_MERGE":
36746
+ case "REMOVE_MERGE":
36747
+ for (const target of cmd.target) {
36748
+ for (const position of positions(target)) {
36749
+ this.updateRowSizeForCellChange(cmd.sheetId, position.row, position.col);
36750
+ }
36751
+ }
36752
+ }
36753
+ return;
36754
+ }
36755
+ getRowSize(sheetId, row) {
36756
+ return Math.round(this.getters.getUserRowSize(sheetId, row) ??
36757
+ this.tallestCellInRow[sheetId][row]?.size ??
36758
+ DEFAULT_CELL_HEIGHT);
36759
+ }
36760
+ updateRowSizeForCellChange(sheetId, row, col) {
36761
+ const tallestCellInRow = this.tallestCellInRow[sheetId]?.[row];
36762
+ if (tallestCellInRow?.cell.col === col) {
36763
+ const newTallestCell = this.getRowTallestCell(sheetId, row);
36764
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36765
+ }
36766
+ const updatedCellHeight = this.getCellHeight({ sheetId, col, row });
36767
+ if (updatedCellHeight <= DEFAULT_CELL_HEIGHT) {
36768
+ return;
36769
+ }
36770
+ if ((!tallestCellInRow && updatedCellHeight > DEFAULT_CELL_HEIGHT) ||
36771
+ (tallestCellInRow && updatedCellHeight > tallestCellInRow.size)) {
36772
+ const newTallestCell = { cell: { sheetId, col, row }, size: updatedCellHeight };
36773
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36774
+ }
36775
+ }
36776
+ initializeSheet(sheetId) {
36777
+ const tallestCells = [];
36778
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
36779
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36780
+ tallestCells.push(tallestCell);
36781
+ }
36782
+ this.history.update("tallestCellInRow", sheetId, tallestCells);
36783
+ }
36784
+ /**
36785
+ * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
36786
+ * merge, or the height of the cell computed based on its style/content.
36787
+ */
36788
+ getCellHeight(position) {
36789
+ if (this.isInMultiRowMerge(position)) {
36790
+ return DEFAULT_CELL_HEIGHT;
36791
+ }
36792
+ const cell = this.getters.getCell(position);
36793
+ const colSize = this.getters.getColSize(position.sheetId, position.col);
36794
+ return getDefaultCellHeight(this.ctx, cell, colSize);
36795
+ }
36796
+ isInMultiRowMerge(position) {
36797
+ const merge = this.getters.getMerge(position);
36798
+ return !!merge && merge.bottom !== merge.top;
36799
+ }
36800
+ /**
36801
+ * Get the tallest cell of a row and its size.
36802
+ */
36803
+ getRowTallestCell(sheetId, row) {
36804
+ const userRowSize = this.getters.getUserRowSize(sheetId, row);
36805
+ if (userRowSize !== undefined) {
36806
+ return undefined;
36807
+ }
36808
+ const cellIds = this.getters.getRowCells(sheetId, row);
36809
+ let maxHeight = 0;
36810
+ let tallestCell = undefined;
36811
+ for (let i = 0; i < cellIds.length; i++) {
36812
+ const cell = this.getters.getCellById(cellIds[i]);
36813
+ if (!cell) {
36814
+ continue;
36815
+ }
36816
+ const position = this.getters.getCellPosition(cell.id);
36817
+ const cellHeight = this.getCellHeight(position);
36818
+ if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
36819
+ maxHeight = cellHeight;
36820
+ tallestCell = { cell: position, size: cellHeight };
36821
+ }
36822
+ }
36823
+ if (tallestCell && tallestCell.size > DEFAULT_CELL_HEIGHT) {
36824
+ return tallestCell;
36825
+ }
36826
+ return undefined;
36827
+ }
36828
+ }
36829
+
36602
36830
  /**
36603
36831
  * This plugin manage the autofill.
36604
36832
  *
@@ -38300,6 +38528,7 @@ class FindAndReplacePlugin extends UIPlugin {
38300
38528
  searchFormulas: false,
38301
38529
  };
38302
38530
  toSearch = "";
38531
+ isSearchDirty = false;
38303
38532
  // ---------------------------------------------------------------------------
38304
38533
  // Command Handling
38305
38534
  // ---------------------------------------------------------------------------
@@ -38327,14 +38556,21 @@ class FindAndReplacePlugin extends UIPlugin {
38327
38556
  case "REDO":
38328
38557
  case "REMOVE_COLUMNS_ROWS":
38329
38558
  case "ADD_COLUMNS_ROWS":
38330
- this.clearSearch();
38559
+ case "EVALUATE_CELLS":
38560
+ case "UPDATE_CELL":
38561
+ this.isSearchDirty = true;
38331
38562
  break;
38332
38563
  case "ACTIVATE_SHEET":
38333
- case "REFRESH_SEARCH":
38334
38564
  this.refreshSearch();
38335
38565
  break;
38336
38566
  }
38337
38567
  }
38568
+ finalize() {
38569
+ if (this.isSearchDirty) {
38570
+ this.refreshSearch();
38571
+ this.isSearchDirty = false;
38572
+ }
38573
+ }
38338
38574
  // ---------------------------------------------------------------------------
38339
38575
  // Getters
38340
38576
  // ---------------------------------------------------------------------------
@@ -38390,6 +38626,11 @@ class FindAndReplacePlugin extends UIPlugin {
38390
38626
  if (this.toSearch) {
38391
38627
  for (const cell of Object.values(cells)) {
38392
38628
  const { col, row } = this.getters.getCellPosition(cell.id);
38629
+ const isColHidden = this.getters.isColHidden(sheetId, col);
38630
+ const isRowHidden = this.getters.isRowHidden(sheetId, row);
38631
+ if (isColHidden || isRowHidden) {
38632
+ continue;
38633
+ }
38393
38634
  if (cell &&
38394
38635
  this.currentSearchRegex &&
38395
38636
  this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
@@ -40032,10 +40273,8 @@ class SortPlugin extends UIPlugin {
40032
40273
  }
40033
40274
  updateCellCommands.push(newCellValues);
40034
40275
  }
40035
- for (const cmd of updateCellCommands) {
40036
- this.dispatch("UPDATE_CELL", cmd);
40037
- }
40038
40276
  }
40277
+ updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
40039
40278
  }
40040
40279
  /**
40041
40280
  * Return the distances between main merge cells in the zone.
@@ -40136,24 +40375,28 @@ class SheetUIPlugin extends UIPlugin {
40136
40375
  // Getters
40137
40376
  // ---------------------------------------------------------------------------
40138
40377
  getCellWidth(position) {
40139
- const text = this.getCellText(position);
40140
40378
  const style = this.getters.getCellComputedStyle(position);
40141
- const multiLineText = text.split(NEWLINE);
40142
- let contentWidth = Math.max(...multiLineText.map((line) => this.getTextWidth(line, style)));
40379
+ let contentWidth = 0;
40380
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
40381
+ if (content) {
40382
+ const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
40383
+ contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
40384
+ }
40143
40385
  const icon = this.getters.getConditionalIcon(position);
40144
40386
  if (icon) {
40145
- contentWidth += computeIconWidth(this.getters.getCellStyle(position));
40387
+ contentWidth += computeIconWidth(style);
40146
40388
  }
40147
40389
  const isFilterHeader = this.getters.isFilterHeader(position);
40148
40390
  if (isFilterHeader) {
40149
40391
  contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;
40150
40392
  }
40151
- if (contentWidth > 0) {
40152
- contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40153
- if (this.getters.getCellStyle(position).wrapping === "wrap") {
40154
- const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40155
- return Math.min(colWidth, contentWidth);
40156
- }
40393
+ if (contentWidth === 0) {
40394
+ return 0;
40395
+ }
40396
+ contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40397
+ if (style.wrapping === "wrap") {
40398
+ const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40399
+ return Math.min(colWidth, contentWidth);
40157
40400
  }
40158
40401
  return contentWidth;
40159
40402
  }
@@ -40176,56 +40419,7 @@ class SheetUIPlugin extends UIPlugin {
40176
40419
  getCellMultiLineText(position, width) {
40177
40420
  const style = this.getters.getCellStyle(position);
40178
40421
  const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());
40179
- const brokenText = [];
40180
- for (const line of text.split("\n")) {
40181
- const words = line.split(" ");
40182
- if (!width) {
40183
- brokenText.push(line);
40184
- continue;
40185
- }
40186
- let textLine = "";
40187
- let availableWidth = width;
40188
- for (let word of words) {
40189
- const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);
40190
- const lastPart = splitWord.pop();
40191
- const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);
40192
- // At this step: "splitWord" is an array composed of parts of word whose
40193
- // length is at most equal to "width".
40194
- // Last part contains the end of the word.
40195
- // Note that: When word length is less than width, then lastPart is equal
40196
- // to word and splitWord is empty
40197
- if (splitWord.length) {
40198
- if (textLine !== "") {
40199
- brokenText.push(textLine);
40200
- textLine = "";
40201
- availableWidth = width;
40202
- }
40203
- splitWord.forEach((wordPart) => {
40204
- brokenText.push(wordPart);
40205
- });
40206
- textLine = lastPart;
40207
- availableWidth = width - lastPartWidth;
40208
- }
40209
- else {
40210
- // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
40211
- const _word = textLine === "" ? lastPart : " " + lastPart;
40212
- const wordWidth = computeTextWidth(this.ctx, _word, style);
40213
- if (wordWidth <= availableWidth) {
40214
- textLine += _word;
40215
- availableWidth -= wordWidth;
40216
- }
40217
- else {
40218
- brokenText.push(textLine);
40219
- textLine = lastPart;
40220
- availableWidth = width - lastPartWidth;
40221
- }
40222
- }
40223
- }
40224
- if (textLine !== "") {
40225
- brokenText.push(textLine);
40226
- }
40227
- }
40228
- return brokenText;
40422
+ return splitTextToWidth(this.ctx, text, style, width);
40229
40423
  }
40230
40424
  /**
40231
40425
  * Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -40284,26 +40478,6 @@ class SheetUIPlugin extends UIPlugin {
40284
40478
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
40285
40479
  return Math.max(0, ...sizes);
40286
40480
  }
40287
- splitWordToSpecificWidth(ctx, word, width, style) {
40288
- const wordWidth = computeTextWidth(ctx, word, style);
40289
- if (wordWidth <= width) {
40290
- return [word];
40291
- }
40292
- const splitWord = [];
40293
- let wordPart = "";
40294
- for (let l of word) {
40295
- const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
40296
- if (wordPartWidth > width) {
40297
- splitWord.push(wordPart);
40298
- wordPart = l;
40299
- }
40300
- else {
40301
- wordPart += l;
40302
- }
40303
- }
40304
- splitWord.push(wordPart);
40305
- return splitWord;
40306
- }
40307
40481
  /**
40308
40482
  * Check that any "sheetId" in the command matches an existing
40309
40483
  * sheet.
@@ -41295,7 +41469,7 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
41295
41469
  .join("\n") || "\t");
41296
41470
  }
41297
41471
  getHTMLContent() {
41298
- if (this.cells.length == 1 && this.cells[0].length == 1) {
41472
+ if (this.cells.length === 1 && this.cells[0].length === 1) {
41299
41473
  return this.getters.getCellText(this.cells[0][0].position);
41300
41474
  }
41301
41475
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
@@ -41620,7 +41794,7 @@ class ClipboardPlugin extends UIPlugin {
41620
41794
  status = "invisible";
41621
41795
  state;
41622
41796
  lastPasteState;
41623
- _isPaintingFormat = false;
41797
+ paintFormatStatus = "inactive";
41624
41798
  originSheetId;
41625
41799
  // ---------------------------------------------------------------------------
41626
41800
  // Command Handling
@@ -41635,7 +41809,7 @@ class ClipboardPlugin extends UIPlugin {
41635
41809
  if (!this.state) {
41636
41810
  return 24 /* CommandResult.EmptyClipboard */;
41637
41811
  }
41638
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41812
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41639
41813
  return this.state.isPasteAllowed(cmd.target, { pasteOption });
41640
41814
  case "PASTE_FROM_OS_CLIPBOARD": {
41641
41815
  const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
@@ -41651,6 +41825,11 @@ class ClipboardPlugin extends UIPlugin {
41651
41825
  const state = this.getClipboardStateForCopyCells(cut, "CUT");
41652
41826
  return state.isPasteAllowed(paste);
41653
41827
  }
41828
+ case "ACTIVATE_PAINT_FORMAT": {
41829
+ if (this.paintFormatStatus !== "inactive") {
41830
+ return 95 /* CommandResult.AlreadyInPaintingFormatMode */;
41831
+ }
41832
+ }
41654
41833
  }
41655
41834
  return 0 /* CommandResult.Success */;
41656
41835
  }
@@ -41667,11 +41846,13 @@ class ClipboardPlugin extends UIPlugin {
41667
41846
  if (!this.state) {
41668
41847
  break;
41669
41848
  }
41670
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41671
- this._isPaintingFormat = false;
41849
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41672
41850
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
41673
41851
  this.lastPasteState = this.state;
41674
- this.status = "invisible";
41852
+ if (this.paintFormatStatus === "oneOff") {
41853
+ this.paintFormatStatus = "inactive";
41854
+ this.status = "invisible";
41855
+ }
41675
41856
  break;
41676
41857
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
41677
41858
  this.status = "invisible";
@@ -41739,8 +41920,13 @@ class ClipboardPlugin extends UIPlugin {
41739
41920
  case "ACTIVATE_PAINT_FORMAT": {
41740
41921
  const zones = this.getters.getSelectedZones();
41741
41922
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
41742
- this._isPaintingFormat = true;
41743
41923
  this.status = "visible";
41924
+ if (cmd.persistent) {
41925
+ this.paintFormatStatus = "persistent";
41926
+ }
41927
+ else {
41928
+ this.paintFormatStatus = "oneOff";
41929
+ }
41744
41930
  break;
41745
41931
  }
41746
41932
  case "DELETE_SHEET":
@@ -41752,6 +41938,11 @@ class ClipboardPlugin extends UIPlugin {
41752
41938
  this.status = "invisible";
41753
41939
  }
41754
41940
  break;
41941
+ case "CANCEL_PAINT_FORMAT": {
41942
+ this.paintFormatStatus = "inactive";
41943
+ this.status = "invisible";
41944
+ break;
41945
+ }
41755
41946
  default:
41756
41947
  if (isCoreCommand(cmd)) {
41757
41948
  this.status = "invisible";
@@ -41782,7 +41973,7 @@ class ClipboardPlugin extends UIPlugin {
41782
41973
  return this.state ? this.state.operation === "CUT" : false;
41783
41974
  }
41784
41975
  isPaintingFormat() {
41785
- return this._isPaintingFormat;
41976
+ return this.paintFormatStatus !== "inactive";
41786
41977
  }
41787
41978
  // ---------------------------------------------------------------------------
41788
41979
  // Private methods
@@ -44417,6 +44608,7 @@ const coreViewsPluginRegistry = new Registry()
44417
44608
  .add("evaluation", EvaluationPlugin)
44418
44609
  .add("evaluation_chart", EvaluationChartPlugin)
44419
44610
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
44611
+ .add("row_size", UIRowSizePlugin)
44420
44612
  .add("custom_colors", CustomColorsPlugin);
44421
44613
 
44422
44614
  const clickableCellRegistry = new Registry();
@@ -45539,6 +45731,11 @@ css /* scss */ `
45539
45731
  .o-sidePanelButton:enabled {
45540
45732
  cursor: pointer;
45541
45733
  }
45734
+
45735
+ .o-sidePanelButton:disabled {
45736
+ color: ${DISABLED_TEXT_COLOR};
45737
+ }
45738
+
45542
45739
  .o-sidePanelButton:last-child {
45543
45740
  margin-right: 0px;
45544
45741
  }
@@ -45672,11 +45869,11 @@ class ActionButton extends owl.Component {
45672
45869
  }
45673
45870
  get title() {
45674
45871
  const name = this.actionButton.name(this.env);
45675
- const description = this.actionButton.description;
45872
+ const description = this.actionButton.description(this.env);
45676
45873
  return name + (description ? ` (${description})` : "");
45677
45874
  }
45678
45875
  get iconTitle() {
45679
- return this.actionButton.icon;
45876
+ return this.actionButton.icon(this.env);
45680
45877
  }
45681
45878
  onClick(ev) {
45682
45879
  if (this.isEnabled) {
@@ -46061,6 +46258,27 @@ FontSizeEditor.props = {
46061
46258
  class: String,
46062
46259
  };
46063
46260
 
46261
+ class PaintFormatButton extends owl.Component {
46262
+ static template = "o-spreadsheet-PaintFormatButton";
46263
+ get isActive() {
46264
+ return this.env.model.getters.isPaintingFormat();
46265
+ }
46266
+ onDblClick() {
46267
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: true });
46268
+ }
46269
+ togglePaintFormat() {
46270
+ if (this.isActive) {
46271
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
46272
+ }
46273
+ else {
46274
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: false });
46275
+ }
46276
+ }
46277
+ }
46278
+ PaintFormatButton.props = {
46279
+ class: { type: String, optional: true },
46280
+ };
46281
+
46064
46282
  // -----------------------------------------------------------------------------
46065
46283
  // TopBar
46066
46284
  // -----------------------------------------------------------------------------
@@ -46161,6 +46379,7 @@ class TopBar extends owl.Component {
46161
46379
  TopBarComposer,
46162
46380
  FontSizeEditor,
46163
46381
  ActionButton,
46382
+ PaintFormatButton,
46164
46383
  BorderEditorWidget,
46165
46384
  };
46166
46385
  state = owl.useState({
@@ -48013,7 +48232,7 @@ class SelectionStreamProcessorImpl {
48013
48232
  : "nextCluster";
48014
48233
  while (true) {
48015
48234
  const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);
48016
- // Break if nextPosition == currentPosition, which happens if there's no next valid position
48235
+ // Break if nextPosition === currentPosition, which happens if there's no next valid position
48017
48236
  if (currentPosition.col === nextCellPosition.col &&
48018
48237
  currentPosition.row === nextCellPosition.row) {
48019
48238
  break;
@@ -49927,6 +50146,7 @@ class Model extends EventBus {
49927
50146
  ...config,
49928
50147
  mode: config.mode || "normal",
49929
50148
  custom: config.custom || {},
50149
+ defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
49930
50150
  external: this.setupExternalConfig(config.external || {}),
49931
50151
  transportService,
49932
50152
  client,
@@ -50224,6 +50444,7 @@ const helpers = {
50224
50444
  numberToLetters,
50225
50445
  UuidGenerator,
50226
50446
  formatValue,
50447
+ createCurrencyFormat,
50227
50448
  computeTextWidth,
50228
50449
  createEmptyWorkbookData,
50229
50450
  createEmptySheet,
@@ -50307,6 +50528,6 @@ exports.setTranslationMethod = setTranslationMethod;
50307
50528
  exports.tokenize = tokenize;
50308
50529
 
50309
50530
 
50310
- __info__.version = '16.4.0-alpha.7';
50311
- __info__.date = '2023-07-04T13:46:24.130Z';
50312
- __info__.hash = 'b04cf78';
50531
+ __info__.version = '16.4.1';
50532
+ __info__.date = '2023-07-26T12:13:04.992Z';
50533
+ __info__.hash = 'e687fa9';