@odoo/o-spreadsheet 18.0.31 → 18.0.33

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.0.31
6
- * @date 2025-05-30T08:43:10.315Z
7
- * @hash d201086
5
+ * @version 18.0.33
6
+ * @date 2025-06-12T09:17:53.747Z
7
+ * @hash c1d64fb
8
8
  */
9
9
 
10
10
  'use strict';
@@ -5678,7 +5678,9 @@ function isTextFormat(format) {
5678
5678
  }
5679
5679
 
5680
5680
  function evaluateLiteral(literalCell, localeFormat) {
5681
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5681
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5682
+ ? literalCell.content
5683
+ : literalCell.parsedValue;
5682
5684
  const functionResult = { value, format: localeFormat.format };
5683
5685
  return createEvaluatedCell(functionResult, localeFormat.locale);
5684
5686
  }
@@ -5727,6 +5729,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5727
5729
  if (isEvaluationError(value)) {
5728
5730
  return errorCell(value, message);
5729
5731
  }
5732
+ if (value === null) {
5733
+ return emptyCell(format);
5734
+ }
5730
5735
  if (isTextFormat(format)) {
5731
5736
  // TO DO:
5732
5737
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5734,9 +5739,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5734
5739
  // to interpret the value as a number.
5735
5740
  return textCell(toString(value), format, formattedValue);
5736
5741
  }
5737
- if (value === null) {
5738
- return emptyCell(format);
5739
- }
5740
5742
  if (typeof value === "number") {
5741
5743
  if (isDateTimeFormat(format || "")) {
5742
5744
  return dateTimeCell(value, format, formattedValue);
@@ -10122,7 +10124,8 @@ function drawLineOrBarChartValues(chart, options, ctx) {
10122
10124
  textsPositions[xPosition].push(yPosition);
10123
10125
  ctx.fillStyle = point.options.backgroundColor;
10124
10126
  ctx.strokeStyle = options.background || "#ffffff";
10125
- drawTextWithBackground(options.callback(value - 0), xPosition, yPosition, ctx);
10127
+ const valueToDisplay = options.callback(Number(value), dataset, i);
10128
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10126
10129
  }
10127
10130
  }
10128
10131
  }
@@ -10136,7 +10139,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10136
10139
  }
10137
10140
  for (let i = 0; i < dataset._parsed.length; i++) {
10138
10141
  const value = dataset._parsed[i].x;
10139
- const displayValue = options.callback(value - 0);
10142
+ const displayValue = options.callback(value, dataset, i);
10140
10143
  const point = dataset.data[i];
10141
10144
  const yPosition = point.y;
10142
10145
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -10171,10 +10174,22 @@ function drawPieChartValues(chart, options, ctx) {
10171
10174
  const midAngle = (startAngle + endAngle) / 2;
10172
10175
  const midRadius = (innerRadius + outerRadius) / 2;
10173
10176
  const x = bar.x + midRadius * Math.cos(midAngle);
10174
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10177
+ const y = bar.y + midRadius * Math.sin(midAngle);
10178
+ const displayValue = options.callback(value, dataset, i);
10179
+ const textHeight = 12; // ChartJS default
10180
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10181
+ const radius = outerRadius - innerRadius;
10182
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10183
+ if (textWidth >= radius || radius < textHeight) {
10184
+ continue;
10185
+ }
10186
+ const sliceAngle = endAngle - startAngle;
10187
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10188
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10189
+ continue;
10190
+ }
10175
10191
  ctx.fillStyle = chartFontColor(options.background);
10176
10192
  ctx.strokeStyle = options.background || "#ffffff";
10177
- const displayValue = options.callback(value);
10178
10193
  drawTextWithBackground(displayValue, x, y, ctx);
10179
10194
  }
10180
10195
  }
