@odoo/o-spreadsheet 18.3.7 → 18.3.9

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.3.7
6
- * @date 2025-06-06T09:31:27.123Z
7
- * @hash 05333f1
5
+ * @version 18.3.9
6
+ * @date 2025-06-19T18:24:02.754Z
7
+ * @hash a820230
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';
@@ -5880,7 +5880,9 @@ function isTextFormat(format) {
5880
5880
  }
5881
5881
 
5882
5882
  function evaluateLiteral(literalCell, localeFormat) {
5883
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5883
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5884
+ ? literalCell.content
5885
+ : literalCell.parsedValue;
5884
5886
  const functionResult = { value, format: localeFormat.format };
5885
5887
  return createEvaluatedCell(functionResult, localeFormat.locale);
5886
5888
  }
@@ -5929,6 +5931,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5929
5931
  if (isEvaluationError(value)) {
5930
5932
  return errorCell(value, message);
5931
5933
  }
5934
+ if (value === null) {
5935
+ return emptyCell(format);
5936
+ }
5932
5937
  if (isTextFormat(format)) {
5933
5938
  // TO DO:
5934
5939
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5936,9 +5941,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5936
5941
  // to interpret the value as a number.
5937
5942
  return textCell(toString(value), format, formattedValue);
5938
5943
  }
