@odoo/o-spreadsheet 18.1.24 → 18.1.26

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.24
6
- * @date 2025-06-06T09:31:57.011Z
7
- * @hash 0e386b2
5
+ * @version 18.1.26
6
+ * @date 2025-06-19T18:21:37.648Z
7
+ * @hash 06479d4
8
8
  */
9
9
 
10
10
  'use strict';
@@ -5847,7 +5847,9 @@ function isTextFormat(format) {
5847
5847
  }
5848
5848
 
5849
5849
  function evaluateLiteral(literalCell, localeFormat) {
5850
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5850
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5851
+ ? literalCell.content
5852
+ : literalCell.parsedValue;
5851
5853
  const functionResult = { value, format: localeFormat.format };
5852
5854
  return createEvaluatedCell(functionResult, localeFormat.locale);
5853
5855
  }
@@ -5896,6 +5898,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5896
5898
  if (isEvaluationError(value)) {
5897
5899
  return errorCell(value, message);
5898
5900
  }
5901
+ if (value === null) {
5902
+ return emptyCell(format);
5903
+ }
5899
5904
  if (isTextFormat(format)) {
5900
5905
  // TO DO:
5901
5906
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5903,9 +5908,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5903
5908
  // to interpret the value as a number.
5904
5909
  return textCell(toString(value), format, formattedValue);
5905
5910
  }
