@odoo/o-spreadsheet 18.1.23 → 18.1.25

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.23
6
- * @date 2025-05-30T08:45:44.408Z
7
- * @hash a21fa01
5
+ * @version 18.1.25
6
+ * @date 2025-06-12T09:49:08.707Z
7
+ * @hash a232524
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -5845,7 +5845,9 @@ function isTextFormat(format) {
5845
5845
  }
5846
5846
 
5847
5847
  function evaluateLiteral(literalCell, localeFormat) {
5848
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5848
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5849
+ ? literalCell.content
5850
+ : literalCell.parsedValue;
5849
5851
  const functionResult = { value, format: localeFormat.format };
5850
5852
  return createEvaluatedCell(functionResult, localeFormat.locale);
5851
5853
  }
@@ -5894,6 +5896,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5894
5896
  if (isEvaluationError(value)) {
5895
5897
  return errorCell(value, message);
5896
5898
  }
5899
+ if (value === null) {
5900
+ return emptyCell(format);
5901
+ }
5897
5902
  if (isTextFormat(format)) {
5898
5903
  // TO DO:
5899
5904
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5901,9 +5906,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5901
5906
  // to interpret the value as a number.
5902
5907
  return textCell(toString(value), format, formattedValue);
5903
5908
  }
5904
- if (value === null) {
5905
- return emptyCell(format);
5906
- }
5907
5909
  if (typeof value === "number") {
5908
5910
  if (isDateTimeFormat(format || "")) {
5909
5911
  return dateTimeCell(value, format, formattedValue);
@@ -10206,8 +10208,6 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10206
10208
  if (isNaN(value)) {
10207
10209
  continue;
10208
10210
  }
10209
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
10210
- const displayValue = options.callback(Number(value), axisId);
10211
10211
  const point = dataset.data[i];
10212
10212
  const xPosition = point.x;
10213
10213
  let yPosition = 0;
@@ -10231,7 +10231,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10231
10231
  textsPositions[xPosition].push(yPosition);
10232
10232
  ctx.fillStyle = point.options.backgroundColor;
10233
10233
  ctx.strokeStyle = options.background || "#ffffff";
10234
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
10234
+ const valueToDisplay = options.callback(Number(value), dataset, i);
10235
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10235
10236
  }
10236
10237
  }
10237
10238
  }
@@ -10248,7 +10249,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10248
10249
  if (isNaN(value)) {
10249
10250
  continue;
10250
10251
  }
10251
- const displayValue = options.callback(value, dataset.xAxisID);
10252
+ const displayValue = options.callback(value, dataset, i);
10252
10253
  const point = dataset.data[i];
10253
10254
  const yPosition = point.y;
10254
10255
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -10283,10 +10284,22 @@ function drawPieChartValues(chart, options, ctx) {
10283
10284
  const midAngle = (startAngle + endAngle) / 2;
10284
10285
  const midRadius = (innerRadius + outerRadius) / 2;
10285
10286
  const x = bar.x + midRadius * Math.cos(midAngle);
10286
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10287
+ const y = bar.y + midRadius * Math.sin(midAngle);
10288
+ const displayValue = options.callback(value, dataset, i);
10289
+ const textHeight = 12; // ChartJS default
10290
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10291
+ const radius = outerRadius - innerRadius;
10292
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10293
+ if (textWidth >= radius || radius < textHeight) {
10294
+ continue;
10295
+ }
10296
+ const sliceAngle = endAngle - startAngle;
10297
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10298
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10299
+ continue;
10300
+ }
10287
10301
  ctx.fillStyle = chartFontColor(options.background);
10288
10302
  ctx.strokeStyle = options.background || "#ffffff";
10289
- const displayValue = options.callback(value, "y");
10290
10303
  drawTextWithBackground(displayValue, x, y, ctx);
10291
10304
  }
10292
10305
  }
