@odoo/o-spreadsheet 18.0.10 → 18.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.0.10
6
- * @date 2025-01-15T08:05:47.616Z
7
- * @hash 94c45c7
5
+ * @version 18.0.12
6
+ * @date 2025-01-29T06:24:22.122Z
7
+ * @hash a881cff
8
8
  */
9
9
 
10
10
  'use strict';
@@ -4211,15 +4211,18 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4211
4211
  let currentIndex;
4212
4212
  let currentVal;
4213
4213
  let currentType;
4214
+ const getValue = sortOrder === "desc"
4215
+ ? (i) => normalizeValue(getValueInData(data, rangeLength - i - 1))
4216
+ : (i) => normalizeValue(getValueInData(data, i));
4214
4217
  while (indexRight - indexLeft >= 0) {
4215
4218
  indexMedian = Math.floor((indexLeft + indexRight) / 2);
4216
4219
  currentIndex = indexMedian;
4217
- currentVal = normalizeValue(getValueInData(data, currentIndex));
4220
+ currentVal = getValue(currentIndex);
4218
4221
  currentType = typeof currentVal;
4219
4222
  // 1 - linear search to find value with the same type
4220
4223
  while (indexLeft < currentIndex && targetType !== currentType) {
4221
4224
  currentIndex--;
4222
- currentVal = normalizeValue(getValueInData(data, currentIndex));
4225
+ currentVal = getValue(currentIndex);
4223
4226
  currentType = typeof currentVal;
4224
4227
  }
4225
4228
  if (currentType !== targetType || currentVal === undefined || currentVal === null) {
@@ -4235,8 +4238,7 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4235
4238
  if (matchVal === undefined ||
4236
4239
  matchVal === null ||
4237
4240
  matchVal < currentVal ||
4238
- (matchVal === currentVal && sortOrder === "asc" && matchValIndex < currentIndex) ||
4239
- (matchVal === currentVal && sortOrder === "desc" && matchValIndex > currentIndex)) {
4241
+ (matchVal === currentVal && matchValIndex < currentIndex)) {
4240
4242
  matchVal = currentVal;
4241
4243
  matchValIndex = currentIndex;
4242
4244
  }
@@ -4244,15 +4246,13 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4244
4246
  else if (mode === "nextGreater" && currentVal >= _target) {
4245
4247
  if (matchVal === undefined ||
4246
4248
  matchVal > currentVal ||
4247
- (matchVal === currentVal && sortOrder === "asc" && matchValIndex < currentIndex) ||
4248
- (matchVal === currentVal && sortOrder === "desc" && matchValIndex > currentIndex)) {
4249
+ (matchVal === currentVal && matchValIndex < currentIndex)) {
4249
4250
  matchVal = currentVal;
4250
4251
  matchValIndex = currentIndex;
4251
4252
  }
4252
4253
  }
4253
4254
  // 3 - give new indexes for the Binary search
4254
- if ((sortOrder === "asc" && currentVal > _target) ||
4255
- (sortOrder === "desc" && currentVal <= _target)) {
4255
+ if (currentVal > _target || (mode === "strict" && currentVal === _target)) {
4256
4256
  indexRight = currentIndex - 1;
4257
4257
  }
4258
4258
  else {
@@ -4260,7 +4260,10 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4260
4260
  }
4261
4261
  }
4262
4262
  // note that valMinIndex could be 0
4263
- return matchValIndex !== undefined ? matchValIndex : -1;
4263
+ if (matchValIndex === undefined) {
4264
+ return -1;
4265
+ }
4266
+ return sortOrder === "desc" ? rangeLength - matchValIndex - 1 : matchValIndex;
4264
4267
  }
4265
4268
  /**
4266
4269
  * Perform a linear search and return the index of the match.
@@ -4392,47 +4395,83 @@ function isDataNonEmpty(data) {
4392
4395
  return true;
4393
4396
  }
4394
4397
 
4395
- function toCriterionDateNumber(dateValue) {
4396
- const today = DateTime.now();
4397
- switch (dateValue) {
4398
- case "today":
4399
- return jsDateToNumber(today);
4400
- case "yesterday":
4401
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
4402
- case "tomorrow":
4403
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
4404
- case "lastWeek":
4405
- return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
4406
- case "lastMonth":
4407
- return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
4408
- case "lastYear":
4409
- return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
4410
- }
4398
+ /**
4399
+ * Add the `https` prefix to the url if it's missing
4400
+ */
4401
+ function withHttps(url) {
4402
+ return !/^https?:\/\//i.test(url) ? `https://${url}` : url;
4411
4403
  }
4412
- /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
4413
- function getDateNumberCriterionValues(criterion, locale) {
4414
- if ("dateValue" in criterion && criterion.dateValue !== "exactDate") {
4415
- return [toCriterionDateNumber(criterion.dateValue)];
4416
- }
4417
- return criterion.values.map((value) => valueToDateNumber(value, locale));
4404
+ const urlRegistry = new Registry();
4405
+ function createWebLink(url, label) {
4406
+ url = withHttps(url);
4407
+ return {
4408
+ url,
4409
+ label: label || url,
4410
+ isExternal: true,
4411
+ isUrlEditable: true,
4412
+ };
4418
4413
  }
4419
- /** Convert the criterion values to numbers. Return undefined values if they cannot be converted to numbers. */
4420
- function getCriterionValuesAsNumber(criterion, locale) {
4421
- return criterion.values.map((value) => tryToNumber(value, locale));
4414
+ urlRegistry.add("sheet_URL", {
4415
+ match: (url) => isSheetUrl(url),
4416
+ createLink: (url, label) => {
4417
+ return {
4418
+ label,
4419
+ url,
4420
+ isExternal: false,
4421
+ isUrlEditable: false,
4422
+ };
4423
+ },
4424
+ urlRepresentation(url, getters) {
4425
+ const sheetId = parseSheetUrl(url);
4426
+ return getters.tryGetSheetName(sheetId) || _t("Invalid sheet");
4427
+ },
4428
+ open(url, env) {
4429
+ const sheetId = parseSheetUrl(url);
4430
+ const result = env.model.dispatch("ACTIVATE_SHEET", {
4431
+ sheetIdFrom: env.model.getters.getActiveSheetId(),
4432
+ sheetIdTo: sheetId,
4433
+ });
4434
+ if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
4435
+ env.notifyUser({
4436
+ type: "warning",
4437
+ sticky: false,
4438
+ text: _t("Cannot open the link because the linked sheet is hidden."),
4439
+ });
4440
+ }
4441
+ },
4442
+ sequence: 0,
4443
+ });
4444
+ const WebUrlSpec = {
4445
+ createLink: createWebLink,
4446
+ match: (url) => isWebLink(url),
4447
+ open: (url) => window.open(url, "_blank"),
4448
+ urlRepresentation: (url) => url,
4449
+ sequence: 0,
4450
+ };
4451
+ function findMatchingSpec(url) {
4452
+ return (urlRegistry
4453
+ .getAll()
4454
+ .sort((a, b) => a.sequence - b.sequence)
4455
+ .find((urlType) => urlType.match(url)) || WebUrlSpec);
4422
4456
  }
4423
-
4424
- const MAX_DELAY = 140;
4425
- const MIN_DELAY = 20;
4426
- const ACCELERATION = 0.035;
4427
- /**
4428
- * Decreasing exponential function used to determine the "speed" of edge-scrolling
4429
- * as the timeout delay.
4430
- *
4431
- * Returns a timeout delay in milliseconds.
4432
- */
4433
- function scrollDelay(value) {
4434
- // decreasing exponential from MAX_DELAY to MIN_DELAY
4435
- return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
4457
+ function urlRepresentation(link, getters) {
4458
+ return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
4459
+ }
4460
+ function openLink(link, env) {
4461
+ findMatchingSpec(link.url).open(link.url, env);
4462
+ }
4463
+ function detectLink(value) {
4464
+ if (typeof value !== "string") {
4465
+ return undefined;
4466
+ }
4467
+ if (isMarkdownLink(value)) {
4468
+ const { label, url } = parseMarkdownLink(value);
4469
+ return findMatchingSpec(url).createLink(url, label);
4470
+ }
4471
+ else if (isWebLink(value)) {
4472
+ return createWebLink(value);
4473
+ }
4474
+ return undefined;
4436
4475
  }
4437
4476
 
4438
4477
  function tokenizeFormat(str) {
@@ -5494,6 +5533,193 @@ function isTextFormat(format) {
5494
5533
  }
5495
5534
  }
5496
5535
 
5536
+ function evaluateLiteral(literalCell, localeFormat) {
5537
+ const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5538
+ const functionResult = { value, format: localeFormat.format };
5539
+ return createEvaluatedCell(functionResult, localeFormat.locale);
5540
+ }
5541
+ function parseLiteral(content, locale) {
5542
+ if (content.startsWith("=")) {
5543
+ throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
5544
+ }
5545
+ if (content === "") {
5546
+ return null;
5547
+ }
5548
+ if (isNumber(content, DEFAULT_LOCALE)) {
5549
+ return parseNumber(content, DEFAULT_LOCALE);
5550
+ }
5551
+ const internalDate = parseDateTime(content, locale);
5552
+ if (internalDate) {
5553
+ return internalDate.value;
5554
+ }
5555
+ if (isBoolean(content)) {
5556
+ return content.toUpperCase() === "TRUE" ? true : false;
5557
+ }
5558
+ return content;
5559
+ }
5560
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
5561
+ const link = detectLink(functionResult.value);
5562
+ if (!link) {
5563
+ return _createEvaluatedCell(functionResult, locale, cell);
5564
+ }
5565
+ const value = parseLiteral(link.label, locale);
5566
+ const format = functionResult.format ||
5567
+ (typeof value === "number"
5568
+ ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
5569
+ : undefined);
5570
+ const linkPayload = {
5571
+ value,
5572
+ format,
5573
+ };
5574
+ return {
5575
+ ..._createEvaluatedCell(linkPayload, locale, cell),
5576
+ link,
5577
+ };
5578
+ }
5579
+ function _createEvaluatedCell(functionResult, locale, cell) {
5580
+ let { value, format, message } = functionResult;
5581
+ format = cell?.format || format;
5582
+ const formattedValue = formatValue(value, { format, locale });
5583
+ if (isEvaluationError(value)) {
5584
+ return errorCell(value, message);
5585
+ }
5586
+ if (isTextFormat(format)) {
5587
+ // TO DO:
5588
+ // with the next line, the value of the cell is transformed depending on the format.
5589
+ // This shouldn't happen, by doing this, the formulas handling numbers are not able
5590
+ // to interpret the value as a number.
5591
+ return textCell(toString(value), format, formattedValue);
5592
+ }
5593
+ if (value === null) {
5594
+ return emptyCell(format);
5595
+ }
5596
+ if (typeof value === "number") {
5597
+ if (isDateTimeFormat(format || "")) {
5598
+ return dateTimeCell(value, format, formattedValue);
5599
+ }
5600
+ return numberCell(value, format, formattedValue);
5601
+ }
5602
+ if (typeof value === "boolean") {
5603
+ return booleanCell(value, format, formattedValue);
5604
+ }
5605
+ return textCell(value, format, formattedValue);
5606
+ }
5607
+ function textCell(value, format, formattedValue) {
5608
+ return {
5609
+ value,
5610
+ format,
5611
+ formattedValue,
5612
+ type: CellValueType.text,
5613
+ isAutoSummable: true,
5614
+ defaultAlign: "left",
5615
+ };
5616
+ }
5617
+ function numberCell(value, format, formattedValue) {
5618
+ return {
5619
+ value: value || 0, // necessary to avoid "-0" and NaN values,
5620
+ format,
5621
+ formattedValue,
5622
+ type: CellValueType.number,
5623
+ isAutoSummable: true,
5624
+ defaultAlign: "right",
5625
+ };
5626
+ }
5627
+ const emptyCell = memoize(function emptyCell(format) {
5628
+ return {
5629
+ value: null,
5630
+ format,
5631
+ formattedValue: "",
5632
+ type: CellValueType.empty,
5633
+ isAutoSummable: true,
5634
+ defaultAlign: "left",
5635
+ };
5636
+ });
5637
+ function dateTimeCell(value, format, formattedValue) {
5638
+ return {
5639
+ value,
5640
+ format,
5641
+ formattedValue,
5642
+ type: CellValueType.number,
5643
+ isAutoSummable: false,
5644
+ defaultAlign: "right",
5645
+ };
5646
+ }
5647
+ function booleanCell(value, format, formattedValue) {
5648
+ return {
5649
+ value,
5650
+ format,
5651
+ formattedValue,
5652
+ type: CellValueType.boolean,
5653
+ isAutoSummable: false,
5654
+ defaultAlign: "center",
5655
+ };
5656
+ }
5657
+ function errorCell(value, message) {
5658
+ return {
5659
+ value,
5660
+ formattedValue: value,
5661
+ message,
5662
+ type: CellValueType.error,
5663
+ isAutoSummable: false,
5664
+ defaultAlign: "center",
5665
+ };
5666
+ }
5667
+
5668
+ function toCriterionDateNumber(dateValue) {
5669
+ const today = DateTime.now();
5670
+ switch (dateValue) {
5671
+ case "today":
5672
+ return jsDateToNumber(today);
5673
+ case "yesterday":
5674
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 1)));
5675
+ case "tomorrow":
5676
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() + 1)));
5677
+ case "lastWeek":
5678
+ return jsDateToNumber(DateTime.fromTimestamp(today.setDate(today.getDate() - 7)));
5679
+ case "lastMonth":
5680
+ return jsDateToNumber(DateTime.fromTimestamp(today.setMonth(today.getMonth() - 1)));
5681
+ case "lastYear":
5682
+ return jsDateToNumber(DateTime.fromTimestamp(today.setFullYear(today.getFullYear() - 1)));
5683
+ }
5684
+ }
5685
+ /** Get all the dates values of a criterion converted to numbers, converting date values such as "today" to actual dates */
5686
+ function getDateNumberCriterionValues(criterion, locale) {
5687
+ if ("dateValue" in criterion && criterion.dateValue !== "exactDate") {
5688
+ return [toCriterionDateNumber(criterion.dateValue)];
5689
+ }
5690
+ return criterion.values.map((value) => valueToDateNumber(value, locale));
5691
+ }
5692
+ /** Convert the criterion values to numbers. Return undefined values if they cannot be converted to numbers. */
5693
+ function getCriterionValuesAsNumber(criterion, locale) {
5694
+ return criterion.values.map((value) => tryToNumber(value, locale));
5695
+ }
5696
+ function getDateCriterionFormattedValues(values, locale) {
5697
+ return values.map((valueStr) => {
5698
+ if (valueStr.startsWith("=")) {
5699
+ return valueStr;
5700
+ }
5701
+ const value = parseLiteral(valueStr, locale);
5702
+ if (typeof value === "number") {
5703
+ return formatValue(value, { format: locale.dateFormat, locale });
5704
+ }
5705
+ return "";
5706
+ });
5707
+ }
5708
+
5709
+ const MAX_DELAY = 140;
5710
+ const MIN_DELAY = 20;
5711
+ const ACCELERATION = 0.035;
5712
+ /**
5713
+ * Decreasing exponential function used to determine the "speed" of edge-scrolling
5714
+ * as the timeout delay.
5715
+ *
5716
+ * Returns a timeout delay in milliseconds.
5717
+ */
5718
+ function scrollDelay(value) {
5719
+ // decreasing exponential from MAX_DELAY to MIN_DELAY
5720
+ return MIN_DELAY + (MAX_DELAY - MIN_DELAY) * Math.exp(-ACCELERATION * (value - 1));
5721
+ }
5722
+
5497
5723
  class RangeImpl {
5498
5724
  getSheetSize;
5499
5725
  _zone;
@@ -9231,13 +9457,13 @@ function toExcelDataset(getters, ds) {
9231
9457
  else if (ds.labelCell) {
9232
9458
  label = {
9233
9459
  reference: getters.getRangeString(ds.labelCell, "forceSheetReference", {
9234
- useFixedReference: true,
9460
+ useBoundedReference: true,
9235
9461
  }),
9236
9462
  };
9237
9463
  }
9238
9464
  return {
9239
9465
  label,
9240
- range: getters.getRangeString(dataRange, "forceSheetReference", { useFixedReference: true }),
9466
+ range: getters.getRangeString(dataRange, "forceSheetReference", { useBoundedReference: true }),
9241
9467
  backgroundColor: ds.backgroundColor,
9242
9468
  rightYAxis: ds.rightYAxis,
9243
9469
  };
@@ -9252,7 +9478,7 @@ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
9252
9478
  zone.top = zone.top + 1;
9253
9479
  }
9254
9480
  const range = labelRange.clone({ zone });
9255
- return getters.getRangeString(range, "forceSheetReference", { useFixedReference: true });
9481
+ return getters.getRangeString(range, "forceSheetReference", { useBoundedReference: true });
9256
9482
  }