@@ -13391,7 +13406,7 @@ class XlsxBaseExtractor {
13391
13406
  */
13392
13407
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
13393
13408
  if (optionalArgs?.required) {
13394
- if (optionalArgs?.default) {
13409
+ if (optionalArgs?.default !== undefined) {
13395
13410
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
13396
13411
  }
13397
13412
  else {
@@ -30891,7 +30906,7 @@ function createPyramidChartRuntime(chart, getters) {
30891
30906
  return tooltipLabelCallback(tooltipItem);
30892
30907
  };
30893
30908
  const callback = config.options.plugins.chartShowValuesPlugin.callback;
30894
- config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
30909
+ config.options.plugins.chartShowValuesPlugin.callback = (value, dataset, index) => callback(Math.abs(value), dataset, index);
30895
30910
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30896
30911
  }
30897
30912
 
@@ -31166,7 +31181,7 @@ class WaterfallChart extends AbstractChart {
31166
31181
  return new WaterfallChart(definition, this.sheetId, this.getters);
31167
31182
  }
31168
31183
  }
31169
- function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat) {
31184
+ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat, dataSetsValues) {
31170
31185
  const { locale, format } = localeFormat;
31171
31186
  const fontColor = chartFontColor(chart.background);
31172
31187
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
@@ -31259,10 +31274,22 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
31259
31274
  },
31260
31275
  };
31261
31276
  config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
31277
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
31278
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
31279
+ return subtotalIndexes;
31280
+ }, []);
31262
31281
  config.options.plugins.chartShowValuesPlugin = {
31263
31282
  showValues: chart.showValues,
31264
31283
  background: chart.background,
31265
- callback: formatTickValue(localeFormat),
31284
+ callback: (value, dataset, index) => {
31285
+ const raw = dataset._dataset.data[index];
31286
+ const delta = raw[1] - raw[0];
31287
+ let sign = delta >= 0 ? "+" : "";
31288
+ if (chart.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
31289
+ sign = "";
31290
+ }
31291
+ return `${sign}${formatTickValue(localeFormat)(delta)}`;
31292
+ },
31266
31293
  };
31267
31294
  return config;
31268
31295
  }
@@ -31283,10 +31310,7 @@ function createWaterfallChartRuntime(chart, getters) {
31283
31310
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
31284
31311
  const locale = getters.getLocale();
31285
31312
  const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
31286
- const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
31287
- format: dataSetFormat,
31288
- locale,
31289
- });
31313
+ const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, { format: dataSetFormat, locale }, dataSetsValues);
31290
31314
  config.type = "bar";
31291
31315
  const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
31292
31316
  const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
@@ -32508,19 +32532,26 @@ class FilterMenu extends owl.Component {
32508
32532
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
32509
32533
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
32510
32534
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
32511
- const strValues = [...cellValues, ...filterValues];
32512
- const normalizedFilteredValues = filterValues.map(toLowerCase);
32513
- // Set with lowercase values to avoid duplicates
32514
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
32515
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
32516
- return sortedValues.map((normalizedValue) => {
32517
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
32518
- -1;
32519
- return {
32520
- checked,
32521
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
32522
- };
32523
- });
32535
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
32536
+ const set = new Set();
32537
+ const values = [];
32538
+ const addValue = (value) => {
32539
+ const normalizedValue = toLowerCase(value);
32540
+ if (!set.has(normalizedValue)) {
32541
+ values.push({
32542
+ string: value || "",
32543
+ checked: !normalizedFilteredValues.has(normalizedValue),
32544
+ normalizedValue,
32545
+ });
32546
+ set.add(normalizedValue);
32547
+ }
32548
+ };
32549
+ cellValues.forEach(addValue);
32550
+ filterValues.forEach(addValue);
32551
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
32552
+ numeric: true,
32553
+ sensitivity: "base",
32554
+ }));
32524
32555
  }
