@odoo/o-spreadsheet 18.0.4 → 18.0.5

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.4
6
- * @date 2024-11-13T15:08:50.620Z
7
- * @hash 88e1aee
5
+ * @version 18.0.5
6
+ * @date 2024-11-22T14:19:19.721Z
7
+ * @hash 9eb34d9
8
8
  */
9
9
 
10
10
  'use strict';
@@ -130,7 +130,6 @@ const SELECTION_BORDER_COLOR = "#3266ca";
130
130
  const HEADER_BORDER_COLOR = "#C0C0C0";
131
131
  const CELL_BORDER_COLOR = "#E2E3E3";
132
132
  const BACKGROUND_CHART_COLOR = "#FFFFFF";
133
- const BORDER_CHART_COLOR = "#FFFFFF";
134
133
  const DISABLED_TEXT_COLOR = "#CACACA";
135
134
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
136
135
  const LINK_COLOR = "#017E84";
@@ -3224,7 +3223,6 @@ var ClipboardMIMEType;
3224
3223
  (function (ClipboardMIMEType) {
3225
3224
  ClipboardMIMEType["PlainText"] = "text/plain";
3226
3225
  ClipboardMIMEType["Html"] = "text/html";
3227
- ClipboardMIMEType["OSpreadsheet"] = "web application/o-spreadsheet";
3228
3226
  })(ClipboardMIMEType || (ClipboardMIMEType = {}));
3229
3227
 
3230
3228
  function isSheetDependent(cmd) {
@@ -4334,7 +4332,9 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4334
4332
  return reverseSearch ? numberOfValues - i - 1 : i;
4335
4333
  }
4336
4334
  }
4337
- return reverseSearch ? numberOfValues - closestMatchIndex - 1 : closestMatchIndex;
4335
+ return reverseSearch && closestMatchIndex !== -1
4336
+ ? numberOfValues - closestMatchIndex - 1
4337
+ : closestMatchIndex;
4338
4338
  }
4339
4339
  /**
4340
4340
  * Normalize a value.
@@ -6147,6 +6147,22 @@ function getPasteZones(target, content) {
6147
6147
  const width = content[0].length, height = content.length;
6148
6148
  return target.map((t) => splitZoneForPaste(t, width, height)).flat();
6149
6149
  }
6150
+ function parseOSClipboardContent(content) {
6151
+ if (!content[ClipboardMIMEType.Html]) {
6152
+ return {
6153
+ text: content[ClipboardMIMEType.PlainText],
6154
+ };
6155
+ }
6156
+ const htmlDocument = new DOMParser().parseFromString(content[ClipboardMIMEType.Html], "text/html");
6157
+ const oSheetClipboardData = htmlDocument
6158
+ .querySelector("div")
6159
+ ?.getAttribute("data-osheet-clipboard");
6160
+ const spreadsheetContent = oSheetClipboardData && JSON.parse(oSheetClipboardData);
6161
+ return {
6162
+ text: content[ClipboardMIMEType.PlainText],
6163
+ data: spreadsheetContent,
6164
+ };
6165
+ }
6150
6166
 
6151
6167
  class ClipboardHandler {
6152
6168
  getters;
@@ -6168,7 +6184,7 @@ class ClipboardHandler {
6168
6184
  getPasteTarget(sheetId, target, content, options) {
6169
6185
  return { zones: [], sheetId };
6170
6186
  }
6171
- convertOSClipboardData(data) {
6187
+ convertTextToClipboardData(data) {
6172
6188
  return;
6173
6189
  }
6174
6190
  }
@@ -7998,7 +8014,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
7998
8014
  this.dispatch("CLEAR_CELL", target);
7999
8015
  }
8000
8016
  }
8001
- convertOSClipboardData(text) {
8017
+ convertTextToClipboardData(text) {
8002
8018
  const locale = this.getters.getLocale();
8003
8019
  const copiedData = {
8004
8020
  cells: [],
@@ -8106,6 +8122,7 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
8106
8122
 
8107
8123
  class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8108
8124
  uuidGenerator = new UuidGenerator();
8125
+ queuedChanges = {};
8109
8126
  copy(data) {
8110
8127
  if (!data.zones.length) {
8111
8128
  return;
@@ -8127,6 +8144,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8127
8144
  return { cfRules };
8128
8145
  }
8129
8146
  paste(target, clippedContent, options) {
8147
+ this.queuedChanges = {};
8130
8148
  if (options.pasteOption === "asValue") {
8131
8149
  return;
8132
8150
  }
@@ -8138,6 +8156,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8138
8156
  else {
8139
8157
  this.pasteFromCut(sheetId, zones, clippedContent);
8140
8158
  }
8159
+ this.executeQueuedChanges();
8141
8160
  }
8142
8161
  pasteFromCut(sheetId, target, content) {
8143
8162
  const selection = target[0];
@@ -8177,34 +8196,56 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8177
8196
  * Add or remove cells to a given conditional formatting rule.
8178
8197
  */