9257
9483
  /**
9258
9484
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
@@ -9416,11 +9642,7 @@ function interpolateData(config, values, labels, newLabels) {
9416
9642
  if (values.length < 2 || labels.length < 2 || newLabels.length === 0) {
9417
9643
  return [];
9418
9644
  }
9419
- const labelMin = Math.min(...labels);
9420
- const labelMax = Math.max(...labels);
9421
- const labelRange = labelMax - labelMin;
9422
- const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
9423
- const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
9645
+ const { normalizedLabels, normalizedNewLabels } = normalizeLabels(labels, newLabels, config);
9424
9646
  try {
9425
9647
  switch (config.type) {
9426
9648
  case "polynomial": {
@@ -9459,6 +9681,30 @@ function interpolateData(config, values, labels, newLabels) {
9459
9681
  return Array.from({ length: newLabels.length }, () => NaN);
9460
9682
  }
9461
9683
  }
9684
+ function normalizeLabels(labels, newLabels, config) {
9685
+ let normalizedLabels = [];
9686
+ let normalizedNewLabels = [];
9687
+ if (config.type === "logarithmic") {
9688
+ // Logarithmic trends in charts are used to visualize proportional growth or
9689
+ // relative changes. Therefore, we change the normalization technique for
9690
+ // logarithmic trend lines for a better fit. The method used here is Max Absolute
9691
+ // Scaling. This Technique is ideal for data spanning several orders of magnitude,
9692
+ // as it balances differences between small and large values by compressing larger
9693
+ // values while preserving proportionality and ensuring all values are scaled relative
9694
+ // to the largest magnitude.
9695
+ const labelMax = Math.max(...labels.map(Math.abs));
9696
+ normalizedLabels = labels.map((l) => l / labelMax);
9697
+ normalizedNewLabels = newLabels.map((l) => l / labelMax);
9698
+ }
9699
+ else {
9700
+ const labelMax = Math.max(...labels);
9701
+ const labelMin = Math.min(...labels);
9702
+ const labelRange = labelMax - labelMin;
9703
+ normalizedLabels = labels.map((l) => (l - labelMax) / labelRange);
9704
+ normalizedNewLabels = newLabels.map((l) => (l - labelMax) / labelRange);
9705
+ }
9706
+ return { normalizedLabels, normalizedNewLabels };
9707
+ }
9462
9708
  function formatTickValue(localeFormat) {
9463
9709
  return (value) => {
9464
9710
  value = Number(value);
@@ -22095,217 +22341,6 @@ autoCompleteProviders.add("sheet_names", {
22095
22341
  },
22096
22342
  });
22097
22343
 
22098
- /**
22099
- * Add the `https` prefix to the url if it's missing
22100
- */
22101
- function withHttps(url) {
22102
- return !/^https?:\/\//i.test(url) ? `https://${url}` : url;
22103
- }
22104
- const urlRegistry = new Registry();
22105
- function createWebLink(url, label) {
22106
- url = withHttps(url);
22107
- return {
22108
- url,
22109
- label: label || url,
22110
- isExternal: true,
22111
- isUrlEditable: true,
22112
- };
22113
- }
22114
- urlRegistry.add("sheet_URL", {
22115
- match: (url) => isSheetUrl(url),
22116
- createLink: (url, label) => {
22117
- return {
22118
- label,
22119
- url,
22120
- isExternal: false,
22121
- isUrlEditable: false,
22122
- };
22123
- },
22124
- urlRepresentation(url, getters) {
22125
- const sheetId = parseSheetUrl(url);
22126
- return getters.tryGetSheetName(sheetId) || _t("Invalid sheet");
22127
- },
22128
- open(url, env) {
22129
- const sheetId = parseSheetUrl(url);
22130
- const result = env.model.dispatch("ACTIVATE_SHEET", {
22131
- sheetIdFrom: env.model.getters.getActiveSheetId(),
22132
- sheetIdTo: sheetId,
22133
- });
22134
- if (result.isCancelledBecause("SheetIsHidden" /* CommandResult.SheetIsHidden */)) {
22135
- env.notifyUser({
22136
- type: "warning",
22137
- sticky: false,
22138
- text: _t("Cannot open the link because the linked sheet is hidden."),
22139
- });
22140
- }
22141
- },
22142
- sequence: 0,
22143
- });
22144
- const WebUrlSpec = {
22145
- createLink: createWebLink,
22146
- match: (url) => isWebLink(url),
22147
- open: (url) => window.open(url, "_blank"),
22148
- urlRepresentation: (url) => url,
22149
- sequence: 0,
22150
- };
22151
- function findMatchingSpec(url) {
22152
- return (urlRegistry
22153
- .getAll()
22154
- .sort((a, b) => a.sequence - b.sequence)
22155
- .find((urlType) => urlType.match(url)) || WebUrlSpec);
22156
- }
22157
- function urlRepresentation(link, getters) {
22158
- return findMatchingSpec(link.url).urlRepresentation(link.url, getters);
22159
- }
22160
- function openLink(link, env) {
22161
- findMatchingSpec(link.url).open(link.url, env);
22162
- }
22163
- function detectLink(value) {
22164
- if (typeof value !== "string") {
22165
- return undefined;
22166
- }
22167
- if (isMarkdownLink(value)) {
22168
- const { label, url } = parseMarkdownLink(value);
22169
- return findMatchingSpec(url).createLink(url, label);
22170
- }
22171
- else if (isWebLink(value)) {
22172
- return createWebLink(value);
22173
- }
22174
- return undefined;
22175
- }
22176
-
22177
- function evaluateLiteral(literalCell, localeFormat) {
22178
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
22179
- const functionResult = { value, format: localeFormat.format };
22180
- return createEvaluatedCell(functionResult, localeFormat.locale);
22181
- }
22182
- function parseLiteral(content, locale) {
22183
- if (content.startsWith("=")) {
22184
- throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
22185
- }
22186
- if (content === "") {
22187
- return null;
22188
- }
22189
- if (isNumber(content, DEFAULT_LOCALE)) {
22190
- return parseNumber(content, DEFAULT_LOCALE);
22191
- }
22192
- const internalDate = parseDateTime(content, locale);
22193
- if (internalDate) {
22194
- return internalDate.value;
22195
- }
22196
- if (isBoolean(content)) {
22197
- return content.toUpperCase() === "TRUE" ? true : false;
22198
- }
22199
- return content;
22200
- }
22201
- function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
22202
- const link = detectLink(functionResult.value);
22203
- if (!link) {
22204
- return _createEvaluatedCell(functionResult, locale, cell);
22205
- }
22206
- const value = parseLiteral(link.label, locale);
22207
- const format = functionResult.format ||
22208
- (typeof value === "number"
22209
- ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
22210
- : undefined);
22211
- const linkPayload = {
22212
- value,
22213
- format,
22214
- };
22215
- return {
22216
- ..._createEvaluatedCell(linkPayload, locale, cell),
22217
- link,
22218
- };
22219
- }
22220
- function _createEvaluatedCell(functionResult, locale, cell) {
22221
- let { value, format, message } = functionResult;
22222
- format = cell?.format || format;
22223
- const formattedValue = formatValue(value, { format, locale });
22224
- if (isEvaluationError(value)) {
22225
- return errorCell(value, message);
22226
- }
22227
- if (isTextFormat(format)) {
22228
- // TO DO:
22229
- // with the next line, the value of the cell is transformed depending on the format.
22230
- // This shouldn't happen, by doing this, the formulas handling numbers are not able
22231
- // to interpret the value as a number.
22232
- return textCell(toString(value), format, formattedValue);
22233
- }
22234
- if (value === null) {
22235
- return emptyCell(format);
22236
- }
22237
- if (typeof value === "number") {
22238
- if (isDateTimeFormat(format || "")) {
22239
- return dateTimeCell(value, format, formattedValue);
22240
- }
22241
- return numberCell(value, format, formattedValue);
22242
- }
22243
- if (typeof value === "boolean") {
22244
- return booleanCell(value, format, formattedValue);
22245
- }
22246
- return textCell(value, format, formattedValue);
22247
- }
22248
- function textCell(value, format, formattedValue) {
22249
- return {
22250
- value,
22251
- format,
22252
- formattedValue,
22253
- type: CellValueType.text,
22254
- isAutoSummable: true,
22255
- defaultAlign: "left",
22256
- };
22257
- }
22258
- function numberCell(value, format, formattedValue) {
22259
- return {
22260
- value: value || 0, // necessary to avoid "-0" and NaN values,
22261
- format,
22262
- formattedValue,
22263
- type: CellValueType.number,
22264
- isAutoSummable: true,
22265
- defaultAlign: "right",
22266
- };
22267
- }
22268
- const emptyCell = memoize(function emptyCell(format) {
22269
- return {
22270
- value: null,
22271
- format,
22272
- formattedValue: "",
22273
- type: CellValueType.empty,
22274
- isAutoSummable: true,
22275
- defaultAlign: "left",
22276
- };
22277
- });
22278
- function dateTimeCell(value, format, formattedValue) {
22279
- return {
22280
- value,
22281
- format,
22282
- formattedValue,
22283
- type: CellValueType.number,
22284
- isAutoSummable: false,
22285
- defaultAlign: "right",
22286
- };
22287
- }
22288
- function booleanCell(value, format, formattedValue) {
22289
- return {
22290
- value,
22291
- format,
22292
- formattedValue,
22293
- type: CellValueType.boolean,
22294
- isAutoSummable: false,
22295
- defaultAlign: "center",
22296
- };
22297
- }
22298
- function errorCell(value, message) {
22299
- return {
22300
- value,
22301
- formattedValue: value,
22302
- message,
22303
- type: CellValueType.error,
22304
- isAutoSummable: false,
22305
- defaultAlign: "center",
22306
- };
22307
- }
22308
-
22309
22344
  /**
22310
22345
  * An AutofillModifierImplementation is used to describe how to handle a
22311
22346
  * AutofillModifier.
@@ -23451,6 +23486,10 @@ var WarningTypes;
23451
23486
  WarningTypes["CfIconSetEmptyIconNotSupported"] = "IconSets with empty icons";
23452
23487
  WarningTypes["BadlyFormattedHyperlink"] = "Badly formatted hyperlink";
23453
23488
  WarningTypes["NumFmtIdNotSupported"] = "Number format";
23489
+ WarningTypes["TimeDataValidationNotSupported"] = "Time data validation rules";
23490
+ WarningTypes["TextLengthDataValidationNotSupported"] = "Text length data validation rules";
23491
+ WarningTypes["WholeNumberDataValidationNotSupported"] = "Whole number data validation rules";
23492
+ WarningTypes["NotEqualDateDataValidationNotSupported"] = "Not equal date data validation rules";
23454
23493
  })(WarningTypes || (WarningTypes = {}));
23455
23494
  class XLSXImportWarningManager {
23456
23495
  _parsingWarnings = new Set();
@@ -23829,6 +23868,25 @@ const IMAGE_EXTENSION_TO_MIMETYPE_MAPPING = {
23829
23868
  webp: "image/webp",
23830
23869
  jpg: "image/jpeg",
23831
23870
  };
23871
+ const XLSX_DV_DECIMAL_OPERATOR_MAPPING = {
23872
+ between: "isBetween",
23873
+ notBetween: "isNotBetween",
23874
+ equal: "isEqual",
23875
+ notEqual: "isNotEqual",
23876
+ greaterThan: "isGreaterThan",
23877
+ greaterThanOrEqual: "isGreaterOrEqualTo",
23878
+ lessThan: "isLessThan",
23879
+ lessThanOrEqual: "isLessOrEqualTo",
23880
+ };
23881
+ const XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING = {
23882
+ between: "dateIsBetween",
23883
+ notBetween: "dateIsNotBetween",
23884
+ equal: "dateIs",
23885
+ greaterThan: "dateIsAfter",
23886
+ greaterThanOrEqual: "dateIsOnOrAfter",
23887
+ lessThan: "dateIsBefore",
23888
+ lessThanOrEqual: "dateIsOnOrBefore",
23889
+ };
23832
23890
 
23833
23891
  /**
23834
23892
  * Most of the functions could stay private, but are exported for testing purposes
@@ -24598,6 +24656,20 @@ function getRowPosition(rowIndex, sheetData) {
24598
24656
  }
24599
24657
  return position / HEIGHT_FACTOR;
24600
24658
  }
24659
+ /**
24660
+ * Convert the o-spreadsheet data validation decimal
24661
+ * criterion type to the corresponding excel operator.
24662
+ */
24663
+ function convertDecimalCriterionTypeToExcelOperator(operator) {
24664
+ return Object.keys(XLSX_DV_DECIMAL_OPERATOR_MAPPING).find((key) => XLSX_DV_DECIMAL_OPERATOR_MAPPING[key] === operator);
24665
+ }
24666
+ /**
24667
+ * Convert the o-spreadsheet data validation date
24668
+ * criterion type to the corresponding excel operator.
24669
+ */
24670
+ function convertDateCriterionTypeToExcelOperator(operator) {
24671
+ return Object.keys(XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING).find((key) => XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[key] === operator);
24672
+ }
24601
24673
 