32525
32556
  checkValue(value) {
32526
32557
  this.state.selectedValue = value.string;
@@ -42347,13 +42378,14 @@ class FindAndReplaceStore extends SpreadsheetStore {
42347
42378
  if (this.selectedMatchIndex === null) {
42348
42379
  return;
42349
42380
  }
42381
+ this.preserveSelectedMatchIndex = true;
42350
42382
  this.model.dispatch("REPLACE_SEARCH", {
42351
42383
  searchString: this.toSearch,
42352
42384
  replaceWith: this.toReplace,
42353
42385
  matches: [this.searchMatches[this.selectedMatchIndex]],
42354
42386
  searchOptions: this.searchOptions,
42355
42387
  });
42356
- this.selectNextCell(Direction.next);
42388
+ this.preserveSelectedMatchIndex = false;
42357
42389
  }
42358
42390
  /**
42359
42391
  * Apply the replace function to all the matches one time.
@@ -49698,6 +49730,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49698
49730
  let deltaX = lastX - clientX;
49699
49731
  let deltaY = lastY - clientY;
49700
49732
  const elapsedTime = currentTime - lastTime;
49733
+ if (!elapsedTime) {
49734
+ return;
49735
+ }
49701
49736
  velocityX = deltaX / elapsedTime;
49702
49737
  velocityY = deltaY / elapsedTime;
49703
49738
  lastX = clientX;
@@ -49718,6 +49753,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49718
49753
  function onTouchEnd(ev) {
49719
49754
  isMouseDown = false;
49720
49755
  lastX = lastY = 0;
49756
+ if (resetTimeout) {
49757
+ clearTimeout(resetTimeout);
49758
+ }
49759
+ velocityX *= 1.2;
49760
+ velocityY *= 1.2;
49721
49761
  requestAnimationFrame(scroll);
49722
49762
  }
49723
49763
  function scroll() {
@@ -56010,7 +56050,9 @@ class TablePlugin extends CorePlugin {
56010
56050
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
56011
56051
  const union = this.getters.getRangesUnion(ranges);
56012
56052
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
56013
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
56053
+ if (mergesInTarget.length) {
56054
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
56055
+ }
56014
56056
  const id = this.uuidGenerator.smallUuid();
56015
56057
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
56016
56058
  const newTable = cmd.tableType === "dynamic"
@@ -56121,14 +56163,16 @@ class TablePlugin extends CorePlugin {
56121
56163
  const zoneToCheckIfEmpty = direction === "down"
56122
56164
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
56123
56165
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
56124
- for (const position of positions(zoneToCheckIfEmpty)) {
56125
- const cellPosition = { sheetId, ...position };
56126
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
56127
- const cellContent = this.getters.getCell(cellPosition)?.content;
56128
- if (cellContent ||
56129
- this.getters.isInMerge(cellPosition) ||
56130
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
56131
- return "none";
56166
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
56167
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
56168
+ const cellPosition = { sheetId, col, row };
56169
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
56170
+ const cellContent = this.getters.getCell(cellPosition)?.content;
56171
+ if (cellContent ||
56172
+ this.getters.isInMerge(cellPosition) ||
56173
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
56174
+ return "none";
56175
+ }
56132
56176
  }
56133
56177
  }
56134
56178
  return direction;
@@ -65453,9 +65497,10 @@ class FilterEvaluationPlugin extends UIPlugin {
65453
65497
  const filteredZone = filter.filteredRange?.zone;
65454
65498
  if (!filteredValues || !filteredZone)
65455
65499
  continue;
65500
+ const filteredValuesSet = new Set(filteredValues);
65456
65501
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
65457
65502
  const value = this.getCellValueAsString(sheetId, filter.col, row);
65458
- if (filteredValues.includes(value)) {
65503
+ if (filteredValuesSet.has(value)) {
65459
65504
  hiddenRows.add(row);
65460
65505
  }
65461
65506
  }
@@ -65970,11 +66015,6 @@ class GridSelectionPlugin extends UIPlugin {
65970
66015
  },
65971
66016
  ];
65972
66017
  const sheetId = this.getActiveSheetId();
65973
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
65974
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
65975
- if (!data) {
65976
- return;
65977
- }
65978
66018
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
65979
66019
  const pasteTarget = [
65980
66020
  {
@@ -65984,7 +66024,14 @@ class GridSelectionPlugin extends UIPlugin {
65984
66024
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
65985
66025
  },
65986
66026
  ];
65987
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
66027
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
66028
+ const handler = new Handler(this.getters, this.dispatch);
66029
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
66030
+ if (!data) {
66031
+ continue;
66032
+ }
66033
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
66034
+ }
65988
66035
  const selection = pasteTarget[0];
65989
66036
  const col = selection.left;
65990
66037
  const row = selection.top;
@@ -74400,6 +74447,6 @@ exports.tokenColors = tokenColors;
74400
74447
  exports.tokenize = tokenize;
74401
74448
 
74402
74449
 
74403
- __info__.version = "18.0.31";
74404
- __info__.date = "2025-05-30T08:43:10.315Z";
74405
- __info__.hash = "d201086";
74450
+ __info__.version = "18.0.33";
74451
+ __info__.date = "2025-06-12T09:17:53.747Z";
74452
+ __info__.hash = "c1d64fb";
@@ -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.0.31
6
- * @date 2025-05-30T08:43:10.315Z
7
- * @hash d201086
5
+ * @version 18.0.33
6
+ * @date 2025-06-12T09:17:53.747Z
7
+ * @hash c1d64fb
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';
@@ -5676,7 +5676,9 @@ function isTextFormat(format) {
5676
5676
  }
5677
5677
 
5678
5678
  function evaluateLiteral(literalCell, localeFormat) {
5679
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5679
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5680
+ ? literalCell.content
5681
+ : literalCell.parsedValue;
5680
5682
  const functionResult = { value, format: localeFormat.format };
5681
5683
  return createEvaluatedCell(functionResult, localeFormat.locale);
5682
5684
  }
@@ -5725,6 +5727,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5725
5727
  if (isEvaluationError(value)) {
5726
5728
  return errorCell(value, message);
5727
5729
  }
5730
+ if (value === null) {
5731
+ return emptyCell(format);
5732
+ }
5728
5733
  if (isTextFormat(format)) {
5729
5734
  // TO DO:
5730
5735
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5732,9 +5737,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5732
5737
  // to interpret the value as a number.
5733
5738
  return textCell(toString(value), format, formattedValue);
5734
5739
  }
5735
- if (value === null) {
5736
- return emptyCell(format);
5737
- }
5738
5740
  if (typeof value === "number") {
5739
5741
  if (isDateTimeFormat(format || "")) {
5740
5742
  return dateTimeCell(value, format, formattedValue);
@@ -10120,7 +10122,8 @@ function drawLineOrBarChartValues(chart, options, ctx) {
10120
10122
  textsPositions[xPosition].push(yPosition);
10121
10123
  ctx.fillStyle = point.options.backgroundColor;
10122
10124
  ctx.strokeStyle = options.background || "#ffffff";
10123
- drawTextWithBackground(options.callback(value - 0), xPosition, yPosition, ctx);
10125
+ const valueToDisplay = options.callback(Number(value), dataset, i);
10126
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
10124
10127
  }
10125
10128
  }
10126
10129
  }
@@ -10134,7 +10137,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
10134
10137
  }
10135
10138
  for (let i = 0; i < dataset._parsed.length; i++) {
10136
10139
  const value = dataset._parsed[i].x;
10137
- const displayValue = options.callback(value - 0);
10140
+ const displayValue = options.callback(value, dataset, i);
10138
10141
  const point = dataset.data[i];
10139
10142
  const yPosition = point.y;
10140
10143
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -10169,10 +10172,22 @@ function drawPieChartValues(chart, options, ctx) {
10169
10172
  const midAngle = (startAngle + endAngle) / 2;
10170
10173
  const midRadius = (innerRadius + outerRadius) / 2;
10171
10174
  const x = bar.x + midRadius * Math.cos(midAngle);
10172
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10175
+ const y = bar.y + midRadius * Math.sin(midAngle);
10176
+ const displayValue = options.callback(value, dataset, i);
10177
+ const textHeight = 12; // ChartJS default
10178
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
10179
+ const radius = outerRadius - innerRadius;
10180
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
10181
+ if (textWidth >= radius || radius < textHeight) {
10182
+ continue;
10183
+ }
10184
+ const sliceAngle = endAngle - startAngle;
10185
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
10186
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
10187
+ continue;
10188
+ }
10173
10189
  ctx.fillStyle = chartFontColor(options.background);
10174
10190
  ctx.strokeStyle = options.background || "#ffffff";
10175
- const displayValue = options.callback(value);
10176
10191
  drawTextWithBackground(displayValue, x, y, ctx);
10177
10192
  }
10178
10193
  }
@@ -13389,7 +13404,7 @@ class XlsxBaseExtractor {
13389
13404
  */
13390
13405
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
13391
13406
  if (optionalArgs?.required) {
13392
- if (optionalArgs?.default) {
13407
+ if (optionalArgs?.default !== undefined) {
13393
13408
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
13394
13409
  }
13395
13410
  else {
@@ -30889,7 +30904,7 @@ function createPyramidChartRuntime(chart, getters) {
30889
30904
  return tooltipLabelCallback(tooltipItem);
30890
30905
  };
30891
30906
  const callback = config.options.plugins.chartShowValuesPlugin.callback;
30892
- config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
30907
+ config.options.plugins.chartShowValuesPlugin.callback = (value, dataset, index) => callback(Math.abs(value), dataset, index);
30893
30908
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30894
30909
  }
30895
30910
 
@@ -31164,7 +31179,7 @@ class WaterfallChart extends AbstractChart {
31164
31179
  return new WaterfallChart(definition, this.sheetId, this.getters);
31165
31180
  }
31166
31181
  }
31167
- function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat) {
31182
+ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat, dataSetsValues) {
31168
31183
  const { locale, format } = localeFormat;
31169
31184
  const fontColor = chartFontColor(chart.background);
31170
31185
  const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
@@ -31257,10 +31272,22 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
31257
31272
  },
31258
31273
  };
