@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.
@@ -109,8 +109,8 @@
109
109
  const HEADER_BORDER_COLOR = "#C0C0C0";
110
110
  const CELL_BORDER_COLOR = "#E2E3E3";
111
111
  const BACKGROUND_CHART_COLOR = "#FFFFFF";
112
- const MENU_ITEM_DISABLED_COLOR = "#CACACA";
113
112
  const BG_HOVER_COLOR = "#EBEBEB";
113
+ const DISABLED_TEXT_COLOR = "#CACACA";
114
114
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
115
115
  const LINK_COLOR = "#01666b";
116
116
  const FILTERS_COLOR = "#188038";
@@ -236,6 +236,7 @@
236
236
  const HEADER_FONT_SIZE = 11;
237
237
  const DEFAULT_FONT = "'Roboto', arial";
238
238
  const DEFAULT_VERTICAL_ALIGN = "bottom";
239
+ const DEFAULT_WRAPPING_MODE = "overflow";
239
240
  // Borders
240
241
  const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
241
242
  const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
@@ -393,82 +394,6 @@
393
394
  function clip(val, min, max) {
394
395
  return val < min ? min : val > max ? max : val;
395
396
  }
396
- function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
397
- return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
398
- }
399
- /**
400
- * Get the default height of the cell given its style.
401
- */
402
- function getDefaultCellHeight(cell) {
403
- if (!cell || !cell.content) {
404
- return DEFAULT_CELL_HEIGHT;
405
- }
406
- const fontSize = computeTextFontSizeInPixels(cell.style);
407
- // the number of lines should be computed from the formula result, but it's not evaluated at this point
408
- const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
409
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
410
- }
411
- function computeTextWidth(context, text, style) {
412
- const font = computeTextFont(style);
413
- if (!textWidthCache[font]) {
414
- textWidthCache[font] = {};
415
- }
416
- if (textWidthCache[font][text] === undefined) {
417
- context.save();
418
- context.font = font;
419
- const textWidth = context.measureText(text).width;
420
- context.restore();
421
- textWidthCache[font][text] = textWidth;
422
- }
423
- return textWidthCache[font][text];
424
- }
425
- const textWidthCache = {};
426
- function fontSizeInPixels(fontSize) {
427
- return Math.round((fontSize * 96) / 72);
428
- }
429
- function computeTextFont(style) {
430
- const italic = style.italic ? "italic " : "";
431
- const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
432
- const size = computeTextFontSizeInPixels(style);
433
- return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
434
- }
435
- function computeTextFontSizeInPixels(style) {
436
- const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
437
- return fontSizeInPixels(sizeInPt);
438
- }
439
- /**
440
- * Return the font size that makes the width of a text match the given line width.
441
- * Minimum font size is 1.
442
- *
443
- * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
444
- */
445
- function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
446
- let minFontSize = 1;
447
- if (getTextWidth(minFontSize) > lineWidth)
448
- return minFontSize;
449
- if (getTextWidth(maxFontSize) < lineWidth)
450
- return maxFontSize;
451
- // Dichotomic search
452
- let fontSize = (minFontSize + maxFontSize) / 2;
453
- let currentTextWidth = getTextWidth(fontSize);
454
- // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
455
- let iterations = 0;
456
- while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
457
- if (currentTextWidth >= lineWidth) {
458
- maxFontSize = (minFontSize + maxFontSize) / 2;
459
- }
460
- else {
461
- minFontSize = (minFontSize + maxFontSize) / 2;
462
- }
463
- fontSize = (minFontSize + maxFontSize) / 2;
464
- currentTextWidth = getTextWidth(fontSize);
465
- iterations++;
466
- }
467
- return fontSize;
468
- }
469
- function computeIconWidth(style) {
470
- return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
471
- }
472
397
  /**
473
398
  * Create a range from start (included) to end (excluded).
474
399
  * range(10, 13) => [10, 11, 12]
@@ -723,10 +648,6 @@
723
648
  Object.keys(cleanObject).forEach((key) => !cleanObject[key] && delete cleanObject[key]);
724
649
  return cleanObject;
725
650
  }
726
- /** Transform a string to lower case. If the string is undefined, return an empty string */
727
- function toLowerCase(str) {
728
- return str ? str.toLowerCase() : "";
729
- }
730
651
  function transpose2dArray(matrix, callback = (val) => val) {
731
652
  if (!matrix.length)
732
653
  return [];
@@ -815,6 +736,14 @@
815
736
  return cache.get(args[0]);
816
737
  },
817
738
  }[funcName];
739
+ }
740
+ function removeIndexesFromArray(array, indexes) {
741
+ return array.filter((_, index) => !indexes.includes(index));
742
+ }
743
+ function insertItemsAtIndex(array, items, index) {
744
+ const newArray = [...array];
745
+ newArray.splice(index, 0, ...items);
746
+ return newArray;
818
747
  }
819
748
 
820
749
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -958,13 +887,13 @@
958
887
  let g = rgba.g.toString(16);
959
888
  let b = rgba.b.toString(16);
960
889
  let a = Math.round(rgba.a * 255).toString(16);
961
- if (r.length == 1)
890
+ if (r.length === 1)
962
891
  r = "0" + r;
963
- if (g.length == 1)
892
+ if (g.length === 1)
964
893
  g = "0" + g;
965
- if (b.length == 1)
894
+ if (b.length === 1)
966
895
  b = "0" + b;
967
- if (a.length == 1)
896
+ if (a.length === 1)
968
897
  a = "0" + a;
969
898
  if (a === "ff")
970
899
  a = "";
@@ -1067,13 +996,13 @@
1067
996
  let l = 0;
1068
997
  // Calculate hue
1069
998
  // No difference
1070
- if (delta == 0)
999
+ if (delta === 0)
1071
1000
  h = 0;
1072
1001
  // Red is max
1073
- else if (cMax == r)
1002
+ else if (cMax === r)
1074
1003
  h = ((g - b) / delta) % 6;
1075
1004
  // Green is max
1076
- else if (cMax == g)
1005
+ else if (cMax === g)
1077
1006
  h = (b - r) / delta + 2;
1078
1007
  // Blue is max
1079
1008
  else
@@ -1084,7 +1013,7 @@
1084
1013
  h += 360;
1085
1014
  l = (cMax + cMin) / 2;
1086
1015
  // Calculate saturation
1087
- s = delta == 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1016
+ s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1));
1088
1017
  // Multiply l and s by 100
1089
1018
  s = +(s * 100).toFixed(1);
1090
1019
  l = +(l * 100).toFixed(1);
@@ -1521,7 +1450,7 @@
1521
1450
  }
1522
1451
  function isLeapYear(year) {
1523
1452
  const _year = Math.trunc(year);
1524
- return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 == 0;
1453
+ return (_year % 4 === 0 && _year % 100 != 0) || _year % 400 === 0;
1525
1454
  }
1526
1455
  function getYearFrac(startDate, endDate, _dayCountConvention) {
1527
1456
  if (startDate === endDate) {
@@ -2182,6 +2111,39 @@
2182
2111
  }
2183
2112
  return undefined;
2184
2113
  }
2114
+ function createCurrencyFormat(currency) {
2115
+ const decimalPlaces = currency.decimalPlaces ?? 2;
2116
+ const position = currency.position ?? "before";
2117
+ const code = currency.code ?? "";
2118
+ const symbol = currency.symbol ?? "";
2119
+ const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
2120
+ const numberFormat = "#,##0" + decimalRepresentation;
2121
+ let textExpression = `${code} ${symbol}`.trim();
2122
+ if (position === "after" && code) {
2123
+ textExpression = " " + textExpression;
2124
+ }
2125
+ return insertTextInFormat(textExpression, position, numberFormat);
2126
+ }
2127
+ function insertTextInFormat(text, position, format) {
2128
+ const textExpression = `[$${text}]`;
2129
+ return position === "before" ? textExpression + format : format + textExpression;
2130
+ }
2131
+ function roundFormat(format) {
2132
+ const internalFormat = parseFormat(format);
2133
+ const roundedFormat = internalFormat.map((formatPart) => {
2134
+ if (formatPart.type === "NUMBER") {
2135
+ return {
2136
+ type: formatPart.type,
2137
+ format: {
2138
+ ...formatPart.format,
2139
+ decimalPart: undefined,
2140
+ },
2141
+ };
2142
+ }
2143
+ return formatPart;
2144
+ });
2145
+ return convertInternalFormatToFormat(roundedFormat);
2146
+ }
2185
2147
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
2186
2148
  const internalFormat = parseFormat(format || "#,##0");
2187
2149
  const largeNumberFormat = internalFormat
@@ -3275,6 +3237,169 @@
3275
3237
  return rows;
3276
3238
  }
3277
3239
 