24602
24674
  function convertFigures(sheetData) {
24603
24675
  let id = 1;
@@ -24707,6 +24779,112 @@ function getPositionFromAnchor(anchor, sheetData) {
24707
24779
  };
24708
24780
  }
24709
24781
 
24782
+ function convertDataValidationRules(xlsxDataValidations, warningManager) {
24783
+ const dvRules = [];
24784
+ let dvId = 1;
24785
+ for (const dv of xlsxDataValidations) {
24786
+ if (!dv) {
24787
+ continue;
24788
+ }
24789
+ switch (dv.type) {
24790
+ case "time":
24791
+ warningManager.generateNotSupportedWarning(WarningTypes.TimeDataValidationNotSupported);
24792
+ break;
24793
+ case "textLength":
24794
+ warningManager.generateNotSupportedWarning(WarningTypes.TextLengthDataValidationNotSupported);
24795
+ break;
24796
+ case "whole":
24797
+ warningManager.generateNotSupportedWarning(WarningTypes.WholeNumberDataValidationNotSupported);
24798
+ break;
24799
+ case "decimal":
24800
+ const decimalRule = convertDecimalRule(dvId++, dv);
24801
+ dvRules.push(decimalRule);
24802
+ break;
24803
+ case "list":
24804
+ const listRule = convertListrule(dvId++, dv);
24805
+ dvRules.push(listRule);
24806
+ break;
24807
+ case "date":
24808
+ if (dv.operator === "notEqual") {
24809
+ warningManager.generateNotSupportedWarning(WarningTypes.NotEqualDateDataValidationNotSupported);
24810
+ break;
24811
+ }
24812
+ const dateRule = convertDateRule(dvId++, dv);
24813
+ dvRules.push(dateRule);
24814
+ break;
24815
+ case "custom":
24816
+ const customRule = convertCustomRule(dvId++, dv);
24817
+ dvRules.push(customRule);
24818
+ break;
24819
+ }
24820
+ }
24821
+ return dvRules;
24822
+ }
24823
+ function convertDecimalRule(id, dv) {
24824
+ const values = [dv.formula1.toString()];
24825
+ if (dv.formula2) {
24826
+ values.push(dv.formula2.toString());
24827
+ }
24828
+ return {
24829
+ id: id.toString(),
24830
+ ranges: dv.sqref,
24831
+ isBlocking: dv.errorStyle !== "warning",
24832
+ criterion: {
24833
+ type: XLSX_DV_DECIMAL_OPERATOR_MAPPING[dv.operator],
24834
+ values,
24835
+ },
24836
+ };
24837
+ }
24838
+ function convertListrule(id, dv) {
24839
+ const formula1 = dv.formula1.toString();
24840
+ const isRangeRule = rangeReference.test(formula1);
24841
+ return {
24842
+ id: id.toString(),
24843
+ ranges: dv.sqref,
24844
+ isBlocking: dv.errorStyle !== "warning",
24845
+ criterion: {
24846
+ type: isRangeRule ? "isValueInRange" : "isValueInList",
24847
+ values: isRangeRule ? [formula1] : formula1.replaceAll('"', "").split(","),
24848
+ displayStyle: "arrow",
24849
+ },
24850
+ };
24851
+ }
24852
+ function convertDateRule(id, dv) {
24853
+ let criterion;
24854
+ const values = [dv.formula1.toString()];
24855
+ if (dv.formula2) {
24856
+ values.push(dv.formula2.toString());
24857
+ criterion = {
24858
+ type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
24859
+ values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
24860
+ };
24861
+ }
24862
+ else {
24863
+ criterion = {
24864
+ type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
24865
+ values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
24866
+ dateValue: "exactDate",
24867
+ };
24868
+ }
24869
+ return {
24870
+ id: id.toString(),
24871
+ ranges: dv.sqref,
24872
+ isBlocking: dv.errorStyle !== "warning",
24873
+ criterion: criterion,
24874
+ };
24875
+ }
24876
+ function convertCustomRule(id, dv) {
24877
+ return {
24878
+ id: id.toString(),
24879
+ ranges: dv.sqref,
24880
+ isBlocking: dv.errorStyle !== "warning",
24881
+ criterion: {
24882
+ type: "customFormula",
24883
+ values: [`=${dv.formula1.toString()}`],
24884
+ },
24885
+ };
24886
+ }
24887
+
24710
24888
  /**
24711
24889
  * Match external reference (ex. '[1]Sheet 3'!$B$4)
24712
24890
  *
@@ -24827,6 +25005,7 @@ function convertSheets(data, warningManager) {
24827
25005
  cols: convertCols(sheet, sheetDims[0], colHeaderGroups),
24828
25006
  rows: convertRows(sheet, sheetDims[1], rowHeaderGroups),
24829
25007
  conditionalFormats: convertConditionalFormats(sheet.cfs, data.dxfs, warningManager),
25008
+ dataValidationRules: convertDataValidationRules(sheet.dataValidations, warningManager),
24830
25009
  figures: convertFigures(sheet),
24831
25010
  isVisible: sheet.isVisible,
24832
25011
  panes: sheetOptions
@@ -26107,6 +26286,41 @@ class XlsxCfExtractor extends XlsxBaseExtractor {
26107
26286
  }
26108
26287
  }
26109
26288
 
26289
+ class XlsxDataValidationExtractor extends XlsxBaseExtractor {
26290
+ theme;
26291
+ constructor(sheetFile, xlsxStructure, warningManager, theme) {
26292
+ super(sheetFile, xlsxStructure, warningManager);
26293
+ this.theme = theme;
26294
+ }
26295
+ extractDataValidations() {
26296
+ const dataValidations = this.mapOnElements({ parent: this.rootFile.file.xml, query: "worksheet > dataValidations > dataValidation" }, (dvElement) => {
26297
+ return {
26298
+ type: this.extractAttr(dvElement, "type", { required: true }).asString(),
26299
+ operator: this.extractAttr(dvElement, "operator", {
26300
+ default: "between",
26301
+ })?.asString(),
26302
+ sqref: this.extractAttr(dvElement, "sqref", { required: true }).asString().split(" "),
26303
+ errorStyle: this.extractAttr(dvElement, "errorStyle")?.asString(),
26304
+ formula1: this.extractDataValidationFormula(dvElement, 1)[0],
26305
+ formula2: this.extractDataValidationFormula(dvElement, 2)[0],
26306
+ showErrorMessage: this.extractAttr(dvElement, "showErrorMessage")?.asBool(),
26307
+ errorTitle: this.extractAttr(dvElement, "errorTitle")?.asString(),
26308
+ error: this.extractAttr(dvElement, "error")?.asString(),
26309
+ showInputMessage: this.extractAttr(dvElement, "showInputMessage")?.asBool(),
26310
+ promptTitle: this.extractAttr(dvElement, "promptTitle")?.asString(),
26311
+ prompt: this.extractAttr(dvElement, "prompt")?.asString(),
26312
+ allowBlank: this.extractAttr(dvElement, "allowBlank")?.asBool(),
26313
+ };
26314
+ });
26315
+ return dataValidations;
26316
+ }
26317
+ extractDataValidationFormula(dvElement, index) {
26318
+ return this.mapOnElements({ parent: dvElement, query: `formula${index}` }, (cfFormulaElements) => {
26319
+ return this.extractTextContent(cfFormulaElements, { required: true });
26320
+ });
26321
+ }
26322
+ }
26323
+
26110
26324
  class XlsxChartExtractor extends XlsxBaseExtractor {
26111
26325
  extractChart() {
26112
26326
  return this.mapOnElements({ parent: this.rootFile.file.xml, query: "c:chartSpace" }, (rootChartElement) => {
@@ -26474,6 +26688,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26474
26688
  sharedFormulas: this.extractSharedFormulas(sheetElement),
26475
26689
  merges: this.extractMerges(sheetElement),
26476
26690
  cfs: this.extractConditionalFormats(),
26691
+ dataValidations: this.extractDataValidations(),
26477
26692
  figures: this.extractFigures(sheetElement),
26478
26693
  hyperlinks: this.extractHyperLinks(sheetElement),
26479
26694
  tables: this.extractTables(sheetElement),
@@ -26545,6 +26760,9 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26545
26760
  extractConditionalFormats() {
26546
26761
  return new XlsxCfExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractConditionalFormattings();
26547
26762
  }
26763
+ extractDataValidations() {
26764
+ return new XlsxDataValidationExtractor(this.rootFile, this.xlsxFileStructure, this.warningManager, this.theme).extractDataValidations();
26765
+ }
26548
26766
  extractFigures(worksheet) {
26549
26767
  const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
26550
26768
  const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
@@ -27825,6 +28043,7 @@ function createEmptySheet(sheetId, name) {
27825
28043
  rows: {},
27826
28044
  merges: [],
27827
28045
  conditionalFormats: [],
28046
+ dataValidationRules: [],
27828
28047
  figures: [],
27829
28048
  tables: [],
27830
28049
  isVisible: true,
@@ -31388,6 +31607,10 @@ class Popover extends owl.Component {
31388
31607
  this.currentDisplayValue = newDisplay;
31389
31608
  if (!anchor)
31390
31609
  return;
31610
+ el.style.top = "";
31611
+ el.style.left = "";
31612
+ el.style["max-height"] = "";
31613
+ el.style["max-width"] = "";
31391
31614
  const propsMaxSize = { width: this.props.maxWidth, height: this.props.maxHeight };
31392
31615
  let elDims = {
31393
31616
  width: el.getBoundingClientRect().width,
@@ -40248,7 +40471,7 @@ dataValidationEvaluatorRegistry.add("dateIs", {
40248
40471
  name: _t("Date is"),
40249
40472
  getPreview: (criterion, getters) => {
40250
40473
  return criterion.dateValue === "exactDate"
40251
- ? _t("Date is %s", getDateCriterionFormattedValues(criterion, getters)[0])
40474
+ ? _t("Date is %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
40252
40475
  : _t("Date is %s", DVTerms.DateIs[criterion.dateValue]);
40253
40476
  },
40254
40477
  });
@@ -40273,7 +40496,7 @@ dataValidationEvaluatorRegistry.add("dateIsBefore", {
40273
40496
  name: _t("Date is before"),
40274
40497
  getPreview: (criterion, getters) => {
40275
40498
  return criterion.dateValue === "exactDate"
40276
- ? _t("Date is before %s", getDateCriterionFormattedValues(criterion, getters)[0])
40499
+ ? _t("Date is before %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
40277
40500
  : _t("Date is before %s", DVTerms.DateIsBefore[criterion.dateValue]);
40278
40501
  },
40279
40502
  });
@@ -40298,7 +40521,7 @@ dataValidationEvaluatorRegistry.add("dateIsOnOrBefore", {
40298
40521
  name: _t("Date is on or before"),
40299
40522
  getPreview: (criterion, getters) => {
40300
40523
  return criterion.dateValue === "exactDate"
40301
- ? _t("Date is on or before %s", getDateCriterionFormattedValues(criterion, getters)[0])
40524
+ ? _t("Date is on or before %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
40302
40525
  : _t("Date is on or before %s", DVTerms.DateIsBefore[criterion.dateValue]);
40303
40526
  },
40304
40527
  });
@@ -40323,7 +40546,7 @@ dataValidationEvaluatorRegistry.add("dateIsAfter", {
40323
40546
  name: _t("Date is after"),
40324
40547
  getPreview: (criterion, getters) => {
40325
40548
  return criterion.dateValue === "exactDate"
40326
- ? _t("Date is after %s", getDateCriterionFormattedValues(criterion, getters)[0])
40549
+ ? _t("Date is after %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
40327
40550
  : _t("Date is after %s", DVTerms.DateIsBefore[criterion.dateValue]);
40328
40551
  },
40329
40552
  });
@@ -40348,7 +40571,7 @@ dataValidationEvaluatorRegistry.add("dateIsOnOrAfter", {
40348
40571
  name: _t("Date is on or after"),
40349
40572
  getPreview: (criterion, getters) => {
40350
40573
  return criterion.dateValue === "exactDate"
40351
- ? _t("Date is on or after %s", getDateCriterionFormattedValues(criterion, getters)[0])
40574
+ ? _t("Date is on or after %s", getDateCriterionFormattedValues(criterion.values, getters.getLocale())[0])
40352
40575
  : _t("Date is on or after %s", DVTerms.DateIsBefore[criterion.dateValue]);
40353
40576
  },
40354
40577
  });
@@ -40374,7 +40597,7 @@ dataValidationEvaluatorRegistry.add("dateIsBetween", {
40374
40597
  numberOfValues: () => 2,
40375
40598
  name: _t("Date is between"),
40376
40599
  getPreview: (criterion, getters) => {
40377
- const values = getDateCriterionFormattedValues(criterion, getters);
40600
+ const values = getDateCriterionFormattedValues(criterion.values, getters.getLocale());
40378
40601
  return _t("Date is between %s and %s", values[0], values[1]);
40379
40602
  },
40380
40603
  });
@@ -40400,7 +40623,7 @@ dataValidationEvaluatorRegistry.add("dateIsNotBetween", {
40400
40623
  numberOfValues: () => 2,
40401
40624
  name: _t("Date is not between"),
40402
40625
  getPreview: (criterion, getters) => {
40403
- const values = getDateCriterionFormattedValues(criterion, getters);
40626
+ const values = getDateCriterionFormattedValues(criterion.values, getters.getLocale());
40404
40627
  return _t("Date is not between %s and %s", values[0], values[1]);
40405
40628
  },
40406
40629
  });
@@ -40683,19 +40906,6 @@ function checkValueIsNumber(value) {
40683
40906
  const valueAsNumber = tryToNumber(value, DEFAULT_LOCALE);
40684
40907
  return valueAsNumber !== undefined;
40685
40908
  }
40686
- function getDateCriterionFormattedValues(criterion, getters) {
40687
- const locale = getters.getLocale();
40688
- return criterion.values.map((valueStr) => {
40689
- if (valueStr.startsWith("=")) {
40690
- return valueStr;
40691
- }
40692
- const value = parseLiteral(valueStr, locale);
40693
- if (typeof value === "number") {
40694
- return formatValue(value, { format: locale.dateFormat, locale });
40695
- }
40696
- return "";
40697
- });
40698
- }
40699
40909
 
40700
40910
  /** This component looks like a select input, but on click it opens a Menu with the items given as props instead of a dropdown */
