@odoo/o-spreadsheet 18.2.16 → 18.2.18

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.16
6
- * @date 2025-06-06T09:32:04.909Z
7
- * @hash 7ee118c
5
+ * @version 18.2.18
6
+ * @date 2025-06-19T18:24:41.051Z
7
+ * @hash 024c134
8
8
  */
9
9
 
10
10
  'use strict';
@@ -5856,7 +5856,9 @@ function isTextFormat(format) {
5856
5856
  }
5857
5857
 
5858
5858
  function evaluateLiteral(literalCell, localeFormat) {
5859
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5859
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5860
+ ? literalCell.content
5861
+ : literalCell.parsedValue;
5860
5862
  const functionResult = { value, format: localeFormat.format };
5861
5863
  return createEvaluatedCell(functionResult, localeFormat.locale);
5862
5864
  }
@@ -5905,6 +5907,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5905
5907
  if (isEvaluationError(value)) {
5906
5908
  return errorCell(value, message);
5907
5909
  }
5910
+ if (value === null) {
5911
+ return emptyCell(format);
5912
+ }
5908
5913
  if (isTextFormat(format)) {
5909
5914
  // TO DO:
5910
5915
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5912,9 +5917,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5912
5917
  // to interpret the value as a number.
5913
5918
  return textCell(toString(value), format, formattedValue);
5914
5919
  }
