@odoo/o-spreadsheet 19.3.15 → 19.3.17

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 19.3.15
6
- * @date 2026-08-10T12:32:40.247Z
7
- * @hash 25f760e
5
+ * @version 19.3.17
6
+ * @date 2026-08-21T15:31:50.940Z
7
+ * @hash cf8c614
8
8
  */
9
9
 
10
10
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
@@ -4070,6 +4070,18 @@ function toString(data) {
4070
4070
  default: return "";
4071
4071
  }
4072
4072
  }
4073
+ /**
4074
+ * Only converts the decimal separator, ignore number format such as % or dates
4075
+ */
4076
+ function toLocaleString(data, locale) {
4077
+ const value = toValue(data);
4078
+ switch (typeof value) {
4079
+ case "string": return value;
4080
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
4081
+ case "boolean": return value ? "TRUE" : "FALSE";
4082
+ default: return "";
4083
+ }
4084
+ }
4073
4085
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
4074
4086
  const normalizeString = memoize(function normalizeString(str) {
4075
4087
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -4744,6 +4756,9 @@ var FunctionRegistry = class extends Registry {
4744
4756
  }
4745
4757
  };
4746
4758
  const functionRegistry = new FunctionRegistry();
4759
+ /** Formulas using one of these functions are never squished: they are always exported
4760
+ * as a full formula string, and they don't become the base formula of the next cells. */
4761
+ const NonSquishableFunctionRegistry = new Registry();
4747
4762
 
4748
4763
  //#endregion
4749
4764
  //#region src/types/locale.ts
@@ -29085,12 +29100,13 @@ var FilterMenu = class extends _odoo_owl.Component {
29085
29100
  }
29086
29101
  sortFilterZone(sortDirection) {
29087
29102
  const filterPosition = this.props.filterPosition;
29088
- const tableZone = this.table?.range.zone;
29103
+ const table = this.table;
29104
+ const tableZone = table?.range.zone;
29089
29105
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
29090
29106
  const sheetId = this.env.model.getters.getActiveSheetId();
29091
29107
  const contentZone = {
29092
29108
  ...tableZone,
29093
- top: tableZone.top + 1
29109
+ top: tableZone.top + table.config.numberOfHeaders
29094
29110
  };
29095
29111
  const sortAnchor = {
29096
29112
  col: filterPosition.col,
@@ -49864,7 +49880,8 @@ var Squisher = class {
49864
49880
  * The result of this method is:
49865
49881
  * - if the cell is not a formula, returns the content as is and resets the base formula
49866
49882
  * - if the cell is a formula:
49867
- * - if there is no previous formula, or the normalized formula is different from the previous one, resets the base formula to this one and returns the full formula string
49883
+ * - if there is no previous formula, or the normalized formula is different from the previous one or it contains blacklisted functions (see NonSquishableFunctionRegistry),
49884
+ * resets the base formula to this one and returns the full formula string
49868
49885
  * - else, compares the literal values and range dependencies to the previous formula, and for each parameter:
49869
49886
  * - for numbers: returns a relative change (+N or -N) if possible, else the full number or "=" if unchanged
49870
49887
  * - for strings: returns the full string if changed, else "="
@@ -49872,6 +49889,10 @@ var Squisher = class {
49872
49889
  * */
49873
49890
  squish(cell, forSheetId) {
49874
49891
  if (cell.isFormula) {
49892
+ if (NonSquishableFunctionRegistry.getAll().some((functionName) => cell.compiledFormula.usesSymbol(functionName))) {
49893
+ this.resetBaseFormula();
49894
+ return cell.compiledFormula.toFormulaString(this.getters, this.rangeToStringOptions);
49895
+ }
49875
49896
  let numbers = [];
49876
49897
  let strings = [];
49877
49898
  let references = [];
@@ -61237,7 +61258,7 @@ var Session = class extends EventBus {
61237
61258
  }
61238
61259
  message = {
61239
61260
  ...message,
61240
- commands: this.commandSquisher.squish(revision.commands)
61261
+ commands: revision.commands
61241
61262
  };
61242
61263
  }
61243
61264
  if (this.isReplayingInitialRevisions) throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.
@@ -77391,7 +77412,7 @@ const ARRAYTOTEXT = {
77391
77412
  else if (_format === 0) {
77392
77413
  const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
77393
77414
  return transposeMatrix(_array).flatMap((row) => row.map((value) => {
77394
- return isEvaluationError(value.value) ? value.value : toString(value);
77415
+ return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
77395
77416
  })).join(rowSeparator);
77396
77417
  } else return new EvaluationError(_t("Format must be 0 or 1"));
77397
77418
  },
@@ -83152,7 +83173,7 @@ const CONCAT = {
83152
83173
  description: _t("Concatenation of two values."),
83153
83174
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
83154
83175
  compute: function(value1, value2) {
83155
- return toString(value1) + toString(value2);
83176
+ return toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale);
83156
83177
  },
83157
83178
  isExported: true
83158
83179
  };
@@ -84017,7 +84038,7 @@ const CLEAN = {
84017
84038
  description: _t("Remove non-printable characters from a piece of text."),
84018
84039
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
84019
84040
  compute: function(text) {
84020
- const _text = toString(text);
84041
+ const _text = toLocaleString(text, this.locale);
84021
84042
  let cleanedStr = "";
84022
84043
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
84023
84044
  return cleanedStr;
@@ -84028,7 +84049,7 @@ const CONCATENATE = {
84028
84049
  description: _t("Appends strings to one another."),
84029
84050
  args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
84030
84051
  compute: function(...datas) {
84031
- return reduceAny(datas, (acc, a) => acc + toString(a), "");
84052
+ return reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "");
84032
84053
  },
84033
84054
  isExported: true
84034
84055
  };
@@ -84036,7 +84057,7 @@ const EXACT = {
84036
84057
  description: _t("Tests whether two strings are identical."),
84037
84058
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
84038
84059
  compute: function(string1, string2) {
84039
- return toString(string1) === toString(string2);
84060
+ return toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale);
84040
84061
  },
84041
84062
  isExported: true
84042
84063
  };
@@ -84048,8 +84069,8 @@ const FIND = {
84048
84069
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
84049
84070
  ],
84050
84071
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
84051
- const _searchFor = toString(searchFor);
84052
- const _textToSearch = toString(textToSearch);
84072
+ const _searchFor = toLocaleString(searchFor, this.locale);
84073
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
84053
84074
  const _startingAt = toNumber(startingAt, this.locale);
84054
84075
  if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
84055
84076
  if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
@@ -84086,8 +84107,8 @@ const JOIN = {
84086
84107
  description: _t("Concatenates elements of arrays with delimiter."),
84087
84108
  args: [arg("delimiter (string)", _t("The character or string to place between each concatenated value.")), arg("value_or_array (string, range<string>, repeating)", _t("Value to be appended using delimiter."))],
84088
84109
  compute: function(delimiter, ...valuesOrArrays) {
84089
- const _delimiter = toString(delimiter);
84090
- return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "");
84110
+ const _delimiter = toLocaleString(delimiter, this.locale);
84111
+ return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "");
84091
84112
  }
84092
84113
  };
84093
84114
  const LEFT = {
@@ -84096,7 +84117,7 @@ const LEFT = {
84096
84117
  compute: function(text, ...args) {
84097
84118
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
84098
84119
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
84099
- return toString(text).substring(0, _numberOfCharacters);
84120
+ return toLocaleString(text, this.locale).substring(0, _numberOfCharacters);
84100
84121
  },
84101
84122
  isExported: true
84102
84123
  };
@@ -84104,7 +84125,7 @@ const LEN = {
84104
84125
  description: _t("Length of a string."),
84105
84126
  args: [arg("text (string)", _t("The string whose length will be returned."))],
84106
84127
  compute: function(text) {
84107
- return toString(text).length;
84128
+ return toLocaleString(text, this.locale).length;
84108
84129
  },
84109
84130
  isExported: true
84110
84131
  };
@@ -84112,7 +84133,7 @@ const LOWER = {
84112
84133
  description: _t("Converts a specified string to lowercase."),
84113
84134
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
84114
84135
  compute: function(text) {
84115
- return toString(text).toLowerCase();
84136
+ return toLocaleString(text, this.locale).toLowerCase();
84116
84137
  },
84117
84138
  isExported: true
84118
84139
  };
@@ -84124,7 +84145,7 @@ const MID = {
84124
84145
  arg("extract_length (number)", _t("The length of the segment to extract."))
84125
84146
  ],
84126
84147
  compute: function(text, starting_at, extract_length) {
84127
- const _text = toString(text);
84148
+ const _text = toLocaleString(text, this.locale);
84128
84149
  const _starting_at = toNumber(starting_at, this.locale);
84129
84150
  const _extract_length = toNumber(extract_length, this.locale);
84130
84151
  if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -84137,7 +84158,7 @@ const PROPER = {
84137
84158
  description: _t("Capitalizes each word in a specified string."),
84138
84159
  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."))],
84139
84160
  compute: function(text) {
84140
- return toString(text).replace(wordRegex, (word) => {
84161
+ return toLocaleString(text, this.locale).replace(wordRegex, (word) => {
84141
84162
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
84142
84163
  });
84143
84164
  },
@@ -84285,9 +84306,9 @@ const REPLACE = {
84285
84306
  compute: function(text, position, length, newText) {
84286
84307
  const _position = toNumber(position, this.locale);
84287
84308
  if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
84288
- const _text = toString(text);
84309
+ const _text = toLocaleString(text, this.locale);
84289
84310
  const _length = toNumber(length, this.locale);
84290
- const _newText = toString(newText);
84311
+ const _newText = toLocaleString(newText, this.locale);
84291
84312
  return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);
84292
84313
  },
84293
84314
  isExported: true
@@ -84298,7 +84319,7 @@ const RIGHT = {
84298
84319
  compute: function(text, ...args) {
84299
84320
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
84300
84321
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
84301
- const _text = toString(text);
84322
+ const _text = toLocaleString(text, this.locale);
84302
84323
  const stringLength = _text.length;
84303
84324
  return _text.substring(stringLength - _numberOfCharacters, stringLength);
84304
84325
  },
@@ -84312,8 +84333,8 @@ const SEARCH = {
84312
84333
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
84313
84334
  ],
84314
84335
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
84315
- const _searchFor = toString(searchFor).toLowerCase();
84316
- const _textToSearch = toString(textToSearch).toLowerCase();
84336
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
84337
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
84317
84338
  const _startingAt = toNumber(startingAt, this.locale);
84318
84339
  if (_textToSearch === "") return {
84319
84340
  value: CellErrorType.GenericError,
@@ -84343,8 +84364,8 @@ const SPLIT = {
84343
84364
  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."))
84344
84365
  ],
84345
84366
  compute: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
84346
- const _text = toString(text);
84347
- const _delimiter = escapeRegExp(toString(delimiter));
84367
+ const _text = toLocaleString(text, this.locale);
84368
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
84348
84369
  const _splitByEach = toBoolean(splitByEach);
84349
84370
  const _removeEmptyText = toBoolean(removeEmptyText);
84350
84371
  if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
@@ -84366,10 +84387,10 @@ const SUBSTITUTE = {
84366
84387
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
84367
84388
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
84368
84389
  if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
84369
- const _textToSearch = toString(textToSearch);
84370
- const _searchFor = toString(searchFor);
84390
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
84391
+ const _searchFor = toLocaleString(searchFor, this.locale);
84371
84392
  if (_searchFor === "") return _textToSearch;
84372
- const _replaceWith = toString(replaceWith);
84393
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
84373
84394
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
84374
84395
  if (_occurrenceNumber === 0) return _textToSearch.replace(reg, _replaceWith);
84375
84396
  let n = 0;
@@ -84392,10 +84413,10 @@ const TEXTJOIN = {
84392
84413
  arg("texts (string, range<string>, repeating)", _t("Text item to join."))
84393
84414
  ],
84394
84415
  compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
84395
- const _delimiter = toString(delimiter);
84416
+ const _delimiter = toLocaleString(delimiter, this.locale);
84396
84417
  const _ignoreEmpty = toBoolean(ignoreEmpty);
84397
84418
  let n = 0;
84398
- return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "");
84419
+ return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "");
84399
84420
  },
84400
84421
  isExported: true
84401
84422
  };
@@ -84448,7 +84469,7 @@ const TRIM = {
84448
84469
  description: _t("Removes space characters."),
84449
84470
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
84450
84471
  compute: function(text) {
84451
- return trimContent(toString(text));
84472
+ return trimContent(toLocaleString(text, this.locale));
84452
84473
  },
84453
84474
  isExported: true
84454
84475
  };
@@ -84456,7 +84477,7 @@ const UPPER = {
84456
84477
  description: _t("Converts a specified string to uppercase."),
84457
84478
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
84458
84479
  compute: function(text) {
84459
- return toString(text).toUpperCase();
84480
+ return toLocaleString(text, this.locale).toUpperCase();
84460
84481
  },
84461
84482
  isExported: true
84462
84483
  };
@@ -84553,7 +84574,7 @@ const HYPERLINK = {
84553
84574
  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."))],
84554
84575
  compute: function(url, linkLabel) {
84555
84576
  const processedUrl = toString(url).trim();
84556
- const processedLabel = toString(linkLabel) || processedUrl;
84577
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
84557
84578
  if (processedUrl === "") return processedLabel;
84558
84579
  return markdownLink(processedLabel, processedUrl);
84559
84580
  },
@@ -84696,7 +84717,8 @@ const registries = {
84696
84717
  supportedPivotPositionalFormulaRegistry,
84697
84718
  pivotToFunctionValueRegistry,
84698
84719
  migrationStepRegistry,
84699
- chartJsExtensionRegistry
84720
+ chartJsExtensionRegistry,
84721
+ NonSquishableFunctionRegistry
84700
84722
  };
84701
84723
  const helpers = {
84702
84724
  arg,
@@ -84949,6 +84971,6 @@ exports.stores = stores;
84949
84971
  exports.tokenColors = tokenColors;
84950
84972
  exports.tokenize = tokenize;
84951
84973
 
84952
- __info__.version = "19.3.15";
84953
- __info__.date = "2026-08-10T12:32:40.247Z";
84954
- __info__.hash = "25f760e";
84974
+ __info__.version = "19.3.17";
84975
+ __info__.date = "2026-08-21T15:31:50.940Z";
84976
+ __info__.hash = "cf8c614";
@@ -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 19.3.15
6
- * @date 2026-08-10T12:32:41.948Z
7
- * @hash 25f760e
5
+ * @version 19.3.17
6
+ * @date 2026-08-21T15:31:52.347Z
7
+ * @hash cf8c614
8
8
  */
9
9
  :root {
10
10
  --os-gray-100: light-dark(#f9fafb, #1b1d26);
@@ -300,6 +300,7 @@
300
300
  .os-input {
301
301
  border-width: 0 0 1px 0;
302
302
  border-color: transparent;
303
+ border-style: solid;
303
304
  outline: none;
304
305
  text-overflow: ellipsis;
305
306
  color: var(--os-text-body);
@@ -630,6 +631,7 @@
630
631
  width: 100%;
631
632
  outline: none;
632
633
  border-color: var(--os-border-color);
634
+ border-style: solid;
633
635
  color: var(--os-gray-900);
634
636
 
635
637
  &::placeholder {
@@ -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 19.3.15
6
- * @date 2026-08-10T12:32:40.247Z
7
- * @hash 25f760e
5
+ * @version 19.3.17
6
+ * @date 2026-08-21T15:31:50.940Z
7
+ * @hash cf8c614
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, whenReady, xml } from "@odoo/owl";
@@ -4069,6 +4069,18 @@ function toString(data) {
4069
4069
  default: return "";
4070
4070
  }
4071
4071
  }
4072
+ /**
4073
+ * Only converts the decimal separator, ignore number format such as % or dates
4074
+ */
4075
+ function toLocaleString(data, locale) {
4076
+ const value = toValue(data);
4077
+ switch (typeof value) {
4078
+ case "string": return value;
4079
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
4080
+ case "boolean": return value ? "TRUE" : "FALSE";
4081
+ default: return "";
4082
+ }
4083
+ }
4072
4084
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
4073
4085
  const normalizeString = memoize(function normalizeString(str) {
4074
4086
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -4743,6 +4755,9 @@ var FunctionRegistry = class extends Registry {
4743
4755
  }
4744
4756
  };
4745
4757
  const functionRegistry = new FunctionRegistry();
4758
+ /** Formulas using one of these functions are never squished: they are always exported
4759
+ * as a full formula string, and they don't become the base formula of the next cells. */
4760
+ const NonSquishableFunctionRegistry = new Registry();
4746
4761
 
4747
4762
  //#endregion
4748
4763
  //#region src/types/locale.ts
@@ -29084,12 +29099,13 @@ var FilterMenu = class extends Component {
29084
29099
  }
29085
29100
  sortFilterZone(sortDirection) {
29086
29101
  const filterPosition = this.props.filterPosition;
29087
- const tableZone = this.table?.range.zone;
29102
+ const table = this.table;
29103
+ const tableZone = table?.range.zone;
29088
29104
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
29089
29105
  const sheetId = this.env.model.getters.getActiveSheetId();
29090
29106
  const contentZone = {
29091
29107
  ...tableZone,
29092
- top: tableZone.top + 1
29108
+ top: tableZone.top + table.config.numberOfHeaders
29093
29109
  };
29094
29110
  const sortAnchor = {
29095
29111
  col: filterPosition.col,
@@ -49863,7 +49879,8 @@ var Squisher = class {
49863
49879
  * The result of this method is:
49864
49880
  * - if the cell is not a formula, returns the content as is and resets the base formula
49865
49881
  * - if the cell is a formula:
49866
- * - if there is no previous formula, or the normalized formula is different from the previous one, resets the base formula to this one and returns the full formula string
49882
+ * - if there is no previous formula, or the normalized formula is different from the previous one or it contains blacklisted functions (see NonSquishableFunctionRegistry),
49883
+ * resets the base formula to this one and returns the full formula string
49867
49884
  * - else, compares the literal values and range dependencies to the previous formula, and for each parameter:
49868
49885
  * - for numbers: returns a relative change (+N or -N) if possible, else the full number or "=" if unchanged
49869
49886
  * - for strings: returns the full string if changed, else "="
@@ -49871,6 +49888,10 @@ var Squisher = class {
49871
49888
  * */
49872
49889
  squish(cell, forSheetId) {
49873
49890
  if (cell.isFormula) {
49891
+ if (NonSquishableFunctionRegistry.getAll().some((functionName) => cell.compiledFormula.usesSymbol(functionName))) {
49892
+ this.resetBaseFormula();
49893
+ return cell.compiledFormula.toFormulaString(this.getters, this.rangeToStringOptions);
49894
+ }
49874
49895
  let numbers = [];
49875
49896
  let strings = [];
49876
49897
  let references = [];
@@ -61052,7 +61073,7 @@ var Session = class extends EventBus {
61052
61073
  }
61053
61074
  message = {
61054
61075
  ...message,
61055
- commands: this.commandSquisher.squish(revision.commands)
61076
+ commands: revision.commands
61056
61077
  };
61057
61078
  }
61058
61079
  if (this.isReplayingInitialRevisions) throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.
@@ -77206,7 +77227,7 @@ const ARRAYTOTEXT = {
77206
77227
  else if (_format === 0) {
77207
77228
  const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
77208
77229
  return transposeMatrix(_array).flatMap((row) => row.map((value) => {
77209
- return isEvaluationError(value.value) ? value.value : toString(value);
77230
+ return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
77210
77231
  })).join(rowSeparator);
77211
77232
  } else return new EvaluationError(_t("Format must be 0 or 1"));
77212
77233
  },
@@ -82967,7 +82988,7 @@ const CONCAT = {
82967
82988
  description: _t("Concatenation of two values."),
82968
82989
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
82969
82990
  compute: function(value1, value2) {
82970
- return toString(value1) + toString(value2);
82991
+ return toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale);
82971
82992
  },
82972
82993
  isExported: true
82973
82994
  };
@@ -83832,7 +83853,7 @@ const CLEAN = {
83832
83853
  description: _t("Remove non-printable characters from a piece of text."),
83833
83854
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
83834
83855
  compute: function(text) {
83835
- const _text = toString(text);
83856
+ const _text = toLocaleString(text, this.locale);
83836
83857
  let cleanedStr = "";
83837
83858
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
83838
83859
  return cleanedStr;
@@ -83843,7 +83864,7 @@ const CONCATENATE = {
83843
83864
  description: _t("Appends strings to one another."),
83844
83865
  args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
83845
83866
  compute: function(...datas) {
83846
- return reduceAny(datas, (acc, a) => acc + toString(a), "");
83867
+ return reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "");
83847
83868
  },
83848
83869
  isExported: true
83849
83870
  };
@@ -83851,7 +83872,7 @@ const EXACT = {
83851
83872
  description: _t("Tests whether two strings are identical."),
83852
83873
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
83853
83874
  compute: function(string1, string2) {
83854
- return toString(string1) === toString(string2);
83875
+ return toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale);
83855
83876
  },
83856
83877
  isExported: true
83857
83878
  };
@@ -83863,8 +83884,8 @@ const FIND = {
83863
83884
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
83864
83885
  ],
83865
83886
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
83866
- const _searchFor = toString(searchFor);
83867
- const _textToSearch = toString(textToSearch);
83887
+ const _searchFor = toLocaleString(searchFor, this.locale);
83888
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
83868
83889
  const _startingAt = toNumber(startingAt, this.locale);
83869
83890
  if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
83870
83891
  if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
@@ -83901,8 +83922,8 @@ const JOIN = {
83901
83922
  description: _t("Concatenates elements of arrays with delimiter."),
83902
83923
  args: [arg("delimiter (string)", _t("The character or string to place between each concatenated value.")), arg("value_or_array (string, range<string>, repeating)", _t("Value to be appended using delimiter."))],
83903
83924
  compute: function(delimiter, ...valuesOrArrays) {
83904
- const _delimiter = toString(delimiter);
83905
- return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "");
83925
+ const _delimiter = toLocaleString(delimiter, this.locale);
83926
+ return reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "");
83906
83927
  }
83907
83928
  };
83908
83929
  const LEFT = {
@@ -83911,7 +83932,7 @@ const LEFT = {
83911
83932
  compute: function(text, ...args) {
83912
83933
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
83913
83934
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
83914
- return toString(text).substring(0, _numberOfCharacters);
83935
+ return toLocaleString(text, this.locale).substring(0, _numberOfCharacters);
83915
83936
  },
83916
83937
  isExported: true
83917
83938
  };
@@ -83919,7 +83940,7 @@ const LEN = {
83919
83940
  description: _t("Length of a string."),
83920
83941
  args: [arg("text (string)", _t("The string whose length will be returned."))],
83921
83942
  compute: function(text) {
83922
- return toString(text).length;
83943
+ return toLocaleString(text, this.locale).length;
83923
83944
  },
83924
83945
  isExported: true
83925
83946
  };
@@ -83927,7 +83948,7 @@ const LOWER = {
83927
83948
  description: _t("Converts a specified string to lowercase."),
83928
83949
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
83929
83950
  compute: function(text) {
83930
- return toString(text).toLowerCase();
83951
+ return toLocaleString(text, this.locale).toLowerCase();
83931
83952
  },
83932
83953
  isExported: true
83933
83954
  };
@@ -83939,7 +83960,7 @@ const MID = {
83939
83960
  arg("extract_length (number)", _t("The length of the segment to extract."))
83940
83961
  ],
83941
83962
  compute: function(text, starting_at, extract_length) {
83942
- const _text = toString(text);
83963
+ const _text = toLocaleString(text, this.locale);
83943
83964
  const _starting_at = toNumber(starting_at, this.locale);
83944
83965
  const _extract_length = toNumber(extract_length, this.locale);
83945
83966
  if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -83952,7 +83973,7 @@ const PROPER = {
83952
83973
  description: _t("Capitalizes each word in a specified string."),
83953
83974
  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."))],
83954
83975
  compute: function(text) {
83955
- return toString(text).replace(wordRegex, (word) => {
83976
+ return toLocaleString(text, this.locale).replace(wordRegex, (word) => {
83956
83977
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
83957
83978
  });
83958
83979
  },
@@ -84100,9 +84121,9 @@ const REPLACE = {
84100
84121
  compute: function(text, position, length, newText) {
84101
84122
  const _position = toNumber(position, this.locale);
84102
84123
  if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
84103
- const _text = toString(text);
84124
+ const _text = toLocaleString(text, this.locale);
84104
84125
  const _length = toNumber(length, this.locale);
84105
- const _newText = toString(newText);
84126
+ const _newText = toLocaleString(newText, this.locale);
84106
84127
  return _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length);
84107
84128
  },
84108
84129
  isExported: true
@@ -84113,7 +84134,7 @@ const RIGHT = {
84113
84134
  compute: function(text, ...args) {
84114
84135
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
84115
84136
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
84116
- const _text = toString(text);
84137
+ const _text = toLocaleString(text, this.locale);
84117
84138
  const stringLength = _text.length;
84118
84139
  return _text.substring(stringLength - _numberOfCharacters, stringLength);
84119
84140
  },
@@ -84127,8 +84148,8 @@ const SEARCH = {
84127
84148
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
84128
84149
  ],
84129
84150
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
84130
- const _searchFor = toString(searchFor).toLowerCase();
84131
- const _textToSearch = toString(textToSearch).toLowerCase();
84151
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
84152
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
84132
84153
  const _startingAt = toNumber(startingAt, this.locale);
84133
84154
  if (_textToSearch === "") return {
84134
84155
  value: CellErrorType.GenericError,
@@ -84158,8 +84179,8 @@ const SPLIT = {
84158
84179
  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."))
84159
84180
  ],
84160
84181
  compute: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
84161
- const _text = toString(text);
84162
- const _delimiter = escapeRegExp(toString(delimiter));
84182
+ const _text = toLocaleString(text, this.locale);
84183
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
84163
84184
  const _splitByEach = toBoolean(splitByEach);
84164
84185
  const _removeEmptyText = toBoolean(removeEmptyText);
84165
84186
  if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
@@ -84181,10 +84202,10 @@ const SUBSTITUTE = {
84181
84202
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
84182
84203
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
84183
84204
  if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
84184
- const _textToSearch = toString(textToSearch);
84185
- const _searchFor = toString(searchFor);
84205
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
84206
+ const _searchFor = toLocaleString(searchFor, this.locale);
84186
84207
  if (_searchFor === "") return _textToSearch;
84187
- const _replaceWith = toString(replaceWith);
84208
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
84188
84209
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
84189
84210
  if (_occurrenceNumber === 0) return _textToSearch.replace(reg, _replaceWith);
84190
84211
  let n = 0;
@@ -84207,10 +84228,10 @@ const TEXTJOIN = {
84207
84228
  arg("texts (string, range<string>, repeating)", _t("Text item to join."))
84208
84229
  ],
84209
84230
  compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
84210
- const _delimiter = toString(delimiter);
84231
+ const _delimiter = toLocaleString(delimiter, this.locale);
84211
84232
  const _ignoreEmpty = toBoolean(ignoreEmpty);
84212
84233
  let n = 0;
84213
- return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "");
84234
+ return reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "");
84214
84235
  },
84215
84236
  isExported: true
84216
84237
  };
@@ -84263,7 +84284,7 @@ const TRIM = {
84263
84284
  description: _t("Removes space characters."),
84264
84285
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
84265
84286
  compute: function(text) {
84266
- return trimContent(toString(text));
84287
+ return trimContent(toLocaleString(text, this.locale));
84267
84288
  },
84268
84289
  isExported: true
84269
84290
  };
@@ -84271,7 +84292,7 @@ const UPPER = {
84271
84292
  description: _t("Converts a specified string to uppercase."),
84272
84293
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
84273
84294
  compute: function(text) {
84274
- return toString(text).toUpperCase();
84295
+ return toLocaleString(text, this.locale).toUpperCase();
84275
84296
  },
84276
84297
  isExported: true
84277
84298
  };
@@ -84368,7 +84389,7 @@ const HYPERLINK = {
84368
84389
  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."))],
84369
84390
  compute: function(url, linkLabel) {
84370
84391
  const processedUrl = toString(url).trim();
84371
- const processedLabel = toString(linkLabel) || processedUrl;
84392
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
84372
84393
  if (processedUrl === "") return processedLabel;
84373
84394
  return markdownLink(processedLabel, processedUrl);
84374
84395
  },
@@ -84511,7 +84532,8 @@ const registries = {
84511
84532
  supportedPivotPositionalFormulaRegistry,
84512
84533
  pivotToFunctionValueRegistry,
84513
84534
  migrationStepRegistry,
84514
- chartJsExtensionRegistry
84535
+ chartJsExtensionRegistry,
84536
+ NonSquishableFunctionRegistry
84515
84537
  };
84516
84538
  const helpers = {
84517
84539
  arg,
@@ -84706,6 +84728,6 @@ const chartHelpers = {
84706
84728
  //#endregion
84707
84729
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CompiledFormula, CorePlugin, CoreViewPlugin, DEFAULT_LOCALE, DEFAULT_LOCALES, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, canExecuteInReadonly, categories, chartHelpers, components, constants, convertAstNodes, coreTypes, createAutocompleteArgumentsProvider, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, isCoreCommand, isSheetDependent, iterateAstNodes, links, load, lockedSheetAllowedCommands, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
84708
84730
 
84709
- __info__.version = "19.3.15";
84710
- __info__.date = "2026-08-10T12:32:40.247Z";
84711
- __info__.hash = "25f760e";
84731
+ __info__.version = "19.3.17";
84732
+ __info__.date = "2026-08-21T15:31:50.940Z";
84733
+ __info__.hash = "cf8c614";