3240
+ function computeTextLinesHeight(textLineHeight, numberOfLines = 1) {
3241
+ return numberOfLines * (textLineHeight + MIN_CELL_TEXT_MARGIN) - MIN_CELL_TEXT_MARGIN;
3242
+ }
3243
+ /**
3244
+ * Get the default height of the cell given its style.
3245
+ */
3246
+ function getDefaultCellHeight(ctx, cell, colSize) {
3247
+ if (!cell || !cell.content)
3248
+ return DEFAULT_CELL_HEIGHT;
3249
+ const maxWidth = cell.style?.wrapping ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
3250
+ const numberOfLines = cell.isFormula
3251
+ ? 1
3252
+ : splitTextToWidth(ctx, cell.content, cell.style, maxWidth).length;
3253
+ const fontSize = computeTextFontSizeInPixels(cell.style);
3254
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
3255
+ }
3256
+ const textWidthCache = {};
3257
+ function computeTextWidth(context, text, style) {
3258
+ const font = computeTextFont(style);
3259
+ if (!textWidthCache[font]) {
3260
+ textWidthCache[font] = {};
3261
+ }
3262
+ if (textWidthCache[font][text] === undefined) {
3263
+ context.save();
3264
+ context.font = font;
3265
+ const textWidth = context.measureText(text).width;
3266
+ context.restore();
3267
+ textWidthCache[font][text] = textWidth;
3268
+ }
3269
+ return textWidthCache[font][text];
3270
+ }
3271
+ function fontSizeInPixels(fontSize) {
3272
+ return Math.round((fontSize * 96) / 72);
3273
+ }
3274
+ function computeTextFont(style) {
3275
+ const italic = style.italic ? "italic " : "";
3276
+ const weight = style.bold ? "bold" : DEFAULT_FONT_WEIGHT;
3277
+ const size = computeTextFontSizeInPixels(style);
3278
+ return `${italic}${weight} ${size}px ${DEFAULT_FONT}`;
3279
+ }
3280
+ function computeTextFontSizeInPixels(style) {
3281
+ const sizeInPt = style?.fontSize || DEFAULT_FONT_SIZE;
3282
+ return fontSizeInPixels(sizeInPt);
3283
+ }
3284
+ function splitWordToSpecificWidth(ctx, word, width, style) {
3285
+ const wordWidth = computeTextWidth(ctx, word, style);
3286
+ if (wordWidth <= width) {
3287
+ return [word];
3288
+ }
3289
+ const splitWord = [];
3290
+ let wordPart = "";
3291
+ for (let l of word) {
3292
+ const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
3293
+ if (wordPartWidth > width) {
3294
+ splitWord.push(wordPart);
3295
+ wordPart = l;
3296
+ }
3297
+ else {
3298
+ wordPart += l;
3299
+ }
3300
+ }
3301
+ splitWord.push(wordPart);
3302
+ return splitWord;
3303
+ }
3304
+ /**
3305
+ * Return the given text, split in multiple lines if needed. The text will be split in multiple
3306
+ * line if it contains NEWLINE characters, or if it's longer than the given width.
3307
+ */
3308
+ function splitTextToWidth(ctx, text, style, width) {
3309
+ if (!style)
3310
+ style = {};
3311
+ const brokenText = [];
3312
+ // Checking if text contains NEWLINE before split makes it very slightly slower if text contains it,
3313
+ // but 5-10x faster if it doesn't
3314
+ const lines = text.includes(NEWLINE) ? text.split(NEWLINE) : [text];
3315
+ for (const line of lines) {
3316
+ const words = line.includes(" ") ? line.split(" ") : [line];
3317
+ if (!width) {
3318
+ brokenText.push(line);
3319
+ continue;
3320
+ }
3321
+ let textLine = "";
3322
+ let availableWidth = width;
3323
+ for (let word of words) {
3324
+ const splitWord = splitWordToSpecificWidth(ctx, word, width, style);
3325
+ const lastPart = splitWord.pop();
3326
+ const lastPartWidth = computeTextWidth(ctx, lastPart, style);
3327
+ // At this step: "splitWord" is an array composed of parts of word whose
3328
+ // length is at most equal to "width".
3329
+ // Last part contains the end of the word.
3330
+ // Note that: When word length is less than width, then lastPart is equal
3331
+ // to word and splitWord is empty
3332
+ if (splitWord.length) {
3333
+ if (textLine !== "") {
3334
+ brokenText.push(textLine);
3335
+ textLine = "";
3336
+ availableWidth = width;
3337
+ }
3338
+ splitWord.forEach((wordPart) => {
3339
+ brokenText.push(wordPart);
3340
+ });
3341
+ textLine = lastPart;
3342
+ availableWidth = width - lastPartWidth;
3343
+ }
3344
+ else {
3345
+ // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
3346
+ const _word = textLine === "" ? lastPart : " " + lastPart;
3347
+ const wordWidth = computeTextWidth(ctx, _word, style);
3348
+ if (wordWidth <= availableWidth) {
3349
+ textLine += _word;
3350
+ availableWidth -= wordWidth;
3351
+ }
3352
+ else {
3353
+ brokenText.push(textLine);
3354
+ textLine = lastPart;
3355
+ availableWidth = width - lastPartWidth;
3356
+ }
3357
+ }
3358
+ }
3359
+ if (textLine !== "") {
3360
+ brokenText.push(textLine);
3361
+ }
3362
+ }
3363
+ return brokenText;
3364
+ }
3365
+ /**
3366
+ * Return the font size that makes the width of a text match the given line width.
3367
+ * Minimum font size is 1.
3368
+ *
3369
+ * @param getTextWidth function that takes a fontSize as argument, and return the width of the text with this font size.
3370
+ */
3371
+ function getFontSizeMatchingWidth(lineWidth, maxFontSize, getTextWidth, precision = 0.25) {
3372
+ let minFontSize = 1;
3373
+ if (getTextWidth(minFontSize) > lineWidth)
3374
+ return minFontSize;
3375
+ if (getTextWidth(maxFontSize) < lineWidth)
3376
+ return maxFontSize;
3377
+ // Dichotomic search
3378
+ let fontSize = (minFontSize + maxFontSize) / 2;
3379
+ let currentTextWidth = getTextWidth(fontSize);
3380
+ // Use a maximum number of iterations to be safe, because measuring text isn't 100% precise
3381
+ let iterations = 0;
3382
+ while (Math.abs(currentTextWidth - lineWidth) > precision && iterations < 20) {
3383
+ if (currentTextWidth >= lineWidth) {
3384
+ maxFontSize = (minFontSize + maxFontSize) / 2;
3385
+ }
3386
+ else {
3387
+ minFontSize = (minFontSize + maxFontSize) / 2;
3388
+ }
3389
+ fontSize = (minFontSize + maxFontSize) / 2;
3390
+ currentTextWidth = getTextWidth(fontSize);
3391
+ iterations++;
3392
+ }
3393
+ return fontSize;
3394
+ }
3395
+ function computeIconWidth(style) {
3396
+ return computeTextFontSizeInPixels(style) + 2 * MIN_CF_ICON_MARGIN;
3397
+ }
3398
+ /** Transform a string to lower case. If the string is undefined, return an empty string */
3399
+ function toLowerCase(str) {
3400
+ return str ? str.toLowerCase() : "";
3401
+ }
3402
+
3278
3403
  /*
3279
3404
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
3280
3405
  * */
@@ -3297,7 +3422,7 @@
3297
3422
  else {
3298
3423
  // mainly for jest and other browsers that do not have the crypto functionality
3299
3424
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
3300
- var r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
3425
+ var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
3301
3426
  return v.toString(16);
3302
3427
  });
3303
3428
  }
@@ -3927,7 +4052,6 @@
3927
4052
  "SET_VIEWPORT_OFFSET",
3928
4053
  "SELECT_SEARCH_NEXT_MATCH",
3929
4054
  "SELECT_SEARCH_PREVIOUS_MATCH",
3930
- "REFRESH_SEARCH",
3931
4055
  "UPDATE_SEARCH",
3932
4056
  "CLEAR_SEARCH",
3933
4057
  "EVALUATE_CELLS",
@@ -4125,6 +4249,7 @@
4125
4249
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 92] = "NoSplitSeparatorInSelection";
4126
4250
  CommandResult[CommandResult["NoActiveSheet"] = 93] = "NoActiveSheet";
4127
4251
  CommandResult[CommandResult["InvalidLocale"] = 94] = "InvalidLocale";
4252
+ CommandResult[CommandResult["AlreadyInPaintingFormatMode"] = 95] = "AlreadyInPaintingFormatMode";
4128
4253
  })(exports.CommandResult || (exports.CommandResult = {}));
4129
4254
 
4130
4255
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
@@ -5865,7 +5990,7 @@
5865
5990
  }
5866
5991
  }
5867
5992
  &.disabled {
5868
- color: ${MENU_ITEM_DISABLED_COLOR};
5993
+ color: ${DISABLED_TEXT_COLOR};
5869
5994
  cursor: not-allowed;
5870
5995
  }
5871
5996
  }
@@ -5940,8 +6065,8 @@
5940
6065
  return this.props.menuItems.some((menuItem) => !!menuItem.icon || !!menuItem.isActive);
5941
6066
  }
5942
6067
  getIconName(menu) {
5943
- if (menu.icon) {
5944
- return menu.icon;
6068
+ if (menu.icon(this.env)) {
6069
+ return menu.icon(this.env);
5945
6070
  }
5946
6071
  if (menu.isActive?.(this.env)) {
5947
6072
  return "o-spreadsheet-Icon.CHECK";
@@ -6375,7 +6500,7 @@
6375
6500
  for (let pivot = 0; pivot < dim; pivot++) {
6376
6501
  let diagonalElement = C[pivot][pivot];
6377
6502
  // if we have a 0 on the diagonal we'll need to swap with a lower row
6378
- if (diagonalElement == 0) {
6503
+ if (diagonalElement === 0) {
6379
6504
  //look through every row below the i'th row
6380
6505
  for (let row = pivot + 1; row < dim; row++) {
6381
6506
  //if the ii'th row has a non-0 in the i'th col, swap it with that row
@@ -6402,7 +6527,7 @@
6402
6527
  // the other rows so that there will be 0's in this column in the
6403
6528
  // rows above and below this one
6404
6529
  for (let row = 0; row < dim; row++) {
6405
- if (row == pivot) {
6530
+ if (row === pivot) {
6406
6531
  continue;
6407
6532
  }
6408
6533
  // We want to change this element to 0
@@ -12467,7 +12592,7 @@
12467
12592
  return false;
12468
12593
  }
12469
12594
  catch (e) {
12470
- return e?.errorType == CellErrorType.NotAvailable;
12595
+ return e?.errorType === CellErrorType.NotAvailable;
12471
12596
  }
12472
12597
  },
12473
12598
  isExported: true,
@@ -15183,6 +15308,8 @@
15183
15308
  function createAction(item) {
15184
15309
  const name = item.name;
15185
15310
  const children = item.children;
15311
+ const description = item.description;
15312
+ const icon = item.icon;
15186
15313
  return {
15187
15314
  id: item.id || uuidGenerator$2.uuidv4(),
15188
15315
  name: typeof name === "function" ? name : () => name,
@@ -15200,8 +15327,8 @@
15200
15327
  : () => [],
15201
15328
  isReadonlyAllowed: item.isReadonlyAllowed || false,
15202
15329
  separator: item.separator || false,
15203
- icon: item.icon || "",
15204
- description: item.description || "",
15330
+ icon: typeof icon === "function" ? icon : () => icon || "",
15331
+ description: typeof description === "function" ? description : () => description || "",
15205
15332
  textColor: item.textColor,
15206
15333
  sequence: item.sequence || 0,
15207
15334
  };
@@ -16667,7 +16794,7 @@
16667
16794
  function convertDateFormatForMoment(format) {
16668
16795
  format = format.replace(/y/g, "Y");
16669
16796
  format = format.replace(/d/g, "D");
16670
- // "m" before "h" == month, "m" after "h" == minute
16797
+ // "m" before "h" === month, "m" after "h" === minute
16671
16798
  const indexH = format.indexOf("h");
16672
16799
  if (indexH >= 0) {
16673
16800
  format = format.slice(0, indexH).replace(/m/g, "M") + format.slice(indexH);
@@ -17521,6 +17648,7 @@
17521
17648
  env.model.dispatch("SELECT_FIGURE", { id: figureId });
17522
17649
  env.openSidePanel("ChartPanel");
17523
17650
  },
17651
+ icon: "o-spreadsheet-Icon.EDIT",
17524
17652
  },
17525
17653
  getCopyMenuItem(figureId, env),
17526
17654
  getCutMenuItem(figureId, env),
@@ -17552,6 +17680,7 @@
17552
17680
  width,
17553
17681
  });
17554
17682
  },
17683
+ icon: "o-spreadsheet-Icon.REFRESH",
17555
17684
  },
17556
17685
  getDeleteMenuItem(figureId, onFigureDeleted, env),
17557
17686
  ];
@@ -17568,6 +17697,7 @@
17568
17697
  env.model.dispatch("COPY");
17569
17698
  await env.clipboard.write(env.model.getters.getClipboardContent());
17570
17699
  },
17700
+ icon: "o-spreadsheet-Icon.COPY",
17571
17701
  };
17572
17702
  }
17573
17703
  function getCutMenuItem(figureId, env) {
@@ -17581,6 +17711,7 @@
17581
17711
  env.model.dispatch("CUT");
17582
17712
  await env.clipboard.write(env.model.getters.getClipboardContent());
17583
17713
  },
17714
+ icon: "o-spreadsheet-Icon.CUT",
17584
17715
  };
17585
17716
  }