@@ -26192,7 +26205,7 @@ class XlsxBaseExtractor {
26192
26205
  */
26193
26206
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26194
26207
  if (optionalArgs?.required) {
26195
- if (optionalArgs?.default) {
26208
+ if (optionalArgs?.default !== undefined) {
26196
26209
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26197
26210
  }
26198
26211
  else {
@@ -30051,9 +30064,51 @@ function getChartShowValues(definition, args) {
30051
30064
  horizontal: "horizontal" in definition && definition.horizontal,
30052
30065
  showValues: "showValues" in definition ? !!definition.showValues : false,
30053
30066
  background: definition.background,
30054
- callback: formatChartDatasetValue(axisFormats, locale),
30067
+ callback: (value, dataset) => {
30068
+ const axisId = getDatasetAxisId(definition, dataset);
30069
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
30070
+ },
30055
30071
  };
30056
30072
  }
30073
+ function getPyramidChartShowValues(definition, args) {
30074
+ const { axisFormats, locale } = args;
30075
+ return {
30076
+ horizontal: true,
30077
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30078
+ background: definition.background,
30079
+ callback: (value, dataset) => {
30080
+ value = Math.abs(Number(value));
30081
+ return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30082
+ },
30083
+ };
30084
+ }
30085
+ function getWaterfallChartShowValues(definition, args) {
30086
+ const { axisFormats, locale, dataSetsValues } = args;
30087
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
30088
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
30089
+ return subtotalIndexes;
30090
+ }, []);
30091
+ return {
30092
+ showValues: "showValues" in definition ? !!definition.showValues : false,
30093
+ background: definition.background,
30094
+ callback: (value, dataset, index) => {
30095
+ const raw = dataset._dataset.data[index];
30096
+ const delta = raw[1] - raw[0];
30097
+ let sign = delta >= 0 ? "+" : "";
30098
+ if (definition.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
30099
+ sign = "";
30100
+ }
30101
+ return `${sign}${formatChartDatasetValue(axisFormats, locale)(delta, dataset.yAxisID)}`;
30102
+ },
30103
+ };
30104
+ }
30105
+ function getDatasetAxisId(definition, dataset) {
30106
+ if (dataset.rAxisID) {
30107
+ return dataset.rAxisID;
30108
+ }
30109
+ const axisId = "horizontal" in definition && definition.horizontal ? dataset.xAxisID : dataset.yAxisID;
30110
+ return axisId || "y";
30111
+ }
30057
30112
 
30058
30113
  function getChartTitle(definition) {
30059
30114
  const chartTitle = definition.title;
@@ -30262,6 +30317,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
30262
30317
  getPieChartTooltip: getPieChartTooltip,
30263
30318
  getPyramidChartData: getPyramidChartData,
30264
30319
  getPyramidChartScales: getPyramidChartScales,
30320
+ getPyramidChartShowValues: getPyramidChartShowValues,
30265
30321
  getPyramidChartTooltip: getPyramidChartTooltip,
30266
30322
  getRadarChartData: getRadarChartData,
30267
30323
  getRadarChartDatasets: getRadarChartDatasets,
@@ -30275,6 +30331,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
30275
30331
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
30276
30332
  getWaterfallChartLegend: getWaterfallChartLegend,
30277
30333
  getWaterfallChartScales: getWaterfallChartScales,
30334
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
30278
30335
  getWaterfallChartTooltip: getWaterfallChartTooltip,
30279
30336
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels
30280
30337
  });
@@ -31373,7 +31430,7 @@ function createPyramidChartRuntime(chart, getters) {
31373
31430
  title: getChartTitle(definition),
31374
31431
  legend: getBarChartLegend(definition),
31375
31432
  tooltip: getPyramidChartTooltip(definition, chartData),
31376
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
31433
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
31377
31434
  },
31378
31435
  },
31379
31436
  };
@@ -31836,7 +31893,7 @@ function createWaterfallChartRuntime(chart, getters) {
31836
31893
  title: getChartTitle(definition),
31837
31894
  legend: getWaterfallChartLegend(definition),
31838
31895
  tooltip: getWaterfallChartTooltip(definition, chartData),
31839
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
31896
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
31840
31897
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
31841
31898
  },
31842
31899
  },