31259
31274
  config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
31275
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
31276
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
31277
+ return subtotalIndexes;
31278
+ }, []);
31260
31279
  config.options.plugins.chartShowValuesPlugin = {
31261
31280
  showValues: chart.showValues,
31262
31281
  background: chart.background,
31263
- callback: formatTickValue(localeFormat),
31282
+ callback: (value, dataset, index) => {
31283
+ const raw = dataset._dataset.data[index];
31284
+ const delta = raw[1] - raw[0];
31285
+ let sign = delta >= 0 ? "+" : "";
31286
+ if (chart.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
31287
+ sign = "";
31288
+ }
31289
+ return `${sign}${formatTickValue(localeFormat)(delta)}`;
31290
+ },
31264
31291
  };
31265
31292
  return config;
31266
31293
  }
@@ -31281,10 +31308,7 @@ function createWaterfallChartRuntime(chart, getters) {
31281
31308
  const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
31282
31309
  const locale = getters.getLocale();
31283
31310
  const dataSeriesLabels = dataSetsValues.map((dataSet) => dataSet.label);
31284
- const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, {
31285
- format: dataSetFormat,
31286
- locale,
31287
- });
31311
+ const config = getWaterfallConfiguration(chart, labels, dataSeriesLabels, { format: dataSetFormat, locale }, dataSetsValues);
31288
31312
  config.type = "bar";