40701
40911
  class SelectMenu extends owl.Component {
@@ -46398,7 +46608,7 @@ class GridComposer extends owl.Component {
46398
46608
  return;
46399
46609
  }
46400
46610
  const sheetId = this.env.model.getters.getActiveSheetId();
46401
- const zone = this.env.model.getters.getSelectedZone();
46611
+ const zone = positionToZone(this.env.model.getters.getSelection().anchor.cell);
46402
46612
  const rect = this.env.model.getters.getVisibleRect(zone);
46403
46613
  if (!deepEquals(rect, this.rect) || sheetId !== this.composerStore.currentEditedCell.sheetId) {
46404
46614
  this.isCellReferenceVisible = true;
@@ -48232,13 +48442,23 @@ class GridRenderer {
48232
48442
  drawLayer(renderingContext, layer) {
48233
48443
  switch (layer) {
48234
48444
  case "Background":
48235
- const boxes = this.getGridBoxes();
48236
- this.drawBackground(renderingContext, boxes);
48237
- this.drawOverflowingCellBackground(renderingContext, boxes);
48238
- this.drawCellBackground(renderingContext, boxes);
48239
- this.drawBorders(renderingContext, boxes);
48240
- this.drawTexts(renderingContext, boxes);
48241
- this.drawIcon(renderingContext, boxes);
48445
+ this.drawGlobalBackground(renderingContext);
48446
+ for (const zone of this.getters.getAllActiveViewportsZones()) {
48447
+ const { ctx } = renderingContext;
48448
+ ctx.save();
48449
+ ctx.beginPath();
48450
+ const rect = this.getters.getVisibleRect(zone);
48451
+ ctx.rect(rect.x, rect.y, rect.width, rect.height);
48452
+ ctx.clip();
48453
+ const boxes = this.getGridBoxes(zone);
48454
+ this.drawBackground(renderingContext, boxes);
48455
+ this.drawOverflowingCellBackground(renderingContext, boxes);
48456
+ this.drawCellBackground(renderingContext, boxes);
48457
+ this.drawBorders(renderingContext, boxes);
48458
+ this.drawTexts(renderingContext, boxes);
48459
+ this.drawIcon(renderingContext, boxes);
48460
+ ctx.restore();
48461
+ }
48242
48462
  this.drawFrozenPanes(renderingContext);
48243
48463
  break;
48244
48464
  case "Headers":
@@ -48249,12 +48469,15 @@ class GridRenderer {
48249
48469
  break;
48250
48470
  }
48251
48471
  }
48252
- drawBackground(renderingContext, boxes) {
48253
- const { ctx, thinLineWidth } = renderingContext;
48472
+ drawGlobalBackground(renderingContext) {
48473
+ const { ctx } = renderingContext;
48254
48474
  const { width, height } = this.getters.getSheetViewDimensionWithHeaders();
48255
48475
  // white background
48256
48476
  ctx.fillStyle = "#ffffff";
48257
48477
  ctx.fillRect(0, 0, width + CANVAS_SHIFT, height + CANVAS_SHIFT);
48478
+ }
48479
+ drawBackground(renderingContext, boxes) {
48480
+ const { ctx, thinLineWidth } = renderingContext;
48258
48481
  const areGridLinesVisible = !this.getters.isDashboard() &&
48259
48482
  this.getters.getGridLinesVisibility(this.getters.getActiveSheetId());
48260
48483
  const inset = areGridLinesVisible ? 0.1 * thinLineWidth : 0;
@@ -48685,7 +48908,7 @@ class GridRenderer {
48685
48908
  const position = { sheetId, col, row };
48686
48909
  const cell = this.getters.getEvaluatedCell(position);
48687
48910
  const showFormula = this.getters.shouldShowFormulas();
48688
- const { x, y, width, height } = this.getters.getVisibleRect(zone);
48911
+ const { x, y, width, height } = this.getters.getRect(zone);
48689
48912
  const { verticalAlign } = this.getters.getCellStyle(position);
48690
48913
  const box = {
48691
48914
  x,
@@ -48803,12 +49026,16 @@ class GridRenderer {
48803
49026
  }
48804
49027
  return box;
48805
49028
  }
48806
- getGridBoxes() {
49029
+ getGridBoxes(zone) {
48807
49030
  const boxes = [];
48808
- const visibleCols = this.getters.getSheetViewVisibleCols();
49031
+ const visibleCols = this.getters
49032
+ .getSheetViewVisibleCols()
49033
+ .filter((col) => col >= zone.left && col <= zone.right);
48809
49034
  const left = visibleCols[0];
48810
49035
  const right = visibleCols[visibleCols.length - 1];
48811
- const visibleRows = this.getters.getSheetViewVisibleRows();
49036
+ const visibleRows = this.getters
49037
+ .getSheetViewVisibleRows()
49038
+ .filter((row) => row >= zone.top && row <= zone.bottom);
48812
49039
  const top = visibleRows[0];
48813
49040
  const bottom = visibleRows[visibleRows.length - 1];
48814
49041
  const viewport = { left, right, top, bottom };
@@ -51158,7 +51385,7 @@ class CellPlugin extends CorePlugin {
51158
51385
  /*
51159
51386
  * Reconstructs the original formula string based on new dependencies
51160
51387
  */
51161
- getFormulaString(sheetId, tokens, dependencies, useFixedReference = false) {
51388
+ getFormulaString(sheetId, tokens, dependencies, useBoundedReference = false) {
51162
51389
  if (!dependencies.length) {
51163
51390
  return concat(tokens.map((token) => token.value));
51164
51391
  }
@@ -51166,7 +51393,7 @@ class CellPlugin extends CorePlugin {
51166
51393
  return concat(tokens.map((token) => {
51167
51394
  if (token.type === "REFERENCE") {
51168
51395
  const range = dependencies[rangeIndex++];
51169
- return this.getters.getRangeString(range, sheetId, { useFixedReference });
51396
+ return this.getters.getRangeString(range, sheetId, { useBoundedReference });
51170
51397
  }
51171
51398
  return token.value;
51172
51399
  }));
@@ -51446,7 +51673,7 @@ class FormulaCellWithDependencies {
51446
51673
  if (token.type === "REFERENCE") {
51447
51674
  const index = rangeIndex++;
51448
51675
  return this.getRangeString(this.compiledFormula.dependencies[index], this.sheetId, {
51449
- useFixedReference: true,
51676
+ useBoundedReference: true,
51450
51677
  });
51451
51678
  }
51452
51679
  return token.value;
@@ -51783,7 +52010,7 @@ class ConditionalFormatPlugin extends CorePlugin {
51783
52010
  if (data.sheets) {
51784
52011
  for (let sheet of data.sheets) {
51785
52012
  if (this.cfRules[sheet.id]) {
51786
- sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useFixedReference: true }));
52013
+ sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useBoundedReference: true }));
51787
52014
  }
51788
52015
  }
51789
52016
  }
@@ -51852,9 +52079,9 @@ class ConditionalFormatPlugin extends CorePlugin {
51852
52079
  // ---------------------------------------------------------------------------
51853
52080
  // Private
51854
52081
  // ---------------------------------------------------------------------------
51855
- mapToConditionalFormat(sheetId, cf, { useFixedReference } = { useFixedReference: false }) {
52082
+ mapToConditionalFormat(sheetId, cf, { useBoundedReference } = { useBoundedReference: false }) {
51856
52083
  const ranges = cf.ranges.map((range) => {
51857
- return this.getters.getRangeString(range, sheetId, { useFixedReference });
52084
+ return this.getters.getRangeString(range, sheetId, { useBoundedReference });
51858
52085
  });
51859
52086
  if (cf.rule.type !== "DataBarRule") {
51860
52087
  return {
@@ -51869,7 +52096,7 @@ class ConditionalFormatPlugin extends CorePlugin {
51869
52096
  ...cf.rule,
51870
52097
  rangeValues: cf.rule.rangeValues &&
51871
52098
  this.getters.getRangeString(cf.rule.rangeValues, sheetId, {
51872
- useFixedReference,
52099
+ useBoundedReference,
51873
52100
  }),
51874
52101
  },
51875
52102
  ranges,
@@ -52292,6 +52519,30 @@ class DataValidationPlugin extends CorePlugin {
52292
52519
  }
52293
52520
  }
52294
52521
  }
52522
+ exportForExcel(data) {
52523
+ if (!data.sheets) {
52524
+ return;
52525
+ }
52526
+ for (const sheet of data.sheets) {
52527
+ sheet.dataValidationRules = [];
52528
+ for (const rule of this.rules[sheet.id]) {
52529
+ const excelRule = {
52530
+ ...deepCopy(rule),
52531
+ ranges: rule.ranges.map((range) => this.getters.getRangeString(range, sheet.id, { useBoundedReference: true })),
52532
+ };
52533
+ if (rule.criterion.type === "isValueInRange") {
52534
+ excelRule.criterion.values = rule.criterion.values.map((value) => {
52535
+ const range = this.getters.getRangeFromSheetXC(sheet.id, value);
52536
+ return this.getters.getRangeString(range, sheet.id, {
52537
+ useBoundedReference: true,
52538
+ useFixedReference: true,
52539
+ });
52540
+ });
52541
+ }
52542
+ sheet.dataValidationRules.push(excelRule);
52543
+ }
52544
+ }
52545
+ }
52295
52546
  checkCriterionTypeIsValid(cmd) {
52296
52547
  return dataValidationEvaluatorRegistry.contains(cmd.rule.criterion.type)
52297
52548
  ? "Success" /* CommandResult.Success */
@@ -53702,9 +53953,10 @@ class RangeAdapter {
53702
53953
  * @param range the range (received from getRangeFromXC or getRangeFromZone)
53703
53954
  * @param forSheetId the id of the sheet where the range string is supposed to be used.
53704
53955
  * @param options
53956
+ * @param options.useBoundedReference if true, the range will be returned with bounded row and column
53705
53957
  * @param options.useFixedReference if true, the range will be returned with fixed row and column
53706
53958
  */
53707
- getRangeString(range, forSheetId, options = { useFixedReference: false }) {
53959
+ getRangeString(range, forSheetId, options = { useBoundedReference: false, useFixedReference: false }) {
53708
53960
  if (!range) {
53709
53961
  return CellErrorType.InvalidReference;
53710
53962
  }
@@ -53807,13 +54059,13 @@ class RangeAdapter {
53807
54059
  /**
53808
54060
  * Get a Xc string that represent a part of a range
53809
54061
  */
53810
- getRangePartString(range, part, options = { useFixedReference: false }) {
53811
- const colFixed = range.parts && range.parts[part]?.colFixed ? "$" : "";
54062
+ getRangePartString(range, part, options = { useBoundedReference: false, useFixedReference: false }) {
54063
+ const colFixed = range.parts[part]?.colFixed || options.useFixedReference ? "$" : "";
53812
54064
  const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);
53813
- const rowFixed = range.parts && range.parts[part]?.rowFixed ? "$" : "";
54065
+ const rowFixed = range.parts[part]?.rowFixed || options.useFixedReference ? "$" : "";
53814
54066
  const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);
53815
54067
  let str = "";
53816
- if (range.isFullCol && !options.useFixedReference) {
54068
+ if (range.isFullCol && !options.useBoundedReference) {
53817
54069
  if (part === 0 && range.unboundedZone.hasHeader) {
53818
54070
  str = colFixed + col + rowFixed + row;
53819
54071
  }
@@ -53821,7 +54073,7 @@ class RangeAdapter {
53821
54073
  str = colFixed + col;
53822
54074
  }
53823
54075
  }
53824
- else if (range.isFullRow && !options.useFixedReference) {
54076
+ else if (range.isFullRow && !options.useBoundedReference) {
53825
54077
  if (part === 0 && range.unboundedZone.hasHeader) {
53826
54078
  str = colFixed + col + rowFixed + row;
53827
54079
  }
@@ -54059,6 +54311,7 @@ class SheetPlugin extends CorePlugin {
54059
54311
  formats: {},
54060
54312
  borders: {},
54061
54313
  conditionalFormats: [],
54314
+ dataValidationRules: [],
54062
54315
  figures: [],
54063
54316
  tables: [],
54064
54317
  areGridLinesVisible: sheet.areGridLinesVisible === undefined ? true : sheet.areGridLinesVisible,
@@ -61020,6 +61273,7 @@ class Session extends EventBus {
61020
61273
  waitingUndoRedoAck = false;
61021
61274
  isReplayingInitialRevisions = false;
61022
61275
  processedRevisions = new Set();
61276
+ lastRevisionMessage = undefined;
61023
61277
  uuidGenerator = new UuidGenerator();
61024
61278
  lastLocalOperation;
61025
61279
  /**
@@ -61120,7 +61374,10 @@ class Session extends EventBus {
61120
61374
  * Notify the server that the user client left the collaborative session
61121
61375
  */
61122
61376
  async leave(data) {
61123
- if (data && Object.keys(this.clients).length === 1 && this.processedRevisions.size) {
61377
+ if (data &&
61378
+ Object.keys(this.clients).length === 1 &&
61379
+ this.lastRevisionMessage &&
61380
+ this.lastRevisionMessage?.type !== "SNAPSHOT_CREATED") {
61124
61381
  await this.snapshot(data());
61125
61382
  }
61126
61383
  delete this.clients[this.clientId];
@@ -61341,6 +61598,7 @@ class Session extends EventBus {
61341
61598
  this.pendingMessages = this.pendingMessages.filter((msg) => msg.nextRevisionId !== message.nextRevisionId);
61342
61599
  this.serverRevisionId = message.nextRevisionId;
61343
61600
  this.processedRevisions.add(message.nextRevisionId);
61601
+ this.lastRevisionMessage = message;
61344
61602
  this.sendPendingMessage();
61345
61603
  break;
61346
61604
  }
@@ -64542,8 +64800,12 @@ class GridSelectionPlugin extends UIPlugin {
64542
64800
  },
64543
64801
  ];
64544
64802
  handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
64803
+ const selection = pasteTarget[0];
64804
+ const col = selection.left;
64805
+ const row = selection.top;
64806
+ this.setSelectionMixin({ zone: selection, cell: { col, row } }, [selection]);
64545
64807
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
64546
- let currentIndex = cmd.base;
64808
+ let currentIndex = isBasedBefore ? cmd.base : cmd.base + 1;
64547
64809
  for (const element of toRemove) {
64548
64810
  const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
64549
64811
  this.dispatch("RESIZE_COLUMNS_ROWS", {
@@ -64826,22 +65088,33 @@ class InternalViewport {
64826
65088
  }
64827
65089
  /**
64828
65090
  *
64829
- * @param zone
64830
- * @returns Computes the absolute coordinate of a given zone inside the viewport
65091
+ * Computes the visible coordinates & dimensions of a given zone inside the viewport
65092
+ *
64831
65093
  */
64832
- getRect(zone) {
65094
+ getVisibleRect(zone) {
64833
65095
  const targetZone = intersection(zone, this);
64834
65096
  if (targetZone) {
64835
65097
  const x = this.getters.getColRowOffset("COL", this.left, targetZone.left) + this.offsetCorrectionX;
64836
65098
  const y = this.getters.getColRowOffset("ROW", this.top, targetZone.top) + this.offsetCorrectionY;
64837
65099
  const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
64838
65100
  const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
64839
- return {
64840
- x,
64841
- y,
64842
- width,
64843
- height,
64844
- };
65101
+ return { x, y, width, height };
65102
+ }
65103
+ return undefined;
65104
+ }
65105
+ /**
65106
+ *
65107
+ * @returns Computes the absolute coordinates & dimensions of a given zone inside the viewport
65108
+ *
65109
+ */
65110
+ getFullRect(zone) {
65111
+ const targetZone = intersection(zone, this);
65112
+ if (targetZone) {
65113
+ const x = this.getters.getColRowOffset("COL", this.left, zone.left) + this.offsetCorrectionX;
65114
+ const y = this.getters.getColRowOffset("ROW", this.top, zone.top) + this.offsetCorrectionY;
65115
+ const width = this.getters.getColRowOffset("COL", zone.left, zone.right + 1);
65116
+ const height = this.getters.getColRowOffset("ROW", zone.top, zone.bottom + 1);
65117
+ return { x, y, width, height };
64845
65118
  }
64846
65119
  return undefined;
64847
65120
  }
@@ -65011,6 +65284,8 @@ class SheetViewPlugin extends UIPlugin {
65011
65284
  "isPositionVisible",
65012
65285
  "getColDimensionsInViewport",
65013
65286
  "getRowDimensionsInViewport",
65287
+ "getAllActiveViewportsZones",
65288
+ "getRect",
65014
65289
  ];
65015
65290
  viewports = {};
65016
65291
  /**
@@ -65397,16 +65672,27 @@ class SheetViewPlugin extends UIPlugin {
65397
65672
  getVisibleRectWithoutHeaders(zone) {
65398
65673
  const sheetId = this.getters.getActiveSheetId();
65399
65674
  const viewportRects = this.getSubViewports(sheetId)
65400
- .map((viewport) => viewport.getRect(zone))
65675
+ .map((viewport) => viewport.getVisibleRect(zone))
65401
65676
  .filter(isDefined);
65402
65677
  if (viewportRects.length === 0) {
65403
65678
  return { x: 0, y: 0, width: 0, height: 0 };
65404
65679
  }
65405
- const x = Math.min(...viewportRects.map((rect) => rect.x));
65406
- const y = Math.min(...viewportRects.map((rect) => rect.y));
65407
- const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
65408
- const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
65409
- return { x, y, width, height };
65680
+ return this.recomposeRect(viewportRects);
65681
+ }
65682
+ /**
65683
+ * Computes the actual size and position (:Rect) of the zone on the canvas
65684
+ * regardless of the viewport dimensions.
65685
+ */
65686
+ getRect(zone) {
65687
+ const sheetId = this.getters.getActiveSheetId();
65688
+ const viewportRects = this.getSubViewports(sheetId)
65689
+ .map((viewport) => viewport.getFullRect(zone))
65690
+ .filter(isDefined);
65691
+ if (viewportRects.length === 0) {
65692
+ return { x: 0, y: 0, width: 0, height: 0 };
65693
+ }
65694
+ const rect = this.recomposeRect(viewportRects);
65695
+ return { ...rect, x: rect.x + this.gridOffsetX, y: rect.y + this.gridOffsetY };
65410
65696
  }
65411
65697
  /**
65412
65698
  * Returns the position of the MainViewport relatively to the start of the grid (without headers)
@@ -65450,6 +65736,10 @@ class SheetViewPlugin extends UIPlugin {
65450
65736
  end: start + (isRowHidden ? 0 : size),
65451
65737
  };
65452
65738
  }
65739
+ getAllActiveViewportsZones() {
65740
+ const sheetId = this.getters.getActiveSheetId();
65741
+ return this.getSubViewports(sheetId);
65742
+ }
65453
65743
  // ---------------------------------------------------------------------------
65454
65744
  // Private
65455
65745
  // ---------------------------------------------------------------------------
@@ -65640,6 +65930,13 @@ class SheetViewPlugin extends UIPlugin {
65640
65930
  const height = this.sheetViewHeight + this.gridOffsetY;
65641
65931
  return { xRatio: offsetCorrectionX / width, yRatio: offsetCorrectionY / height };
65642
65932
  }
65933
+ recomposeRect(viewportRects) {
65934
+ const x = Math.min(...viewportRects.map((rect) => rect.x));
65935
+ const y = Math.min(...viewportRects.map((rect) => rect.y));
65936
+ const width = Math.max(...viewportRects.map((rect) => rect.x + rect.width)) - x;
65937
+ const height = Math.max(...viewportRects.map((rect) => rect.y + rect.height)) - y;
65938
+ return { x, y, width, height };
65939
+ }
65643
65940
  }
65644
65941
 
65645
65942
  class HeaderPositionsUIPlugin extends UIPlugin {
@@ -70945,6 +71242,124 @@ function getExcelThresholdType(type, position) {
70945
71242
  }
70946
71243
  }
70947
71244
 
71245
+ function addDataValidationRules(dataValidationRules) {
71246
+ const dvRulesCount = dataValidationRules.length;
71247
+ if (dvRulesCount === 0) {
71248
+ return [];
71249
+ }
71250
+ const dvNodes = [new XMLString(`<dataValidations count="${dvRulesCount}">`)];
71251
+ for (const dvRule of dataValidationRules) {
71252
+ switch (dvRule.criterion.type) {
71253
+ case "dateIs":
71254
+ case "dateIsBefore":
71255
+ case "dateIsOnOrBefore":
71256
+ case "dateIsAfter":
71257
+ case "dateIsOnOrAfter":
71258
+ case "dateIsBetween":
71259
+ case "dateIsNotBetween":
71260
+ dvNodes.push(addDateRule(dvRule));
71261
+ break;
71262
+ case "isEqual":
71263
+ case "isNotEqual":
71264
+ case "isGreaterThan":
71265
+ case "isGreaterOrEqualTo":
71266
+ case "isLessThan":
71267
+ case "isLessOrEqualTo":
71268
+ case "isBetween":
71269
+ case "isNotBetween":
71270
+ dvNodes.push(addDecimalRule(dvRule));
71271
+ break;
71272
+ case "isValueInRange":
71273
+ case "isValueInList":
71274
+ dvNodes.push(addListRule(dvRule));
71275
+ break;
71276
+ case "customFormula":
71277
+ dvNodes.push(addCustomFormulaRule(dvRule));
71278
+ break;
71279
+ default:
71280
+ console.warn(`Data validation ${dvRule.criterion.type} is not supported in xlsx.`);
71281
+ break;
71282
+ }
71283
+ }
71284
+ dvNodes.push(new XMLString("</dataValidations>"));
71285
+ return dvNodes;
71286
+ }
71287
+ function addDateRule(dvRule) {
71288
+ const rule = dvRule.criterion;
71289
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
71290
+ const formula2 = rule.values[1] ? adaptFormulaToExcel(rule.values[1]) : undefined;
71291
+ const operator = convertDateCriterionTypeToExcelOperator(dvRule.criterion.type);
71292
+ const attributes = commonDataValidationAttributes(dvRule);
71293
+ attributes.push(["type", "date"], ["operator", operator]);
71294
+ if (formula2) {
71295
+ return escapeXml /*xml*/ `
71296
+ <dataValidation ${formatAttributes(attributes)}>
71297
+ <formula1>${toNumber(formula1, DEFAULT_LOCALE)}</formula1>
71298
+ <formula2>${toNumber(formula2, DEFAULT_LOCALE)}</formula2>
71299
+ </dataValidation>
71300
+ `;
71301
+ }
71302
+ return escapeXml /*xml*/ `
71303
+ <dataValidation ${formatAttributes(attributes)}>
71304
+ <formula1>${toNumber(formula1, DEFAULT_LOCALE)}</formula1>
71305
+ </dataValidation>
71306
+ `;
71307
+ }
71308
+ function addDecimalRule(dvRule) {
71309
+ const rule = dvRule.criterion;
71310
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
71311
+ const formula2 = rule.values[1] ? adaptFormulaToExcel(rule.values[1]) : undefined;
71312
+ const operator = convertDecimalCriterionTypeToExcelOperator(dvRule.criterion.type);
71313
+ const attributes = commonDataValidationAttributes(dvRule);
71314
+ attributes.push(["type", "decimal"], ["operator", operator]);
71315
+ if (formula2) {
71316
+ return escapeXml /*xml*/ `
71317
+ <dataValidation ${formatAttributes(attributes)}>
71318
+ <formula1>${formula1}</formula1>
71319
+ <formula2>${formula2}</formula2>
71320
+ </dataValidation>
71321
+ `;
71322
+ }
71323
+ return escapeXml /*xml*/ `
71324
+ <dataValidation ${formatAttributes(attributes)}>
71325
+ <formula1>${formula1}</formula1>
71326
+ </dataValidation>
71327
+ `;
71328
+ }
71329
+ function addListRule(dvRule) {
71330
+ const rule = dvRule.criterion;
71331
+ const formula1 = dvRule.criterion.type === "isValueInRange"
71332
+ ? adaptFormulaToExcel(rule.values[0])
71333
+ : `"${rule.values.join(",")}"`;
71334
+ const attributes = commonDataValidationAttributes(dvRule);
71335
+ attributes.push(["type", "list"]);
71336
+ return escapeXml /*xml*/ `
71337
+ <dataValidation ${formatAttributes(attributes)}>
71338
+ <formula1>${formula1}</formula1>
71339
+ </dataValidation>
71340
+ `;
71341
+ }
71342
+ function addCustomFormulaRule(dvRule) {
71343
+ const rule = dvRule.criterion;
71344
+ const formula1 = adaptFormulaToExcel(rule.values[0]);
71345
+ const attributes = commonDataValidationAttributes(dvRule);
71346
+ attributes.push(["type", "custom"]);
71347
+ return escapeXml /*xml*/ `
71348
+ <dataValidation ${formatAttributes(attributes)}>
71349
+ <formula1>${formula1}</formula1>
71350
+ </dataValidation>
71351
+ `;
71352
+ }
71353
+ function commonDataValidationAttributes(dvRule) {
71354
+ return [
71355
+ ["allowBlank", "1"],
71356
+ ["showInputMessage", "1"],
71357
+ ["showErrorMessage", "1"],
71358
+ ["errorStyle", !dvRule.isBlocking ? "warning" : ""],
71359
+ ["sqref", dvRule.ranges.join(" ")],
71360
+ ];
71361
+ }
71362
+
70948
71363
  function createDrawing(drawingRelIds, sheet, figures) {
70949
71364
  const namespaces = [
70950
71365
  ["xmlns:xdr", NAMESPACE.drawing],
@@ -71723,6 +72138,7 @@ function createWorksheets(data, construct) {
71723
72138
  ${addRows(construct, data, sheet)}
71724
72139
  ${addMerges(sheet.merges)}
71725
72140
  ${joinXmlNodes(addConditionalFormatting(construct.dxfs, sheet.conditionalFormats))}
72141
+ ${joinXmlNodes(addDataValidationRules(sheet.dataValidationRules))}
71726
72142
  ${addHyperlinks(construct, data, sheetIndex)}
71727
72143
  ${drawingNode}
71728
72144
  ${tablesNode}
@@ -72674,6 +73090,6 @@ exports.tokenColors = tokenColors;
72674
73090
  exports.tokenize = tokenize;
72675
73091
 
72676
73092
 
72677
- __info__.version = "18.0.10";
72678
- __info__.date = "2025-01-15T08:05:47.616Z";
72679
- __info__.hash = "94c45c7";
73093
+ __info__.version = "18.0.12";
73094
+ __info__.date = "2025-01-29T06:24:22.122Z";
73095
+ __info__.hash = "a881cff";