@odoo/o-spreadsheet 18.0.77 → 18.0.79

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.0.77
6
- * @date 2026-07-30T06:52:06.623Z
7
- * @hash 54a18dc
5
+ * @version 18.0.79
6
+ * @date 2026-08-21T15:27:46.928Z
7
+ * @hash 173a29c
8
8
  */
9
9
 
10
10
  import { Component, markRaw, onMounted, onPatched, onWillPatch, onWillStart, onWillUnmount, onWillUpdateProps, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, xml } from "@odoo/owl";
@@ -3272,6 +3272,18 @@ function toString(data) {
3272
3272
  default: return "";
3273
3273
  }
3274
3274
  }
3275
+ /**
3276
+ * Only converts the decimal separator, ignore number format such as % or dates
3277
+ */
3278
+ function toLocaleString(data, locale) {
3279
+ const value = toValue(data);
3280
+ switch (typeof value) {
3281
+ case "string": return value;
3282
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
3283
+ case "boolean": return value ? "TRUE" : "FALSE";
3284
+ default: return "";
3285
+ }
3286
+ }
3275
3287
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
3276
3288
  const normalizeString = memoize(function normalizeString(str) {
3277
3289
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -3461,19 +3473,58 @@ function applyVectorization(formula, args, acceptToVectorize = void 0) {
3461
3473
  }
3462
3474
  }
3463
3475
  if (countVectorizedCol === 1 && countVectorizedRow === 1) return formula(...args);
3464
- const getArgOffset = (i, j) => args.map((arg, index) => {
3465
- switch (vectorArgsType?.[index]) {
3466
- case "matrix": return arg[i][j];
3467
- case "horizontal": return arg[i][0];
3468
- case "vertical": return arg[0][j];
3469
- case void 0: return arg;
3476
+ const argsBuffer = new Array(args.length);
3477
+ const argGetters = [];
3478
+ const vectorizedIndices = [];
3479
+ for (let k = 0; k < args.length; k++) {
3480
+ const arg = args[k];
3481
+ switch (vectorArgsType?.[k]) {
3482
+ case "matrix":
3483
+ argGetters.push((i, j) => arg[i][j]);
3484
+ vectorizedIndices.push(k);
3485
+ break;
3486
+ case "horizontal":
3487
+ argGetters.push((i) => arg[i][0]);
3488
+ vectorizedIndices.push(k);
3489
+ break;
3490
+ case "vertical":
3491
+ argGetters.push((_i, j) => arg[0][j]);
3492
+ vectorizedIndices.push(k);
3493
+ break;
3494
+ case void 0:
3495
+ argsBuffer[k] = arg;
3496
+ break;
3470
3497
  }
3471
- });
3472
- return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
3473
- if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3474
- const singleCellComputeResult = formula(...getArgOffset(col, row));
3475
- return isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3476
- });
3498
+ }
3499
+ const nbVectorized = vectorizedIndices.length;
3500
+ let callFormula;
3501
+ switch (argsBuffer.length) {
3502
+ case 1:
3503
+ callFormula = () => formula(argsBuffer[0]);
3504
+ break;
3505
+ case 2:
3506
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1]);
3507
+ break;
3508
+ case 3:
3509
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1], argsBuffer[2]);
3510
+ break;
3511
+ default: callFormula = () => formula(...argsBuffer);
3512
+ }
3513
+ const result = new Array(countVectorizedCol);
3514
+ for (let col = 0; col < countVectorizedCol; col++) {
3515
+ const column = new Array(countVectorizedRow);
3516
+ result[col] = column;
3517
+ for (let row = 0; row < countVectorizedRow; row++) {
3518
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
3519
+ column[row] = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3520
+ continue;
3521
+ }
3522
+ for (let k = 0; k < nbVectorized; k++) argsBuffer[vectorizedIndices[k]] = argGetters[k](col, row);
3523
+ const singleCellComputeResult = callFormula();
3524
+ column[row] = isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3525
+ }
3526
+ }
3527
+ return result;
3477
3528
  }