31289
31313
  const negativeColor = chart.negativeValuesColor || CHART_WATERFALL_NEGATIVE_COLOR;
31290
31314
  const positiveColor = chart.positiveValuesColor || CHART_WATERFALL_POSITIVE_COLOR;
@@ -32506,19 +32530,26 @@ class FilterMenu extends Component {
32506
32530
  .filter(({ row }) => !this.env.model.getters.isRowHidden(sheetId, row))
32507
32531
  .map(({ col, row }) => this.env.model.getters.getEvaluatedCell({ sheetId, col, row }).formattedValue);
32508
32532
  const filterValues = this.env.model.getters.getFilterHiddenValues({ sheetId, ...position });
32509
- const strValues = [...cellValues, ...filterValues];
32510
- const normalizedFilteredValues = filterValues.map(toLowerCase);
32511
- // Set with lowercase values to avoid duplicates
32512
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
32513
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
32514
- return sortedValues.map((normalizedValue) => {
32515
- const checked = normalizedFilteredValues.findIndex((filteredValue) => filteredValue === normalizedValue) ===
32516
- -1;
32517
- return {
32518
- checked,
32519
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
32520
- };
32521
- });
32533
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
32534
+ const set = new Set();
32535
+ const values = [];
32536
+ const addValue = (value) => {
32537
+ const normalizedValue = toLowerCase(value);
32538
+ if (!set.has(normalizedValue)) {
32539
+ values.push({
32540
+ string: value || "",
32541
+ checked: !normalizedFilteredValues.has(normalizedValue),
32542
+ normalizedValue,
32543
+ });
32544
+ set.add(normalizedValue);
32545
+ }
32546
+ };
32547
+ cellValues.forEach(addValue);
32548
+ filterValues.forEach(addValue);
32549
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
32550
+ numeric: true,
32551
+ sensitivity: "base",
32552
+ }));
32522
32553
  }
