@odoo/o-spreadsheet 18.4.50 → 18.4.52

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.4.50
6
- * @date 2026-07-30T06:52:56.556Z
7
- * @hash 0a41352
5
+ * @version 18.4.52
6
+ * @date 2026-08-21T15:29:28.329Z
7
+ * @hash 5715a49
8
8
  */
9
9
  /* Originates from src/components/top_bar/top_bar.scss */
10
10
  @media (max-width: 1200px) {
@@ -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.4.50
6
- * @date 2026-07-30T06:52:55.061Z
7
- * @hash 0a41352
5
+ * @version 18.4.52
6
+ * @date 2026-08-21T15:29:26.810Z
7
+ * @hash 5715a49
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";
@@ -2848,6 +2848,18 @@ function toString(data) {
2848
2848
  default: return "";
2849
2849
  }
2850
2850
  }
2851
+ /**
2852
+ * Only converts the decimal separator, ignore number format such as % or dates
2853
+ */
2854
+ function toLocaleString(data, locale) {
2855
+ const value = toValue(data);
2856
+ switch (typeof value) {
2857
+ case "string": return value;
2858
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
2859
+ case "boolean": return value ? "TRUE" : "FALSE";
2860
+ default: return "";
2861
+ }
2862
+ }
2851
2863
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
2852
2864
  const normalizeString = memoize(function normalizeString(str) {
2853
2865
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -3037,19 +3049,58 @@ function applyVectorization(formula, args, acceptToVectorize = void 0) {
3037
3049
  }
3038
3050
  }
3039
3051
  if (countVectorizedCol === 1 && countVectorizedRow === 1) return formula(...args);
3040
- const getArgOffset = (i, j) => args.map((arg, index) => {
3041
- switch (vectorArgsType?.[index]) {
3042
- case "matrix": return arg[i][j];
3043
- case "horizontal": return arg[i][0];
3044
- case "vertical": return arg[0][j];
3045
- case void 0: return arg;
3052
+ const argsBuffer = new Array(args.length);
3053
+ const argGetters = [];
3054
+ const vectorizedIndices = [];
3055
+ for (let k = 0; k < args.length; k++) {
3056
+ const arg = args[k];
3057
+ switch (vectorArgsType?.[k]) {
3058
+ case "matrix":
3059
+ argGetters.push((i, j) => arg[i][j]);
3060
+ vectorizedIndices.push(k);
3061
+ break;
3062
+ case "horizontal":
3063
+ argGetters.push((i) => arg[i][0]);
3064
+ vectorizedIndices.push(k);
3065
+ break;
3066
+ case "vertical":
3067
+ argGetters.push((_i, j) => arg[0][j]);
3068
+ vectorizedIndices.push(k);
3069
+ break;
3070
+ case void 0:
3071
+ argsBuffer[k] = arg;
3072
+ break;
3046
3073
  }
3047
- });
3048
- return generateMatrix(countVectorizedCol, countVectorizedRow, (col, row) => {
3049
- if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) return new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3050
- const singleCellComputeResult = formula(...getArgOffset(col, row));
3051
- return isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3052
- });
3074
+ }
3075
+ const nbVectorized = vectorizedIndices.length;
3076
+ let callFormula;
3077
+ switch (argsBuffer.length) {
3078
+ case 1:
3079
+ callFormula = () => formula(argsBuffer[0]);
3080
+ break;
3081
+ case 2:
3082
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1]);
3083
+ break;
3084
+ case 3:
3085
+ callFormula = () => formula(argsBuffer[0], argsBuffer[1], argsBuffer[2]);
3086
+ break;
3087
+ default: callFormula = () => formula(...argsBuffer);
3088
+ }
3089
+ const result = new Array(countVectorizedCol);
3090
+ for (let col = 0; col < countVectorizedCol; col++) {
3091
+ const column = new Array(countVectorizedRow);
3092
+ result[col] = column;
3093
+ for (let row = 0; row < countVectorizedRow; row++) {
3094
+ if (col > vectorizedColLimit - 1 || row > vectorizedRowLimit - 1) {
3095
+ column[row] = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
3096
+ continue;
3097
+ }
3098
+ for (let k = 0; k < nbVectorized; k++) argsBuffer[vectorizedIndices[k]] = argGetters[k](col, row);
3099
+ const singleCellComputeResult = callFormula();
3100
+ column[row] = isMatrix(singleCellComputeResult) ? singleCellComputeResult[0][0] : singleCellComputeResult;
3101
+ }
3102
+ }
3103
+ return result;
3053
3104
  }
