@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
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -5854,7 +5854,9 @@ function isTextFormat(format) {
5854
5854
  }
5855
5855
 
5856
5856
  function evaluateLiteral(literalCell, localeFormat) {
5857
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5857
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5858
+ ? literalCell.content
5859
+ : literalCell.parsedValue;
5858
5860
  const functionResult = { value, format: localeFormat.format };
5859
5861
  return createEvaluatedCell(functionResult, localeFormat.locale);
5860
5862
  }
@@ -5903,6 +5905,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5903
5905
  if (isEvaluationError(value)) {
5904
5906
  return errorCell(value, message);
5905
5907
  }
5908
+ if (value === null) {
5909
+ return emptyCell(format);
5910
+ }
5906
5911
  if (isTextFormat(format)) {
5907
5912
  // TO DO:
5908
5913
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5910,9 +5915,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5910
5915
  // to interpret the value as a number.
5911
5916
  return textCell(toString(value), format, formattedValue);
5912
5917
  }
5913
- if (value === null) {
5914
- return emptyCell(format);
5915
- }
5916
5918
  if (typeof value === "number") {
5917
5919
  if (isDateTimeFormat(format || "")) {
5918
5920
  return dateTimeCell(value, format, formattedValue);
@@ -10370,8 +10372,6 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10370
10372
  if (isNaN(value)) {
10371
10373
  continue;
10372
10374
  }
10373
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
10374
- const displayValue = options.callback(Number(value), axisId);
10375
10375
  const point = dataset.data[i];
10376
10376
  const xPosition = point.x;
10377
10377
  let yPosition = 0;
@@ -10395,7 +10395,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10395
10395
  textsPositions[xPosition].push(yPosition);
10396
10396
  ctx.fillStyle = point.options.backgroundColor;
10397
10397
  ctx.strokeStyle = options.background || "#ffffff";
10398
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10398
+ const valueToDisplay = options.callback(Number(value), dataset, i);
10399
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10399
10400
  }
10400
10401
  }
10401
10402
  }
@@ -10412,7 +10413,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10412
10413
  if (isNaN(value)) {
10413
10414
  continue;
10414
10415
  }
10415
- const displayValue = options.callback(value, dataset.xAxisID);
10416
+ const displayValue = options.callback(value, dataset, i);
10416
10417
  const point = dataset.data[i];
10417
10418
  const yPosition = point.y;
10418
10419
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -10447,10 +10448,22 @@ function drawPieChartValues(chart, options, ctx) {
10447
10448
  const midAngle = (startAngle + endAngle) / 2;
10448
10449
  const midRadius = (innerRadius + outerRadius) / 2;
10449
10450
  const x = bar.x + midRadius * Math.cos(midAngle);
10450
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10451
+ const y = bar.y + midRadius * Math.sin(midAngle);
10452
+ const displayValue = options.callback(value, dataset, i);
10453
+ const textHeight = 12; // ChartJS default
10454
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10455
+ const radius = outerRadius - innerRadius;
10456
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10457
+ if (textWidth >= radius || radius < textHeight) {
10458
+ continue;
10459
+ }
10460
+ const sliceAngle = endAngle - startAngle;
10461
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10462
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10463
+ continue;
10464
+ }
10451
10465
  ctx.fillStyle = chartFontColor(options.background);
10452
10466
  ctx.strokeStyle = options.background || "#ffffff";
10453
- const displayValue = options.callback(value, "y");
10454
10467
  drawTextWithBackground(displayValue, x, y, ctx);
10455
10468
  }
10456
10469
  }