32523
32554
  checkValue(value) {
32524
32555
  this.state.selectedValue = value.string;
@@ -42345,13 +42376,14 @@ class FindAndReplaceStore extends SpreadsheetStore {
42345
42376
  if (this.selectedMatchIndex === null) {
42346
42377
  return;
42347
42378
  }
42379
+ this.preserveSelectedMatchIndex = true;
42348
42380
  this.model.dispatch("REPLACE_SEARCH", {
42349
42381
  searchString: this.toSearch,
42350
42382
  replaceWith: this.toReplace,
42351
42383
  matches: [this.searchMatches[this.selectedMatchIndex]],
42352
42384
  searchOptions: this.searchOptions,
42353
42385
  });
42354
- this.selectNextCell(Direction.next);
42386
+ this.preserveSelectedMatchIndex = false;
42355
42387
  }
42356
42388
  /**
42357
42389
  * Apply the replace function to all the matches one time.
@@ -49696,6 +49728,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49696
49728
  let deltaX = lastX - clientX;
49697
49729
  let deltaY = lastY - clientY;
49698
49730
  const elapsedTime = currentTime - lastTime;
49731
+ if (!elapsedTime) {
49732
+ return;
49733
+ }
49699
49734
  velocityX = deltaX / elapsedTime;
49700
49735
  velocityY = deltaY / elapsedTime;
49701
49736
  lastX = clientX;
@@ -49716,6 +49751,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
49716
49751
  function onTouchEnd(ev) {
49717
49752
  isMouseDown = false;
49718
49753
  lastX = lastY = 0;
49754
+ if (resetTimeout) {
49755
+ clearTimeout(resetTimeout);
49756
+ }
49757
+ velocityX *= 1.2;
49758
+ velocityY *= 1.2;
49719
49759
  requestAnimationFrame(scroll);
49720
49760
  }
49721
49761
  function scroll() {
@@ -56008,7 +56048,9 @@ class TablePlugin extends CorePlugin {
56008
56048
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
56009
56049
  const union = this.getters.getRangesUnion(ranges);
56010
56050
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
56011
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
56051
+ if (mergesInTarget.length) {
56052
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
56053
+ }
56012
56054
  const id = this.uuidGenerator.smallUuid();
56013
56055
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
56014
56056
  const newTable = cmd.tableType === "dynamic"
@@ -56119,14 +56161,16 @@ class TablePlugin extends CorePlugin {
56119
56161
  const zoneToCheckIfEmpty = direction === "down"
56120
56162
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
56121
56163
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
56122
- for (const position of positions(zoneToCheckIfEmpty)) {
56123
- const cellPosition = { sheetId, ...position };
56124
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
56125
- const cellContent = this.getters.getCell(cellPosition)?.content;
56126
- if (cellContent ||
56127
- this.getters.isInMerge(cellPosition) ||
56128
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
56129
- return "none";
56164
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
56165
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
56166
+ const cellPosition = { sheetId, col, row };
56167
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
56168
+ const cellContent = this.getters.getCell(cellPosition)?.content;
56169
+ if (cellContent ||
56170
+ this.getters.isInMerge(cellPosition) ||
56171
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
56172
+ return "none";
56173
+ }
56130
56174
  }
56131
56175
  }
56132
56176
  return direction;
@@ -65451,9 +65495,10 @@ class FilterEvaluationPlugin extends UIPlugin {
65451
65495
  const filteredZone = filter.filteredRange?.zone;
65452
65496
  if (!filteredValues || !filteredZone)
65453
65497
  continue;
65498
+ const filteredValuesSet = new Set(filteredValues);
65454
65499
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
65455
65500
  const value = this.getCellValueAsString(sheetId, filter.col, row);
65456
- if (filteredValues.includes(value)) {
65501
+ if (filteredValuesSet.has(value)) {
65457
65502
  hiddenRows.add(row);
65458
65503
  }
65459
65504
  }
@@ -65968,11 +66013,6 @@ class GridSelectionPlugin extends UIPlugin {
65968
66013
  },
65969
66014
  ];
65970
66015
  const sheetId = this.getActiveSheetId();
65971
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
65972
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
65973
- if (!data) {
65974
- return;
65975
- }
65976
66016
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
65977
66017
  const pasteTarget = [
65978
66018
  {
@@ -65982,7 +66022,14 @@ class GridSelectionPlugin extends UIPlugin {
65982
66022
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
65983
66023
  },
65984
66024
  ];
65985
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
66025
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
66026
+ const handler = new Handler(this.getters, this.dispatch);
66027
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
66028
+ if (!data) {
66029
+ continue;
66030
+ }
66031
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
66032
+ }
65986
66033
  const selection = pasteTarget[0];
65987
66034
  const col = selection.left;
65988
66035
  const row = selection.top;
@@ -74355,6 +74402,6 @@ const constants = {
74355
74402
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, 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 };
74356
74403
 
74357
74404
 
74358
- __info__.version = "18.0.31";
74359
- __info__.date = "2025-05-30T08:43:10.315Z";
74360
- __info__.hash = "d201086";
74405
+ __info__.version = "18.0.33";
74406
+ __info__.date = "2025-06-12T09:17:53.747Z";
74407
+ __info__.hash = "c1d64fb";