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