@@ -33170,19 +33227,26 @@ class FilterMenu extends Component {
33170
33227
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33171
33228
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33172
33229
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33173
- const strValues = [...cellValues, ...filterValues];
33174
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33175
- // Set with lowercase values to avoid duplicates
33176
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33177
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33178
- return sortedValues.map((normalizedValue) => {
33179
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33180
- -1;
33181
- return {
33182
- checked,
33183
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33184
- };
33185
- });
33230
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33231
+ const set = new Set();
33232
+ const values = [];
33233
+ const addValue = (value) => {
33234
+ const normalizedValue = toLowerCase(value);
33235
+ if (!set.has(normalizedValue)) {
33236
+ values.push({
33237
+ string: value || "",
33238
+ checked: !normalizedFilteredValues.has(normalizedValue),
33239
+ normalizedValue,
33240
+ });
33241
+ set.add(normalizedValue);
33242
+ }
33243
+ };
33244
+ cellValues.forEach(addValue);
33245
+ filterValues.forEach(addValue);
33246
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33247
+ numeric: true,
33248
+ sensitivity: "base",
33249
+ }));
33186
33250
  }
33187
33251
  checkValue(value) {
33188
33252
  this.state.selectedValue = value.string;
@@ -44319,13 +44383,15 @@ class FindAndReplaceStore extends SpreadsheetStore {
44319
44383
  if (this.selectedMatchIndex === null) {
44320
44384
  return;
44321
44385
  }
44386
+ this.preserveSelectedMatchIndex = true;
44387
+ this.shouldFinalizeUpdateSelection = true;
44322
44388
  this.model.dispatch("REPLACE_SEARCH", {
44323
44389
  searchString: this.toSearch,
44324
44390
  replaceWith: this.toReplace,
44325
44391
  matches: [this.searchMatches[this.selectedMatchIndex]],
44326
44392
  searchOptions: this.searchOptions,
44327
44393
  });
44328
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
44394
+ this.preserveSelectedMatchIndex = false;
44329
44395
  }
44330
44396
  /**
44331
44397
  * Apply the replace function to all the matches one time.
@@ -51821,6 +51887,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
51821
51887
  let deltaX = lastX - clientX;
51822
51888
  let deltaY = lastY - clientY;
51823
51889
  const elapsedTime = currentTime - lastTime;
51890
+ if (!elapsedTime) {
51891
+ return;
51892
+ }
51824
51893
  velocityX = deltaX / elapsedTime;
51825
51894
  velocityY = deltaY / elapsedTime;
51826
51895
  lastX = clientX;
@@ -51841,6 +51910,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
51841
51910
  function onTouchEnd(ev) {
51842
51911
  isMouseDown = false;
51843
51912
  lastX = lastY = 0;
51913
+ if (resetTimeout) {
51914
+ clearTimeout(resetTimeout);
51915
+ }
51916
+ velocityX *= 1.2;
51917
+ velocityY *= 1.2;
51844
51918
  requestAnimationFrame(scroll);
51845
51919
  }
51846
51920
  function scroll() {
@@ -58040,7 +58114,9 @@ class TablePlugin extends CorePlugin {
58040
58114
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58041
58115
  const union = this.getters.getRangesUnion(ranges);
58042
58116
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58043
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58117
+ if (mergesInTarget.length) {
58118
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58119
+ }
58044
58120
  const id = `${nextTableId++}`;
58045
58121
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58046
58122
  const newTable = cmd.tableType === "dynamic"
@@ -58151,14 +58227,16 @@ class TablePlugin extends CorePlugin {
58151
58227
  const zoneToCheckIfEmpty = direction === "down"
58152
58228
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58153
58229
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58154
- for (const position of positions(zoneToCheckIfEmpty)) {
58155
- const cellPosition = { sheetId, ...position };
58156
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58157
- const cellContent = this.getters.getCell(cellPosition)?.content;
58158
- if (cellContent ||
58159
- this.getters.isInMerge(cellPosition) ||
58160
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58161
- return "none";
58230
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58231
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58232
+ const cellPosition = { sheetId, col, row };
58233
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58234
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58235
+ if (cellContent ||
58236
+ this.getters.isInMerge(cellPosition) ||
58237
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58238
+ return "none";
58239
+ }
58162
58240
  }
58163
58241
  }
58164
58242
  return direction;
@@ -58942,7 +59020,7 @@ class PivotCorePlugin extends CorePlugin {
58942
59020
  break;
58943
59021
  }
58944
59022
  case "UPDATE_PIVOT": {
58945
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
59023
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
58946
59024
  this.compileCalculatedMeasures(cmd.pivot.measures);
58947
59025
  break;
58948
59026
  }
@@ -59013,7 +59091,10 @@ class PivotCorePlugin extends CorePlugin {
59013
59091
  // Private
59014
59092
  // -------------------------------------------------------------------------
59015
59093
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
59016
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
59094
+ this.history.update("pivots", pivotId, {
59095
+ definition: this.repairSortedColumn(deepCopy(pivot)),
59096
+ formulaId,
59097
+ });
59017
59098
  this.compileCalculatedMeasures(pivot.measures);
59018
59099
  this.history.update("formulaIds", formulaId, pivotId);
59019
59100
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -59106,6 +59187,26 @@ class PivotCorePlugin extends CorePlugin {
59106
59187
  }
59107
59188
  return "Success" /* CommandResult.Success */;
59108
59189
  }
