@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
  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);
@@ -10200,6 +10202,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10200
10202
  if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) {
10201
10203
  continue;
10202
10204
  }
10205
+ const yAxisScale = chart.scales[dataset.yAxisID];
10203
10206
  for (let i = 0; i < dataset._parsed.length; i++) {
10204
10207
  const parsedValue = dataset._parsed[i];
10205
10208
  const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
@@ -10210,10 +10213,18 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10210
10213
  const xPosition = point.x;
10211
10214
  let yPosition = 0;
10212
10215
  if (chart.config.type === "line" || chart.config.type === "radar") {
10213
- yPosition = point.y - 10;
10216
+ yPosition = value < 0 ? point.y + 10 : point.y - 10;
10214
10217
  }
10215
10218
  else {
10216
- yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10219
+ const yZeroLine = yAxisScale.getPixelForValue(0);
10220
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
10221
+ const textHeight = 12; // ChartJS default text height
10222
+ if (distanceFromAxisOrigin < textHeight) {
10223
+ yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
10224
+ }
10225
+ else {
10226
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
10227
+ }
10217
10228
  }
10218
10229
  yPosition = Math.min(yPosition, yMax);
10219
10230
  yPosition = Math.max(yPosition, yMin);
@@ -10223,7 +10234,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
10223
10234
  }
10224
10235
  for (const otherPosition of textsPositions[xPosition] || []) {
10225
10236
  if (Math.abs(otherPosition - yPosition) < 13) {
10226
- yPosition = otherPosition - 13;
10237
+ yPosition = value < 0 ? otherPosition + 13 : otherPosition - 13;
10227
10238
  }
10228
10239
  }
10229
10240
  textsPositions[xPosition].push(yPosition);
@@ -10242,6 +10253,8 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10242
10253
  if (isTrendLineAxis(dataset.xAxisID)) {
10243
10254
  return; // ignore trend lines
10244
10255
  }
10256
+ const xAxisScale = chart.scales[dataset.xAxisID];
10257
+ const xZeroLine = xAxisScale.getPixelForValue(0);
10245
10258
  for (let i = 0; i < dataset._parsed.length; i++) {
10246
10259
  const value = Number(dataset._parsed[i].x);
10247
10260
  if (isNaN(value)) {
@@ -10250,17 +10263,27 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10250
10263
  const displayValue = options.callback(value, dataset, i);
10251
10264
  const point = dataset.data[i];
10252
10265
  const yPosition = point.y;
10253
- let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10254
- xPosition = Math.min(xPosition, xMax);
10255
- xPosition = Math.max(xPosition, xMin);
10266
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10267
+ const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
10268
+ const PADDING = 3;
10269
+ let xPosition;
10270
+ if (distanceFromAxisOrigin < textWidth) {
10271
+ xPosition =
10272
+ value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
10273
+ }
10274
+ else {
10275
+ xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
10276
+ xPosition = Math.min(xPosition, xMax);
10277
+ xPosition = Math.max(xPosition, xMin);
10278
+ }
10256
10279
  // Avoid overlapping texts with same Y
10257
10280
  if (!textsPositions[yPosition]) {
10258
10281
  textsPositions[yPosition] = [];
10259
10282
  }
10260
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
10261
10283
  for (const otherPosition of textsPositions[yPosition]) {
10262
10284
  if (Math.abs(otherPosition - xPosition) < textWidth) {
10263
- xPosition = otherPosition + textWidth + 3;
10285
+ xPosition =
10286
+ value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
10264
10287
  }
10265
10288
  }
10266
10289
  textsPositions[yPosition].push(xPosition);
@@ -10282,10 +10305,22 @@ function drawPieChartValues(chart, options, ctx) {
10282
10305
  const midAngle = (startAngle + endAngle) / 2;
10283
10306
  const midRadius = (innerRadius + outerRadius) / 2;
10284
10307
  const x = bar.x + midRadius * Math.cos(midAngle);
10285
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10308
+ const y = bar.y + midRadius * Math.sin(midAngle);
10309
+ const displayValue = options.callback(value, dataset, i);
10310
+ const textHeight = 12; // ChartJS default
10311
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10312
+ const radius = outerRadius - innerRadius;
10313
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10314
+ if (textWidth >= radius || radius < textHeight) {
10315
+ continue;
10316
+ }
10317
+ const sliceAngle = endAngle - startAngle;
10318
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10319
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10320
+ continue;
10321
+ }
10286
10322
  ctx.fillStyle = chartFontColor(options.background);
10287
10323
  ctx.strokeStyle = options.background || "#ffffff";
10288
- const displayValue = options.callback(value, dataset, i);
10289
10324
  drawTextWithBackground(displayValue, x, y, ctx);
10290
10325
  }
10291
10326
  }
