@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
  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);
@@ -10364,6 +10366,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10364
10366
  if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) {
10365
10367
  continue;
10366
10368
  }
10369
+ const yAxisScale = chart.scales[dataset.yAxisID];
10367
10370
  for (let i = 0; i < dataset._parsed.length; i++) {
10368
10371
  const parsedValue = dataset._parsed[i];
10369
10372
  const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
@@ -10374,10 +10377,18 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10374
10377
  const xPosition = point.x;
10375
10378
  let yPosition = 0;
10376
10379
  if (chart.config.type === "line" || chart.config.type === "radar") {
10377
- yPosition = point.y - 10;
10380
+ yPosition = value < 0 ? point.y + 10 : point.y - 10;
10378
10381
  }
10379
10382
  else {
10380
- yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10383
+ const yZeroLine = yAxisScale.getPixelForValue(0);
10384
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10385
+ const textHeight = 12; // ChartJS default text height
10386
+ if (distanceFromAxisOrigin < textHeight) {
10387
+ yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10388
+ }
10389
+ else {
10390
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10391
+ }
10381
10392
  }
10382
10393
  yPosition = Math.min(yPosition, yMax);
10383
10394
  yPosition = Math.max(yPosition, yMin);
@@ -10387,7 +10398,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10387
10398
  }
10388
10399
  for (const otherPosition of textsPositions[xPosition] || []) {
10389
10400
  if (Math.abs(otherPosition - yPosition) < 13) {
10390
- yPosition = otherPosition - 13;
10401
+ yPosition = value < 0 ? otherPosition + 13 : otherPosition - 13;
10391
10402
  }
10392
10403
  }
10393
10404
  textsPositions[xPosition].push(yPosition);
@@ -10406,6 +10417,8 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10406
10417
  if (isTrendLineAxis(dataset.xAxisID)) {
10407
10418
  return; // ignore trend lines
10408
10419
  }
10420
+ const xAxisScale = chart.scales[dataset.xAxisID];
10421
+ const xZeroLine = xAxisScale.getPixelForValue(0);
10409
10422
  for (let i = 0; i < dataset._parsed.length; i++) {
10410
10423
  const value = Number(dataset._parsed[i].x);
10411
10424
  if (isNaN(value)) {
@@ -10414,17 +10427,27 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10414
10427
  const displayValue = options.callback(value, dataset, i);
10415
10428
  const point = dataset.data[i];
10416
10429
  const yPosition = point.y;
10417
- let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10418
- xPosition = Math.min(xPosition, xMax);
10419
- xPosition = Math.max(xPosition, xMin);
10430
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10431
+ const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10432
+ const PADDING = 3;
10433
+ let xPosition;
10434
+ if (distanceFromAxisOrigin < textWidth) {
10435
+ xPosition =
10436
+ value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10437
+ }
10438
+ else {
10439
+ xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10440
+ xPosition = Math.min(xPosition, xMax);
10441
+ xPosition = Math.max(xPosition, xMin);
10442
+ }
10420
10443
  // Avoid overlapping texts with same Y
10421
10444
  if (!textsPositions[yPosition]) {
10422
10445
  textsPositions[yPosition] = [];
10423
10446
  }
10424
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10425
10447
  for (const otherPosition of textsPositions[yPosition]) {
10426
10448
  if (Math.abs(otherPosition - xPosition) < textWidth) {
10427
- xPosition = otherPosition + textWidth + 3;
10449
+ xPosition =
10450
+ value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10428
10451
  }
10429
10452
  }
10430
10453
  textsPositions[yPosition].push(xPosition);
@@ -10446,10 +10469,22 @@ function drawPieChartValues(chart, options, ctx) {
10446
10469
  const midAngle = (startAngle + endAngle) / 2;
10447
10470
  const midRadius = (innerRadius + outerRadius) / 2;
10448
10471
  const x = bar.x + midRadius * Math.cos(midAngle);
10449
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10472
+ const y = bar.y + midRadius * Math.sin(midAngle);
10473
+ const displayValue = options.callback(value, dataset, i);
10474
+ const textHeight = 12; // ChartJS default
10475
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10476
+ const radius = outerRadius - innerRadius;
10477
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10478
+ if (textWidth >= radius || radius < textHeight) {
10479
+ continue;
10480
+ }
10481
+ const sliceAngle = endAngle - startAngle;
10482
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10483
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10484
+ continue;
10485
+ }
10450
10486
  ctx.fillStyle = chartFontColor(options.background);
10451
10487
  ctx.strokeStyle = options.background || "#ffffff";
10452
- const displayValue = options.callback(value, dataset, i);
10453
10488
  drawTextWithBackground(displayValue, x, y, ctx);
10454
10489
  }
10455
10490
  }