@@ -26216,7 +26229,7 @@ class XlsxBaseExtractor {
26216
26229
  */
26217
26230
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26218
26231
  if (optionalArgs?.required) {
26219
- if (optionalArgs?.default) {
26232
+ if (optionalArgs?.default !== undefined) {
26220
26233
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26221
26234
  }
26222
26235
  else {
@@ -30083,9 +30096,51 @@ function getChartShowValues(definition, args) {
30083
30096
  horizontal: "horizontal" in definition && definition.horizontal,
30084
30097
  showValues: "showValues" in definition ? !!definition.showValues : false,
30085
30098
  background: definition.background,
30086
- callback: formatChartDatasetValue(axisFormats, locale),
30099
+ callback: (value, dataset) => {
30100
+ const axisId = getDatasetAxisId(definition, dataset);
30101
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
30102
+ },
30087
30103
  };
30088
30104
  }
30105
+ function getPyramidChartShowValues(definition, args) {
30106
+ const { axisFormats, locale } = args;
30107
+ return {
30108
+ horizontal: true,
30109
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30110
+ background: definition.background,
30111
+ callback: (value, dataset) => {
30112
+ value = Math.abs(Number(value));
30113
+ return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30114
+ },
30115
+ };
30116
+ }
30117
+ function getWaterfallChartShowValues(definition, args) {
30118
+ const { axisFormats, locale, dataSetsValues } = args;
30119
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
30120
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
30121
+ return subtotalIndexes;
30122
+ }, []);
30123
+ return {
30124
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30125
+ background: definition.background,
30126
+ callback: (value, dataset, index) => {
30127
+ const raw = dataset._dataset.data[index];
30128
+ const delta = raw[1] - raw[0];
30129
+ let sign = delta >= 0 ? "+" : "";
30130
+ if (definition.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
30131
+ sign = "";
30132
+ }
30133
+ return `${sign}${formatChartDatasetValue(axisFormats, locale)(delta, dataset.yAxisID)}`;
30134
+ },
30135
+ };
30136
+ }
30137
+ function getDatasetAxisId(definition, dataset) {
30138
+ if (dataset.rAxisID) {
30139
+ return dataset.rAxisID;
30140
+ }
30141
+ const axisId = "horizontal" in definition && definition.horizontal ? dataset.xAxisID : dataset.yAxisID;
30142
+ return axisId || "y";
30143
+ }
30089
30144
 
30090
30145
  function getChartTitle(definition) {
30091
30146
  const chartTitle = definition.title;
@@ -30409,6 +30464,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
30409
30464
  getPieChartTooltip: getPieChartTooltip,
30410
30465
  getPyramidChartData: getPyramidChartData,
30411
30466
  getPyramidChartScales: getPyramidChartScales,
30467
+ getPyramidChartShowValues: getPyramidChartShowValues,
30412
30468
  getPyramidChartTooltip: getPyramidChartTooltip,
30413
30469
  getRadarChartData: getRadarChartData,
30414
30470
  getRadarChartDatasets: getRadarChartDatasets,
@@ -30422,6 +30478,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
30422
30478
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
30423
30479
  getWaterfallChartLegend: getWaterfallChartLegend,
30424
30480
  getWaterfallChartScales: getWaterfallChartScales,
30481
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
30425
30482
  getWaterfallChartTooltip: getWaterfallChartTooltip,
30426
30483
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels
30427
30484
  });
@@ -31554,7 +31611,7 @@ function createPyramidChartRuntime(chart, getters) {
31554
31611
  title: getChartTitle(definition),
31555
31612
  legend: getBarChartLegend(definition),
31556
31613
  tooltip: getPyramidChartTooltip(definition, chartData),
31557
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
31614
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
31558
31615
  },
31559
31616
  },
31560
31617
  };
@@ -32017,7 +32074,7 @@ function createWaterfallChartRuntime(chart, getters) {
32017
32074
  title: getChartTitle(definition),
32018
32075
  legend: getWaterfallChartLegend(definition),
32019
32076
  tooltip: getWaterfallChartTooltip(definition, chartData),
32020
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
32077
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
32021
32078
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
32022
32079
  },
32023
32080
  },