5906
- if (value === null) {
5907
- return emptyCell(format);
5908
- }
5909
5911
  if (typeof value === "number") {
5910
5912
  if (isDateTimeFormat(format || "")) {
5911
5913
  return dateTimeCell(value, format, formattedValue);
@@ -10202,6 +10204,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10202
10204
  if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) {
10203
10205
  continue;
10204
10206
  }
10207
+ const yAxisScale = chart.scales[dataset.yAxisID];
10205
10208
  for (let i = 0; i < dataset._parsed.length; i++) {
10206
10209
  const parsedValue = dataset._parsed[i];
10207
10210
  const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
@@ -10212,10 +10215,18 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10212
10215
  const xPosition = point.x;
10213
10216
  let yPosition = 0;
10214
10217
  if (chart.config.type === "line" || chart.config.type === "radar") {
10215
- yPosition = point.y - 10;
10218
+ yPosition = value < 0 ? point.y + 10 : point.y - 10;
10216
10219
  }
10217
10220
  else {
10218
- yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10221
+ const yZeroLine = yAxisScale.getPixelForValue(0);
10222
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10223
+ const textHeight = 12; // ChartJS default text height
10224
+ if (distanceFromAxisOrigin < textHeight) {
10225
+ yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10226
+ }
10227
+ else {
10228
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10229
+ }
10219
10230
  }
10220
10231
  yPosition = Math.min(yPosition, yMax);
10221
10232
  yPosition = Math.max(yPosition, yMin);
@@ -10225,7 +10236,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10225
10236
  }
10226
10237
  for (const otherPosition of textsPositions[xPosition] || []) {
10227
10238
  if (Math.abs(otherPosition - yPosition) < 13) {
10228
- yPosition = otherPosition - 13;
10239
+ yPosition = value < 0 ? otherPosition + 13 : otherPosition - 13;
10229
10240
  }
10230
10241
  }
10231
10242
  textsPositions[xPosition].push(yPosition);
@@ -10244,6 +10255,8 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10244
10255
  if (isTrendLineAxis(dataset.xAxisID)) {
10245
10256
  return; // ignore trend lines
10246
10257
  }
10258
+ const xAxisScale = chart.scales[dataset.xAxisID];
10259
+ const xZeroLine = xAxisScale.getPixelForValue(0);
10247
10260
  for (let i = 0; i < dataset._parsed.length; i++) {
10248
10261
  const value = Number(dataset._parsed[i].x);
10249
10262
  if (isNaN(value)) {
@@ -10252,17 +10265,27 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10252
10265
  const displayValue = options.callback(value, dataset, i);
10253
10266
  const point = dataset.data[i];
10254
10267
  const yPosition = point.y;
10255
- let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10256
- xPosition = Math.min(xPosition, xMax);
10257
- xPosition = Math.max(xPosition, xMin);
10268
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10269
+ const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10270
+ const PADDING = 3;
10271
+ let xPosition;
10272
+ if (distanceFromAxisOrigin < textWidth) {
10273
+ xPosition =
10274
+ value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10275
+ }
10276
+ else {
10277
+ xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10278
+ xPosition = Math.min(xPosition, xMax);
10279
+ xPosition = Math.max(xPosition, xMin);
10280
+ }
10258
10281
  // Avoid overlapping texts with same Y
10259
10282
  if (!textsPositions[yPosition]) {
10260
10283
  textsPositions[yPosition] = [];
10261
10284
  }
10262
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10263
10285
  for (const otherPosition of textsPositions[yPosition]) {
10264
10286
  if (Math.abs(otherPosition - xPosition) < textWidth) {
10265
- xPosition = otherPosition + textWidth + 3;
10287
+ xPosition =
10288
+ value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10266
10289
  }
10267
10290
  }
10268
10291
  textsPositions[yPosition].push(xPosition);
@@ -10284,10 +10307,22 @@ function drawPieChartValues(chart, options, ctx) {
10284
10307
  const midAngle = (startAngle + endAngle) / 2;
10285
10308
  const midRadius = (innerRadius + outerRadius) / 2;
10286
10309
  const x = bar.x + midRadius * Math.cos(midAngle);
10287
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10310
+ const y = bar.y + midRadius * Math.sin(midAngle);
10311
+ const displayValue = options.callback(value, dataset, i);
10312
+ const textHeight = 12; // ChartJS default
10313
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10314
+ const radius = outerRadius - innerRadius;
10315
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10316
+ if (textWidth >= radius || radius < textHeight) {
10317
+ continue;
10318
+ }
10319
+ const sliceAngle = endAngle - startAngle;
10320
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10321
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10322
+ continue;
10323
+ }
10288
10324
  ctx.fillStyle = chartFontColor(options.background);
10289
10325
  ctx.strokeStyle = options.background || "#ffffff";
10290
- const displayValue = options.callback(value, dataset, i);
10291
10326
  drawTextWithBackground(displayValue, x, y, ctx);
10292
10327
  }
10293
10328
  }
@@ -26193,7 +26228,7 @@ class XlsxBaseExtractor {
26193
26228
  */
26194
26229
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26195
26230
  if (optionalArgs?.required) {
26196
- if (optionalArgs?.default) {
26231
+ if (optionalArgs?.default !== undefined) {
26197
26232
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26198
26233
  }
26199
26234
  else {
@@ -30066,7 +30101,9 @@ function getPyramidChartShowValues(definition, args) {
30066
30101
  background: definition.background,
30067
30102
  callback: (value, dataset) => {
30068
30103
  value = Math.abs(Number(value));
30069
- return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30104
+ return value === 0
30105
+ ? ""
30106
+ : formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30070
30107
  },
30071
30108
  };
30072
30109
  }
@@ -33215,19 +33252,26 @@ class FilterMenu extends owl.Component {
33215
33252
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33216
33253
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33217
33254
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33218
- const strValues = [...cellValues, ...filterValues];
33219
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33220
- // Set with lowercase values to avoid duplicates
33221
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33222
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33223
- return sortedValues.map((normalizedValue) => {
33224
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33225
- -1;
33226
- return {
33227
- checked,
33228
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33229
- };
33230
- });
33255
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33256
+ const set = new Set();
33257
+ const values = [];
33258
+ const addValue = (value) => {
33259
+ const normalizedValue = toLowerCase(value);
33260
+ if (!set.has(normalizedValue)) {
33261
+ values.push({
33262
+ string: value || "",
33263
+ checked: !normalizedFilteredValues.has(normalizedValue),
33264
+ normalizedValue,
33265
+ });
33266
+ set.add(normalizedValue);
33267
+ }
33268
+ };
33269
+ cellValues.forEach(addValue);
33270
+ filterValues.forEach(addValue);
33271
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33272
+ numeric: true,
33273
+ sensitivity: "base",
33274
+ }));
33231
33275
  }
33232
33276
  checkValue(value) {
33233
33277
  this.state.selectedValue = value.string;
@@ -34559,6 +34603,10 @@ const REMOVE_ROWS_ACTION = (env) => {
34559
34603
  });
34560
34604
  };
