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