@odoo/o-spreadsheet 18.2.15 → 18.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.2.15
6
- * @date 2025-05-30T08:45:53.952Z
7
- * @hash 607492d
5
+ * @version 18.2.17
6
+ * @date 2025-06-12T09:52:15.050Z
7
+ * @hash ea64209
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -5855,7 +5855,9 @@
5855
5855
  }
5856
5856
 
5857
5857
  function evaluateLiteral(literalCell, localeFormat) {
5858
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5858
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5859
+ ? literalCell.content
5860
+ : literalCell.parsedValue;
5859
5861
  const functionResult = { value, format: localeFormat.format };
5860
5862
  return createEvaluatedCell(functionResult, localeFormat.locale);
5861
5863
  }
@@ -5904,6 +5906,9 @@
5904
5906
  if (isEvaluationError(value)) {
5905
5907
  return errorCell(value, message);
5906
5908
  }
5909
+ if (value === null) {
5910
+ return emptyCell(format);
5911
+ }
5907
5912
  if (isTextFormat(format)) {
5908
5913
  // TO DO:
5909
5914
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5911,9 +5916,6 @@
5911
5916
  // to interpret the value as a number.
5912
5917
  return textCell(toString(value), format, formattedValue);
5913
5918
  }
5914
- if (value === null) {
5915
- return emptyCell(format);
5916
- }
5917
5919
  if (typeof value === "number") {
5918
5920
  if (isDateTimeFormat(format || "")) {
5919
5921
  return dateTimeCell(value, format, formattedValue);
@@ -10371,8 +10373,6 @@ stores.inject(MyMetaStore, storeInstance);
10371
10373
  if (isNaN(value)) {
10372
10374
  continue;
10373
10375
  }
10374
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
10375
- const displayValue = options.callback(Number(value), axisId);
10376
10376
  const point = dataset.data[i];
10377
10377
  const xPosition = point.x;
10378
10378
  let yPosition = 0;
@@ -10396,7 +10396,8 @@ stores.inject(MyMetaStore, storeInstance);
10396
10396
  textsPositions[xPosition].push(yPosition);
10397
10397
  ctx.fillStyle = point.options.backgroundColor;
10398
10398
  ctx.strokeStyle = options.background || "#ffffff";
10399
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10399
+ const valueToDisplay = options.callback(Number(value), dataset, i);
10400
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10400
10401
  }
10401
10402
  }
10402
10403
  }
@@ -10413,7 +10414,7 @@ stores.inject(MyMetaStore, storeInstance);
10413
10414
  if (isNaN(value)) {
10414
10415
  continue;
10415
10416
  }
10416
- const displayValue = options.callback(value, dataset.xAxisID);
10417
+ const displayValue = options.callback(value, dataset, i);
10417
10418
  const point = dataset.data[i];
10418
10419
  const yPosition = point.y;
10419
10420
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -10448,10 +10449,22 @@ stores.inject(MyMetaStore, storeInstance);
10448
10449
  const midAngle = (startAngle + endAngle) / 2;
10449
10450
  const midRadius = (innerRadius + outerRadius) / 2;
10450
10451
  const x = bar.x + midRadius * Math.cos(midAngle);
10451
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10452
+ const y = bar.y + midRadius * Math.sin(midAngle);
10453
+ const displayValue = options.callback(value, dataset, i);
10454
+ const textHeight = 12; // ChartJS default
10455
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10456
+ const radius = outerRadius - innerRadius;
10457
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10458
+ if (textWidth >= radius || radius < textHeight) {
10459
+ continue;
10460
+ }
10461
+ const sliceAngle = endAngle - startAngle;
10462
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10463
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10464
+ continue;
10465
+ }
10452
10466
  ctx.fillStyle = chartFontColor(options.background);
10453
10467
  ctx.strokeStyle = options.background || "#ffffff";
10454
- const displayValue = options.callback(value, "y");
10455
10468
  drawTextWithBackground(displayValue, x, y, ctx);
10456
10469
  }
10457
10470
  }
@@ -26217,7 +26230,7 @@ stores.inject(MyMetaStore, storeInstance);
26217
26230
  */