8179
8198
  adaptCFRules(sheetId, cf, toAdd, toRemove) {
8180
- const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
8181
- if (!newRangesXc) {
8182
- return;
8199
+ if (!this.queuedChanges[sheetId]) {
8200
+ this.queuedChanges[sheetId] = [];
8183
8201
  }
8184
- if (newRangesXc.length === 0) {
8185
- this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
8186
- return;
8202
+ const queuedChange = this.queuedChanges[sheetId].find((queued) => queued.cf.id === cf.id);
8203
+ if (!queuedChange) {
8204
+ this.queuedChanges[sheetId].push({ toAdd, toRemove, cf });
8205
+ }
8206
+ else {
8207
+ queuedChange.toAdd.push(...toAdd);
8208
+ queuedChange.toRemove.push(...toRemove);
8209
+ }
8210
+ }
8211
+ executeQueuedChanges() {
8212
+ for (const sheetId in this.queuedChanges) {
8213
+ for (const { toAdd, toRemove, cf } of this.queuedChanges[sheetId]) {
8214
+ const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
8215
+ if (!newRangesXc) {
8216
+ continue;
8217
+ }
8218
+ if (newRangesXc.length === 0) {
8219
+ this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
8220
+ continue;
8221
+ }
8222
+ this.dispatch("ADD_CONDITIONAL_FORMAT", {
8223
+ cf: {
8224
+ id: cf.id,
8225
+ rule: cf.rule,
8226
+ stopIfTrue: cf.stopIfTrue,
8227
+ },
8228
+ ranges: newRangesXc,
8229
+ sheetId,
8230
+ });
8231
+ }
8187
8232
  }
8188
- this.dispatch("ADD_CONDITIONAL_FORMAT", {
8189
- cf: {
8190
- id: cf.id,
8191
- rule: cf.rule,
8192
- stopIfTrue: cf.stopIfTrue,
8193
- },
8194
- ranges: newRangesXc,
8195
- sheetId,
8196
- });
8197
8233
  }
8198
8234
  getCFToCopyTo(targetSheetId, originCF) {
8199
- const cfInTarget = this.getters
8235
+ let targetCF = this.getters
8200
8236
  .getConditionalFormats(targetSheetId)
8201
8237
  .find((cf) => cf.stopIfTrue === originCF.stopIfTrue && deepEquals(cf.rule, originCF.rule));
8202
- return cfInTarget ? cfInTarget : { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
8238
+ const queuedCfs = this.queuedChanges[targetSheetId];
8239
+ if (!targetCF && queuedCfs) {
8240
+ targetCF = queuedCfs.find((queued) => queued.cf.stopIfTrue === originCF.stopIfTrue && deepEquals(queued.cf.rule, originCF.rule))?.cf;
8241
+ }
8242
+ return targetCF || { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
8203
8243
  }
8204
8244
  }
8205
8245
 
8206
8246
  class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8207
8247
  uuidGenerator = new UuidGenerator();
8248
+ queuedChanges = {};
8208
8249
  copy(data) {
8209
8250
  const { rowsIndexes, columnsIndexes } = data;
8210
8251
  const sheetId = data.sheetId;
@@ -8221,6 +8262,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8221
8262
  return { dvRules };
8222
8263
  }
8223
8264
  paste(target, clippedContent, options) {
8265
+ this.queuedChanges = {};
8224
8266
  if (options.pasteOption) {
8225
8267
  return;
8226
8268
  }
@@ -8232,6 +8274,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8232
8274
  else {
8233
8275
  this.pasteFromCut(sheetId, zones, clippedContent);
8234
8276
  }
8277
+ this.executeQueuedChanges();
8235
8278
  }
8236
8279
  pasteFromCut(sheetId, target, content) {
8237
8280
  const selection = target[0];
@@ -8278,29 +8321,55 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8278
8321
  }
8279
8322
  }
8280
8323
  getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
8281
- const ruleInTargetSheet = this.getters
8324
+ let targetRule = this.getters
8282
8325
  .getDataValidationRules(targetSheetId)
8283
8326
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
8284
8327
  originRule.isBlocking === rule.isBlocking);
8285
- return ruleInTargetSheet
8286
- ? ruleInTargetSheet
8287
- : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
8328
+ const queuedRules = this.queuedChanges[targetSheetId];
8329
+ if (!targetRule && queuedRules) {
8330
+ targetRule = queuedRules.find((queued) => deepEquals(originRule.criterion, queued.rule.criterion) &&
8331
+ originRule.isBlocking === queued.rule.isBlocking)?.rule;
8332
+ }
8333
+ return (targetRule || {
8334
+ ...originRule,
8335
+ id: newId ? this.uuidGenerator.uuidv4() : originRule.id,
8336
+ ranges: [],
8337
+ });
8288
8338
  }
8289
8339
  /**
8290
8340
  * Add or remove XCs to a given data validation rule.
8291
8341
  */
8292
8342
  adaptDataValidationRule(sheetId, rule, toAdd, toRemove) {
8293
- const dvZones = rule.ranges.map((range) => range.zone);
8294
- const newDvZones = recomputeZones([...dvZones, ...toAdd], toRemove);
8295
- if (newDvZones.length === 0) {
8296
- this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: rule.id });
8297
- return;
8343
+ if (!this.queuedChanges[sheetId]) {
8344
+ this.queuedChanges[sheetId] = [];
8345
+ }
8346
+ const queuedChange = this.queuedChanges[sheetId].find((queued) => queued.rule.id === rule.id);
8347
+ if (!queuedChange) {
8348
+ this.queuedChanges[sheetId].push({ toAdd, toRemove, rule });
8349
+ }
8350
+ else {
8351
+ queuedChange.toAdd.push(...toAdd);
8352
+ queuedChange.toRemove.push(...toRemove);
8353
+ }
8354
+ }
8355
+ executeQueuedChanges() {
8356
+ for (const sheetId in this.queuedChanges) {
8357
+ for (const { toAdd, toRemove, rule: dv } of this.queuedChanges[sheetId]) {
8358
+ // Remove the zones first in case the same position is in toAdd and toRemove
8359
+ const dvZones = dv.ranges.map((range) => range.zone);
8360
+ const withRemovedZones = recomputeZones(dvZones, toRemove);
8361
+ const newDvZones = recomputeZones([...withRemovedZones, ...toAdd], []);
8362
+ if (newDvZones.length === 0) {
8363
+ this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: dv.id });
8364
+ continue;
8365
+ }
8366
+ this.dispatch("ADD_DATA_VALIDATION_RULE", {
8367
+ rule: dv,
8368
+ ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
8369
+ sheetId,
8370
+ });
8371
+ }
8298
8372
  }
