@odoo/o-spreadsheet 18.3.58 → 18.3.60

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.3.58
6
- * @date 2026-07-30T06:52:22.167Z
7
- * @hash a2d77fd
5
+ * @version 18.3.60
6
+ * @date 2026-08-21T15:28:08.047Z
7
+ * @hash ab0ffc5
8
8
  */
9
9
  /* Originates from src/components/top_bar/top_bar.scss */
10
10
  @media (max-width: 900px) {
@@ -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.3.58
6
- * @date 2026-07-30T06:52:20.660Z
7
- * @hash a2d77fd
5
+ * @version 18.3.60
6
+ * @date 2026-08-21T15:28:06.523Z
7
+ * @hash ab0ffc5
8
8
  */
9
9
 
10
10
  import { App, Component, blockDom, markRaw, onMounted, onPatched, onWillPatch, onWillStart, onWillUnmount, onWillUpdateProps, status, toRaw, useChildSubEnv, useComponent, useEffect, useEnv, useExternalListener, useRef, useState, useSubEnv, xml } from "@odoo/owl";
@@ -3457,6 +3457,18 @@ function toString(data) {
3457
3457
  default: return "";
3458
3458
  }
3459
3459
  }
3460
+ /**
3461
+ * Only converts the decimal separator, ignore number format such as % or dates
3462
+ */
3463
+ function toLocaleString(data, locale) {
3464
+ const value = toValue(data);
3465
+ switch (typeof value) {
3466
+ case "string": return value;
3467
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
3468
+ case "boolean": return value ? "TRUE" : "FALSE";
3469
+ default: return "";
3470
+ }
3471
+ }
3460
3472
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
3461
3473
  const normalizeString = memoize(function normalizeString(str) {
3462
3474
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -3646,19 +3658,58 @@ function applyVectorization(formula, args, acceptToVectorize = void 0) {
3646
3658
  }
3647
3659
  }
3648
3660
  if (countVectorizedCol === 1 && countVectorizedRow === 1) return formula(...args);
3649
- const getArgOffset = (i, j) => args.map((arg, index) => {
3650
- switch (vectorArgsType?.[index]) {
3651
- case "matrix": return arg[i][j];
3652
- case "horizontal": return arg[i][0];
3653
- case "vertical": return arg[0][j];
3654
- case void 0: return arg;
3661
+ const argsBuffer = new Array(args.length);
3662
+ const argGetters = [];
3663
+ const vectorizedIndices = [];
3664
+ for (let k = 0; k < args.length; k++) {
3665
+ const arg = args[k];
3666
+ switch (vectorArgsType?.[k]) {
3667
+ case "matrix":
3668
+ argGetters.push((i, j) => arg[i][j]);
3669
+ vectorizedIndices.push(k);
3670
+ break;
3671
+ case "horizontal":
3672
+ argGetters.push((i) => arg[i][0]);
3673
+ vectorizedIndices.push(k);
3674
+ break;
3675
+ case "vertical":
3676
+ argGetters.push((_i, j) => arg[0][j]);
3677
+ vectorizedIndices.push(k);
3678
+ break;
3679
+ case void 0:
3680
+ argsBuffer[k] = arg;
3681
+ break;
3655
3682
  }
3656
- });
3657
- return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
3658
- if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3659
- const singleCellComputeResult = formula(...getArgOffset(col, row));
3660
- return isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3661
- });
3683
+ }
3684
+ const nbVectorized = vectorizedIndices.length;
3685
+ let callFormula;
3686
+ switch (argsBuffer.length) {
3687
+ case 1:
3688
+ callFormula = () => formula(argsBuffer[0]);
3689
+ break;
3690
+ case 2:
3691
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1]);
3692
+ break;
3693
+ case 3:
3694
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1], argsBuffer[2]);
3695
+ break;
3696
+ default: callFormula = () => formula(...argsBuffer);
3697
+ }
3698
+ const result = new Array(countVectorizedCol);
3699
+ for (let col = 0; col < countVectorizedCol; col++) {
3700
+ const column = new Array(countVectorizedRow);
3701
+ result[col] = column;
3702
+ for (let row = 0; row < countVectorizedRow; row++) {
3703
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
3704
+ column[row] = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3705
+ continue;
3706
+ }
3707
+ for (let k = 0; k < nbVectorized; k++) argsBuffer[vectorizedIndices[k]] = argGetters[k](col, row);
3708
+ const singleCellComputeResult = callFormula();
3709
+ column[row] = isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3710
+ }
3711
+ }
3712
+ return result;
3662
3713
  }