@@ -33369,19 +33426,26 @@ class FilterMenu extends Component {
33369
33426
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33370
33427
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33371
33428
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33372
- const strValues = [...cellValues, ...filterValues];
33373
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33374
- // Set with lowercase values to avoid duplicates
33375
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33376
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33377
- return sortedValues.map((normalizedValue) => {
33378
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33379
- -1;
33380
- return {
33381
- checked,
33382
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33383
- };
33384
- });
33429
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33430
+ const set = new Set();
33431
+ const values = [];
33432
+ const addValue = (value) => {
33433
+ const normalizedValue = toLowerCase(value);
33434
+ if (!set.has(normalizedValue)) {
33435
+ values.push({
33436
+ string: value || "",
33437
+ checked: !normalizedFilteredValues.has(normalizedValue),
33438
+ normalizedValue,
33439
+ });
33440
+ set.add(normalizedValue);
33441
+ }
33442
+ };
33443
+ cellValues.forEach(addValue);
33444
+ filterValues.forEach(addValue);
33445
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33446
+ numeric: true,
33447
+ sensitivity: "base",
33448
+ }));
33385
33449
  }
33386
33450
  checkValue(value) {
33387
33451
  this.state.selectedValue = value.string;
@@ -44663,13 +44727,15 @@ class FindAndReplaceStore extends SpreadsheetStore {
44663
44727
  if (this.selectedMatchIndex === null) {
44664
44728
  return;
44665
44729
  }
44730
+ this.preserveSelectedMatchIndex = true;
44731
+ this.shouldFinalizeUpdateSelection = true;
44666
44732
  this.model.dispatch("REPLACE_SEARCH", {
44667
44733
  searchString: this.toSearch,
44668
44734
  replaceWith: this.toReplace,
44669
44735
  matches: [this.searchMatches[this.selectedMatchIndex]],
44670
44736
  searchOptions: this.searchOptions,
44671
44737
  });
44672
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
44738
+ this.preserveSelectedMatchIndex = false;
44673
44739
  }
44674
44740
  /**
44675
44741
  * Apply the replace function to all the matches one time.
@@ -52270,6 +52336,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
52270
52336
  let deltaX = lastX - clientX;
52271
52337
  let deltaY = lastY - clientY;
52272
52338
  const elapsedTime = currentTime - lastTime;
52339
+ if (!elapsedTime) {
52340
+ return;
52341
+ }
52273
52342
  velocityX = deltaX / elapsedTime;
52274
52343
  velocityY = deltaY / elapsedTime;
52275
52344
  lastX = clientX;
@@ -52290,6 +52359,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
52290
52359
  function onTouchEnd(ev) {
52291
52360
  isMouseDown = false;
52292
52361
  lastX = lastY = 0;
52362
+ if (resetTimeout) {
52363
+ clearTimeout(resetTimeout);
52364
+ }
52365
+ velocityX *= 1.2;
52366
+ velocityY *= 1.2;
52293
52367
  requestAnimationFrame(scroll);
52294
52368
  }
52295
52369
  function scroll() {
@@ -58538,7 +58612,9 @@ class TablePlugin extends CorePlugin {
58538
58612
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58539
58613
  const union = this.getters.getRangesUnion(ranges);
58540
58614
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58541
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58615
+ if (mergesInTarget.length) {
58616
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58617
+ }
58542
58618
  const id = this.consumeNextId();
58543
58619
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58544
58620
  const newTable = cmd.tableType === "dynamic"
@@ -58637,14 +58713,16 @@ class TablePlugin extends CorePlugin {
58637
58713
  const zoneToCheckIfEmpty = direction === "down"
58638
58714
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58639
58715
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58640
- for (const position of positions(zoneToCheckIfEmpty)) {
58641
- const cellPosition = { sheetId, ...position };
58642
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58643
- const cellContent = this.getters.getCell(cellPosition)?.content;
58644
- if (cellContent ||
58645
- this.getters.isInMerge(cellPosition) ||
58646
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58647
- return "none";
58716
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58717
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58718
+ const cellPosition = { sheetId, col, row };
58719
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58720
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58721
+ if (cellContent ||
58722
+ this.getters.isInMerge(cellPosition) ||
58723
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58724
+ return "none";
58725
+ }
58648
58726
  }
58649
58727
  }
58650
58728
  return direction;
@@ -59430,7 +59508,7 @@ class PivotCorePlugin extends CorePlugin {
59430
59508
  break;
59431
59509
  }
59432
59510
  case "UPDATE_PIVOT": {
59433
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
59511
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
59434
59512
  this.compileCalculatedMeasures(cmd.pivot.measures);
59435
59513
  break;
59436
59514
  }
@@ -59501,7 +59579,10 @@ class PivotCorePlugin extends CorePlugin {
59501
59579
  // Private
59502
59580
  // -------------------------------------------------------------------------
59503
59581
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
59504
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
59582
+ this.history.update("pivots", pivotId, {
59583
+ definition: this.repairSortedColumn(deepCopy(pivot)),
59584
+ formulaId,
59585
+ });
59505
59586
  this.compileCalculatedMeasures(pivot.measures);
59506
59587
  this.history.update("formulaIds", formulaId, pivotId);
59507
59588
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -59594,6 +59675,26 @@ class PivotCorePlugin extends CorePlugin {
59594
59675
  }
59595
59676
  return "Success" /* CommandResult.Success */;
59596
59677
  }