3478
3529
  /**
3479
3530
  * This function allows to visit arguments and stop the visit if necessary.
@@ -21674,7 +21725,7 @@ const CONCAT = {
21674
21725
  description: _t("Concatenation of two values."),
21675
21726
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
21676
21727
  compute: function(value1, value2) {
21677
- return toString(value1) + toString(value2);
21728
+ return toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale);
21678
21729
  },
21679
21730
  isExported: true
21680
21731
  };
@@ -22476,7 +22527,7 @@ const CLEAN = {
22476
22527
  description: _t("Remove non-printable characters from a piece of text."),
22477
22528
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
22478
22529
  compute: function(text) {
22479
- const _text = toString(text);
22530
+ const _text = toLocaleString(text, this.locale);
22480
22531
  let cleanedStr = "";
22481
22532
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
22482
22533
  return cleanedStr;
@@ -22487,7 +22538,7 @@ const CONCATENATE = {
22487
22538
  description: _t("Appends strings to one another."),
22488
22539
  args: [arg("string1 (string, range<string>)", _t("The initial string.")), arg("string2 (string, range<string>, repeating)", _t("More strings to append in sequence."))],
22489
22540
  compute: function(...datas) {
22490
- return reduceAny(datas, (acc, a) => acc + toString(a), "");
22541
+ return reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "");
22491
22542
  },
22492
22543
  isExported: true
22493
22544
  };
@@ -22495,7 +22546,7 @@ const EXACT = {
22495
22546
  description: _t("Tests whether two strings are identical."),
22496
22547
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
22497
22548
  compute: function(string1, string2) {
22498
- return toString(string1) === toString(string2);
22549
+ return toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale);
22499
22550
  },
22500
22551
  isExported: true
22501
22552
  };
@@ -22507,13 +22558,13 @@ const FIND = {
22507
22558
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
22508
22559
  ],
22509
22560
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
22510
- const _searchFor = toString(searchFor);
22511
- const _textToSearch = toString(textToSearch);
22561
+ const _searchFor = toLocaleString(searchFor, this.locale);
22562
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
22512
22563
  const _startingAt = toNumber(startingAt, this.locale);
22513
22564
  assert(() => _textToSearch !== "", _t("The text_to_search must be non-empty."));
22514
22565
  assert(() => _startingAt >= 1, _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt.toString()));
22515
22566
  const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);
22516
- assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor.toString(), _textToSearch));
22567
+ assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch));
22517
22568
  return result + 1;
22518
22569
  },
22519
22570
  isExported: true
@@ -22526,8 +22577,8 @@ const JOIN = {
22526
22577
  arg("value_or_array2 (string, range<string>, repeating)", _t("More values to be appended using delimiter."))
22527
22578
  ],
22528
22579
  compute: function(delimiter, ...valuesOrArrays) {
22529
- const _delimiter = toString(delimiter);
22530
- return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "");
22580
+ const _delimiter = toLocaleString(delimiter, this.locale);
22581
+ return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "");
22531
22582
  }
22532
22583
  };
22533
22584
  const LEFT = {
@@ -22536,7 +22587,7 @@ const LEFT = {
22536
22587
  compute: function(text, ...args) {
22537
22588
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
22538
22589
  assert(() => _numberOfCharacters >= 0, _t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters.toString()));
22539
- return toString(text).substring(0, _numberOfCharacters);
22590
+ return toLocaleString(text, this.locale).substring(0, _numberOfCharacters);
22540
22591
  },
22541
22592
  isExported: true
22542
22593
  };
@@ -22544,7 +22595,7 @@ const LEN = {
22544
22595
  description: _t("Length of a string."),
22545
22596
  args: [arg("text (string)", _t("The string whose length will be returned."))],
22546
22597
  compute: function(text) {
22547
- return toString(text).length;
22598
+ return toLocaleString(text, this.locale).length;
22548
22599
  },
22549
22600
  isExported: true
22550
22601
  };
@@ -22552,7 +22603,7 @@ const LOWER = {
22552
22603
  description: _t("Converts a specified string to lowercase."),
22553
22604
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
22554
22605
  compute: function(text) {
22555
- return toString(text).toLowerCase();
22606
+ return toLocaleString(text, this.locale).toLowerCase();
22556
22607
  },
22557
22608
  isExported: true
22558
22609
  };
@@ -22564,7 +22615,7 @@ const MID = {
22564
22615
  arg("extract_length (number)", _t("The length of the segment to extract."))
22565
22616
  ],
22566
22617
  compute: function(text, starting_at, extract_length) {
22567
- const _text = toString(text);
22618
+ const _text = toLocaleString(text, this.locale);
22568
22619
  const _starting_at = toNumber(starting_at, this.locale);
22569
22620
  const _extract_length = toNumber(extract_length, this.locale);
22570
22621
  assert(() => _starting_at >= 1, _t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -22577,7 +22628,7 @@ const PROPER = {
22577
22628
  description: _t("Capitalizes each word in a specified string."),
22578
22629
  args: [arg("text_to_capitalize (string)", _t("The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase."))],
22579
22630
  compute: function(text) {
22580
- return toString(text).replace(wordRegex, (word) => {
22631
+ return toLocaleString(text, this.locale).replace(wordRegex, (word) => {
22581
22632
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
22582
22633
  });
22583
22634
  },
@@ -22594,9 +22645,9 @@ const REPLACE = {
22594
22645
  compute: function(text, position, length, newText) {
22595
22646
  const _position = toNumber(position, this.locale);
22596
22647
  assert(() => _position >= 1, _t("The position (%s) must be greater than or equal to 1.", _position.toString()));
22597
- const _text = toString(text);
22648
+ const _text = toLocaleString(text, this.locale);
22598
22649
  const _length = toNumber(length, this.locale);
22599
- const _newText = toString(newText);
22650
+ const _newText = toLocaleString(newText, this.locale);
22600
22651
  return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);
22601
22652
  },
22602
22653
  isExported: true
@@ -22607,7 +22658,7 @@ const RIGHT = {
22607
22658
  compute: function(text, ...args) {
22608
22659
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
22609
22660
  assert(() => _numberOfCharacters >= 0, _t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters.toString()));
22610
- const _text = toString(text);
22661
+ const _text = toLocaleString(text, this.locale);
22611
22662
  const stringLength = _text.length;
22612
22663
  return _text.substring(stringLength - _numberOfCharacters, stringLength);
22613
22664
  },
@@ -22621,8 +22672,8 @@ const SEARCH = {
22621
22672
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
22622
22673
  ],
22623
22674
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
22624
- const _searchFor = toString(searchFor).toLowerCase();
22625
- const _textToSearch = toString(textToSearch).toLowerCase();
22675
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
22676
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
22626
22677
  const _startingAt = toNumber(startingAt, this.locale);
22627
22678
  if (_textToSearch === "") return {
22628
22679
  value: CellErrorType.GenericError,
@@ -22652,8 +22703,8 @@ const SPLIT = {
22652
22703
  arg(`remove_empty_text (boolean, default=${SPLIT_DEFAULT_REMOVE_EMPTY_TEXT})`, _t("Whether or not to remove empty text messages from the split results. The default behavior is to treat consecutive delimiters as one (if TRUE). If FALSE, empty cells values are added between consecutive delimiters."))
22653
22704
  ],
22654
22705
  compute: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
22655
- const _text = toString(text);
22656
- const _delimiter = escapeRegExp(toString(delimiter));
22706
+ const _text = toLocaleString(text, this.locale);
22707
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
22657
22708
  const _splitByEach = toBoolean(splitByEach);
22658
22709
  const _removeEmptyText = toBoolean(removeEmptyText);
22659
22710
  assert(() => _delimiter.length > 0, _t("The _delimiter (%s) must be not be empty.", _delimiter));
@@ -22675,10 +22726,10 @@ const SUBSTITUTE = {
22675
22726
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
22676
22727
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
22677
22728
  assert(() => _occurrenceNumber >= 0, _t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber.toString()));
22678
- const _textToSearch = toString(textToSearch);
22679
- const _searchFor = toString(searchFor);
22729
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
22730
+ const _searchFor = toLocaleString(searchFor, this.locale);
22680
22731
  if (_searchFor === "") return _textToSearch;
22681
- const _replaceWith = toString(replaceWith);
22732
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
22682
22733
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
22683
22734
  if (_occurrenceNumber === 0) return _textToSearch.replace(reg, _replaceWith);
22684
22735
  let n = 0;
@@ -22695,10 +22746,10 @@ const TEXTJOIN = {
22695
22746
  arg("text2 (string, range<string>, repeating)", _t("Additional text item(s)."))
22696
22747
  ],
22697
22748
  compute: function(delimiter, ignoreEmpty, ...textsOrArrays) {
22698
- const _delimiter = toString(delimiter);
22749
+ const _delimiter = toLocaleString(delimiter, this.locale);
22699
22750
  const _ignoreEmpty = toBoolean(ignoreEmpty);
22700
22751
  let n = 0;
22701
- return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "");
22752
+ return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "");
22702
22753
  },
22703
22754
  isExported: true
22704
22755
  };
@@ -22706,7 +22757,7 @@ const TRIM = {
22706
22757
  description: _t("Removes space characters."),
22707
22758
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
22708
22759
  compute: function(text) {
22709
- return trimContent(toString(text));
22760
+ return trimContent(toLocaleString(text, this.locale));
22710
22761
  },
22711
22762
  isExported: true
22712
22763
  };
@@ -22714,7 +22765,7 @@ const UPPER = {
22714
22765
  description: _t("Converts a specified string to uppercase."),
22715
22766
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
22716
22767
  compute: function(text) {
22717
- return toString(text).toUpperCase();
22768
+ return toLocaleString(text, this.locale).toUpperCase();
22718
22769
  },
22719
22770
  isExported: true
22720
22771
  };
@@ -22746,7 +22797,7 @@ const HYPERLINK = {
22746
22797
  args: [arg("url (string)", _t("The full URL of the link enclosed in quotation marks.")), arg("link_label (string, optional)", _t("The text to display in the cell, enclosed in quotation marks."))],
22747
22798
  compute: function(url, linkLabel) {
22748
22799
  const processedUrl = toString(url).trim();
22749
- const processedLabel = toString(linkLabel) || processedUrl;
22800
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
22750
22801
  if (processedUrl === "") return processedLabel;
22751
22802
  return markdownLink(processedLabel, processedUrl);
22752
22803
  },
@@ -22848,10 +22899,13 @@ for (const category of categories) {
22848
22899
  }
22849
22900
  }
22850
22901
  function createComputeFunction(descr, functionName) {
22902
+ let currentArgDefinitions = [];
22851
22903
  function vectorizedCompute(...args) {
22852
22904
  const acceptToVectorize = [];
22905
+ currentArgDefinitions = new Array(args.length);
22853
22906
  for (let i = 0; i < args.length; i++) {
22854
22907
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
22908
+ currentArgDefinitions[i] = argDefinition;
22855
22909
  const arg = args[i];
22856
22910
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) throw new BadExpressionError(_t("Function %s expects the parameter '%s' to be reference to a cell or range.", functionName, (i + 1).toString()));
22857
22911
  acceptToVectorize.push(!argDefinition.acceptMatrix);
@@ -22866,7 +22920,7 @@ function createComputeFunction(descr, functionName) {
22866
22920
  function errorHandlingCompute(...args) {
22867
22921
  for (let i = 0; i < args.length; i++) {
22868
22922
  const arg = args[i];
22869
- if (!descr.args[descr.getArgToFocus(i + 1) - 1].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
22923
+ if (!currentArgDefinitions[i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
22870
22924
  }
22871
22925
  try {
22872
22926
  return computeFunctionToObject.apply(this, args);
@@ -27929,12 +27983,13 @@ var FilterMenu = class extends Component {
27929
27983
  }
27930
27984
  sortFilterZone(sortDirection) {
27931
27985
  const filterPosition = this.props.filterPosition;
27932
- const tableZone = this.table?.range.zone;
27986
+ const table = this.table;
27987
+ const tableZone = table?.range.zone;
27933
27988
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
27934
27989
  const sheetId = this.env.model.getters.getActiveSheetId();
27935
27990
  const contentZone = {
27936
27991
  ...tableZone,
27937
- top: tableZone.top + 1
27992
+ top: tableZone.top + table.config.numberOfHeaders
27938
27993
  };
27939
27994
  this.env.model.dispatch("SORT_CELLS", {
27940
27995
  sheetId,
@@ -38048,6 +38103,7 @@ css`
38048
38103
  .os-input {
38049
38104
  box-sizing: border-box;
38050
38105
  border-width: 0 0 1px 0;
38106
+ border-style: solid;
38051
38107
  border-color: transparent;
38052
38108
  outline: none;
38053
38109
  text-overflow: ellipsis;
@@ -62021,6 +62077,7 @@ css`
62021
62077
  width: 100%;
62022
62078
  outline: none;
62023
62079
  border-color: ${GRAY_300};
62080
+ border-style: solid;
62024
62081
  color: ${GRAY_900};
62025
62082
 
62026
62083
  &::placeholder {
@@ -66346,6 +66403,6 @@ const constants = {
66346
66403
  //#endregion
66347
66404
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
66348
66405
 
66349
- __info__.version = "18.0.77";
66350
- __info__.date = "2026-07-30T06:52:06.623Z";
66351
- __info__.hash = "54a18dc";
66406
+ __info__.version = "18.0.79";
66407
+ __info__.date = "2026-08-21T15:27:46.928Z";
66408
+ __info__.hash = "173a29c";