26218
26231
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26219
26232
  if (optionalArgs?.required) {
26220
- if (optionalArgs?.default) {
26233
+ if (optionalArgs?.default !== undefined) {
26221
26234
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26222
26235
  }
26223
26236
  else {
@@ -30084,9 +30097,51 @@ stores.inject(MyMetaStore, storeInstance);
30084
30097
  horizontal: "horizontal" in definition && definition.horizontal,
30085
30098
  showValues: "showValues" in definition ? !!definition.showValues : false,
30086
30099
  background: definition.background,
30087
- callback: formatChartDatasetValue(axisFormats, locale),
30100
+ callback: (value, dataset) => {
30101
+ const axisId = getDatasetAxisId(definition, dataset);
30102
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
30103
+ },
30088
30104
  };
30089
30105
  }
30106
+ function getPyramidChartShowValues(definition, args) {
30107
+ const { axisFormats, locale } = args;
30108
+ return {
30109
+ horizontal: true,
30110
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30111
+ background: definition.background,
30112
+ callback: (value, dataset) => {
30113
+ value = Math.abs(Number(value));
30114
+ return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30115
+ },
30116
+ };
30117
+ }
30118
+ function getWaterfallChartShowValues(definition, args) {
30119
+ const { axisFormats, locale, dataSetsValues } = args;
30120
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
30121
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
30122
+ return subtotalIndexes;
30123
+ }, []);
30124
+ return {
30125
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30126
+ background: definition.background,
30127
+ callback: (value, dataset, index) => {
30128
+ const raw = dataset._dataset.data[index];
30129
+ const delta = raw[1] - raw[0];
30130
+ let sign = delta >= 0 ? "+" : "";
30131
+ if (definition.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
30132
+ sign = "";
30133
+ }
30134
+ return `${sign}${formatChartDatasetValue(axisFormats, locale)(delta, dataset.yAxisID)}`;
30135
+ },
30136
+ };
30137
+ }
30138
+ function getDatasetAxisId(definition, dataset) {
30139
+ if (dataset.rAxisID) {
30140
+ return dataset.rAxisID;
30141
+ }
30142
+ const axisId = "horizontal" in definition && definition.horizontal ? dataset.xAxisID : dataset.yAxisID;
30143
+ return axisId || "y";
30144
+ }
30090
30145
 
30091
30146
  function getChartTitle(definition) {
30092
30147
  const chartTitle = definition.title;
@@ -30410,6 +30465,7 @@ stores.inject(MyMetaStore, storeInstance);
30410
30465
  getPieChartTooltip: getPieChartTooltip,
30411
30466
  getPyramidChartData: getPyramidChartData,
30412
30467
  getPyramidChartScales: getPyramidChartScales,
30468
+ getPyramidChartShowValues: getPyramidChartShowValues,
30413
30469
  getPyramidChartTooltip: getPyramidChartTooltip,
30414
30470
  getRadarChartData: getRadarChartData,
30415
30471
  getRadarChartDatasets: getRadarChartDatasets,
@@ -30423,6 +30479,7 @@ stores.inject(MyMetaStore, storeInstance);
30423
30479
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
30424
30480
  getWaterfallChartLegend: getWaterfallChartLegend,
30425
30481
  getWaterfallChartScales: getWaterfallChartScales,
30482
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
30426
30483
  getWaterfallChartTooltip: getWaterfallChartTooltip,
30427
30484
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels
30428
30485
  });
@@ -31555,7 +31612,7 @@ stores.inject(MyMetaStore, storeInstance);
31555
31612
  title: getChartTitle(definition),
31556
31613
  legend: getBarChartLegend(definition),
31557
31614
  tooltip: getPyramidChartTooltip(definition, chartData),
31558
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
31615
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
31559
31616
  },
31560
31617
  },
31561
31618
  };
@@ -32018,7 +32075,7 @@ stores.inject(MyMetaStore, storeInstance);
32018
32075
  title: getChartTitle(definition),
32019
32076
  legend: getWaterfallChartLegend(definition),
32020
32077
  tooltip: getWaterfallChartTooltip(definition, chartData),
32021
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
32078
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
32022
32079
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
32023
32080
  },
32024
32081
  },
