@odoo/o-spreadsheet 18.1.7 → 18.1.9

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.7
6
- * @date 2025-02-10T09:00:28.556Z
7
- * @hash 338d8a1
5
+ * @version 18.1.9
6
+ * @date 2025-02-25T05:59:45.472Z
7
+ * @hash 6789c1c
8
8
  */
9
9
 
10
10
  'use strict';
@@ -2220,17 +2220,7 @@ function toZoneWithoutBoundaryChanges(xc) {
2220
2220
  */
2221
2221
  function toUnboundedZone(xc) {
2222
2222
  const zone = toZoneWithoutBoundaryChanges(xc);
2223
- if (zone.right !== undefined && zone.right < zone.left) {
2224
- const tmp = zone.left;
2225
- zone.left = zone.right;
2226
- zone.right = tmp;
2227
- }
2228
- if (zone.bottom !== undefined && zone.bottom < zone.top) {
2229
- const tmp = zone.top;
2230
- zone.top = zone.bottom;
2231
- zone.bottom = tmp;
2232
- }
2233
- return zone;
2223
+ return reorderZone(zone);
2234
2224
  }
2235
2225
  /**
2236
2226
  * Convert from a cartesian reference to a Zone.
@@ -2504,11 +2494,11 @@ function positions(zone) {
2504
2494
  return positions;
2505
2495
  }
2506
2496
  function reorderZone(zone) {
2507
- if (zone.left > zone.right) {
2508
- zone = { left: zone.right, right: zone.left, top: zone.top, bottom: zone.bottom };
2497
+ if (zone.right !== undefined && zone.left > zone.right) {
2498
+ zone = { ...zone, left: zone.right, right: zone.left };
2509
2499
  }
2510
- if (zone.top > zone.bottom) {
2511
- zone = { left: zone.left, right: zone.right, top: zone.bottom, bottom: zone.top };
2500
+ if (zone.bottom !== undefined && zone.top > zone.bottom) {
2501
+ zone = { ...zone, top: zone.bottom, bottom: zone.top };
2512
2502
  }
2513
2503
  return zone;
2514
2504
  }
@@ -3413,12 +3403,12 @@ function isTargetDependent(cmd) {
3413
3403
  function isRangeDependant(cmd) {
3414
3404
  return "ranges" in cmd;
3415
3405
  }
3416
- function isZoneDependent(cmd) {
3417
- return "zone" in cmd;
3418
- }
3419
3406
  function isPositionDependent(cmd) {
3420
3407
  return "col" in cmd && "row" in cmd && "sheetId" in cmd;
3421
3408
  }
3409
+ function isZoneDependent(cmd) {
3410
+ return "sheetId" in cmd && "zone" in cmd;
3411
+ }
3422
3412
  const invalidateEvaluationCommands = new Set([
3423
3413
  "RENAME_SHEET",
3424
3414
  "DELETE_SHEET",
@@ -3430,6 +3420,7 @@ const invalidateEvaluationCommands = new Set([
3430
3420
  "REDO",
3431
3421
  "ADD_MERGE",
3432
3422
  "REMOVE_MERGE",
3423
+ "DUPLICATE_SHEET",
3433
3424
  "UPDATE_LOCALE",
3434
3425
  "ADD_PIVOT",
3435
3426
  "UPDATE_PIVOT",
@@ -3459,7 +3450,6 @@ const invalidateChartEvaluationCommands = new Set([
3459
3450
  ]);
3460
3451
  const invalidateDependenciesCommands = new Set(["MOVE_RANGES"]);
3461
3452
  const invalidateCFEvaluationCommands = new Set([
3462
- "DUPLICATE_SHEET",
3463
3453
  "EVALUATE_CELLS",
3464
3454
  "ADD_CONDITIONAL_FORMAT",
3465
3455
  "REMOVE_CONDITIONAL_FORMAT",
@@ -3629,6 +3619,7 @@ exports.CommandResult = void 0;
3629
3619
  CommandResult["InvalidRange"] = "InvalidRange";
3630
3620
  CommandResult["InvalidZones"] = "InvalidZones";
3631
3621
  CommandResult["InvalidSheetId"] = "InvalidSheetId";
3622
+ CommandResult["InvalidCellId"] = "InvalidCellId";
3632
3623
  CommandResult["InvalidFigureId"] = "InvalidFigureId";
3633
3624
  CommandResult["InputAlreadyFocused"] = "InputAlreadyFocused";
3634
3625
  CommandResult["MaximumRangesReached"] = "MaximumRangesReached";
@@ -4460,7 +4451,7 @@ function dichotomicSearch(data, target, mode, sortOrder, rangeLength, getValueIn
4460
4451
  * @param reverseSearch if true, search in the array starting from the end.
4461
4452
 
4462
4453
  */
4463
- function linearSearch(data, target, mode, numberOfValues, getValueInData, reverseSearch = false) {
4454
+ function linearSearch(data, target, mode, numberOfValues, getValueInData, lookupCaches, reverseSearch = false) {
4464
4455
  if (target === undefined || target.value === null) {
4465
4456
  return -1;
4466
4457
  }
@@ -4469,17 +4460,48 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4469
4460
  }
4470
4461
  const _target = normalizeValue(target.value);
4471
4462
  const getValue = reverseSearch
4472
- ? (data, i) => getValueInData(data, numberOfValues - i - 1)
4473
- : getValueInData;
4463
+ ? (data, i) => normalizeValue(getValueInData(data, numberOfValues - i - 1))
4464
+ : (data, i) => normalizeValue(getValueInData(data, i));
4465
+ // first check if the target is in the cache
4466
+ const isNotWildcardTarget = mode !== "wildcard" ||
4467
+ typeof _target !== "string" ||
4468
+ !(_target.includes("*") || _target.includes("?"));
4469
+ if (lookupCaches && isNotWildcardTarget) {
4470
+ const searchMode = reverseSearch ? "reverseSearch" : "forwardSearch";
4471
+ let cache = lookupCaches[searchMode].get(data);
4472
+ if (cache === undefined) {
4473
+ // build the cache for all the values
4474
+ cache = new Map();
4475
+ for (let i = 0; i < numberOfValues; i++) {
4476
+ const value = getValue(data, i) ?? null;
4477
+ if (!cache.has(value)) {
4478
+ cache.set(value, i);
4479
+ }
4480
+ }
4481
+ lookupCaches[searchMode].set(data, cache);
4482
+ }
4483
+ if (cache.has(_target)) {
4484
+ const resultIndex = cache.get(_target);
4485
+ return reverseSearch ? numberOfValues - resultIndex - 1 : resultIndex;
4486
+ }
4487
+ if (mode === "strict") {
4488
+ return -1;
4489
+ }
4490
+ }
4491
+ // else perform the linear search
4492
+ const resultIndex = _linearSearch(data, _target, mode, numberOfValues, getValue);
4493
+ return reverseSearch && resultIndex !== -1 ? numberOfValues - resultIndex - 1 : resultIndex;
4494
+ }
4495
+ function _linearSearch(data, _target, mode, numberOfValues, getNormalizeValue) {
4474
4496
  let indexMatchTarget = (i) => {
4475
- return normalizeValue(getValue(data, i)) === _target;
4497
+ return getNormalizeValue(data, i) === _target;
4476
4498
  };
4477
4499
  if (mode === "wildcard" &&
4478
4500
  typeof _target === "string" &&
4479
4501
  (_target.includes("*") || _target.includes("?"))) {
4480
4502
  const regExp = wildcardToRegExp(_target);
4481
4503
  indexMatchTarget = (i) => {
4482
- const value = normalizeValue(getValue(data, i));
4504
+ const value = getNormalizeValue(data, i);
4483
4505
  if (typeof value === "string") {
4484
4506
  return regExp.test(value);
4485
4507
  }
@@ -4490,7 +4512,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4490
4512
  let closestMatchIndex = -1;
4491
4513
  if (mode === "nextSmaller") {
4492
4514
  indexMatchTarget = (i) => {
4493
- const value = normalizeValue(getValue(data, i));
4515
+ const value = getNormalizeValue(data, i);
4494
4516
  if ((!closestMatch && compareCellValues(_target, value) >= 0) ||
4495
4517
  (compareCellValues(_target, value) >= 0 && compareCellValues(value, closestMatch) > 0)) {
4496
4518
  closestMatch = value;
@@ -4501,7 +4523,7 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4501
4523
  }
4502
4524
  if (mode === "nextGreater") {
4503
4525
  indexMatchTarget = (i) => {
4504
- const value = normalizeValue(getValue(data, i));
4526
+ const value = getNormalizeValue(data, i);
4505
4527
  if ((!closestMatch && compareCellValues(_target, value) <= 0) ||
4506
4528
  (compareCellValues(_target, value) <= 0 && compareCellValues(value, closestMatch) < 0)) {
4507
4529
  closestMatch = value;
@@ -4512,12 +4534,10 @@ function linearSearch(data, target, mode, numberOfValues, getValueInData, revers
4512
4534
  }
4513
4535
  for (let i = 0; i < numberOfValues; i++) {
4514
4536
  if (indexMatchTarget(i)) {
4515
- return reverseSearch ? numberOfValues - i - 1 : i;
4537
+ return i;
4516
4538
  }
4517
4539
  }
4518
- return reverseSearch && closestMatchIndex !== -1
4519
- ? numberOfValues - closestMatchIndex - 1
4520
- : closestMatchIndex;
4540
+ return closestMatchIndex;
4521
4541
  }
4522
4542
  /**
4523
4543
  * Normalize a value.
@@ -6481,11 +6501,40 @@ function drawDecoratedText(context, text, position, underline = false, strikethr
6481
6501
  * https://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
6482
6502
  * */
6483
6503
  class UuidGenerator {
6504
+ /**
6505
+ * Generates a custom UUID using a simple 36^12 method (8-character alphanumeric string with lowercase letters)
6506
+ * This has a higher chance of collision than a UUIDv4, but not only faster to generate than an UUIDV4,
6507
+ * it also has a smaller size, which is preferable to alleviate the overall data size.
6508
+ *
6509
+ * This method is preferable when generating uuids for the core data (sheetId, figureId, etc)
6510
+ * as they will appear several times in the revisions and local history.
6511
+ *
6512
+ */
6513
+ smallUuid() {
6514
+ if (window.crypto) {
6515
+ return "10000000-1000".replace(/[01]/g, (c) => {
6516
+ const n = Number(c);
6517
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6518
+ });
6519
+ }
6520
+ else {
6521
+ // mainly for jest and other browsers that do not have the crypto functionality
6522
+ return "xxxxxxxx-xxxx".replace(/[xy]/g, function (c) {
6523
+ const r = (Math.random() * 16) | 0, v = c == "x" ? r : (r & 0x3) | 0x8;
6524
+ return v.toString(16);
6525
+ });
6526
+ }
6527
+ }
6528
+ /**
6529
+ * Generates an UUIDV4, has astronomically low chance of collision, but is larger in size than the smallUuid.
6530
+ * This method should be used when you need to avoid collisions at all costs, like the id of a revision.
6531
+ */
6484
6532
  uuidv4() {
6485
- //@ts-ignore
6486
- if (window.crypto && window.crypto.getRandomValues) {
6487
- //@ts-ignore
6488
- return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) => (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16));
6533
+ if (window.crypto) {
6534
+ return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, (c) => {
6535
+ const n = Number(c);
6536
+ return (n ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (n / 4)))).toString(16);
6537
+ });
6489
6538
  }
6490
6539
  else {
6491
6540
  // mainly for jest and other browsers that do not have the crypto functionality
@@ -8515,7 +8564,7 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
8515
8564
  };
8516
8565
  }
8517
8566
  getPasteTarget(sheetId, target, content, options) {
8518
- const newId = new UuidGenerator().uuidv4();
8567
+ const newId = new UuidGenerator().smallUuid();
8519
8568
  return { zones: [], figureId: newId, sheetId };
8520
8569
  }
8521
8570
  paste(target, clippedContent, options) {
@@ -8681,7 +8730,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
8681
8730
  if (!targetCF && queuedCfs) {
8682
8731
  targetCF = queuedCfs.find((queued) => queued.cf.stopIfTrue === originCF.stopIfTrue && deepEquals(queued.cf.rule, originCF.rule))?.cf;
8683
8732
  }
8684
- return targetCF || { ...originCF, id: this.uuidGenerator.uuidv4(), ranges: [] };
8733
+ return targetCF || { ...originCF, id: this.uuidGenerator.smallUuid(), ranges: [] };
8685
8734
  }
8686
8735
  }
8687
8736
 
@@ -8774,7 +8823,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
8774
8823
  }
8775
8824
  return (targetRule || {
8776
8825
  ...originRule,
8777
- id: newId ? this.uuidGenerator.uuidv4() : originRule.id,
8826
+ id: newId ? this.uuidGenerator.smallUuid() : originRule.id,
8778
8827
  ranges: [],
8779
8828
  });
8780
8829
  }
@@ -8836,7 +8885,7 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
8836
8885
  };
8837
8886
  }