17586
17717
  function getDeleteMenuItem(figureId, onFigureDeleted, env) {
@@ -17595,6 +17726,7 @@
17595
17726
  });
17596
17727
  onFigureDeleted();
17597
17728
  },
17729
+ icon: "o-spreadsheet-Icon.DELETE",
17598
17730
  };
17599
17731
  }
17600
17732
 
@@ -18662,7 +18794,7 @@
18662
18794
  };
18663
18795
  const categorieFunctionAll = {
18664
18796
  name: _lt("All"),
18665
- children: allFunctionListMenuBuilder(),
18797
+ children: [allFunctionListMenuBuilder],
18666
18798
  };
18667
18799
  function allFunctionListMenuBuilder() {
18668
18800
  const fnNames = functionRegistry.getKeys();
@@ -18994,7 +19126,10 @@
18994
19126
  };
18995
19127
  const formatNumberNumber = {
18996
19128
  name: _lt("Number"),
18997
- description: "1,000.12",
19129
+ description: (env) => formatValue(1000.12, {
19130
+ format: "#,##0.00",
19131
+ locale: env.model.getters.getLocale(),
19132
+ }),
18998
19133
  execute: (env) => setFormatter(env, "#,##0.00"),
18999
19134
  isActive: (env) => isFormatSelected(env, "#,##0.00"),
19000
19135
  };
@@ -19005,21 +19140,34 @@
19005
19140
  };
19006
19141
  const formatNumberPercent = {
19007
19142
  name: _lt("Percent"),
19008
- description: "10.12%",
19143
+ description: (env) => formatValue(0.1012, {
19144
+ format: "0.00%",
19145
+ locale: env.model.getters.getLocale(),
19146
+ }),
19009
19147
  execute: FORMAT_PERCENT_ACTION,
19010
19148
  isActive: (env) => isFormatSelected(env, "0.00%"),
19011
19149
  };
19012
19150
  const formatNumberCurrency = {
19013
19151
  name: _lt("Currency"),
19014
- description: "$1,000.12",
19015
- execute: (env) => setFormatter(env, "[$$]#,##0.00"),
19016
- isActive: (env) => isFormatSelected(env, "[$$]#,##0.00"),
19152
+ description: (env) => formatValue(1000.12, {
19153
+ format: env.model.config.defaultCurrencyFormat,
19154
+ locale: env.model.getters.getLocale(),
19155
+ }),
19156
+ execute: (env) => setFormatter(env, env.model.config.defaultCurrencyFormat),
19157
+ isActive: (env) => isFormatSelected(env, env.model.config.defaultCurrencyFormat),
19017
19158
  };
19018
19159
  const formatNumberCurrencyRounded = {
19019
19160
  name: _lt("Currency rounded"),
19020
- description: "$1,000",
19021
- execute: (env) => setFormatter(env, "[$$]#,##0"),
19022
- isActive: (env) => isFormatSelected(env, "[$$]#,##0"),
19161
+ description: (env) => formatValue(1000, {
19162
+ format: roundFormat(env.model.config.defaultCurrencyFormat),
19163
+ locale: env.model.getters.getLocale(),
19164
+ }),
19165
+ execute: (env) => setFormatter(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19166
+ isActive: (env) => isFormatSelected(env, roundFormat(env.model.config.defaultCurrencyFormat)),
19167
+ isVisible: (env) => {
19168
+ const currencyFormat = env.model.config.defaultCurrencyFormat;
19169
+ return currencyFormat !== roundFormat(currencyFormat);
19170
+ },
19023
19171
  };
19024
19172
  const formatCustomCurrency = {
19025
19173
  name: _lt("Custom currency"),
@@ -19028,19 +19176,37 @@
19028
19176
  };
19029
19177
  const formatNumberDate = {
19030
19178
  name: _lt("Date"),
19031
- description: "9/26/2008",
19179
+ description: (env) => {
19180
+ const locale = env.model.getters.getLocale();
19181
+ return formatValue(parseLiteral("9/26/2023", DEFAULT_LOCALE), {
19182
+ format: locale.dateFormat,
19183
+ locale,
19184
+ });
19185
+ },
19032
19186
  execute: (env) => setFormatter(env, env.model.getters.getLocale().dateFormat),
19033
19187
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().dateFormat),
19034
19188
  };
19035
19189
  const formatNumberTime = {
19036
19190
  name: _lt("Time"),
19037
- description: "10:43:00 PM",
19191
+ description: (env) => {
19192
+ const locale = env.model.getters.getLocale();
19193
+ return formatValue(parseLiteral("9/26/2023 10:43:00 PM", DEFAULT_LOCALE), {
19194
+ format: locale.timeFormat,
19195
+ locale,
19196
+ });
19197
+ },
19038
19198
  execute: (env) => setFormatter(env, env.model.getters.getLocale().timeFormat),
19039
19199
  isActive: (env) => isFormatSelected(env, env.model.getters.getLocale().timeFormat),
19040
19200
  };
19041
19201
  const formatNumberDateTime = {
19042
19202
  name: _lt("Date time"),
19043
- description: "9/26/2008 22:43:00",
19203
+ description: (env) => {
19204
+ const locale = env.model.getters.getLocale();
19205
+ return formatValue(parseLiteral("9/26/2023 22:43:00", DEFAULT_LOCALE), {
19206
+ format: locale.dateFormat + " " + locale.timeFormat,
19207
+ locale,
19208
+ });
19209
+ },
19044
19210
  execute: (env) => {
19045
19211
  const locale = env.model.getters.getLocale();
19046
19212
  setFormatter(env, locale.dateFormat + " " + locale.timeFormat);
@@ -19112,7 +19278,7 @@
19112
19278
  };
19113
19279
  const formatAlignmentHorizontal = {
19114
19280
  name: _lt("Horizontal align"),
19115
- icon: "o-spreadsheet-Icon.ALIGN_LEFT",
19281
+ icon: (env) => getHorizontalAlignmentIcon(env),
19116
19282
  };
19117
19283
  const formatAlignmentLeft = {
19118
19284
  name: _lt("Left"),
@@ -19137,46 +19303,50 @@
19137
19303
  };
19138
19304
  const formatAlignmentVertical = {
19139
19305
  name: _lt("Vertical align"),
19140
- icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19306
+ icon: (env) => getVerticalAlignmentIcon(env),
19141
19307
  };
19142
19308
  const formatAlignmentTop = {
19143
19309
  name: _lt("Top"),
19144
19310
  execute: (env) => setStyle(env, { verticalAlign: "top" }),
19145
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "top",
19311
+ isActive: (env) => getVerticalAlign(env) === "top",
19146
19312
  icon: "o-spreadsheet-Icon.ALIGN_TOP",
19147
19313
  };
19148
19314
  const formatAlignmentMiddle = {
19149
19315
  name: _lt("Middle"),
19150
19316
  execute: (env) => setStyle(env, { verticalAlign: "middle" }),
19151
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "middle",
19317
+ isActive: (env) => getVerticalAlign(env) === "middle",
19152
19318
  icon: "o-spreadsheet-Icon.ALIGN_MIDDLE",
19153
19319
  };
19154
19320
  const formatAlignmentBottom = {
19155
19321
  name: _lt("Bottom"),
19156
19322
  execute: (env) => setStyle(env, { verticalAlign: "bottom" }),
19157
- isActive: (env) => (env.model.getters.getCurrentStyle().verticalAlign || DEFAULT_VERTICAL_ALIGN) === "bottom",
19323
+ isActive: (env) => getVerticalAlign(env) === "bottom",
19158
19324
  icon: "o-spreadsheet-Icon.ALIGN_BOTTOM",
19159
19325
  };
19160
- const formatWrapping = {
19326
+ const formatWrappingIcon = {
19161
19327
  name: _lt("Wrapping"),
19162
19328
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19163
19329
  };
19330
+ const formatWrapping = {
19331
+ name: _lt("Wrapping"),
19332
+ icon: (env) => getWrapModeIcon(env),
19333
+ };
19164
19334
  const formatWrappingOverflow = {
19165
19335
  name: _lt("Overflow"),
19166
19336
  execute: (env) => setStyle(env, { wrapping: "overflow" }),
19167
- isActive: (env) => (env.model.getters.getCurrentStyle().wrapping || "overflow") === "overflow",
19337
+ isActive: (env) => getWrappingMode(env) === "overflow",
19168
19338
  icon: "o-spreadsheet-Icon.WRAPPING_OVERFLOW",
19169
19339
  };
19170
19340
  const formatWrappingWrap = {
19171
19341
  name: _lt("Wrap"),
19172
19342
  execute: (env) => setStyle(env, { wrapping: "wrap" }),
19173
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "wrap",
19343
+ isActive: (env) => getWrappingMode(env) === "wrap",
19174
19344
  icon: "o-spreadsheet-Icon.WRAPPING_WRAP",
19175
19345
  };
19176
19346
  const formatWrappingClip = {
19177
19347
  name: _lt("Clip"),
19178
19348
  execute: (env) => setStyle(env, { wrapping: "clip" }),
19179
- isActive: (env) => env.model.getters.getCurrentStyle().wrapping === "clip",
19349
+ isActive: (env) => getWrappingMode(env) === "clip",
19180
19350
  icon: "o-spreadsheet-Icon.WRAPPING_CLIP",
19181
19351
  };
19182
19352
  const textColor = {
@@ -19192,14 +19362,6 @@
19192
19362
  execute: OPEN_CF_SIDEPANEL_ACTION,
19193
19363
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
19194
19364
  };
19195
- const paintFormat = {
19196
- name: _lt("Paint Format"),
19197
- execute: (env) => env.model.dispatch("ACTIVATE_PAINT_FORMAT", {
19198
- target: env.model.getters.getSelectedZones(),
19199
- }),
19200
- icon: "o-spreadsheet-Icon.PAINT_FORMAT",
19201
- isActive: (env) => env.model.getters.isPaintingFormat(),
19202
- };
19203
19365
  const clearFormat = {
19204
19366
  name: _lt("Clear formatting"),
19205
19367
  description: "Ctrl+<",
@@ -19239,6 +19401,53 @@
19239
19401
  }
19240
19402
  const cell = env.model.getters.getActiveCell();
19241
19403
  return cell.defaultAlign;
19404
+ }
19405
+ function getVerticalAlign(env) {
19406
+ const style = env.model.getters.getCurrentStyle();
19407
+ if (style.verticalAlign) {
19408
+ return style.verticalAlign;
19409
+ }
19410
+ return DEFAULT_VERTICAL_ALIGN;
19411
+ }
19412
+ function getWrappingMode(env) {
19413
+ const style = env.model.getters.getCurrentStyle();
19414
+ if (style.wrapping) {
19415
+ return style.wrapping;
19416
+ }
19417
+ return DEFAULT_WRAPPING_MODE;
19418
+ }
19419
+ function getHorizontalAlignmentIcon(env) {
19420
+ const horizontalAlign = getHorizontalAlign(env);
19421
+ switch (horizontalAlign) {
19422
+ case "right":
19423
+ return "o-spreadsheet-Icon.ALIGN_RIGHT";
19424
+ case "center":
19425
+ return "o-spreadsheet-Icon.ALIGN_CENTER";
19426
+ default:
19427
+ return "o-spreadsheet-Icon.ALIGN_LEFT";
19428
+ }
19429
+ }
19430
+ function getVerticalAlignmentIcon(env) {
19431
+ const verticalAlign = getVerticalAlign(env);
19432
+ switch (verticalAlign) {
19433
+ case "top":
19434
+ return "o-spreadsheet-Icon.ALIGN_TOP";
19435
+ case "middle":
19436
+ return "o-spreadsheet-Icon.ALIGN_MIDDLE";
19437
+ default:
19438
+ return "o-spreadsheet-Icon.ALIGN_BOTTOM";
19439
+ }
19440
+ }
19441
+ function getWrapModeIcon(env) {
19442
+ const wrapMode = getWrappingMode(env);
19443
+ switch (wrapMode) {
19444
+ case "wrap":
19445
+ return "o-spreadsheet-Icon.WRAPPING_WRAP";
19446
+ case "clip":
19447
+ return "o-spreadsheet-Icon.WRAPPING_CLIP";
19448
+ default:
19449
+ return "o-spreadsheet-Icon.WRAPPING_OVERFLOW";
19450
+ }
19242
19451
  }
19243
19452
 
19244
19453
  var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
@@ -19270,6 +19479,7 @@
19270
19479
  formatAlignmentTop: formatAlignmentTop,
19271
19480
  formatAlignmentMiddle: formatAlignmentMiddle,
19272
19481
  formatAlignmentBottom: formatAlignmentBottom,
19482
+ formatWrappingIcon: formatWrappingIcon,
19273
19483
  formatWrapping: formatWrapping,
19274
19484
  formatWrappingOverflow: formatWrappingOverflow,
19275
19485
  formatWrappingWrap: formatWrappingWrap,
@@ -19277,7 +19487,6 @@
19277
19487
  textColor: textColor,
19278
19488
  fillColor: fillColor,
19279
19489
  formatCF: formatCF,
19280
- paintFormat: paintFormat,
19281
19490
  clearFormat: clearFormat
19282
19491
  });
