@odoo/o-spreadsheet 18.1.0-alpha.4 → 18.1.0-alpha.6

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.1.0-alpha.4
6
- * @date 2024-11-13T15:06:47.769Z
7
- * @hash e1ad985
5
+ * @version 18.1.0-alpha.6
6
+ * @date 2024-11-28T09:06:59.527Z
7
+ * @hash 875c901
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";
@@ -3236,7 +3235,6 @@ var ClipboardMIMEType;
3236
3235
  (function (ClipboardMIMEType) {
3237
3236
  ClipboardMIMEType["PlainText"] = "text/plain";
3238
3237
  ClipboardMIMEType["Html"] = "text/html";
3239
- ClipboardMIMEType["OSpreadsheet"] = "web application/o-spreadsheet";
3240
3238
  })(ClipboardMIMEType || (ClipboardMIMEType = {}));
3241
3239
 
3242
3240
  function isSheetDependent(cmd) {
@@ -3500,7 +3498,9 @@ exports.CommandResult = void 0;
3500
3498
  CommandResult["MaxInvalidFormula"] = "MaxInvalidFormula";
3501
3499
  CommandResult["ValueUpperInvalidFormula"] = "ValueUpperInvalidFormula";
3502
3500
  CommandResult["ValueLowerInvalidFormula"] = "ValueLowerInvalidFormula";
3501
+ CommandResult["InvalidSortAnchor"] = "InvalidSortAnchor";
3503
3502
  CommandResult["InvalidSortZone"] = "InvalidSortZone";
3503
+ CommandResult["SortZoneWithArrayFormulas"] = "SortZoneWithArrayFormulas";
3504
3504
  CommandResult["WaitingSessionConfirmation"] = "WaitingSessionConfirmation";
3505
3505
  CommandResult["MergeOverlap"] = "MergeOverlap";
3506
3506
  CommandResult["TooManyHiddenElements"] = "TooManyHiddenElements";
@@ -3556,7 +3556,6 @@ exports.CommandResult = void 0;
3556
3556
  CommandResult["ValueCellIsInvalidFormula"] = "ValueCellIsInvalidFormula";
3557
3557
  CommandResult["InvalidDefinition"] = "InvalidDefinition";
3558
3558
  CommandResult["InvalidColor"] = "InvalidColor";
3559
- CommandResult["DataBarRangeValuesMismatch"] = "DataBarRangeValuesMismatch";
3560
3559
  })(exports.CommandResult || (exports.CommandResult = {}));
3561
3560
 
3562
3561
  const DEFAULT_LOCALES = [
@@ -4346,7 +4345,9 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4346
4345
  return reverseSearch ? numberOfValues - i - 1 : i;
4347
4346
  }
4348
4347
  }
4349
- return reverseSearch ? numberOfValues - closestMatchIndex - 1 : closestMatchIndex;
4348
+ return reverseSearch && closestMatchIndex !== -1
4349
+ ? numberOfValues - closestMatchIndex - 1
4350
+ : closestMatchIndex;
4350
4351
  }
4351
4352
  /**
4352
4353
  * Normalize a value.
@@ -4400,47 +4401,83 @@ function isDataNonEmpty(data) {
4400
4401
  return true;
4401
4402
  }
4402
4403
 
4403
- function toCriterionDateNumber(dateValue) {
4404
- const today = DateTime.now();
4405
- switch (dateValue) {
4406
- case "today":
4407
- return jsDateToNumber(today);
4408
- case "yesterday":
4409
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
4410
- case "tomorrow":
4411
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
4412
- case "lastWeek":
4413
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
4414
- case "lastMonth":
4415
- return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
4416
- case "lastYear":
4417
- return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
4418
- }
4404
+ /**
4405
+ * Add the `https` prefix to the url if it's missing
4406
+ */
4407
+ function withHttps(url) {
4408
+ return !/^https?:\/\//i.test(url) ? `https://${url}` : url;
4419
4409
  }