@@ -26215,7 +26250,7 @@ class XlsxBaseExtractor {
26215
26250
  */
26216
26251
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26217
26252
  if (optionalArgs?.required) {
26218
- if (optionalArgs?.default) {
26253
+ if (optionalArgs?.default !== undefined) {
26219
26254
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26220
26255
  }
26221
26256
  else {
@@ -30096,7 +30131,9 @@ function getPyramidChartShowValues(definition, args) {
30096
30131
  background: definition.background,
30097
30132
  callback: (value, dataset) => {
30098
30133
  value = Math.abs(Number(value));
30099
- return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30134
+ return value === 0
30135
+ ? ""
30136
+ : formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30100
30137
  },
30101
30138
  };
30102
30139
  }
@@ -33412,19 +33449,26 @@ class FilterMenu extends Component {
33412
33449
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33413
33450
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33414
33451
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33415
- const strValues = [...cellValues, ...filterValues];
33416
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33417
- // Set with lowercase values to avoid duplicates
33418
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33419
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33420
- return sortedValues.map((normalizedValue) => {
33421
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33422
- -1;
33423
- return {
33424
- checked,
33425
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33426
- };
33427
- });
33452
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33453
+ const set = new Set();
33454
+ const values = [];
33455
+ const addValue = (value) => {
33456
+ const normalizedValue = toLowerCase(value);
33457
+ if (!set.has(normalizedValue)) {
33458
+ values.push({
33459
+ string: value || "",
33460
+ checked: !normalizedFilteredValues.has(normalizedValue),
33461
+ normalizedValue,
33462
+ });
33463
+ set.add(normalizedValue);
33464
+ }
33465
+ };
33466
+ cellValues.forEach(addValue);
33467
+ filterValues.forEach(addValue);
33468
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33469
+ numeric: true,
33470
+ sensitivity: "base",
33471
+ }));
33428
33472
  }
33429
33473
  checkValue(value) {
33430
33474
  this.state.selectedValue = value.string;
@@ -34758,6 +34802,10 @@ const REMOVE_ROWS_ACTION = (env) => {
34758
34802
  });
34759
34803
  };