8299
- this.dispatch("ADD_DATA_VALIDATION_RULE", {
8300
- rule,
8301
- ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
8302
- sheetId,
8303
- });
8304
8373
  }
8305
8374
  }
8306
8375
 
@@ -10360,6 +10429,10 @@ function makeArg(str, description) {
10360
10429
  description,
10361
10430
  type: types,
10362
10431
  };
10432
+ const acceptErrors = types.includes("ANY") || types.includes("RANGE");
10433
+ if (acceptErrors) {
10434
+ result.acceptErrors = true;
10435
+ }
10363
10436
  if (isOptional) {
10364
10437
  result.optional = true;
10365
10438
  }
@@ -12423,8 +12496,8 @@ const AVERAGEIFS = {
12423
12496
  const COUNT = {
12424
12497
  description: _t("The number of numeric values in dataset."),
12425
12498
  args: [
12426
- arg("value1 (number, range<number>)", _t("The first value or range to consider when counting.")),
12427
- arg("value2 (number, range<number>, repeating)", _t("Additional values or ranges to consider when counting.")),
12499
+ arg("value1 (number, any, range<number>)", _t("The first value or range to consider when counting.")),
12500
+ arg("value2 (number, any, range<number>, repeating)", _t("Additional values or ranges to consider when counting.")),
12428
12501
  ],
12429
12502
  compute: function (...values) {
12430
12503
  return countNumbers(values, this.locale);
@@ -12813,6 +12886,7 @@ const PEARSON = {
12813
12886
  },
12814
12887
  isExported: true,
12815
12888
  };
12889
+ // CORREL
12816
12890
  // In GSheet, CORREL is just an alias to PEARSON
12817
12891
  const CORREL = PEARSON;
12818
12892
  // -----------------------------------------------------------------------------
@@ -13424,7 +13498,7 @@ function getMatchingCells(database, field, criteria, locale) {
13424
13498
  }
13425
13499
  const databaseArgs = [
13426
13500
  arg("database (range)", _t("The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.")),
13427
- arg("field (any)", _t("Indicates which column in database contains the values to be extracted and operated on.")),
13501
+ arg("field (number, string)", _t("Indicates which column in database contains the values to be extracted and operated on.")),
13428
13502
  arg("criteria (range)", _t("An array or range containing zero or more criteria to filter the database values by before operating.")),
13429
13503
  ];
13430
13504
  // -----------------------------------------------------------------------------
@@ -17783,7 +17857,7 @@ const IFS = {
17783
17857
  args: [
17784
17858
  arg("condition1 (boolean)", _t("The first condition to be evaluated. This can be a boolean, a number, an array, or a reference to any of those.")),
17785
17859
  arg("value1 (any)", _t("The returned value if condition1 is TRUE.")),
17786
- arg("condition2 (boolean, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
17860
+ arg("condition2 (boolean, any, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
17787
17861
  arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
17788
17862
  ],
17789
17863
  compute: function (...values) {
@@ -18022,7 +18096,7 @@ const COLUMNS = {
18022
18096
  const HLOOKUP = {
18023
18097
  description: _t("Horizontal lookup"),
18024
18098
  args: [
18025
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18099
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18026
18100
  arg("range (range)", _t("The range to consider for the search. The first row in the range is searched for the key specified in search_key.")),
18027
18101
  arg("index (number)", _t("The row index of the value to be returned, where the first row in range is numbered 1.")),
18028
18102
  arg(`is_sorted (boolean, default=${DEFAULT_IS_SORTED})`, _t("Indicates whether the row to be searched (the first row of the specified range) is sorted, in which case the closest match for search_key will be returned.")),
@@ -18030,9 +18104,6 @@ const HLOOKUP = {
18030
18104
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18031
18105
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18032
18106
  assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18033
- if (searchKey && isEvaluationError(searchKey.value)) {
18034
- return searchKey;
18035
- }
18036
18107
  const getValueFromRange = (range, index) => range[index][0].value;
18037
18108
  const _isSorted = toBoolean(isSorted.value);
18038
18109
  const colIndex = _isSorted
@@ -18129,7 +18200,7 @@ const INDIRECT = {
18129
18200
  const LOOKUP = {
18130
18201
  description: _t("Look up a value."),
18131
18202
  args: [
18132
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18203
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18133
18204
  arg("search_array (range)", _t("One method of using this function is to provide a single sorted row or column search_array to look through for the search_key with a second argument result_range. The other way is to combine these two arguments into one search_array where the first row or column is searched and a value is returned from the last row or column in the array. If search_key is not found, a non-exact match may be returned.")),
18134
18205
  arg("result_range (range, optional)", _t("The range from which to return a result. The value returned corresponds to the location where search_key is found in search_range. This range must be only a single row or column and should not be used if using the search_result_array method.")),
18135
18206
  ],
@@ -18169,7 +18240,7 @@ const DEFAULT_SEARCH_TYPE = 1;
18169
18240
  const MATCH = {
18170
18241
  description: _t("Position of item in range that matches value."),
18171
18242
  args: [
18172
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18243
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18173
18244
  arg("range (any, range)", _t("The one-dimensional array to be searched.")),
18174
18245
  arg(`search_type (number, default=${DEFAULT_SEARCH_TYPE})`, _t("The search method. 1 (default) finds the largest value less than or equal to search_key when range is sorted in ascending order. 0 finds the exact value when range is unsorted. -1 finds the smallest value greater than or equal to search_key when range is sorted in descending order.")),
18175
18246
  ],
@@ -18244,7 +18315,7 @@ const ROWS = {
18244
18315
  const VLOOKUP = {
18245
18316
  description: _t("Vertical lookup."),
18246
18317
  args: [
18247
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18318
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18248
18319
  arg("range (any, range)", _t("The range to consider for the search. The first column in the range is searched for the key specified in search_key.")),
18249
18320
  arg("index (number)", _t("The column index of the value to be returned, where the first column in range is numbered 1.")),
18250
18321
  arg(`is_sorted (boolean, default=${DEFAULT_IS_SORTED})`, _t("Indicates whether the column to be searched (the first column of the specified range) is sorted, in which case the closest match for search_key will be returned.")),
@@ -18252,9 +18323,6 @@ const VLOOKUP = {
18252
18323
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18253
18324
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18254
18325
  assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18255
- if (searchKey && isEvaluationError(searchKey.value)) {
18256
- return searchKey;
18257
- }
18258
18326
  const getValueFromRange = (range, index) => range[0][index].value;
18259
18327
  const _isSorted = toBoolean(isSorted.value);
18260
18328
  const rowIndex = _isSorted
@@ -18280,7 +18348,7 @@ const MATCH_MODE = {
18280
18348
  const XLOOKUP = {
18281
18349
  description: _t("Search a range for a match and return the corresponding item from a second range."),
18282
18350
  args: [
18283
- arg("search_key (any)", _t("The value to search for.")),
18351
+ arg("search_key (string,number,boolean)", _t("The value to search for.")),
18284
18352
  arg("lookup_range (any, range)", _t("The range to consider for the search. Should be a single column or a single row.")),
18285
18353
  arg("return_range (any, range)", _t("The range containing the return value. Should have the same dimensions as lookup_range.")),
18286
18354
  arg("if_not_found (any, optional)", _t("If a valid match is not found, return this value.")),
@@ -18305,9 +18373,6 @@ const XLOOKUP = {
18305
18373
  assert(() => lookupDirection === "col"
18306
18374
  ? returnRange[0].length === lookupRange[0].length
18307
18375
  : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18308
- if (searchKey && isEvaluationError(searchKey.value)) {
18309
- return [[searchKey]];
18310
- }
18311
18376
  const getElement = lookupDirection === "col"
18312
18377
  ? (range, index) => range[0][index].value
18313
18378
  : (range, index) => range[index][0].value;
@@ -18332,13 +18397,14 @@ const XLOOKUP = {
18332
18397
  //--------------------------------------------------------------------------
18333
18398
  // Pivot functions
18334
18399
  //--------------------------------------------------------------------------
18400
+ // PIVOT.VALUE
18335
18401
  const PIVOT_VALUE = {
18336
18402
  description: _t("Get the value from a pivot."),
18337
18403
  args: [
18338
- arg("pivot_id (string)", _t("ID of the pivot.")),
18404
+ arg("pivot_id (number,string)", _t("ID of the pivot.")),
18339
18405
  arg("measure_name (string)", _t("Name of the measure.")),
18340
18406
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18341
- arg("domain_value (string,optional,repeating)", _t("Value.")),
18407
+ arg("domain_value (number,string,boolean,optional,repeating)", _t("Value.")),
18342
18408
  ],
18343
18409
  compute: function (formulaId, measureName, ...domainArgs) {
18344
18410
  const _pivotFormulaId = toString(formulaId);
@@ -18365,12 +18431,13 @@ const PIVOT_VALUE = {
18365
18431
  return pivot.getPivotCellValueAndFormat(_measure, domain);
18366
18432
  },
18367
18433
  };
18434
+ // PIVOT.HEADER
18368
18435
  const PIVOT_HEADER = {
18369
18436
  description: _t("Get the header of a pivot."),
18370
18437
  args: [
18371
- arg("pivot_id (string)", _t("ID of the pivot.")),
18438
+ arg("pivot_id (number,string)", _t("ID of the pivot.")),
18372
18439
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18373
- arg("domain_value (string,optional,repeating)", _t("Value.")),
18440
+ arg("domain_value (number,string,value,optional,repeating)", _t("Value.")),
18374
18441
  ],
18375
18442
  compute: function (pivotId, ...domainArgs) {
18376
18443
  const _pivotFormulaId = toString(pivotId);
@@ -18617,8 +18684,8 @@ const getNeutral = { number: 0, string: "", boolean: false };
18617
18684
  const EQ = {
18618
18685
  description: _t("Equal."),
18619
18686
  args: [
18620
- arg("value1 (any)", _t("The first value.")),
18621
- arg("value2 (any)", _t("The value to test against value1 for equality.")),
18687
+ arg("value1 (string, number, boolean)", _t("The first value.")),
18688
+ arg("value2 (string, number, boolean)", _t("The value to test against value1 for equality.")),
18622
18689
  ],
18623
18690
  compute: function (value1, value2) {
18624
18691
  let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
@@ -18629,12 +18696,6 @@ const EQ = {
18629
18696
  if (typeof _value2 === "string") {
18630
18697
  _value2 = _value2.toUpperCase();
18631
18698
  }
18632
- if (isEvaluationError(_value1)) {
18633
- throw value1;
18634
- }
18635
- if (isEvaluationError(_value2)) {
18636
- throw value2;
18637
- }
18638
18699
  return _value1 === _value2;
18639
18700
  },
18640
18701
  };
@@ -18669,8 +18730,8 @@ function applyRelationalOperator(value1, value2, cb) {
18669
18730
  const GT = {
18670
18731
  description: _t("Strictly greater than."),
18671
18732
  args: [
18672
- arg("value1 (any)", _t("The value to test as being greater than value2.")),
18673
- arg("value2 (any)", _t("The second value.")),
18733
+ arg("value1 (number, string, boolean)", _t("The value to test as being greater than value2.")),
18734
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18674
18735
  ],
18675
18736
  compute: function (value1, value2) {
18676
18737
  return applyRelationalOperator(value1, value2, (v1, v2) => {
@@ -18684,8 +18745,8 @@ const GT = {
18684
18745
  const GTE = {
18685
18746
  description: _t("Greater than or equal to."),
18686
18747
  args: [
18687
- arg("value1 (any)", _t("The value to test as being greater than or equal to value2.")),
18688
- arg("value2 (any)", _t("The second value.")),
18748
+ arg("value1 (number, string, boolean)", _t("The value to test as being greater than or equal to value2.")),
18749
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18689
18750
  ],
18690
18751
  compute: function (value1, value2) {
18691
18752
  return applyRelationalOperator(value1, value2, (v1, v2) => {
@@ -18699,8 +18760,8 @@ const GTE = {
18699
18760
  const LT = {
18700
18761
  description: _t("Less than."),
18701
18762
  args: [
18702
- arg("value1 (any)", _t("The value to test as being less than value2.")),
18703
- arg("value2 (any)", _t("The second value.")),
18763
+ arg("value1 (number, string, boolean)", _t("The value to test as being less than value2.")),
18764
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18704
18765
  ],
18705
18766
  compute: function (value1, value2) {
18706
18767
  return !GTE.compute.bind(this)(value1, value2);
@@ -18712,8 +18773,8 @@ const LT = {
18712
18773
  const LTE = {
18713
18774
  description: _t("Less than or equal to."),
18714
18775
  args: [
18715
- arg("value1 (any)", _t("The value to test as being less than or equal to value2.")),
18716
- arg("value2 (any)", _t("The second value.")),
18776
+ arg("value1 (number, string, boolean)", _t("The value to test as being less than or equal to value2.")),
18777
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18717
18778
  ],
18718
18779
  compute: function (value1, value2) {
18719
18780
  return !GT.compute.bind(this)(value1, value2);
@@ -18757,8 +18818,8 @@ const MULTIPLY = {
18757
18818
  const NE = {
18758
18819
  description: _t("Not equal."),
18759
18820
  args: [
18760
- arg("value1 (any)", _t("The first value.")),
18761
- arg("value2 (any)", _t("The value to test against value1 for inequality.")),
18821
+ arg("value1 (string, number, boolean)", _t("The first value.")),
18822
+ arg("value2 (string, number, boolean)", _t("The value to test against value1 for inequality.")),
18762
18823
  ],
18763
18824
  compute: function (value1, value2) {
18764
18825
  return !EQ.compute.bind(this)(value1, value2);
@@ -19672,6 +19733,16 @@ function createComputeFunction(descr, functionName) {
19672
19733
  });
19673
19734
  }
19674
19735
  function errorHandlingCompute(...args) {
19736
+ for (let i = 0; i < args.length; i++) {
19737
+ const arg = args[i];
19738
+ const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
19739
+ // Early exit if the argument is an error and the function does not accept errors
19740
+ // We only check scalar arguments, not matrix arguments for performance reasons.
19741
+ // Casting helpers are responsible for handling errors in matrix arguments.
19742
+ if (!argDefinition.acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) {
19743
+ return arg;
19744
+ }
19745
+ }
19675
19746
  try {
19676
19747
  return computeFunctionToObject.apply(this, args);
19677
19748
  }
@@ -20611,10 +20682,10 @@ css /* scss */ `
20611
20682
  }
20612
20683
 
20613
20684
  .fa-stack {
20614
- // reset stack size which is doubled by default
20615
- width: 1em;
20616
- height: 1em;
20617
- line-height: 1em;
20685
+ /* reset stack size which is doubled by default */
20686
+ width: ${CLOSE_ICON_RADIUS * 2}px;
20687
+ height: ${CLOSE_ICON_RADIUS * 2}px;
20688
+ line-height: ${CLOSE_ICON_RADIUS * 2}px;
20618
20689
  }
20619
20690
 
20620
20691
  .force-open-assistant {
@@ -27814,17 +27885,15 @@ function interactivePasteFromOS(env, target, clipboardContent, pasteOption) {
27814
27885
  });
27815
27886
  }
27816
27887
  catch (error) {
27817
- const parsedSpreadsheetContent = clipboardContent[ClipboardMIMEType.OSpreadsheet]
27818
- ? JSON.parse(clipboardContent[ClipboardMIMEType.OSpreadsheet])
27819
- : {};
27820
- if (parsedSpreadsheetContent.version && parsedSpreadsheetContent.version !== CURRENT_VERSION) {
27888
+ const parsedSpreadsheetContent = clipboardContent.data;
27889
+ if (parsedSpreadsheetContent?.version !== CURRENT_VERSION) {
27821
27890
  env.raiseError(_t("An unexpected error occurred while pasting content.\
27822
27891
  This is probably due to a spreadsheet version mismatch."));
27823
27892
  }
27824
27893
  result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
27825
27894
  target,
27826
27895
  clipboardContent: {
27827
- [ClipboardMIMEType.PlainText]: clipboardContent[ClipboardMIMEType.PlainText],
27896
+ text: clipboardContent.text,
27828
27897
  },
27829
27898
  pasteOption,
27830
27899
  });
@@ -28552,8 +28621,8 @@ function createBarChartRuntime(chart, getters) {
28552
28621
  const dataset = {
28553
28622
  label,
28554
28623
  data,
28555
- borderColor: BORDER_CHART_COLOR,
28556
- borderWidth: 1,
28624
+ borderColor: definition.background || BACKGROUND_CHART_COLOR,
28625
+ borderWidth: definition.stacked ? 1 : 0,
28557
28626
  backgroundColor: color,
28558
28627
  };
28559
28628
  config.data.datasets.push(dataset);
@@ -32652,13 +32721,11 @@ async function paste$1(env, pasteOption) {
32652
32721
  const osClipboard = await env.clipboard.read();
32653
32722
  switch (osClipboard.status) {
32654
32723
  case "ok":
32655
- const htmlDocument = new DOMParser().parseFromString(osClipboard.content[ClipboardMIMEType.Html] ?? "<div></div>", "text/html");
32656
- const osClipboardSpreadsheetContent = osClipboard.content[ClipboardMIMEType.OSpreadsheet] || "{}";
32657
- const clipboardId = JSON.parse(osClipboardSpreadsheetContent).clipboardId ??
32658
- htmlDocument.querySelector("div")?.getAttribute("data-clipboard-id");
32724
+ const clipboardContent = parseOSClipboardContent(osClipboard.content);
32725
+ const clipboardId = clipboardContent.data?.clipboardId;
32659
32726
  const target = env.model.getters.getSelectedZones();
32660
32727
  if (env.model.getters.getClipboardId() !== clipboardId) {
32661
- interactivePasteFromOS(env, target, osClipboard.content, pasteOption);
32728
+ interactivePasteFromOS(env, target, clipboardContent, pasteOption);
32662
32729
  }
32663
32730
  else {
32664
32731
  interactivePaste(env, target, pasteOption);
@@ -49794,25 +49861,20 @@ class Grid extends owl.Component {
49794
49861
  if (!clipboardData) {
49795
49862
  return;
49796
49863
  }
49797
- const clipboardDataTextContent = clipboardData?.getData(ClipboardMIMEType.PlainText);
49798
- const clipboardDataHtmlContent = clipboardData?.getData(ClipboardMIMEType.Html);
49799
- const htmlDocument = new DOMParser().parseFromString(clipboardDataHtmlContent ?? "<div></div>", "text/html");
49800
- const osClipboardSpreadsheetContent = clipboardData.getData(ClipboardMIMEType.OSpreadsheet) || "{}";
49864
+ const osClipboard = {
49865
+ content: {
49866
+ [ClipboardMIMEType.PlainText]: clipboardData?.getData(ClipboardMIMEType.PlainText),
49867
+ [ClipboardMIMEType.Html]: clipboardData?.getData(ClipboardMIMEType.Html),
49868
+ },
49869
+ };
49801
49870
  const target = this.env.model.getters.getSelectedZones();
49802
49871
  const isCutOperation = this.env.model.getters.isCutOperation();
49803
- const clipboardId = JSON.parse(osClipboardSpreadsheetContent).clipboardId ??
49804
- htmlDocument.querySelector("div")?.getAttribute("data-clipboard-id");
49872
+ const clipboardContent = parseOSClipboardContent(osClipboard.content);
49873
+ const clipboardId = clipboardContent.data?.clipboardId;
49805
49874
  if (this.env.model.getters.getClipboardId() === clipboardId) {
49806
49875
  interactivePaste(this.env, target);
49807
49876
  }
49808
49877
  else {
49809
- const clipboardContent = {
49810
- [ClipboardMIMEType.PlainText]: clipboardDataTextContent,
49811
- [ClipboardMIMEType.Html]: clipboardDataHtmlContent,
49812
- };
49813
- if (osClipboardSpreadsheetContent !== "{}") {
49814
- clipboardContent[ClipboardMIMEType.OSpreadsheet] = osClipboardSpreadsheetContent;
49815
- }
49816
49878
  interactivePasteFromOS(this.env, target, clipboardContent);
49817
49879
  }
49818
49880
  if (isCutOperation) {
@@ -51695,8 +51757,9 @@ class ConditionalFormatPlugin extends CorePlugin {
51695
51757
  if (replaceIndex > -1) {
51696
51758
  currentRanges = rules[replaceIndex].ranges.map(toUnboundedZone);
51697
51759
  }
51698
- currentRanges = currentRanges.concat(toAdd);
51699
- return recomputeZones(currentRanges, toRemove).map((zone) => this.getters.getRangeDataFromZone(sheetId, zone));
51760
+ // Remove the zones first in case the same position is in toAdd and toRemove
51761
+ const withRemovedZones = recomputeZones(currentRanges, toRemove);
51762
+ return recomputeZones([...toAdd, ...withRemovedZones], []).map((zone) => this.getters.getRangeDataFromZone(sheetId, zone));
51700
51763
  }
51701
51764
  // ---------------------------------------------------------------------------
51702
51765
  // Private
@@ -57290,6 +57353,9 @@ class Evaluator {
57290
57353
  }
57291
57354
  for (let i = 0; i < positions.length; ++i) {
57292
57355
  const position = positions[i];
57356
+ if (this.nextPositionsToUpdate.has(position)) {
57357
+ continue;
57358
+ }
57293
57359
  const evaluatedCell = this.computeCell(position);
57294
57360
  if (evaluatedCell !== EMPTY_CELL) {
57295
57361
  this.evaluatedCells.set(position, evaluatedCell);
@@ -57297,6 +57363,9 @@ class Evaluator {
57297
57363
  }
57298
57364
  onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
57299
57365
  }
57366
+ if (currentIteration >= MAX_ITERATION) {
57367
+ console.warn("Maximum iteration reached while evaluating cells");
57368
+ }
57300
57369
  }
57301
57370
  computeCell(position) {
57302
57371
  const evaluation = this.evaluatedCells.get(position);
@@ -57331,7 +57400,6 @@ class Evaluator {
57331
57400
  }
57332
57401
  finally {
57333
57402
  this.cellsBeingComputed.delete(cellId);
57334
- this.nextPositionsToUpdate.delete(position);
57335
57403
  }
57336
57404
  }
57337
57405
  computeAndSave(position) {
@@ -57367,6 +57435,7 @@ class Evaluator {
57367
57435
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
57368
57436
  // the result matrix is split in 2 zones to exclude the array formula position
57369
57437
  const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
57438
+ invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
57370
57439
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
57371
57440
  }
57372
57441
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
@@ -58489,25 +58558,11 @@ class EvaluationDataValidationPlugin extends UIPlugin {
58489
58558
  }
58490
58559
  switch (cmd.type) {
58491
58560
  case "ADD_DATA_VALIDATION_RULE":
58492
- const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
58493
- if (cmd.rule.criterion.type === "isBoolean") {
58494
- this.setContentToBooleanCells({ ...cmd.rule, ranges });
58495
- }
58496
- delete this.validationResults[cmd.sheetId];
58497
- break;
58498
58561
  case "REMOVE_DATA_VALIDATION_RULE":
58499
58562
  delete this.validationResults[cmd.sheetId];
58500
58563
  break;
58501
58564
  }
58502
58565
  }
58503
- setContentToBooleanCells(rule) {
58504
- for (const position of getCellPositionsInRanges(rule.ranges)) {
58505
- const evaluatedCell = this.getters.getEvaluatedCell(position);
58506
- if (evaluatedCell.type !== CellValueType.boolean) {
58507
- this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
58508
- }
58509
- }
58510
- }
58511
58566
  isDataValidationInvalid(cellPosition) {
58512
58567
  return !this.getValidationResultForCell(cellPosition).isValid;
58513
58568
  }
@@ -60987,7 +61042,7 @@ class Session extends EventBus {
60987
61042
  * Notify the server that the user client left the collaborative session
60988
61043
  */
60989
61044
  async leave(data) {
60990
- if (Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
61045
+ if (data && Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
60991
61046
  await this.snapshot(data());
60992
61047
  }
60993
61048
  delete this.clients[this.clientId];
@@ -62542,6 +62597,42 @@ class CellComputedStylePlugin extends UIPlugin {
62542
62597
  }
62543
62598
  }
62544
62599
 
62600
+ class DataValidationInsertionPlugin extends UIPlugin {
62601
+ handle(cmd) {
62602
+ switch (cmd.type) {
62603
+ case "ADD_DATA_VALIDATION_RULE":
62604
+ if (cmd.rule.criterion.type === "isBoolean") {
62605
+ const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
62606
+ for (const position of getCellPositionsInRanges(ranges)) {
62607
+ const cell = this.getters.getCell(position);
62608
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
62609
+ if (!cell?.content) {
62610
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
62611
+ // In this case, a cell has been updated in the core plugin but
62612
+ // not yet evaluated. This can occur after a paste operation.
62613
+ }
62614
+ else if (cell?.content && evaluatedCell.type === CellValueType.empty) {
62615
+ let value;
62616
+ if (cell.content.startsWith("=")) {
62617
+ const result = this.getters.evaluateFormula(position.sheetId, cell.content);
62618
+ value = (isMatrix(result) ? result[0][0] : result)?.toString();
62619
+ }
62620
+ else {
62621
+ value = cell.content;
62622
+ }
62623
+ if (!value || !isBoolean(value)) {
62624
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
62625
+ }
62626
+ }
62627
+ else if (evaluatedCell.type !== CellValueType.boolean) {
62628
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
62629
+ }
62630
+ }
62631
+ }
62632
+ }
62633
+ }
62634
+ }
62635
+
62545
62636
  const genericRepeatsTransforms = [
62546
62637
  repeatSheetDependantCommand,
62547
62638
  repeatTargetDependantCommand,
@@ -63195,7 +63286,7 @@ class ClipboardPlugin extends UIPlugin {
63195
63286
  const zones = this.getters.getSelectedZones();
63196
63287
  return this.isCutAllowedOn(zones);
63197
63288
  case "PASTE_FROM_OS_CLIPBOARD": {
63198
- const copiedData = this.convertOSClipboardData(cmd.clipboardContent[ClipboardMIMEType.PlainText] ?? "");
63289
+ const copiedData = this.convertTextToClipboardData(cmd.clipboardContent.text ?? "");
63199
63290
  const pasteOption = cmd.pasteOption;
63200
63291
  return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
63201
63292
  }
@@ -63248,12 +63339,9 @@ class ClipboardPlugin extends UIPlugin {
63248
63339
  break;
63249
63340
  case "PASTE_FROM_OS_CLIPBOARD": {
63250
63341
  this._isCutOperation = false;
63251
- if (cmd.clipboardContent[ClipboardMIMEType.OSpreadsheet]) {
63252
- this.copiedData = JSON.parse(cmd.clipboardContent[ClipboardMIMEType.OSpreadsheet]);
63253
- }
63254
- else {
63255
- this.copiedData = this.convertOSClipboardData(cmd.clipboardContent[ClipboardMIMEType.PlainText] ?? "");
63256
- }
63342
+ this.copiedData =
63343
+ cmd.clipboardContent.data ||
63344
+ this.convertTextToClipboardData(cmd.clipboardContent.text ?? "");
63257
63345
  const pasteOption = cmd.pasteOption;
63258
63346
  this.paste(cmd.target, this.copiedData, {
63259
63347
  pasteOption,
@@ -63384,11 +63472,11 @@ class ClipboardPlugin extends UIPlugin {
63384
63472
  }
63385
63473
  }
63386
63474
  }
63387
- convertOSClipboardData(clipboardData) {
63475
+ convertTextToClipboardData(clipboardData) {
63388
63476
  const handlers = this.selectClipboardHandlers({ figureId: true }).concat(this.selectClipboardHandlers({}));
63389
63477
  let copiedData = {};
63390
63478
  for (const { handlerName, handler } of handlers) {
63391
- const data = handler.convertOSClipboardData(clipboardData);
63479
+ const data = handler.convertTextToClipboardData(clipboardData);
63392
63480
  copiedData[handlerName] = data;
63393
63481
  const minimalKeys = ["sheetId", "cells", "zones", "figureId"];
63394
63482
  for (const key of minimalKeys) {
@@ -63557,21 +63645,20 @@ class ClipboardPlugin extends UIPlugin {
63557
63645
  return {
63558
63646
  [ClipboardMIMEType.PlainText]: this.getPlainTextContent(),
63559
63647
  [ClipboardMIMEType.Html]: this.getHTMLContent(),
63560
- [ClipboardMIMEType.OSpreadsheet]: this.getSerializedGridData(),
63561
63648
  };
63562
63649
  }
63563
- getSerializedGridData() {
63650
+ getSheetData() {
63564
63651
  const data = {
63565
63652
  version: CURRENT_VERSION,
63566
63653
  clipboardId: this.clipboardId,
63567
63654
  };
63568
63655
  if (this.copiedData && "figureId" in this.copiedData) {
63569
- return JSON.stringify(data);
63656
+ return data;
63570
63657
  }
63571
- return JSON.stringify({
63658
+ return {
63572
63659
  ...data,
63573
63660
  ...this.copiedData,
63574
- });
63661
+ };
63575
63662
  }
63576
63663
  getPlainTextContent() {
63577
63664
  if (!this.copiedData?.cells) {
@@ -63588,31 +63675,36 @@ class ClipboardPlugin extends UIPlugin {
63588
63675
  .join("\n") || "\t");
63589
63676
  }
63590
63677
  getHTMLContent() {
63591
- if (!this.copiedData?.cells) {
63592
- return `<div data-clipboard-id="${this.clipboardId}">\t</div>`;
63678
+ let innerHTML = "";
63679
+ const cells = this.copiedData?.cells;
63680
+ if (!cells) {
63681
+ innerHTML = "\t";
63593
63682
  }
63594
- const cells = this.copiedData.cells;
63595
- if (cells.length === 1 && cells[0].length === 1) {
63596
- return `<div data-clipboard-id="${this.clipboardId}">${this.getters.getCellText(cells[0][0].position)}</div>`;
63683
+ else if (cells.length === 1 && cells[0].length === 1) {
63684
+ innerHTML = `${this.getters.getCellText(cells[0][0].position)}`;
63597
63685
  }
63598
- if (!cells[0][0]) {
63686
+ else if (!cells[0][0]) {
63599
63687
  return "";
63600
63688
  }
63601
- let htmlTable = `<div data-clipboard-id="${this.clipboardId}"><table border="1" style="border-collapse:collapse">`;
63602
- for (const row of cells) {
63603
- htmlTable += "<tr>";
63604
- for (const cell of row) {
63605
- if (!cell) {
63606
- continue;
63689
+ else {
63690
+ let htmlTable = `<table border="1" style="border-collapse:collapse">`;
63691
+ for (const row of cells) {
63692
+ htmlTable += "<tr>";
63693
+ for (const cell of row) {
63694
+ if (!cell) {
63695
+ continue;
63696
+ }
63697
+ const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
63698
+ const cellText = this.getters.getCellText(cell.position);
63699
+ htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
63607
63700
  }
63608
- const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
63609
- const cellText = this.getters.getCellText(cell.position);
63610
- htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
63701
+ htmlTable += "</tr>";
63611
63702
  }
63612
- htmlTable += "</tr>";
63703
+ htmlTable += "</table>";
63704
+ innerHTML = htmlTable;
63613
63705
  }
63614
- htmlTable += "</table></div>";
63615
- return htmlTable;
63706
+ const serializedData = JSON.stringify(this.getSheetData());
63707
+ return `<div data-osheet-clipboard='${xmlEscape(serializedData)}'>${innerHTML}</div>`;
63616
63708
  }
63617
63709
  isCutOperation() {
63618
63710
  return this._isCutOperation ?? false;
@@ -65599,7 +65691,8 @@ const featurePluginRegistry = new Registry()
65599
65691
  .add("history", HistoryPlugin)
65600
65692
  .add("data_cleanup", DataCleanupPlugin)
65601
65693
  .add("table_autofill", TableAutofillPlugin)
65602
- .add("table_ui_resize", TableResizeUI);
65694
+ .add("table_ui_resize", TableResizeUI)
65695
+ .add("datavalidation_insert", DataValidationInsertionPlugin);
65603
65696
  // Plugins which have a state, but which should not be shared in collaborative
65604
65697
  const statefulUIPluginRegistry = new Registry()
65605
65698
  .add("selection", GridSelectionPlugin)
@@ -67859,10 +67952,6 @@ class WebClipboardWrapper {
67859
67952
  [ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),
67860
67953
  [ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),
67861
67954
  };
67862
- const spreadsheetData = content[ClipboardMIMEType.OSpreadsheet];
67863
- if (spreadsheetData) {
67864
- clipboardItemData[ClipboardMIMEType.OSpreadsheet] = this.getBlob(content, ClipboardMIMEType.OSpreadsheet);
67865
- }
67866
67955
  return [new ClipboardItem(clipboardItemData)];
67867
67956
  }
67868
67957
  getBlob(clipboardContent, type) {
@@ -71866,7 +71955,8 @@ class Model extends EventBus {
71866
71955
  this.session.join(this.config.client);
71867
71956
  }
71868
71957
  async leaveSession() {
71869
- await this.session.leave(lazy(() => this.exportData()));
71958
+ const snapshot = this.getters.isReadonly() ? undefined : lazy(() => this.exportData());
71959
+ await this.session.leave(snapshot);
71870
71960
  }
71871
71961
  setupUiPlugin(Plugin) {
71872
71962
  const plugin = new Plugin(this.uiPluginConfig);
@@ -72480,6 +72570,6 @@ exports.tokenColors = tokenColors;
72480
72570
  exports.tokenize = tokenize;
72481
72571
 
72482
72572
 
72483
- __info__.version = "18.0.4";
72484
- __info__.date = "2024-11-13T15:08:50.620Z";
72485
- __info__.hash = "88e1aee";
72573
+ __info__.version = "18.0.5";
72574
+ __info__.date = "2024-11-22T14:19:19.721Z";
72575
+ __info__.hash = "9eb34d9";