59190
+ repairSortedColumn(definition) {
59191
+ if (definition.sortedColumn) {
59192
+ // Fix for an upgrade issue: the sortedColumn measure was not updated
59193
+ // from using fieldName to using id. If the sortedColumn measure matches
59194
+ // a measure fieldName in the definition, update it to use the measure's id instead
59195
+ // of its fieldName.
59196
+ // TODO: add an upgrade step to fix this in master and remove this code
59197
+ const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
59198
+ if (sortedMeasure) {
59199
+ return {
59200
+ ...definition,
59201
+ sortedColumn: {
59202
+ ...definition.sortedColumn,
59203
+ measure: sortedMeasure.id,
59204
+ },
59205
+ };
59206
+ }
59207
+ }
59208
+ return definition;
59209
+ }
59109
59210
  // ---------------------------------------------------------------------
59110
59211
  // Import/Export
59111
59212
  // ---------------------------------------------------------------------
@@ -67501,9 +67602,10 @@ class FilterEvaluationPlugin extends UIPlugin {
67501
67602
  const filteredZone = filter.filteredRange?.zone;
67502
67603
  if (!filteredValues || !filteredZone)
67503
67604
  continue;
67605
+ const filteredValuesSet = new Set(filteredValues);
67504
67606
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
67505
67607
  const value = this.getCellValueAsString(sheetId, filter.col, row);
67506
- if (filteredValues.includes(value)) {
67608
+ if (filteredValuesSet.has(value)) {
67507
67609
  hiddenRows.add(row);
67508
67610
  }
67509
67611
  }
@@ -68014,11 +68116,6 @@ class GridSelectionPlugin extends UIPlugin {
68014
68116
  },
68015
68117
  ];
68016
68118
  const sheetId = this.getActiveSheetId();
68017
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
68018
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
68019
- if (!data) {
68020
- return;
68021
- }
68022
68119
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
68023
68120
  const pasteTarget = [
68024
68121
  {
@@ -68028,7 +68125,14 @@ class GridSelectionPlugin extends UIPlugin {
68028
68125
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
68029
68126
  },
68030
68127
  ];
68031
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68128
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
68129
+ const handler = new Handler(this.getters, this.dispatch);
68130
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
68131
+ if (!data) {
68132
+ continue;
68133
+ }
68134
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
68135
+ }
68032
68136
  const selection = pasteTarget[0];
68033
68137
  const col = selection.left;
68034
68138
  const row = selection.top;
@@ -76387,6 +76491,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
76387
76491
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, 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 };
76388
76492
 
76389
76493
 
76390
- __info__.version = "18.1.23";
76391
- __info__.date = "2025-05-30T08:45:44.408Z";
76392
- __info__.hash = "a21fa01";
76494
+ __info__.version = "18.1.25";
76495
+ __info__.date = "2025-06-12T09:49:08.707Z";
76496
+ __info__.hash = "a232524";