@@ -33370,19 +33427,26 @@ stores.inject(MyMetaStore, storeInstance);
33370
33427
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33371
33428
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33372
33429
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33373
- const strValues = [...cellValues, ...filterValues];
33374
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33375
- // Set with lowercase values to avoid duplicates
33376
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33377
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33378
- return sortedValues.map((normalizedValue) => {
33379
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33380
- -1;
33381
- return {
33382
- checked,
33383
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33384
- };
33385
- });
33430
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33431
+ const set = new Set();
33432
+ const values = [];
33433
+ const addValue = (value) => {
33434
+ const normalizedValue = toLowerCase(value);
33435
+ if (!set.has(normalizedValue)) {
33436
+ values.push({
33437
+ string: value || "",
33438
+ checked: !normalizedFilteredValues.has(normalizedValue),
33439
+ normalizedValue,
33440
+ });
33441
+ set.add(normalizedValue);
33442
+ }
33443
+ };
33444
+ cellValues.forEach(addValue);
33445
+ filterValues.forEach(addValue);
33446
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33447
+ numeric: true,
33448
+ sensitivity: "base",
33449
+ }));
33386
33450
  }
33387
33451
  checkValue(value) {
33388
33452
  this.state.selectedValue = value.string;
@@ -44664,13 +44728,15 @@ stores.inject(MyMetaStore, storeInstance);
44664
44728
  if (this.selectedMatchIndex === null) {
44665
44729
  return;
44666
44730
  }
44731
+ this.preserveSelectedMatchIndex = true;
44732
+ this.shouldFinalizeUpdateSelection = true;
44667
44733
  this.model.dispatch("REPLACE_SEARCH", {
44668
44734
  searchString: this.toSearch,
44669
44735
  replaceWith: this.toReplace,
44670
44736
  matches: [this.searchMatches[this.selectedMatchIndex]],
44671
44737
  searchOptions: this.searchOptions,
44672
44738
  });
44673
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
44739
+ this.preserveSelectedMatchIndex = false;
44674
44740
  }
44675
44741
  /**
44676
44742
  * Apply the replace function to all the matches one time.
@@ -52271,6 +52337,9 @@ stores.inject(MyMetaStore, storeInstance);
52271
52337
  let deltaX = lastX - clientX;
52272
52338
  let deltaY = lastY - clientY;
52273
52339
  const elapsedTime = currentTime - lastTime;
52340
+ if (!elapsedTime) {
52341
+ return;
52342
+ }
52274
52343
  velocityX = deltaX / elapsedTime;
52275
52344
  velocityY = deltaY / elapsedTime;
52276
52345
  lastX = clientX;
@@ -52291,6 +52360,11 @@ stores.inject(MyMetaStore, storeInstance);
52291
52360
  function onTouchEnd(ev) {
52292
52361
  isMouseDown = false;
52293
52362
  lastX = lastY = 0;
52363
+ if (resetTimeout) {
52364
+ clearTimeout(resetTimeout);
52365
+ }
52366
+ velocityX *= 1.2;
52367
+ velocityY *= 1.2;
52294
52368
  requestAnimationFrame(scroll);
52295
52369
  }
52296
52370
  function scroll() {
@@ -58539,7 +58613,9 @@ stores.inject(MyMetaStore, storeInstance);
58539
58613
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58540
58614
  const union = this.getters.getRangesUnion(ranges);
58541
58615
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58542
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58616
+ if (mergesInTarget.length) {
58617
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58618
+ }
58543
58619
  const id = this.consumeNextId();
58544
58620
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58545
58621
  const newTable = cmd.tableType === "dynamic"
@@ -58638,14 +58714,16 @@ stores.inject(MyMetaStore, storeInstance);
58638
58714
  const zoneToCheckIfEmpty = direction === "down"
58639
58715
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58640
58716
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58641
- for (const position of positions(zoneToCheckIfEmpty)) {
58642
- const cellPosition = { sheetId, ...position };
58643
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58644
- const cellContent = this.getters.getCell(cellPosition)?.content;
58645
- if (cellContent ||
58646
- this.getters.isInMerge(cellPosition) ||
58647
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58648
- return "none";
58717
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58718
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58719
+ const cellPosition = { sheetId, col, row };
58720
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58721
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58722
+ if (cellContent ||
58723
+ this.getters.isInMerge(cellPosition) ||
58724
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58725
+ return "none";
58726
+ }
58649
58727
  }
58650
58728
  }
58651
58729
  return direction;
@@ -59431,7 +59509,7 @@ stores.inject(MyMetaStore, storeInstance);
59431
59509
  break;
59432
59510
  }
59433
59511
  case "UPDATE_PIVOT": {
59434
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
59512
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
59435
59513
  this.compileCalculatedMeasures(cmd.pivot.measures);
59436
59514
  break;
59437
59515
  }
@@ -59502,7 +59580,10 @@ stores.inject(MyMetaStore, storeInstance);
59502
59580
  // Private
59503
59581
  // -------------------------------------------------------------------------
59504
59582
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
59505
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
59583
+ this.history.update("pivots", pivotId, {
59584
+ definition: this.repairSortedColumn(deepCopy(pivot)),
59585
+ formulaId,
59586
+ });
59506
59587
  this.compileCalculatedMeasures(pivot.measures);
59507
59588
  this.history.update("formulaIds", formulaId, pivotId);
59508
59589
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -59595,6 +59676,26 @@ stores.inject(MyMetaStore, storeInstance);
59595
59676
  }
59596
59677
  return "Success" /* CommandResult.Success */;