@@ -26191,7 +26226,7 @@ class XlsxBaseExtractor {
26191
26226
  */
26192
26227
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
26193
26228
  if (optionalArgs?.required) {
26194
- if (optionalArgs?.default) {
26229
+ if (optionalArgs?.default !== undefined) {
26195
26230
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
26196
26231
  }
26197
26232
  else {
@@ -30064,7 +30099,9 @@ function getPyramidChartShowValues(definition, args) {
30064
30099
  background: definition.background,
30065
30100
  callback: (value, dataset) => {
30066
30101
  value = Math.abs(Number(value));
30067
- return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30102
+ return value === 0
30103
+ ? ""
30104
+ : formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
30068
30105
  },
30069
30106
  };
30070
30107
  }
@@ -33213,19 +33250,26 @@ class FilterMenu extends Component {
33213
33250
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
33214
33251
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
33215
33252
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
33216
- const strValues = [...cellValues, ...filterValues];
33217
- const normalizedFilteredValues = filterValues.map(toLowerCase);
33218
- // Set with lowercase values to avoid duplicates
33219
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
33220
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
33221
- return sortedValues.map((normalizedValue) => {
33222
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
33223
- -1;
33224
- return {
33225
- checked,
33226
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
33227
- };
33228
- });
33253
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
33254
+ const set = new Set();
33255
+ const values = [];
33256
+ const addValue = (value) => {
33257
+ const normalizedValue = toLowerCase(value);
33258
+ if (!set.has(normalizedValue)) {
33259
+ values.push({
33260
+ string: value || "",
33261
+ checked: !normalizedFilteredValues.has(normalizedValue),
33262
+ normalizedValue,
33263
+ });
33264
+ set.add(normalizedValue);
33265
+ }
33266
+ };
33267
+ cellValues.forEach(addValue);
33268
+ filterValues.forEach(addValue);
33269
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
33270
+ numeric: true,
33271
+ sensitivity: "base",
33272
+ }));
33229
33273
  }
33230
33274
  checkValue(value) {
33231
33275
  this.state.selectedValue = value.string;
@@ -34557,6 +34601,10 @@ const REMOVE_ROWS_ACTION = (env) => {
34557
34601
  });
34558
34602
  };