34760
34804
  const CAN_REMOVE_COLUMNS_ROWS = (dimension, env) => {
34805
+ if ((dimension === "COL" && env.model.getters.getActiveRows().size > 0) ||
34806
+ (dimension === "ROW" && env.model.getters.getActiveCols().size > 0)) {
34807
+ return false;
34808
+ }
34761
34809
  const sheetId = env.model.getters.getActiveSheetId();
34762
34810
  const selectedElements = env.model.getters.getElementsFromSelection(dimension);
34763
34811
  const includesAllVisibleHeaders = env.model.getters.checkElementsIncludeAllVisibleHeaders(sheetId, dimension, selectedElements);
@@ -37737,11 +37785,11 @@ class OTRegistry extends Registry {
37737
37785
  * transformation function given
37738
37786
  */
37739
37787
  addTransformation(executed, toTransforms, fn) {
37740
- for (let toTransform of toTransforms) {
37741
- if (!this.content[toTransform]) {
37742
- this.content[toTransform] = new Map();
37743
- }
37744
- this.content[toTransform].set(executed, fn);
37788
+ if (!this.content[executed]) {
37789
+ this.content[executed] = new Map();
37790
+ }
37791
+ for (const toTransform of toTransforms) {
37792
+ this.content[executed].set(toTransform, fn);
37745
37793
  }
37746
37794
  return this;
37747
37795
  }
@@ -37750,7 +37798,7 @@ class OTRegistry extends Registry {
37750
37798
  * that the executed command happened.
37751
37799
  */
37752
37800
  getTransformation(toTransform, executed) {
37753
- return this.content[toTransform] && this.content[toTransform].get(executed);
37801
+ return this.content[executed] && this.content[executed].get(toTransform);
37754
37802
  }
37755
37803
  }
37756
37804
  const otRegistry = new OTRegistry();
@@ -58591,7 +58639,9 @@ class TablePlugin extends CorePlugin {
58591
58639
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58592
58640
  const union = this.getters.getRangesUnion(ranges);
58593
58641
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58594
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58642
+ if (mergesInTarget.length) {
58643
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58644
+ }
58595
58645
  const id = this.consumeNextId();
58596
58646
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58597
58647
  const newTable = cmd.tableType === "dynamic"
@@ -58690,14 +58740,16 @@ class TablePlugin extends CorePlugin {
58690
58740
  const zoneToCheckIfEmpty = direction === "down"
58691
58741
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58692
58742
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58693
- for (const position of positions(zoneToCheckIfEmpty)) {
58694
- const cellPosition = { sheetId, ...position };
58695
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58696
- const cellContent = this.getters.getCell(cellPosition)?.content;
58697
- if (cellContent ||
58698
- this.getters.isInMerge(cellPosition) ||
58699
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58700
- return "none";
58743
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58744
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58745
+ const cellPosition = { sheetId, col, row };
58746
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58747
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58748
+ if (cellContent ||
58749
+ this.getters.isInMerge(cellPosition) ||
58750
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58751
+ return "none";
58752
+ }
58701
58753
  }
58702
58754
  }
58703
58755
  return direction;
@@ -64740,10 +64792,20 @@ function transform(toTransform, executed) {
64740
64792
  */
64741
64793
  function transformAll(toTransform, executed) {
64742
64794
  let transformedCommands = [...toTransform];
64795
+ const possibleTransformations = new Set(otRegistry.getKeys());
64743
64796
  for (const executedCommand of executed) {
64744
- transformedCommands = transformedCommands
64745
- .map((cmd) => transform(cmd, executedCommand))
64746
- .filter(isDefined);
64797
+ // If the executed command is not in the registry, we skip it
64798
+ // because we know there won't be any transformation impacting the
64799
+ // commands to transform.
64800
+ if (possibleTransformations.has(executedCommand.type)) {
64801
+ transformedCommands = transformedCommands.reduce((acc, cmd) => {
64802
+ const transformed = transform(cmd, executedCommand);
64803
+ if (transformed) {
64804
+ acc.push(transformed);
64805
+ }
64806
+ return acc;
64807
+ }, []);
64808
+ }
64747
64809
  }
64748
64810
  return transformedCommands;
64749
64811
  }
@@ -66432,7 +66494,7 @@ class SheetUIPlugin extends UIPlugin {
66432
66494
  }
66433
66495
  const position = this.getters.getCellPosition(cell.id);
66434
66496
  const colSize = this.getters.getColSize(sheetId, position.col);
66435
- if (cell.isFormula) {
66497
+ if (cell.isFormula || this.getters.getArrayFormulaSpreadingOn(position)) {
66436
66498
  const content = this.getters.getEvaluatedCell(position).formattedValue;
66437
66499
  const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
66438
66500
  if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
@@ -68071,9 +68133,10 @@ class FilterEvaluationPlugin extends UIPlugin {
68071
68133
  const filteredZone = filter.filteredRange?.zone;
68072
68134
  if (!filteredValues || !filteredZone)
68073
68135
  continue;
68136
+ const filteredValuesSet = new Set(filteredValues);
68074
68137
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
68075
68138
  const value = this.getCellValueAsString(sheetId, filter.col, row);
68076
- if (filteredValues.includes(value)) {
68139
+ if (filteredValuesSet.has(value)) {
68077
68140
  hiddenRows.add(row);
68078
68141
  }
68079
68142
  }
@@ -76942,6 +77005,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
76942
77005
  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 };
76943
77006
 
76944
77007
 
76945
- __info__.version = "18.2.16";
76946
- __info__.date = "2025-06-06T09:32:04.909Z";
76947
- __info__.hash = "7ee118c";
77008
+ __info__.version = "18.2.18";
77009
+ __info__.date = "2025-06-19T18:24:41.051Z";
77010
+ __info__.hash = "024c134";