3663
3714
  /**
3664
3715
  * This function allows to visit arguments and stop the visit if necessary.
@@ -15664,7 +15715,7 @@ const CONCAT = {
15664
15715
  description: _t("Concatenation of two values."),
15665
15716
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
15666
15717
  compute: function(value1, value2) {
15667
- return toString(value1) + toString(value2);
15718
+ return toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale);
15668
15719
  },
15669
15720
  isExported: true
15670
15721
  };
@@ -16474,7 +16525,7 @@ const CLEAN = {
16474
16525
  description: _t("Remove non-printable characters from a piece of text."),
16475
16526
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
16476
16527
  compute: function(text) {
16477
- const _text = toString(text);
16528
+ const _text = toLocaleString(text, this.locale);
16478
16529
  let cleanedStr = "";
16479
16530
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
16480
16531
  return cleanedStr;
@@ -16485,7 +16536,7 @@ const CONCATENATE = {
16485
16536
  description: _t("Appends strings to one another."),
16486
16537
  args: [arg("string1 (string, range<string>)", _t("The initial string.")), arg("string2 (string, range<string>, repeating)", _t("More strings to append in sequence."))],
16487
16538
  compute: function(...datas) {
16488
- return reduceAny(datas, (acc, a) => acc + toString(a), "");
16539
+ return reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "");
16489
16540
  },
16490
16541
  isExported: true
16491
16542
  };
@@ -16493,7 +16544,7 @@ const EXACT = {
16493
16544
  description: _t("Tests whether two strings are identical."),
16494
16545
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
16495
16546
  compute: function(string1, string2) {
16496
- return toString(string1) === toString(string2);
16547
+ return toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale);
16497
16548
  },
16498
16549
  isExported: true
16499
16550
  };
@@ -16505,13 +16556,13 @@ const FIND = {
16505
16556
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
16506
16557
  ],
16507
16558
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
16508
- const _searchFor = toString(searchFor);
16509
- const _textToSearch = toString(textToSearch);
16559
+ const _searchFor = toLocaleString(searchFor, this.locale);
16560
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
16510
16561
  const _startingAt = toNumber(startingAt, this.locale);
16511
16562
  assert(() => _textToSearch !== "", _t("The text_to_search must be non-empty."));
16512
16563
  assert(() => _startingAt >= 1, _t("The starting_at (%s) must be greater than or equal to 1.", _startingAt.toString()));
16513
16564
  const result = _textToSearch.indexOf(_searchFor, _startingAt - 1);
16514
- assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor.toString(), _textToSearch));
16565
+ assert(() => result >= 0, _t("In [[FUNCTION_NAME]] evaluation, cannot find '%s' within '%s'.", _searchFor, _textToSearch));
16515
16566
  return result + 1;
16516
16567
  },
16517
16568
  isExported: true
@@ -16524,8 +16575,8 @@ const JOIN = {
16524
16575
  arg("value_or_array2 (string, range<string>, repeating)", _t("More values to be appended using delimiter."))
16525
16576
  ],
16526
16577
  compute: function(delimiter, ...valuesOrArrays) {
16527
- const _delimiter = toString(delimiter);
16528
- return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "");
16578
+ const _delimiter = toLocaleString(delimiter, this.locale);
16579
+ return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "");
16529
16580
  }
16530
16581
  };
16531
16582
  const LEFT = {
@@ -16534,7 +16585,7 @@ const LEFT = {
16534
16585
  compute: function(text, ...args) {
16535
16586
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
16536
16587
  assert(() => _numberOfCharacters >= 0, _t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters.toString()));
16537
- return toString(text).substring(0, _numberOfCharacters);
16588
+ return toLocaleString(text, this.locale).substring(0, _numberOfCharacters);
16538
16589
  },
16539
16590
  isExported: true
16540
16591
  };
@@ -16542,7 +16593,7 @@ const LEN = {
16542
16593
  description: _t("Length of a string."),
16543
16594
  args: [arg("text (string)", _t("The string whose length will be returned."))],
16544
16595
  compute: function(text) {
16545
- return toString(text).length;
16596
+ return toLocaleString(text, this.locale).length;
16546
16597
  },
16547
16598
  isExported: true
16548
16599
  };
@@ -16550,7 +16601,7 @@ const LOWER = {
16550
16601
  description: _t("Converts a specified string to lowercase."),
16551
16602
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
16552
16603
  compute: function(text) {
16553
- return toString(text).toLowerCase();
16604
+ return toLocaleString(text, this.locale).toLowerCase();
16554
16605
  },
16555
16606
  isExported: true
16556
16607
  };
@@ -16562,7 +16613,7 @@ const MID = {
16562
16613
  arg("extract_length (number)", _t("The length of the segment to extract."))
16563
16614
  ],
16564
16615
  compute: function(text, starting_at, extract_length) {
16565
- const _text = toString(text);
16616
+ const _text = toLocaleString(text, this.locale);
16566
16617
  const _starting_at = toNumber(starting_at, this.locale);
16567
16618
  const _extract_length = toNumber(extract_length, this.locale);
16568
16619
  assert(() => _starting_at >= 1, _t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -16575,7 +16626,7 @@ const PROPER = {
16575
16626
  description: _t("Capitalizes each word in a specified string."),
16576
16627
  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."))],
16577
16628
  compute: function(text) {
16578
- return toString(text).replace(wordRegex, (word) => {
16629
+ return toLocaleString(text, this.locale).replace(wordRegex, (word) => {
16579
16630
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
16580
16631
  });
16581
16632
  },
@@ -16592,9 +16643,9 @@ const REPLACE = {
16592
16643
  compute: function(text, position, length, newText) {
16593
16644
  const _position = toNumber(position, this.locale);
16594
16645
  assert(() => _position >= 1, _t("The position (%s) must be greater than or equal to 1.", _position.toString()));
16595
- const _text = toString(text);
16646
+ const _text = toLocaleString(text, this.locale);
16596
16647
  const _length = toNumber(length, this.locale);
16597
- const _newText = toString(newText);
16648
+ const _newText = toLocaleString(newText, this.locale);
16598
16649
  return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);
16599
16650
  },
16600
16651
  isExported: true
@@ -16605,7 +16656,7 @@ const RIGHT = {
16605
16656
  compute: function(text, ...args) {
16606
16657
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
16607
16658
  assert(() => _numberOfCharacters >= 0, _t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters.toString()));
16608
- const _text = toString(text);
16659
+ const _text = toLocaleString(text, this.locale);
16609
16660
  const stringLength = _text.length;
16610
16661
  return _text.substring(stringLength - _numberOfCharacters, stringLength);
16611
16662
  },
@@ -16619,8 +16670,8 @@ const SEARCH = {
16619
16670
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
16620
16671
  ],
16621
16672
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
16622
- const _searchFor = toString(searchFor).toLowerCase();
16623
- const _textToSearch = toString(textToSearch).toLowerCase();
16673
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
16674
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
16624
16675
  const _startingAt = toNumber(startingAt, this.locale);
16625
16676
  if (_textToSearch === "") return {
16626
16677
  value: CellErrorType.GenericError,
@@ -16650,8 +16701,8 @@ const SPLIT = {
16650
16701
  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."))
16651
16702
  ],
16652
16703
  compute: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
16653
- const _text = toString(text);
16654
- const _delimiter = escapeRegExp(toString(delimiter));
16704
+ const _text = toLocaleString(text, this.locale);
16705
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
16655
16706
  const _splitByEach = toBoolean(splitByEach);
16656
16707
  const _removeEmptyText = toBoolean(removeEmptyText);
16657
16708
  assert(() => _delimiter.length > 0, _t("The _delimiter (%s) must be not be empty.", _delimiter));
@@ -16673,10 +16724,10 @@ const SUBSTITUTE = {
16673
16724
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
16674
16725
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
16675
16726
  assert(() => _occurrenceNumber >= 0, _t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber.toString()));
16676
- const _textToSearch = toString(textToSearch);
16677
- const _searchFor = toString(searchFor);
16727
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
16728
+ const _searchFor = toLocaleString(searchFor, this.locale);
16678
16729
  if (_searchFor === "") return _textToSearch;
16679
- const _replaceWith = toString(replaceWith);
16730
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
16680
16731
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
16681
16732
  if (_occurrenceNumber === 0) return _textToSearch.replace(reg, _replaceWith);
16682
16733
  let n = 0;
@@ -16693,10 +16744,10 @@ const TEXTJOIN = {
16693
16744
  arg("text2 (string, range<string>, repeating)", _t("Additional text item(s)."))
16694
16745
  ],
16695
16746
  compute: function(delimiter, ignoreEmpty, ...textsOrArrays) {
16696
- const _delimiter = toString(delimiter);
16747
+ const _delimiter = toLocaleString(delimiter, this.locale);
16697
16748
  const _ignoreEmpty = toBoolean(ignoreEmpty);
16698
16749
  let n = 0;
16699
- return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "");
16750
+ return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "");
16700
16751
  },
16701
16752
  isExported: true
16702
16753
  };
@@ -16704,7 +16755,7 @@ const TRIM = {
16704
16755
  description: _t("Removes space characters."),
16705
16756
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
16706
16757
  compute: function(text) {
16707
- return trimContent(toString(text));
16758
+ return trimContent(toLocaleString(text, this.locale));
16708
16759
  },
16709
16760
  isExported: true
16710
16761
  };
@@ -16712,7 +16763,7 @@ const UPPER = {
16712
16763
  description: _t("Converts a specified string to uppercase."),
16713
16764
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
16714
16765
  compute: function(text) {
16715
- return toString(text).toUpperCase();
16766
+ return toLocaleString(text, this.locale).toUpperCase();
16716
16767
  },
16717
16768
  isExported: true
16718
16769
  };
@@ -16744,7 +16795,7 @@ const HYPERLINK = {
16744
16795
  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."))],
16745
16796
  compute: function(url, linkLabel) {
16746
16797
  const processedUrl = toString(url).trim();
16747
- const processedLabel = toString(linkLabel) || processedUrl;
16798
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
16748
16799
  if (processedUrl === "") return processedLabel;
16749
16800
  return markdownLink(processedLabel, processedUrl);
16750
16801
  },
@@ -16846,12 +16897,15 @@ for (const category of categories) {
16846
16897
  }
16847
16898
  }
16848
16899
  function createComputeFunction(descr) {
16900
+ let currentArgDefinitions = [];
16849
16901
  function vectorizedCompute(...args) {
16850
16902
  const acceptToVectorize = [];
16903
+ currentArgDefinitions = new Array(args.length);
16851
16904
  const getArgToFocus = argTargeting(descr, args.length);
16852
16905
  for (let i = 0; i < args.length; i++) {
16853
16906
  const argIndex = getArgToFocus(i) ?? -1;
16854
16907
  const argDefinition = descr.args[argIndex];
16908
+ currentArgDefinitions[i] = argDefinition;
16855
16909
  const arg = args[i];
16856
16910
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) throw new BadExpressionError(_t("Function %s expects the parameter '%s' to be reference to a cell or range.", descr.name, (i + 1).toString()));
16857
16911
  acceptToVectorize.push(!argDefinition.acceptMatrix);
@@ -16866,8 +16920,7 @@ function createComputeFunction(descr) {
16866
16920
  function errorHandlingCompute(...args) {
16867
16921
  for (let i = 0; i < args.length; i++) {
16868
16922
  const arg = args[i];
16869
- const getArgToFocus = argTargeting(descr, args.length);
16870
- if (!descr.args[getArgToFocus(i) || i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
16923
+ if (!currentArgDefinitions[i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
16871
16924
  }
16872
16925
  try {
16873
16926
  return computeFunctionToObject.apply(this, args);
@@ -31282,12 +31335,13 @@ var FilterMenu = class extends Component {
31282
31335
  }
31283
31336
  sortFilterZone(sortDirection) {
31284
31337
  const filterPosition = this.props.filterPosition;
31285
- const tableZone = this.table?.range.zone;
31338
+ const table = this.table;
31339
+ const tableZone = table?.range.zone;
31286
31340
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
31287
31341
  const sheetId = this.env.model.getters.getActiveSheetId();
31288
31342
  const contentZone = {
31289
31343
  ...tableZone,
31290
- top: tableZone.top + 1
31344
+ top: tableZone.top + table.config.numberOfHeaders
31291
31345
  };
31292
31346
  const sortAnchor = {
31293
31347
  col: filterPosition.col,
@@ -42869,6 +42923,7 @@ css`
42869
42923
  .o-spreadsheet {
42870
42924
  .os-input {
42871
42925
  border-width: 0 0 1px 0;
42926
+ border-style: solid;
42872
42927
  border-color: transparent;
42873
42928
  outline: none;
42874
42929
  text-overflow: ellipsis;
@@ -67825,6 +67880,7 @@ css`
67825
67880
  width: 100%;
67826
67881
  outline: none;
67827
67882
  border-color: ${GRAY_300};
67883
+ border-style: solid;
67828
67884
  color: ${GRAY_900};
67829
67885
 
67830
67886
  &::placeholder {
@@ -72238,6 +72294,6 @@ const chartHelpers = {
72238
72294
  //#endregion
72239
72295
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
72240
72296
 
72241
- __info__.version = "18.3.58";
72242
- __info__.date = "2026-07-30T06:52:20.660Z";
72243
- __info__.hash = "a2d77fd";
72297
+ __info__.version = "18.3.60";
72298
+ __info__.date = "2026-08-21T15:28:06.523Z";
72299
+ __info__.hash = "ab0ffc5";