34559
34603
  const CAN_REMOVE_COLUMNS_ROWS = (dimension, env) => {
34604
+ if ((dimension === "COL" && env.model.getters.getActiveRows().size > 0) ||
34605
+ (dimension === "ROW" && env.model.getters.getActiveCols().size > 0)) {
34606
+ return false;
34607
+ }
34560
34608
  const sheetId = env.model.getters.getActiveSheetId();
34561
34609
  const selectedElements = env.model.getters.getElementsFromSelection(dimension);
34562
34610
  const includesAllVisibleHeaders = env.model.getters.checkElementsIncludeAllVisibleHeaders(sheetId, dimension, selectedElements);
@@ -37536,11 +37584,11 @@ class OTRegistry extends Registry {
37536
37584
  * transformation function given
37537
37585
  */
37538
37586
  addTransformation(executed, toTransforms, fn) {
37539
- for (let toTransform of toTransforms) {
37540
- if (!this.content[toTransform]) {
37541
- this.content[toTransform] = new Map();
37542
- }
37543
- this.content[toTransform].set(executed, fn);
37587
+ if (!this.content[executed]) {
37588
+ this.content[executed] = new Map();
37589
+ }
37590
+ for (const toTransform of toTransforms) {
37591
+ this.content[executed].set(toTransform, fn);
37544
37592
  }
37545
37593
  return this;
37546
37594
  }
@@ -37549,7 +37597,7 @@ class OTRegistry extends Registry {
37549
37597
  * that the executed command happened.
37550
37598
  */
37551
37599
  getTransformation(toTransform, executed) {
37552
- return this.content[toTransform] && this.content[toTransform].get(executed);
37600
+ return this.content[executed] && this.content[executed].get(toTransform);
37553
37601
  }
37554
37602
  }
37555
37603
  const otRegistry = new OTRegistry();
@@ -58093,7 +58141,9 @@ class TablePlugin extends CorePlugin {
58093
58141
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
58094
58142
  const union = this.getters.getRangesUnion(ranges);
58095
58143
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
58096
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58144
+ if (mergesInTarget.length) {
58145
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
58146
+ }
58097
58147
  const id = `${nextTableId++}`;
58098
58148
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
58099
58149
  const newTable = cmd.tableType === "dynamic"
@@ -58204,14 +58254,16 @@ class TablePlugin extends CorePlugin {
58204
58254
  const zoneToCheckIfEmpty = direction === "down"
58205
58255
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
58206
58256
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
58207
- for (const position of positions(zoneToCheckIfEmpty)) {
58208
- const cellPosition = { sheetId, ...position };
58209
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58210
- const cellContent = this.getters.getCell(cellPosition)?.content;
58211
- if (cellContent ||
58212
- this.getters.isInMerge(cellPosition) ||
58213
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
58214
- return "none";
58257
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
58258
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
58259
+ const cellPosition = { sheetId, col, row };
58260
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
58261
+ const cellContent = this.getters.getCell(cellPosition)?.content;
58262
+ if (cellContent ||
58263
+ this.getters.isInMerge(cellPosition) ||
58264
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
58265
+ return "none";
58266
+ }
58215
58267
  }
58216
58268
  }
58217
58269
  return direction;
@@ -64240,10 +64292,20 @@ function transform(toTransform, executed) {
64240
64292
  */
64241
64293
  function transformAll(toTransform, executed) {
64242
64294
  let transformedCommands = [...toTransform];
64295
+ const possibleTransformations = new Set(otRegistry.getKeys());
64243
64296
  for (const executedCommand of executed) {
64244
- transformedCommands = transformedCommands
64245
- .map((cmd) => transform(cmd, executedCommand))
64246
- .filter(isDefined);
64297
+ // If the executed command is not in the registry, we skip it
64298
+ // because we know there won't be any transformation impacting the
64299
+ // commands to transform.
64300
+ if (possibleTransformations.has(executedCommand.type)) {
64301
+ transformedCommands = transformedCommands.reduce((acc, cmd) => {
64302
+ const transformed = transform(cmd, executedCommand);
64303
+ if (transformed) {
64304
+ acc.push(transformed);
64305
+ }
64306
+ return acc;
64307
+ }, []);
64308
+ }
64247
64309
  }
64248
64310
  return transformedCommands;
64249
64311
  }
@@ -65938,7 +66000,7 @@ class SheetUIPlugin extends UIPlugin {
65938
66000
  }
65939
66001
  const position = this.getters.getCellPosition(cell.id);
65940
66002
  const colSize = this.getters.getColSize(sheetId, position.col);
65941
- if (cell.isFormula) {
66003
+ if (cell.isFormula || this.getters.getArrayFormulaSpreadingOn(position)) {
65942
66004
  const content = this.getters.getEvaluatedCell(position).formattedValue;
65943
66005
  const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
65944
66006
  if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
@@ -67577,9 +67639,10 @@ class FilterEvaluationPlugin extends UIPlugin {
67577
67639
  const filteredZone = filter.filteredRange?.zone;
67578
67640
  if (!filteredValues || !filteredZone)
67579
67641
  continue;
67642
+ const filteredValuesSet = new Set(filteredValues);
67580
67643
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
67581
67644
  const value = this.getCellValueAsString(sheetId, filter.col, row);
67582
- if (filteredValues.includes(value)) {
67645
+ if (filteredValuesSet.has(value)) {
67583
67646
  hiddenRows.add(row);
67584
67647
  }
67585
67648
  }
@@ -76465,6 +76528,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
76465
76528
  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 };
76466
76529
 
76467
76530
 
76468
- __info__.version = "18.1.24";
76469
- __info__.date = "2025-06-06T09:31:57.011Z";
76470
- __info__.hash = "0e386b2";
76531
+ __info__.version = "18.1.26";
76532
+ __info__.date = "2025-06-19T18:21:37.648Z";
76533
+ __info__.hash = "06479d4";