4420
- /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
4421
- function getDateNumberCriterionValues(criterion, locale) {
4422
- if ("dateValue" in criterion && criterion.dateValue !== "exactDate") {
4423
- return [toCriterionDateNumber(criterion.dateValue)];
4424
- }
4425
- return criterion.values.map((value) => valueToDateNumber(value, locale));
4410
+ const urlRegistry = new Registry();
4411
+ function createWebLink(url, label) {
4412
+ url = withHttps(url);
4413
+ return {
4414
+ url,
4415
+ label: label || url,
4416
+ isExternal: true,
4417
+ isUrlEditable: true,
4418
+ };
4426
4419
  }
4427
- /** Convert the criterion values to numbers. Return undefined values if they cannot be converted to numbers. */
4428
- function getCriterionValuesAsNumber(criterion, locale) {
4429
- return criterion.values.map((value) => tryToNumber(value, locale));
4420
+ urlRegistry.add("sheet_URL", {
4421
+ match: (url) => isSheetUrl(url),
4422
+ createLink: (url, label) => {
4423
+ return {
4424
+ label,
4425
+ url,
4426
+ isExternal: false,
4427
+ isUrlEditable: false,
4428
+ };
4429
+ },
4430
+ urlRepresentation(url, getters) {
4431
+ const sheetId = parseSheetUrl(url);
4432
+ return getters.tryGetSheetName(sheetId) || _t("Invalid sheet");
4433
+ },
4434
+ open(url, env) {
4435
+ const sheetId = parseSheetUrl(url);
4436
+ const result = env.model.dispatch("ACTIVATE_SHEET", {
4437
+ sheetIdFrom: env.model.getters.getActiveSheetId(),
4438
+ sheetIdTo: sheetId,
4439
+ });
4440
+ if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
4441
+ env.notifyUser({
4442
+ type: "warning",
4443
+ sticky: false,
4444
+ text: _t("Cannot open the link because the linked sheet is hidden."),
4445
+ });
4446
+ }
4447
+ },
4448
+ sequence: 0,
4449
+ });
4450
+ const WebUrlSpec = {
4451
+ createLink: createWebLink,
4452
+ match: (url) => isWebLink(url),
4453
+ open: (url) => window.open(url, "_blank"),
4454
+ urlRepresentation: (url) => url,
4455
+ sequence: 0,
4456
+ };
4457
+ function findMatchingSpec(url) {
4458
+ return (urlRegistry
4459
+ .getAll()
4460
+ .sort((a, b) => a.sequence - b.sequence)
4461
+ .find((urlType) => urlType.match(url)) || WebUrlSpec);
4430
4462
  }
4431
-
4432
- const MAX_DELAY = 140;
4433
- const MIN_DELAY = 20;
4434
- const ACCELERATION = 0.035;
4435
- /**
4436
- * Decreasing exponential function used to determine the "speed" of edge-scrolling
4437
- * as the timeout delay.
4438
- *
4439
- * Returns a timeout delay in milliseconds.
4440
- */
4441
- function scrollDelay(value) {
4442
- // decreasing exponential from MAX_DELAY to MIN_DELAY
4443
- return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
4463
+ function urlRepresentation(link, getters) {
4464
+ return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
4465
+ }
4466
+ function openLink(link, env) {
4467
+ findMatchingSpec(link.url).open(link.url, env);
4468
+ }
4469
+ function detectLink(value) {
4470
+ if (typeof value !== "string") {
4471
+ return undefined;
4472
+ }
4473
+ if (isMarkdownLink(value)) {
4474
+ const { label, url } = parseMarkdownLink(value);
4475
+ return findMatchingSpec(url).createLink(url, label);
4476
+ }
4477
+ else if (isWebLink(value)) {
4478
+ return createWebLink(value);
4479
+ }
4480
+ return undefined;
4444
4481
  }
4445
4482
 
4446
4483
  function tokenizeFormat(str) {
@@ -5502,6 +5539,193 @@ function isTextFormat(format) {
5502
5539
  }
5503
5540
  }
5504
5541
 
5542
+ function evaluateLiteral(literalCell, localeFormat) {
5543
+ const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5544
+ const functionResult = { value, format: localeFormat.format };
5545
+ return createEvaluatedCell(functionResult, localeFormat.locale);
5546
+ }
5547
+ function parseLiteral(content, locale) {
5548
+ if (content.startsWith("=")) {
5549
+ throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
5550
+ }
5551
+ if (content === "") {
5552
+ return null;
5553
+ }
5554
+ if (isNumber(content, DEFAULT_LOCALE)) {
5555
+ return parseNumber(content, DEFAULT_LOCALE);
5556
+ }
5557
+ const internalDate = parseDateTime(content, locale);
5558
+ if (internalDate) {
5559
+ return internalDate.value;
5560
+ }
5561
+ if (isBoolean(content)) {
5562
+ return content.toUpperCase() === "TRUE";
5563
+ }
5564
+ return content;
5565
+ }
5566
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5567
+ const link = detectLink(functionResult.value);
5568
+ if (!link) {
5569
+ return _createEvaluatedCell(functionResult, locale, cell);
5570
+ }
5571
+ const value = parseLiteral(link.label, locale);
5572
+ const format = functionResult.format ||
5573
+ (typeof value === "number"
5574
+ ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
5575
+ : undefined);
5576
+ const linkPayload = {
5577
+ value,
5578
+ format,
5579
+ };
5580
+ return {
5581
+ ..._createEvaluatedCell(linkPayload, locale, cell),
5582
+ link,
5583
+ };
5584
+ }
5585
+ function _createEvaluatedCell(functionResult, locale, cell) {
5586
+ let { value, format, message } = functionResult;
5587
+ format = cell?.format || format;
5588
+ const formattedValue = formatValue(value, { format, locale });
5589
+ if (isEvaluationError(value)) {
5590
+ return errorCell(value, message);
5591
+ }
5592
+ if (isTextFormat(format)) {
5593
+ // TO DO:
5594
+ // with the next line, the value of the cell is transformed depending on the format.
5595
+ // This shouldn't happen, by doing this, the formulas handling numbers are not able
5596
+ // to interpret the value as a number.
5597
+ return textCell(toString(value), format, formattedValue);
5598
+ }
5599
+ if (value === null) {
5600
+ return emptyCell(format);
5601
+ }
5602
+ if (typeof value === "number") {
5603
+ if (isDateTimeFormat(format || "")) {
5604
+ return dateTimeCell(value, format, formattedValue);
5605
+ }
5606
+ return numberCell(value, format, formattedValue);
5607
+ }
5608
+ if (typeof value === "boolean") {
5609
+ return booleanCell(value, format, formattedValue);
5610
+ }
5611
+ return textCell(value, format, formattedValue);
5612
+ }
5613
+ function textCell(value, format, formattedValue) {
5614
+ return {
5615
+ value,
5616
+ format,
5617
+ formattedValue,
5618
+ type: CellValueType.text,
5619
+ isAutoSummable: true,
5620
+ defaultAlign: "left",
5621
+ };
5622
+ }
5623
+ function numberCell(value, format, formattedValue) {
5624
+ return {
5625
+ value: value || 0, // necessary to avoid "-0" and NaN values,
5626
+ format,
5627
+ formattedValue,
5628
+ type: CellValueType.number,
5629
+ isAutoSummable: true,
5630
+ defaultAlign: "right",
5631
+ };
5632
+ }
5633
+ const emptyCell = memoize(function emptyCell(format) {
5634
+ return {
5635
+ value: null,
5636
+ format,
5637
+ formattedValue: "",
5638
+ type: CellValueType.empty,
5639
+ isAutoSummable: true,
5640
+ defaultAlign: "left",
5641
+ };
5642
+ });
5643
+ function dateTimeCell(value, format, formattedValue) {
5644
+ return {
5645
+ value,
5646
+ format,
5647
+ formattedValue,
5648
+ type: CellValueType.number,
5649
+ isAutoSummable: false,
5650
+ defaultAlign: "right",
5651
+ };
5652
+ }
5653
+ function booleanCell(value, format, formattedValue) {
5654
+ return {
5655
+ value,
5656
+ format,
5657
+ formattedValue,
5658
+ type: CellValueType.boolean,
5659
+ isAutoSummable: false,
5660
+ defaultAlign: "center",
5661
+ };
5662
+ }
5663
+ function errorCell(value, message) {
5664
+ return {
5665
+ value,
5666
+ formattedValue: value,
5667
+ message,
5668
+ type: CellValueType.error,
5669
+ isAutoSummable: false,
5670
+ defaultAlign: "center",
5671
+ };
5672
+ }
5673
+
5674
+ function toCriterionDateNumber(dateValue) {
5675
+ const today = DateTime.now();
5676
+ switch (dateValue) {
5677
+ case "today":
5678
+ return jsDateToNumber(today);
5679
+ case "yesterday":
5680
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
5681
+ case "tomorrow":
5682
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
5683
+ case "lastWeek":
5684
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
5685
+ case "lastMonth":
5686
+ return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
5687
+ case "lastYear":
5688
+ return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
5689
+ }
5690
+ }
5691
+ /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
5692
+ function getDateNumberCriterionValues(criterion, locale) {
5693
+ if ("dateValue" in criterion && criterion.dateValue !== "exactDate") {
5694
+ return [toCriterionDateNumber(criterion.dateValue)];
5695
+ }
5696
+ return criterion.values.map((value) => valueToDateNumber(value, locale));
5697
+ }
5698
+ /** Convert the criterion values to numbers. Return undefined values if they cannot be converted to numbers. */
5699
+ function getCriterionValuesAsNumber(criterion, locale) {
5700
+ return criterion.values.map((value) => tryToNumber(value, locale));
5701
+ }
5702
+ function getDateCriterionFormattedValues(values, locale) {
5703
+ return values.map((valueStr) => {
5704
+ if (valueStr.startsWith("=")) {
5705
+ return valueStr;
5706
+ }
5707
+ const value = parseLiteral(valueStr, locale);
5708
+ if (typeof value === "number") {
5709
+ return formatValue(value, { format: locale.dateFormat, locale });
5710
+ }
5711
+ return "";
5712
+ });
5713
+ }
5714
+
5715
+ const MAX_DELAY = 140;
5716
+ const MIN_DELAY = 20;
5717
+ const ACCELERATION = 0.035;
5718
+ /**
5719
+ * Decreasing exponential function used to determine the "speed" of edge-scrolling
5720
+ * as the timeout delay.
5721
+ *
5722
+ * Returns a timeout delay in milliseconds.
5723
+ */
5724
+ function scrollDelay(value) {
5725
+ // decreasing exponential from MAX_DELAY to MIN_DELAY
5726
+ return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5727
+ }
5728
+
5505
5729
  class RangeImpl {
5506
5730
  getSheetSize;
5507
5731
  _zone;
@@ -6149,6 +6373,22 @@ function getPasteZones(target, content) {
6149
6373
  const width = content[0].length, height = content.length;
6150
6374
  return target.map((t) => splitZoneForPaste(t, width, height)).flat();
6151
6375
  }
6376
+ function parseOSClipboardContent(content) {
6377
+ if (!content[ClipboardMIMEType.Html]) {
6378
+ return {
6379
+ text: content[ClipboardMIMEType.PlainText],
6380
+ };
6381
+ }
6382
+ const htmlDocument = new DOMParser().parseFromString(content[ClipboardMIMEType.Html], "text/html");
6383
+ const oSheetClipboardData = htmlDocument
6384
+ .querySelector("div")
6385
+ ?.getAttribute("data-osheet-clipboard");
6386
+ const spreadsheetContent = oSheetClipboardData && JSON.parse(oSheetClipboardData);
6387
+ return {
6388
+ text: content[ClipboardMIMEType.PlainText],
6389
+ data: spreadsheetContent,
6390
+ };
6391
+ }
6152
6392
 
6153
6393
  class ClipboardHandler {
6154
6394
  getters;
@@ -6170,7 +6410,7 @@ class ClipboardHandler {
6170
6410
  getPasteTarget(sheetId, target, content, options) {
6171
6411
  return { zones: [], sheetId };
6172
6412
  }
6173
- convertOSClipboardData(data) {
6413
+ convertTextToClipboardData(data) {
6174
6414
  return;
6175
6415
  }
6176
6416
  }
@@ -8013,7 +8253,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
8013
8253
  this.dispatch("CLEAR_CELL", target);
8014
8254
  }
8015
8255
  }
8016
- convertOSClipboardData(text) {
8256
+ convertTextToClipboardData(text) {
8017
8257
  const locale = this.getters.getLocale();
8018
8258
  const copiedData = {
8019
8259
  cells: [],
@@ -8121,6 +8361,7 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
8121
8361
 
8122
8362
  class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8123
8363
  uuidGenerator = new UuidGenerator();
8364
+ queuedChanges = {};
8124
8365
  copy(data) {
8125
8366
  if (!data.zones.length) {
8126
8367
  return;
@@ -8142,6 +8383,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8142
8383
  return { cfRules };
8143
8384
  }
8144
8385
  paste(target, clippedContent, options) {
8386
+ this.queuedChanges = {};
8145
8387
  if (options.pasteOption === "asValue") {
8146
8388
  return;
8147
8389
  }
@@ -8153,6 +8395,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8153
8395
  else {
8154
8396
  this.pasteFromCut(sheetId, zones, clippedContent);
8155
8397
  }
8398
+ this.executeQueuedChanges();
8156
8399
  }
8157
8400
  pasteFromCut(sheetId, target, content) {
8158
8401
  const selection = target[0];
@@ -8192,34 +8435,56 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8192
8435
  * Add or remove cells to a given conditional formatting rule.
8193
8436
  */
8194
8437
  adaptCFRules(sheetId, cf, toAdd, toRemove) {
8195
- const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
8196
- if (!newRangesXc) {
8197
- return;
8438
+ if (!this.queuedChanges[sheetId]) {
8439
+ this.queuedChanges[sheetId] = [];
8198
8440
  }
8199
- if (newRangesXc.length === 0) {
8200
- this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
8201
- return;
8441
+ const queuedChange = this.queuedChanges[sheetId].find((queued) => queued.cf.id === cf.id);
8442
+ if (!queuedChange) {
8443
+ this.queuedChanges[sheetId].push({ toAdd, toRemove, cf });
8444
+ }
8445
+ else {
8446
+ queuedChange.toAdd.push(...toAdd);
8447
+ queuedChange.toRemove.push(...toRemove);
8448
+ }
8449
+ }
8450
+ executeQueuedChanges() {
8451
+ for (const sheetId in this.queuedChanges) {
8452
+ for (const { toAdd, toRemove, cf } of this.queuedChanges[sheetId]) {
8453
+ const newRangesXc = this.getters.getAdaptedCfRanges(sheetId, cf, toAdd, toRemove);
8454
+ if (!newRangesXc) {
8455
+ continue;
8456
+ }
8457
+ if (newRangesXc.length === 0) {
8458
+ this.dispatch("REMOVE_CONDITIONAL_FORMAT", { id: cf.id, sheetId });
8459
+ continue;
8460
+ }
8461
+ this.dispatch("ADD_CONDITIONAL_FORMAT", {
8462
+ cf: {
8463
+ id: cf.id,
8464
+ rule: cf.rule,
8465
+ stopIfTrue: cf.stopIfTrue,
8466
+ },
8467
+ ranges: newRangesXc,
8468
+ sheetId,
8469
+ });
8470
+ }
8202
8471
  }
8203
- this.dispatch("ADD_CONDITIONAL_FORMAT", {
8204
- cf: {
8205
- id: cf.id,
8206
- rule: cf.rule,
8207
- stopIfTrue: cf.stopIfTrue,
8208
- },
8209
- ranges: newRangesXc,
8210
- sheetId,
8211
- });
8212
8472
  }
8213
8473
  getCFToCopyTo(targetSheetId, originCF) {
8214
- const cfInTarget = this.getters
8474
+ let targetCF = this.getters
8215
8475
  .getConditionalFormats(targetSheetId)
8216
8476
  .find((cf) => cf.stopIfTrue === originCF.stopIfTrue && deepEquals(cf.rule, originCF.rule));
8217
- return cfInTarget ? cfInTarget : { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
8477
+ const queuedCfs = this.queuedChanges[targetSheetId];
8478
+ if (!targetCF && queuedCfs) {
8479
+ targetCF = queuedCfs.find((queued) => queued.cf.stopIfTrue === originCF.stopIfTrue && deepEquals(queued.cf.rule, originCF.rule))?.cf;
8480
+ }
8481
+ return targetCF || { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
8218
8482
  }
8219
8483
  }
8220
8484
 
8221
8485
  class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8222
8486
  uuidGenerator = new UuidGenerator();
8487
+ queuedChanges = {};
8223
8488
  copy(data) {
8224
8489
  const { rowsIndexes, columnsIndexes } = data;
8225
8490
  const sheetId = data.sheetId;
@@ -8236,6 +8501,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8236
8501
  return { dvRules };
8237
8502
  }
8238
8503
  paste(target, clippedContent, options) {
8504
+ this.queuedChanges = {};
8239
8505
  if (options.pasteOption) {
8240
8506
  return;
8241
8507
  }
@@ -8247,6 +8513,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8247
8513
  else {
8248
8514
  this.pasteFromCut(sheetId, zones, clippedContent);
8249
8515
  }
8516
+ this.executeQueuedChanges();
8250
8517
  }
8251
8518
  pasteFromCut(sheetId, target, content) {
8252
8519
  const selection = target[0];
@@ -8293,29 +8560,55 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8293
8560
  }
8294
8561
  }
8295
8562
  getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
8296
- const ruleInTargetSheet = this.getters
8563
+ let targetRule = this.getters
8297
8564
  .getDataValidationRules(targetSheetId)
8298
8565
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
8299
8566
  originRule.isBlocking === rule.isBlocking);
8300
- return ruleInTargetSheet
8301
- ? ruleInTargetSheet
8302
- : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
8567
+ const queuedRules = this.queuedChanges[targetSheetId];
8568
+ if (!targetRule && queuedRules) {
8569
+ targetRule = queuedRules.find((queued) => deepEquals(originRule.criterion, queued.rule.criterion) &&
8570
+ originRule.isBlocking === queued.rule.isBlocking)?.rule;
8571
+ }
8572
+ return (targetRule || {
8573
+ ...originRule,
8574
+ id: newId ? this.uuidGenerator.uuidv4() : originRule.id,
8575
+ ranges: [],
8576
+ });
8303
8577
  }
8304
8578
  /**
8305
8579
  * Add or remove XCs to a given data validation rule.
8306
8580
  */
8307
8581
  adaptDataValidationRule(sheetId, rule, toAdd, toRemove) {
8308
- const dvZones = rule.ranges.map((range) => range.zone);
8309
- const newDvZones = recomputeZones([...dvZones, ...toAdd], toRemove);
8310
- if (newDvZones.length === 0) {
8311
- this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: rule.id });
8312
- return;
8582
+ if (!this.queuedChanges[sheetId]) {
8583
+ this.queuedChanges[sheetId] = [];
8584
+ }
8585
+ const queuedChange = this.queuedChanges[sheetId].find((queued) => queued.rule.id === rule.id);
8586
+ if (!queuedChange) {
8587
+ this.queuedChanges[sheetId].push({ toAdd, toRemove, rule });
8588
+ }
8589
+ else {
8590
+ queuedChange.toAdd.push(...toAdd);
8591
+ queuedChange.toRemove.push(...toRemove);
8592
+ }
8593
+ }
8594
+ executeQueuedChanges() {
8595
+ for (const sheetId in this.queuedChanges) {
8596
+ for (const { toAdd, toRemove, rule: dv } of this.queuedChanges[sheetId]) {
8597
+ // Remove the zones first in case the same position is in toAdd and toRemove
8598
+ const dvZones = dv.ranges.map((range) => range.zone);
8599
+ const withRemovedZones = recomputeZones(dvZones, toRemove);
8600
+ const newDvZones = recomputeZones([...withRemovedZones, ...toAdd], []);
8601
+ if (newDvZones.length === 0) {
8602
+ this.dispatch("REMOVE_DATA_VALIDATION_RULE", { sheetId, id: dv.id });
8603
+ continue;
8604
+ }
8605
+ this.dispatch("ADD_DATA_VALIDATION_RULE", {
8606
+ rule: dv,
8607
+ ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
8608
+ sheetId,
8609
+ });
8610
+ }
8313
8611
  }
8314
- this.dispatch("ADD_DATA_VALIDATION_RULE", {
8315
- rule,
8316
- ranges: newDvZones.map((zone) => this.getters.getRangeDataFromZone(sheetId, zone)),
8317
- sheetId,
8318
- });
8319
8612
  }
8320
8613
  }
8321
8614
 
@@ -10286,6 +10579,10 @@ function makeArg(str, description) {
10286
10579
  description,
10287
10580
  type: types,
10288
10581
  };
10582
+ const acceptErrors = types.includes("ANY") || types.includes("RANGE");
10583
+ if (acceptErrors) {
10584
+ result.acceptErrors = true;
10585
+ }
10289
10586
  if (isOptional) {
10290
10587
  result.optional = true;
10291
10588
  }
@@ -12348,8 +12645,8 @@ const AVERAGEIFS = {
12348
12645
  const COUNT = {
12349
12646
  description: _t("The number of numeric values in dataset."),
12350
12647
  args: [
12351
- arg("value1 (number, range<number>)", _t("The first value or range to consider when counting.")),
12352
- arg("value2 (number, range<number>, repeating)", _t("Additional values or ranges to consider when counting.")),
12648
+ arg("value1 (number, any, range<number>)", _t("The first value or range to consider when counting.")),
12649
+ arg("value2 (number, any, range<number>, repeating)", _t("Additional values or ranges to consider when counting.")),
12353
12650
  ],
12354
12651
  compute: function (...values) {
12355
12652
  return countNumbers(values, this.locale);
@@ -12738,6 +13035,7 @@ const PEARSON = {
12738
13035
  },
12739
13036
  isExported: true,
12740
13037
  };
13038
+ // CORREL
12741
13039
  // In GSheet, CORREL is just an alias to PEARSON
12742
13040
  const CORREL = PEARSON;
12743
13041
  // -----------------------------------------------------------------------------
@@ -13348,7 +13646,7 @@ function getMatchingCells(database, field, criteria, locale) {
13348
13646
  }
13349
13647
  const databaseArgs = [
13350
13648
  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.")),
13351
- arg("field (any)", _t("Indicates which column in database contains the values to be extracted and operated on.")),
13649
+ arg("field (number, string)", _t("Indicates which column in database contains the values to be extracted and operated on.")),
13352
13650
  arg("criteria (range)", _t("An array or range containing zero or more criteria to filter the database values by before operating.")),
13353
13651
  ];
13354
13652
  // -----------------------------------------------------------------------------
@@ -14374,7 +14672,6 @@ function sortCells(cells, sortDirection, emptyCellAsZero) {
14374
14672
  return cellsToSort.sort(cellsSortingCriterion(sortDirection));
14375
14673
  }
14376
14674
  function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
14377
- let result = DispatchResult.Success;
14378
14675
  //several columns => bypass the contiguity check
14379
14676
  let multiColumns = zone.right > zone.left;
14380
14677
  if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
@@ -14394,49 +14691,37 @@ function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
14394
14691
  }
14395
14692
  }
14396
14693
  }
14397
- const { col, row } = anchor;
14398
14694
  if (multiColumns) {
14399
- result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
14695
+ interactiveSort(env, sheetId, anchor, zone, sortDirection);
14696
+ return;
14697
+ }
14698
+ const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
14699
+ if (isEqual(contiguousZone, zone)) {
14700
+ interactiveSort(env, sheetId, anchor, zone, sortDirection);
14400
14701
  }
14401
14702
  else {
14402
- // check contiguity
14403
- const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
14404
- if (isEqual(contiguousZone, zone)) {
14405
- // merge as it is
14406
- result = env.model.dispatch("SORT_CELLS", {
14407
- sheetId,
14408
- col,
14409
- row,
14410
- zone,
14411
- sortDirection,
14412
- });
14413
- }
14414
- else {
14415
- env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
14416
- zone = contiguousZone;
14417
- result = env.model.dispatch("SORT_CELLS", {
14418
- sheetId,
14419
- col,
14420
- row,
14421
- zone,
14422
- sortDirection,
14423
- });
14424
- }, () => {
14425
- result = env.model.dispatch("SORT_CELLS", {
14426
- sheetId,
14427
- col,
14428
- row,
14429
- zone,
14430
- sortDirection,
14431
- });
14432
- });
14433
- }
14703
+ env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
14434
14704
  }
14705
+ }
14706
+ function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
14707
+ const result = env.model.dispatch("SORT_CELLS", {
14708
+ sheetId,
14709
+ col: anchor.col,
14710
+ row: anchor.row,
14711
+ zone,
14712
+ sortDirection,
14713
+ sortOptions,
14714
+ });
14435
14715
  if (result.isCancelledBecause("InvalidSortZone" /* CommandResult.InvalidSortZone */)) {
14436
14716
  const { col, row } = anchor;
14437
14717
  env.model.selection.selectZone({ cell: { col, row }, zone });
14438
14718
  env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
14439
14719
  }
14720
+ if (result.isCancelledBecause("SortZoneWithArrayFormulas" /* CommandResult.SortZoneWithArrayFormulas */)) {
14721
+ const { col, row } = anchor;
14722
+ env.model.selection.selectZone({ cell: { col, row }, zone });
14723
+ env.raiseError(_t("Cannot sort a zone with array formulas."));
14724
+ }
14440
14725
  }
14441
14726
 