59597
59678
  }
59679
+ repairSortedColumn(definition) {
59680
+ if (definition.sortedColumn) {
59681
+ // Fix for an upgrade issue: the sortedColumn measure was not updated
59682
+ // from using fieldName to using id. If the sortedColumn measure matches
59683
+ // a measure fieldName in the definition, update it to use the measure's id instead
59684
+ // of its fieldName.
59685
+ // TODO: add an upgrade step to fix this in master and remove this code
59686
+ const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
59687
+ if (sortedMeasure) {
59688
+ return {
59689
+ ...definition,
59690
+ sortedColumn: {
59691
+ ...definition.sortedColumn,
59692
+ measure: sortedMeasure.id,
59693
+ },
59694
+ };
59695
+ }
59696
+ }
59697
+ return definition;
59698
+ }
59598
59699
  // ---------------------------------------------------------------------
59599
59700
  // Import/Export
59600
59701
  // ---------------------------------------------------------------------
@@ -67996,9 +68097,10 @@ stores.inject(MyMetaStore, storeInstance);
67996
68097
  const filteredZone = filter.filteredRange?.zone;
67997
68098
  if (!filteredValues || !filteredZone)
67998
68099
  continue;
68100
+ const filteredValuesSet = new Set(filteredValues);
67999
68101
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
68000
68102
  const value = this.getCellValueAsString(sheetId, filter.col, row);
68001
- if (filteredValues.includes(value)) {
68103
+ if (filteredValuesSet.has(value)) {
68002
68104
  hiddenRows.add(row);
68003
68105
  }
68004
68106
  }
@@ -68506,11 +68608,6 @@ stores.inject(MyMetaStore, storeInstance);
68506
68608
  },
68507
68609
  ];
68508
68610
  const sheetId = this.getActiveSheetId();
68509
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
68510
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
68511
- if (!data) {
68512
- return;
68513
- }
68514
68611
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
68515
68612
  const pasteTarget = [
68516
68613
  {
@@ -68520,7 +68617,14 @@ stores.inject(MyMetaStore, storeInstance);
68520
68617
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
68521
68618
  },
68522
68619
  ];
68523
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68620
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
68621
+ const handler = new Handler(this.getters, this.dispatch);
68622
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
68623
+ if (!data) {
68624
+ continue;
68625
+ }
68626
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68627
+ }
68524
68628
  const selection = pasteTarget[0];
68525
68629
  const col = selection.left;
68526
68630
  const row = selection.top;
@@ -76910,9 +77014,9 @@ stores.inject(MyMetaStore, storeInstance);
76910
77014
  exports.tokenize = tokenize;
76911
77015
 
76912
77016
 
76913
- __info__.version = "18.2.15";
76914
- __info__.date = "2025-05-30T08:45:53.952Z";
76915
- __info__.hash = "607492d";
77017
+ __info__.version = "18.2.17";
77018
+ __info__.date = "2025-06-12T09:52:15.050Z";
77019
+ __info__.hash = "ea64209";
76916
77020
 
76917
77021
 
76918
77022
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);