5915
- if (value === null) {
5916
- return emptyCell(format);
5917
- }
5918
5920
  if (typeof value === "number") {
5919
5921
  if (isDateTimeFormat(format || "")) {
5920
5922
  return dateTimeCell(value, format, formattedValue);
@@ -10366,6 +10368,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10366
10368
  if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) {
10367
10369
  continue;
10368
10370
  }
10371
+ const yAxisScale = chart.scales[dataset.yAxisID];
10369
10372
  for (let i = 0; i < dataset._parsed.length; i++) {
10370
10373
  const parsedValue = dataset._parsed[i];
10371
10374
  const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
@@ -10376,10 +10379,18 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10376
10379
  const xPosition = point.x;
10377
10380
  let yPosition = 0;
10378
10381
  if (chart.config.type === "line" || chart.config.type === "radar") {
10379
- yPosition = point.y - 10;
10382
+ yPosition = value < 0 ? point.y + 10 : point.y - 10;
10380
10383
  }
10381
10384
  else {
10382
- yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10385
+ const yZeroLine = yAxisScale.getPixelForValue(0);
10386
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10387
+ const textHeight = 12; // ChartJS default text height
10388
+ if (distanceFromAxisOrigin < textHeight) {
10389
+ yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10390
+ }
10391
+ else {
10392
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10393
+ }
10383
10394
  }
10384
10395
  yPosition = Math.min(yPosition, yMax);
10385
10396
  yPosition = Math.max(yPosition, yMin);
@@ -10389,7 +10400,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10389
10400
  }
10390
10401
  for (const otherPosition of textsPositions[xPosition] || []) {
10391
10402
  if (Math.abs(otherPosition - yPosition) < 13) {
10392
- yPosition = otherPosition - 13;
10403
+ yPosition = value < 0 ? otherPosition + 13 : otherPosition - 13;
10393
10404
  }
10394
10405
  }
10395
10406
  textsPositions[xPosition].push(yPosition);
@@ -10408,6 +10419,8 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10408
10419
  if (isTrendLineAxis(dataset.xAxisID)) {
10409
10420
  return; // ignore trend lines
10410
10421
  }
10422
+ const xAxisScale = chart.scales[dataset.xAxisID];
10423
+ const xZeroLine = xAxisScale.getPixelForValue(0);
10411
10424
  for (let i = 0; i < dataset._parsed.length; i++) {
10412
10425
  const value = Number(dataset._parsed[i].x);
10413
10426
  if (isNaN(value)) {
@@ -10416,17 +10429,27 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10416
10429
  const displayValue = options.callback(value, dataset, i);
10417
10430
  const point = dataset.data[i];
10418
10431
  const yPosition = point.y;
10419
- let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10420
- xPosition = Math.min(xPosition, xMax);
10421
- xPosition = Math.max(xPosition, xMin);
10432
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10433
+ const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10434
+ const PADDING = 3;
10435
+ let xPosition;
10436
+ if (distanceFromAxisOrigin < textWidth) {
10437
+ xPosition =
10438
+ value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10439
+ }
10440
+ else {
10441
+ xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10442
+ xPosition = Math.min(xPosition, xMax);
10443
+ xPosition = Math.max(xPosition, xMin);
10444
+ }
10422
10445
  // Avoid overlapping texts with same Y
10423
10446
  if (!textsPositions[yPosition]) {
10424
10447
  textsPositions[yPosition] = [];
10425
10448
  }
10426
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10427
10449
  for (const otherPosition of textsPositions[yPosition]) {
10428
10450
  if (Math.abs(otherPosition - xPosition) < textWidth) {
10429
- xPosition = otherPosition + textWidth + 3;
10451
+ xPosition =
10452
+ value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10430
10453
  }
10431
10454
  }
10432
10455
  textsPositions[yPosition].push(xPosition);
@@ -10448,10 +10471,22 @@ function drawPieChartValues(chart, options, ctx) {
10448
10471
  const midAngle = (startAngle + endAngle) / 2;
10449
10472
  const midRadius = (innerRadius + outerRadius) / 2;
10450
10473
  const x = bar.x + midRadius * Math.cos(midAngle);
10451
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10474
+ const y = bar.y + midRadius * Math.sin(midAngle);
10475
+ const displayValue = options.callback(value, dataset, i);
10476
+ const textHeight = 12; // ChartJS default
10477
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10478
+ const radius = outerRadius - innerRadius;
10479
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10480
+ if (textWidth >= radius || radius < textHeight) {
10481
+ continue;
10482
+ }
10483
+ const sliceAngle = endAngle - startAngle;
10484
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10485
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10486
+ continue;
10487
+ }
10452
10488
  ctx.fillStyle = chartFontColor(options.background);
10453
10489
  ctx.strokeStyle = options.background || "#ffffff";
10454
- const displayValue = options.callback(value, dataset, i);
10455
10490
  drawTextWithBackground(displayValue, x, y, ctx);
10456
10491
  }
10457
10492
  }
@@ -26217,7 +26252,7 @@ class XlsxBaseExtractor {
26217
26252
  */
26218
26253
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26219
26254
  if (optionalArgs?.required) {
26220
- if (optionalArgs?.default) {
26255
+ if (optionalArgs?.default !== undefined) {
26221
26256
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26222
26257
  }
26223
26258
  else {
@@ -30098,7 +30133,9 @@ function getPyramidChartShowValues(definition, args) {
30098
30133
  background: definition.background,
30099
30134
  callback: (value, dataset) => {
30100
30135
  value = Math.abs(Number(value));
30101
- return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30136
+ return value === 0
30137
+ ? ""
30138
+ : formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30102
30139
  },
30103
30140
  };
30104
30141
  }
@@ -33414,19 +33451,26 @@ class FilterMenu extends owl.Component {
33414
33451
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33415
33452
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33416
33453
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33417
- const strValues = [...cellValues, ...filterValues];
33418
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33419
- // Set with lowercase values to avoid duplicates
33420
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33421
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33422
- return sortedValues.map((normalizedValue) => {
33423
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33424
- -1;
33425
- return {
33426
- checked,
33427
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33428
- };
33429
- });
33454
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33455
+ const set = new Set();
33456
+ const values = [];
33457
+ const addValue = (value) => {
33458
+ const normalizedValue = toLowerCase(value);
33459
+ if (!set.has(normalizedValue)) {
33460
+ values.push({
33461
+ string: value || "",
33462
+ checked: !normalizedFilteredValues.has(normalizedValue),
33463
+ normalizedValue,
33464
+ });
33465
+ set.add(normalizedValue);
33466
+ }
33467
+ };
33468
+ cellValues.forEach(addValue);
33469
+ filterValues.forEach(addValue);
33470
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33471
+ numeric: true,
33472
+ sensitivity: "base",
33473
+ }));
33430
33474
  }
33431
33475
  checkValue(value) {
33432
33476
  this.state.selectedValue = value.string;
@@ -34760,6 +34804,10 @@ const REMOVE_ROWS_ACTION = (env) => {
34760
34804
  });
34761
34805
  };