34561
34605
  const CAN_REMOVE_COLUMNS_ROWS = (dimension, env) => {
34606
+ if ((dimension === "COL" && env.model.getters.getActiveRows().size > 0) ||
34607
+ (dimension === "ROW" && env.model.getters.getActiveCols().size > 0)) {
34608
+ return false;
34609
+ }
34562
34610
  const sheetId = env.model.getters.getActiveSheetId();
34563
34611
  const selectedElements = env.model.getters.getElementsFromSelection(dimension);
34564
34612
  const includesAllVisibleHeaders = env.model.getters.checkElementsIncludeAllVisibleHeaders(sheetId, dimension, selectedElements);
@@ -37538,11 +37586,11 @@ class OTRegistry extends Registry {
37538
37586
  * transformation function given
37539
37587
  */
37540
37588
  addTransformation(executed, toTransforms, fn) {
37541
- for (let toTransform of toTransforms) {
37542
- if (!this.content[toTransform]) {
37543
- this.content[toTransform] = new Map();
37544
- }
37545
- this.content[toTransform].set(executed, fn);
37589
+ if (!this.content[executed]) {
37590
+ this.content[executed] = new Map();
37591
+ }
37592
+ for (const toTransform of toTransforms) {
37593
+ this.content[executed].set(toTransform, fn);
37546
37594
  }
37547
37595
  return this;
37548
37596
  }
@@ -37551,7 +37599,7 @@ class OTRegistry extends Registry {
37551
37599
  * that the executed command happened.
37552
37600
  */
37553
37601
  getTransformation(toTransform, executed) {
37554
- return this.content[toTransform] && this.content[toTransform].get(executed);
37602
+ return this.content[executed] && this.content[executed].get(toTransform);
37555
37603
  }
37556
37604
  }
37557
37605
  const otRegistry = new OTRegistry();
@@ -58095,7 +58143,9 @@ class TablePlugin extends CorePlugin {
58095
58143
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58096
58144
  const union = this.getters.getRangesUnion(ranges);
58097
58145
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58098
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58146
+ if (mergesInTarget.length) {
58147
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58148
+ }
58099
58149
  const id = `${nextTableId++}`;
58100
58150
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58101
58151
  const newTable = cmd.tableType === "dynamic"
@@ -58206,14 +58256,16 @@ class TablePlugin extends CorePlugin {
58206
58256
  const zoneToCheckIfEmpty = direction === "down"
58207
58257
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58208
58258
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58209
- for (const position of positions(zoneToCheckIfEmpty)) {
58210
- const cellPosition = { sheetId, ...position };
58211
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58212
- const cellContent = this.getters.getCell(cellPosition)?.content;
58213
- if (cellContent ||
58214
- this.getters.isInMerge(cellPosition) ||
58215
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58216
- return "none";
58259
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58260
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58261
+ const cellPosition = { sheetId, col, row };
58262
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58263
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58264
+ if (cellContent ||
58265
+ this.getters.isInMerge(cellPosition) ||
58266
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58267
+ return "none";
58268
+ }
58217
58269
  }
58218
58270
  }
58219
58271
  return direction;
@@ -64242,10 +64294,20 @@ function transform(toTransform, executed) {
64242
64294
  */
64243
64295
  function transformAll(toTransform, executed) {
64244
64296
  let transformedCommands = [...toTransform];
64297
+ const possibleTransformations = new Set(otRegistry.getKeys());
64245
64298
  for (const executedCommand of executed) {
64246
- transformedCommands = transformedCommands
64247
- .map((cmd) => transform(cmd, executedCommand))
64248
- .filter(isDefined);
64299
+ // If the executed command is not in the registry, we skip it
64300
+ // because we know there won't be any transformation impacting the
64301
+ // commands to transform.
64302
+ if (possibleTransformations.has(executedCommand.type)) {
64303
+ transformedCommands = transformedCommands.reduce((acc, cmd) => {
64304
+ const transformed = transform(cmd, executedCommand);
64305
+ if (transformed) {
64306
+ acc.push(transformed);
64307
+ }
64308
+ return acc;
64309
+ }, []);
64310
+ }
64249
64311
  }
64250
64312
  return transformedCommands;
64251
64313
  }
@@ -65940,7 +66002,7 @@ class SheetUIPlugin extends UIPlugin {
65940
66002
  }
65941
66003
  const position = this.getters.getCellPosition(cell.id);
65942
66004
  const colSize = this.getters.getColSize(sheetId, position.col);
65943
- if (cell.isFormula) {
66005
+ if (cell.isFormula || this.getters.getArrayFormulaSpreadingOn(position)) {
65944
66006
  const content = this.getters.getEvaluatedCell(position).formattedValue;
65945
66007
  const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
65946
66008
  if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
@@ -67579,9 +67641,10 @@ class FilterEvaluationPlugin extends UIPlugin {
67579
67641
  const filteredZone = filter.filteredRange?.zone;
67580
67642
  if (!filteredValues || !filteredZone)
67581
67643
  continue;
67644
+ const filteredValuesSet = new Set(filteredValues);
67582
67645
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
67583
67646
  const value = this.getCellValueAsString(sheetId, filter.col, row);
67584
- if (filteredValues.includes(value)) {
67647
+ if (filteredValuesSet.has(value)) {
67585
67648
  hiddenRows.add(row);
67586
67649
  }
67587
67650
  }
@@ -76511,6 +76574,6 @@ exports.tokenColors = tokenColors;
76511
76574
  exports.tokenize = tokenize;
76512
76575
 
76513
76576
 
76514
- __info__.version = "18.1.24";
76515
- __info__.date = "2025-06-06T09:31:57.011Z";
76516
- __info__.hash = "0e386b2";
76577
+ __info__.version = "18.1.26";
76578
+ __info__.date = "2025-06-19T18:21:37.648Z";
76579
+ __info__.hash = "06479d4";