5939
- if (value === null) {
5940
- return emptyCell(format);
5941
- }
5942
5944
  if (typeof value === "number") {
5943
5945
  if (isDateTimeFormat(format || "")) {
5944
5946
  return dateTimeCell(value, format, formattedValue);
@@ -20949,6 +20951,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
20949
20951
  if (isTrendLineAxis(dataset.xAxisID) || dataset.hidden) {
20950
20952
  continue;
20951
20953
  }
20954
+ const yAxisScale = chart.scales[dataset.yAxisID];
20952
20955
  for (let i = 0; i < dataset._parsed.length; i++) {
20953
20956
  const parsedValue = dataset._parsed[i];
20954
20957
  const value = Number(chart.config.type === "radar" ? parsedValue.r : parsedValue.y);
@@ -20959,10 +20962,18 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
20959
20962
  const xPosition = point.x;
20960
20963
  let yPosition = 0;
20961
20964
  if (chart.config.type === "line" || chart.config.type === "radar") {
20962
- yPosition = point.y - 10;
20965
+ yPosition = value < 0 ? point.y + 10 : point.y - 10;
20963
20966
  }
20964
20967
  else {
20965
- yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
20968
+ const yZeroLine = yAxisScale.getPixelForValue(0);
20969
+ const distanceFromAxisOrigin = Math.abs(yZeroLine - point.y);
20970
+ const textHeight = 12; // ChartJS default text height
20971
+ if (distanceFromAxisOrigin < textHeight) {
20972
+ yPosition = value < 0 ? yZeroLine + textHeight / 2 : yZeroLine - textHeight / 2;
20973
+ }
20974
+ else {
20975
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
20976
+ }
20966
20977
  }
20967
20978
  yPosition = Math.min(yPosition, yMax);
20968
20979
  yPosition = Math.max(yPosition, yMin);
@@ -20972,7 +20983,7 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
20972
20983
  }
20973
20984
  for (const otherPosition of textsPositions[xPosition] || []) {
20974
20985
  if (Math.abs(otherPosition - yPosition) < 13) {
20975
- yPosition = otherPosition - 13;
20986
+ yPosition = value < 0 ? otherPosition + 13 : otherPosition - 13;
20976
20987
  }
20977
20988
  }
20978
20989
  textsPositions[xPosition].push(yPosition);
@@ -20991,6 +21002,8 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
20991
21002
  if (isTrendLineAxis(dataset.xAxisID)) {
20992
21003
  return; // ignore trend lines
20993
21004
  }
21005
+ const xAxisScale = chart.scales[dataset.xAxisID];
21006
+ const xZeroLine = xAxisScale.getPixelForValue(0);
20994
21007
  for (let i = 0; i < dataset._parsed.length; i++) {
20995
21008
  const value = Number(dataset._parsed[i].x);
20996
21009
  if (isNaN(value)) {
@@ -20999,17 +21012,27 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
20999
21012
  const displayValue = options.callback(value, dataset, i);
21000
21013
  const point = dataset.data[i];
21001
21014
  const yPosition = point.y;
21002
- let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
21003
- xPosition = Math.min(xPosition, xMax);
21004
- xPosition = Math.max(xPosition, xMin);
21015
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
21016
+ const distanceFromAxisOrigin = Math.abs(point.x - xZeroLine);
21017
+ const PADDING = 3;
21018
+ let xPosition;
21019
+ if (distanceFromAxisOrigin < textWidth) {
21020
+ xPosition =
21021
+ value < 0 ? xZeroLine - textWidth / 2 - PADDING : xZeroLine + textWidth / 2 + PADDING;
21022
+ }
21023
+ else {
21024
+ xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
21025
+ xPosition = Math.min(xPosition, xMax);
21026
+ xPosition = Math.max(xPosition, xMin);
21027
+ }
21005
21028
  // Avoid overlapping texts with same Y
21006
21029
  if (!textsPositions[yPosition]) {
21007
21030
  textsPositions[yPosition] = [];
21008
21031
  }
21009
- const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
21010
21032
  for (const otherPosition of textsPositions[yPosition]) {
21011
21033
  if (Math.abs(otherPosition - xPosition) < textWidth) {
21012
- xPosition = otherPosition + textWidth + 3;
21034
+ xPosition =
21035
+ value < 0 ? otherPosition - textWidth - PADDING : otherPosition + textWidth + PADDING;
21013
21036
  }
21014
21037
  }
21015
21038
  textsPositions[yPosition].push(xPosition);
@@ -21031,10 +21054,22 @@ function drawPieChartValues(chart, options, ctx) {
21031
21054
  const midAngle = (startAngle + endAngle) / 2;
21032
21055
  const midRadius = (innerRadius + outerRadius) / 2;
21033
21056
  const x = bar.x + midRadius * Math.cos(midAngle);
21034
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
21057
+ const y = bar.y + midRadius * Math.sin(midAngle);
21058
+ const displayValue = options.callback(value, dataset, i);
21059
+ const textHeight = 12; // ChartJS default
21060
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
21061
+ const radius = outerRadius - innerRadius;
21062
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
21063
+ if (textWidth >= radius || radius < textHeight) {
21064
+ continue;
21065
+ }
21066
+ const sliceAngle = endAngle - startAngle;
21067
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
21068
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
21069
+ continue;
21070
+ }
21035
21071
  ctx.fillStyle = chartFontColor(options.background);
21036
21072
  ctx.strokeStyle = options.background || "#ffffff";
21037
- const displayValue = options.callback(value, dataset, i);
21038
21073
  drawTextWithBackground(displayValue, x, y, ctx);
21039
21074
  }
21040
21075
  }
@@ -26384,7 +26419,9 @@ function getPyramidChartShowValues(definition, args) {
26384
26419
  background: definition.background,
26385
26420
  callback: (value, dataset) => {
26386
26421
  value = Math.abs(Number(value));
26387
- return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
26422
+ return value === 0
26423
+ ? ""
26424
+ : formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
26388
26425
  },
26389
26426
  };
26390
26427
  }
@@ -33170,7 +33207,7 @@ class XlsxBaseExtractor {
33170
33207
  */
33171
33208
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
33172
33209
  if (optionalArgs?.required) {
33173
- if (optionalArgs?.default) {
33210
+ if (optionalArgs?.default !== undefined) {
33174
33211
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
33175
33212
  }
33176
33213
  else {
@@ -36123,19 +36160,26 @@ class FilterMenu extends Component {
36123
36160
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
36124
36161
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
36125
36162
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
36126
- const strValues = [...cellValues, ...filterValues];
36127
- const normalizedFilteredValues = filterValues.map(toLowerCase);
36128
- // Set with lowercase values to avoid duplicates
36129
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
36130
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
36131
- return sortedValues.map((normalizedValue) => {
36132
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
36133
- -1;
36134
- return {
36135
- checked,
36136
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
36137
- };
36138
- });
36163
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
36164
+ const set = new Set();
36165
+ const values = [];
36166
+ const addValue = (value) => {
36167
+ const normalizedValue = toLowerCase(value);
36168
+ if (!set.has(normalizedValue)) {
36169
+ values.push({
36170
+ string: value || "",
36171
+ checked: !normalizedFilteredValues.has(normalizedValue),
36172
+ normalizedValue,
36173
+ });
36174
+ set.add(normalizedValue);
36175
+ }
36176
+ };
36177
+ cellValues.forEach(addValue);
36178
+ filterValues.forEach(addValue);
36179
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
36180
+ numeric: true,
36181
+ sensitivity: "base",
36182
+ }));
36139
36183
  }
36140
36184
  checkValue(value) {
36141
36185
  this.state.selectedValue = value.string;
@@ -37110,6 +37154,10 @@ const REMOVE_ROWS_ACTION = (env) => {
37110
37154
  });
37111
37155
  };
37112
37156
  const CAN_REMOVE_COLUMNS_ROWS = (dimension, env) => {
37157
+ if ((dimension === "COL" && env.model.getters.getActiveRows().size > 0) ||
37158
+ (dimension === "ROW" && env.model.getters.getActiveCols().size > 0)) {
37159
+ return false;
37160
+ }
37113
37161
  const sheetId = env.model.getters.getActiveSheetId();
37114
37162
  const selectedElements = env.model.getters.getElementsFromSelection(dimension);
37115
37163
  const includesAllVisibleHeaders = env.model.getters.checkElementsIncludeAllVisibleHeaders(sheetId, dimension, selectedElements);
@@ -40007,11 +40055,11 @@ class OTRegistry extends Registry {
40007
40055
  * transformation function given
40008
40056
  */
40009
40057
  addTransformation(executed, toTransforms, fn) {
40010
- for (let toTransform of toTransforms) {
40011
- if (!this.content[toTransform]) {
40012
- this.content[toTransform] = new Map();
40013
- }
40014
- this.content[toTransform].set(executed, fn);
40058
+ if (!this.content[executed]) {
40059
+ this.content[executed] = new Map();
40060
+ }
40061
+ for (const toTransform of toTransforms) {
40062
+ this.content[executed].set(toTransform, fn);
40015
40063
  }
40016
40064
  return this;
40017
40065
  }
@@ -40020,7 +40068,7 @@ class OTRegistry extends Registry {
40020
40068
  * that the executed command happened.
40021
40069
  */
40022
40070
  getTransformation(toTransform, executed) {
40023
- return this.content[toTransform] && this.content[toTransform].get(executed);
40071
+ return this.content[executed] && this.content[executed].get(toTransform);
40024
40072
  }
40025
40073
  }
40026
40074
  const otRegistry = new OTRegistry();
@@ -43310,6 +43358,12 @@ class Composer extends Component {
43310
43358
  useEffect(() => {
43311
43359
  this.processTokenAtCursor();
43312
43360
  }, () => [this.props.composerStore.editionMode !== "inactive"]);
43361
+ useEffect(() => {
43362
+ this.contentHelper.scrollSelectionIntoView();
43363
+ }, () => [
43364
+ this.props.composerStore.composerSelection.start,
43365
+ this.props.composerStore.composerSelection.end,
43366
+ ]);
43313
43367
  }
43314
43368
  // ---------------------------------------------------------------------------
43315
43369
  // Handlers
@@ -43532,6 +43586,7 @@ class Composer extends Component {
43532
43586
  // not main button, probably a context menu
43533
43587
  return;
43534
43588
  }
43589
+ this.debouncedHover.stopDebounce();
43535
43590
  this.contentHelper.removeSelection();
43536
43591
  }
43537
43592
  onMouseup() {
@@ -43610,7 +43665,6 @@ class Composer extends Component {
43610
43665
  const { start, end } = this.props.composerStore.composerSelection;
43611
43666
  this.contentHelper.selectRange(start, end);
43612
43667
  }
43613
- this.contentHelper.scrollSelectionIntoView();
43614
43668
  }
43615
43669
  this.shouldProcessInputEvents = true;
43616
43670
  }
@@ -61476,7 +61530,9 @@ class TablePlugin extends CorePlugin {
61476
61530
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
61477
61531
  const union = this.getters.getRangesUnion(ranges);
61478
61532
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
61479
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
61533
+ if (mergesInTarget.length) {
61534
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
61535
+ }
61480
61536
  const id = this.consumeNextId();
61481
61537
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
61482
61538
  const newTable = cmd.tableType === "dynamic"
@@ -61575,14 +61631,16 @@ class TablePlugin extends CorePlugin {
61575
61631
  const zoneToCheckIfEmpty = direction === "down"
61576
61632
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
61577
61633
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
61578
- for (const position of positions(zoneToCheckIfEmpty)) {
61579
- const cellPosition = { sheetId, ...position };
61580
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
61581
- const cellContent = this.getters.getCell(cellPosition)?.content;
61582
- if (cellContent ||
61583
- this.getters.isInMerge(cellPosition) ||
61584
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
61585
- return "none";
61634
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
61635
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
61636
+ const cellPosition = { sheetId, col, row };
61637
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
61638
+ const cellContent = this.getters.getCell(cellPosition)?.content;
61639
+ if (cellContent ||
61640
+ this.getters.isInMerge(cellPosition) ||
61641
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
61642
+ return "none";
61643
+ }
61586
61644
  }
61587
61645
  }
61588
61646
  return direction;
@@ -67751,10 +67809,20 @@ function adaptTransform(toTransform, executed) {
67751
67809
  */
67752
67810
  function transformAll(toTransform, executed) {
67753
67811
  let transformedCommands = [...toTransform];
67812
+ const possibleTransformations = new Set(otRegistry.getKeys());
67754
67813
  for (const executedCommand of executed) {
67755
- transformedCommands = transformedCommands
67756
- .map((cmd) => transform(cmd, executedCommand))
67757
- .filter(isDefined);
67814
+ // If the executed command is not in the registry, we skip it
67815
+ // because we know there won't be any transformation impacting the
67816
+ // commands to transform.
67817
+ if (possibleTransformations.has(executedCommand.type)) {
67818
+ transformedCommands = transformedCommands.reduce((acc, cmd) => {
67819
+ const transformed = transform(cmd, executedCommand);
67820
+ if (transformed) {
67821
+ acc.push(transformed);
67822
+ }
67823
+ return acc;
67824
+ }, []);
67825
+ }
67758
67826
  }
67759
67827
  return transformedCommands;
67760
67828
  }
@@ -69456,7 +69524,7 @@ class SheetUIPlugin extends UIPlugin {
69456
69524
  }
69457
69525
  const position = this.getters.getCellPosition(cell.id);
69458
69526
  const colSize = this.getters.getColSize(sheetId, position.col);
69459
- if (cell.isFormula) {
69527
+ if (cell.isFormula || this.getters.getArrayFormulaSpreadingOn(position)) {
69460
69528
  const content = this.getters.getEvaluatedCell(position).formattedValue;
69461
69529
  const evaluatedSize = getCellContentHeight(this.ctx, content, cell?.style, colSize);
69462
69530
  if (evaluatedSize > evaluatedRowSize && evaluatedSize > DEFAULT_CELL_HEIGHT) {
@@ -71208,9 +71276,10 @@ class FilterEvaluationPlugin extends UIPlugin {
71208
71276
  const filteredZone = filter.filteredRange?.zone;
71209
71277
  if (!filteredValues || !filteredZone)
71210
71278
  continue;
71279
+ const filteredValuesSet = new Set(filteredValues);
71211
71280
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
71212
71281
  const value = this.getCellValueAsString(sheetId, filter.col, row);
71213
- if (filteredValues.includes(value)) {
71282
+ if (filteredValuesSet.has(value)) {
71214
71283
  hiddenRows.add(row);
71215
71284
  }
71216
71285
  }
@@ -80594,6 +80663,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
80594
80663
  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, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
80595
80664
 
80596
80665
 
80597
- __info__.version = "18.3.7";
80598
- __info__.date = "2025-06-06T09:31:27.123Z";
80599
- __info__.hash = "05333f1";
80666
+ __info__.version = "18.3.9";
80667
+ __info__.date = "2025-06-19T18:24:02.754Z";
80668
+ __info__.hash = "a820230";