34762
34806
  const CAN_REMOVE_COLUMNS_ROWS = (dimension, env) => {
34807
+ if ((dimension === "COL" && env.model.getters.getActiveRows().size > 0) ||
34808
+ (dimension === "ROW" && env.model.getters.getActiveCols().size > 0)) {
34809
+ return false;
34810
+ }
34763
34811
  const sheetId = env.model.getters.getActiveSheetId();
34764
34812
  const selectedElements = env.model.getters.getElementsFromSelection(dimension);
34765
34813
  const includesAllVisibleHeaders = env.model.getters.checkElementsIncludeAllVisibleHeaders(sheetId, dimension, selectedElements);
@@ -37739,11 +37787,11 @@ class OTRegistry extends Registry {
37739
37787
  * transformation function given
37740
37788
  */
37741
37789
  addTransformation(executed, toTransforms, fn) {
37742
- for (let toTransform of toTransforms) {
37743
- if (!this.content[toTransform]) {
37744
- this.content[toTransform] = new Map();
37745
- }
37746
- this.content[toTransform].set(executed, fn);
37790
+ if (!this.content[executed]) {
37791
+ this.content[executed] = new Map();
37792
+ }
37793
+ for (const toTransform of toTransforms) {
37794
+ this.content[executed].set(toTransform, fn);
37747
37795
  }
37748
37796
  return this;
37749
37797
  }
@@ -37752,7 +37800,7 @@ class OTRegistry extends Registry {
37752
37800
  * that the executed command happened.
37753
37801
  */
37754
37802
  getTransformation(toTransform, executed) {
37755
- return this.content[toTransform] && this.content[toTransform].get(executed);
37803
+ return this.content[executed] && this.content[executed].get(toTransform);
37756
37804
  }
37757
37805
  }
37758
37806
  const otRegistry = new OTRegistry();
@@ -58593,7 +58641,9 @@ class TablePlugin extends CorePlugin {
58593
58641
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58594
58642
  const union = this.getters.getRangesUnion(ranges);
58595
58643
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58596
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58644
+ if (mergesInTarget.length) {
58645
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58646
+ }
58597
58647
  const id = this.consumeNextId();
58598
58648
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58599
58649
  const newTable = cmd.tableType === "dynamic"
@@ -58692,14 +58742,16 @@ class TablePlugin extends CorePlugin {
58692
58742
  const zoneToCheckIfEmpty = direction === "down"
58693
58743
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58694
58744
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58695
- for (const position of positions(zoneToCheckIfEmpty)) {
58696
- const cellPosition = { sheetId, ...position };
58697
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58698
- const cellContent = this.getters.getCell(cellPosition)?.content;
58699
- if (cellContent ||
58700
- this.getters.isInMerge(cellPosition) ||
58701
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58702
- return "none";
58745
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58746
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58747
+ const cellPosition = { sheetId, col, row };
58748
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58749
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58750
+ if (cellContent ||
58751
+ this.getters.isInMerge(cellPosition) ||
58752
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58753
+ return "none";
58754
+ }
58703
58755
  }
58704
58756
  }
58705
58757
  return direction;
@@ -64742,10 +64794,20 @@ function transform(toTransform, executed) {
64742
64794
  */
64743
64795
  function transformAll(toTransform, executed) {
64744
64796
  let transformedCommands = [...toTransform];
64797
+ const possibleTransformations = new Set(otRegistry.getKeys());
64745
64798
  for (const executedCommand of executed) {
64746
- transformedCommands = transformedCommands
64747
- .map((cmd) => transform(cmd, executedCommand))
64748
- .filter(isDefined);
64799
+ // If the executed command is not in the registry, we skip it
64800
+ // because we know there won't be any transformation impacting the
64801
+ // commands to transform.
64802
+ if (possibleTransformations.has(executedCommand.type)) {
64803
+ transformedCommands = transformedCommands.reduce((acc, cmd) => {
64804
+ const transformed = transform(cmd, executedCommand);
64805
+ if (transformed) {
64806
+ acc.push(transformed);
64807
+ }
64808
+ return acc;
64809
+ }, []);
64810
+ }
64749
64811
  }
64750
64812
  return transformedCommands;
64751
64813
  }
@@ -66434,7 +66496,7 @@ class SheetUIPlugin extends UIPlugin {
66434
66496
  }
66435
66497
  const position = this.getters.getCellPosition(cell.id);
66436
66498
  const colSize = this.getters.getColSize(sheetId, position.col);
66437
- if (cell.isFormula) {
66499
+ if (cell.isFormula || this.getters.getArrayFormulaSpreadingOn(position)) {
66438
66500
  const content = this.getters.getEvaluatedCell(position).formattedValue;
66439
66501
  const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
66440
66502
  if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
@@ -68073,9 +68135,10 @@ class FilterEvaluationPlugin extends UIPlugin {
68073
68135
  const filteredZone = filter.filteredRange?.zone;
68074
68136
  if (!filteredValues || !filteredZone)
68075
68137
  continue;
68138
+ const filteredValuesSet = new Set(filteredValues);
68076
68139
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
68077
68140
  const value = this.getCellValueAsString(sheetId, filter.col, row);
68078
- if (filteredValues.includes(value)) {
68141
+ if (filteredValuesSet.has(value)) {
68079
68142
  hiddenRows.add(row);
68080
68143
  }
68081
68144
  }
@@ -76989,6 +77052,6 @@ exports.tokenColors = tokenColors;
76989
77052
  exports.tokenize = tokenize;
76990
77053
 
76991
77054
 
76992
- __info__.version = "18.2.16";
76993
- __info__.date = "2025-06-06T09:32:04.909Z";
76994
- __info__.hash = "7ee118c";
77055
+ __info__.version = "18.2.18";
77056
+ __info__.date = "2025-06-19T18:24:41.051Z";
77057
+ __info__.hash = "024c134";