59678
+ repairSortedColumn(definition) {
59679
+ if (definition.sortedColumn) {
59680
+ // Fix for an upgrade issue: the sortedColumn measure was not updated
59681
+ // from using fieldName to using id. If the sortedColumn measure matches
59682
+ // a measure fieldName in the definition, update it to use the measure's id instead
59683
+ // of its fieldName.
59684
+ // TODO: add an upgrade step to fix this in master and remove this code
59685
+ const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
59686
+ if (sortedMeasure) {
59687
+ return {
59688
+ ...definition,
59689
+ sortedColumn: {
59690
+ ...definition.sortedColumn,
59691
+ measure: sortedMeasure.id,
59692
+ },
59693
+ };
59694
+ }
59695
+ }
59696
+ return definition;
59697
+ }
59597
59698
  // ---------------------------------------------------------------------
59598
59699
  // Import/Export
59599
59700
  // ---------------------------------------------------------------------
@@ -67995,9 +68096,10 @@ class FilterEvaluationPlugin extends UIPlugin {
67995
68096
  const filteredZone = filter.filteredRange?.zone;
67996
68097
  if (!filteredValues || !filteredZone)
67997
68098
  continue;
68099
+ const filteredValuesSet = new Set(filteredValues);
67998
68100
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
67999
68101
  const value = this.getCellValueAsString(sheetId, filter.col, row);
68000
- if (filteredValues.includes(value)) {
68102
+ if (filteredValuesSet.has(value)) {
68001
68103
  hiddenRows.add(row);
68002
68104
  }
68003
68105
  }
@@ -68505,11 +68607,6 @@ class GridSelectionPlugin extends UIPlugin {
68505
68607
  },
68506
68608
  ];
68507
68609
  const sheetId = this.getActiveSheetId();
68508
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
68509
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
68510
- if (!data) {
68511
- return;
68512
- }
68513
68610
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
68514
68611
  const pasteTarget = [
68515
68612
  {
@@ -68519,7 +68616,14 @@ class GridSelectionPlugin extends UIPlugin {
68519
68616
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
68520
68617
  },
68521
68618
  ];
68522
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68619
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
68620
+ const handler = new Handler(this.getters, this.dispatch);
68621
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
68622
+ if (!data) {
68623
+ continue;
68624
+ }
68625
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68626
+ }
68523
68627
  const selection = pasteTarget[0];
68524
68628
  const col = selection.left;
68525
68629
  const row = selection.top;
@@ -76864,6 +76968,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
76864
76968
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
76865
76969
 
76866
76970
 
76867
- __info__.version = "18.2.15";
76868
- __info__.date = "2025-05-30T08:45:53.952Z";
76869
- __info__.hash = "607492d";
76971
+ __info__.version = "18.2.17";
76972
+ __info__.date = "2025-06-12T09:52:15.050Z";
76973
+ __info__.hash = "ea64209";