14442
14727
  function sortMatrix(matrix, locale, ...criteria) {
@@ -17730,7 +18015,7 @@ const IFS = {
17730
18015
  args: [
17731
18016
  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.")),
17732
18017
  arg("value1 (any)", _t("The returned value if condition1 is TRUE.")),
17733
- arg("condition2 (boolean, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
18018
+ arg("condition2 (boolean, any, repeating)", _t("Additional conditions to be evaluated if the previous ones are FALSE.")),
17734
18019
  arg("value2 (any, repeating)", _t("Additional values to be returned if their corresponding conditions are TRUE.")),
17735
18020
  ],
17736
18021
  compute: function (...values) {
@@ -17969,7 +18254,7 @@ const COLUMNS = {
17969
18254
  const HLOOKUP = {
17970
18255
  description: _t("Horizontal lookup"),
17971
18256
  args: [
17972
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18257
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
17973
18258
  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.")),
17974
18259
  arg("index (number)", _t("The row index of the value to be returned, where the first row in range is numbered 1.")),
17975
18260
  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.")),
@@ -17977,9 +18262,6 @@ const HLOOKUP = {
17977
18262
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
17978
18263
  const _index = Math.trunc(toNumber(index?.value, this.locale));
17979
18264
  assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
17980
- if (searchKey && isEvaluationError(searchKey.value)) {
17981
- return searchKey;
17982
- }
17983
18265
  const getValueFromRange = (range, index) => range[index][0].value;
17984
18266
  const _isSorted = toBoolean(isSorted.value);
17985
18267
  const colIndex = _isSorted
@@ -18076,7 +18358,7 @@ const INDIRECT = {
18076
18358
  const LOOKUP = {
18077
18359
  description: _t("Look up a value."),
18078
18360
  args: [
18079
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18361
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18080
18362
  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.")),
18081
18363
  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.")),
18082
18364
  ],
@@ -18116,7 +18398,7 @@ const DEFAULT_SEARCH_TYPE = 1;
18116
18398
  const MATCH = {
18117
18399
  description: _t("Position of item in range that matches value."),
18118
18400
  args: [
18119
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18401
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18120
18402
  arg("range (any, range)", _t("The one-dimensional array to be searched.")),
18121
18403
  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.")),
18122
18404
  ],
@@ -18191,7 +18473,7 @@ const ROWS = {
18191
18473
  const VLOOKUP = {
18192
18474
  description: _t("Vertical lookup."),
18193
18475
  args: [
18194
- arg("search_key (any)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18476
+ arg("search_key (string, number, boolean)", _t("The value to search for. For example, 42, 'Cats', or I24.")),
18195
18477
  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.")),
18196
18478
  arg("index (number)", _t("The column index of the value to be returned, where the first column in range is numbered 1.")),
18197
18479
  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.")),
@@ -18199,9 +18481,6 @@ const VLOOKUP = {
18199
18481
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18200
18482
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18201
18483
  assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18202
- if (searchKey && isEvaluationError(searchKey.value)) {
18203
- return searchKey;
18204
- }
18205
18484
  const getValueFromRange = (range, index) => range[0][index].value;
18206
18485
  const _isSorted = toBoolean(isSorted.value);
18207
18486
  const rowIndex = _isSorted
@@ -18227,7 +18506,7 @@ const MATCH_MODE = {
18227
18506
  const XLOOKUP = {
18228
18507
  description: _t("Search a range for a match and return the corresponding item from a second range."),
18229
18508
  args: [
18230
- arg("search_key (any)", _t("The value to search for.")),
18509
+ arg("search_key (string,number,boolean)", _t("The value to search for.")),
18231
18510
  arg("lookup_range (any, range)", _t("The range to consider for the search. Should be a single column or a single row.")),
18232
18511
  arg("return_range (any, range)", _t("The range containing the return value. Should have the same dimensions as lookup_range.")),
18233
18512
  arg("if_not_found (any, optional)", _t("If a valid match is not found, return this value.")),
@@ -18252,9 +18531,6 @@ const XLOOKUP = {
18252
18531
  assert(() => lookupDirection === "col"
18253
18532
  ? returnRange[0].length === lookupRange[0].length
18254
18533
  : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18255
- if (searchKey && isEvaluationError(searchKey.value)) {
18256
- return [[searchKey]];
18257
- }
18258
18534
  const getElement = lookupDirection === "col"
18259
18535
  ? (range, index) => range[0][index].value
18260
18536
  : (range, index) => range[index][0].value;
@@ -18279,13 +18555,14 @@ const XLOOKUP = {
18279
18555
  //--------------------------------------------------------------------------
18280
18556
  // Pivot functions
18281
18557
  //--------------------------------------------------------------------------
18558
+ // PIVOT.VALUE
18282
18559
  const PIVOT_VALUE = {
18283
18560
  description: _t("Get the value from a pivot."),
18284
18561
  args: [
18285
- arg("pivot_id (string)", _t("ID of the pivot.")),
18562
+ arg("pivot_id (number,string)", _t("ID of the pivot.")),
18286
18563
  arg("measure_name (string)", _t("Name of the measure.")),
18287
18564
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18288
- arg("domain_value (string,optional,repeating)", _t("Value.")),
18565
+ arg("domain_value (number,string,boolean,optional,repeating)", _t("Value.")),
18289
18566
  ],
18290
18567
  compute: function (formulaId, measureName, ...domainArgs) {
18291
18568
  const _pivotFormulaId = toString(formulaId);
@@ -18312,12 +18589,13 @@ const PIVOT_VALUE = {
18312
18589
  return pivot.getPivotCellValueAndFormat(_measure, domain);
18313
18590
  },
18314
18591
  };
18592
+ // PIVOT.HEADER
18315
18593
  const PIVOT_HEADER = {
18316
18594
  description: _t("Get the header of a pivot."),
18317
18595
  args: [
18318
- arg("pivot_id (string)", _t("ID of the pivot.")),
18596
+ arg("pivot_id (number,string)", _t("ID of the pivot.")),
18319
18597
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18320
- arg("domain_value (string,optional,repeating)", _t("Value.")),
18598
+ arg("domain_value (number,string,value,optional,repeating)", _t("Value.")),
18321
18599
  ],
18322
18600
  compute: function (pivotId, ...domainArgs) {
18323
18601
  const _pivotFormulaId = toString(pivotId);
@@ -18564,8 +18842,8 @@ const getNeutral = { number: 0, string: "", boolean: false };
18564
18842
  const EQ = {
18565
18843
  description: _t("Equal."),
18566
18844
  args: [
18567
- arg("value1 (any)", _t("The first value.")),
18568
- arg("value2 (any)", _t("The value to test against value1 for equality.")),
18845
+ arg("value1 (string, number, boolean)", _t("The first value.")),
18846
+ arg("value2 (string, number, boolean)", _t("The value to test against value1 for equality.")),
18569
18847
  ],
18570
18848
  compute: function (value1, value2) {
18571
18849
  if (isEvaluationError(value1?.value)) {
@@ -18616,8 +18894,8 @@ function applyRelationalOperator(value1, value2, cb) {
18616
18894
  const GT = {
18617
18895
  description: _t("Strictly greater than."),
18618
18896
  args: [
18619
- arg("value1 (any)", _t("The value to test as being greater than value2.")),
18620
- arg("value2 (any)", _t("The second value.")),
18897
+ arg("value1 (number, string, boolean)", _t("The value to test as being greater than value2.")),
18898
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18621
18899
  ],
18622
18900
  compute: function (value1, value2) {
18623
18901
  return applyRelationalOperator(value1, value2, (v1, v2) => {
@@ -18631,8 +18909,8 @@ const GT = {
18631
18909
  const GTE = {
18632
18910
  description: _t("Greater than or equal to."),
18633
18911
  args: [
18634
- arg("value1 (any)", _t("The value to test as being greater than or equal to value2.")),
18635
- arg("value2 (any)", _t("The second value.")),
18912
+ arg("value1 (number, string, boolean)", _t("The value to test as being greater than or equal to value2.")),
18913
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18636
18914
  ],
18637
18915
  compute: function (value1, value2) {
18638
18916
  return applyRelationalOperator(value1, value2, (v1, v2) => {
@@ -18646,8 +18924,8 @@ const GTE = {
18646
18924
  const LT = {
18647
18925
  description: _t("Less than."),
18648
18926
  args: [
18649
- arg("value1 (any)", _t("The value to test as being less than value2.")),
18650
- arg("value2 (any)", _t("The second value.")),
18927
+ arg("value1 (number, string, boolean)", _t("The value to test as being less than value2.")),
18928
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18651
18929
  ],
18652
18930
  compute: function (value1, value2) {
18653
18931
  const result = GTE.compute.bind(this)(value1, value2);
@@ -18663,8 +18941,8 @@ const LT = {
18663
18941
  const LTE = {
18664
18942
  description: _t("Less than or equal to."),
18665
18943
  args: [
18666
- arg("value1 (any)", _t("The value to test as being less than or equal to value2.")),
18667
- arg("value2 (any)", _t("The second value.")),
18944
+ arg("value1 (number, string, boolean)", _t("The value to test as being less than or equal to value2.")),
18945
+ arg("value2 (number, string, boolean)", _t("The second value.")),
18668
18946
  ],
18669
18947
  compute: function (value1, value2) {
18670
18948
  const result = GT.compute.bind(this)(value1, value2);
@@ -18712,8 +18990,8 @@ const MULTIPLY = {
18712
18990
  const NE = {
18713
18991
  description: _t("Not equal."),
18714
18992
  args: [
18715
- arg("value1 (any)", _t("The first value.")),
18716
- arg("value2 (any)", _t("The value to test against value1 for inequality.")),
18993
+ arg("value1 (string, number, boolean)", _t("The first value.")),
18994
+ arg("value2 (string, number, boolean)", _t("The value to test against value1 for inequality.")),
18717
18995
  ],
18718
18996
  compute: function (value1, value2) {
18719
18997
  const result = EQ.compute.bind(this)(value1, value2);
@@ -19631,6 +19909,16 @@ function createComputeFunction(descr, functionName) {
19631
19909
  });
19632
19910
  }
19633
19911
  function errorHandlingCompute(...args) {
19912
+ for (let i = 0; i < args.length; i++) {
19913
+ const arg = args[i];
19914
+ const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
19915
+ // Early exit if the argument is an error and the function does not accept errors
19916
+ // We only check scalar arguments, not matrix arguments for performance reasons.
19917
+ // Casting helpers are responsible for handling errors in matrix arguments.
19918
+ if (!argDefinition.acceptErrors && !isMatrix(arg) && isEvaluationError(arg?.value)) {
19919
+ return arg;
19920
+ }
19921
+ }
19634
19922
  try {
19635
19923
  return computeFunctionToObject.apply(this, args);
19636
19924
  }
@@ -19639,6 +19927,9 @@ function createComputeFunction(descr, functionName) {
19639
19927
  }
19640
19928
  }
19641
19929
  function computeFunctionToObject(...args) {
19930
+ if (this.debug) {
19931
+ debugger;
19932
+ }
19642
19933
  const result = descr.compute.apply(this, args);
19643
19934
  if (!isMatrix(result)) {
19644
19935
  if (typeof result === "object" && result !== null && "value" in result) {
@@ -20748,6 +21039,7 @@ function compileTokensOrThrow(tokens) {
20748
21039
  }
20749
21040
  if (ast.debug) {
20750
21041
  code.append("debugger;");
21042
+ code.append(`ctx["debug"] = true;`);
20751
21043
  }
20752
21044
  switch (ast.type) {
20753
21045
  case "BOOLEAN":
@@ -21346,217 +21638,6 @@ autoCompleteProviders.add("sheet_names", {
21346
21638
  },
21347
21639
  });
21348
21640
 
21349
- /**
21350
- * Add the `https` prefix to the url if it's missing
21351
- */
21352
- function withHttps(url) {
21353
- return !/^https?:\/\//i.test(url) ? `https://${url}` : url;
21354
- }
21355
- const urlRegistry = new Registry();
21356
- function createWebLink(url, label) {
21357
- url = withHttps(url);
21358
- return {
21359
- url,
21360
- label: label || url,
21361
- isExternal: true,
21362
- isUrlEditable: true,
21363
- };
21364
- }
21365
- urlRegistry.add("sheet_URL", {
21366
- match: (url) => isSheetUrl(url),
21367
- createLink: (url, label) => {
21368
- return {
21369
- label,
21370
- url,
21371
- isExternal: false,
21372
- isUrlEditable: false,
21373
- };
21374
- },
21375
- urlRepresentation(url, getters) {
21376
- const sheetId = parseSheetUrl(url);
21377
- return getters.tryGetSheetName(sheetId) || _t("Invalid sheet");
21378
- },
21379
- open(url, env) {
21380
- const sheetId = parseSheetUrl(url);
21381
- const result = env.model.dispatch("ACTIVATE_SHEET", {
21382
- sheetIdFrom: env.model.getters.getActiveSheetId(),
21383
- sheetIdTo: sheetId,
21384
- });
21385
- if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
21386
- env.notifyUser({
21387
- type: "warning",
21388
- sticky: false,
21389
- text: _t("Cannot open the link because the linked sheet is hidden."),
21390
- });
21391
- }
21392
- },
21393
- sequence: 0,
21394
- });
21395
- const WebUrlSpec = {
21396
- createLink: createWebLink,
21397
- match: (url) => isWebLink(url),
21398
- open: (url) => window.open(url, "_blank"),
21399
- urlRepresentation: (url) => url,
21400
- sequence: 0,
21401
- };
21402
- function findMatchingSpec(url) {
21403
- return (urlRegistry
21404
- .getAll()
21405
- .sort((a, b) => a.sequence - b.sequence)
21406
- .find((urlType) => urlType.match(url)) || WebUrlSpec);
21407
- }
21408
- function urlRepresentation(link, getters) {
21409
- return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
21410
- }
21411
- function openLink(link, env) {
21412
- findMatchingSpec(link.url).open(link.url, env);
21413
- }
21414
- function detectLink(value) {
21415
- if (typeof value !== "string") {
21416
- return undefined;
21417
- }
21418
- if (isMarkdownLink(value)) {
21419
- const { label, url } = parseMarkdownLink(value);
21420
- return findMatchingSpec(url).createLink(url, label);
21421
- }
21422
- else if (isWebLink(value)) {
21423
- return createWebLink(value);
21424
- }
21425
- return undefined;
21426
- }
21427
-
21428
- function evaluateLiteral(literalCell, localeFormat) {
21429
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
21430
- const functionResult = { value, format: localeFormat.format };
21431
- return createEvaluatedCell(functionResult, localeFormat.locale);
21432
- }
21433
- function parseLiteral(content, locale) {
21434
- if (content.startsWith("=")) {
21435
- throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
21436
- }
21437
- if (content === "") {
21438
- return null;
21439
- }
21440
- if (isNumber(content, DEFAULT_LOCALE)) {
21441
- return parseNumber(content, DEFAULT_LOCALE);
21442
- }
21443
- const internalDate = parseDateTime(content, locale);
21444
- if (internalDate) {
21445
- return internalDate.value;
21446
- }
21447
- if (isBoolean(content)) {
21448
- return content.toUpperCase() === "TRUE";
21449
- }
21450
- return content;
21451
- }
21452
- function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
21453
- const link = detectLink(functionResult.value);
21454
- if (!link) {
21455
- return _createEvaluatedCell(functionResult, locale, cell);
21456
- }
21457
- const value = parseLiteral(link.label, locale);
21458
- const format = functionResult.format ||
21459
- (typeof value === "number"
21460
- ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
21461
- : undefined);
21462
- const linkPayload = {
21463
- value,
21464
- format,
21465
- };
21466
- return {
21467
- ..._createEvaluatedCell(linkPayload, locale, cell),
21468
- link,
21469
- };
21470
- }
21471
- function _createEvaluatedCell(functionResult, locale, cell) {
21472
- let { value, format, message } = functionResult;
21473
- format = cell?.format || format;
21474
- const formattedValue = formatValue(value, { format, locale });
21475
- if (isEvaluationError(value)) {
21476
- return errorCell(value, message);
21477
- }
21478
- if (isTextFormat(format)) {
21479
- // TO DO:
21480
- // with the next line, the value of the cell is transformed depending on the format.
21481
- // This shouldn't happen, by doing this, the formulas handling numbers are not able
21482
- // to interpret the value as a number.
21483
- return textCell(toString(value), format, formattedValue);
21484
- }
21485
- if (value === null) {
21486
- return emptyCell(format);
21487
- }
21488
- if (typeof value === "number") {
21489
- if (isDateTimeFormat(format || "")) {
21490
- return dateTimeCell(value, format, formattedValue);
21491
- }
21492
- return numberCell(value, format, formattedValue);
21493
- }
21494
- if (typeof value === "boolean") {
21495
- return booleanCell(value, format, formattedValue);
21496
- }
21497
- return textCell(value, format, formattedValue);
21498
- }
21499
- function textCell(value, format, formattedValue) {
21500
- return {
21501
- value,
21502
- format,
21503
- formattedValue,
21504
- type: CellValueType.text,
21505
- isAutoSummable: true,
21506
- defaultAlign: "left",
21507
- };
21508
- }
21509
- function numberCell(value, format, formattedValue) {
21510
- return {
21511
- value: value || 0, // necessary to avoid "-0" and NaN values,
21512
- format,
21513
- formattedValue,
21514
- type: CellValueType.number,
21515
- isAutoSummable: true,
21516
- defaultAlign: "right",
21517
- };
21518
- }
21519
- const emptyCell = memoize(function emptyCell(format) {
21520
- return {
21521
- value: null,
21522
- format,
21523
- formattedValue: "",
21524
- type: CellValueType.empty,
21525
- isAutoSummable: true,
21526
- defaultAlign: "left",
21527
- };
21528
- });
21529
- function dateTimeCell(value, format, formattedValue) {
21530
- return {
21531
- value,
21532
- format,
21533
- formattedValue,
21534
- type: CellValueType.number,
21535
- isAutoSummable: false,
21536
- defaultAlign: "right",
21537
- };
21538
- }
21539
- function booleanCell(value, format, formattedValue) {
21540
- return {
21541
- value,
21542
- format,
21543
- formattedValue,
21544
- type: CellValueType.boolean,
21545
- isAutoSummable: false,
21546
- defaultAlign: "center",
21547
- };
21548
- }
21549
- function errorCell(value, message) {
21550
- return {
21551
- value,
21552
- formattedValue: value,
21553
- message,
21554
- type: CellValueType.error,
21555
- isAutoSummable: false,
21556
- defaultAlign: "center",
21557
- };
21558
- }
21559
-
21560
21641
  /**
21561
21642
  * An AutofillModifierImplementation is used to describe how to handle a
21562
21643
  * AutofillModifier.
@@ -22921,6 +23002,10 @@ var WarningTypes;
22921
23002
  WarningTypes["CfIconSetEmptyIconNotSupported"] = "IconSets with empty icons";
22922
23003
  WarningTypes["BadlyFormattedHyperlink"] = "Badly formatted hyperlink";
22923
23004
  WarningTypes["NumFmtIdNotSupported"] = "Number format";
23005
+ WarningTypes["TimeDataValidationNotSupported"] = "Time data validation rules";
23006
+ WarningTypes["TextLengthDataValidationNotSupported"] = "Text length data validation rules";
23007
+ WarningTypes["WholeNumberDataValidationNotSupported"] = "Whole number data validation rules";
23008
+ WarningTypes["NotEqualDateDataValidationNotSupported"] = "Not equal date data validation rules";
22924
23009
  })(WarningTypes || (WarningTypes = {}));
22925
23010
  class XLSXImportWarningManager {
22926
23011
  _parsingWarnings = new Set();
@@ -23299,6 +23384,25 @@ const IMAGE_EXTENSION_TO_MIMETYPE_MAPPING = {
23299
23384
  webp: "image/webp",
23300
23385
  jpg: "image/jpeg",
23301
23386
  };
23387
+ const XLSX_DV_DECIMAL_OPERATOR_MAPPING = {
23388
+ between: "isBetween",
23389
+ notBetween: "isNotBetween",
23390
+ equal: "isEqual",
23391
+ notEqual: "isNotEqual",
23392
+ greaterThan: "isGreaterThan",
23393
+ greaterThanOrEqual: "isGreaterOrEqualTo",
23394
+ lessThan: "isLessThan",
23395
+ lessThanOrEqual: "isLessOrEqualTo",
23396
+ };
23397
+ const XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING = {
23398
+ between: "dateIsBetween",
23399
+ notBetween: "dateIsNotBetween",
23400
+ equal: "dateIs",
23401
+ greaterThan: "dateIsAfter",
23402
+ greaterThanOrEqual: "dateIsOnOrAfter",
23403
+ lessThan: "dateIsBefore",
23404
+ lessThanOrEqual: "dateIsOnOrBefore",
23405
+ };
23302
23406
 
23303
23407
  /**
23304
23408
  * Most of the functions could stay private, but are exported for testing purposes
@@ -24080,6 +24184,20 @@ function getRowPosition(rowIndex, sheetData) {
24080
24184
  }
24081
24185
  return position / HEIGHT_FACTOR;
24082
24186
  }
24187
+ /**
24188
+ * Convert the o-spreadsheet data validation decimal
24189
+ * criterion type to the corresponding excel operator.
24190
+ */
24191
+ function convertDecimalCriterionTypeToExcelOperator(operator) {
24192
+ return Object.keys(XLSX_DV_DECIMAL_OPERATOR_MAPPING).find((key) => XLSX_DV_DECIMAL_OPERATOR_MAPPING[key] === operator);
24193
+ }
24194
+ /**
24195
+ * Convert the o-spreadsheet data validation date
24196
+ * criterion type to the corresponding excel operator.
24197
+ */
24198
+ function convertDateCriterionTypeToExcelOperator(operator) {
24199
+ return Object.keys(XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING).find((key) => XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[key] === operator);
24200
+ }
24083
24201
 
24084
24202
  function convertFigures(sheetData) {
24085
24203
  let id = 1;
@@ -24189,6 +24307,112 @@ function getPositionFromAnchor(anchor, sheetData) {
24189
24307
  };
24190
24308
  }
24191
24309
 
24310
+ function convertDataValidationRules(xlsxDataValidations, warningManager) {
24311
+ const dvRules = [];
24312
+ let dvId = 1;
24313
+ for (const dv of xlsxDataValidations) {
24314
+ if (!dv) {
24315
+ continue;
24316
+ }
24317
+ switch (dv.type) {
24318
+ case "time":
24319
+ warningManager.generateNotSupportedWarning(WarningTypes.TimeDataValidationNotSupported);
24320
+ break;
24321
+ case "textLength":
24322
+ warningManager.generateNotSupportedWarning(WarningTypes.TextLengthDataValidationNotSupported);
24323
+ break;
24324
+ case "whole":
24325
+ warningManager.generateNotSupportedWarning(WarningTypes.WholeNumberDataValidationNotSupported);
24326
+ break;
24327
+ case "decimal":
24328
+ const decimalRule = convertDecimalRule(dvId++, dv);
24329
+ dvRules.push(decimalRule);
24330
+ break;
24331
+ case "list":
24332
+ const listRule = convertListrule(dvId++, dv);
24333
+ dvRules.push(listRule);
24334
+ break;
24335
+ case "date":
24336
+ if (dv.operator === "notEqual") {
24337
+ warningManager.generateNotSupportedWarning(WarningTypes.NotEqualDateDataValidationNotSupported);
24338
+ break;
24339
+ }
24340
+ const dateRule = convertDateRule(dvId++, dv);
24341
+ dvRules.push(dateRule);
24342
+ break;
24343
+ case "custom":
24344
+ const customRule = convertCustomRule(dvId++, dv);
24345
+ dvRules.push(customRule);
24346
+ break;
24347
+ }
24348
+ }
24349
+ return dvRules;
24350
+ }
24351
+ function convertDecimalRule(id, dv) {
24352
+ const values = [dv.formula1.toString()];
24353
+ if (dv.formula2) {
24354
+ values.push(dv.formula2.toString());
24355
+ }
24356
+ return {
24357
+ id: id.toString(),
24358
+ ranges: dv.sqref,
24359
+ isBlocking: dv.errorStyle !== "warning",
24360
+ criterion: {
24361
+ type: XLSX_DV_DECIMAL_OPERATOR_MAPPING[dv.operator],
24362
+ values,
24363
+ },
24364
+ };
24365
+ }
24366
+ function convertListrule(id, dv) {
24367
+ const formula1 = dv.formula1.toString();
24368
+ const isRangeRule = rangeReference.test(formula1);
24369
+ return {
24370
+ id: id.toString(),
24371
+ ranges: dv.sqref,
24372
+ isBlocking: dv.errorStyle !== "warning",
24373
+ criterion: {
24374
+ type: isRangeRule ? "isValueInRange" : "isValueInList",
24375
+ values: isRangeRule ? [formula1] : formula1.replaceAll('"', "").split(","),
24376
+ displayStyle: "arrow",
24377
+ },
24378
+ };
24379
+ }
24380
+ function convertDateRule(id, dv) {
24381
+ let criterion;
24382
+ const values = [dv.formula1.toString()];
24383
+ if (dv.formula2) {
24384
+ values.push(dv.formula2.toString());
24385
+ criterion = {
24386
+ type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
24387
+ values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
24388
+ };
24389
+ }
24390
+ else {
24391
+ criterion = {
24392
+ type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
24393
+ values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
24394
+ dateValue: "exactDate",
24395
+ };
24396
+ }
24397
+ return {
24398
+ id: id.toString(),
24399
+ ranges: dv.sqref,
24400
+ isBlocking: dv.errorStyle !== "warning",
24401
+ criterion: criterion,
24402
+ };
24403
+ }
24404
+ function convertCustomRule(id, dv) {
24405
+ return {
24406
+ id: id.toString(),
24407
+ ranges: dv.sqref,
24408
+ isBlocking: dv.errorStyle !== "warning",
24409
+ criterion: {
24410
+ type: "customFormula",
24411
+ values: [`=${dv.formula1.toString()}`],
24412
+ },
24413
+ };
24414
+ }
24415
+
24192
24416
  /**
24193
24417
  * Match external reference (ex. '[1]Sheet 3'!$B$4)
24194
24418
  *
@@ -24309,6 +24533,7 @@ function convertSheets(data, warningManager) {
24309
24533
  cols: convertCols(sheet, sheetDims[0], colHeaderGroups),
24310
24534
  rows: convertRows(sheet, sheetDims[1], rowHeaderGroups),
24311
24535
  conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),
24536
+ dataValidationRules: convertDataValidationRules(sheet.dataValidations, warningManager),
24312
24537
  figures: convertFigures(sheet),
24313
24538
  isVisible: sheet.isVisible,
24314
24539
  panes: sheetOptions
@@ -25588,6 +25813,41 @@ class XlsxCfExtractor extends XlsxBaseExtractor {
25588
25813
  }
25589
25814
  }
25590
25815
 
25816
+ class XlsxDataValidationExtractor extends XlsxBaseExtractor {
25817
+ theme;
25818
+ constructor(sheetFile, xlsxStructure, warningManager, theme) {
25819
+ super(sheetFile, xlsxStructure, warningManager);
25820
+ this.theme = theme;
25821
+ }
25822
+ extractDataValidations() {
25823
+ const dataValidations = this.mapOnElements({ parent: this.rootFile.file.xml, query: "worksheet > dataValidations > dataValidation" }, (dvElement) => {
25824
+ return {
25825
+ type: this.extractAttr(dvElement, "type", { required: true }).asString(),
25826
+ operator: this.extractAttr(dvElement, "operator", {
25827
+ default: "between",
25828
+ })?.asString(),
25829
+ sqref: this.extractAttr(dvElement, "sqref", { required: true }).asString().split(" "),
25830
+ errorStyle: this.extractAttr(dvElement, "errorStyle")?.asString(),
25831
+ formula1: this.extractDataValidationFormula(dvElement, 1)[0],
25832
+ formula2: this.extractDataValidationFormula(dvElement, 2)[0],
25833
+ showErrorMessage: this.extractAttr(dvElement, "showErrorMessage")?.asBool(),
25834
+ errorTitle: this.extractAttr(dvElement, "errorTitle")?.asString(),
25835
+ error: this.extractAttr(dvElement, "error")?.asString(),
25836
+ showInputMessage: this.extractAttr(dvElement, "showInputMessage")?.asBool(),
25837
+ promptTitle: this.extractAttr(dvElement, "promptTitle")?.asString(),
25838
+ prompt: this.extractAttr(dvElement, "prompt")?.asString(),
25839
+ allowBlank: this.extractAttr(dvElement, "allowBlank")?.asBool(),
25840
+ };
25841
+ });
25842
+ return dataValidations;
25843
+ }
25844
+ extractDataValidationFormula(dvElement, index) {
25845
+ return this.mapOnElements({ parent: dvElement, query: `formula${index}` }, (cfFormulaElements) => {
25846
+ return this.extractTextContent(cfFormulaElements, { required: true });
25847
+ });
25848
+ }
25849
+ }
25850
+
25591
25851
  class XlsxChartExtractor extends XlsxBaseExtractor {
25592
25852
  extractChart() {
25593
25853
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "c:chartSpace" }, (rootChartElement) => {
@@ -25955,6 +26215,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
25955
26215
  sharedFormulas: this.extractSharedFormulas(sheetElement),
25956
26216
  merges: this.extractMerges(sheetElement),
25957
26217
  cfs: this.extractConditionalFormats(),
26218
+ dataValidations: this.extractDataValidations(),
25958
26219
  figures: this.extractFigures(sheetElement),
25959
26220
  hyperlinks: this.extractHyperLinks(sheetElement),
25960
26221
  tables: this.extractTables(sheetElement),
@@ -26026,6 +26287,9 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26026
26287
  extractConditionalFormats() {
26027
26288
  return new XlsxCfExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractConditionalFormattings();
26028
26289
  }
26290
+ extractDataValidations() {
26291
+ return new XlsxDataValidationExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractDataValidations();
26292
+ }
26029
26293
  extractFigures(worksheet) {
26030
26294
  const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
26031
26295
  const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
@@ -27333,6 +27597,7 @@ function createEmptySheet(sheetId, name) {
27333
27597
  rows: {},
27334
27598
  merges: [],
27335
27599
  conditionalFormats: [],
27600
+ dataValidationRules: [],
27336
27601
  figures: [],
27337
27602
  tables: [],
27338
27603
  isVisible: true,
@@ -27407,17 +27672,15 @@ function interactivePasteFromOS(env, target, clipboardContent, pasteOption) {
27407
27672
  });
27408
27673
  }
27409
27674
  catch (error) {
27410
- const parsedSpreadsheetContent = clipboardContent[ClipboardMIMEType.OSpreadsheet]
27411
- ? JSON.parse(clipboardContent[ClipboardMIMEType.OSpreadsheet])
27412
- : {};
27413
- if (parsedSpreadsheetContent.version && parsedSpreadsheetContent.version !== CURRENT_VERSION) {
27675
+ const parsedSpreadsheetContent = clipboardContent.data;
27676
+ if (parsedSpreadsheetContent?.version !== CURRENT_VERSION) {
27414
27677
  env.raiseError(_t("An unexpected error occurred while pasting content.\
27415
27678
  This is probably due to a spreadsheet version mismatch."));
27416
27679
  }
27417
27680
  result = env.model.dispatch("PASTE_FROM_OS_CLIPBOARD", {
27418
27681
  target,
27419
27682
  clipboardContent: {
27420
- [ClipboardMIMEType.PlainText]: clipboardContent[ClipboardMIMEType.PlainText],
27683
+ text: clipboardContent.text,
27421
27684
  },
27422
27685
  pasteOption,
27423
27686
  });
@@ -27446,7 +27709,6 @@ const CfTerms = {
27446
27709
  ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
27447
27710
  ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
27448
27711
  ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
27449
- ["DataBarRangeValuesMismatch" /* CommandResult.DataBarRangeValuesMismatch */]: _t("All the ranges and the range values must have the same size"),
27450
27712
  Unexpected: _t("The rule is invalid for an unknown reason"),
27451
27713
  },
27452
27714
  ColorScale: _t("Color scale"),
@@ -27542,6 +27804,13 @@ const DVTerms = {
27542
27804
  numberValue: _t("The value must be a number"),
27543
27805
  dateValue: _t("The value must be a date"),
27544
27806
  validRange: _t("The value must be a valid range"),
27807
+ validFormula: _t("The formula must be valid"),
27808
+ },
27809
+ Errors: {
27810
+ ["InvalidRange" /* CommandResult.InvalidRange */]: _t("The range is invalid."),
27811
+ ["InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */]: _t("One or more of the provided criteria values are invalid. Please review and correct them."),
27812
+ ["InvalidNumberOfCriterionValues" /* CommandResult.InvalidNumberOfCriterionValues */]: _t("One or more of the provided criteria values are missing."),
27813
+ Unexpected: _t("The rule is invalid for an unknown reason."),
27545
27814
  },
27546
27815
  };
27547
27816
  const TableTerms = {
@@ -27752,7 +28021,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
27752
28021
  labels.length > dataSetsValues[0].data.length) {
27753
28022
  labels.shift();
27754
28023
  }
27755
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28024
+ ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
27756
28025
  if (definition.aggregated) {
27757
28026
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27758
28027
  }
@@ -27807,7 +28076,7 @@ function getLineChartData(definition, dataSets, labelRange, getters) {
27807
28076
  labels.length > dataSetsValues[0].data.length) {
27808
28077
  labels.shift();
27809
28078
  }
27810
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28079
+ ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
27811
28080
  if (axisType === "time") {
27812
28081
  ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
27813
28082
  }
@@ -27824,7 +28093,7 @@ function getLineChartData(definition, dataSets, labelRange, getters) {
27824
28093
  if (definition.cumulative) {
27825
28094
  let accumulator = 0;
27826
28095
  data = data.map((value) => {
27827
- if (!isNaN(value)) {
28096
+ if (!isNaN(parseFloat(value))) {
27828
28097
  accumulator += parseFloat(value);
27829
28098
  return accumulator;
27830
28099
  }
@@ -27857,11 +28126,11 @@ function getPieChartData(definition, dataSets, labelRange, getters) {
27857
28126
  labels.length > dataSetsValues[0].data.length) {
27858
28127
  labels.shift();
27859
28128
  }
27860
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28129
+ ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
27861
28130
  if (definition.aggregated) {
27862
28131
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27863
28132
  }
27864
- ({ dataSetsValues, labels } = filterNegativeValues(labels, dataSetsValues));
28133
+ ({ dataSetsValues, labels } = keepOnlyPositiveValues(labels, dataSetsValues));
27865
28134
  const dataSetFormat = getChartDatasetFormat(getters, dataSets, "left");
27866
28135
  return {
27867
28136
  dataSetsValues,
@@ -27879,7 +28148,7 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
27879
28148
  labels.length > dataSetsValues[0].data.length) {
27880
28149
  labels.shift();
27881
28150
  }
27882
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
28151
+ ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
27883
28152
  if (definition.aggregated) {
27884
28153
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
27885
28154
  }
@@ -28058,26 +28327,16 @@ function isLuxonTimeAdapterInstalled() {
28058
28327
  }
28059
28328
  return isInstalled;
28060
28329
  }
28061
- function filterNegativeValues(labels, datasets) {
28062
- const dataPointsIndexes = labels.reduce((indexes, label, i) => {
28063
- const shouldKeep = datasets.some((dataset) => {
28064
- const dataPoint = dataset.data[i];
28065
- return typeof dataPoint !== "number" || dataPoint >= 0;
28066
- });
28067
- if (shouldKeep) {
28068
- indexes.push(i);
28069
- }
28070
- return indexes;
28071
- }, []);
28072
- const filteredLabels = dataPointsIndexes.map((i) => labels[i] || "");
28073
- const filteredDatasets = datasets.map((dataset) => ({
28074
- ...dataset,
28075
- data: dataPointsIndexes.map((i) => {
28076
- const dataPoint = dataset.data[i];
28077
- return typeof dataPoint !== "number" || dataPoint >= 0 ? dataPoint : 0;
28078
- }),
28079
- }));
28080
- return { labels: filteredLabels, dataSetsValues: filteredDatasets };
28330
+ function keepOnlyPositiveValues(labels, datasets) {
28331
+ const numberOfDataPoints = Math.max(labels.length, ...datasets.map((dataset) => dataset.data?.length || 0));
28332
+ const filteredIndexes = range(0, numberOfDataPoints).filter((i) => datasets.some((ds) => typeof ds.data[i] === "number" && ds.data[i] > 0));
28333
+ return {
28334
+ labels: filteredIndexes.map((i) => labels[i] || ""),
28335
+ dataSetsValues: datasets.map((ds) => ({
28336
+ ...ds,
28337
+ data: filteredIndexes.map((i) => typeof ds.data[i] === "number" && ds.data[i] > 0 ? ds.data[i] : null),
28338
+ })),
28339
+ };
28081
28340
  }
28082
28341
  function fixEmptyLabelsForDateCharts(labels, dataSetsValues) {
28083
28342
  if (labels.length === 0 || labels.every((label) => !label)) {
@@ -28110,18 +28369,23 @@ function getData(getters, ds) {
28110
28369
  }
28111
28370
  return [];
28112
28371
  }
28113
- function filterEmptyDataPoints(labels, datasets) {
28372
+ /**
28373
+ * Filter the data points that:
28374
+ * - have neither a label nor a value
28375
+ * - have no label and a non-numeric value
28376
+ */
28377
+ function filterInvalidDataPoints(labels, datasets) {
28114
28378
  const numberOfDataPoints = Math.max(labels.length, ...datasets.map((dataset) => dataset.data?.length || 0));
28115
28379
  const dataPointsIndexes = range(0, numberOfDataPoints).filter((dataPointIndex) => {
28116
28380
  const label = labels[dataPointIndex];
28117
28381
  const values = datasets.map((dataset) => dataset.data?.[dataPointIndex]);
28118
- return label || values.some((value) => value === 0 || Boolean(value));
28382
+ return label || values.some((value) => typeof value === "number");
28119
28383
  });
28120
28384
  return {
28121
28385
  labels: dataPointsIndexes.map((i) => labels[i] || ""),
28122
28386
  dataSetsValues: datasets.map((dataset) => ({
28123
28387
  ...dataset,
28124
- data: dataPointsIndexes.map((i) => dataset.data[i]),
28388
+ data: dataPointsIndexes.map((i) => typeof dataset.data[i] === "number" ? dataset.data[i] : null),
28125
28389
  })),
28126
28390
  };
28127
28391
  }
@@ -28264,8 +28528,8 @@ function getBarChartDatasets(definition, args) {
28264
28528
  const dataset = {
28265
28529
  label,
28266
28530
  data,
28267
- borderColor: BORDER_CHART_COLOR,
28268
- borderWidth: 1,
28531
+ borderColor: definition.background || BACKGROUND_CHART_COLOR,
28532
+ borderWidth: definition.stacked ? 1 : 0,
28269
28533
  backgroundColor,
28270
28534
  yAxisID: definition.horizontal ? "y" : definition.dataSets?.[index].yAxisId || "y",
28271
28535
  xAxisID: "x",
@@ -28818,20 +29082,27 @@ function getWaterfallChartScales(definition, args) {
28818
29082
  return scales;
28819
29083
  }
28820
29084
  function getPyramidChartScales(definition, args) {
29085
+ const { dataSetsValues } = args;
28821
29086
  const scales = getBarChartScales(definition, args);
28822
29087
  const scalesXCallback = scales.x.ticks.callback;
28823
29088
  scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
29089
+ const maxValue = Math.max(...dataSetsValues.map((dataSet) => Math.max(...dataSet.data.map(Math.abs))));
29090
+ scales.x.suggestedMin = -maxValue;
29091
+ scales.x.suggestedMax = maxValue;
28824
29092
  return scales;
28825
29093
  }
28826
29094
  function getRadarChartScales(definition, args) {
28827
- const { locale, axisFormats } = args;
29095
+ const { locale, axisFormats, dataSetsValues } = args;
29096
+ const minValue = Math.min(...dataSetsValues.map((ds) => Math.min(...ds.data.filter((x) => !isNaN(x)))));
28828
29097
  return {
28829
29098
  r: {
29099
+ beginAtZero: true,
28830
29100
  ticks: {
28831
29101
  callback: formatTickValue({ format: axisFormats?.r, locale }),
28832
29102
  backdropColor: definition.background || "#FFFFFF",
28833
29103
  },
28834
29104
  pointLabels: { color: chartFontColor(definition.background) },
29105
+ suggestedMin: minValue < 0 ? minValue - 1 : 0,
28835
29106
  },
28836
29107
  };
28837
29108
  }
@@ -31932,14 +32203,9 @@ class FilterMenu extends owl.Component {
31932
32203
  }
31933
32204
  const sheetId = this.env.model.getters.getActiveSheetId();
31934
32205
  const contentZone = { ...tableZone, top: tableZone.top + 1 };
31935
- this.env.model.dispatch("SORT_CELLS", {
31936
- sheetId,
31937
- col: filterPosition.col,
31938
- row: contentZone.top,
31939
- zone: contentZone,
31940
- sortDirection,
31941
- sortOptions: { emptyCellAsZero: true, sortHeaders: true },
31942
- });
32206
+ const sortAnchor = { col: filterPosition.col, row: contentZone.top };
32207
+ const sortOptions = { emptyCellAsZero: true, sortHeaders: true };
32208
+ interactiveSort(this.env, sheetId, sortAnchor, contentZone, sortDirection, sortOptions);
31943
32209
  this.props.onClosed?.();
31944
32210
  }
31945
32211
  }
@@ -32937,13 +33203,11 @@ async function paste$1(env, pasteOption) {
32937
33203
  const osClipboard = await env.clipboard.read();
32938
33204
  switch (osClipboard.status) {
32939
33205
  case "ok":
32940
- const htmlDocument = new DOMParser().parseFromString(osClipboard.content[ClipboardMIMEType.Html] ?? "<div></div>", "text/html");
32941
- const osClipboardSpreadsheetContent = osClipboard.content[ClipboardMIMEType.OSpreadsheet] || "{}";
32942
- const clipboardId = JSON.parse(osClipboardSpreadsheetContent).clipboardId ??
32943
- htmlDocument.querySelector("div")?.getAttribute("data-clipboard-id");
33206
+ const clipboardContent = parseOSClipboardContent(osClipboard.content);
33207
+ const clipboardId = clipboardContent.data?.clipboardId;
32944
33208
  const target = env.model.getters.getSelectedZones();
32945
33209
  if (env.model.getters.getClipboardId() !== clipboardId) {
32946
- interactivePasteFromOS(env, target, osClipboard.content, pasteOption);
33210
+ interactivePasteFromOS(env, target, clipboardContent, pasteOption);
32947
33211
  }
32948
33212
  else {
32949
33213
  interactivePaste(env, target, pasteOption);
@@ -33957,6 +34221,7 @@ const pivotProperties = {
33957
34221
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
33958
34222
  return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
33959
34223
  },
34224
+ isReadonlyAllowed: true,
33960
34225
  icon: "o-spreadsheet-Icon.PIVOT",
33961
34226
  };
33962
34227
  const FIX_FORMULAS = {
@@ -35674,6 +35939,7 @@ topbarMenuRegistry
35674
35939
  id: `item_pivot_${env.model.getters.getPivotFormulaId(pivotId)}`,
35675
35940
  name: env.model.getters.getPivotDisplayName(pivotId),
35676
35941
  sequence: sequence + index,
35942
+ isReadonlyAllowed: true,
35677
35943
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
35678
35944
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
35679
35945
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
@@ -36163,7 +36429,7 @@ css /* scss */ `
36163
36429
  flex-grow: 0;
36164
36430
  }
36165
36431
 
36166
- /** Make the character a bit bigger
36432
+ /* Make the character a bit bigger
36167
36433
  compared to its neighbor INPUT box */
36168
36434
  .o-remove-selection {
36169
36435
  font-size: calc(100% + 4px);
@@ -36768,7 +37034,7 @@ const CONTAINER_WIDTH = CONTENT_WIDTH + 2 * PICKER_PADDING;
36768
37034
  css /* scss */ `
36769
37035
  .o-color-picker {
36770
37036
  padding: ${PICKER_PADDING}px 0;
36771
- /** FIXME: this is useless, overiden by the popover container */
37037
+ /* FIXME: this is useless, overiden by the popover container */
36772
37038
  box-shadow: 1px 2px 5px 2px rgba(51, 51, 51, 0.15);
36773
37039
  background-color: white;
36774
37040
  line-height: 1.2;
@@ -36911,7 +37177,7 @@ css /* scss */ `
36911
37177
  margin-right: 2px;
36912
37178
  }
36913
37179
  .o-wrong-color {
36914
- /** FIXME bootstrap class instead? */
37180
+ /* FIXME bootstrap class instead? */
36915
37181
  outline-color: red;
36916
37182
  border-color: red;
36917
37183
  &:focus {
@@ -38955,10 +39221,10 @@ css /* scss */ `
38955
39221
  }
38956
39222
 
38957
39223
  .fa-stack {
38958
- // reset stack size which is doubled by default
38959
- width: 1em;
38960
- height: 1em;
38961
- line-height: 1em;
39224
+ /* reset stack size which is doubled by default */
39225
+ width: ${CLOSE_ICON_RADIUS * 2}px;
39226
+ height: ${CLOSE_ICON_RADIUS * 2}px;
39227
+ line-height: ${CLOSE_ICON_RADIUS * 2}px;
38962
39228
  }
38963
39229
 
38964
39230
  .force-open-assistant {
@@ -38975,7 +39241,7 @@ css /* scss */ `
38975
39241
  margin: 1px 4px;
38976
39242
 
38977
39243
  .o-semi-bold {
38978
- /** FIXME: to remove in favor of Bootstrap
39244
+ /* FIXME: to remove in favor of Bootstrap
38979
39245
  * 'fw-semibold' when we upgrade to Bootstrap 5.2
38980
39246
  */
38981
39247
  font-weight: 600 !important;
@@ -40211,7 +40477,7 @@ css /* scss */ `
40211
40477
  .o-threshold-value {
40212
40478
  flex-grow: 1;
40213
40479
  flex-basis: 60%;
40214
- min-width: 0px; // input overflows in Firefox otherwise
40480
+ min-width: 0px; /* input overflows in Firefox otherwise */
40215
40481
  }
40216
40482
  .o-threshold-value input:disabled {
40217
40483
  background-color: #edebed;
@@ -41030,7 +41296,7 @@ dataValidationEvaluatorRegistry.add("dateIs", {
41030
41296
  name: _t("Date is"),
41031
41297
  getPreview: (criterion, getters) => {
41032
41298
  return criterion.dateValue === "exactDate"
41033
- ? _t("Date is %s", getDateCriterionFormattedValues(criterion, getters)[0])
41299
+ ? _t("Date is %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
41034
41300
  : _t("Date is %s", DVTerms.DateIs[criterion.dateValue]);
41035
41301
  },
41036
41302
  });
@@ -41055,7 +41321,7 @@ dataValidationEvaluatorRegistry.add("dateIsBefore", {
41055
41321
  name: _t("Date is before"),
41056
41322
  getPreview: (criterion, getters) => {
41057
41323
  return criterion.dateValue === "exactDate"
41058
- ? _t("Date is before %s", getDateCriterionFormattedValues(criterion, getters)[0])
41324
+ ? _t("Date is before %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
41059
41325
  : _t("Date is before %s", DVTerms.DateIsBefore[criterion.dateValue]);
41060
41326
  },
41061
41327
  });
@@ -41080,7 +41346,7 @@ dataValidationEvaluatorRegistry.add("dateIsOnOrBefore", {
41080
41346
  name: _t("Date is on or before"),
41081
41347
  getPreview: (criterion, getters) => {
41082
41348
  return criterion.dateValue === "exactDate"
41083
- ? _t("Date is on or before %s", getDateCriterionFormattedValues(criterion, getters)[0])
41349
+ ? _t("Date is on or before %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
41084
41350
  : _t("Date is on or before %s", DVTerms.DateIsBefore[criterion.dateValue]);
41085
41351
  },
41086
41352
  });
@@ -41105,7 +41371,7 @@ dataValidationEvaluatorRegistry.add("dateIsAfter", {
41105
41371
  name: _t("Date is after"),
41106
41372
  getPreview: (criterion, getters) => {
41107
41373
  return criterion.dateValue === "exactDate"
41108
- ? _t("Date is after %s", getDateCriterionFormattedValues(criterion, getters)[0])
41374
+ ? _t("Date is after %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
41109
41375
  : _t("Date is after %s", DVTerms.DateIsBefore[criterion.dateValue]);
41110
41376
  },
41111
41377
  });
@@ -41130,7 +41396,7 @@ dataValidationEvaluatorRegistry.add("dateIsOnOrAfter", {
41130
41396
  name: _t("Date is on or after"),
41131
41397
  getPreview: (criterion, getters) => {
41132
41398
  return criterion.dateValue === "exactDate"
41133
- ? _t("Date is on or after %s", getDateCriterionFormattedValues(criterion, getters)[0])
41399
+ ? _t("Date is on or after %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
41134
41400
  : _t("Date is on or after %s", DVTerms.DateIsBefore[criterion.dateValue]);
41135
41401
  },
41136
41402
  });
@@ -41156,7 +41422,7 @@ dataValidationEvaluatorRegistry.add("dateIsBetween", {
41156
41422
  numberOfValues: () => 2,
41157
41423
  name: _t("Date is between"),
41158
41424
  getPreview: (criterion, getters) => {
41159
- const values = getDateCriterionFormattedValues(criterion, getters);
41425
+ const values = getDateCriterionFormattedValues(criterion.values, getters.getLocale());
41160
41426
  return _t("Date is between %s and %s", values[0], values[1]);
41161
41427
  },
41162
41428
  });
@@ -41182,7 +41448,7 @@ dataValidationEvaluatorRegistry.add("dateIsNotBetween", {
41182
41448
  numberOfValues: () => 2,
41183
41449
  name: _t("Date is not between"),
41184
41450
  getPreview: (criterion, getters) => {
41185
- const values = getDateCriterionFormattedValues(criterion, getters);
41451
+ const values = getDateCriterionFormattedValues(criterion.values, getters.getLocale());
41186
41452
  return _t("Date is not between %s and %s", values[0], values[1]);
41187
41453
  },
41188
41454
  });
@@ -41465,19 +41731,6 @@ function checkValueIsNumber(value) {
41465
41731
  const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
41466
41732
  return valueAsNumber !== undefined;
41467
41733
  }
41468
- function getDateCriterionFormattedValues(criterion, getters) {
41469
- const locale = getters.getLocale();
41470
- return criterion.values.map((valueStr) => {
41471
- if (valueStr.startsWith("=")) {
41472
- return valueStr;
41473
- }
41474
- const value = parseLiteral(valueStr, locale);
41475
- if (typeof value === "number") {
41476
- return formatValue(value, { format: locale.dateFormat, locale });
41477
- }
41478
- return "";
41479
- });
41480
- }
41481
41734
 
41482
41735
  /** This component looks like a select input, but on click it opens a Menu with the items given as props instead of a dropdown */
41483
41736
  class SelectMenu extends owl.Component {
@@ -41559,6 +41812,7 @@ class DataValidationInput extends owl.Component {
41559
41812
  focused: false,
41560
41813
  onBlur: () => { },
41561
41814
  };
41815
+ static components = { StandaloneComposer: StandaloneComposer };
41562
41816
  inputRef = owl.useRef("input");
41563
41817
  setup() {
41564
41818
  owl.useEffect(() => {
@@ -41570,10 +41824,6 @@ class DataValidationInput extends owl.Component {
41570
41824
  state = owl.useState({
41571
41825
  shouldDisplayError: !!this.props.value, // Don't display error if user inputted nothing yet
41572
41826
  });
41573
- onValueChanged(ev) {
41574
- this.state.shouldDisplayError = true;
41575
- this.props.onValueChanged(ev.target.value);
41576
- }
41577
41827
  get placeholder() {
41578
41828
  const evaluator = dataValidationEvaluatorRegistry.get(this.props.criterionType);
41579
41829
  if (evaluator.allowedValues === "onlyFormulas") {
@@ -41584,6 +41834,27 @@ class DataValidationInput extends owl.Component {
41584
41834
  }
41585
41835
  return _t("Value or formula");
41586
41836
  }
41837
+ get allowedValues() {
41838
+ const evaluator = dataValidationEvaluatorRegistry.get(this.props.criterionType);
41839
+ return evaluator.allowedValues ?? "any";
41840
+ }
41841
+ onInputValueChanged(ev) {
41842
+ this.state.shouldDisplayError = true;
41843
+ this.props.onValueChanged(ev.target.value);
41844
+ }
41845
+ onChangeComposerValue(str) {
41846
+ this.state.shouldDisplayError = true;
41847
+ this.props.onValueChanged(str);
41848
+ }
41849
+ getDataValidationRuleInputComposerProps() {
41850
+ return {
41851
+ onConfirm: (str) => this.onChangeComposerValue(str),
41852
+ composerContent: this.props.value,
41853
+ placeholder: this.placeholder,
41854
+ class: "o-sidePanel-composer",
41855
+ defaultRangeSheetId: this.env.model.getters.getActiveSheetId(),
41856
+ };
41857
+ }
41587
41858
  get errorMessage() {
41588
41859
  if (!this.state.shouldDisplayError) {
41589
41860
  return undefined;
@@ -41919,13 +42190,13 @@ function getDataValidationCriterionMenuItems(callback) {
41919
42190
 
41920
42191
  class DataValidationEditor extends owl.Component {
41921
42192
  static template = "o-spreadsheet-DataValidationEditor";
41922
- static components = { SelectionInput, SelectMenu, Section };
42193
+ static components = { SelectionInput, SelectMenu, Section, ValidationMessages };
41923
42194
  static props = {
41924
42195
  rule: { type: Object, optional: true },
41925
42196
  onExit: Function,
41926
42197
  onCloseSidePanel: { type: Function, optional: true },
41927
42198
  };
41928
- state = owl.useState({ rule: this.defaultDataValidationRule });
42199
+ state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
41929
42200
  setup() {
41930
42201
  if (this.props.rule) {
41931
42202
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -41950,15 +42221,15 @@ class DataValidationEditor extends owl.Component {
41950
42221
  this.state.rule.isBlocking = isBlocking === "true";
41951
42222
  }
41952
42223
  onSave() {
41953
- if (!this.canSave) {
41954
- return;
42224
+ if (this.state.rule) {
42225
+ const result = this.env.model.dispatch("ADD_DATA_VALIDATION_RULE", this.dispatchPayload);
42226
+ if (!result.isSuccessful) {
42227
+ this.state.errors = result.reasons;
42228
+ }
42229
+ else {
42230
+ this.props.onExit();
42231
+ }
41955
42232
  }
41956
- this.env.model.dispatch("ADD_DATA_VALIDATION_RULE", this.dispatchPayload);
41957
- this.props.onExit();
41958
- }
41959
- get canSave() {
41960
- return this.env.model.canDispatch("ADD_DATA_VALIDATION_RULE", this.dispatchPayload)
41961
- .isSuccessful;
41962
42233
  }
41963
42234
  get dispatchPayload() {
41964
42235
  const rule = { ...this.state.rule, ranges: undefined };
@@ -41999,6 +42270,9 @@ class DataValidationEditor extends owl.Component {
41999
42270
  get criterionComponent() {
42000
42271
  return dataValidationPanelCriteriaRegistry.get(this.state.rule.criterion.type).component;
42001
42272
  }
42273
+ get errorMessages() {
42274
+ return this.state.errors.map((error) => DVTerms.Errors[error] || DVTerms.Errors.Unexpected);
42275
+ }
42002
42276
  }
42003
42277
 
42004
42278
  css /* scss */ `
@@ -42010,7 +42284,7 @@ css /* scss */ `
42010
42284
  border-bottom: 1px solid ${FIGURE_BORDER_COLOR};
42011
42285
 
42012
42286
  .o-dv-container {
42013
- min-width: 0; // otherwise flex won't shrink correctly
42287
+ min-width: 0; /* otherwise flex won't shrink correctly */
42014
42288
  }
42015
42289
 
42016
42290
  .o-dv-preview-description {
@@ -45028,6 +45302,9 @@ class PivotSidePanel extends owl.Component {
45028
45302
  PivotLayoutConfigurator,
45029
45303
  Section,
45030
45304
  };
45305
+ setup() {
45306
+ useHighlights(this);
45307
+ }
45031
45308
  get sidePanelEditor() {
45032
45309
  const pivot = this.env.model.getters.getPivotCoreDefinition(this.props.pivotId);
45033
45310
  if (!pivot) {
@@ -45035,6 +45312,9 @@ class PivotSidePanel extends owl.Component {
45035
45312
  }
45036
45313
  return pivotSidePanelRegistry.get(pivot.type).editor;
45037
45314
  }
45315
+ get highlights() {
45316
+ return getPivotHighlights(this.env.model.getters, this.props.pivotId);
45317
+ }
45038
45318
  }
45039
45319
 
45040
45320
  css /* scss */ `
@@ -45704,7 +45984,7 @@ class TableStylePreview extends owl.Component {
45704
45984
 
45705
45985
  css /* scss */ `
45706
45986
  .o-table-style-popover {
45707
- /** 7 tables preview + padding by line */
45987
+ /* 7 tables preview + padding by line */
45708
45988
  width: calc((66px + 4px * 2) * 7 + 1.5rem * 2);
45709
45989
  background: #fff;
45710
45990
  font-size: 14px;
@@ -47256,7 +47536,7 @@ css /* scss */ `
47256
47536
  box-sizing: border-box !important;
47257
47537
  accent-color: #808080;
47258
47538
  margin: ${MARGIN}px;
47259
- /** required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
47539
+ /* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
47260
47540
  position: absolute;
47261
47541
  }
47262
47542
  `;
@@ -47603,7 +47883,7 @@ css /*SCSS*/ `
47603
47883
  }
47604
47884
  }
47605
47885
  .o-figure-container {
47606
- -webkit-user-select: none; // safari
47886
+ -webkit-user-select: none; /* safari */
47607
47887
  user-select: none;
47608
47888
  }
47609
47889
  `;
@@ -50676,25 +50956,20 @@ class Grid extends owl.Component {
50676
50956
  if (!clipboardData) {
50677
50957
  return;
50678
50958
  }
50679
- const clipboardDataTextContent = clipboardData?.getData(ClipboardMIMEType.PlainText);
50680
- const clipboardDataHtmlContent = clipboardData?.getData(ClipboardMIMEType.Html);
50681
- const htmlDocument = new DOMParser().parseFromString(clipboardDataHtmlContent ?? "<div></div>", "text/html");
50682
- const osClipboardSpreadsheetContent = clipboardData.getData(ClipboardMIMEType.OSpreadsheet) || "{}";
50959
+ const osClipboard = {
50960
+ content: {
50961
+ [ClipboardMIMEType.PlainText]: clipboardData?.getData(ClipboardMIMEType.PlainText),
50962
+ [ClipboardMIMEType.Html]: clipboardData?.getData(ClipboardMIMEType.Html),
50963
+ },
50964
+ };
50683
50965
  const target = this.env.model.getters.getSelectedZones();
50684
50966
  const isCutOperation = this.env.model.getters.isCutOperation();
50685
- const clipboardId = JSON.parse(osClipboardSpreadsheetContent).clipboardId ??
50686
- htmlDocument.querySelector("div")?.getAttribute("data-clipboard-id");
50967
+ const clipboardContent = parseOSClipboardContent(osClipboard.content);
50968
+ const clipboardId = clipboardContent.data?.clipboardId;
50687
50969
  if (this.env.model.getters.getClipboardId() === clipboardId) {
50688
50970
  interactivePaste(this.env, target);
50689
50971
  }
50690
50972
  else {
50691
- const clipboardContent = {
50692
- [ClipboardMIMEType.PlainText]: clipboardDataTextContent,
50693
- [ClipboardMIMEType.Html]: clipboardDataHtmlContent,
50694
- };
50695
- if (osClipboardSpreadsheetContent !== "{}") {
50696
- clipboardContent[ClipboardMIMEType.OSpreadsheet] = osClipboardSpreadsheetContent;
50697
- }
50698
50973
  interactivePasteFromOS(this.env, target, clipboardContent);
50699
50974
  }
50700
50975
  if (isCutOperation) {
@@ -52514,8 +52789,9 @@ class ConditionalFormatPlugin extends CorePlugin {
52514
52789
  if (replaceIndex > -1) {
52515
52790
  currentRanges = rules[replaceIndex].ranges.map(toUnboundedZone);
52516
52791
  }
52517
- currentRanges = currentRanges.concat(toAdd);
52518
- return recomputeZones(currentRanges, toRemove).map((zone) => this.getters.getRangeDataFromZone(sheetId, zone));
52792
+ // Remove the zones first in case the same position is in toAdd and toRemove
52793
+ const withRemovedZones = recomputeZones(currentRanges, toRemove);
52794
+ return recomputeZones([...toAdd, ...withRemovedZones], []).map((zone) => this.getters.getRangeDataFromZone(sheetId, zone));
52519
52795
  }
52520
52796
  // ---------------------------------------------------------------------------
52521
52797
  // Private
@@ -52619,8 +52895,6 @@ class ConditionalFormatPlugin extends CorePlugin {
52619
52895
  case "IconSetRule": {
52620
52896
  return this.checkValidations(rule, this.chainValidations(this.checkInflectionPoints(this.checkNaN), this.checkLowerBiggerThanUpper), this.chainValidations(this.checkInflectionPoints(this.checkFormulaCompilation)));
52621
52897
  }
52622
- case "DataBarRule":
52623
- return this.checkDataBarRangeValues(rule, cmd.ranges, cmd.sheetId);
52624
52898
  }
52625
52899
  return "Success" /* CommandResult.Success */;
52626
52900
  }
@@ -52751,18 +53025,6 @@ class ConditionalFormatPlugin extends CorePlugin {
52751
53025
  }
52752
53026
  return "Success" /* CommandResult.Success */;
52753
53027
  }
52754
- checkDataBarRangeValues(rule, ranges, sheetId) {
52755
- if (rule.rangeValues) {
52756
- const { numberOfCols, numberOfRows } = zoneToDimension(this.getters.getRangeFromSheetXC(sheetId, rule.rangeValues).zone);
52757
- for (const range of ranges) {
52758
- const dimensions = zoneToDimension(this.getters.getRangeFromRangeData(range).zone);
52759
- if (numberOfCols !== dimensions.numberOfCols || numberOfRows !== dimensions.numberOfRows) {
52760
- return "DataBarRangeValuesMismatch" /* CommandResult.DataBarRangeValuesMismatch */;
52761
- }
52762
- }
52763
- }
52764
- return "Success" /* CommandResult.Success */;
52765
- }
52766
53028
  removeConditionalFormatting(id, sheet) {
52767
53029
  const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id);
52768
53030
  if (cfIndex !== -1) {
@@ -52826,7 +53088,7 @@ class DataValidationPlugin extends CorePlugin {
52826
53088
  allowDispatch(cmd) {
52827
53089
  switch (cmd.type) {
52828
53090
  case "ADD_DATA_VALIDATION_RULE":
52829
- return this.checkValidations(cmd, this.chainValidations(this.checkEmptyRange, this.checkCriterionTypeIsValid, this.checkCriterionHasValidNumberOfValues, this.checkCriterionValuesAreValid));
53091
+ return this.checkValidations(cmd, this.chainValidations(this.checkEmptyRange, this.checkValidRange, this.checkCriterionTypeIsValid, this.checkCriterionHasValidNumberOfValues, this.checkCriterionValuesAreValid));
52830
53092
  case "REMOVE_DATA_VALIDATION_RULE":
52831
53093
  if (!this.rules[cmd.sheetId].find((rule) => rule.id === cmd.id)) {
52832
53094
  return "UnknownDataValidationRule" /* CommandResult.UnknownDataValidationRule */;
@@ -52985,6 +53247,20 @@ class DataValidationPlugin extends CorePlugin {
52985
53247
  }
52986
53248
  }
52987
53249
  }
53250
+ exportForExcel(data) {
53251
+ if (!data.sheets) {
53252
+ return;
53253
+ }
53254
+ for (const sheet of data.sheets) {
53255
+ sheet.dataValidationRules = [];
53256
+ for (const rule of this.rules[sheet.id]) {
53257
+ sheet.dataValidationRules.push({
53258
+ ...rule,
53259
+ ranges: rule.ranges.map((range) => this.getters.getRangeString(range, sheet.id, { useFixedReference: true })),
53260
+ });
53261
+ }
53262
+ }
53263
+ }
52988
53264
  checkCriterionTypeIsValid(cmd) {
52989
53265
  return dataValidationEvaluatorRegistry.contains(cmd.rule.criterion.type)
52990
53266
  ? "Success" /* CommandResult.Success */
@@ -53003,21 +53279,28 @@ class DataValidationPlugin extends CorePlugin {
53003
53279
  checkCriterionValuesAreValid(cmd) {
53004
53280
  const criterion = cmd.rule.criterion;
53005
53281
  const evaluator = dataValidationEvaluatorRegistry.get(criterion.type);
53006
- if (criterion.values.some((value) => {
53007
- if (value.startsWith("=")) {
53008
- return evaluator.allowedValues === "onlyLiterals";
53009
- }
53010
- else if (evaluator.allowedValues === "onlyFormulas") {
53282
+ const isInvalid = (value) => {
53283
+ if (evaluator.allowedValues === "onlyFormulas" && !value.startsWith("=")) {
53011
53284
  return true;
53012
53285
  }
53013
- else {
53014
- return !evaluator.isCriterionValueValid(value);
53286
+ if (value.startsWith("=")) {
53287
+ return evaluator.allowedValues === "onlyLiterals" || compile(value).isBadExpression;
53015
53288
  }
53016
- })) {
53289
+ return !evaluator.isCriterionValueValid(value);
53290
+ };
53291
+ if (criterion.values.some(isInvalid)) {
53017
53292
  return "InvalidDataValidationCriterionValue" /* CommandResult.InvalidDataValidationCriterionValue */;
53018
53293
  }
53019
53294
  return "Success" /* CommandResult.Success */;
53020
53295
  }
53296
+ checkValidRange(cmd) {
53297
+ const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
53298
+ const stringRanges = ranges.map((range) => this.getters.getRangeString(range, cmd.sheetId));
53299
+ if (stringRanges.some((xc) => !this.getters.isRangeValid(xc))) {
53300
+ return "InvalidRange" /* CommandResult.InvalidRange */;
53301
+ }
53302
+ return "Success" /* CommandResult.Success */;
53303
+ }
53021
53304
  }
53022
53305
 
53023
53306
  class FigurePlugin extends CorePlugin {
@@ -54752,6 +55035,7 @@ class SheetPlugin extends CorePlugin {
54752
55035
  formats: {},
54753
55036
  borders: {},
54754
55037
  conditionalFormats: [],
55038
+ dataValidationRules: [],
54755
55039
  figures: [],
54756
55040
  tables: [],
54757
55041
  areGridLinesVisible: sheet.areGridLinesVisible === undefined ? true : sheet.areGridLinesVisible,
@@ -58121,6 +58405,9 @@ class Evaluator {
58121
58405
  }
58122
58406
  for (let i = 0; i < positions.length; ++i) {
58123
58407
  const position = positions[i];
58408
+ if (this.nextPositionsToUpdate.has(position)) {
58409
+ continue;
58410
+ }
58124
58411
  const evaluatedCell = this.computeCell(position);
58125
58412
  if (evaluatedCell !== EMPTY_CELL) {
58126
58413
  this.evaluatedCells.set(position, evaluatedCell);
@@ -58128,6 +58415,9 @@ class Evaluator {
58128
58415
  }
58129
58416
  onIterationEndEvaluationRegistry.getAll().forEach((callback) => callback(this.getters));
58130
58417
  }
58418
+ if (currentIteration >= MAX_ITERATION) {
58419
+ console.warn("Maximum iteration reached while evaluating cells");
58420
+ }
58131
58421
  }
58132
58422
  computeCell(position) {
58133
58423
  const evaluation = this.evaluatedCells.get(position);
@@ -58162,7 +58452,6 @@ class Evaluator {
58162
58452
  }
58163
58453
  finally {
58164
58454
  this.cellsBeingComputed.delete(cellId);
58165
- this.nextPositionsToUpdate.delete(position);
58166
58455
  }
58167
58456
  }
58168
58457
  computeAndSave(position) {
@@ -58198,6 +58487,7 @@ class Evaluator {
58198
58487
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
58199
58488
  // the result matrix is split in 2 zones to exclude the array formula position
58200
58489
  const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
58490
+ invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
58201
58491
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
58202
58492
  }
58203
58493
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
@@ -59139,8 +59429,12 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59139
59429
  const zoneOfValues = rangeValues.zone;
59140
59430
  for (let row = zone.top; row <= zone.bottom; row++) {
59141
59431
  for (let col = zone.left; col <= zone.right; col++) {
59142
- const cell = this.getEvaluatedCellInZone(sheetId, zone, col, row, zoneOfValues);
59143
- if (cell.type !== CellValueType.number || cell.value <= 0) {
59432
+ const targetCol = col - zone.left + zoneOfValues.left;
59433
+ const targetRow = row - zone.top + zoneOfValues.top;
59434
+ const cell = this.getters.getEvaluatedCell({ sheetId, col: targetCol, row: targetRow });
59435
+ if (!isInside(targetCol, targetRow, zoneOfValues) ||
59436
+ cell.type !== CellValueType.number ||
59437
+ cell.value <= 0) {
59144
59438
  // values negatives or 0 are ignored
59145
59439
  continue;
59146
59440
  }
@@ -59153,11 +59447,6 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59153
59447
  }
59154
59448
  }
59155
59449
  }
59156
- getEvaluatedCellInZone(sheetId, zone, col, row, targetZone) {
59157
- const targetCol = col - zone.left + targetZone.left;
59158
- const targetRow = row - zone.top + targetZone.top;
59159
- return this.getters.getEvaluatedCell({ sheetId, col: targetCol, row: targetRow });
59160
- }
59161
59450
  /** Compute the color scale for the given range and CF rule, and apply in in the given computedStyle object */
59162
59451
  applyColorScale(sheetId, range, rule, computedStyle) {
59163
59452
  const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
@@ -59321,25 +59610,11 @@ class EvaluationDataValidationPlugin extends UIPlugin {
59321
59610
  }
59322
59611
  switch (cmd.type) {
59323
59612
  case "ADD_DATA_VALIDATION_RULE":
59324
- const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
59325
- if (cmd.rule.criterion.type === "isBoolean") {
59326
- this.setContentToBooleanCells({ ...cmd.rule, ranges });
59327
- }
59328
- delete this.validationResults[cmd.sheetId];
59329
- break;
59330
59613
  case "REMOVE_DATA_VALIDATION_RULE":
59331
59614
  delete this.validationResults[cmd.sheetId];
59332
59615
  break;
59333
59616
  }
59334
59617
  }
59335
- setContentToBooleanCells(rule) {
59336
- for (const position of getCellPositionsInRanges(rule.ranges)) {
59337
- const evaluatedCell = this.getters.getEvaluatedCell(position);
59338
- if (evaluatedCell.type !== CellValueType.boolean) {
59339
- this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
59340
- }
59341
- }
59342
- }
59343
59618
  isDataValidationInvalid(cellPosition) {
59344
59619
  return !this.getValidationResultForCell(cellPosition).isValid;
59345
59620
  }
@@ -59355,9 +59630,10 @@ class EvaluationDataValidationPlugin extends UIPlugin {
59355
59630
  getDataValidationInvalidCriterionValueMessage(criterionType, value) {
59356
59631
  const evaluator = dataValidationEvaluatorRegistry.get(criterionType);
59357
59632
  if (value.startsWith("=")) {
59358
- return evaluator.allowedValues === "onlyLiterals"
59359
- ? _t("The value must not be a formula")
59360
- : undefined;
59633
+ if (evaluator.allowedValues === "onlyLiterals") {
59634
+ return _t("The value must not be a formula");
59635
+ }
59636
+ return this.isValidFormula(value) ? undefined : DVTerms.CriterionError.validFormula;
59361
59637
  }
59362
59638
  else if (evaluator.allowedValues === "onlyFormulas") {
59363
59639
  return _t("The value must be a formula");
@@ -59383,6 +59659,9 @@ class EvaluationDataValidationPlugin extends UIPlugin {
59383
59659
  const error = this.getRuleErrorForCellValue(cellValue, cellPosition, rule);
59384
59660
  return error ? { error, rule, isValid: false } : VALID_RESULT;
59385
59661
  }
59662
+ isValidFormula(value) {
59663
+ return !compile(value).isBadExpression;
59664
+ }
59386
59665
  getValidationResultForCell(cellPosition) {
59387
59666
  const { col, row, sheetId } = cellPosition;
59388
59667
  if (!this.validationResults[sheetId]) {
@@ -61818,7 +62097,7 @@ class Session extends EventBus {
61818
62097
  * Notify the server that the user client left the collaborative session
61819
62098
  */
61820
62099
  async leave(data) {
61821
- if (Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
62100
+ if (data && Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
61822
62101
  await this.snapshot(data());
61823
62102
  }
61824
62103
  delete this.clients[this.clientId];
@@ -62745,9 +63024,9 @@ class SortPlugin extends UIPlugin {
62745
63024
  switch (cmd.type) {
62746
63025
  case "SORT_CELLS":
62747
63026
  if (!isInside(cmd.col, cmd.row, cmd.zone)) {
62748
- throw new Error(_t("The anchor must be part of the provided zone"));
63027
+ return "InvalidSortAnchor" /* CommandResult.InvalidSortAnchor */;
62749
63028
  }
62750
- return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes);
63029
+ return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes, this.checkArrayFormulaInSortZone);
62751
63030
  }
62752
63031
  return "Success" /* CommandResult.Success */;
62753
63032
  }
@@ -62788,6 +63067,10 @@ class SortPlugin extends UIPlugin {
62788
63067
  }
62789
63068
  return "Success" /* CommandResult.Success */;
62790
63069
  }
63070
+ checkArrayFormulaInSortZone({ sheetId, zone }) {
63071
+ const arrayFormulaInZone = positions(zone).some(({ col, row }) => this.getters.getArrayFormulaSpreadingOn({ sheetId, col, row }));
63072
+ return arrayFormulaInZone ? "SortZoneWithArrayFormulas" /* CommandResult.SortZoneWithArrayFormulas */ : "Success" /* CommandResult.Success */;
63073
+ }
62791
63074
  /**
62792
63075
  * This function evaluates if the top row of a provided zone can be considered as a `header`
62793
63076
  * by checking the following criteria:
@@ -63370,6 +63653,42 @@ class CellComputedStylePlugin extends UIPlugin {
63370
63653
  }
63371
63654
  }
63372
63655
 
63656
+ class DataValidationInsertionPlugin extends UIPlugin {
63657
+ handle(cmd) {
63658
+ switch (cmd.type) {
63659
+ case "ADD_DATA_VALIDATION_RULE":
63660
+ if (cmd.rule.criterion.type === "isBoolean") {
63661
+ const ranges = cmd.ranges.map((range) => this.getters.getRangeFromRangeData(range));
63662
+ for (const position of getCellPositionsInRanges(ranges)) {
63663
+ const cell = this.getters.getCell(position);
63664
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
63665
+ if (!cell?.content) {
63666
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
63667
+ // In this case, a cell has been updated in the core plugin but
63668
+ // not yet evaluated. This can occur after a paste operation.
63669
+ }
63670
+ else if (cell?.content && evaluatedCell.type === CellValueType.empty) {
63671
+ let value;
63672
+ if (cell.content.startsWith("=")) {
63673
+ const result = this.getters.evaluateFormula(position.sheetId, cell.content);
63674
+ value = (isMatrix(result) ? result[0][0] : result)?.toString();
63675
+ }
63676
+ else {
63677
+ value = cell.content;
63678
+ }
63679
+ if (!value || !isBoolean(value)) {
63680
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
63681
+ }
63682
+ }
63683
+ else if (evaluatedCell.type !== CellValueType.boolean) {
63684
+ this.dispatch("UPDATE_CELL", { ...position, content: "FALSE" });
63685
+ }
63686
+ }
63687
+ }
63688
+ }
63689
+ }
63690
+ }
63691
+
63373
63692
  const genericRepeatsTransforms = [
63374
63693
  repeatSheetDependantCommand,
63375
63694
  repeatTargetDependantCommand,
@@ -64023,7 +64342,7 @@ class ClipboardPlugin extends UIPlugin {
64023
64342
  const zones = this.getters.getSelectedZones();
64024
64343
  return this.isCutAllowedOn(zones);
64025
64344
  case "PASTE_FROM_OS_CLIPBOARD": {
64026
- const copiedData = this.convertOSClipboardData(cmd.clipboardContent[ClipboardMIMEType.PlainText] ?? "");
64345
+ const copiedData = this.convertTextToClipboardData(cmd.clipboardContent.text ?? "");
64027
64346
  const pasteOption = cmd.pasteOption;
64028
64347
  return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
64029
64348
  }
@@ -64076,12 +64395,9 @@ class ClipboardPlugin extends UIPlugin {
64076
64395
  break;
64077
64396
  case "PASTE_FROM_OS_CLIPBOARD": {
64078
64397
  this._isCutOperation = false;
64079
- if (cmd.clipboardContent[ClipboardMIMEType.OSpreadsheet]) {
64080
- this.copiedData = JSON.parse(cmd.clipboardContent[ClipboardMIMEType.OSpreadsheet]);
64081
- }
64082
- else {
64083
- this.copiedData = this.convertOSClipboardData(cmd.clipboardContent[ClipboardMIMEType.PlainText] ?? "");
64084
- }
64398
+ this.copiedData =
64399
+ cmd.clipboardContent.data ||
64400
+ this.convertTextToClipboardData(cmd.clipboardContent.text ?? "");
64085
64401
  const pasteOption = cmd.pasteOption;
64086
64402
  this.paste(cmd.target, this.copiedData, {
64087
64403
  pasteOption,
@@ -64212,11 +64528,11 @@ class ClipboardPlugin extends UIPlugin {
64212
64528
  }
64213
64529
  }
64214
64530
  }
64215
- convertOSClipboardData(clipboardData) {
64531
+ convertTextToClipboardData(clipboardData) {
64216
64532
  const handlers = this.selectClipboardHandlers({ figureId: true }).concat(this.selectClipboardHandlers({}));
64217
64533
  let copiedData = {};
64218
64534
  for (const { handlerName, handler } of handlers) {
64219
- const data = handler.convertOSClipboardData(clipboardData);
64535
+ const data = handler.convertTextToClipboardData(clipboardData);
64220
64536
  copiedData[handlerName] = data;
64221
64537
  const minimalKeys = ["sheetId", "cells", "zones", "figureId"];
64222
64538
  for (const key of minimalKeys) {
@@ -64385,21 +64701,20 @@ class ClipboardPlugin extends UIPlugin {
64385
64701
  return {
64386
64702
  [ClipboardMIMEType.PlainText]: this.getPlainTextContent(),
64387
64703
  [ClipboardMIMEType.Html]: this.getHTMLContent(),
64388
- [ClipboardMIMEType.OSpreadsheet]: this.getSerializedGridData(),
64389
64704
  };
64390
64705
  }
64391
- getSerializedGridData() {
64706
+ getSheetData() {
64392
64707
  const data = {
64393
64708
  version: CURRENT_VERSION,
64394
64709
  clipboardId: this.clipboardId,
64395
64710
  };
64396
64711
  if (this.copiedData && "figureId" in this.copiedData) {
64397
- return JSON.stringify(data);
64712
+ return data;
64398
64713
  }
64399
- return JSON.stringify({
64714
+ return {
64400
64715
  ...data,
64401
64716
  ...this.copiedData,
64402
- });
64717
+ };
64403
64718
  }
64404
64719
  getPlainTextContent() {
64405
64720
  if (!this.copiedData?.cells) {
@@ -64416,31 +64731,36 @@ class ClipboardPlugin extends UIPlugin {
64416
64731
  .join("\n") || "\t");
64417
64732
  }
64418
64733
  getHTMLContent() {
64419
- if (!this.copiedData?.cells) {
64420
- return `<div data-clipboard-id="${this.clipboardId}">\t</div>`;
64734
+ let innerHTML = "";
64735
+ const cells = this.copiedData?.cells;
64736
+ if (!cells) {
64737
+ innerHTML = "\t";
64421
64738
  }
64422
- const cells = this.copiedData.cells;
64423
- if (cells.length === 1 && cells[0].length === 1) {
64424
- return `<div data-clipboard-id="${this.clipboardId}">${this.getters.getCellText(cells[0][0].position)}</div>`;
64739
+ else if (cells.length === 1 && cells[0].length === 1) {
64740
+ innerHTML = `${this.getters.getCellText(cells[0][0].position)}`;
64425
64741
  }
64426
- if (!cells[0][0]) {
64742
+ else if (!cells[0][0]) {
64427
64743
  return "";
64428
64744
  }
64429
- let htmlTable = `<div data-clipboard-id="${this.clipboardId}"><table border="1" style="border-collapse:collapse">`;
64430
- for (const row of cells) {
64431
- htmlTable += "<tr>";
64432
- for (const cell of row) {
64433
- if (!cell) {
64434
- continue;
64745
+ else {
64746
+ let htmlTable = `<table border="1" style="border-collapse:collapse">`;
64747
+ for (const row of cells) {
64748
+ htmlTable += "<tr>";
64749
+ for (const cell of row) {
64750
+ if (!cell) {
64751
+ continue;
64752
+ }
64753
+ const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
64754
+ const cellText = this.getters.getCellText(cell.position);
64755
+ htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
64435
64756
  }
64436
- const cssStyle = cssPropertiesToCss(cellStyleToCss(this.getters.getCellComputedStyle(cell.position)));
64437
- const cellText = this.getters.getCellText(cell.position);
64438
- htmlTable += `<td style="${cssStyle}">` + xmlEscape(cellText) + "</td>";
64757
+ htmlTable += "</tr>";
64439
64758
  }
64440
- htmlTable += "</tr>";
64759
+ htmlTable += "</table>";
64760
+ innerHTML = htmlTable;
64441
64761
  }
64442
- htmlTable += "</table></div>";
64443
- return htmlTable;
64762
+ const serializedData = JSON.stringify(this.getSheetData());
64763
+ return `<div data-osheet-clipboard='${xmlEscape(serializedData)}'>${innerHTML}</div>`;
64444
64764
  }
64445
64765
  isCutOperation() {
64446
64766
  return this._isCutOperation ?? false;
@@ -66420,7 +66740,8 @@ const featurePluginRegistry = new Registry()
66420
66740
  .add("history", HistoryPlugin)
66421
66741
  .add("data_cleanup", DataCleanupPlugin)
66422
66742
  .add("table_autofill", TableAutofillPlugin)
66423
- .add("table_ui_resize", TableResizeUI);
66743
+ .add("table_ui_resize", TableResizeUI)
66744
+ .add("datavalidation_insert", DataValidationInsertionPlugin);
66424
66745
  // Plugins which have a state, but which should not be shared in collaborative
66425
66746
  const statefulUIPluginRegistry = new Registry()
66426
66747
  .add("selection", GridSelectionPlugin)
@@ -67646,7 +67967,7 @@ css /* scss */ `
67646
67967
 
67647
67968
  .o-header-group-main-pane {
67648
67969
  &.o-group-rows {
67649
- margin-top: -2px; // Counteract o-header-group-frozen-pane-border offset
67970
+ margin-top: -2px; /* Counteract o-header-group-frozen-pane-border offset */
67650
67971
  }
67651
67972
  &.o-group-columns {
67652
67973
  margin-left: -2px;
@@ -68679,10 +69000,6 @@ class WebClipboardWrapper {
68679
69000
  [ClipboardMIMEType.PlainText]: this.getBlob(content, ClipboardMIMEType.PlainText),
68680
69001
  [ClipboardMIMEType.Html]: this.getBlob(content, ClipboardMIMEType.Html),
68681
69002
  };
68682
- const spreadsheetData = content[ClipboardMIMEType.OSpreadsheet];
68683
- if (spreadsheetData) {
68684
- clipboardItemData[ClipboardMIMEType.OSpreadsheet] = this.getBlob(content, ClipboardMIMEType.OSpreadsheet);
68685
- }
68686
69003
  return [new ClipboardItem(clipboardItemData)];
68687
69004
  }
68688
69005
  getBlob(clipboardContent, type) {
@@ -68724,7 +69041,7 @@ css /* scss */ `
68724
69041
  *:before,
68725
69042
  *:after {
68726
69043
  box-sizing: content-box;
68727
- /** rtl not supported ATM */
69044
+ /* rtl not supported ATM */
68728
69045
  direction: ltr;
68729
69046
  }
68730
69047
  .o-separator {
@@ -71629,6 +71946,124 @@ function getExcelThresholdType(type, position) {
71629
71946
  }
71630
71947
  }
71631
71948
 
71949
+ function addDataValidationRules(dataValidationRules) {
71950
+ const dvRulesCount = dataValidationRules.length;
71951
+ if (dvRulesCount === 0) {
71952
+ return [];
71953
+ }
71954
+ const dvNodes = [new XMLString(`<dataValidations count="${dvRulesCount}">`)];
71955
+ for (const dvRule of dataValidationRules) {
71956
+ switch (dvRule.criterion.type) {
71957
+ case "dateIs":
71958
+ case "dateIsBefore":
71959
+ case "dateIsOnOrBefore":
71960
+ case "dateIsAfter":
71961
+ case "dateIsOnOrAfter":
71962
+ case "dateIsBetween":
71963
+ case "dateIsNotBetween":
71964
+ dvNodes.push(addDateRule(dvRule));
71965
+ break;
71966
+ case "isEqual":
71967
+ case "isNotEqual":
71968
+ case "isGreaterThan":
71969
+ case "isGreaterOrEqualTo":
71970
+ case "isLessThan":
71971
+ case "isLessOrEqualTo":
71972
+ case "isBetween":
71973
+ case "isNotBetween":
71974
+ dvNodes.push(addDecimalRule(dvRule));
71975
+ break;
71976
+ case "isValueInRange":
71977
+ case "isValueInList":
71978
+ dvNodes.push(addListRule(dvRule));
71979
+ break;
71980
+ case "customFormula":
71981
+ dvNodes.push(addCustomFormulaRule(dvRule));
71982
+ break;
71983
+ default:
71984
+ console.warn(`Data validation ${dvRule.criterion.type} is not supported in xlsx.`);
71985
+ break;
71986
+ }
71987
+ }
71988
+ dvNodes.push(new XMLString("</dataValidations>"));
71989
+ return dvNodes;
71990
+ }
71991
+ function addDateRule(dvRule) {
71992
+ const rule = dvRule.criterion;
71993
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
71994
+ const formula2 = rule.values[1] ? adaptFormulaToExcel(rule.values[1]) : undefined;
71995
+ const operator = convertDateCriterionTypeToExcelOperator(dvRule.criterion.type);
71996
+ const attributes = commonDataValidationAttributes(dvRule);
71997
+ attributes.push(["type", "date"], ["operator", operator]);
71998
+ if (formula2) {
71999
+ return escapeXml /*xml*/ `
72000
+ <dataValidation ${formatAttributes(attributes)}>
72001
+ <formula1>${toNumber(formula1, DEFAULT_LOCALE)}</formula1>
72002
+ <formula2>${toNumber(formula2, DEFAULT_LOCALE)}</formula2>
72003
+ </dataValidation>
72004
+ `;
72005
+ }
72006
+ return escapeXml /*xml*/ `
72007
+ <dataValidation ${formatAttributes(attributes)}>
72008
+ <formula1>${toNumber(formula1, DEFAULT_LOCALE)}</formula1>
72009
+ </dataValidation>
72010
+ `;
72011
+ }
72012
+ function addDecimalRule(dvRule) {
72013
+ const rule = dvRule.criterion;
72014
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
72015
+ const formula2 = rule.values[1] ? adaptFormulaToExcel(rule.values[1]) : undefined;
72016
+ const operator = convertDecimalCriterionTypeToExcelOperator(dvRule.criterion.type);
72017
+ const attributes = commonDataValidationAttributes(dvRule);
72018
+ attributes.push(["type", "decimal"], ["operator", operator]);
72019
+ if (formula2) {
72020
+ return escapeXml /*xml*/ `
72021
+ <dataValidation ${formatAttributes(attributes)}>
72022
+ <formula1>${formula1}</formula1>
72023
+ <formula2>${formula2}</formula2>
72024
+ </dataValidation>
72025
+ `;
72026
+ }
72027
+ return escapeXml /*xml*/ `
72028
+ <dataValidation ${formatAttributes(attributes)}>
72029
+ <formula1>${formula1}</formula1>
72030
+ </dataValidation>
72031
+ `;
72032
+ }
72033
+ function addListRule(dvRule) {
72034
+ const rule = dvRule.criterion;
72035
+ const formula1 = dvRule.criterion.type === "isValueInRange"
72036
+ ? adaptFormulaToExcel(rule.values[0])
72037
+ : `"${rule.values.join(",")}"`;
72038
+ const attributes = commonDataValidationAttributes(dvRule);
72039
+ attributes.push(["type", "list"]);
72040
+ return escapeXml /*xml*/ `
72041
+ <dataValidation ${formatAttributes(attributes)}>
72042
+ <formula1>${formula1}</formula1>
72043
+ </dataValidation>
72044
+ `;
72045
+ }
72046
+ function addCustomFormulaRule(dvRule) {
72047
+ const rule = dvRule.criterion;
72048
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
72049
+ const attributes = commonDataValidationAttributes(dvRule);
72050
+ attributes.push(["type", "custom"]);
72051
+ return escapeXml /*xml*/ `
72052
+ <dataValidation ${formatAttributes(attributes)}>
72053
+ <formula1>${formula1}</formula1>
72054
+ </dataValidation>
72055
+ `;
72056
+ }
72057
+ function commonDataValidationAttributes(dvRule) {
72058
+ return [
72059
+ ["allowBlank", "1"],
72060
+ ["showInputMessage", "1"],
72061
+ ["showErrorMessage", "1"],
72062
+ ["errorStyle", !dvRule.isBlocking ? "warning" : ""],
72063
+ ["sqref", dvRule.ranges.join(" ")],
72064
+ ];
72065
+ }
72066
+
71632
72067
  function createDrawing(drawingRelIds, sheet, figures) {
71633
72068
  const namespaces = [
71634
72069
  ["xmlns:xdr", NAMESPACE.drawing],
@@ -72413,6 +72848,7 @@ function createWorksheets(data, construct) {
72413
72848
  ${addRows(construct, data, sheet)}
72414
72849
  ${addMerges(sheet.merges)}
72415
72850
  ${joinXmlNodes(addConditionalFormatting(construct.dxfs, sheet.conditionalFormats))}
72851
+ ${joinXmlNodes(addDataValidationRules(sheet.dataValidationRules))}
72416
72852
  ${addHyperlinks(construct, data, sheetIndex)}
72417
72853
  ${drawingNode}
72418
72854
  ${tablesNode}
@@ -72736,7 +73172,8 @@ class Model extends EventBus {
72736
73172
  this.session.join(this.config.client);
72737
73173
  }
72738
73174
  async leaveSession() {
72739
- await this.session.leave(lazy(() => this.exportData()));
73175
+ const snapshot = this.getters.isReadonly() ? undefined : lazy(() => this.exportData());
73176
+ await this.session.leave(snapshot);
72740
73177
  }
72741
73178
  setupUiPlugin(Plugin) {
72742
73179
  const plugin = new Plugin(this.uiPluginConfig);
@@ -73235,6 +73672,9 @@ const components = {
73235
73672
  GaugeChartDesignPanel,
73236
73673
  ScorecardChartConfigPanel,
73237
73674
  ScorecardChartDesignPanel,
73675
+ RadarChartDesignPanel,
73676
+ WaterfallChartDesignPanel,
73677
+ ComboChartDesignPanel,
73238
73678
  ChartTypePicker,
73239
73679
  FigureComponent,
73240
73680
  Menu,
@@ -73288,6 +73728,7 @@ const constants = {
73288
73728
  DEFAULT_LOCALE,
73289
73729
  HIGHLIGHT_COLOR,
73290
73730
  PIVOT_TABLE_CONFIG,
73731
+ ChartTerms,
73291
73732
  };
73292
73733
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73293
73734
 
@@ -73338,6 +73779,6 @@ exports.tokenColors = tokenColors;
73338
73779
  exports.tokenize = tokenize;
73339
73780
 
73340
73781
 
73341
- __info__.version = "18.1.0-alpha.4";
73342
- __info__.date = "2024-11-13T15:06:47.769Z";
73343
- __info__.hash = "e1ad985";
73782
+ __info__.version = "18.1.0-alpha.6";
73783
+ __info__.date = "2024-11-28T09:06:59.527Z";
73784
+ __info__.hash = "875c901";