8838
8887
  getPasteTarget(sheetId, target, content, options) {
8839
- const newId = new UuidGenerator().uuidv4();
8888
+ const newId = new UuidGenerator().smallUuid();
8840
8889
  return { sheetId, zones: [], figureId: newId };
8841
8890
  }
8842
8891
  paste(target, clippedContent, options) {
@@ -12038,6 +12087,25 @@ const LN = {
12038
12087
  isExported: true,
12039
12088
  };
12040
12089
  // -----------------------------------------------------------------------------
12090
+ // LOG
12091
+ // -----------------------------------------------------------------------------
12092
+ const LOG = {
12093
+ description: _t("The logarithm of a number, for a given base."),
12094
+ args: [
12095
+ arg("value (number)", _t("The value for which to calculate the logarithm.")),
12096
+ arg("base (number, default=10)", _t("The base of the logarithm.")),
12097
+ ],
12098
+ compute: function (value, base = { value: 10 }) {
12099
+ const _value = toNumber(value, this.locale);
12100
+ const _base = toNumber(base, this.locale);
12101
+ assert(() => _value > 0, _t("The value (%s) must be strictly positive.", _value.toString()));
12102
+ assert(() => _base > 0, _t("The base (%s) must be strictly positive.", _base.toString()));
12103
+ assert(() => _base !== 1, _t("The base must be different from 1."));
12104
+ return Math.log10(_value) / Math.log10(_base);
12105
+ },
12106
+ isExported: true,
12107
+ };
12108
+ // -----------------------------------------------------------------------------
12041
12109
  // MOD
12042
12110
  // -----------------------------------------------------------------------------
12043
12111
  function mod(dividend, divisor) {
@@ -12577,6 +12645,7 @@ var math = /*#__PURE__*/Object.freeze({
12577
12645
  ISODD: ISODD,
12578
12646
  ISO_CEILING: ISO_CEILING,
12579
12647
  LN: LN,
12648
+ LOG: LOG,
12580
12649
  MOD: MOD,
12581
12650
  MUNIT: MUNIT,
12582
12651
  ODD: ODD,
@@ -15111,7 +15180,7 @@ const SORTN = {
15111
15180
  }
15112
15181
  }
15113
15182
  },
15114
- isExported: true,
15183
+ isExported: false,
15115
15184
  };
15116
15185
  // -----------------------------------------------------------------------------
15117
15186
  // UNIQUE
@@ -18475,7 +18544,7 @@ const HLOOKUP = {
18475
18544
  const _isSorted = toBoolean(isSorted.value);
18476
18545
  const colIndex = _isSorted
18477
18546
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range.length, getValueFromRange)
18478
- : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange);
18547
+ : linearSearch(_range, searchKey, "wildcard", _range.length, getValueFromRange, this.lookupCaches);
18479
18548
  const col = _range[colIndex];