19283
19492
 
@@ -20040,7 +20249,7 @@
20040
20249
  separator: true,
20041
20250
  })
20042
20251
  .addChild("format_wrapping", ["format"], {
20043
- ...formatWrapping,
20252
+ ...formatWrappingIcon,
20044
20253
  sequence: 80,
20045
20254
  separator: true,
20046
20255
  })
@@ -22330,17 +22539,45 @@
22330
22539
  }
22331
22540
  get formatProposals() {
22332
22541
  const currency = this.availableCurrencies[this.state.selectedCurrencyIndex];
22333
- const proposalBases = this.initProposalBases(currency.decimalPlaces);
22334
- const firstPosition = currency.position;
22335
- const secondPosition = currency.position === "before" ? "after" : "before";
22542
+ const position = currency.position;
22543
+ const opposite = currency.position === "before" ? "after" : "before";
22336
22544
  const symbol = this.state.currencySymbol.trim() ? this.state.currencySymbol : "";
22337
22545
  const code = this.state.currencyCode.trim() ? this.state.currencyCode : "";
22338
- return code || symbol
22339
- ? [
22340
- ...this.createFormatProposals(proposalBases, symbol, code, firstPosition),
22341
- ...this.createFormatProposals(proposalBases, symbol, code, secondPosition),
22342
- ]
22343
- : [];
22546
+ const decimalPlaces = currency.decimalPlaces;
22547
+ if (!symbol && !code) {
22548
+ return [];
22549
+ }
22550
+ const simple = symbol ? createCurrencyFormat({ symbol, position, decimalPlaces }) : "";
22551
+ const rounded = simple ? roundFormat(simple) : "";
22552
+ const simpleWithCode = createCurrencyFormat({ symbol, position, decimalPlaces, code });
22553
+ const roundedWithCode = roundFormat(simpleWithCode);
22554
+ const simpleOpposite = symbol
22555
+ ? createCurrencyFormat({ symbol, position: opposite, decimalPlaces })
22556
+ : "";
22557
+ const roundedOpposite = simpleOpposite ? roundFormat(simpleOpposite) : "";
22558
+ const simpleOppositeWithCode = createCurrencyFormat({
22559
+ symbol,
22560
+ position: opposite,
22561
+ decimalPlaces,
22562
+ code,
22563
+ });
22564
+ const roundedOppositeWithCode = roundFormat(simpleOppositeWithCode);
22565
+ const formats = new Set([
22566
+ rounded,
22567
+ simple,
22568
+ roundedWithCode,
22569
+ simpleWithCode,
22570
+ roundedOpposite,
22571
+ simpleOpposite,
22572
+ roundedOppositeWithCode,
22573
+ simpleOppositeWithCode,
22574
+ ]);
22575
+ return [...formats]
22576
+ .filter((format) => format !== "")
22577
+ .map((format) => ({
22578
+ format,
22579
+ example: formatValue(1000.0, { format, locale: this.env.model.getters.getLocale() }),
22580
+ }));
22344
22581
  }
22345
22582
  get isSameFormat() {
22346
22583
  const selectedFormat = this.formatProposals[this.state.selectedFormatIndex];
@@ -22397,41 +22634,6 @@
22397
22634
  initAvailableCurrencies() {
22398
22635
  this.state.selectedCurrencyIndex = 0;
22399
22636
  }
22400
- initProposalBases(decimalPlaces) {
22401
- const result = [{ format: "#,##0", example: "1,000" }];
22402
- const decimalRepresentation = decimalPlaces ? "." + "0".repeat(decimalPlaces) : "";
22403
- if (decimalRepresentation) {
22404
- result.push({
22405
- format: "#,##0" + decimalRepresentation,
22406
- example: "1,000" + decimalRepresentation,
22407
- });
22408
- }
22409
- return result;
22410
- }
22411
- createFormatProposals(proposalBases, symbol, code, position) {
22412
- let formatProposals = [];
22413
- // 1 - add proposal with symbol and without code
22414
- if (symbol) {
22415
- for (let base of proposalBases) {
22416
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, symbol));
22417
- }
22418
- }
22419
- // 2 - if code exist --> add more proposal with symbol and with code
22420
- if (code) {
22421
- for (let base of proposalBases) {
22422
- const expression = (position === "after" ? " " : "") + code + " " + symbol;
22423
- formatProposals.push(this.createFormatProposal(position, base.example, base.format, expression));
22424
- }
22425
- }
22426
- return formatProposals;
22427
- }
22428
- createFormatProposal(position, baseExample, formatBase, expression) {
22429
- const formatExpression = "[$" + expression + "]";
22430
- return {
22431
- example: position === "before" ? expression + baseExample : baseExample + expression,
22432
- format: position === "before" ? formatExpression + formatBase : formatBase + formatExpression,
22433
- };
22434
- }
22435
22637
  getCommonFormat() {
22436
22638
  const selectedZones = this.env.model.getters.getSelectedZones();
22437
22639
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -22488,6 +22690,7 @@
22488
22690
  state = owl.useState(this.initialState());
22489
22691
  debounceTimeoutId;
22490
22692
  showFormulaState = false;
22693
+ debouncedUpdateSearch;
22491
22694
  findAndReplaceRef = owl.useRef("findAndReplace");
22492
22695
  get hasSearchResult() {
22493
22696
  return this.env.model.getters.getCurrentSelectedMatchIndex() !== null;
@@ -22497,11 +22700,16 @@
22497
22700
  }
22498
22701
  setup() {
22499
22702
  this.showFormulaState = this.env.model.getters.shouldShowFormulas();
22703
+ this.debouncedUpdateSearch = debounce(this.updateSearch.bind(this), 200);
22500
22704
  owl.onMounted(() => this.focusInput());
22501
22705
  owl.onWillUnmount(() => {
22502
22706
  this.env.model.dispatch("CLEAR_SEARCH");
22503
22707
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.showFormulaState });
22504
22708
  });
22709
+ owl.useEffect(() => {
22710
+ this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
22711
+ this.searchFormulas();
22712
+ }, () => [this.env.model.getters.shouldShowFormulas()]);
22505
22713
  }
22506
22714
  onInput(ev) {
22507
22715
  this.state.toSearch = ev.target.value;
@@ -22521,10 +22729,6 @@
22521
22729
  this.replace();
22522
22730
  }
22523
22731
  }
22524
- onFocusSidePanel() {
22525
- this.state.searchOptions.searchFormulas = this.env.model.getters.shouldShowFormulas();
22526
- this.env.model.dispatch("REFRESH_SEARCH");
22527
- }
22528
22732
  searchFormulas() {
22529
22733
  this.env.model.dispatch("SET_FORMULA_VISIBILITY", {
22530
22734
  show: this.state.searchOptions.searchFormulas,
@@ -22543,13 +22747,6 @@
22543
22747
  searchOptions: this.state.searchOptions,
22544
22748
  });
22545
22749
  }
22546
- debouncedUpdateSearch() {
22547
- clearTimeout(this.debounceTimeoutId);
22548
- this.debounceTimeoutId = setTimeout(() => {
22549
- this.updateSearch();
22550
- this.debounceTimeoutId = undefined;
22551
- }, 400);
22552
- }
22553
22750
  replace() {
22554
22751
  this.env.model.dispatch("REPLACE_SEARCH", {
22555
22752
  replaceWith: this.state.replaceWith,
@@ -23234,7 +23431,7 @@
23234
23431
  selection.removeAllRanges();
23235
23432
  selection.addRange(range);
23236
23433
  }
23237
- if (start == end && start === 0) {
23434
+ if (start === end && start === 0) {
23238
23435
  range.setStart(this.el, 0);
23239
23436
  range.setEnd(this.el, 0);
23240
23437
  }
@@ -23487,7 +23684,7 @@
23487
23684
  }, 0);
23488
23685
  }
23489
23686
  }
23490
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
23687
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
23491
23688
  usedCharacters++;
23492
23689
  }
23493
23690
  return usedCharacters;
@@ -25034,6 +25231,14 @@
25034
25231
  };
25035
25232
  }
25036
25233
 
25234
+ const CURSOR_SVG = /*xml*/ `
25235
+ <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>
25236
+ `;
25237
+ css /* scss */ `
25238
+ .o-paint-format-cursor {
25239
+ cursor: url("data:image/svg+xml,${encodeURIComponent(CURSOR_SVG)}"), auto;
25240
+ }
25241
+ `;
25037
25242
  function useCellHovered(env, gridRef, callback) {
25038
25243
  let hoveredPosition = {
25039
25244
  col: undefined,
@@ -25157,6 +25362,12 @@
25157
25362
  }
25158
25363
  return this.gridOverlay.el;
25159
25364
  }