3054
3105
  /**
3055
3106
  * This function allows to visit arguments and stop the visit if necessary.
@@ -15949,7 +16000,7 @@ const CONCAT = {
15949
16000
  description: _t("Concatenation of two values."),
15950
16001
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
15951
16002
  compute: function(value1, value2) {
15952
- return toString(value1) + toString(value2);
16003
+ return toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale);
15953
16004
  },
15954
16005
  isExported: true
15955
16006
  };
@@ -16760,7 +16811,7 @@ const CLEAN = {
16760
16811
  description: _t("Remove non-printable characters from a piece of text."),
16761
16812
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
16762
16813
  compute: function(text) {
16763
- const _text = toString(text);
16814
+ const _text = toLocaleString(text, this.locale);
16764
16815
  let cleanedStr = "";
16765
16816
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
16766
16817
  return cleanedStr;
@@ -16771,7 +16822,7 @@ const CONCATENATE = {
16771
16822
  description: _t("Appends strings to one another."),
16772
16823
  args: [arg("string1 (string, range<string>)", _t("The initial string.")), arg("string2 (string, range<string>, repeating)", _t("More strings to append in sequence."))],
16773
16824
  compute: function(...datas) {
16774
- return reduceAny(datas, (acc, a) => acc + toString(a), "");
16825
+ return reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "");
16775
16826
  },
16776
16827
  isExported: true
16777
16828
  };
@@ -16779,7 +16830,7 @@ const EXACT = {
16779
16830
  description: _t("Tests whether two strings are identical."),
16780
16831
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
16781
16832
  compute: function(string1, string2) {
16782
- return toString(string1) === toString(string2);
16833
+ return toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale);
16783
16834
  },
16784
16835
  isExported: true
16785
16836
  };
@@ -16791,8 +16842,8 @@ const FIND = {
16791
16842
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
16792
16843
  ],
16793
16844
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
16794
- const _searchFor = toString(searchFor);
16795
- const _textToSearch = toString(textToSearch);
16845
+ const _searchFor = toLocaleString(searchFor, this.locale);
16846
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
16796
16847
  const _startingAt = toNumber(startingAt, this.locale);
16797
16848
  if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
16798
16849
  if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
@@ -16810,8 +16861,8 @@ const JOIN = {
16810
16861
  arg("value_or_array2 (string, range<string>, repeating)", _t("More values to be appended using delimiter."))
16811
16862
  ],
16812
16863
  compute: function(delimiter, ...valuesOrArrays) {
16813
- const _delimiter = toString(delimiter);
16814
- return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "");
16864
+ const _delimiter = toLocaleString(delimiter, this.locale);
16865
+ return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "");
16815
16866
  }
16816
16867
  };
16817
16868
  const LEFT = {
@@ -16820,7 +16871,7 @@ const LEFT = {
16820
16871
  compute: function(text, ...args) {
16821
16872
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
16822
16873
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
16823
- return toString(text).substring(0, _numberOfCharacters);
16874
+ return toLocaleString(text, this.locale).substring(0, _numberOfCharacters);
16824
16875
  },
16825
16876
  isExported: true
16826
16877
  };
@@ -16828,7 +16879,7 @@ const LEN = {
16828
16879
  description: _t("Length of a string."),
16829
16880
  args: [arg("text (string)", _t("The string whose length will be returned."))],
16830
16881
  compute: function(text) {
16831
- return toString(text).length;
16882
+ return toLocaleString(text, this.locale).length;
16832
16883
  },
16833
16884
  isExported: true
16834
16885
  };
@@ -16836,7 +16887,7 @@ const LOWER = {
16836
16887
  description: _t("Converts a specified string to lowercase."),
16837
16888
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
16838
16889
  compute: function(text) {
16839
- return toString(text).toLowerCase();
16890
+ return toLocaleString(text, this.locale).toLowerCase();
16840
16891
  },
16841
16892
  isExported: true
16842
16893
  };
@@ -16848,7 +16899,7 @@ const MID = {
16848
16899
  arg("extract_length (number)", _t("The length of the segment to extract."))
16849
16900
  ],
16850
16901
  compute: function(text, starting_at, extract_length) {
16851
- const _text = toString(text);
16902
+ const _text = toLocaleString(text, this.locale);
16852
16903
  const _starting_at = toNumber(starting_at, this.locale);
16853
16904
  const _extract_length = toNumber(extract_length, this.locale);
16854
16905
  if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -16861,7 +16912,7 @@ const PROPER = {
16861
16912
  description: _t("Capitalizes each word in a specified string."),
16862
16913
  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."))],
16863
16914
  compute: function(text) {
16864
- return toString(text).replace(wordRegex, (word) => {
16915
+ return toLocaleString(text, this.locale).replace(wordRegex, (word) => {
16865
16916
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
16866
16917
  });
16867
16918
  },
@@ -16878,9 +16929,9 @@ const REPLACE = {
16878
16929
  compute: function(text, position, length, newText) {
16879
16930
  const _position = toNumber(position, this.locale);
16880
16931
  if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
16881
- const _text = toString(text);
16932
+ const _text = toLocaleString(text, this.locale);
16882
16933
  const _length = toNumber(length, this.locale);
16883
- const _newText = toString(newText);
16934
+ const _newText = toLocaleString(newText, this.locale);
16884
16935
  return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);
16885
16936
  },
16886
16937
  isExported: true
@@ -16891,7 +16942,7 @@ const RIGHT = {
16891
16942
  compute: function(text, ...args) {
16892
16943
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
16893
16944
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
16894
- const _text = toString(text);
16945
+ const _text = toLocaleString(text, this.locale);
16895
16946
  const stringLength = _text.length;
16896
16947
  return _text.substring(stringLength - _numberOfCharacters, stringLength);
16897
16948
  },
@@ -16905,8 +16956,8 @@ const SEARCH = {
16905
16956
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
16906
16957
  ],
16907
16958
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
16908
- const _searchFor = toString(searchFor).toLowerCase();
16909
- const _textToSearch = toString(textToSearch).toLowerCase();
16959
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
16960
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
16910
16961
  const _startingAt = toNumber(startingAt, this.locale);
16911
16962
  if (_textToSearch === "") return {
16912
16963
  value: CellErrorType.GenericError,
@@ -16936,8 +16987,8 @@ const SPLIT = {
16936
16987
  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."))
16937
16988
  ],
16938
16989
  compute: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
16939
- const _text = toString(text);
16940
- const _delimiter = escapeRegExp(toString(delimiter));
16990
+ const _text = toLocaleString(text, this.locale);
16991
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
16941
16992
  const _splitByEach = toBoolean(splitByEach);
16942
16993
  const _removeEmptyText = toBoolean(removeEmptyText);
16943
16994
  if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
@@ -16959,10 +17010,10 @@ const SUBSTITUTE = {
16959
17010
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
16960
17011
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
16961
17012
  if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
16962
- const _textToSearch = toString(textToSearch);
16963
- const _searchFor = toString(searchFor);
17013
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
17014
+ const _searchFor = toLocaleString(searchFor, this.locale);
16964
17015
  if (_searchFor === "") return _textToSearch;
16965
- const _replaceWith = toString(replaceWith);
17016
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
16966
17017
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
16967
17018
  if (_occurrenceNumber === 0) return _textToSearch.replace(reg, _replaceWith);
16968
17019
  let n = 0;
@@ -16979,10 +17030,10 @@ const TEXTJOIN = {
16979
17030
  arg("text2 (string, range<string>, repeating)", _t("Additional text item(s)."))
16980
17031
  ],
16981
17032
  compute: function(delimiter, ignoreEmpty, ...textsOrArrays) {
16982
- const _delimiter = toString(delimiter);
17033
+ const _delimiter = toLocaleString(delimiter, this.locale);
16983
17034
  const _ignoreEmpty = toBoolean(ignoreEmpty);
16984
17035
  let n = 0;
16985
- return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "");
17036
+ return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "");
16986
17037
  },
16987
17038
  isExported: true
16988
17039
  };
@@ -17029,7 +17080,7 @@ const TRIM = {
17029
17080
  description: _t("Removes space characters."),
17030
17081
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
17031
17082
  compute: function(text) {
17032
- return trimContent(toString(text));
17083
+ return trimContent(toLocaleString(text, this.locale));
17033
17084
  },
17034
17085
  isExported: true
17035
17086
  };
@@ -17037,7 +17088,7 @@ const UPPER = {
17037
17088
  description: _t("Converts a specified string to uppercase."),
17038
17089
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
17039
17090
  compute: function(text) {
17040
- return toString(text).toUpperCase();
17091
+ return toLocaleString(text, this.locale).toUpperCase();
17041
17092
  },
17042
17093
  isExported: true
17043
17094
  };
@@ -17069,7 +17120,7 @@ const HYPERLINK = {
17069
17120
  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."))],
17070
17121
  compute: function(url, linkLabel) {
17071
17122
  const processedUrl = toString(url).trim();
17072
- const processedLabel = toString(linkLabel) || processedUrl;
17123
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
17073
17124
  if (processedUrl === "") return processedLabel;
17074
17125
  return markdownLink(processedLabel, processedUrl);
17075
17126
  },
@@ -17176,12 +17227,15 @@ for (const category of categories) {
17176
17227
  }
17177
17228
  }
17178
17229
  function createComputeFunction(descr) {
17230
+ let currentArgDefinitions = [];
17179
17231
  function vectorizedCompute(...args) {
17180
17232
  const acceptToVectorize = [];
17233
+ currentArgDefinitions = new Array(args.length);
17181
17234
  const getArgToFocus = argTargeting(descr, args.length);
17182
17235
  for (let i = 0; i < args.length; i++) {
17183
17236
  const argIndex = getArgToFocus(i) ?? -1;
17184
17237
  const argDefinition = descr.args[argIndex];
17238
+ currentArgDefinitions[i] = argDefinition;
17185
17239
  const arg = args[i];
17186
17240
  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()));
17187
17241
  acceptToVectorize.push(!argDefinition.acceptMatrix);
@@ -17196,8 +17250,7 @@ function createComputeFunction(descr) {
17196
17250
  function errorHandlingCompute(...args) {
17197
17251
  for (let i = 0; i < args.length; i++) {
17198
17252
  const arg = args[i];
17199
- const getArgToFocus = argTargeting(descr, args.length);
17200
- if (!descr.args[getArgToFocus(i) || i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
17253
+ if (!currentArgDefinitions[i].acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) return arg;
17201
17254
  }
17202
17255
  try {
17203
17256
  return computeFunctionToObject.apply(this, args);
@@ -31251,12 +31304,13 @@ var FilterMenu = class extends Component {
31251
31304
  }
31252
31305
  sortFilterZone(sortDirection) {
31253
31306
  const filterPosition = this.props.filterPosition;
31254
- const tableZone = this.table?.range.zone;
31307
+ const table = this.table;
31308
+ const tableZone = table?.range.zone;
31255
31309
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
31256
31310
  const sheetId = this.env.model.getters.getActiveSheetId();
31257
31311
  const contentZone = {
31258
31312
  ...tableZone,
31259
- top: tableZone.top + 1
31313
+ top: tableZone.top + table.config.numberOfHeaders
31260
31314
  };
31261
31315
  const sortAnchor = {
31262
31316
  col: filterPosition.col,
@@ -47927,6 +47981,7 @@ css`
47927
47981
  .o-spreadsheet {
47928
47982
  .os-input {
47929
47983
  border-width: 0 0 1px 0;
47984
+ border-style: solid;
47930
47985
  border-color: transparent;
47931
47986
  outline: none;
47932
47987
  text-overflow: ellipsis;
@@ -70906,6 +70961,7 @@ css`
70906
70961
  width: 100%;
70907
70962
  outline: none;
70908
70963
  border-color: ${GRAY_300};
70964
+ border-style: solid;
70909
70965
  color: ${GRAY_900};
70910
70966
 
70911
70967
  &::placeholder {
@@ -75380,6 +75436,6 @@ const chartHelpers = {
75380
75436
  //#endregion
75381
75437
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, 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 };
75382
75438
 
75383
- __info__.version = "18.4.50";
75384
- __info__.date = "2026-07-30T06:52:55.061Z";
75385
- __info__.hash = "0a41352";
75439
+ __info__.version = "18.4.52";
75440
+ __info__.date = "2026-08-21T15:29:26.810Z";
75441
+ __info__.hash = "5715a49";