18480
18549
  if (col === undefined) {
18481
18550
  return valueNotAvailable(searchKey);
@@ -18630,7 +18699,7 @@ const MATCH = {
18630
18699
  index = dichotomicSearch(_range, searchKey, "nextSmaller", "asc", rangeLen, getElement);
18631
18700
  break;
18632
18701
  case 0:
18633
- index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement);
18702
+ index = linearSearch(_range, searchKey, "wildcard", rangeLen, getElement, this.lookupCaches);
18634
18703
  break;
18635
18704
  case -1:
18636
18705
  index = dichotomicSearch(_range, searchKey, "nextGreater", "desc", rangeLen, getElement);
@@ -18698,7 +18767,7 @@ const VLOOKUP = {
18698
18767
  const _isSorted = toBoolean(isSorted.value);
18699
18768
  const rowIndex = _isSorted
18700
18769
  ? dichotomicSearch(_range, searchKey, "nextSmaller", "asc", _range[0].length, getValueFromRange)
18701
- : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange);
18770
+ : linearSearch(_range, searchKey, "wildcard", _range[0].length, getValueFromRange, this.lookupCaches);
18702
18771
  const value = _range[_index - 1][rowIndex];
18703
18772
  if (value === undefined) {
18704
18773
  return valueNotAvailable(searchKey);
@@ -18754,7 +18823,7 @@ const XLOOKUP = {
18754
18823
  const reverseSearch = _searchMode === -1;
18755
18824
  const index = _searchMode === 2 || _searchMode === -2
18756
18825
  ? dichotomicSearch(_lookupRange, searchKey, mode, _searchMode === 2 ? "asc" : "desc", rangeLen, getElement)
18757
- : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, reverseSearch);
18826
+ : linearSearch(_lookupRange, searchKey, mode, rangeLen, getElement, this.lookupCaches, reverseSearch);
18758
18827
  if (index !== -1) {
18759
18828
  return lookupDirection === "col"
18760
18829
  ? _returnRange.map((col) => [col[index]])
@@ -27624,7 +27693,7 @@ function forceUnicityOfFigure(data) {
27624
27693
  for (const sheet of data.sheets || []) {
27625
27694
  for (const figure of sheet.figures || []) {
27626
27695
  if (figureIds.has(figure.id)) {
27627
- figure.id += uuidGenerator.uuidv4();
27696
+ figure.id += uuidGenerator.smallUuid();
27628
27697
  }
27629
27698
  figureIds.add(figure.id);
27630
27699
  }
@@ -28209,9 +28278,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
28209
28278
  const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28210
28279
  let labels = labelValues.formattedValues;
28211
28280
  let dataSetsValues = getChartDatasetValues(getters, dataSets);
28212
- if (definition.dataSetsHaveTitle &&
28213
- dataSetsValues[0] &&
28214
- labels.length > dataSetsValues[0].data.length) {
28281
+ if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
28215
28282
  labels.shift();
28216
28283
  }
28217
28284
  ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
@@ -28244,7 +28311,7 @@ function getBarChartData(definition, dataSets, labelRange, getters) {
28244
28311
  }
28245
28312
  function getPyramidChartData(definition, dataSets, labelRange, getters) {
28246
28313
  const barChartData = getBarChartData(definition, dataSets, labelRange, getters);
28247
- const barDataset = barChartData.dataSetsValues;
28314
+ const barDataset = barChartData.dataSetsValues.filter((ds) => !ds.hidden);
28248
28315
  const pyramidDatasetValues = [];
28249
28316
  if (barDataset[0]) {
28250
28317
  const pyramidData = barDataset[0].data.map((value) => (value > 0 ? value : 0));
@@ -28260,13 +28327,12 @@ function getPyramidChartData(definition, dataSets, labelRange, getters) {
28260
28327
  };
28261
28328
  }
28262
28329
  function getLineChartData(definition, dataSets, labelRange, getters) {
28263
- const axisType = getChartAxisType(definition, labelRange, getters);
28330
+ const axisType = getChartAxisType(definition, dataSets, labelRange, getters);
28264
28331
  const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28265
28332
  let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
28266
28333
  let dataSetsValues = getChartDatasetValues(getters, dataSets);
28267
- if (definition.dataSetsHaveTitle &&
28268
- dataSetsValues[0] &&
28269
- labels.length > dataSetsValues[0].data.length) {
28334
+ const removeFirstLabel = shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false);
28335
+ if (removeFirstLabel) {
28270
28336
  labels.shift();
28271
28337
  }
28272
28338
  ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
@@ -28278,7 +28344,7 @@ function getLineChartData(definition, dataSets, labelRange, getters) {
28278
28344
  }
28279
28345
  const leftAxisFormat = getChartDatasetFormat(getters, dataSets, "left");
28280
28346
  const rightAxisFormat = getChartDatasetFormat(getters, dataSets, "right");
28281
- const labelsFormat = getChartLabelFormat(getters, labelRange);
28347
+ const labelsFormat = getChartLabelFormat(getters, labelRange, removeFirstLabel);
28282
28348
  const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat, x: labelsFormat };
28283
28349
  const trendDataSetsValues = [];
28284
28350
  for (const index in dataSetsValues) {
@@ -28314,9 +28380,7 @@ function getPieChartData(definition, dataSets, labelRange, getters) {
28314
28380
  const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28315
28381
  let labels = labelValues.formattedValues;
28316
28382
  let dataSetsValues = getChartDatasetValues(getters, dataSets);
28317
- if (definition.dataSetsHaveTitle &&
28318
- dataSetsValues[0] &&
28319
- labels.length > dataSetsValues[0].data.length) {
28383
+ if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
28320
28384
  labels.shift();
28321
28385
  }
28322
28386
  ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
@@ -28336,9 +28400,7 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
28336
28400
  const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28337
28401
  let labels = labelValues.formattedValues;
28338
28402
  let dataSetsValues = getChartDatasetValues(getters, dataSets);
28339
- if (definition.dataSetsHaveTitle &&
28340
- dataSetsValues[0] &&
28341
- labels.length > dataSetsValues[0].data.length) {
28403
+ if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
28342
28404
  labels.shift();
28343
28405
  }
28344
28406
  ({ labels, dataSetsValues } = filterInvalidDataPoints(labels, dataSetsValues));
@@ -28358,7 +28420,7 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
28358
28420
  function getGeoChartData(definition, dataSets, labelRange, getters) {
28359
28421
  const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28360
28422
  let labels = labelValues.formattedValues;
28361
- if (definition.dataSetsHaveTitle) {
28423
+ if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
28362
28424
  labels.shift();
28363
28425
  }
28364
28426
  let dataSetsValues = getChartDatasetValues(getters, dataSets);
@@ -28519,36 +28581,41 @@ function normalizeLabels(labels, newLabels, config) {
28519
28581
  }
28520
28582
  return { normalizedLabels, normalizedNewLabels };
28521
28583
  }
28522
- function getChartAxisType(chart, labelRange, getters) {
28523
- if (isDateChart(chart, labelRange, getters) && isLuxonTimeAdapterInstalled()) {
28584
+ function getChartAxisType(definition, dataSets, labelRange, getters) {
28585
+ if (isDateChart(definition, dataSets, labelRange, getters) && isLuxonTimeAdapterInstalled()) {
28524
28586
  return "time";
28525
28587
  }
28526
- if (isLinearChart(chart, labelRange, getters)) {
28588
+ if (isLinearChart(definition, dataSets, labelRange, getters)) {
28527
28589
  return "linear";
28528
28590
  }
28529
28591
  return "category";
28530
28592
  }
28531
- function isDateChart(definition, labelRange, getters) {
28532
- return !definition.labelsAsText && canBeDateChart(labelRange, getters);
28593
+ function isDateChart(definition, dataSets, labelRange, getters) {
28594
+ return !definition.labelsAsText && canBeDateChart(definition, dataSets, labelRange, getters);
28533
28595
  }
28534
- function isLinearChart(definition, labelRange, getters) {
28535
- return !definition.labelsAsText && canBeLinearChart(labelRange, getters);
28596
+ function isLinearChart(definition, dataSets, labelRange, getters) {
28597
+ return !definition.labelsAsText && canBeLinearChart(definition, dataSets, labelRange, getters);
28536
28598
  }
28537
- function canChartParseLabels(labelRange, getters) {
28538
- return canBeDateChart(labelRange, getters) || canBeLinearChart(labelRange, getters);
28599
+ function canChartParseLabels(definition, dataSets, labelRange, getters) {
28600
+ return (canBeDateChart(definition, dataSets, labelRange, getters) ||
28601
+ canBeLinearChart(definition, dataSets, labelRange, getters));
28539
28602
  }
28540
- function canBeDateChart(labelRange, getters) {
28541
- if (!labelRange || !canBeLinearChart(labelRange, getters)) {
28603
+ function canBeDateChart(definition, dataSets, labelRange, getters) {
28604
+ if (!labelRange || !canBeLinearChart(definition, dataSets, labelRange, getters)) {
28542
28605
  return false;
28543
28606
  }
28544
- const labelFormat = getChartLabelFormat(getters, labelRange);
28607
+ const removeFirstLabel = shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false);
28608
+ const labelFormat = getChartLabelFormat(getters, labelRange, removeFirstLabel);
28545
28609
  return Boolean(labelFormat && timeFormatLuxonCompatible.test(labelFormat));
28546
28610
  }
28547
- function canBeLinearChart(labelRange, getters) {
28611
+ function canBeLinearChart(definition, dataSets, labelRange, getters) {
28548
28612
  if (!labelRange) {
28549
28613
  return false;
28550
28614
  }
28551
28615
  const labels = getters.getRangeValues(labelRange);
28616
+ if (shouldRemoveFirstLabel(labelRange, dataSets[0], definition.dataSetsHaveTitle || false)) {
28617
+ labels.shift();
28618
+ }
28552
28619
  if (labels.some((label) => isNaN(Number(label)) && label)) {
28553
28620
  return false;
28554
28621
  }
@@ -28657,17 +28724,15 @@ function aggregateDataForLabels(labels, datasets) {
28657
28724
  })),
28658
28725
  };
28659
28726
  }
28660
- function getChartLabelFormat(getters, range) {
28727
+ function getChartLabelFormat(getters, range, shouldRemoveFirstLabel) {
28661
28728
  if (!range)
28662
28729
  return undefined;
28663
- const { sheetId, zone: { left, top, bottom }, } = range;
28664
- for (let row = top; row <= bottom; row++) {
28665
- const format = getters.getEvaluatedCell({ sheetId, col: left, row }).format;
28666
- if (format) {
28667
- return format;
28668
- }
28730
+ const { sheetId, zone } = range;
28731
+ const formats = positions(zone).map((position) => getters.getEvaluatedCell({ sheetId, ...position }).format);
28732
+ if (shouldRemoveFirstLabel) {
28733
+ formats.shift();
28669
28734
  }
28670
- return undefined;
28735
+ return formats.find((format) => format !== undefined);
28671
28736
  }
28672
28737
  function getChartLabelValues(getters, dataSets, labelRange) {
28673
28738
  let labels = { values: [], formattedValues: [] };
@@ -28723,10 +28788,8 @@ function getChartDatasetFormat(getters, allDataSets, axis) {
28723
28788
  function getChartDatasetValues(getters, dataSets) {
28724
28789
  const datasetValues = [];
28725
28790
  for (const [dsIndex, ds] of Object.entries(dataSets)) {
28726
- if (getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left)) {
28727
- continue;
28728
- }
28729
28791
  let label;
28792
+ let hidden = getters.isColHidden(ds.dataRange.sheetId, ds.dataRange.zone.left);
28730
28793
  if (ds.labelCell) {
28731
28794
  const labelRange = ds.labelCell;
28732
28795
  const cell = labelRange
@@ -28753,9 +28816,9 @@ function getChartDatasetValues(getters, dataSets) {
28753
28816
  data.fill(1);
28754
28817
  }
28755
28818
  else if (data.every((cell) => cell === undefined || cell === null || !isNumber(cell.toString(), DEFAULT_LOCALE))) {
28756
- continue;
28819
+ hidden = true;
28757
28820
  }
28758
- datasetValues.push({ data, label });
28821
+ datasetValues.push({ data, label, hidden });
28759
28822
  }
28760
28823
  return datasetValues;
28761
28824
  }
@@ -28766,12 +28829,13 @@ function getBarChartDatasets(definition, args) {
28766
28829
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28767
28830
  const trendDatasets = [];
28768
28831
  for (const index in dataSetsValues) {
28769
- let { label, data } = dataSetsValues[index];
28832
+ let { label, data, hidden } = dataSetsValues[index];
28770
28833
  label = definition.dataSets?.[index].label || label;
28771
28834
  const backgroundColor = colors.next();
28772
28835
  const dataset = {
28773
28836
  label,
28774
28837
  data,
28838
+ hidden,
28775
28839
  borderColor: definition.background || BACKGROUND_CHART_COLOR,
28776
28840
  borderWidth: definition.stacked ? 1 : 0,
28777
28841
  backgroundColor,
@@ -28804,6 +28868,9 @@ function getWaterfallDatasetAndLabels(definition, args) {
28804
28868
  const labelsWithSubTotals = [];
28805
28869
  let lastValue = 0;
28806
28870
  for (const dataSetsValue of dataSetsValues) {
28871
+ if (dataSetsValue.hidden) {
28872
+ continue;
28873
+ }
28807
28874
  for (let i = 0; i < dataSetsValue.data.length; i++) {
28808
28875
  const data = dataSetsValue.data[i];
28809
28876
  labelsWithSubTotals.push(labels[i]);
@@ -28839,7 +28906,7 @@ function getLineChartDatasets(definition, args) {
28839
28906
  const trendDatasets = [];
28840
28907
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28841
28908
  for (let index = 0; index < dataSetsValues.length; index++) {
28842
- let { label, data } = dataSetsValues[index];
28909
+ let { label, data, hidden } = dataSetsValues[index];
28843
28910
  label = definition.dataSets?.[index].label || label;
28844
28911
  const color = colors.next();
28845
28912
  if (axisType && ["linear", "time"].includes(axisType)) {
@@ -28849,6 +28916,7 @@ function getLineChartDatasets(definition, args) {
28849
28916
  const dataset = {
28850
28917
  label,
28851
28918
  data,
28919
+ hidden,
28852
28920
  tension: 0, // 0 -> render straight lines, which is much faster
28853
28921
  borderColor: color,
28854
28922
  backgroundColor: areaChart ? setColorAlpha(color, LINE_FILL_TRANSPARENCY) : color,
@@ -28881,11 +28949,13 @@ function getPieChartDatasets(definition, args) {
28881
28949
  const dataSets = [];
28882
28950
  const dataSetsLength = Math.max(0, ...dataSetsValues.map((ds) => ds?.data?.length ?? 0));
28883
28951
  const backgroundColor = getPieColors(new ColorGenerator(dataSetsLength), dataSetsValues);
28884
- for (const { label, data } of dataSetsValues) {
28952
+ for (const { label, data, hidden } of dataSetsValues) {
28953
+ if (hidden)
28954
+ continue;
28885
28955
  const dataset = {
28886
28956
  label,
28887
28957
  data,
28888
- borderColor: BACKGROUND_CHART_COLOR,
28958
+ borderColor: definition.background || "#FFFFFF",
28889
28959
  backgroundColor,
28890
28960
  hoverOffset: 30,
28891
28961
  };
@@ -28899,7 +28969,7 @@ function getComboChartDatasets(definition, args) {
28899
28969
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28900
28970
  const trendDatasets = [];
28901
28971
  for (let index = 0; index < dataSetsValues.length; index++) {
28902
- let { label, data } = dataSetsValues[index];
28972
+ let { label, data, hidden } = dataSetsValues[index];
28903
28973
  label = definition.dataSets?.[index].label || label;
28904
28974
  const design = definition.dataSets?.[index];
28905
28975
  const color = colors.next();
@@ -28907,6 +28977,7 @@ function getComboChartDatasets(definition, args) {
28907
28977
  const dataset = {
28908
28978
  label: label,
28909
28979
  data,
28980
+ hidden,
28910
28981
  borderColor: color,
28911
28982
  backgroundColor: color,
28912
28983
  yAxisID: definition.dataSets?.[index].yAxisId || "y",
@@ -28931,7 +29002,7 @@ function getRadarChartDatasets(definition, args) {
28931
29002
  const fill = definition.fillArea ?? false;
28932
29003
  const colors = getChartColorsGenerator(definition, dataSetsValues.length);
28933
29004
  for (let i = 0; i < dataSetsValues.length; i++) {
28934
- let { label, data } = dataSetsValues[i];
29005
+ let { label, data, hidden } = dataSetsValues[i];
28935
29006
  if (definition.dataSets?.[i]?.label) {
28936
29007
  label = definition.dataSets[i].label;
28937
29008
  }
@@ -28939,6 +29010,7 @@ function getRadarChartDatasets(definition, args) {
28939
29010
  const dataset = {
28940
29011
  label,
28941
29012
  data,
29013
+ hidden,
28942
29014
  borderColor,
28943
29015
  backgroundColor: borderColor,
28944
29016
  };
@@ -29084,6 +29156,11 @@ function getPieChartLegend(definition, args) {
29084
29156
  hidden: false,
29085
29157
  lineWidth: 2,
29086
29158
  })),
29159
+ filter: (legendItem, data) => {
29160
+ return "datasetIndex" in legendItem
29161
+ ? !data.datasets[legendItem.datasetIndex].hidden
29162
+ : true;
29163
+ },
29087
29164
  },
29088
29165
  };
29089
29166
  }
@@ -29145,6 +29222,11 @@ function getWaterfallChartLegend(definition, args) {
29145
29222
  }
29146
29223
  return legendValues;
29147
29224
  },
29225
+ filter: (legendItem, data) => {
29226
+ return "datasetIndex" in legendItem
29227
+ ? !data.datasets[legendItem.datasetIndex].hidden
29228
+ : true;
29229
+ },
29148
29230
  },
29149
29231
  onClick: () => { }, // Disables click interaction with the waterfall chart legend items
29150
29232
  };
@@ -29228,6 +29310,11 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
29228
29310
  ...legendLabelConfig,
29229
29311
  };
29230
29312
  }),
29313
+ filter: (legendItem, data) => {
29314
+ return "datasetIndex" in legendItem
29315
+ ? !data.datasets[legendItem.datasetIndex].hidden
29316
+ : true;
29317
+ },
29231
29318
  },
29232
29319
  };
29233
29320
  }
@@ -32900,7 +32987,7 @@ const linkSheet = {
32900
32987
  const deleteSheet = {
32901
32988
  name: _t("Delete"),
32902
32989
  isVisible: (env) => {
32903
- return env.model.getters.getSheetIds().length > 1;
32990
+ return env.model.getters.getVisibleSheetIds().length > 1;
32904
32991
  },
32905
32992
  execute: (env) => env.askConfirmation(_t("Are you sure you want to delete this sheet?"), () => {
32906
32993
  env.model.dispatch("DELETE_SHEET", { sheetId: env.model.getters.getActiveSheetId() });
@@ -32911,7 +32998,7 @@ const duplicateSheet = {
32911
32998
  name: _t("Duplicate"),
32912
32999
  execute: (env) => {
32913
33000
  const sheetIdFrom = env.model.getters.getActiveSheetId();
32914
- const sheetIdTo = env.model.uuidGenerator.uuidv4();
33001
+ const sheetIdTo = env.model.uuidGenerator.smallUuid();
32915
33002
  env.model.dispatch("DUPLICATE_SHEET", {
32916
33003
  sheetId: sheetIdFrom,
32917
33004
  sheetIdTo,
@@ -33017,6 +33104,100 @@ function* iterateChildren(el) {
33017
33104
  function getOpenedMenus() {
33018
33105
  return Array.from(document.querySelectorAll(".o-spreadsheet .o-menu"));
33019
33106
  }
33107
+ function getCurrentSelection(el) {
33108
+ let { startElement, endElement, startSelectionOffset, endSelectionOffset } = getStartAndEndSelection(el);
33109
+ let startSizeBefore = findSelectionIndex(el, startElement, startSelectionOffset);
33110
+ let endSizeBefore = findSelectionIndex(el, endElement, endSelectionOffset);
33111
+ return {
33112
+ start: startSizeBefore,
33113
+ end: endSizeBefore,
33114
+ };
33115
+ }
33116
+ function getStartAndEndSelection(el) {
33117
+ const selection = document.getSelection();
33118
+ return {
33119
+ startElement: selection.anchorNode || el,
33120
+ startSelectionOffset: selection.anchorOffset,
33121
+ endElement: selection.focusNode || el,
33122
+ endSelectionOffset: selection.focusOffset,
33123
+ };
33124
+ }
33125
+ /**
33126
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
33127
+ * The selected node is either a Text node or an Element node.
33128
+ *
33129
+ * case 1 -Text node:
33130
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
33131
+ * content length of all previous nodes.
33132
+ *
33133
+ * case 2 - Element node:
33134
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
33135
+ * all the nodes prior to the selected node as well as the content of the child node before the offset.
33136
+ *
33137
+ * See the MDN documentation for more details.
33138
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
33139
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
33140
+ *
33141
+ */
33142
+ function findSelectionIndex(el, nodeToFind, nodeOffset) {
33143
+ let usedCharacters = 0;
33144
+ let it = iterateChildren(el);
33145
+ let current = it.next();
33146
+ let isFirstParagraph = true;
33147
+ while (!current.done && current.value !== nodeToFind) {
33148
+ if (!current.value.hasChildNodes()) {
33149
+ if (current.value.textContent) {
33150
+ usedCharacters += current.value.textContent.length;
33151
+ }
33152
+ }
33153
+ // One new paragraph = one new line character, except for the first paragraph
33154
+ if (current.value.nodeName === "P" ||
33155
+ (current.value.nodeName === "DIV" && current.value !== el) // On paste, the HTML may contain <div> instead of <p>
33156
+ ) {
33157
+ if (isFirstParagraph) {
33158
+ isFirstParagraph = false;
33159
+ }
33160
+ else {
33161
+ usedCharacters++;
33162
+ }
33163
+ }
33164
+ current = it.next();
33165
+ }
33166
+ if (current.value !== nodeToFind) {
33167
+ /** This situation can happen if the code is called while the selection is not currently on the element.
33168
+ * In this case, we return 0 because we don't know the size of the text before the selection.
33169
+ *
33170
+ * A known occurrence is triggered since the introduction of commit d4663158 (PR #2038).
33171
+ */
33172
+ return 0;
33173
+ }
33174
+ else {
33175
+ if (!current.value.hasChildNodes()) {
33176
+ usedCharacters += nodeOffset;
33177
+ }
33178
+ else {
33179
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
33180
+ usedCharacters += children.reduce((acc, child, index) => {
33181
+ if (child.textContent !== null) {
33182
+ // need to account for paragraph nodes that implicitly add a new line
33183
+ // except for the last paragraph
33184
+ let chars = child.textContent.length;
33185
+ if (child.nodeName === "P" && index !== children.length - 1) {
33186
+ chars++;
33187
+ }
33188
+ return acc + chars;
33189
+ }
33190
+ else {
33191
+ return acc;
33192
+ }
33193
+ }, 0);
33194
+ }
33195
+ }
33196
+ if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
33197
+ usedCharacters++;
33198
+ }
33199
+ return usedCharacters;
33200
+ }
33020
33201
  const letterRegex = /^[a-zA-Z]$/;
33021
33202
  /**
33022
33203
  * Transform a keyboard event into a shortcut string that represent this event. The letters keys will be uppercased.
@@ -33614,20 +33795,21 @@ function getSmartChartDefinition(zone, getters) {
33614
33795
  }
33615
33796
  // Only display legend for several datasets.
33616
33797
  const newLegendPos = dataSetZone.right === dataSetZone.left ? "none" : "top";
33617
- const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
33618
- if (canChartParseLabels(labelRange, getters)) {
33619
- return {
33620
- title: {},
33621
- dataSets,
33622
- labelsAsText: false,
33623
- stacked: false,
33624
- aggregated: false,
33625
- cumulative: false,
33626
- labelRange: labelRangeXc,
33627
- type: "line",
33628
- dataSetsHaveTitle,
33629
- legendPosition: newLegendPos,
33630
- };
33798
+ const lineChartDefinition = {
33799
+ title: {},
33800
+ dataSets,
33801
+ labelsAsText: false,
33802
+ stacked: false,
33803
+ aggregated: false,
33804
+ cumulative: false,
33805
+ labelRange: labelRangeXc,
33806
+ type: "line",
33807
+ dataSetsHaveTitle,
33808
+ legendPosition: newLegendPos,
33809
+ };
33810
+ const chart = new LineChart(lineChartDefinition, sheetId, getters);
33811
+ if (canChartParseLabels(lineChartDefinition, chart.dataSets, chart.labelRange, getters)) {
33812
+ return lineChartDefinition;
33631
33813
  }
33632
33814
  const _dataSets = createDataSets(getters, dataSets, sheetId, dataSetsHaveTitle);
33633
33815
  if (singleColumn &&
@@ -34041,7 +34223,7 @@ const HIDE_ROWS_NAME = (env) => {
34041
34223
  //------------------------------------------------------------------------------
34042
34224
  const CREATE_CHART = (env) => {
34043
34225
  const getters = env.model.getters;
34044
- const id = env.model.uuidGenerator.uuidv4();
34226
+ const id = env.model.uuidGenerator.smallUuid();
34045
34227
  const sheetId = getters.getActiveSheetId();
34046
34228
  if (getZoneArea(env.model.getters.getSelectedZone()) === 1) {
34047
34229
  env.model.selection.selectTableAroundSelection();
@@ -34064,8 +34246,8 @@ const CREATE_CHART = (env) => {
34064
34246
  // Pivots
34065
34247
  //------------------------------------------------------------------------------
34066
34248
  const CREATE_PIVOT = (env) => {
34067
- const pivotId = env.model.uuidGenerator.uuidv4();
34068
- const newSheetId = env.model.uuidGenerator.uuidv4();
34249
+ const pivotId = env.model.uuidGenerator.smallUuid();
34250
+ const newSheetId = env.model.uuidGenerator.smallUuid();
34069
34251
  const result = env.model.dispatch("INSERT_NEW_PIVOT", { pivotId, newSheetId });
34070
34252
  if (result.isSuccessful) {
34071
34253
  env.openSidePanel("PivotSidePanel", { pivotId });
@@ -34124,7 +34306,7 @@ async function requestImage(env) {
34124
34306
  const CREATE_IMAGE = async (env) => {
34125
34307
  if (env.imageProvider) {
34126
34308
  const sheetId = env.model.getters.getActiveSheetId();
34127
- const figureId = env.model.uuidGenerator.uuidv4();
34309
+ const figureId = env.model.uuidGenerator.smallUuid();
34128
34310
  const image = await requestImage(env);
34129
34311
  if (!image) {
34130
34312
  throw new Error("No image provider was given to the environment");
@@ -34677,7 +34859,7 @@ const insertCheckbox = {
34677
34859
  ranges,
34678
34860
  sheetId,
34679
34861
  rule: {
34680
- id: env.model.uuidGenerator.uuidv4(),
34862
+ id: env.model.uuidGenerator.smallUuid(),
34681
34863
  criterion: {
34682
34864
  type: "isBoolean",
34683
34865
  values: [],
@@ -34693,7 +34875,7 @@ const insertDropdown = {
34693
34875
  const zones = env.model.getters.getSelectedZones();
34694
34876
  const sheetId = env.model.getters.getActiveSheetId();
34695
34877
  const ranges = zones.map((zone) => env.model.getters.getRangeDataFromZone(sheetId, zone));
34696
- const ruleID = env.model.uuidGenerator.uuidv4();
34878
+ const ruleID = env.model.uuidGenerator.smallUuid();
34697
34879
  env.model.dispatch("ADD_DATA_VALIDATION_RULE", {
34698
34880
  ranges,
34699
34881
  sheetId,
@@ -34724,7 +34906,7 @@ const insertSheet = {
34724
34906
  execute: (env) => {
34725
34907
  const activeSheetId = env.model.getters.getActiveSheetId();
34726
34908
  const position = env.model.getters.getSheetIds().indexOf(activeSheetId) + 1;
34727
- const sheetId = env.model.uuidGenerator.uuidv4();
34909
+ const sheetId = env.model.uuidGenerator.smallUuid();
34728
34910
  env.model.dispatch("CREATE_SHEET", { sheetId, position });
34729
34911
  env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: sheetId });
34730
34912
  },
@@ -38251,7 +38433,7 @@ css /* scss */ `
38251
38433
  .o-font-size-editor {
38252
38434
  height: calc(100% - 4px);
38253
38435
  input.o-font-size {
38254
- outline-color: ${SELECTION_BORDER_COLOR};
38436
+ outline: none;
38255
38437
  height: 20px;
38256
38438
  width: 23px;
38257
38439
  }
@@ -39213,7 +39395,7 @@ class LineConfigPanel extends GenericChartConfigPanel {
39213
39395
  get canTreatLabelsAsText() {
39214
39396
  const chart = this.env.model.getters.getChart(this.props.figureId);
39215
39397
  if (chart && chart instanceof LineChart) {
39216
- return canChartParseLabels(chart.labelRange, this.env.model.getters);
39398
+ return canChartParseLabels(chart.getDefinition(), chart.dataSets, chart.labelRange, this.env.model.getters);
39217
39399
  }
39218
39400
  return false;
39219
39401
  }
@@ -39290,7 +39472,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
39290
39472
  get canTreatLabelsAsText() {
39291
39473
  const chart = this.env.model.getters.getChart(this.props.figureId);
39292
39474
  if (chart && chart instanceof ScatterChart) {
39293
- return canChartParseLabels(chart.labelRange, this.env.model.getters);
39475
+ return canChartParseLabels(chart.getDefinition(), chart.dataSets, chart.labelRange, this.env.model.getters);
39294
39476
  }
39295
39477
  return false;
39296
39478
  }
@@ -39864,6 +40046,10 @@ class ContentEditableHelper {
39864
40046
  if (currentStart === start && currentEnd === end) {
39865
40047
  return;
39866
40048
  }
40049
+ if (selection.rangeCount === 0) {
40050
+ const range = document.createRange();
40051
+ selection.addRange(range);
40052
+ }
39867
40053
  const currentRange = selection.getRangeAt(0);
39868
40054
  let range;
39869
40055
  if (this.el.contains(currentRange.startContainer)) {
@@ -39891,8 +40077,16 @@ class ContentEditableHelper {
39891
40077
  }
39892
40078
  let startNode = this.findChildAtCharacterIndex(start);
39893
40079
  let endNode = this.findChildAtCharacterIndex(end);
39894
- range.setStart(startNode.node, startNode.offset);
39895
- range.setEnd(endNode.node, endNode.offset);
40080
+ // setEnd (setStart) will result in a collapsed range if the end point is before the start point
40081
+ // https://developer.mozilla.org/en-US/docs/Web/API/Range/setEnd
40082
+ if (start <= end) {
40083
+ range.setStart(startNode.node, startNode.offset);
40084
+ range.setEnd(endNode.node, endNode.offset);
40085
+ }
40086
+ else {
40087
+ range.setStart(endNode.node, endNode.offset);
40088
+ range.setEnd(startNode.node, startNode.offset);
40089
+ }
39896
40090
  }
39897
40091
  }
39898
40092
  /**
@@ -40026,7 +40220,7 @@ class ContentEditableHelper {
40026
40220
  if (!focusedNode || !this.el.contains(focusedNode))
40027
40221
  return;
40028
40222
  const element = focusedNode instanceof HTMLElement ? focusedNode : focusedNode.parentElement;
40029
- element?.scrollIntoView({ block: "nearest" });
40223
+ element?.scrollIntoView?.({ block: "nearest" });
40030
40224
  }
40031
40225
  /**
40032
40226
  * remove the current selection of the user
@@ -40046,100 +40240,7 @@ class ContentEditableHelper {
40046
40240
  * finds the indexes of the current selection.
40047
40241
  * */
40048
40242
  getCurrentSelection() {
40049
- let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();
40050
- let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
40051
- let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
40052
- return {
40053
- start: startSizeBefore,
40054
- end: endSizeBefore,
40055
- };
40056
- }
40057
- /**
40058
- * Computes the text 'index' inside this.el based on the currently selected node and its offset.
40059
- * The selected node is either a Text node or an Element node.
40060
- *
40061
- * case 1 -Text node:
40062
- * the offset is the number of characters from the start of the node. We have to add this offset to the
40063
- * content length of all previous nodes.
40064
- *
40065
- * case 2 - Element node:
40066
- * the offset is the number of child nodes before the selected node. We have to add the content length of
40067
- * all the bnodes prior to the selected node as well as the content of the child node before the offset.
40068
- *
40069
- * See the MDN documentation for more details.
40070
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
40071
- * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
40072
- *
40073
- */
40074
- findSelectionIndex(nodeToFind, nodeOffset) {
40075
- let usedCharacters = 0;
40076
- let it = iterateChildren(this.el);
40077
- let current = it.next();
40078
- let isFirstParagraph = true;
40079
- while (!current.done && current.value !== nodeToFind) {
40080
- if (!current.value.hasChildNodes()) {
40081
- if (current.value.textContent) {
40082
- usedCharacters += current.value.textContent.length;
40083
- }
40084
- }
40085
- // One new paragraph = one new line character, except for the first paragraph
40086
- if (current.value.nodeName === "P" ||
40087
- (current.value.nodeName === "DIV" && current.value !== this.el) // On paste, the HTML may contain <div> instead of <p>
40088
- ) {
40089
- if (isFirstParagraph) {
40090
- isFirstParagraph = false;
40091
- }
40092
- else {
40093
- usedCharacters++;
40094
- }
40095
- }
40096
- current = it.next();
40097
- }
40098
- if (current.value !== nodeToFind) {
40099
- /** This situation can happen if the code is called while the selection is not currently on the ContentEditableHelper.
40100
- * In this case, we return 0 because we don't know the size of the text before the selection.
40101
- *
40102
- * A known occurence is triggered since the introduction of commit d4663158 (PR #2038).
40103
- *
40104
- * FIXME: find a way to test eventhough the selection API is not available in jsDOM.
40105
- */
40106
- return 0;
40107
- }
40108
- else {
40109
- if (!current.value.hasChildNodes()) {
40110
- usedCharacters += nodeOffset;
40111
- }
40112
- else {
40113
- const children = [...current.value.childNodes].slice(0, nodeOffset);
40114
- usedCharacters += children.reduce((acc, child, index) => {
40115
- if (child.textContent !== null) {
40116
- // need to account for paragraph nodes that implicitely add a new line
40117
- // except for the last paragraph
40118
- let chars = child.textContent.length;
40119
- if (child.nodeName === "P" && index !== children.length - 1) {
40120
- chars++;
40121
- }
40122
- return acc + chars;
40123
- }
40124
- else {
40125
- return acc;
40126
- }
40127
- }, 0);
40128
- }
40129
- }
40130
- if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent === "") {
40131
- usedCharacters++;
40132
- }
40133
- return usedCharacters;
40134
- }
40135
- getStartAndEndSelection() {
40136
- const selection = document.getSelection();
40137
- return {
40138
- startElement: selection.anchorNode || this.el,
40139
- startSelectionOffset: selection.anchorOffset,
40140
- endElement: selection.focusNode || this.el,
40141
- endSelectionOffset: selection.focusOffset,
40142
- };
40243
+ return getCurrentSelection(this.el);
40143
40244
  }
40144
40245
  getText() {
40145
40246
  let text = "";
@@ -42068,7 +42169,7 @@ class ConditionalFormattingPanel extends owl.Component {
42068
42169
  this.originalEditedCf = undefined;
42069
42170
  }
42070
42171
  addConditionalFormat() {
42071
- const cfId = this.env.model.uuidGenerator.uuidv4();
42172
+ const cfId = this.env.model.uuidGenerator.smallUuid();
42072
42173
  this.env.model.dispatch("ADD_CONDITIONAL_FORMAT", {
42073
42174
  sheetId: this.activeSheetId,
42074
42175
  ranges: this.env.model.getters
@@ -43338,7 +43439,7 @@ class DataValidationEditor extends owl.Component {
43338
43439
  .getSelectedZones()
43339
43440
  .map((zone) => zoneToXc(this.env.model.getters.getUnboundedZone(sheetId, zone)));
43340
43441
  return {
43341
- id: this.env.model.uuidGenerator.uuidv4(),
43442
+ id: this.env.model.uuidGenerator.smallUuid(),
43342
43443
  criterion: { type: "textContains", values: [""] },
43343
43444
  ranges,
43344
43445
  };
@@ -44956,8 +45057,8 @@ class PivotTitleSection extends owl.Component {
44956
45057
  return this.env.model.getters.getPivotDisplayName(this.props.pivotId);
44957
45058
  }
44958
45059
  duplicatePivot() {
44959
- const newPivotId = this.env.model.uuidGenerator.uuidv4();
44960
- const newSheetId = this.env.model.uuidGenerator.uuidv4();
45060
+ const newPivotId = this.env.model.uuidGenerator.smallUuid();
45061
+ const newSheetId = this.env.model.uuidGenerator.smallUuid();
44961
45062
  const result = this.env.model.dispatch("DUPLICATE_PIVOT_IN_NEW_SHEET", {
44962
45063
  pivotId: this.props.pivotId,
44963
45064
  newPivotId,
@@ -47582,7 +47683,7 @@ class TableStyleEditorPanel extends owl.Component {
47582
47683
  this.state.selectedTemplateName = templateName;
47583
47684
  }
47584
47685
  onConfirm() {
47585
- const tableStyleId = this.props.styleId || this.env.model.uuidGenerator.uuidv4();
47686
+ const tableStyleId = this.props.styleId || this.env.model.uuidGenerator.smallUuid();
47586
47687
  this.env.model.dispatch("CREATE_TABLE_STYLE", {
47587
47688
  tableStyleId,
47588
47689
  tableStyleName: this.state.styleName,
@@ -53094,6 +53195,10 @@ class CellPlugin extends CorePlugin {
53094
53195
  return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
53095
53196
  case "CLEAR_CELL":
53096
53197
  return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
53198
+ case "UPDATE_CELL_POSITION":
53199
+ return !cmd.cellId || this.cells[cmd.sheetId]?.[cmd.cellId]
53200
+ ? "Success" /* CommandResult.Success */
53201
+ : "InvalidCellId" /* CommandResult.InvalidCellId */;
53097
53202
  default:
53098
53203
  return "Success" /* CommandResult.Success */;
53099
53204
  }
@@ -53138,6 +53243,9 @@ class CellPlugin extends CorePlugin {
53138
53243
  case "DELETE_CONTENT":
53139
53244
  this.clearZones(cmd.sheetId, cmd.target);
53140
53245
  break;
53246
+ case "DELETE_SHEET": {
53247
+ this.history.update("cells", cmd.sheetId, undefined);
53248
+ }
53141
53249
  }
53142
53250
  }
53143
53251
  clearZones(sheetId, zones) {
@@ -53928,6 +54036,9 @@ class ConditionalFormatPlugin extends CorePlugin {
53928
54036
  allowDispatch(cmd) {
53929
54037
  switch (cmd.type) {
53930
54038
  case "ADD_CONDITIONAL_FORMAT":
54039
+ if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
54040
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
54041
+ }
53931
54042
  return this.checkValidations(cmd, this.checkCFRule, this.checkEmptyRange, this.checkCFHasChanged);
53932
54043
  case "CHANGE_CONDITIONAL_FORMAT_PRIORITY":
53933
54044
  return this.checkValidPriorityChange(cmd.cfId, cmd.delta, cmd.sheetId);
@@ -54344,8 +54455,17 @@ class DataValidationPlugin extends CorePlugin {
54344
54455
  allowDispatch(cmd) {
54345
54456
  switch (cmd.type) {
54346
54457
  case "ADD_DATA_VALIDATION_RULE":
54458
+ if (!this.getters.tryGetSheet(cmd.sheetId)) {
54459
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
54460
+ }
54461
+ if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
54462
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
54463
+ }
54347
54464
  return this.checkValidations(cmd, this.chainValidations(this.checkEmptyRange, this.checkValidRange, this.checkCriterionTypeIsValid, this.checkCriterionHasValidNumberOfValues, this.checkCriterionValuesAreValid));
54348
54465
  case "REMOVE_DATA_VALIDATION_RULE":
54466
+ if (!this.getters.tryGetSheet(cmd.sheetId)) {
54467
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
54468
+ }
54349
54469
  if (!this.rules[cmd.sheetId].find((rule) => rule.id === cmd.id)) {
54350
54470
  return "UnknownDataValidationRule" /* CommandResult.UnknownDataValidationRule */;
54351
54471
  }
@@ -54572,6 +54692,7 @@ class DataValidationPlugin extends CorePlugin {
54572
54692
  class FigurePlugin extends CorePlugin {
54573
54693
  static getters = ["getFigures", "getFigure", "getFigureSheetId"];
54574
54694
  figures = {};
54695
+ insertionOrders = []; // TODO use a list in master
54575
54696
  // ---------------------------------------------------------------------------
54576
54697
  // Command Handling
54577
54698
  // ---------------------------------------------------------------------------
@@ -54674,11 +54795,14 @@ class FigurePlugin extends CorePlugin {
54674
54795
  }
54675
54796
  addFigure(figure, sheetId) {
54676
54797
  this.history.update("figures", sheetId, figure.id, figure);
54798
+ this.history.update("insertionOrders", this.insertionOrders.length, figure.id);
54677
54799
  }
54678
54800
  deleteSheet(sheetId) {
54801
+ this.history.update("insertionOrders", this.insertionOrders.filter((id) => !this.figures[sheetId]?.[id]));
54679
54802
  this.history.update("figures", sheetId, undefined);
54680
54803
  }
54681
54804
  removeFigure(id, sheetId) {
54805
+ this.history.update("insertionOrders", this.insertionOrders.filter((figureId) => figureId !== id));
54682
54806
  this.history.update("figures", sheetId, id, undefined);
54683
54807
  }
54684
54808
  checkFigureExists(sheetId, figureId) {
@@ -54697,7 +54821,14 @@ class FigurePlugin extends CorePlugin {
54697
54821
  // Getters
54698
54822
  // ---------------------------------------------------------------------------
54699
54823
  getFigures(sheetId) {
54700
- return Object.values(this.figures[sheetId] || {}).filter(isDefined);
54824
+ const figures = [];
54825
+ for (const figureId of this.insertionOrders) {
54826
+ const figure = this.figures[sheetId]?.[figureId];
54827
+ if (figure) {
54828
+ figures.push(figure);
54829
+ }
54830
+ }
54831
+ return figures;
54701
54832
  }
54702
54833
  getFigure(sheetId, figureId) {
54703
54834
  return this.figures[sheetId]?.[figureId];
@@ -54710,11 +54841,9 @@ class FigurePlugin extends CorePlugin {
54710
54841
  // ---------------------------------------------------------------------------
54711
54842
  import(data) {
54712
54843
  for (let sheet of data.sheets) {
54713
- const figures = {};
54714
- sheet.figures.forEach((figure) => {
54715
- figures[figure.id] = figure;
54716
- });
54717
- this.figures[sheet.id] = figures;
54844
+ for (const figure of sheet.figures) {
54845
+ this.addFigure(figure, sheet.id);
54846
+ }
54718
54847
  }
54719
54848
  }
54720
54849
  export(data) {
@@ -56140,6 +56269,9 @@ class SheetPlugin extends CorePlugin {
56140
56269
  case "CREATE_SHEET": {
56141
56270
  return this.checkValidations(cmd, this.checkSheetName, this.checkSheetPosition);
56142
56271
  }
56272
+ case "DUPLICATE_SHEET": {
56273
+ return this.sheets[cmd.sheetIdTo] ? "DuplicatedSheetId" /* CommandResult.DuplicatedSheetId */ : "Success" /* CommandResult.Success */;
56274
+ }
56143
56275
  case "MOVE_SHEET":
56144
56276
  try {
56145
56277
  const currentIndex = this.orderedSheetIds.findIndex((id) => id === cmd.sheetId);
@@ -56156,7 +56288,7 @@ class SheetPlugin extends CorePlugin {
56156
56288
  ? "Success" /* CommandResult.Success */
56157
56289
  : "InvalidColor" /* CommandResult.InvalidColor */;
56158
56290
  case "DELETE_SHEET":
56159
- return this.orderedSheetIds.length > 1
56291
+ return this.getVisibleSheetIds().length > 1
56160
56292
  ? "Success" /* CommandResult.Success */
56161
56293
  : "NotEnoughSheets" /* CommandResult.NotEnoughSheets */;
56162
56294
  case "ADD_COLUMNS_ROWS":
@@ -56951,6 +57083,10 @@ class SheetPlugin extends CorePlugin {
56951
57083
  checkZonesAreInSheet(cmd) {
56952
57084
  if (!("sheetId" in cmd))
56953
57085
  return "Success" /* CommandResult.Success */;
57086
+ if ("ranges" in cmd &&
57087
+ cmd.ranges.some((rangeData) => rangeData._sheetId !== "" && !this.getters.tryGetSheet(rangeData._sheetId))) {
57088
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
57089
+ }
56954
57090
  return this.checkZonesExistInSheet(cmd.sheetId, this.getCommandZones(cmd));
56955
57091
  }
56956
57092
  }
@@ -56959,6 +57095,7 @@ let nextTableId = 1;
56959
57095
  class TablePlugin extends CorePlugin {
56960
57096
  static getters = ["getCoreTable", "getCoreTables", "getCoreTableMatchingTopLeft"];
56961
57097
  tables = {};
57098
+ insertionOrders = {};
56962
57099
  adaptRanges(applyChange, sheetId) {
56963
57100
  const sheetIds = sheetId ? [sheetId] : this.getters.getSheetIds();
56964
57101
  for (const sheetId of sheetIds) {
@@ -56970,6 +57107,9 @@ class TablePlugin extends CorePlugin {
56970
57107
  allowDispatch(cmd) {
56971
57108
  switch (cmd.type) {
56972
57109
  case "CREATE_TABLE":
57110
+ if (cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId) || rangeData._sheetId !== cmd.sheetId)) {
57111
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
57112
+ }
56973
57113
  const zones = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone);
56974
57114
  if (!areZonesContinuous(zones)) {
56975
57115
  return "NonContinuousTargets" /* CommandResult.NonContinuousTargets */;
@@ -57000,11 +57140,13 @@ class TablePlugin extends CorePlugin {
57000
57140
  switch (cmd.type) {
57001
57141
  case "CREATE_SHEET":
57002
57142
  this.history.update("tables", cmd.sheetId, {});
57143
+ this.history.update("insertionOrders", cmd.sheetId, []);
57003
57144
  break;
57004
57145
  case "DELETE_SHEET": {
57005
57146
  const tables = { ...this.tables };
57006
57147
  delete tables[cmd.sheetId];
57007
57148
  this.history.update("tables", tables);
57149
+ this.history.update("insertionOrders", cmd.sheetId, undefined);
57008
57150
  break;
57009
57151
  }
57010
57152
  case "DUPLICATE_SHEET": {
@@ -57016,6 +57158,9 @@ class TablePlugin extends CorePlugin {
57016
57158
  : this.copyStaticTableForSheet(cmd.sheetIdTo, table);
57017
57159
  }
57018
57160
  this.history.update("tables", cmd.sheetIdTo, newTables);
57161
+ this.history.update("insertionOrders", cmd.sheetIdTo, [
57162
+ ...(this.insertionOrders[cmd.sheetId] ?? []),
57163
+ ]);
57019
57164
  break;
57020
57165
  }
57021
57166
  case "CREATE_TABLE": {
@@ -57029,6 +57174,10 @@ class TablePlugin extends CorePlugin {
57029
57174
  ? this.createDynamicTable(id, union, config)
57030
57175
  : this.createStaticTable(id, cmd.tableType, union, config);
57031
57176
  this.history.update("tables", cmd.sheetId, newTable.id, newTable);
57177
+ this.history.update("insertionOrders", cmd.sheetId, [
57178
+ ...(this.insertionOrders[cmd.sheetId] ?? []),
57179
+ newTable.id,
57180
+ ]);
57032
57181
  break;
57033
57182
  }
57034
57183
  case "REMOVE_TABLE": {
@@ -57039,6 +57188,7 @@ class TablePlugin extends CorePlugin {
57039
57188
  }
57040
57189
  }
57041
57190
  this.history.update("tables", cmd.sheetId, tables);
57191
+ this.history.update("insertionOrders", cmd.sheetId, this.insertionOrders[cmd.sheetId]?.filter((id) => id in tables));
57042
57192
  break;
57043
57193
  }
57044
57194
  case "UPDATE_TABLE": {
@@ -57074,7 +57224,14 @@ class TablePlugin extends CorePlugin {
57074
57224
  }
57075
57225
  }
57076
57226
  getCoreTables(sheetId) {
57077
- return this.tables[sheetId] ? Object.values(this.tables[sheetId]).filter(isDefined) : [];
57227
+ const tables = [];
57228
+ for (const tableId of this.insertionOrders[sheetId] || []) {
57229
+ const table = this.tables[sheetId][tableId];
57230
+ if (table) {
57231
+ tables.push(table);
57232
+ }
57233
+ }
57234
+ return tables;
57078
57235
  }
57079
57236
  getCoreTable({ sheetId, col, row }) {
57080
57237
  return this.getCoreTables(sheetId).find((table) => isInside(col, row, table.range.zone));
@@ -57357,6 +57514,7 @@ class TablePlugin extends CorePlugin {
57357
57514
  // ---------------------------------------------------------------------------
57358
57515
  import(data) {
57359
57516
  for (const sheet of data.sheets) {
57517
+ const tableIds = [];
57360
57518
  for (const tableData of sheet.tables || []) {
57361
57519
  const uuid = `${nextTableId++}`;
57362
57520
  const tableConfig = tableData.config || DEFAULT_TABLE_CONFIG;
@@ -57366,7 +57524,9 @@ class TablePlugin extends CorePlugin {
57366
57524
  ? this.createDynamicTable(uuid, range, tableConfig)
57367
57525
  : this.createStaticTable(uuid, tableType, range, tableConfig);
57368
57526
  this.history.update("tables", sheet.id, table.id, table);
57527
+ tableIds.push(table.id);
57369
57528
  }
57529
+ this.history.update("insertionOrders", sheet.id, tableIds);
57370
57530
  }
57371
57531
  }
57372
57532
  export(data) {
@@ -57406,7 +57566,10 @@ class HeaderGroupingPlugin extends CorePlugin {
57406
57566
  allowDispatch(cmd) {
57407
57567
  switch (cmd.type) {
57408
57568
  case "GROUP_HEADERS": {
57409
- const { start, end } = cmd;
57569
+ const { start, end, sheetId } = cmd;
57570
+ if (!this.getters.tryGetSheet(sheetId)) {
57571
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
57572
+ }
57410
57573
  if (!this.getters.doesHeadersExist(cmd.sheetId, cmd.dimension, [start, end])) {
57411
57574
  return "InvalidHeaderGroupStartEnd" /* CommandResult.InvalidHeaderGroupStartEnd */;
57412
57575
  }
@@ -57419,7 +57582,10 @@ class HeaderGroupingPlugin extends CorePlugin {
57419
57582
  break;
57420
57583
  }
57421
57584
  case "UNGROUP_HEADERS": {
57422
- const { start, end } = cmd;
57585
+ const { start, end, sheetId } = cmd;
57586
+ if (!this.getters.tryGetSheet(sheetId)) {
57587
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
57588
+ }
57423
57589
  if (!this.getters.doesHeadersExist(cmd.sheetId, cmd.dimension, [start, end])) {
57424
57590
  return "InvalidHeaderGroupStartEnd" /* CommandResult.InvalidHeaderGroupStartEnd */;
57425
57591
  }
@@ -57430,6 +57596,9 @@ class HeaderGroupingPlugin extends CorePlugin {
57430
57596
  }
57431
57597
  case "UNFOLD_HEADER_GROUP":
57432
57598
  case "FOLD_HEADER_GROUP":
57599
+ if (!this.getters.tryGetSheet(cmd.sheetId)) {
57600
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
57601
+ }
57433
57602
  const group = this.findGroupWithStartEnd(cmd.sheetId, cmd.dimension, cmd.start, cmd.end);
57434
57603
  if (!group) {
57435
57604
  return "UnknownHeaderGroup" /* CommandResult.UnknownHeaderGroup */;
@@ -57830,6 +57999,9 @@ class PivotCorePlugin extends CorePlugin {
57830
57999
  return this.checkDuplicatedMeasureIds(cmd.pivot);
57831
58000
  }
57832
58001
  case "UPDATE_PIVOT": {
58002
+ if (!(cmd.pivotId in this.pivots)) {
58003
+ return "PivotIdNotFound" /* CommandResult.PivotIdNotFound */;
58004
+ }
57833
58005
  if (deepEquals(cmd.pivot, this.pivots[cmd.pivotId]?.definition)) {
57834
58006
  return "NoChanges" /* CommandResult.NoChanges */;
57835
58007
  }
@@ -57846,6 +58018,8 @@ class PivotCorePlugin extends CorePlugin {
57846
58018
  return "EmptyName" /* CommandResult.EmptyName */;
57847
58019
  }
57848
58020
  break;
58021
+ case "REMOVE_PIVOT":
58022
+ case "DUPLICATE_PIVOT":
57849
58023
  case "INSERT_PIVOT": {
57850
58024
  if (!(cmd.pivotId in this.pivots)) {
57851
58025
  return "PivotIdNotFound" /* CommandResult.PivotIdNotFound */;
@@ -57895,7 +58069,7 @@ class PivotCorePlugin extends CorePlugin {
57895
58069
  break;
57896
58070
  }
57897
58071
  case "UPDATE_PIVOT": {
57898
- this.history.update("pivots", cmd.pivotId, "definition", cmd.pivot);
58072
+ this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
57899
58073
  this.compileCalculatedMeasures(cmd.pivot.measures);
57900
58074
  break;
57901
58075
  }
@@ -57966,7 +58140,7 @@ class PivotCorePlugin extends CorePlugin {
57966
58140
  // Private
57967
58141
  // -------------------------------------------------------------------------
57968
58142
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
57969
- this.history.update("pivots", pivotId, { definition: pivot, formulaId });
58143
+ this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
57970
58144
  this.compileCalculatedMeasures(pivot.measures);
57971
58145
  this.history.update("formulaIds", formulaId, pivotId);
57972
58146
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -59552,6 +59726,10 @@ class Evaluator {
59552
59726
  this.compilationParams = buildCompilationParameters(this.context, this.getters, this.computeAndSave.bind(this));
59553
59727
  this.compilationParams.evalContext.updateDependencies = this.updateDependencies.bind(this);
59554
59728
  this.compilationParams.evalContext.addDependencies = this.addDependencies.bind(this);
59729
+ this.compilationParams.evalContext.lookupCaches = {
59730
+ forwardSearch: new Map(),
59731
+ reverseSearch: new Map(),
59732
+ };
59555
59733
  }
59556
59734
  createEmptyPositionSet() {
59557
59735
  const sheetSizes = {};
@@ -62872,6 +63050,9 @@ function updateChartRangesTransformation(toTransform, executed) {
62872
63050
  };
62873
63051
  }
62874
63052
  function createSheetTransformation(toTransform, executed) {
63053
+ if (toTransform.sheetId === executed.sheetId) {
63054
+ toTransform = { ...toTransform, sheetId: `${toTransform.sheetId}~` };
63055
+ }
62875
63056
  if (toTransform.name === executed.name) {
62876
63057
  return {
62877
63058
  ...toTransform,
@@ -63523,21 +63704,14 @@ class Session extends EventBus {
63523
63704
  if (!message)
63524
63705
  return;
63525
63706
  if (message.type === "REMOTE_REVISION") {
63526
- const revision = this.revisions.get(message.nextRevisionId);
63707
+ let revision = this.revisions.get(message.nextRevisionId);
63527
63708
  if (revision.commands.length === 0) {
63528
63709
  /**
63529
- * The command is empty, we have to drop all the next local revisions
63710
+ * The command is empty, we have to rebase all the next local revisions
63530
63711
  * to avoid issues with undo/redo
63531
63712
  */
63532
- this.revisions.drop(revision.id);
63533
- const revisionIds = this.pendingMessages
63534
- .filter((message) => message.type === "REMOTE_REVISION")
63535
- .map((message) => message.nextRevisionId);
63536
- this.trigger("pending-revisions-dropped", { revisionIds });
63537
- this.waitingAck = false;
63538
- this.waitingUndoRedoAck = false;
63539
- this.pendingMessages = [];
63540
- return;
63713
+ this.revisions.rebase(revision.id);
63714
+ revision = this.revisions.get(message.nextRevisionId);
63541
63715
  }
63542
63716
  message = {
63543
63717
  ...message,
@@ -63562,7 +63736,6 @@ class Session extends EventBus {
63562
63736
  switch (message.type) {
63563
63737
  case "REMOTE_REVISION":
63564
63738
  case "REVISION_REDONE":
63565
- case "REVISION_UNDONE":
63566
63739
  case "SNAPSHOT_CREATED":
63567
63740
  this.waitingAck = false;
63568
63741
  this.pendingMessages = this.pendingMessages.filter((msg) => msg.nextRevisionId !== message.nextRevisionId);
@@ -63571,6 +63744,25 @@ class Session extends EventBus {
63571
63744
  this.lastRevisionMessage = message;
63572
63745
  this.sendPendingMessage();
63573
63746
  break;
63747
+ case "REVISION_UNDONE": {
63748
+ this.waitingAck = false;
63749
+ this.pendingMessages = this.pendingMessages.filter((msg) => msg.nextRevisionId !== message.nextRevisionId);
63750
+ const firstPendingRevisionId = this.pendingMessages.findIndex((message) => message.type === "REMOTE_REVISION");
63751
+ if (firstPendingRevisionId !== -1) {
63752
+ /**
63753
+ * Some revisions undergo transformations that may cause issues with
63754
+ * undo/redo if the transformation is destructive (we don't get back
63755
+ * the original command by transforming it with the inverse).
63756
+ * To prevent these problems, we must rebase all subsequent local
63757
+ * revisions.
63758
+ */
63759
+ this.revisions.rebase(this.pendingMessages[firstPendingRevisionId].nextRevisionId);
63760
+ }
63761
+ this.serverRevisionId = message.nextRevisionId;
63762
+ this.processedRevisions.add(message.nextRevisionId);
63763
+ this.sendPendingMessage();
63764
+ break;
63765
+ }
63574
63766
  }
63575
63767
  }
63576
63768
  isAlreadyProcessed(message) {
@@ -64692,6 +64884,10 @@ class SheetUIPlugin extends UIPlugin {
64692
64884
  */
64693
64885
  checkZonesAreInSheet(cmd) {
64694
64886
  const sheetId = "sheetId" in cmd ? cmd.sheetId : this.getters.tryGetActiveSheetId();
64887
+ if ("ranges" in cmd &&
64888
+ cmd.ranges.some((rangeData) => !this.getters.tryGetSheet(rangeData._sheetId))) {
64889
+ return "InvalidSheetId" /* CommandResult.InvalidSheetId */;
64890
+ }
64695
64891
  const zones = this.getters.getCommandZones(cmd);
64696
64892
  if (!sheetId && zones.length > 0) {
64697
64893
  return "NoActiveSheet" /* CommandResult.NoActiveSheet */;
@@ -65041,23 +65237,23 @@ const uuidGenerator = new UuidGenerator();
65041
65237
  function repeatCreateChartCommand(getters, cmd) {
65042
65238
  return {
65043
65239
  ...repeatSheetDependantCommand(getters, cmd),
65044
- id: uuidGenerator.uuidv4(),
65240
+ id: uuidGenerator.smallUuid(),
65045
65241
  };
65046
65242
  }
65047
65243
  function repeatCreateImageCommand(getters, cmd) {
65048
65244
  return {
65049
65245
  ...repeatSheetDependantCommand(getters, cmd),
65050
- figureId: uuidGenerator.uuidv4(),
65246
+ figureId: uuidGenerator.smallUuid(),
65051
65247
  };
65052
65248
  }
65053
65249
  function repeatCreateFigureCommand(getters, cmd) {
65054
65250
  const newCmd = repeatSheetDependantCommand(getters, cmd);
65055
- newCmd.figure.id = uuidGenerator.uuidv4();
65251
+ newCmd.figure.id = uuidGenerator.smallUuid();
65056
65252
  return newCmd;
65057
65253
  }
65058
65254
  function repeatCreateSheetCommand(getters, cmd) {
65059
65255
  const newCmd = deepCopy(cmd);
65060
- newCmd.sheetId = uuidGenerator.uuidv4();
65256
+ newCmd.sheetId = uuidGenerator.smallUuid();
65061
65257
  const sheetName = cmd.name || getters.getSheet(getters.getActiveSheetId()).name;
65062
65258
  // Extract the prefix of the sheet name (everything before the number at the end of the name)
65063
65259
  const namePrefix = sheetName.match(/(.+?)\d*$/)?.[1] || sheetName;
@@ -65246,7 +65442,6 @@ class HistoryPlugin extends UIPlugin {
65246
65442
  super(config);
65247
65443
  this.session = config.session;
65248
65444
  this.session.on("new-local-state-update", this, this.onNewLocalStateUpdate);
65249
- this.session.on("pending-revisions-dropped", this, ({ revisionIds }) => this.drop(revisionIds));
65250
65445
  this.session.on("snapshot", this, () => {
65251
65446
  this.undoStack = [];
65252
65447
  this.redoStack = [];
@@ -65316,10 +65511,6 @@ class HistoryPlugin extends UIPlugin {
65316
65511
  const lastNonRedoRevision = this.getPossibleRevisionToRepeat();
65317
65512
  return canRepeatRevision(lastNonRedoRevision);
65318
65513
  }
65319
- drop(revisionIds) {
65320
- this.undoStack = this.undoStack.filter((id) => !revisionIds.includes(id));
65321
- this.redoStack = [];
65322
- }
65323
65514
  onNewLocalStateUpdate({ id }) {
65324
65515
  this.undoStack.push(id);
65325
65516
  this.redoStack = [];
@@ -66520,23 +66711,7 @@ class GridSelectionPlugin extends UIPlugin {
66520
66711
  gridSelection: deepCopy(gridSelection),
66521
66712
  };
66522
66713
  }
66523
- if (!this.getters.tryGetSheet(this.getters.getActiveSheetId())) {
66524
- const currentSheetIds = this.getters.getVisibleSheetIds();
66525
- this.activeSheet = this.getters.getSheet(currentSheetIds[0]);
66526
- if (this.activeSheet.id in this.sheetsData) {
66527
- const { anchor } = this.clipSelection(this.activeSheet.id, this.sheetsData[this.activeSheet.id].gridSelection);
66528
- this.selectCell(anchor.cell.col, anchor.cell.row);
66529
- }
66530
- else {
66531
- this.selectCell(0, 0);
66532
- }
66533
- const { col, row } = this.gridSelection.anchor.cell;
66534
- this.moveClient({
66535
- sheetId: this.getters.getActiveSheetId(),
66536
- col,
66537
- row,
66538
- });
66539
- }
66714
+ this.fallbackToVisibleSheet();
66540
66715
  const sheetId = this.getters.getActiveSheetId();
66541
66716
  this.gridSelection.zones = this.gridSelection.zones.map((z) => this.getters.expandZone(sheetId, z));
66542
66717
  this.gridSelection.anchor.zone = this.getters.expandZone(sheetId, this.gridSelection.anchor.zone);
@@ -66546,6 +66721,7 @@ class GridSelectionPlugin extends UIPlugin {
66546
66721
  }
66547
66722
  }
66548
66723
  finalize() {
66724
+ this.fallbackToVisibleSheet();
66549
66725
  /** Any change to the selection has to be reflected in the selection processor. */
66550
66726
  this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));
66551
66727
  }
@@ -66856,6 +67032,25 @@ class GridSelectionPlugin extends UIPlugin {
66856
67032
  }
66857
67033
  return "Success" /* CommandResult.Success */;
66858
67034
  }
67035
+ fallbackToVisibleSheet() {
67036
+ if (!this.getters.tryGetSheet(this.getters.getActiveSheetId())) {
67037
+ const currentSheetIds = this.getters.getVisibleSheetIds();
67038
+ this.activeSheet = this.getters.getSheet(currentSheetIds[0]);
67039
+ if (this.activeSheet.id in this.sheetsData) {
67040
+ const { anchor } = this.clipSelection(this.activeSheet.id, this.sheetsData[this.activeSheet.id].gridSelection);
67041
+ this.selectCell(anchor.cell.col, anchor.cell.row);
67042
+ }
67043
+ else {
67044
+ this.selectCell(0, 0);
67045
+ }
67046
+ const { col, row } = this.gridSelection.anchor.cell;
67047
+ this.moveClient({
67048
+ sheetId: this.getters.getActiveSheetId(),
67049
+ col,
67050
+ row,
67051
+ });
67052
+ }
67053
+ }
66859
67054
  //-------------------------------------------
66860
67055
  // Helpers for extensions
66861
67056
  // ------------------------------------------
@@ -68019,7 +68214,9 @@ class HeaderPositionsUIPlugin extends UIPlugin {
68019
68214
  case "UNGROUP_HEADERS":
68020
68215
  case "GROUP_HEADERS":
68021
68216
  case "CREATE_SHEET":
68022
- this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
68217
+ if (this.getters.tryGetSheet(cmd.sheetId)) {
68218
+ this.headerPositions[cmd.sheetId] = this.computeHeaderPositionsOfSheet(cmd.sheetId);
68219
+ }
68023
68220
  break;
68024
68221
  case "DUPLICATE_SHEET":
68025
68222
  this.headerPositions[cmd.sheetIdTo] = deepCopy(this.headerPositions[cmd.sheetId]);
@@ -68027,12 +68224,14 @@ class HeaderPositionsUIPlugin extends UIPlugin {
68027
68224
  }
68028
68225
  }
68029
68226
  finalize() {
68030
- if (this.isDirty) {
68031
- for (const sheetId of this.getters.getSheetIds()) {
68227
+ for (const sheetId of this.getters.getSheetIds()) {
68228
+ // sheets can be created without this plugin being aware of it
68229
+ // in concurrent situations.
68230
+ if (this.isDirty || !this.headerPositions[sheetId]) {
68032
68231
  this.headerPositions[sheetId] = this.computeHeaderPositionsOfSheet(sheetId);
68033
68232
  }
68034
- this.isDirty = false;
68035
68233
  }
68234
+ this.isDirty = false;
68036
68235
  }
68037
68236
  /**
68038
68237
  * Returns the size, start and end coordinates of a column on an unfolded sheet
@@ -68856,7 +69055,7 @@ class BottomBar extends owl.Component {
68856
69055
  clickAddSheet(ev) {
68857
69056
  const activeSheetId = this.env.model.getters.getActiveSheetId();
68858
69057
  const position = this.env.model.getters.getSheetIds().findIndex((sheetId) => sheetId === activeSheetId) + 1;
68859
- const sheetId = this.env.model.uuidGenerator.uuidv4();
69058
+ const sheetId = this.env.model.uuidGenerator.smallUuid();
68860
69059
  const name = this.env.model.getters.getNextSheetName(_t("Sheet"));
68861
69060
  this.env.model.dispatch("CREATE_SHEET", { sheetId, position, name });
68862
69061
  this.env.model.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: sheetId });
@@ -69871,6 +70070,10 @@ const FX_SVG = /*xml*/ `
69871
70070
  </svg>
69872
70071
  `;
69873
70072
  css /* scss */ `
70073
+ .o-topbar-composer-container {
70074
+ height: ${TOPBAR_TOOLBAR_HEIGHT}px;
70075
+ }
70076
+
69874
70077
  .o-topbar-composer {
69875
70078
  height: fit-content;
69876
70079
  margin-top: -1px;
@@ -71144,7 +71347,7 @@ class Tree {
71144
71347
  }
71145
71348
  /**
71146
71349
  * Drop the operation and all following operations in every
71147
- * branch
71350
+ * branches
71148
71351
  */
71149
71352
  drop(operationId) {
71150
71353
  for (const branch of this.branches) {
@@ -71449,9 +71652,16 @@ class SelectiveHistory {
71449
71652
  this.fastForward();
71450
71653
  this.insert(redoId, this.buildEmpty(redoId), insertAfter);
71451
71654
  }
71452
- drop(operationId) {
71655
+ rebase(operationId) {
71656
+ const operation = this.get(operationId);
71657
+ const execution = [...this.tree.execution(this.HEAD_BRANCH).startAfter(operationId)];
71453
71658
  this.revertBefore(operationId);
71659
+ const baseId = this.HEAD_OPERATION.id;
71454
71660
  this.tree.drop(operationId);
71661
+ this.insert(operationId, operation, baseId);
71662
+ for (const { operation } of execution) {
71663
+ this.insert(operation.id, operation.data, this.HEAD_OPERATION.id);
71664
+ }
71455
71665
  }
71456
71666
  /**
71457
71667
  * Revert the state as it was *before* the given operation was executed.
@@ -74562,6 +74772,11 @@ class Model extends EventBus {
74562
74772
  dispatch: (command) => {
74563
74773
  const result = this.checkDispatchAllowed(command);
74564
74774
  if (!result.isSuccessful) {
74775
+ // core views plugins need to be invalidated
74776
+ this.dispatchToHandlers(this.coreHandlers, {
74777
+ type: "UNDO",
74778
+ commands: [command],
74779
+ });
74565
74780
  return;
74566
74781
  }
74567
74782
  this.isReplayingCommand = true;
@@ -74589,7 +74804,7 @@ class Model extends EventBus {
74589
74804
  }
74590
74805
  setupConfig(config) {
74591
74806
  const client = config.client || {
74592
- id: this.uuidGenerator.uuidv4(),
74807
+ id: this.uuidGenerator.smallUuid(),
74593
74808
  name: _t("Anonymous").toString(),
74594
74809
  };
74595
74810
  const transportService = config.transportService || new LocalTransportService();
@@ -75112,6 +75327,6 @@ exports.tokenColors = tokenColors;
75112
75327
  exports.tokenize = tokenize;
75113
75328
 
75114
75329
 
75115
- __info__.version = "18.1.7";
75116
- __info__.date = "2025-02-10T09:00:28.556Z";
75117
- __info__.hash = "338d8a1";
75330
+ __info__.version = "18.1.9";
75331
+ __info__.date = "2025-02-25T05:59:45.472Z";
75332
+ __info__.hash = "6789c1c";