25365
+ get style() {
25366
+ return this.props.gridOverlayDimensions;
25367
+ }
25368
+ get isPaintingFormat() {
25369
+ return this.env.model.getters.isPaintingFormat();
25370
+ }
25160
25371
  onMouseDown(ev) {
25161
25372
  if (ev.button > 0) {
25162
25373
  // not main button, probably a context menu
@@ -26334,6 +26545,9 @@
26334
26545
  else if (this.menuState.isOpen) {
26335
26546
  this.closeMenu();
26336
26547
  }
26548
+ else if (this.env.model.getters.isPaintingFormat()) {
26549
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
26550
+ }
26337
26551
  else {
26338
26552
  this.env.model.dispatch("CLEAN_CLIPBOARD_HIGHLIGHT");
26339
26553
  }
@@ -27536,8 +27750,8 @@
27536
27750
  throw new Error("Multiple escaped blocks in format");
27537
27751
  }
27538
27752
  convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
27539
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
27540
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
27753
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ === ignore width of next char for align purposes. Not supported ATM
27754
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * === repeat next character enough to fill the line. Not supported ATM
27541
27755
  convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
27542
27756
  convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
27543
27757
  if (isXlsxDateFormat(convertedFormat)) {
@@ -28829,29 +29043,26 @@
28829
29043
  return new XMLString(concat(str));
28830
29044
  }
28831
29045
  /**
28832
- * Removes the namespace of all the xml tags in the string.
29046
+ * Removes the escaped namespace of all the xml tags in the string.
28833
29047
  *
28834
- * Eg. : "ns:test a" => "test a"
29048
+ * Eg. : "NAMESPACEnsNAMESPACEtest a" => "test a"
28835
29049
  */
28836
- function removeNamespaces(query) {
28837
- return query.replace(/[a-z0-9]+:(?=[a-z0-9]+)/gi, "");
29050
+ function removeTagEscapedNamespaces(tag) {
29051
+ return tag.replace(/NAMESPACE.*NAMESPACE(.*)/, "$1");
28838
29052
  }
28839
29053
  /**
28840
- * Escape the namespace's colons of all the xml tags in the string.
29054
+ * Encase the namespaces in the element's tags with NAMESPACE string
28841
29055
  *
28842
- * Eg. : "ns:test a" => "ns\\:test a"
28843
- */
28844
- function escapeNamespaces(query) {
28845
- return query.replace(/([a-z0-9]+):(?=[a-z0-9]+)/gi, "$1\\:");
28846
- }
28847
- /**
28848
- * Return true if the querySelector ignores the namespaces when searching for a tag in the DOM.
29056
+ * e.g. <x:foo> becomes <NAMESPACExNAMESPACEFoo>
28849
29057
  *
28850
- * Should return true if it's running on a browser, and false if it's running on jest (jsdom).
29058
+ * That's useful because namespaces aren't supported by the HTML specification, so it's arbitrary whether a HTML parser/querySelector
29059
+ * implementation will support namespaces in the tags or not.
28851
29060
  */
28852
- function areNamespaceIgnoredByQuerySelector() {
28853
- const doc = new DOMParser().parseFromString("<t:test xmlns:t='a'/>", "text/xml");
28854
- return doc.querySelector("test") !== null;
29061
+ function escapeTagNamespaces(str) {
29062
+ return str.replaceAll(/(<\/?)([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "$1" + "NAMESPACE" + "$2" + "NAMESPACE" + "$3");
29063
+ }
29064
+ function escapeQueryNameSpaces(query) {
29065
+ return query.replaceAll(/([a-zA-Z0-9]+):([a-zA-Z0-9]+)/g, "NAMESPACE" + "$1" + "NAMESPACE" + "$2");
28855
29066
  }
28856
29067
 
28857
29068
  class AttributeValue {
@@ -28881,14 +29092,20 @@
28881
29092
  // The xml file we are currently parsing. We should have one Extractor class by XLSXImportFile, but
28882
29093
  // the XLSXImportFile contains both the main .xml file, and the .rels file
28883
29094
  currentFile = undefined;
28884
- // If the parser querySelector() implementation ignores tag namespaces or not
28885
- areNamespaceIgnored;
29095
+ /**
29096
+ * /!\ Important : There should be no namespaces in the tags of the XML files.
29097
+ *
29098
+ * This class use native querySelector and querySelectorAll, that's used for HTML (not XML). These aren't supposed to
29099
+ * handled namespaces, as they are not supported by the HTML specification. Some implementations (most browsers) do
29100
+ * actually support namespaces, but some don't (e.g. jsdom).
29101
+ *
29102
+ * The namespace should be escaped as with NAMESPACE string (eg. <t:foo> => <NAMESPACEtNAMESPACEfoo>).
29103
+ */
28886
29104
  constructor(rootFile, xlsxStructure, warningManager) {
28887
29105
  this.rootFile = rootFile;
28888
29106
  this.currentFile = rootFile.file.fileName;
28889
29107
  this.xlsxFileStructure = xlsxStructure;
28890
29108
  this.warningManager = warningManager;
28891
- this.areNamespaceIgnored = areNamespaceIgnoredByQuerySelector();
28892
29109
  this.relationships = {};
28893
29110
  if (rootFile.rels) {
28894
29111
  this.extractRelationships(rootFile.rels).map((rel) => {
@@ -29114,29 +29331,13 @@
29114
29331
  throw new Error("Cannot find target file");
29115
29332
  return f;
29116
29333
  }
29117
- /**
29118
- * Wrapper of querySelector, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29119
- *
29120
- * Why we need to do this :
29121
- * - For an XML "<t:test />"
29122
- * - on Jest(jsdom) : xml.querySelector("test") == null, xml.querySelector("t\\:test") == <t:test />
29123
- * - on Browser : xml.querySelector("test") == <t:test />, xml.querySelector("t\\:test") == null
29124
- */
29125
29334
  querySelector(element, query) {
29126
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29127
- return element.querySelector(query);
29335
+ const escapedQuery = escapeQueryNameSpaces(query);
29336
+ return element.querySelector(escapedQuery);
29128
29337
  }
29129
- /**
29130
- * Wrapper of querySelectorAll, but we'll remove the namespaces from the query if areNamespacesIgnored is true.
29131
- *
29132
- * Why we need to do this :
29133
- * - For an XML "<t:test />"
29134
- * - on Jest(jsdom) : xml.querySelectorAll("test") == [], xml.querySelectorAll("t\\:test") == [<t:test />]
29135
- * - on Browser : xml.querySelectorAll("test") == [<t:test />], xml.querySelectorAll("t\\:test") == []
29136
- */
29137
29338
  querySelectorAll(element, query) {
29138
- query = this.areNamespaceIgnored ? removeNamespaces(query) : escapeNamespaces(query);
29139
- return element.querySelectorAll(query);
29339
+ const escapedQuery = escapeQueryNameSpaces(query);
29340
+ return element.querySelectorAll(escapedQuery);
29140
29341
  }
29141
29342
  /**
29142
29343
  * Get a color from its id in the Theme's colorScheme.
@@ -29376,7 +29577,7 @@
29376
29577
  throw new Error("Missing plot area in the chart definition.");
29377
29578
  }
29378
29579
  for (let child of plotAreaElement.children) {
29379
- const tag = removeNamespaces(child.tagName);
29580
+ const tag = removeTagEscapedNamespaces(child.tagName);
29380
29581
  if (XLSX_CHART_TYPES.some((chartType) => chartType === tag)) {
29381
29582
  return tag;
29382
29583
  }
@@ -29388,7 +29589,7 @@
29388
29589
  class XlsxFigureExtractor extends XlsxBaseExtractor {
29389
29590
  extractFigures() {
29390
29591
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "xdr:wsDr", children: true }, (figureElement) => {
29391
- const anchorType = removeNamespaces(figureElement.tagName);
29592
+ const anchorType = removeTagEscapedNamespaces(figureElement.tagName);
29392
29593
  if (anchorType !== "twoCellAnchor") {
29393
29594
  throw new Error("Only twoCellAnchor are supported for xlsx drawings.");
29394
29595
  }
@@ -30022,7 +30223,8 @@
30022
30223
  for (let key of Object.keys(files)) {
30023
30224
  // Random files can be in xlsx (like a bin file for printer settings)
30024
30225
  if (key.endsWith(".xml") || key.endsWith(".rels")) {
30025
- this.xmls[key] = parseXML(new XMLString(files[key]));
30226
+ const contentString = escapeTagNamespaces(files[key]);
30227
+ this.xmls[key] = parseXML(new XMLString(contentString));
30026
30228
  }
30027
30229
  else if (key.includes("media/image")) {
30028
30230
  this.images.push({
@@ -31089,7 +31291,7 @@
31089
31291
  }
31090
31292
  /**
31091
31293
  * Set the borders of a cell.
31092
- * It overrides the current border if override == true.
31294
+ * It overrides the current border if override === true.
31093
31295
  */
31094
31296
  setBorder(sheetId, col, row, border, override = true) {
31095
31297
  if (override || !this.borders?.[sheetId]?.[col]?.[row]?.vertical) {
@@ -32359,7 +32561,9 @@
32359
32561
  const numHeader = this.getters.getNumberRows(sheetId);
32360
32562
  let gridHeight = 0;
32361
32563
  for (let i = 0; i < numHeader; i++) {
32362
- gridHeight += this.getters.getRowSize(sheetId, i);
32564
+ // TODO : since the row size is an UI value now, this doesn't work anymore. Using the default cell height is
32565
+ // a temporary solution at best, but is broken.
32566
+ gridHeight += this.getters.getUserRowSize(sheetId, i) || DEFAULT_CELL_HEIGHT;
32363
32567
  }
32364
32568
  const figures = this.getters.getFigures(sheetId);
32365
32569
  for (const figure of figures) {
@@ -32785,33 +32989,15 @@
32785
32989
  }
32786
32990
 
32787
32991
  class HeaderSizePlugin extends CorePlugin {
32788
- static getters = ["getRowSize", "getColSize"];
32992
+ static getters = ["getUserRowSize", "getColSize"];
32789
32993
  sizes = {};
32790
32994
  handle(cmd) {
32791
32995
  switch (cmd.type) {
32792
32996
  case "CREATE_SHEET": {
32793
- const computedSizes = this.computeSheetSizes(cmd.sheetId);
32794
- const sizes = {
32795
- COL: computedSizes.COL.map((size) => ({
32796
- manualSize: undefined,
32797
- computedSize: lazy(size),
32798
- })),
32799
- ROW: computedSizes.ROW.map((size) => ({
32800
- manualSize: undefined,
32801
- computedSize: lazy(size),
32802
- })),
32803
- };
32804
- this.history.update("sizes", cmd.sheetId, sizes);
32997
+ this.history.update("sizes", cmd.sheetId, { COL: [], ROW: [] });
32805
32998
  break;
32806
32999
  }
32807
33000
  case "DUPLICATE_SHEET":
32808
- // make sure the values are computed in case the original sheet is deleted
32809
- for (const row of this.sizes[cmd.sheetId].ROW) {
32810
- row.computedSize();
32811
- }
32812
- for (const col of this.sizes[cmd.sheetId].COL) {
32813
- col.computedSize();
32814
- }
32815
33001
  this.history.update("sizes", cmd.sheetIdTo, deepCopy(this.sizes[cmd.sheetId]));
32816
33002
  break;
32817
33003
  case "DELETE_SHEET":
@@ -32820,21 +33006,8 @@
32820
33006
  this.history.update("sizes", sizes);
32821
33007
  break;
32822
33008
  case "REMOVE_COLUMNS_ROWS": {
32823
- let sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
32824
- for (let headerIndex of [...cmd.elements].sort((a, b) => b - a)) {
32825
- sizes.splice(headerIndex, 1);
32826
- }
32827
- const min = Math.min(...cmd.elements);
32828
- sizes = sizes.map((size, row) => {
32829
- if (cmd.dimension === "ROW" && row >= min) {
32830
- // invalidate sizes
32831
- return {
32832
- manualSize: size.manualSize,
32833
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32834
- };
32835
- }
32836
- return size;
32837
- });
33009
+ const arr = this.sizes[cmd.sheetId][cmd.dimension];
33010
+ const sizes = removeIndexesFromArray(arr, cmd.elements);
32838
33011
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32839
33012
  break;
32840
33013
  }
@@ -32843,51 +33016,18 @@
32843
33016
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
32844
33017
  const baseSize = sizes[cmd.base];
32845
33018
  sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
32846
- sizes = sizes.map((size, row) => {
32847
- if (cmd.dimension === "ROW" && row > cmd.base + cmd.quantity) {
32848
- // invalidate sizes
32849
- return {
32850
- manualSize: size.manualSize,
32851
- computedSize: lazy(() => this.getRowTallestCellSize(cmd.sheetId, row)),
32852
- };
32853
- }
32854
- return size;
32855
- });
32856
33019
  this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
32857
33020
  break;
32858
33021
  }
32859
33022
  case "RESIZE_COLUMNS_ROWS":
32860
- for (let el of cmd.elements) {
32861
- if (cmd.dimension === "ROW") {
32862
- const height = this.getRowTallestCellSize(cmd.sheetId, el);
32863
- const size = height;
32864
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32865
- manualSize: cmd.size || undefined,
32866
- computedSize: lazy(size),
32867
- });
32868
- }
32869
- else {
32870
- this.history.update("sizes", cmd.sheetId, cmd.dimension, el, {
32871
- manualSize: cmd.size || undefined,
32872
- computedSize: lazy(cmd.size || DEFAULT_CELL_WIDTH),
32873
- });
33023
+ if (cmd.dimension === "ROW") {
33024
+ for (const el of cmd.elements) {
33025
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32874
33026
  }
32875
33027
  }
32876
- break;
32877
- case "UPDATE_CELL":
32878
- if (!this.sizes[cmd.sheetId]?.["ROW"]?.[cmd.row]?.manualSize) {
32879
- const { sheetId, row } = cmd;
32880
- this.history.update("sizes", sheetId, "ROW", row, "computedSize", lazy(() => this.getRowTallestCellSize(sheetId, row)));
32881
- }
32882
- break;
32883
- case "ADD_MERGE":
32884
- case "REMOVE_MERGE":
32885
- for (let target of cmd.target) {
32886
- for (let row of range(target.top, target.bottom + 1)) {
32887
- const rowHeight = this.getRowTallestCellSize(cmd.sheetId, row);
32888
- if (rowHeight !== this.getRowSize(cmd.sheetId, row)) {
32889
- this.history.update("sizes", cmd.sheetId, "ROW", row, "computedSize", lazy(rowHeight));
32890
- }
33028
+ else {
33029
+ for (const el of cmd.elements) {
33030
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, el, cmd.size || undefined);
32891
33031
  }
32892
33032
  }
32893
33033
  break;
@@ -32895,94 +33035,29 @@
32895
33035
  return;
32896
33036
  }
32897
33037
  getColSize(sheetId, index) {
32898
- return this.getHeaderSize(sheetId, "COL", index);
32899
- }
32900
- getRowSize(sheetId, index) {
32901
- return this.getHeaderSize(sheetId, "ROW", index);
32902
- }
32903
- getHeaderSize(sheetId, dimension, index) {
32904
- return Math.round(this.sizes[sheetId]?.[dimension][index]?.manualSize ||
32905
- this.sizes[sheetId]?.[dimension][index]?.computedSize() ||
32906
- this.getDefaultHeaderSize(dimension));
32907
- }
32908
- computeSheetSizes(sheetId) {
32909
- const sizes = { COL: [], ROW: [] };
32910
- for (let col of range(0, this.getters.getNumberCols(sheetId))) {
32911
- sizes.COL.push(this.getHeaderSize(sheetId, "COL", col));
32912
- }
32913
- for (let row of range(0, this.getters.getNumberRows(sheetId))) {
32914
- let rowSize = this.sizes[sheetId]?.["ROW"]?.[row].manualSize;
32915
- if (!rowSize) {
32916
- const height = this.getRowTallestCellSize(sheetId, row);
32917
- rowSize = height;
32918
- }
32919
- sizes.ROW.push(rowSize);
32920
- }
32921
- return sizes;
33038
+ return Math.round(this.sizes[sheetId]?.["COL"][index] || DEFAULT_CELL_WIDTH);
32922
33039
  }
32923
- getDefaultHeaderSize(dimension) {
32924
- return dimension === "COL" ? DEFAULT_CELL_WIDTH : DEFAULT_CELL_HEIGHT;
32925
- }
32926
- /**
32927
- * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
32928
- * merge, or the height of the cell computed based on its font size.
32929
- */
32930
- getCellHeight(position) {
32931
- const merge = this.getters.getMerge(position);
32932
- if (merge && merge.bottom !== merge.top) {
32933
- return DEFAULT_CELL_HEIGHT;
32934
- }
32935
- const cell = this.getters.getCell(position);
32936
- return getDefaultCellHeight(cell);
32937
- }
32938
- /**
32939
- * Get the tallest cell of a row and its size.
32940
- *
32941
- * The tallest cell of the row correspond to the cell with the biggest font size,
32942
- * and that is not part of a multi-line merge.
32943
- */
32944
- getRowTallestCellSize(sheetId, row) {
32945
- const cellIds = this.getters.getRowCells(sheetId, row);
32946
- let maxHeight = 0;
32947
- for (let i = 0; i < cellIds.length; i++) {
32948
- const cell = this.getters.getCellById(cellIds[i]);
32949
- if (!cell)
32950
- continue;
32951
- const position = this.getters.getCellPosition(cell.id);
32952
- const cellHeight = this.getCellHeight(position);
32953
- if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
32954
- maxHeight = cellHeight;
32955
- }
32956
- }
32957
- if (maxHeight <= DEFAULT_CELL_HEIGHT) {
32958
- return DEFAULT_CELL_HEIGHT;
32959
- }
32960
- return maxHeight;
33040
+ getUserRowSize(sheetId, index) {
33041
+ const rowSize = this.sizes[sheetId]?.["ROW"][index];
33042
+ return rowSize ? Math.round(rowSize) : undefined;
32961
33043
  }
32962
33044
  import(data) {
32963
33045
  for (let sheet of data.sheets) {
32964
- const manualSizes = { COL: [], ROW: [] };
33046
+ const sizes = {
33047
+ COL: Array(sheet.colNumber).fill(undefined),
33048
+ ROW: Array(sheet.rowNumber).fill(undefined),
33049
+ };
32965
33050
  for (let [rowIndex, row] of Object.entries(sheet.rows)) {
32966
33051
  if (row.size) {
32967
- manualSizes["ROW"][rowIndex] = row.size;
33052
+ sizes["ROW"][rowIndex] = row.size;
32968
33053
  }
32969
33054
  }
32970
33055
  for (let [colIndex, col] of Object.entries(sheet.cols)) {
32971
33056
  if (col.size) {
32972
- manualSizes["COL"][colIndex] = col.size;
32973
- }
32974
- }
32975
- const computedSizes = this.computeSheetSizes(sheet.id);
32976
- this.sizes[sheet.id] = {
32977
- COL: computedSizes.COL.map((size, i) => ({
32978
- manualSize: manualSizes.COL[i],
32979
- computedSize: lazy(size),
32980
- })),
32981
- ROW: computedSizes.ROW.map((size, i) => ({
32982
- manualSize: manualSizes.ROW[i],
32983
- computedSize: lazy(size),
32984
- })),
32985
- };
33057
+ sizes["COL"][colIndex] = col.size;
33058
+ }
33059
+ }
33060
+ this.sizes[sheet.id] = sizes;
32986
33061
  }
32987
33062
  return;
32988
33063
  }
@@ -33003,9 +33078,12 @@
33003
33078
  if (sheet.rows === undefined) {
33004
33079
  sheet.rows = {};
33005
33080
  }
33006
- for (let row of range(0, this.getters.getNumberRows(sheet.id))) {
33007
- if (exportDefaults || this.sizes[sheet.id]["ROW"][row]?.manualSize) {
33008
- sheet.rows[row] = { ...sheet.rows[row], size: this.getRowSize(sheet.id, row) };
33081
+ for (const row of range(0, this.getters.getNumberRows(sheet.id))) {
33082
+ if (exportDefaults || this.sizes[sheet.id]["ROW"][row]) {
33083
+ sheet.rows[row] = {
33084
+ ...sheet.rows[row],
33085
+ size: this.getUserRowSize(sheet.id, row) ?? DEFAULT_CELL_HEIGHT,
33086
+ };
33009
33087
  }
33010
33088
  }
33011
33089
  // Export col sizes
@@ -33013,7 +33091,7 @@
33013
33091
  sheet.cols = {};
33014
33092
  }
33015
33093
  for (let col of range(0, this.getters.getNumberCols(sheet.id))) {
33016
- if (exportDefaults || this.sizes[sheet.id]["COL"][col]?.manualSize) {
33094
+ if (exportDefaults || this.sizes[sheet.id]["COL"][col]) {
33017
33095
  sheet.cols[col] = { ...sheet.cols[col], size: this.getColSize(sheet.id, col) };
33018
33096
  }
33019
33097
  }
@@ -36569,7 +36647,7 @@
36569
36647
  case "ContainsText":
36570
36648
  return cell.value.toString().indexOf(values[0].toString()) > -1;
36571
36649
  case "NotContains":
36572
- return !cell.value || cell.value.toString().indexOf(values[0].toString()) == -1;
36650
+ return !cell.value || cell.value.toString().indexOf(values[0].toString()) === -1;
36573
36651
  case "GreaterThan":
36574
36652
  return cell.value > values[0];
36575
36653
  case "GreaterThanOrEqual":
@@ -36596,6 +36674,156 @@
36596
36674
  };
36597
36675
  }
36598
36676
 
36677
+ class UIRowSizePlugin extends UIPlugin {
36678
+ static getters = ["getRowSize"];
36679
+ tallestCellInRow = {};
36680
+ ctx = document.createElement("canvas").getContext("2d");
36681
+ handle(cmd) {
36682
+ switch (cmd.type) {
36683
+ case "START":
36684
+ for (const sheetId of this.getters.getSheetIds()) {
36685
+ this.initializeSheet(sheetId);
36686
+ }
36687
+ break;
36688
+ case "CREATE_SHEET": {
36689
+ this.initializeSheet(cmd.sheetId);
36690
+ break;
36691
+ }
36692
+ case "DUPLICATE_SHEET": {
36693
+ const tallestCells = deepCopy(this.tallestCellInRow[cmd.sheetId]);
36694
+ this.history.update("tallestCellInRow", cmd.sheetIdTo, tallestCells);
36695
+ break;
36696
+ }
36697
+ case "DELETE_SHEET":
36698
+ const tallestCells = { ...this.tallestCellInRow };
36699
+ delete tallestCells[cmd.sheetId];
36700
+ this.history.update("tallestCellInRow", tallestCells);
36701
+ break;
36702
+ case "REMOVE_COLUMNS_ROWS": {
36703
+ if (cmd.dimension === "COL") {
36704
+ return;
36705
+ }
36706
+ const tallestCells = removeIndexesFromArray(this.tallestCellInRow[cmd.sheetId], cmd.elements);
36707
+ this.history.update("tallestCellInRow", cmd.sheetId, tallestCells);
36708
+ break;
36709
+ }
36710
+ case "ADD_COLUMNS_ROWS": {
36711
+ if (cmd.dimension === "COL") {
36712
+ return;
36713
+ }
36714
+ const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
36715
+ const newCells = Array(cmd.quantity).fill(undefined);
36716
+ const newTallestCells = insertItemsAtIndex(this.tallestCellInRow[cmd.sheetId], newCells, addIndex);
36717
+ this.history.update("tallestCellInRow", cmd.sheetId, newTallestCells);
36718
+ break;
36719
+ }
36720
+ case "RESIZE_COLUMNS_ROWS":
36721
+ {
36722
+ const sheetId = cmd.sheetId;
36723
+ if (cmd.dimension === "ROW") {
36724
+ for (const row of cmd.elements) {
36725
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36726
+ this.history.update("tallestCellInRow", sheetId, row, tallestCell);
36727
+ }
36728
+ }
36729
+ else {
36730
+ // Recompute row heights on col size change, they might have changed because of wrapped text
36731
+ for (const row of range(0, this.getters.getNumberRows(sheetId))) {
36732
+ for (const col of cmd.elements) {
36733
+ this.updateRowSizeForCellChange(sheetId, row, col);
36734
+ }
36735
+ }
36736
+ }
36737
+ }
36738
+ break;
36739
+ case "UPDATE_CELL":
36740
+ this.updateRowSizeForCellChange(cmd.sheetId, cmd.row, cmd.col);
36741
+ break;
36742
+ case "ADD_MERGE":
36743
+ case "REMOVE_MERGE":
36744
+ for (const target of cmd.target) {
36745
+ for (const position of positions(target)) {
36746
+ this.updateRowSizeForCellChange(cmd.sheetId, position.row, position.col);
36747
+ }
36748
+ }
36749
+ }
36750
+ return;
36751
+ }
36752
+ getRowSize(sheetId, row) {
36753
+ return Math.round(this.getters.getUserRowSize(sheetId, row) ??
36754
+ this.tallestCellInRow[sheetId][row]?.size ??
36755
+ DEFAULT_CELL_HEIGHT);
36756
+ }
36757
+ updateRowSizeForCellChange(sheetId, row, col) {
36758
+ const tallestCellInRow = this.tallestCellInRow[sheetId]?.[row];
36759
+ if (tallestCellInRow?.cell.col === col) {
36760
+ const newTallestCell = this.getRowTallestCell(sheetId, row);
36761
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36762
+ }
36763
+ const updatedCellHeight = this.getCellHeight({ sheetId, col, row });
36764
+ if (updatedCellHeight <= DEFAULT_CELL_HEIGHT) {
36765
+ return;
36766
+ }
36767
+ if ((!tallestCellInRow && updatedCellHeight > DEFAULT_CELL_HEIGHT) ||
36768
+ (tallestCellInRow && updatedCellHeight > tallestCellInRow.size)) {
36769
+ const newTallestCell = { cell: { sheetId, col, row }, size: updatedCellHeight };
36770
+ this.history.update("tallestCellInRow", sheetId, row, newTallestCell);
36771
+ }
36772
+ }
36773
+ initializeSheet(sheetId) {
36774
+ const tallestCells = [];
36775
+ for (let row = 0; row < this.getters.getNumberRows(sheetId); row++) {
36776
+ const tallestCell = this.getRowTallestCell(sheetId, row);
36777
+ tallestCells.push(tallestCell);
36778
+ }
36779
+ this.history.update("tallestCellInRow", sheetId, tallestCells);
36780
+ }
36781
+ /**
36782
+ * Return the height the cell should have in the sheet, which is either DEFAULT_CELL_HEIGHT if the cell is in a multi-row
36783
+ * merge, or the height of the cell computed based on its style/content.
36784
+ */
36785
+ getCellHeight(position) {
36786
+ if (this.isInMultiRowMerge(position)) {
36787
+ return DEFAULT_CELL_HEIGHT;
36788
+ }
36789
+ const cell = this.getters.getCell(position);
36790
+ const colSize = this.getters.getColSize(position.sheetId, position.col);
36791
+ return getDefaultCellHeight(this.ctx, cell, colSize);
36792
+ }
36793
+ isInMultiRowMerge(position) {
36794
+ const merge = this.getters.getMerge(position);
36795
+ return !!merge && merge.bottom !== merge.top;
36796
+ }
36797
+ /**
36798
+ * Get the tallest cell of a row and its size.
36799
+ */
36800
+ getRowTallestCell(sheetId, row) {
36801
+ const userRowSize = this.getters.getUserRowSize(sheetId, row);
36802
+ if (userRowSize !== undefined) {
36803
+ return undefined;
36804
+ }
36805
+ const cellIds = this.getters.getRowCells(sheetId, row);
36806
+ let maxHeight = 0;
36807
+ let tallestCell = undefined;
36808
+ for (let i = 0; i < cellIds.length; i++) {
36809
+ const cell = this.getters.getCellById(cellIds[i]);
36810
+ if (!cell) {
36811
+ continue;
36812
+ }
36813
+ const position = this.getters.getCellPosition(cell.id);
36814
+ const cellHeight = this.getCellHeight(position);
36815
+ if (cellHeight > maxHeight && cellHeight > DEFAULT_CELL_HEIGHT) {
36816
+ maxHeight = cellHeight;
36817
+ tallestCell = { cell: position, size: cellHeight };
36818
+ }
36819
+ }
36820
+ if (tallestCell && tallestCell.size > DEFAULT_CELL_HEIGHT) {
36821
+ return tallestCell;
36822
+ }
36823
+ return undefined;
36824
+ }
36825
+ }
36826
+
36599
36827
  /**
36600
36828
  * This plugin manage the autofill.
36601
36829
  *
@@ -38297,6 +38525,7 @@
38297
38525
  searchFormulas: false,
38298
38526
  };
38299
38527
  toSearch = "";
38528
+ isSearchDirty = false;
38300
38529
  // ---------------------------------------------------------------------------
38301
38530
  // Command Handling
38302
38531
  // ---------------------------------------------------------------------------
@@ -38324,14 +38553,21 @@
38324
38553
  case "REDO":
38325
38554
  case "REMOVE_COLUMNS_ROWS":
38326
38555
  case "ADD_COLUMNS_ROWS":
38327
- this.clearSearch();
38556
+ case "EVALUATE_CELLS":
38557
+ case "UPDATE_CELL":
38558
+ this.isSearchDirty = true;
38328
38559
  break;
38329
38560
  case "ACTIVATE_SHEET":
38330
- case "REFRESH_SEARCH":
38331
38561
  this.refreshSearch();
38332
38562
  break;
38333
38563
  }
38334
38564
  }
38565
+ finalize() {
38566
+ if (this.isSearchDirty) {
38567
+ this.refreshSearch();
38568
+ this.isSearchDirty = false;
38569
+ }
38570
+ }
38335
38571
  // ---------------------------------------------------------------------------
38336
38572
  // Getters
38337
38573
  // ---------------------------------------------------------------------------
@@ -38387,6 +38623,11 @@
38387
38623
  if (this.toSearch) {
38388
38624
  for (const cell of Object.values(cells)) {
38389
38625
  const { col, row } = this.getters.getCellPosition(cell.id);
38626
+ const isColHidden = this.getters.isColHidden(sheetId, col);
38627
+ const isRowHidden = this.getters.isRowHidden(sheetId, row);
38628
+ if (isColHidden || isRowHidden) {
38629
+ continue;
38630
+ }
38390
38631
  if (cell &&
38391
38632
  this.currentSearchRegex &&
38392
38633
  this.currentSearchRegex.test(this.getSearchableString({ sheetId, col, row }))) {
@@ -40029,10 +40270,8 @@
40029
40270
  }
40030
40271
  updateCellCommands.push(newCellValues);
40031
40272
  }
40032
- for (const cmd of updateCellCommands) {
40033
- this.dispatch("UPDATE_CELL", cmd);
40034
- }
40035
40273
  }
40274
+ updateCellCommands.forEach((cmdPayload) => this.dispatch("UPDATE_CELL", cmdPayload));
40036
40275
  }
40037
40276
  /**
40038
40277
  * Return the distances between main merge cells in the zone.
@@ -40133,24 +40372,28 @@
40133
40372
  // Getters
40134
40373
  // ---------------------------------------------------------------------------
40135
40374
  getCellWidth(position) {
40136
- const text = this.getCellText(position);
40137
40375
  const style = this.getters.getCellComputedStyle(position);
40138
- const multiLineText = text.split(NEWLINE);
40139
- let contentWidth = Math.max(...multiLineText.map((line) => this.getTextWidth(line, style)));
40376
+ let contentWidth = 0;
40377
+ const content = this.getters.getEvaluatedCell(position).formattedValue;
40378
+ if (content) {
40379
+ const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
40380
+ contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
40381
+ }
40140
40382
  const icon = this.getters.getConditionalIcon(position);
40141
40383
  if (icon) {
40142
- contentWidth += computeIconWidth(this.getters.getCellStyle(position));
40384
+ contentWidth += computeIconWidth(style);
40143
40385
  }
40144
40386
  const isFilterHeader = this.getters.isFilterHeader(position);
40145
40387
  if (isFilterHeader) {
40146
40388
  contentWidth += ICON_EDGE_LENGTH + FILTER_ICON_MARGIN;
40147
40389
  }
40148
- if (contentWidth > 0) {
40149
- contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40150
- if (this.getters.getCellStyle(position).wrapping === "wrap") {
40151
- const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40152
- return Math.min(colWidth, contentWidth);
40153
- }
40390
+ if (contentWidth === 0) {
40391
+ return 0;
40392
+ }
40393
+ contentWidth += 2 * PADDING_AUTORESIZE_HORIZONTAL;
40394
+ if (style.wrapping === "wrap") {
40395
+ const colWidth = this.getters.getColSize(this.getters.getActiveSheetId(), position.col);
40396
+ return Math.min(colWidth, contentWidth);
40154
40397
  }
40155
40398
  return contentWidth;
40156
40399
  }
@@ -40173,56 +40416,7 @@
40173
40416
  getCellMultiLineText(position, width) {
40174
40417
  const style = this.getters.getCellStyle(position);
40175
40418
  const text = this.getters.getCellText(position, this.getters.shouldShowFormulas());
40176
- const brokenText = [];
40177
- for (const line of text.split("\n")) {
40178
- const words = line.split(" ");
40179
- if (!width) {
40180
- brokenText.push(line);
40181
- continue;
40182
- }
40183
- let textLine = "";
40184
- let availableWidth = width;
40185
- for (let word of words) {
40186
- const splitWord = this.splitWordToSpecificWidth(this.ctx, word, width, style);
40187
- const lastPart = splitWord.pop();
40188
- const lastPartWidth = computeTextWidth(this.ctx, lastPart, style);
40189
- // At this step: "splitWord" is an array composed of parts of word whose
40190
- // length is at most equal to "width".
40191
- // Last part contains the end of the word.
40192
- // Note that: When word length is less than width, then lastPart is equal
40193
- // to word and splitWord is empty
40194
- if (splitWord.length) {
40195
- if (textLine !== "") {
40196
- brokenText.push(textLine);
40197
- textLine = "";
40198
- availableWidth = width;
40199
- }
40200
- splitWord.forEach((wordPart) => {
40201
- brokenText.push(wordPart);
40202
- });
40203
- textLine = lastPart;
40204
- availableWidth = width - lastPartWidth;
40205
- }
40206
- else {
40207
- // here "lastPart" is equal to "word" and the "word" size is smaller than "width"
40208
- const _word = textLine === "" ? lastPart : " " + lastPart;
40209
- const wordWidth = computeTextWidth(this.ctx, _word, style);
40210
- if (wordWidth <= availableWidth) {
40211
- textLine += _word;
40212
- availableWidth -= wordWidth;
40213
- }
40214
- else {
40215
- brokenText.push(textLine);
40216
- textLine = lastPart;
40217
- availableWidth = width - lastPartWidth;
40218
- }
40219
- }
40220
- }
40221
- if (textLine !== "") {
40222
- brokenText.push(textLine);
40223
- }
40224
- }
40225
- return brokenText;
40419
+ return splitTextToWidth(this.ctx, text, style, width);
40226
40420
  }
40227
40421
  /**
40228
40422
  * Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -40281,26 +40475,6 @@
40281
40475
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
40282
40476
  return Math.max(0, ...sizes);
40283
40477
  }
40284
- splitWordToSpecificWidth(ctx, word, width, style) {
40285
- const wordWidth = computeTextWidth(ctx, word, style);
40286
- if (wordWidth <= width) {
40287
- return [word];
40288
- }
40289
- const splitWord = [];
40290
- let wordPart = "";
40291
- for (let l of word) {
40292
- const wordPartWidth = computeTextWidth(ctx, wordPart + l, style);
40293
- if (wordPartWidth > width) {
40294
- splitWord.push(wordPart);
40295
- wordPart = l;
40296
- }
40297
- else {
40298
- wordPart += l;
40299
- }
40300
- }
40301
- splitWord.push(wordPart);
40302
- return splitWord;
40303
- }
40304
40478
  /**
40305
40479
  * Check that any "sheetId" in the command matches an existing
40306
40480
  * sheet.
@@ -41292,7 +41466,7 @@
41292
41466
  .join("\n") || "\t");
41293
41467
  }
41294
41468
  getHTMLContent() {
41295
- if (this.cells.length == 1 && this.cells[0].length == 1) {
41469
+ if (this.cells.length === 1 && this.cells[0].length === 1) {
41296
41470
  return this.getters.getCellText(this.cells[0][0].position);
41297
41471
  }
41298
41472
  let htmlTable = '<table border="1" style="border-collapse:collapse">';
@@ -41617,7 +41791,7 @@
41617
41791
  status = "invisible";
41618
41792
  state;
41619
41793
  lastPasteState;
41620
- _isPaintingFormat = false;
41794
+ paintFormatStatus = "inactive";
41621
41795
  originSheetId;
41622
41796
  // ---------------------------------------------------------------------------
41623
41797
  // Command Handling
@@ -41632,7 +41806,7 @@
41632
41806
  if (!this.state) {
41633
41807
  return 24 /* CommandResult.EmptyClipboard */;
41634
41808
  }
41635
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41809
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41636
41810
  return this.state.isPasteAllowed(cmd.target, { pasteOption });
41637
41811
  case "PASTE_FROM_OS_CLIPBOARD": {
41638
41812
  const state = new ClipboardOsState(cmd.text, this.getters, this.dispatch, this.selection);
@@ -41648,6 +41822,11 @@
41648
41822
  const state = this.getClipboardStateForCopyCells(cut, "CUT");
41649
41823
  return state.isPasteAllowed(paste);
41650
41824
  }
41825
+ case "ACTIVATE_PAINT_FORMAT": {
41826
+ if (this.paintFormatStatus !== "inactive") {
41827
+ return 95 /* CommandResult.AlreadyInPaintingFormatMode */;
41828
+ }
41829
+ }
41651
41830
  }
41652
41831
  return 0 /* CommandResult.Success */;
41653
41832
  }
@@ -41664,11 +41843,13 @@
41664
41843
  if (!this.state) {
41665
41844
  break;
41666
41845
  }
41667
- const pasteOption = cmd.pasteOption || (this._isPaintingFormat ? "onlyFormat" : undefined);
41668
- this._isPaintingFormat = false;
41846
+ const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
41669
41847
  this.state.paste(cmd.target, { pasteOption, shouldPasteCF: true, selectTarget: true });
41670
41848
  this.lastPasteState = this.state;
41671
- this.status = "invisible";
41849
+ if (this.paintFormatStatus === "oneOff") {
41850
+ this.paintFormatStatus = "inactive";
41851
+ this.status = "invisible";
41852
+ }
41672
41853
  break;
41673
41854
  case "CLEAN_CLIPBOARD_HIGHLIGHT":
41674
41855
  this.status = "invisible";
@@ -41736,8 +41917,13 @@
41736
41917
  case "ACTIVATE_PAINT_FORMAT": {
41737
41918
  const zones = this.getters.getSelectedZones();
41738
41919
  this.state = this.getClipboardStateForCopyCells(zones, "COPY");
41739
- this._isPaintingFormat = true;
41740
41920
  this.status = "visible";
41921
+ if (cmd.persistent) {
41922
+ this.paintFormatStatus = "persistent";
41923
+ }
41924
+ else {
41925
+ this.paintFormatStatus = "oneOff";
41926
+ }
41741
41927
  break;
41742
41928
  }
41743
41929
  case "DELETE_SHEET":
@@ -41749,6 +41935,11 @@
41749
41935
  this.status = "invisible";
41750
41936
  }
41751
41937
  break;
41938
+ case "CANCEL_PAINT_FORMAT": {
41939
+ this.paintFormatStatus = "inactive";
41940
+ this.status = "invisible";
41941
+ break;
41942
+ }
41752
41943
  default:
41753
41944
  if (isCoreCommand(cmd)) {
41754
41945
  this.status = "invisible";
@@ -41779,7 +41970,7 @@
41779
41970
  return this.state ? this.state.operation === "CUT" : false;
41780
41971
  }
41781
41972
  isPaintingFormat() {
41782
- return this._isPaintingFormat;
41973
+ return this.paintFormatStatus !== "inactive";
41783
41974
  }
41784
41975
  // ---------------------------------------------------------------------------
41785
41976
  // Private methods
@@ -44414,6 +44605,7 @@
44414
44605
  .add("evaluation", EvaluationPlugin)
44415
44606
  .add("evaluation_chart", EvaluationChartPlugin)
44416
44607
  .add("evaluation_cf", EvaluationConditionalFormatPlugin)
44608
+ .add("row_size", UIRowSizePlugin)
44417
44609
  .add("custom_colors", CustomColorsPlugin);
44418
44610
 
44419
44611
  const clickableCellRegistry = new Registry();
@@ -45536,6 +45728,11 @@
45536
45728
  .o-sidePanelButton:enabled {
45537
45729
  cursor: pointer;
45538
45730
  }
45731
+
45732
+ .o-sidePanelButton:disabled {
45733
+ color: ${DISABLED_TEXT_COLOR};
45734
+ }
45735
+
45539
45736
  .o-sidePanelButton:last-child {
45540
45737
  margin-right: 0px;
45541
45738
  }
@@ -45669,11 +45866,11 @@
45669
45866
  }
45670
45867
  get title() {
45671
45868
  const name = this.actionButton.name(this.env);
45672
- const description = this.actionButton.description;
45869
+ const description = this.actionButton.description(this.env);
45673
45870
  return name + (description ? ` (${description})` : "");
45674
45871
  }
45675
45872
  get iconTitle() {
45676
- return this.actionButton.icon;
45873
+ return this.actionButton.icon(this.env);
45677
45874
  }
45678
45875
  onClick(ev) {
45679
45876
  if (this.isEnabled) {
@@ -46058,6 +46255,27 @@
46058
46255
  class: String,
46059
46256
  };
46060
46257
 
46258
+ class PaintFormatButton extends owl.Component {
46259
+ static template = "o-spreadsheet-PaintFormatButton";
46260
+ get isActive() {
46261
+ return this.env.model.getters.isPaintingFormat();
46262
+ }
46263
+ onDblClick() {
46264
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: true });
46265
+ }
46266
+ togglePaintFormat() {
46267
+ if (this.isActive) {
46268
+ this.env.model.dispatch("CANCEL_PAINT_FORMAT");
46269
+ }
46270
+ else {
46271
+ this.env.model.dispatch("ACTIVATE_PAINT_FORMAT", { persistent: false });
46272
+ }
46273
+ }
46274
+ }
46275
+ PaintFormatButton.props = {
46276
+ class: { type: String, optional: true },
46277
+ };
46278
+
46061
46279
  // -----------------------------------------------------------------------------
46062
46280
  // TopBar
46063
46281
  // -----------------------------------------------------------------------------
@@ -46158,6 +46376,7 @@
46158
46376
  TopBarComposer,
46159
46377
  FontSizeEditor,
46160
46378
  ActionButton,
46379
+ PaintFormatButton,
46161
46380
  BorderEditorWidget,
46162
46381
  };
46163
46382
  state = owl.useState({
@@ -48010,7 +48229,7 @@
48010
48229
  : "nextCluster";
48011
48230
  while (true) {
48012
48231
  const nextCellPosition = this.getNextCellPosition(currentPosition, dim, dir);
48013
- // Break if nextPosition == currentPosition, which happens if there's no next valid position
48232
+ // Break if nextPosition === currentPosition, which happens if there's no next valid position
48014
48233
  if (currentPosition.col === nextCellPosition.col &&
48015
48234
  currentPosition.row === nextCellPosition.row) {
48016
48235
  break;
@@ -49924,6 +50143,7 @@
49924
50143
  ...config,
49925
50144
  mode: config.mode || "normal",
49926
50145
  custom: config.custom || {},
50146
+ defaultCurrencyFormat: config.defaultCurrencyFormat || "[$$]#,##0.00",
49927
50147
  external: this.setupExternalConfig(config.external || {}),
49928
50148
  transportService,
49929
50149
  client,
@@ -50221,6 +50441,7 @@
50221
50441
  numberToLetters,
50222
50442
  UuidGenerator,
50223
50443
  formatValue,
50444
+ createCurrencyFormat,
50224
50445
  computeTextWidth,
50225
50446
  createEmptyWorkbookData,
50226
50447
  createEmptySheet,
@@ -50306,9 +50527,9 @@
50306
50527
  Object.defineProperty(exports, '__esModule', { value: true });
50307
50528
 
50308
50529
 
50309
- __info__.version = '16.4.0-alpha.7';
50310
- __info__.date = '2023-07-04T13:46:24.130Z';
50311
- __info__.hash = 'b04cf78';
50530
+ __info__.version = '16.4.1';
50531
+ __info__.date = '2023-07-26T12:13:04.992Z';
50532
+ __info__.hash = 'e687fa9';
50312
50533
 
50313
50534
 
50314
50535
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);