@odoo/o-spreadsheet 18.1.0-alpha.1 → 18.1.0-alpha.2

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.0-alpha.1
6
- * @date 2024-10-14T07:53:17.717Z
7
- * @hash e9ce3aa
5
+ * @version 18.1.0-alpha.2
6
+ * @date 2024-10-24T08:53:21.828Z
7
+ * @hash 2a01250
8
8
  */
9
9
 
10
10
  'use strict';
@@ -331,6 +331,7 @@ const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
331
331
  const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#43C5B1";
332
332
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#EA6175";
333
333
  const LINE_FILL_TRANSPARENCY = 0.4;
334
+ const DEFAULT_WINDOW_SIZE = 2;
334
335
  // session
335
336
  const DEBOUNCE_TIME = 200;
336
337
  const MESSAGE_VERSION = 1;
@@ -376,7 +377,7 @@ const PIVOT_TABLE_CONFIG = {
376
377
  bandedRows: true,
377
378
  bandedColumns: false,
378
379
  styleId: "TableStyleMedium5",
379
- automaticAutofill: true,
380
+ automaticAutofill: false,
380
381
  };
381
382
  const DEFAULT_CURRENCY = {
382
383
  symbol: "$",
@@ -592,7 +593,7 @@ function buildSheetLink(sheetId) {
592
593
  */
593
594
  function parseSheetUrl(sheetLink) {
594
595
  if (sheetLink.startsWith(O_SPREADSHEET_LINK_PREFIX)) {
595
- return sheetLink.substr(O_SPREADSHEET_LINK_PREFIX.length);
596
+ return sheetLink.slice(O_SPREADSHEET_LINK_PREFIX.length);
596
597
  }
597
598
  throw new Error(`${sheetLink} is not a valid sheet link`);
598
599
  }
@@ -3157,8 +3158,7 @@ const getNumberRegex = memoize(function getNumberRegex(locale) {
3157
3158
  const p2 = pMinus + pNumber + pCurrencyFormat;
3158
3159
  const p3 = pCurrencyFormat + pMinus + pNumber;
3159
3160
  const pNumberExp = "^(?:(?:" + [p1, p2, p3].join(")|(?:") + "))$";
3160
- const numberRegexp = new RegExp(pNumberExp, "i");
3161
- return numberRegexp;
3161
+ return new RegExp(pNumberExp, "i");
3162
3162
  });
3163
3163
  /**
3164
3164
  * Return true if the argument is a "number string".
@@ -5832,8 +5832,7 @@ function computeCachedTextWidth(context, text) {
5832
5832
  textWidthCache[font] = {};
5833
5833
  }
5834
5834
  if (textWidthCache[font][text] === undefined) {
5835
- const textWidth = context.measureText(text).width;
5836
- textWidthCache[font][text] = textWidth;
5835
+ textWidthCache[font][text] = context.measureText(text).width;
5837
5836
  }
5838
5837
  return textWidthCache[font][text];
5839
5838
  }
@@ -6085,7 +6084,7 @@ class UuidGenerator {
6085
6084
  else {
6086
6085
  // mainly for jest and other browsers that do not have the crypto functionality
6087
6086
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
6088
- var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
6087
+ const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
6089
6088
  return v.toString(16);
6090
6089
  });
6091
6090
  }
@@ -6582,10 +6581,9 @@ function localizeNumberLiteral(literal, locale) {
6582
6581
  return literal;
6583
6582
  }
6584
6583
  const decimalNumberRegex = getDecimalNumberRegex(DEFAULT_LOCALE);
6585
- const localized = literal.replace(decimalNumberRegex, (match) => {
6584
+ return literal.replace(decimalNumberRegex, (match) => {
6586
6585
  return match.replace(".", locale.decimalSeparator);
6587
6586
  });
6588
- return localized;
6589
6587
  }
6590
6588
  /**
6591
6589
  * Change a literal string from its canonical form (en_US locale) to the given locale. Also convert date string.
@@ -7070,6 +7068,21 @@ function predictLinearValues(Y, X, newX, computeIntercept) {
7070
7068
  });
7071
7069
  return newY.length === newX.length ? newY : transposeMatrix(newY);
7072
7070
  }
7071
+ function getMovingAverageValues(dataset, windowSize = DEFAULT_WINDOW_SIZE) {
7072
+ const values = [];
7073
+ // Fill the starting values with null until we have a full window
7074
+ for (let i = 0; i < windowSize - 1; i++) {
7075
+ values.push(null);
7076
+ }
7077
+ for (let i = 0; i <= dataset.length - windowSize; i++) {
7078
+ let sum = 0;
7079
+ for (let j = i; j < i + windowSize; j++) {
7080
+ sum += dataset[j];
7081
+ }
7082
+ values.push(sum / windowSize);
7083
+ }
7084
+ return values;
7085
+ }
7073
7086
 
7074
7087
  const PREVIOUS_VALUE = "(previous)";
7075
7088
  const NEXT_VALUE = "(next)";
@@ -7605,8 +7618,7 @@ function getMaxObjectId(o) {
7605
7618
  return 0;
7606
7619
  }
7607
7620
  const nums = keys.map((id) => parseInt(id, 10));
7608
- const max = Math.max(...nums);
7609
- return max;
7621
+ return Math.max(...nums);
7610
7622
  }
7611
7623
  const ALL_PERIODS = {
7612
7624
  year: _t("Year"),
@@ -8459,10 +8471,6 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
8459
8471
  tableZone &&
8460
8472
  zones.some((z) => isZoneInside(tableZone, z))) {
8461
8473
  copiedTablesIds.add(table.id);
8462
- const values = [];
8463
- for (const col of range(tableZone.left, tableZone.right + 1)) {
8464
- values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
8465
- }
8466
8474
  copiedTable = {
8467
8475
  range: coreTable.range.rangeData,
8468
8476
  config: coreTable.config,
@@ -9243,11 +9251,10 @@ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9243
9251
  const { x, y } = getters.getMainViewportCoordinates();
9244
9252
  const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9245
9253
  const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9246
- const position = {
9254
+ return {
9247
9255
  x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9248
9256
  y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9249
9257
  }; // Position at the center of the scrollable viewport
9250
- return position;
9251
9258
  }
9252
9259
  function getChartAxisTitleRuntime(design) {
9253
9260
  if (design?.title?.text) {
@@ -9364,7 +9371,7 @@ function getFullTrendingLineDataSet(dataset, config, data) {
9364
9371
  return {
9365
9372
  ...dataset,
9366
9373
  type: "line",
9367
- xAxisID: TREND_LINE_XAXIS_ID,
9374
+ xAxisID: config.type !== "trailingMovingAverage" ? TREND_LINE_XAXIS_ID : "x",
9368
9375
  label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
9369
9376
  data,
9370
9377
  order: -1,
@@ -9406,6 +9413,9 @@ function interpolateData(config, values, labels, newLabels) {
9406
9413
  case "logarithmic": {
9407
9414
  return predictLinearValues([values], logM([labels]), logM([newLabels]), true)[0];
9408
9415
  }
9416
+ case "trailingMovingAverage": {
9417
+ return getMovingAverageValues(values, config.window);
9418
+ }
9409
9419
  default:
9410
9420
  return [];
9411
9421
  }
@@ -9451,72 +9461,115 @@ const chartShowValuesPlugin = {
9451
9461
  ctx.save();
9452
9462
  ctx.textAlign = "center";
9453
9463
  ctx.textBaseline = "middle";
9454
- ctx.fillStyle = chartFontColor(options.background);
9455
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9456
- chart._metasets.forEach(function (dataset) {
9457
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9458
- return; // ignore trend lines
9459
- }
9460
- switch (dataset.type) {
9461
- case "doughnut":
9462
- case "pie": {
9463
- for (let i = 0; i < dataset._parsed.length; i++) {
9464
- const bar = dataset.data[i];
9465
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9466
- const midAngle = (startAngle + endAngle) / 2;
9467
- const midRadius = (innerRadius + outerRadius) / 2;
9468
- const x = bar.x + midRadius * Math.cos(midAngle);
9469
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9470
- ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
9471
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9472
- const value = options.callback(dataset._parsed[i]);
9473
- ctx.strokeText(value, x, y);
9474
- ctx.fillText(value, x, y);
9475
- }
9476
- break;
9477
- }
9478
- case "bar":
9479
- case "line": {
9480
- const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
9481
- const horizontalChart = dataset.type === "bar" && options.horizontal;
9482
- const axisId = horizontalChart ? dataset.xAxisID : dataset.yAxisID;
9483
- for (let i = 0; i < dataset._parsed.length; i++) {
9484
- const point = dataset.data[i];
9485
- const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
9486
- const displayedValue = options.callback(value - 0, axisId);
9487
- let xPosition = 0, yPosition = 0;
9488
- if (options.horizontal) {
9489
- yPosition = point.y;
9490
- if (value < 0) {
9491
- ctx.textAlign = "right";
9492
- xPosition = point.x - yOffset;
9493
- }
9494
- else {
9495
- ctx.textAlign = "left";
9496
- xPosition = point.x + yOffset;
9497
- }
9498
- }
9499
- else {
9500
- xPosition = point.x;
9501
- if (value < 0) {
9502
- ctx.textBaseline = "top";
9503
- yPosition = point.y + yOffset;
9504
- }
9505
- else {
9506
- ctx.textBaseline = "bottom";
9507
- yPosition = point.y - yOffset;
9508
- }
9509
- }
9510
- ctx.strokeText(displayedValue, xPosition, yPosition);
9511
- ctx.fillText(displayedValue, xPosition, yPosition);
9512
- }
9513
- break;
9514
- }
9515
- }
9516
- });
9464
+ ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
9465
+ switch (chart.config.type) {
9466
+ case "pie":
9467
+ case "doughnut":
9468
+ drawPieChartValues(chart, options, ctx);
9469
+ break;
9470
+ case "bar":
9471
+ case "line":
9472
+ options.horizontal
9473
+ ? drawHorizontalBarChartValues(chart, options, ctx)
9474
+ : drawLineOrBarChartValues(chart, options, ctx);
9475
+ break;
9476
+ }
9517
9477
  ctx.restore();
9518
9478
  },
9519
9479
  };
9480
+ function drawTextWithBackground(text, x, y, ctx) {
9481
+ ctx.lineWidth = 3; // Stroke the text with a big lineWidth width to have some kind of background
9482
+ ctx.strokeText(text, x, y);
9483
+ ctx.lineWidth = 1;
9484
+ ctx.fillText(text, x, y);
9485
+ }
9486
+ function drawLineOrBarChartValues(chart, options, ctx) {
9487
+ const yMax = chart.chartArea.bottom;
9488
+ const yMin = chart.chartArea.top;
9489
+ const textsPositions = {};
9490
+ for (const dataset of chart._metasets) {
9491
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9492
+ return; // ignore trend lines
9493
+ }
9494
+ for (let i = 0; i < dataset._parsed.length; i++) {
9495
+ const value = dataset._parsed[i].y;
9496
+ const displayValue = options.callback(value - 0, dataset.yAxisID);
9497
+ const point = dataset.data[i];
9498
+ const xPosition = point.x;
9499
+ let yPosition = 0;
9500
+ if (chart.config.type === "line") {
9501
+ yPosition = point.y - 10;
9502
+ }
9503
+ else {
9504
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
9505
+ }
9506
+ yPosition = Math.min(yPosition, yMax);
9507
+ yPosition = Math.max(yPosition, yMin);
9508
+ // Avoid overlapping texts with same X
9509
+ if (!textsPositions[xPosition]) {
9510
+ textsPositions[xPosition] = [];
9511
+ }
9512
+ for (const otherPosition of textsPositions[xPosition] || []) {
9513
+ if (Math.abs(otherPosition - yPosition) < 13) {
9514
+ yPosition = otherPosition - 13;
9515
+ }
9516
+ }
9517
+ textsPositions[xPosition].push(yPosition);
9518
+ ctx.fillStyle = point.options.backgroundColor;
9519
+ ctx.strokeStyle = options.background || "#ffffff";
9520
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9521
+ }
9522
+ }
9523
+ }
9524
+ function drawHorizontalBarChartValues(chart, options, ctx) {
9525
+ const xMax = chart.chartArea.right;
9526
+ const xMin = chart.chartArea.left;
9527
+ const textsPositions = {};
9528
+ for (const dataset of chart._metasets) {
9529
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9530
+ return; // ignore trend lines
9531
+ }
9532
+ for (let i = 0; i < dataset._parsed.length; i++) {
9533
+ const value = dataset._parsed[i].x;
9534
+ const displayValue = options.callback(value - 0, dataset.xAxisID);
9535
+ const point = dataset.data[i];
9536
+ const yPosition = point.y;
9537
+ let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
9538
+ xPosition = Math.min(xPosition, xMax);
9539
+ xPosition = Math.max(xPosition, xMin);
9540
+ // Avoid overlapping texts with same Y
9541
+ if (!textsPositions[yPosition]) {
9542
+ textsPositions[yPosition] = [];
9543
+ }
9544
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
9545
+ for (const otherPosition of textsPositions[yPosition]) {
9546
+ if (Math.abs(otherPosition - xPosition) < textWidth) {
9547
+ xPosition = otherPosition + textWidth + 3;
9548
+ }
9549
+ }
9550
+ textsPositions[yPosition].push(xPosition);
9551
+ ctx.fillStyle = point.options.backgroundColor;
9552
+ ctx.strokeStyle = options.background || "#ffffff";
9553
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9554
+ }
9555
+ }
9556
+ }
9557
+ function drawPieChartValues(chart, options, ctx) {
9558
+ for (const dataset of chart._metasets) {
9559
+ for (let i = 0; i < dataset._parsed.length; i++) {
9560
+ const bar = dataset.data[i];
9561
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9562
+ const midAngle = (startAngle + endAngle) / 2;
9563
+ const midRadius = (innerRadius + outerRadius) / 2;
9564
+ const x = bar.x + midRadius * Math.cos(midAngle);
9565
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9566
+ ctx.fillStyle = chartFontColor(options.background);
9567
+ ctx.strokeStyle = options.background || "#ffffff";
9568
+ const value = options.callback(dataset._parsed[i]);
9569
+ drawTextWithBackground(value, x, y, ctx);
9570
+ }
9571
+ }
9572
+ }
9520
9573
 
9521
9574
  /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
9522
9575
  const waterfallLinesPlugin = {
@@ -10294,8 +10347,7 @@ function getHtmlContentFromPattern(pattern, value, highlightColor, className) {
10294
10347
  value = value.slice(index + 1);
10295
10348
  }
10296
10349
  pendingHtmlContent.push({ value });
10297
- const htmlContent = pendingHtmlContent.filter((content) => content.value);
10298
- return htmlContent;
10350
+ return pendingHtmlContent.filter((content) => content.value);
10299
10351
  }
10300
10352
 
10301
10353
  //------------------------------------------------------------------------------
@@ -10699,7 +10751,7 @@ const MINVERSE = {
10699
10751
  assertSquareMatrix(_t("The argument square_matrix must have the same number of columns and rows."), _matrix);
10700
10752
  const { inverted } = invertMatrix(_matrix);
10701
10753
  if (!inverted) {
10702
- throw new EvaluationError(_t("The matrix is not invertible."));
10754
+ return new EvaluationError(_t("The matrix is not invertible."));
10703
10755
  }
10704
10756
  return inverted;
10705
10757
  },
@@ -10778,7 +10830,7 @@ function getSumXAndY(arrayX, arrayY, cb) {
10778
10830
  }
10779
10831
  }
10780
10832
  if (!validPairFound) {
10781
- throw new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10833
+ return new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10782
10834
  }
10783
10835
  return result;
10784
10836
  }
@@ -10859,7 +10911,7 @@ const TOCOL = {
10859
10911
  .flat()
10860
10912
  .filter(shouldKeepValue(_ignore));
10861
10913
  if (result.length === 0) {
10862
- throw new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10914
+ return new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10863
10915
  }
10864
10916
  return [result];
10865
10917
  },
@@ -10880,7 +10932,7 @@ const TOROW = {
10880
10932
  .filter(shouldKeepValue(_ignore))
10881
10933
  .map((item) => [item]);
10882
10934
  if (result.length === 0 || result[0].length === 0) {
10883
- throw new NotAvailableError(_t("No results for the given arguments of TOROW."));
10935
+ return new NotAvailableError(_t("No results for the given arguments of TOROW."));
10884
10936
  }
10885
10937
  return result;
10886
10938
  },
@@ -11440,7 +11492,7 @@ const DECIMAL = {
11440
11492
  * Return error if 'value' is positive.
11441
11493
  * Remove '-?' in the next regex to catch this error.
11442
11494
  */
11443
- assert(() => !!DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11495
+ assert(() => DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11444
11496
  const deci = parseInt(_value, _base);
11445
11497
  assert(() => !isNaN(deci), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11446
11498
  return deci;
@@ -11708,7 +11760,7 @@ const PRODUCT = {
11708
11760
  count += 1;
11709
11761
  }
11710
11762
  if (isEvaluationError(f)) {
11711
- throw j;
11763
+ return j;
11712
11764
  }
11713
11765
  }
11714
11766
  }
@@ -12211,9 +12263,8 @@ function covariance(dataY, dataX, isSample) {
12211
12263
  }
12212
12264
  function variance(args, isSample, textAs0, locale) {
12213
12265
  let count = 0;
12214
- let sum = 0;
12215
12266
  const reduceFunction = textAs0 ? reduceNumbersTextAs0 : reduceNumbers;
12216
- sum = reduceFunction(args, (acc, a) => {
12267
+ const sum = reduceFunction(args, (acc, a) => {
12217
12268
  count += 1;
12218
12269
  return acc + a;
12219
12270
  }, 0, locale);
@@ -12610,7 +12661,7 @@ const MATTHEWS = {
12610
12661
  const flatY = dataY.flat();
12611
12662
  assertSameNumberOfElements(flatX, flatY);
12612
12663
  if (flatX.length === 0) {
12613
- throw new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12664
+ return new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12614
12665
  }
12615
12666
  const n = flatX.length;
12616
12667
  let trueN = 0, trueP = 0, falseP = 0, falseN = 0;
@@ -13410,9 +13461,8 @@ function getMatchingCells(database, field, criteria, locale) {
13410
13461
  // 4 - return for each database row corresponding, the cells corresponding to the field parameter
13411
13462
  const fieldCol = database[index];
13412
13463
  // Example continuation:: fieldCol = ["C", "j", "k", 7]
13413
- const matchingCells = [...matchingRows].map((x) => fieldCol[x + 1]);
13414
13464
  // Example continuation:: matchingCells = ["j", 7]
13415
- return matchingCells;
13465
+ return [...matchingRows].map((x) => fieldCol[x + 1]);
13416
13466
  }
13417
13467
  const databaseArgs = [
13418
13468
  arg("database (range)", _t("The array or range containing the data to consider, structured in such a way that the first row contains the labels for each column's values.")),
@@ -14597,7 +14647,7 @@ const FILTER = {
14597
14647
  }
14598
14648
  }
14599
14649
  if (!result.length) {
14600
- throw new NotAvailableError(_t("No match found in FILTER evaluation"));
14650
+ return new NotAvailableError(_t("No match found in FILTER evaluation"));
14601
14651
  }
14602
14652
  return mode === "row" ? transposeMatrix(result) : result;
14603
14653
  },
@@ -17295,7 +17345,7 @@ function mapParentFunction(tokens) {
17295
17345
  argsTokens[argPosition].push({ value: token.value, type: token.type });
17296
17346
  }
17297
17347
  }
17298
- const res = tokens.map((token, i) => {
17348
+ return tokens.map((token, i) => {
17299
17349
  if (!["SPACE", "LEFT_PAREN"].includes(token.type)) {
17300
17350
  functionStarted = "";
17301
17351
  }
@@ -17333,7 +17383,6 @@ function mapParentFunction(tokens) {
17333
17383
  }
17334
17384
  return token;
17335
17385
  });
17336
- return res;
17337
17386
  }
17338
17387
  /**
17339
17388
  * Parse the list of tokens that compose the arguments of a function to
@@ -17789,7 +17838,7 @@ const IFS = {
17789
17838
  return result;
17790
17839
  }
17791
17840
  }
17792
- throw new EvaluationError(_t("No match."));
17841
+ return new EvaluationError(_t("No match."));
17793
17842
  },
17794
17843
  isExported: true,
17795
17844
  };
@@ -17953,8 +18002,8 @@ const ADDRESS = {
17953
18002
  let cellReference;
17954
18003
  if (_useA1Notation) {
17955
18004
  const rangePart = {
17956
- rowFixed: [1, 2].includes(_absoluteRelativeMode) ? true : false,
17957
- colFixed: [1, 3].includes(_absoluteRelativeMode) ? true : false,
18005
+ rowFixed: [1, 2].includes(_absoluteRelativeMode),
18006
+ colFixed: [1, 3].includes(_absoluteRelativeMode),
17958
18007
  };
17959
18008
  cellReference = toXC(colNumber - 1, rowNumber - 1, rangePart);
17960
18009
  }
@@ -17980,7 +18029,7 @@ const COLUMN = {
17980
18029
  ],
17981
18030
  compute: function (cellReference) {
17982
18031
  if (isEvaluationError(cellReference?.value)) {
17983
- throw cellReference;
18032
+ return cellReference;
17984
18033
  }
17985
18034
  const column = cellReference === undefined
17986
18035
  ? this.__originCellPosition?.col
@@ -17998,7 +18047,7 @@ const COLUMNS = {
17998
18047
  args: [arg("range (meta)", _t("The range whose column count will be returned."))],
17999
18048
  compute: function (range) {
18000
18049
  if (isEvaluationError(range?.value)) {
18001
- throw range;
18050
+ return range;
18002
18051
  }
18003
18052
  const zone = toZone(range.value);
18004
18053
  return zone.right - zone.left + 1;
@@ -18078,11 +18127,11 @@ const INDIRECT = {
18078
18127
  compute: function (reference, useA1Notation = { value: true }) {
18079
18128
  let _reference = reference?.value?.toString();
18080
18129
  if (!_reference) {
18081
- throw new InvalidReferenceError(_t("Reference should be defined."));
18130
+ return new InvalidReferenceError(_t("Reference should be defined."));
18082
18131
  }
18083
18132
  const _useA1Notation = toBoolean(useA1Notation);
18084
18133
  if (!_useA1Notation) {
18085
- throw new EvaluationError(_t("R1C1 notation is not supported."));
18134
+ return new EvaluationError(_t("R1C1 notation is not supported."));
18086
18135
  }
18087
18136
  const sheetId = this.__originSheetId;
18088
18137
  const originPosition = this.__originCellPosition;
@@ -18094,7 +18143,7 @@ const INDIRECT = {
18094
18143
  }
18095
18144
  const range = this.getters.getRangeFromSheetXC(sheetId, _reference);
18096
18145
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18097
- throw new InvalidReferenceError();
18146
+ return new InvalidReferenceError();
18098
18147
  }
18099
18148
  if (originPosition) {
18100
18149
  this.addDependencies?.(originPosition, [range]);
@@ -18202,7 +18251,7 @@ const ROW = {
18202
18251
  ],
18203
18252
  compute: function (cellReference) {
18204
18253
  if (isEvaluationError(cellReference?.value)) {
18205
- throw cellReference;
18254
+ return cellReference;
18206
18255
  }
18207
18256
  const row = cellReference === undefined
18208
18257
  ? this.__originCellPosition?.row
@@ -18220,7 +18269,7 @@ const ROWS = {
18220
18269
  args: [arg("range (meta)", _t("The range whose row count will be returned."))],
18221
18270
  compute: function (range) {
18222
18271
  if (isEvaluationError(range?.value)) {
18223
- throw range;
18272
+ return range;
18224
18273
  }
18225
18274
  const zone = toZone(range.value);
18226
18275
  return zone.bottom - zone.top + 1;
@@ -18407,11 +18456,11 @@ const PIVOT = {
18407
18456
  const _pivotFormulaId = toString(pivotFormulaId);
18408
18457
  const _rowCount = toNumber(rowCount, this.locale);
18409
18458
  if (_rowCount < 0) {
18410
- throw new EvaluationError(_t("The number of rows must be positive."));
18459
+ return new EvaluationError(_t("The number of rows must be positive."));
18411
18460
  }
18412
18461
  const _columnCount = toNumber(columnCount, this.locale);
18413
18462
  if (_columnCount < 0) {
18414
- throw new EvaluationError(_t("The number of columns must be positive."));
18463
+ return new EvaluationError(_t("The number of columns must be positive."));
18415
18464
  }
18416
18465
  const _includeColumnHeaders = toBoolean(includeColumnHeaders);
18417
18466
  const _includedTotal = toBoolean(includeTotal);
@@ -18610,6 +18659,12 @@ const EQ = {
18610
18659
  arg("value2 (any)", _t("The value to test against value1 for equality.")),
18611
18660
  ],
18612
18661
  compute: function (value1, value2) {
18662
+ if (isEvaluationError(value1?.value)) {
18663
+ return value1;
18664
+ }
18665
+ if (isEvaluationError(value2?.value)) {
18666
+ return value2;
18667
+ }
18613
18668
  let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18614
18669
  let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18615
18670
  if (typeof _value1 === "string") {
@@ -18618,27 +18673,21 @@ const EQ = {
18618
18673
  if (typeof _value2 === "string") {
18619
18674
  _value2 = _value2.toUpperCase();
18620
18675
  }
18621
- if (isEvaluationError(_value1)) {
18622
- throw value1;
18623
- }
18624
- if (isEvaluationError(_value2)) {
18625
- throw value2;
18626
- }
18627
- return _value1 === _value2;
18676
+ return { value: _value1 === _value2 };
18628
18677
  },
18629
18678
  };
18630
18679
  // -----------------------------------------------------------------------------
18631
18680
  // GT
18632
18681
  // -----------------------------------------------------------------------------
18633
18682
  function applyRelationalOperator(value1, value2, cb) {
18634
- let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18635
- let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18636
- if (isEvaluationError(_value1)) {
18637
- throw value1;
18683
+ if (isEvaluationError(value1?.value)) {
18684
+ return value1;
18638
18685
  }
18639
- if (isEvaluationError(_value2)) {
18640
- throw value2;
18686
+ if (isEvaluationError(value2?.value)) {
18687
+ return value2;
18641
18688
  }
18689
+ let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18690
+ let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18642
18691
  if (typeof _value1 !== "number") {
18643
18692
  _value1 = toString(_value1).toUpperCase();
18644
18693
  }
@@ -18648,12 +18697,12 @@ function applyRelationalOperator(value1, value2, cb) {
18648
18697
  const tV1 = typeof _value1;
18649
18698
  const tV2 = typeof _value2;
18650
18699
  if (tV1 === "string" && tV2 === "number") {
18651
- return true;
18700
+ return { value: true };
18652
18701
  }
18653
18702
  if (tV2 === "string" && tV1 === "number") {
18654
- return false;
18703
+ return { value: false };
18655
18704
  }
18656
- return cb(_value1, _value2);
18705
+ return { value: cb(_value1, _value2) };
18657
18706
  }
18658
18707
  const GT = {
18659
18708
  description: _t("Strictly greater than."),
@@ -18692,7 +18741,11 @@ const LT = {
18692
18741
  arg("value2 (any)", _t("The second value.")),
18693
18742
  ],
18694
18743
  compute: function (value1, value2) {
18695
- return !GTE.compute.bind(this)(value1, value2);
18744
+ const result = GTE.compute.bind(this)(value1, value2);
18745
+ if (isEvaluationError(result.value)) {
18746
+ return result;
18747
+ }
18748
+ return { value: !result.value };
18696
18749
  },
18697
18750
  };
18698
18751
  // -----------------------------------------------------------------------------
@@ -18705,7 +18758,11 @@ const LTE = {
18705
18758
  arg("value2 (any)", _t("The second value.")),
18706
18759
  ],
18707
18760
  compute: function (value1, value2) {
18708
- return !GT.compute.bind(this)(value1, value2);
18761
+ const result = GT.compute.bind(this)(value1, value2);
18762
+ if (isEvaluationError(result.value)) {
18763
+ return result;
18764
+ }
18765
+ return { value: !result.value };
18709
18766
  },
18710
18767
  };
18711
18768
  // -----------------------------------------------------------------------------
@@ -18750,7 +18807,11 @@ const NE = {
18750
18807
  arg("value2 (any)", _t("The value to test against value1 for inequality.")),
18751
18808
  ],
18752
18809
  compute: function (value1, value2) {
18753
- return !EQ.compute.bind(this)(value1, value2);
18810
+ const result = EQ.compute.bind(this)(value1, value2);
18811
+ if (isEvaluationError(result.value)) {
18812
+ return result;
18813
+ }
18814
+ return { value: !result.value };
18754
18815
  },
18755
18816
  };
18756
18817
  // -----------------------------------------------------------------------------
@@ -19909,13 +19970,12 @@ function cssPropertiesToCss(attributes) {
19909
19970
  }
19910
19971
  function getElementMargins(el) {
19911
19972
  const style = window.getComputedStyle(el);
19912
- const margins = {
19973
+ return {
19913
19974
  top: parseInt(style.marginTop, 10) || 0,
19914
19975
  bottom: parseInt(style.marginBottom, 10) || 0,
19915
19976
  left: parseInt(style.marginLeft, 10) || 0,
19916
19977
  right: parseInt(style.marginRight, 10) || 0,
19917
19978
  };
19918
- return margins;
19919
19979
  }
19920
19980
 
19921
19981
  const macRegex = /Mac/i;
@@ -21790,9 +21850,13 @@ autoCompleteProviders.add("pivot_group_fields", {
21790
21850
  const colFields = columns.map((groupBy) => groupBy.nameWithGranularity);
21791
21851
  const rowFields = rows.map((groupBy) => groupBy.nameWithGranularity);
21792
21852
  const proposals = [];
21793
- const previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21853
+ let previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21794
21854
  ? argGroupBys.at(-1)
21795
21855
  : argGroupBys.at(-2);
21856
+ const isPositionalSupported = supportedPivotPositionalFormulaRegistry.get(pivot.type);
21857
+ if (isPositionalSupported && previousGroupBy?.startsWith("#")) {
21858
+ previousGroupBy = previousGroupBy.slice(1);
21859
+ }
21796
21860
  if (previousGroupBy === undefined) {
21797
21861
  proposals.push(colFields[0]);
21798
21862
  proposals.push(rowFields[0]);
@@ -21814,7 +21878,7 @@ autoCompleteProviders.add("pivot_group_fields", {
21814
21878
  return field ? makeFieldProposal(field, granularity) : undefined;
21815
21879
  })
21816
21880
  .concat(groupBys.map((groupBy) => {
21817
- if (!supportedPivotPositionalFormulaRegistry.get(pivot.type)) {
21881
+ if (!isPositionalSupported) {
21818
21882
  return undefined;
21819
21883
  }
21820
21884
  const fieldName = groupBy.split(":")[0];
@@ -21823,13 +21887,12 @@ autoCompleteProviders.add("pivot_group_fields", {
21823
21887
  return undefined;
21824
21888
  }
21825
21889
  const positionalFieldArg = `"#${groupBy}"`;
21826
- const positionalProposal = {
21890
+ return {
21827
21891
  text: positionalFieldArg,
21828
21892
  description: _t("%s (positional)", field.string) + (field.help ? ` (${field.help})` : ""),
21829
21893
  htmlContent: [{ value: positionalFieldArg, color: tokenColors.STRING }],
21830
21894
  fuzzySearchKey: field.string + positionalFieldArg, // search on translated name and on technical name
21831
21895
  };
21832
- return positionalProposal;
21833
21896
  }))
21834
21897
  .filter(isDefined);
21835
21898
  },
@@ -22098,7 +22161,7 @@ function parseLiteral(content, locale) {
22098
22161
  return internalDate.value;
22099
22162
  }
22100
22163
  if (isBoolean(content)) {
22101
- return content.toUpperCase() === "TRUE" ? true : false;
22164
+ return content.toUpperCase() === "TRUE";
22102
22165
  }
22103
22166
  return content;
22104
22167
  }
@@ -22439,8 +22502,7 @@ function calculateDateIncrementBasedOnGroup(group) {
22439
22502
  return 0;
22440
22503
  }
22441
22504
  const previous = jsDates[index - 1];
22442
- const days = Math.floor(date.getTime()) - Math.floor(previous.getTime());
22443
- return days;
22505
+ return Math.floor(date.getTime()) - Math.floor(previous.getTime());
22444
22506
  })
22445
22507
  .slice(1);
22446
22508
  const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
@@ -22999,6 +23061,7 @@ const XLSX_CHART_TYPES = [
22999
23061
  "surface3DChart",
23000
23062
  "bubbleChart",
23001
23063
  "comboChart",
23064
+ "radarChart",
23002
23065
  ];
23003
23066
 
23004
23067
  /** In XLSX color format (no #) */
@@ -23539,7 +23602,7 @@ const CHART_TYPE_CONVERSION_MAP = {
23539
23602
  lineChart: "line",
23540
23603
  line3DChart: undefined,
23541
23604
  stockChart: undefined,
23542
- radarChart: undefined,
23605
+ radarChart: "radar",
23543
23606
  scatterChart: "scatter",
23544
23607
  pieChart: "pie",
23545
23608
  pie3DChart: undefined,
@@ -24952,7 +25015,7 @@ const TABLE_STYLE_CATEGORIES = {
24952
25015
  custom: _t("Custom"),
24953
25016
  };
24954
25017
  const DEFAULT_TABLE_CONFIG = {
24955
- hasFilters: true,
25018
+ hasFilters: false,
24956
25019
  totalRow: false,
24957
25020
  firstColumn: false,
24958
25021
  lastColumn: false,
@@ -25621,12 +25684,11 @@ class XlsxBaseExtractor {
25621
25684
  * Get the list of all the XLSX files in the XLSX file structure
25622
25685
  */
25623
25686
  getListOfXMLFiles() {
25624
- const XMLFiles = Object.entries(this.xlsxFileStructure)
25687
+ return Object.entries(this.xlsxFileStructure)
25625
25688
  .filter(([key]) => key !== "images")
25626
25689
  .map(([_, value]) => value)
25627
25690
  .flat()
25628
25691
  .filter(isDefined);
25629
- return XMLFiles;
25630
25692
  }
25631
25693
  /**
25632
25694
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -25790,13 +25852,12 @@ class XlsxBaseExtractor {
25790
25852
  rgb = this.extractAttr(colorElement, "rgb")?.asString();
25791
25853
  rgb = rgb === DEFAULT_SYSTEM_COLOR ? undefined : rgb;
25792
25854
  }
25793
- const color = {
25855
+ return {
25794
25856
  rgb: rgb || defaultColor,
25795
25857
  auto: this.extractAttr(colorElement, "auto")?.asBool(),
25796
25858
  indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
25797
25859
  tint: this.extractAttr(colorElement, "tint")?.asNum(),
25798
25860
  };
25799
- return color;
25800
25861
  }
25801
25862
  /**
25802
25863
  * Returns the xml file targeted by a relationship.
@@ -26388,7 +26449,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26388
26449
  hyperlinks: this.extractHyperLinks(sheetElement),
26389
26450
  tables: this.extractTables(sheetElement),
26390
26451
  pivotTables: this.extractPivotTables(),
26391
- isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
26452
+ isVisible: sheetWorkbookInfo.state === "visible",
26392
26453
  };
26393
26454
  })[0];
26394
26455
  }
@@ -26459,8 +26520,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26459
26520
  const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
26460
26521
  const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
26461
26522
  const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);
26462
- const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26463
- return figures;
26523
+ return new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26464
26524
  })[0];
26465
26525
  return figures || [];
26466
26526
  }
@@ -26478,8 +26538,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26478
26538
  .filter((relationship) => relationship.type.endsWith("pivotTable"))
26479
26539
  .map((pivotRelationship) => {
26480
26540
  const pivotFile = this.getTargetXmlFile(pivotRelationship);
26481
- const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26482
- return pivot;
26541
+ return new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26483
26542
  });
26484
26543
  }
26485
26544
  catch (e) {
@@ -26878,8 +26937,7 @@ class XlsxReader {
26878
26937
  }
26879
26938
  convertXlsx() {
26880
26939
  const xlsxData = this.getXlsxData();
26881
- const convertedData = this.convertImportedData(xlsxData);
26882
- return convertedData;
26940
+ return this.convertImportedData(xlsxData);
26883
26941
  }
26884
26942
  // ---------------------------------------------------------------------------
26885
26943
  // Parsing XMLs
@@ -27470,6 +27528,20 @@ migrationStepRegistry
27470
27528
  }
27471
27529
  return data;
27472
27530
  },
27531
+ })
27532
+ .add("migration_22", {
27533
+ // "tables are no longer inserted with filters by default",
27534
+ versionFrom: "22",
27535
+ migrate(data) {
27536
+ for (const sheet of data.sheets || []) {
27537
+ for (const table of sheet.tables || []) {
27538
+ if (!table.config) {
27539
+ table.config = { ...DEFAULT_TABLE_CONFIG, hasFilters: true };
27540
+ }
27541
+ }
27542
+ }
27543
+ return data;
27544
+ },
27473
27545
  });
27474
27546
  function fixOverlappingFilters(data) {
27475
27547
  for (let sheet of data.sheets || []) {
@@ -27497,7 +27569,7 @@ function fixOverlappingFilters(data) {
27497
27569
  * a breaking change is made in the way the state is handled, and an upgrade
27498
27570
  * function should be defined
27499
27571
  */
27500
- const CURRENT_VERSION = 22;
27572
+ const CURRENT_VERSION = 23;
27501
27573
  const INITIAL_SHEET_ID = "Sheet1";
27502
27574
  /**
27503
27575
  * This function tries to load anything that could look like a valid
@@ -27510,7 +27582,7 @@ function load(data, verboseImport) {
27510
27582
  if (!data) {
27511
27583
  return createEmptyWorkbookData();
27512
27584
  }
27513
- console.group("Loading data");
27585
+ console.debug("### Loading data ###");
27514
27586
  const start = performance.now();
27515
27587
  if (data["[Content_Types].xml"]) {
27516
27588
  const reader = new XlsxReader(data);
@@ -27524,13 +27596,13 @@ function load(data, verboseImport) {
27524
27596
  // apply migrations, if needed
27525
27597
  if ("version" in data) {
27526
27598
  if (data.version < CURRENT_VERSION) {
27527
- console.info("Migrating data from version", data.version);
27599
+ console.debug("Migrating data from version", data.version);
27528
27600
  data = migrate(data);
27529
27601
  }
27530
27602
  }
27531
27603
  data = repairData(data);
27532
- console.info("Data loaded in", performance.now() - start, "ms");
27533
- console.groupEnd();
27604
+ console.debug("Data loaded in", performance.now() - start, "ms");
27605
+ console.debug("###");
27534
27606
  return data;
27535
27607
  }
27536
27608
  // -----------------------------------------------------------------------------
@@ -27560,7 +27632,7 @@ function migrate(data) {
27560
27632
  for (let i = index; i < steps.length; i++) {
27561
27633
  data = steps[i].migrate(data);
27562
27634
  }
27563
- console.info("Data migrated in", performance.now() - start, "ms");
27635
+ console.debug("Data migrated in", performance.now() - start, "ms");
27564
27636
  return data;
27565
27637
  }
27566
27638
  /**
@@ -27742,7 +27814,7 @@ function createEmptySheet(sheetId, name) {
27742
27814
  };
27743
27815
  }
27744
27816
  function createEmptyWorkbookData(sheetName = "Sheet1") {
27745
- const data = {
27817
+ return {
27746
27818
  version: CURRENT_VERSION,
27747
27819
  sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],
27748
27820
  styles: {},
@@ -27755,7 +27827,6 @@ function createEmptyWorkbookData(sheetName = "Sheet1") {
27755
27827
  pivotNextId: 1,
27756
27828
  customTableStyles: {},
27757
27829
  };
27758
- return data;
27759
27830
  }
27760
27831
  function createEmptyExcelSheet(sheetId, name) {
27761
27832
  return {
@@ -28139,7 +28210,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { axisFormats, local
28139
28210
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
28140
28211
  // tooltipItem.parsed can be an object or a number for pie charts
28141
28212
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
28142
- if (!yLabel) {
28213
+ if (yLabel === undefined || yLabel === null) {
28143
28214
  yLabel = tooltipItem.parsed;
28144
28215
  }
28145
28216
  const axisId = horizontalChart
@@ -28533,8 +28604,7 @@ function createBarChartRuntime(chart, getters) {
28533
28604
  };
28534
28605
  config.data.datasets.push(dataset);
28535
28606
  if (definition.dataSets?.[index]?.label) {
28536
- const label = definition.dataSets[index].label;
28537
- dataset.label = label;
28607
+ dataset.label = definition.dataSets[index].label;
28538
28608
  }
28539
28609
  dataset.yAxisID = chart.horizontal ? "y" : definition.dataSets[index].yAxisId || "y";
28540
28610
  dataset.xAxisID = "x";
@@ -28562,13 +28632,10 @@ function createBarChartRuntime(chart, getters) {
28562
28632
  * datasets to ensure the way we distinguish the originals and trendLine datasets after
28563
28633
  */
28564
28634
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28565
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28566
28635
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28567
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28568
- // @ts-expect-error
28569
- return originalTooltipTitle?.(tooltipItems);
28570
- }
28571
- return "";
28636
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28637
+ ? undefined
28638
+ : "";
28572
28639
  };
28573
28640
  }
28574
28641
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -28906,7 +28973,6 @@ function createLineOrScatterChartRuntime(chart, getters) {
28906
28973
  else if (axisType === "linear") {
28907
28974
  config.options.scales.x.type = "linear";
28908
28975
  config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
28909
- config.options.plugins.tooltip.callbacks.title = () => "";
28910
28976
  config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
28911
28977
  const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
28912
28978
  let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
@@ -28961,8 +29027,7 @@ function createLineOrScatterChartRuntime(chart, getters) {
28961
29027
  const trendDatasets = [];
28962
29028
  for (const [index, dataset] of config.data.datasets.entries()) {
28963
29029
  if (definition.dataSets?.[index]?.label) {
28964
- const label = definition.dataSets[index].label;
28965
- dataset.label = label;
29030
+ dataset.label = definition.dataSets[index].label;
28966
29031
  }
28967
29032
  dataset["yAxisID"] = definition.dataSets[index].yAxisId || "y";
28968
29033
  const trend = definition.dataSets?.[index].trend;
@@ -28991,15 +29056,12 @@ function createLineOrScatterChartRuntime(chart, getters) {
28991
29056
  * distinguish the originals and trendLine datasets after
28992
29057
  */
28993
29058
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28994
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28995
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28996
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28997
- // @ts-expect-error
28998
- return originalTooltipTitle?.(tooltipItems);
28999
- }
29000
- return "";
29001
- };
29002
29059
  }
29060
+ config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29061
+ const displayTooltipTitle = axisType !== "linear" &&
29062
+ tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
29063
+ return displayTooltipTitle ? undefined : "";
29064
+ };
29003
29065
  return {
29004
29066
  chartJsConfig: config,
29005
29067
  background: chart.background || BACKGROUND_CHART_COLOR,
@@ -29222,13 +29284,10 @@ function createComboChartRuntime(chart, getters) {
29222
29284
  * distinguish the originals and trendLine datasets after
29223
29285
  */
29224
29286
  trendDatasets.forEach((x) => config.data.datasets.push(x));
29225
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
29226
29287
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29227
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
29228
- // @ts-expect-error
29229
- return originalTooltipTitle?.(tooltipItems);
29230
- }
29231
- return "";
29288
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
29289
+ ? undefined
29290
+ : "";
29232
29291
  };
29233
29292
  }
29234
29293
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -29967,6 +30026,198 @@ function createPyramidChartRuntime(chart, getters) {
29967
30026
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29968
30027
  }
29969
30028
 
30029
+ class RadarChart extends AbstractChart {
30030
+ dataSets;
30031
+ labelRange;
30032
+ background;
30033
+ legendPosition;
30034
+ stacked;
30035
+ aggregated;
30036
+ type = "radar";
30037
+ dataSetsHaveTitle;
30038
+ dataSetDesign;
30039
+ fillArea;
30040
+ constructor(definition, sheetId, getters) {
30041
+ super(definition, sheetId, getters);
30042
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
30043
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
30044
+ this.background = definition.background;
30045
+ this.legendPosition = definition.legendPosition;
30046
+ this.stacked = definition.stacked;
30047
+ this.aggregated = definition.aggregated;
30048
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
30049
+ this.dataSetDesign = definition.dataSets;
30050
+ this.fillArea = definition.fillArea;
30051
+ }
30052
+ static transformDefinition(definition, executed) {
30053
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
30054
+ }
30055
+ static validateChartDefinition(validator, definition) {
30056
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
30057
+ }
30058
+ static getDefinitionFromContextCreation(context) {
30059
+ return {
30060
+ background: context.background,
30061
+ dataSets: context.range ?? [],
30062
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
30063
+ stacked: context.stacked ?? false,
30064
+ aggregated: context.aggregated ?? false,
30065
+ legendPosition: context.legendPosition ?? "top",
30066
+ title: context.title || { text: "" },
30067
+ type: "radar",
30068
+ labelRange: context.auxiliaryRange || undefined,
30069
+ fillArea: context.fillArea ?? false,
30070
+ };
30071
+ }
30072
+ getContextCreation() {
30073
+ const range = [];
30074
+ for (const [i, dataSet] of this.dataSets.entries()) {
30075
+ range.push({
30076
+ ...this.dataSetDesign?.[i],
30077
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
30078
+ });
30079
+ }
30080
+ return {
30081
+ ...this,
30082
+ range,
30083
+ auxiliaryRange: this.labelRange
30084
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
30085
+ : undefined,
30086
+ };
30087
+ }
30088
+ copyForSheetId(sheetId) {
30089
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30090
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30091
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30092
+ return new RadarChart(definition, sheetId, this.getters);
30093
+ }
30094
+ copyInSheetId(sheetId) {
30095
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
30096
+ return new RadarChart(definition, sheetId, this.getters);
30097
+ }
30098
+ getDefinition() {
30099
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
30100
+ }
30101
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
30102
+ const ranges = [];
30103
+ for (const [i, dataSet] of dataSets.entries()) {
30104
+ ranges.push({
30105
+ ...this.dataSetDesign?.[i],
30106
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
30107
+ });
30108
+ }
30109
+ return {
30110
+ type: "radar",
30111
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
30112
+ background: this.background,
30113
+ dataSets: ranges,
30114
+ legendPosition: this.legendPosition,
30115
+ labelRange: labelRange
30116
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
30117
+ : undefined,
30118
+ title: this.title,
30119
+ stacked: this.stacked,
30120
+ aggregated: this.aggregated,
30121
+ fillArea: this.fillArea,
30122
+ };
30123
+ }
30124
+ getDefinitionForExcel() {
30125
+ if (this.aggregated) {
30126
+ return undefined;
30127
+ }
30128
+ const dataSets = this.dataSets
30129
+ .map((ds) => toExcelDataset(this.getters, ds))
30130
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
30131
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
30132
+ const definition = this.getDefinition();
30133
+ return {
30134
+ ...definition,
30135
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
30136
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
30137
+ dataSets,
30138
+ labelRange,
30139
+ };
30140
+ }
30141
+ updateRanges(applyChange) {
30142
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
30143
+ if (!isStale) {
30144
+ return this;
30145
+ }
30146
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
30147
+ return new RadarChart(definition, this.sheetId, this.getters);
30148
+ }
30149
+ }
30150
+ function createRadarChartRuntime(chart, getters) {
30151
+ const definition = chart.getDefinition();
30152
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
30153
+ let labels = labelValues.formattedValues;
30154
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
30155
+ if (chart.dataSetsHaveTitle &&
30156
+ dataSetsValues[0] &&
30157
+ labels.length > dataSetsValues[0].data.length) {
30158
+ labels.shift();
30159
+ }
30160
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
30161
+ if (chart.aggregated) {
30162
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
30163
+ }
30164
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
30165
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
30166
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
30167
+ const locale = getters.getLocale();
30168
+ const fontColor = chartFontColor(chart.background);
30169
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
30170
+ axisFormats,
30171
+ locale,
30172
+ });
30173
+ const legend = {
30174
+ labels: { color: fontColor },
30175
+ };
30176
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
30177
+ legend.display = false;
30178
+ }
30179
+ else {
30180
+ legend.position = chart.legendPosition;
30181
+ }
30182
+ const fill = definition.fillArea ?? false;
30183
+ if (!fill) {
30184
+ legend.labels["boxHeight"] = 0;
30185
+ }
30186
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
30187
+ config.options.plugins.tooltip = {
30188
+ ...config.options.plugins?.tooltip,
30189
+ callbacks: {
30190
+ label: function (tooltipItem) {
30191
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
30192
+ const yLabel = tooltipItem.parsed.r;
30193
+ return xLabel ? `${xLabel}: ${yLabel}` : yLabel.toString();
30194
+ },
30195
+ },
30196
+ };
30197
+ config.options.layout = {
30198
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
30199
+ };
30200
+ const colorGenerator = getChartColorsGenerator(definition, dataSetsValues.length);
30201
+ for (let i = 0; i < dataSetsValues.length; i++) {
30202
+ let { label, data } = dataSetsValues[i];
30203
+ if (definition.dataSets?.[i]?.label) {
30204
+ label = definition.dataSets[i].label;
30205
+ }
30206
+ const borderColor = colorGenerator.next();
30207
+ const dataset = {
30208
+ label,
30209
+ data,
30210
+ borderColor,
30211
+ };
30212
+ if (fill) {
30213
+ dataset.backgroundColor = setColorAlpha(borderColor, 0.3);
30214
+ dataset["fill"] = true;
30215
+ }
30216
+ config.data.datasets.push(dataset);
30217
+ }
30218
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30219
+ }
30220
+
29970
30221
  class ScatterChart extends AbstractChart {
29971
30222
  dataSets;
29972
30223
  labelRange;
@@ -30489,6 +30740,15 @@ chartRegistry.add("pyramid", {
30489
30740
  getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
30490
30741
  sequence: 80,
30491
30742
  });
30743
+ chartRegistry.add("radar", {
30744
+ match: (type) => type === "radar",
30745
+ createChart: (definition, sheetId, getters) => new RadarChart(definition, sheetId, getters),
30746
+ getChartRuntime: createRadarChartRuntime,
30747
+ validateChartDefinition: RadarChart.validateChartDefinition,
30748
+ transformDefinition: RadarChart.transformDefinition,
30749
+ getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30750
+ sequence: 80,
30751
+ });
30492
30752
  const chartComponentRegistry = new Registry();
30493
30753
  chartComponentRegistry.add("line", ChartJsComponent);
30494
30754
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30499,6 +30759,7 @@ chartComponentRegistry.add("scatter", ChartJsComponent);
30499
30759
  chartComponentRegistry.add("scorecard", ScorecardChart);
30500
30760
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30501
30761
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30762
+ chartComponentRegistry.add("radar", ChartJsComponent);
30502
30763
  const chartCategories = {
30503
30764
  line: _t("Line"),
30504
30765
  column: _t("Column"),
@@ -30640,6 +30901,24 @@ chartSubtypeRegistry
30640
30901
  chartType: "pyramid",
30641
30902
  category: "misc",
30642
30903
  preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
30904
+ })
30905
+ .add("radar", {
30906
+ matcher: (definition) => definition.type === "radar" && !definition.fillArea,
30907
+ displayName: _t("Radar"),
30908
+ chartSubtype: "radar",
30909
+ chartType: "radar",
30910
+ subtypeDefinition: { fillArea: false },
30911
+ category: "misc",
30912
+ preview: "o-spreadsheet-ChartPreview.RADAR_CHART",
30913
+ })
30914
+ .add("filled_radar", {
30915
+ matcher: (definition) => definition.type === "radar" && !!definition.fillArea,
30916
+ displayName: _t("Filled Radar"),
30917
+ chartType: "radar",
30918
+ chartSubtype: "filled_radar",
30919
+ subtypeDefinition: { fillArea: true },
30920
+ category: "misc",
30921
+ preview: "o-spreadsheet-ChartPreview.FILLED_RADAR_CHART",
30643
30922
  });
30644
30923
 
30645
30924
  /**
@@ -30707,11 +30986,10 @@ function centerFigurePosition(getters, size) {
30707
30986
  const rect = getters.getVisibleRect(getters.getActiveMainViewport());
30708
30987
  const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
30709
30988
  const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
30710
- const position = {
30989
+ return {
30711
30990
  x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
30712
30991
  y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
30713
30992
  }; // Position at the center of the scrollable viewport
30714
- return position;
30715
30993
  }
30716
30994
  function getMaxFigureSize(getters, figureSize) {
30717
30995
  const size = deepCopy(figureSize);
@@ -31284,14 +31562,13 @@ class PopoverPositionContext {
31284
31562
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
31285
31563
  const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
31286
31564
  verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
31287
- const cssProperties = {
31565
+ return {
31288
31566
  top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
31289
31567
  this.spreadsheetOffset.y -
31290
31568
  verticalOffset +
31291
31569
  "px",
31292
31570
  left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
31293
31571
  };
31294
- return cssProperties;
31295
31572
  }
31296
31573
  getCurrentPosition(elDims) {
31297
31574
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
@@ -32519,7 +32796,7 @@ function getSmartChartDefinition(zone, getters) {
32519
32796
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
32520
32797
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
32521
32798
  */
32522
- function interactiveCreateTable(env, sheetId, tableConfig) {
32799
+ function interactiveCreateTable(env, sheetId, tableConfig = DEFAULT_TABLE_CONFIG) {
32523
32800
  let target = env.model.getters.getSelectedZones();
32524
32801
  let isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
32525
32802
  if (target.length === 1 && !isDynamic && getZoneArea(target[0]) === 1) {
@@ -33578,7 +33855,7 @@ function getColumnsNumber(env) {
33578
33855
  }
33579
33856
 
33580
33857
  const pivotProperties = {
33581
- name: _t("Edit Pivot"),
33858
+ name: _t("See pivot properties"),
33582
33859
  execute(env) {
33583
33860
  const position = env.model.getters.getActivePosition();
33584
33861
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
@@ -33732,8 +34009,7 @@ cellMenuRegistry
33732
34009
  })
33733
34010
  .add("pivot_properties", {
33734
34011
  ...pivotProperties,
33735
- sequence: 160,
33736
- separator: true,
34012
+ sequence: 170,
33737
34013
  });
33738
34014
 
33739
34015
  const sortRange = {
@@ -35480,6 +35756,7 @@ class Section extends owl.Component {
35480
35756
  static template = "o_spreadsheet.Section";
35481
35757
  static props = {
35482
35758
  class: { type: String, optional: true },
35759
+ title: { type: String, optional: true },
35483
35760
  slots: Object,
35484
35761
  };
35485
35762
  }
@@ -36209,17 +36486,11 @@ class BarConfigPanel extends GenericChartConfigPanel {
36209
36486
  stacked,
36210
36487
  });
36211
36488
  }
36212
- onUpdateAggregated(aggregated) {
36213
- this.props.updateChart(this.props.figureId, {
36214
- aggregated,
36215
- });
36216
- }
36217
36489
  }
36218
36490
 
36219
36491
  css /* scss */ `
36220
36492
  .o_side_panel_collapsible_title {
36221
36493
  font-size: 16px;
36222
- font-weight: bold;
36223
36494
  cursor: pointer;
36224
36495
  padding: 6px 0px 6px 6px !important;
36225
36496
 
@@ -36256,49 +36527,40 @@ class SidePanelCollapsible extends owl.Component {
36256
36527
  static template = "o-spreadsheet-SidePanelCollapsible";
36257
36528
  static props = {
36258
36529
  slots: Object,
36530
+ title: { type: String, optional: true },
36259
36531
  collapsedAtInit: { type: Boolean, optional: true },
36260
36532
  class: { type: String, optional: true },
36261
36533
  };
36262
36534
  currentId = (CURRENT_COLLAPSIBLE_ID++).toString();
36263
36535
  }
36264
36536
 
36265
- const CIRCLE_SVG = /*xml*/ `
36266
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
36267
- <circle r="2" fill="#FFF"/>
36268
- </svg>
36269
- `;
36270
36537
  css /* scss */ `
36271
- .o-radio {
36272
- input {
36273
- appearance: none;
36274
- -webkit-appearance: none;
36275
- -moz-appearance: none;
36276
- width: 14px;
36277
- height: 14px;
36278
- border: 1px solid ${GRAY_300};
36279
- box-sizing: border-box;
36280
- outline: none;
36281
- border-radius: 8px;
36282
-
36283
- &:checked {
36284
- background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
36285
- background-color: ${ACTION_COLOR};
36538
+ .o-badge-selection {
36539
+ gap: 1px;
36540
+ button.o-button {
36541
+ border-radius: 0;
36542
+ &.selected {
36543
+ color: ${GRAY_900};
36286
36544
  border-color: ${ACTION_COLOR};
36545
+ background: ${BADGE_SELECTED_COLOR};
36546
+ font-weight: 600;
36547
+ }
36548
+
36549
+ &:first-child {
36550
+ border-radius: 4px 0 0 4px;
36551
+ }
36552
+ &:last-child {
36553
+ border-radius: 0 4px 4px 0;
36287
36554
  }
36288
36555
  }
36289
36556
  }
36290
36557
  `;
36291
- class RadioSelection extends owl.Component {
36292
- static template = "o-spreadsheet.RadioSelection";
36558
+ class BadgeSelection extends owl.Component {
36559
+ static template = "o-spreadsheet.BadgeSelection";
36293
36560
  static props = {
36294
36561
  choices: Array,
36295
36562
  onChange: Function,
36296
- selectedValue: { optional: false },
36297
- name: String,
36298
- direction: { type: String, optional: true },
36299
- };
36300
- static defaultProps = {
36301
- direction: "horizontal",
36563
+ selectedValue: String,
36302
36564
  };
36303
36565
  }
36304
36566
 
@@ -36843,86 +37105,6 @@ class ColorPickerWidget extends owl.Component {
36843
37105
  }
36844
37106
  }
36845
37107
 
36846
- const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
36847
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
36848
- <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
36849
- </svg>
36850
- `;
36851
- css /* scss */ `
36852
- .o-round-color-picker-button {
36853
- width: 18px;
36854
- height: 18px;
36855
- cursor: pointer;
36856
- border: 1px solid ${GRAY_300};
36857
- background-position: 1px 1px;
36858
- background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
36859
- }
36860
- `;
36861
- class RoundColorPicker extends owl.Component {
36862
- static template = "o-spreadsheet.RoundColorPicker";
36863
- static components = { ColorPickerWidget, Section, ColorPicker };
36864
- static props = {
36865
- currentColor: { type: String, optional: true },
36866
- title: { type: String, optional: true },
36867
- onColorPicked: Function,
36868
- };
36869
- colorPickerButtonRef = owl.useRef("colorPickerButton");
36870
- state;
36871
- setup() {
36872
- this.state = owl.useState({ pickerOpened: false });
36873
- owl.useExternalListener(window, "click", this.closePicker);
36874
- }
36875
- closePicker() {
36876
- this.state.pickerOpened = false;
36877
- }
36878
- togglePicker() {
36879
- this.state.pickerOpened = !this.state.pickerOpened;
36880
- }
36881
- onColorPicked(color) {
36882
- this.props.onColorPicked(color);
36883
- this.state.pickerOpened = false;
36884
- }
36885
- get colorPickerAnchorRect() {
36886
- const button = this.colorPickerButtonRef.el;
36887
- return getBoundingRectAsPOJO(button);
36888
- }
36889
- get buttonStyle() {
36890
- return cssPropertiesToCss({
36891
- background: this.props.currentColor,
36892
- });
36893
- }
36894
- }
36895
-
36896
- css /* scss */ `
36897
- .o-badge-selection {
36898
- gap: 1px;
36899
- button.o-button {
36900
- border-radius: 0;
36901
- &.selected {
36902
- color: ${GRAY_900};
36903
- border-color: ${ACTION_COLOR};
36904
- background: ${BADGE_SELECTED_COLOR};
36905
- font-weight: 600;
36906
- }
36907
-
36908
- &:first-child {
36909
- border-radius: 4px 0 0 4px;
36910
- }
36911
- &:last-child {
36912
- border-radius: 0 4px 4px 0;
36913
- }
36914
- }
36915
- }
36916
- `;
36917
- class BadgeSelection extends owl.Component {
36918
- static template = "o-spreadsheet.BadgeSelection";
36919
- static props = {
36920
- choices: Array,
36921
- onChange: Function,
36922
- selectedValue: String,
36923
- };
36924
- }
36925
-
36926
37108
  css /* scss */ `
36927
37109
  .o-chart-title-designer {
36928
37110
  > span {
@@ -37077,8 +37259,7 @@ class AxisDesignEditor extends owl.Component {
37077
37259
  this.props.updateChart(this.props.figureId, { axesDesign });
37078
37260
  }
37079
37261
  updateAxisEditor(ev) {
37080
- const axis = ev.target.value;
37081
- this.state.currentAxis = axis;
37262
+ this.state.currentAxis = ev.target.value;
37082
37263
  }
37083
37264
  getAxisTitle() {
37084
37265
  const axesDesign = this.props.definition.axesDesign ?? {};
@@ -37097,6 +37278,56 @@ class AxisDesignEditor extends owl.Component {
37097
37278
  }
37098
37279
  }
37099
37280
 
37281
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
37282
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
37283
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
37284
+ </svg>
37285
+ `;
37286
+ css /* scss */ `
37287
+ .o-round-color-picker-button {
37288
+ width: 18px;
37289
+ height: 18px;
37290
+ cursor: pointer;
37291
+ border: 1px solid ${GRAY_300};
37292
+ background-position: 1px 1px;
37293
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
37294
+ }
37295
+ `;
37296
+ class RoundColorPicker extends owl.Component {
37297
+ static template = "o-spreadsheet.RoundColorPicker";
37298
+ static components = { Section, ColorPicker };
37299
+ static props = {
37300
+ currentColor: { type: String, optional: true },
37301
+ title: { type: String, optional: true },
37302
+ onColorPicked: Function,
37303
+ };
37304
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
37305
+ state;
37306
+ setup() {
37307
+ this.state = owl.useState({ pickerOpened: false });
37308
+ owl.useExternalListener(window, "click", this.closePicker);
37309
+ }
37310
+ closePicker() {
37311
+ this.state.pickerOpened = false;
37312
+ }
37313
+ togglePicker() {
37314
+ this.state.pickerOpened = !this.state.pickerOpened;
37315
+ }
37316
+ onColorPicked(color) {
37317
+ this.props.onColorPicked(color);
37318
+ this.state.pickerOpened = false;
37319
+ }
37320
+ get colorPickerAnchorRect() {
37321
+ const button = this.colorPickerButtonRef.el;
37322
+ return getBoundingRectAsPOJO(button);
37323
+ }
37324
+ get buttonStyle() {
37325
+ return cssPropertiesToCss({
37326
+ background: this.props.currentColor,
37327
+ });
37328
+ }
37329
+ }
37330
+
37100
37331
  class GeneralDesignEditor extends owl.Component {
37101
37332
  static template = "o-spreadsheet-GeneralDesignEditor";
37102
37333
  static components = {
@@ -37161,58 +37392,75 @@ class GeneralDesignEditor extends owl.Component {
37161
37392
  }
37162
37393
  }
37163
37394
 
37164
- class ChartWithAxisDesignPanel extends owl.Component {
37165
- static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37395
+ const CIRCLE_SVG = /*xml*/ `
37396
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
37397
+ <circle r="2" fill="#FFF"/>
37398
+ </svg>
37399
+ `;
37400
+ css /* scss */ `
37401
+ .o-radio {
37402
+ input {
37403
+ appearance: none;
37404
+ -webkit-appearance: none;
37405
+ -moz-appearance: none;
37406
+ width: 14px;
37407
+ height: 14px;
37408
+ border: 1px solid ${GRAY_300};
37409
+ box-sizing: border-box;
37410
+ outline: none;
37411
+ border-radius: 8px;
37412
+
37413
+ &:checked {
37414
+ background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
37415
+ background-color: ${ACTION_COLOR};
37416
+ border-color: ${ACTION_COLOR};
37417
+ }
37418
+ }
37419
+ }
37420
+ `;
37421
+ class RadioSelection extends owl.Component {
37422
+ static template = "o-spreadsheet.RadioSelection";
37423
+ static props = {
37424
+ choices: Array,
37425
+ onChange: Function,
37426
+ selectedValue: { optional: false },
37427
+ name: String,
37428
+ direction: { type: String, optional: true },
37429
+ };
37430
+ static defaultProps = {
37431
+ direction: "horizontal",
37432
+ };
37433
+ }
37434
+
37435
+ class SeriesDesignEditor extends owl.Component {
37436
+ static template = "o-spreadsheet-SeriesDesignEditor";
37166
37437
  static components = {
37167
- GeneralDesignEditor,
37168
37438
  SidePanelCollapsible,
37169
37439
  Section,
37170
- AxisDesignEditor,
37171
37440
  RoundColorPicker,
37172
- Checkbox,
37173
- RadioSelection,
37174
37441
  };
37175
37442
  static props = {
37176
37443
  figureId: String,
37177
37444
  definition: Object,
37178
- canUpdateChart: Function,
37179
37445
  updateChart: Function,
37446
+ canUpdateChart: Function,
37447
+ slots: { type: Object, optional: true },
37180
37448
  };
37181
- axisChoices = CHART_AXIS_CHOICES;
37182
37449
  state = owl.useState({ index: 0 });
37183
- get axesList() {
37184
- const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37185
- let axes = [{ id: "x", name: _t("Horizontal axis") }];
37186
- if (useLeftAxis) {
37187
- axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37188
- }
37189
- if (useRightAxis) {
37190
- axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37191
- }
37192
- return axes;
37193
- }
37194
- updateLegendPosition(ev) {
37195
- this.props.updateChart(this.props.figureId, {
37196
- legendPosition: ev.target.value,
37197
- });
37198
- }
37199
37450
  getDataSeries() {
37200
- return this.props.definition.dataSets.map((d, i) => d.label ?? `${ChartTerms.Series} ${i + 1}`);
37451
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
37452
+ if (!runtime || !("chartJsConfig" in runtime)) {
37453
+ return [];
37454
+ }
37455
+ return runtime.chartJsConfig.data.datasets.map((d) => d.label);
37201
37456
  }
37202
37457
  updateSerieEditor(ev) {
37203
- const chartId = this.props.figureId;
37204
- const selectedIndex = ev.target.selectedIndex;
37205
- const runtime = this.env.model.getters.getChartRuntime(chartId);
37206
- if (!runtime) {
37207
- return;
37208
- }
37209
- this.state.index = selectedIndex;
37458
+ this.state.index = ev.target.selectedIndex;
37210
37459
  }
37211
37460
  updateDataSeriesColor(color) {
37212
- const dataSets = [...this.props.definition.dataSets];
37213
- if (!dataSets?.[this.state.index]) {
37461
+ const dataSets = this.props.definition.dataSets;
37462
+ if (!dataSets?.[this.state.index])
37214
37463
  return;
37215
- }
37216
37464
  dataSets[this.state.index] = {
37217
37465
  ...dataSets[this.state.index],
37218
37466
  backgroundColor: color,
@@ -37221,71 +37469,87 @@ class ChartWithAxisDesignPanel extends owl.Component {
37221
37469
  }
37222
37470
  getDataSerieColor() {
37223
37471
  const dataSets = this.props.definition.dataSets;
37224
- if (!dataSets?.[this.state.index]) {
37472
+ if (!dataSets?.[this.state.index])
37225
37473
  return "";
37226
- }
37227
37474
  const color = dataSets[this.state.index].backgroundColor;
37228
- return color ? toHex(color) : getNthColor(this.state.index, getColorsPalette(dataSets.length));
37475
+ return color
37476
+ ? toHex(color)
37477
+ : getNthColor(this.state.index, getColorsPalette(this.props.definition.dataSets.length));
37229
37478
  }
37230
- updateDataSeriesAxis(axis) {
37231
- const dataSets = [...this.props.definition.dataSets];
37232
- if (!dataSets?.[this.state.index]) {
37479
+ updateDataSeriesLabel(ev) {
37480
+ const label = ev.target.value;
37481
+ const dataSets = this.props.definition.dataSets;
37482
+ if (!dataSets?.[this.state.index])
37233
37483
  return;
37234
- }
37235
37484
  dataSets[this.state.index] = {
37236
37485
  ...dataSets[this.state.index],
37237
- yAxisId: axis === "left" ? "y" : "y1",
37486
+ label,
37238
37487
  };
37239
37488
  this.props.updateChart(this.props.figureId, { dataSets });
37240
37489
  }
37241
- getDataSerieAxis() {
37490
+ getDataSerieLabel() {
37242
37491
  const dataSets = this.props.definition.dataSets;
37243
- if (!dataSets?.[this.state.index]) {
37244
- return "left";
37245
- }
37246
- return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
37247
- }
37248
- get canHaveTwoVerticalAxis() {
37249
- return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
37492
+ return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37250
37493
  }
37251
- updateDataSeriesLabel(ev) {
37252
- const label = ev.target.value;
37494
+ }
37495
+
37496
+ class SeriesWithAxisDesignEditor extends owl.Component {
37497
+ static template = "o-spreadsheet-SeriesWithAxisDesignEditor";
37498
+ static components = {
37499
+ SeriesDesignEditor,
37500
+ Checkbox,
37501
+ RadioSelection,
37502
+ Section,
37503
+ RoundColorPicker,
37504
+ };
37505
+ static props = {
37506
+ figureId: String,
37507
+ definition: Object,
37508
+ canUpdateChart: Function,
37509
+ updateChart: Function,
37510
+ slots: { type: Object, optional: true },
37511
+ };
37512
+ axisChoices = CHART_AXIS_CHOICES;
37513
+ updateDataSeriesAxis(index, axis) {
37253
37514
  const dataSets = [...this.props.definition.dataSets];
37254
- if (!dataSets?.[this.state.index]) {
37515
+ if (!dataSets?.[index]) {
37255
37516
  return;
37256
37517
  }
37257
- dataSets[this.state.index] = {
37258
- ...dataSets[this.state.index],
37259
- label,
37518
+ dataSets[index] = {
37519
+ ...dataSets[index],
37520
+ yAxisId: axis === "left" ? "y" : "y1",
37260
37521
  };
37261
37522
  this.props.updateChart(this.props.figureId, { dataSets });
37262
37523
  }
37263
- getDataSerieLabel() {
37524
+ getDataSerieAxis(index) {
37264
37525
  const dataSets = this.props.definition.dataSets;
37265
- return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37526
+ if (!dataSets?.[index]) {
37527
+ return "left";
37528
+ }
37529
+ return dataSets[index].yAxisId === "y1" ? "right" : "left";
37266
37530
  }
37267
- updateShowValues(showValues) {
37268
- this.props.updateChart(this.props.figureId, { showValues });
37531
+ get canHaveTwoVerticalAxis() {
37532
+ return !("horizontal" in this.props.definition && this.props.definition.horizontal);
37269
37533
  }
37270
- toggleDataTrend(display) {
37534
+ toggleDataTrend(index, display) {
37271
37535
  const dataSets = [...this.props.definition.dataSets];
37272
- if (!dataSets?.[this.state.index]) {
37536
+ if (!dataSets?.[index]) {
37273
37537
  return;
37274
37538
  }
37275
- dataSets[this.state.index] = {
37276
- ...dataSets[this.state.index],
37539
+ dataSets[index] = {
37540
+ ...dataSets[index],
37277
37541
  trend: {
37278
37542
  type: "polynomial",
37279
37543
  order: 1,
37280
- ...dataSets[this.state.index].trend,
37544
+ ...dataSets[index].trend,
37281
37545
  display,
37282
37546
  },
37283
37547
  };
37284
37548
  this.props.updateChart(this.props.figureId, { dataSets });
37285
37549
  }
37286
- getTrendLineConfiguration() {
37550
+ getTrendLineConfiguration(index) {
37287
37551
  const dataSets = this.props.definition.dataSets;
37288
- return dataSets?.[this.state.index]?.trend;
37552
+ return dataSets?.[index]?.trend;
37289
37553
  }
37290
37554
  getTrendType(config) {
37291
37555
  if (!config) {
@@ -37293,7 +37557,7 @@ class ChartWithAxisDesignPanel extends owl.Component {
37293
37557
  }
37294
37558
  return config.type === "polynomial" && config.order === 1 ? "linear" : config.type;
37295
37559
  }
37296
- onChangeTrendType(ev) {
37560
+ onChangeTrendType(index, ev) {
37297
37561
  const type = ev.target.value;
37298
37562
  let config;
37299
37563
  switch (type) {
@@ -37306,37 +37570,59 @@ class ChartWithAxisDesignPanel extends owl.Component {
37306
37570
  break;
37307
37571
  case "exponential":
37308
37572
  case "logarithmic":
37573
+ case "trailingMovingAverage":
37309
37574
  config = { type };
37310
37575
  break;
37311
37576
  default:
37312
37577
  return;
37313
37578
  }
37314
- this.updateTrendLineValue(config);
37579
+ this.updateTrendLineValue(index, config);
37315
37580
  }
37316
- onChangePolynomialDegree(ev) {
37581
+ onChangePolynomialDegree(index, ev) {
37317
37582
  const element = ev.target;
37318
37583
  const order = parseInt(element.value || "1");
37319
37584
  if (order < 2) {
37320
- element.value = `${this.getTrendLineConfiguration()?.order ?? 2}`;
37585
+ element.value = `${this.getTrendLineConfiguration(index)?.order ?? 2}`;
37321
37586
  return;
37322
37587
  }
37323
- this.updateTrendLineValue({ order });
37588
+ this.updateTrendLineValue(index, { order });
37324
37589
  }
37325
- getTrendLineColor() {
37326
- return this.getTrendLineConfiguration()?.color ?? setColorAlpha(this.getDataSerieColor(), 0.5);
37590
+ get defaultWindowSize() {
37591
+ return DEFAULT_WINDOW_SIZE;
37327
37592
  }
37328
- updateTrendLineColor(color) {
37329
- this.updateTrendLineValue({ color });
37593
+ onChangeMovingAverageWindow(index, ev) {
37594
+ const element = ev.target;
37595
+ let window = parseInt(element.value) || DEFAULT_WINDOW_SIZE;
37596
+ if (window <= 1) {
37597
+ window = DEFAULT_WINDOW_SIZE;
37598
+ }
37599
+ this.updateTrendLineValue(index, { window });
37330
37600
  }
37331
- updateTrendLineValue(config) {
37601
+ getDataSerieColor(index) {
37602
+ const dataSets = this.props.definition.dataSets;
37603
+ if (!dataSets?.[index])
37604
+ return "";
37605
+ const color = dataSets[index].backgroundColor;
37606
+ return color
37607
+ ? toHex(color)
37608
+ : getNthColor(index, getColorsPalette(this.props.definition.dataSets.length));
37609
+ }
37610
+ getTrendLineColor(index) {
37611
+ return (this.getTrendLineConfiguration(index)?.color ??
37612
+ setColorAlpha(this.getDataSerieColor(index), 0.5));
37613
+ }
37614
+ updateTrendLineColor(index, color) {
37615
+ this.updateTrendLineValue(index, { color });
37616
+ }
37617
+ updateTrendLineValue(index, config) {
37332
37618
  const dataSets = [...this.props.definition.dataSets];
37333
- if (!dataSets?.[this.state.index]) {
37619
+ if (!dataSets?.[index]) {
37334
37620
  return;
37335
37621
  }
37336
- dataSets[this.state.index] = {
37337
- ...dataSets[this.state.index],
37622
+ dataSets[index] = {
37623
+ ...dataSets[index],
37338
37624
  trend: {
37339
- ...dataSets[this.state.index].trend,
37625
+ ...dataSets[index].trend,
37340
37626
  ...config,
37341
37627
  },
37342
37628
  };
@@ -37344,29 +37630,67 @@ class ChartWithAxisDesignPanel extends owl.Component {
37344
37630
  }
37345
37631
  }
37346
37632
 
37633
+ class ChartWithAxisDesignPanel extends owl.Component {
37634
+ static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37635
+ static components = {
37636
+ GeneralDesignEditor,
37637
+ SidePanelCollapsible,
37638
+ Section,
37639
+ AxisDesignEditor,
37640
+ Checkbox,
37641
+ SeriesWithAxisDesignEditor,
37642
+ };
37643
+ static props = {
37644
+ figureId: String,
37645
+ definition: Object,
37646
+ canUpdateChart: Function,
37647
+ updateChart: Function,
37648
+ };
37649
+ get axesList() {
37650
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37651
+ let axes = [{ id: "x", name: _t("Horizontal axis") }];
37652
+ if (useLeftAxis) {
37653
+ axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37654
+ }
37655
+ if (useRightAxis) {
37656
+ axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37657
+ }
37658
+ return axes;
37659
+ }
37660
+ updateLegendPosition(ev) {
37661
+ this.props.updateChart(this.props.figureId, {
37662
+ legendPosition: ev.target.value,
37663
+ });
37664
+ }
37665
+ }
37666
+
37347
37667
  class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
37348
37668
  static template = "o-spreadsheet-ComboChartDesignPanel";
37669
+ static components = {
37670
+ ...ChartWithAxisDesignPanel.components,
37671
+ RadioSelection,
37672
+ };
37349
37673
  seriesTypeChoices = [
37350
37674
  { value: "bar", label: _t("Bar") },
37351
37675
  { value: "line", label: _t("Line") },
37352
37676
  ];
37353
- updateDataSeriesType(type) {
37677
+ updateDataSeriesType(index, type) {
37354
37678
  const dataSets = [...this.props.definition.dataSets];
37355
- if (!dataSets?.[this.state.index]) {
37679
+ if (!dataSets?.[index]) {
37356
37680
  return;
37357
37681
  }
37358
- dataSets[this.state.index] = {
37359
- ...dataSets[this.state.index],
37682
+ dataSets[index] = {
37683
+ ...dataSets[index],
37360
37684
  type,
37361
37685
  };
37362
37686
  this.props.updateChart(this.props.figureId, { dataSets });
37363
37687
  }
37364
- getDataSeriesType() {
37688
+ getDataSeriesType(index) {
37365
37689
  const dataSets = this.props.definition.dataSets;
37366
- if (!dataSets?.[this.state.index]) {
37690
+ if (!dataSets?.[index]) {
37367
37691
  return "bar";
37368
37692
  }
37369
- return dataSets[this.state.index].type ?? "line";
37693
+ return dataSets[index].type ?? "line";
37370
37694
  }
37371
37695
  }
37372
37696
 
@@ -37549,11 +37873,6 @@ class LineConfigPanel extends GenericChartConfigPanel {
37549
37873
  stacked,
37550
37874
  });
37551
37875
  }
37552
- onUpdateAggregated(aggregated) {
37553
- this.props.updateChart(this.props.figureId, {
37554
- aggregated,
37555
- });
37556
- }
37557
37876
  onUpdateCumulative(cumulative) {
37558
37877
  this.props.updateChart(this.props.figureId, {
37559
37878
  cumulative,
@@ -37581,6 +37900,27 @@ class PieChartDesignPanel extends owl.Component {
37581
37900
  }
37582
37901
  }
37583
37902
 
37903
+ class RadarChartDesignPanel extends owl.Component {
37904
+ static template = "o-spreadsheet-RadarChartDesignPanel";
37905
+ static components = {
37906
+ GeneralDesignEditor,
37907
+ SeriesDesignEditor,
37908
+ Section,
37909
+ Checkbox,
37910
+ };
37911
+ static props = {
37912
+ figureId: String,
37913
+ definition: Object,
37914
+ canUpdateChart: Function,
37915
+ updateChart: Function,
37916
+ };
37917
+ updateLegendPosition(ev) {
37918
+ this.props.updateChart(this.props.figureId, {
37919
+ legendPosition: ev.target.value,
37920
+ });
37921
+ }
37922
+ }
37923
+
37584
37924
  class ScatterConfigPanel extends GenericChartConfigPanel {
37585
37925
  static template = "o-spreadsheet-ScatterConfigPanel";
37586
37926
  get canTreatLabelsAsText() {
@@ -37775,9 +38115,6 @@ class WaterfallChartDesignPanel extends owl.Component {
37775
38115
  verticalAxisPosition: value,
37776
38116
  });
37777
38117
  }
37778
- updateShowValues(showValues) {
37779
- this.props.updateChart(this.props.figureId, { showValues });
37780
- }
37781
38118
  }
37782
38119
 
37783
38120
  const chartSidePanelComponentRegistry = new Registry();
@@ -37817,6 +38154,10 @@ chartSidePanelComponentRegistry
37817
38154
  .add("pyramid", {
37818
38155
  configuration: GenericChartConfigPanel,
37819
38156
  design: ChartWithAxisDesignPanel,
38157
+ })
38158
+ .add("radar", {
38159
+ configuration: GenericChartConfigPanel,
38160
+ design: RadarChartDesignPanel,
37820
38161
  });
37821
38162
 
37822
38163
  css /* scss */ `
@@ -39019,13 +39360,6 @@ class DOMDndHelper {
39019
39360
  return;
39020
39361
  this.edgeScrollIntervalId = window.setInterval(() => {
39021
39362
  const offset = direction * 3;
39022
- let newPosition = this.currentMousePosition + offset;
39023
- if (newPosition < Math.min(this.container.start, this.minPosition)) {
39024
- newPosition = Math.min(this.container.start, this.minPosition);
39025
- }
39026
- else if (newPosition > Math.max(this.container.end, this.maxPosition)) {
39027
- newPosition = Math.max(this.container.end, this.maxPosition);
39028
- }
39029
39363
  this.container.scroll += offset;
39030
39364
  }, 5);
39031
39365
  }
@@ -39202,7 +39536,6 @@ css /* scss */ `
39202
39536
  width: 142px;
39203
39537
  .o-cf-preview-description-rule {
39204
39538
  margin-bottom: 4px;
39205
- font-weight: 600;
39206
39539
  max-height: 2.8em;
39207
39540
  line-height: 1.4em;
39208
39541
  }
@@ -39674,7 +40007,7 @@ class ConditionalFormattingEditor extends owl.Component {
39674
40007
  setColorScaleColor(target, color) {
39675
40008
  const point = this.state.rules.colorScale[target];
39676
40009
  if (point) {
39677
- point.color = Number.parseInt(color.substr(1), 16);
40010
+ point.color = Number.parseInt(color.slice(1), 16);
39678
40011
  }
39679
40012
  this.closeMenus();
39680
40013
  }
@@ -39785,7 +40118,7 @@ class ConditionalFormattingEditor extends owl.Component {
39785
40118
  return [this.state.rules.dataBar.rangeValues || ""];
39786
40119
  }
39787
40120
  updateDataBarColor(color) {
39788
- this.state.rules.dataBar.color = Number.parseInt(color.substr(1), 16);
40121
+ this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
39789
40122
  }
39790
40123
  onDataBarRangeUpdate(ranges) {
39791
40124
  this.state.rules.dataBar.rangeValues = ranges[0];
@@ -41198,10 +41531,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41198
41531
  activeSheetMatches = [];
41199
41532
  specificRangeMatches = [];
41200
41533
  currentSearchRegex = null;
41201
- isSearchDirty = false;
41202
41534
  initialShowFormulaState;
41203
41535
  preserveSelectedMatchIndex = false;
41204
41536
  irreplaceableMatchCount = 0;
41537
+ isSearchDirty = false;
41538
+ shouldFinalizeUpdateSelection = false;
41205
41539
  notificationStore = this.get(NotificationStore);
41206
41540
  // fixme: why do we make selectedMatchIndex on top of a selected
41207
41541
  // property in the matches?
@@ -41247,10 +41581,13 @@ class FindAndReplaceStore extends SpreadsheetStore {
41247
41581
  this.updateSearchOptions({ searchFormulas: showFormula });
41248
41582
  }
41249
41583
  selectPreviousMatch() {
41250
- this.selectNextCell(Direction.previous);
41584
+ this.selectNextCell(Direction.previous, {
41585
+ jumpToMatchSheet: true,
41586
+ updateSelection: true,
41587
+ });
41251
41588
  }
41252
41589
  selectNextMatch() {
41253
- this.selectNextCell(Direction.next);
41590
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41254
41591
  }
41255
41592
  handle(cmd) {
41256
41593
  switch (cmd.type) {
@@ -41267,8 +41604,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41267
41604
  case "ADD_COLUMNS_ROWS":
41268
41605
  case "EVALUATE_CELLS":
41269
41606
  case "UPDATE_CELL":
41607
+ this.isSearchDirty = true;
41608
+ break;
41270
41609
  case "ACTIVATE_SHEET":
41271
41610
  this.isSearchDirty = true;
41611
+ this.shouldFinalizeUpdateSelection = true;
41272
41612
  break;
41273
41613
  case "REPLACE_SEARCH":
41274
41614
  for (const match of cmd.matches) {
@@ -41283,7 +41623,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41283
41623
  }
41284
41624
  finalize() {
41285
41625
  if (this.isSearchDirty) {
41286
- this.refreshSearch(false);
41626
+ this.refreshSearch({
41627
+ jumpToMatchSheet: false,
41628
+ updateSelection: this.shouldFinalizeUpdateSelection,
41629
+ });
41630
+ this.shouldFinalizeUpdateSelection = false;
41287
41631
  this.isSearchDirty = false;
41288
41632
  }
41289
41633
  }
@@ -41310,17 +41654,17 @@ class FindAndReplaceStore extends SpreadsheetStore {
41310
41654
  }
41311
41655
  this.toSearch = toSearch;
41312
41656
  this.currentSearchRegex = getSearchRegex(this.toSearch, this.searchOptions);
41313
- this.refreshSearch();
41657
+ this.refreshSearch({ jumpToMatchSheet: true, updateSelection: true });
41314
41658
  }
41315
41659
  /**
41316
41660
  * refresh the matches according to the current search options
41317
41661
  */
41318
- refreshSearch(jumpToMatchSheet = true) {
41662
+ refreshSearch(options) {
41319
41663
  if (!this.preserveSelectedMatchIndex) {
41320
41664
  this.selectedMatchIndex = null;
41321
41665
  }
41322
41666
  this.findMatches();
41323
- this.selectNextCell(Direction.current, jumpToMatchSheet);
41667
+ this.selectNextCell(Direction.current, options);
41324
41668
  }
41325
41669
  getSheetsInSearchOrder() {
41326
41670
  switch (this.searchOptions.searchScope) {
@@ -41390,7 +41734,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41390
41734
  * It is also used to keep coherence between the selected searchMatch
41391
41735
  * and selectedMatchIndex.
41392
41736
  */
41393
- selectNextCell(indexChange, jumpToMatchSheet = true) {
41737
+ selectNextCell(indexChange, options) {
41394
41738
  const matches = this.searchMatches;
41395
41739
  if (!matches.length) {
41396
41740
  this.selectedMatchIndex = null;
@@ -41416,7 +41760,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41416
41760
  this.selectedMatchIndex = nextIndex;
41417
41761
  const selectedMatch = matches[nextIndex];
41418
41762
  // Switch to the sheet where the match is located
41419
- if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41763
+ if (options.jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41420
41764
  // We set `preserveSelectedMatchIndex` to true to avoid resetting the selected search
41421
41765
  // index in the `refreshSearch` function when a new sheet is activated. The reason being
41422
41766
  // that, when we automatically go back to previous sheet while performing a search, the
@@ -41432,7 +41776,9 @@ class FindAndReplaceStore extends SpreadsheetStore {
41432
41776
  }
41433
41777
  // we want grid selection to capture the selection stream
41434
41778
  this.model.selection.getBackToDefault();
41435
- this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41779
+ if (options.updateSelection) {
41780
+ this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41781
+ }
41436
41782
  }
41437
41783
  /**
41438
41784
  * Replace the value of the currently selected match
@@ -41447,7 +41793,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41447
41793
  matches: [this.searchMatches[this.selectedMatchIndex]],
41448
41794
  searchOptions: this.searchOptions,
41449
41795
  });
41450
- this.selectNextCell(Direction.next);
41796
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41451
41797
  }
41452
41798
  /**
41453
41799
  * Apply the replace function to all the matches one time.
@@ -41629,6 +41975,14 @@ class FindAndReplacePanel extends owl.Component {
41629
41975
  owl.onMounted(() => this.searchInput.el?.focus());
41630
41976
  owl.onWillUnmount(() => this.updateSearchContent.stopDebounce());
41631
41977
  this.updateSearchContent = debounce(this.store.updateSearchContent, 200);
41978
+ owl.useExternalListener(window, "keydown", (ev) => {
41979
+ const code = keyboardEventToShortcutString(ev);
41980
+ if (code === "Ctrl+F" || code === "Ctrl+H") {
41981
+ this.searchInput.el?.focus();
41982
+ ev.preventDefault();
41983
+ ev.stopPropagation();
41984
+ }
41985
+ }, { capture: true });
41632
41986
  }
41633
41987
  onFocusSearch() {
41634
41988
  this.updateDataRange();
@@ -42204,7 +42558,6 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
42204
42558
  sequence: 0,
42205
42559
  autoSelectFirstProposal: true,
42206
42560
  getProposals(tokenAtCursor) {
42207
- // return []
42208
42561
  const measureProposals = pivot.measures
42209
42562
  .filter((m) => m !== forComputedMeasure)
42210
42563
  .map((measure) => {
@@ -43785,13 +44138,15 @@ pivotRegistry.add("SPREADSHEET", {
43785
44138
  class PivotSidePanelStore extends SpreadsheetStore {
43786
44139
  pivotId;
43787
44140
  mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
43788
- updatesAreDeferred = false;
44141
+ updatesAreDeferred;
43789
44142
  draft = null;
43790
44143
  notification = this.get(NotificationStore);
43791
44144
  alreadyNotified = false;
43792
44145
  constructor(get, pivotId) {
43793
44146
  super(get);
43794
44147
  this.pivotId = pivotId;
44148
+ this.updatesAreDeferred =
44149
+ this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
43795
44150
  }
43796
44151
  handle(cmd) {
43797
44152
  switch (cmd.type) {
@@ -43879,10 +44234,14 @@ class PivotSidePanelStore extends SpreadsheetStore {
43879
44234
  this.draft = null;
43880
44235
  }
43881
44236
  deferUpdates(shouldDefer) {
43882
- this.updatesAreDeferred = shouldDefer;
43883
44237
  if (shouldDefer === false && this.draft) {
44238
+ this.draft.deferUpdates = false;
43884
44239
  this.applyUpdate();
43885
44240
  }
44241
+ else {
44242
+ this.update({ deferUpdates: shouldDefer });
44243
+ }
44244
+ this.updatesAreDeferred = shouldDefer;
43886
44245
  }
43887
44246
  applyUpdate() {
43888
44247
  if (this.draft) {
@@ -44140,7 +44499,7 @@ class RemoveDuplicatesPanel extends owl.Component {
44140
44499
  return colLabel;
44141
44500
  }
44142
44501
  get isEveryColumnSelected() {
44143
- return Object.values(this.state.columns).every((value) => value === true);
44502
+ return Object.values(this.state.columns).every((value) => value);
44144
44503
  }
44145
44504
  get errorMessages() {
44146
44505
  const cancelledReasons = this.env.model.canDispatch("REMOVE_DUPLICATES", {
@@ -44240,8 +44599,7 @@ class SettingsPanel extends owl.Component {
44240
44599
  const currentLocale = this.currentLocale;
44241
44600
  const localeInLoadedLocales = this.loadedLocales.find((l) => l.code === currentLocale.code);
44242
44601
  if (!localeInLoadedLocales) {
44243
- const locales = [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44244
- return locales;
44602
+ return [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44245
44603
  }
44246
44604
  else if (!deepEquals(currentLocale, localeInLoadedLocales)) {
44247
44605
  const index = this.loadedLocales.indexOf(localeInLoadedLocales);
@@ -46135,10 +46493,9 @@ class GridComposer extends owl.Component {
46135
46493
  });
46136
46494
  }
46137
46495
  get focus() {
46138
- const focus = this.composerFocusStore.activeComposer === this.composerInterface
46496
+ return this.composerFocusStore.activeComposer === this.composerInterface
46139
46497
  ? this.composerFocusStore.focusMode
46140
46498
  : "inactive";
46141
- return focus;
46142
46499
  }
46143
46500
  get composerProps() {
46144
46501
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
@@ -47145,6 +47502,7 @@ class PaintFormatStore extends SpreadsheetStore {
47145
47502
  new CellClipboardHandler(this.getters, this.model.dispatch),
47146
47503
  new BorderClipboardHandler(this.getters, this.model.dispatch),
47147
47504
  new TableClipboardHandler(this.getters, this.model.dispatch),
47505
+ new ConditionalFormatClipboardHandler(this.getters, this.model.dispatch),
47148
47506
  ];
47149
47507
  status = "inactive";
47150
47508
  copiedData;
@@ -47155,6 +47513,13 @@ class PaintFormatStore extends SpreadsheetStore {
47155
47513
  this.highlightStore.unRegister(this);
47156
47514
  });
47157
47515
  }
47516
+ handle(cmd) {
47517
+ switch (cmd.type) {
47518
+ case "PAINT_FORMAT":
47519
+ this.paintFormat(cmd.sheetId, cmd.target);
47520
+ break;
47521
+ }
47522
+ }
47158
47523
  activate(args) {
47159
47524
  this.copiedData = this.copyFormats();
47160
47525
  this.status = args.persistent ? "persistent" : "oneOff";
@@ -47164,18 +47529,7 @@ class PaintFormatStore extends SpreadsheetStore {
47164
47529
  this.copiedData = undefined;
47165
47530
  }
47166
47531
  pasteFormat(target) {
47167
- if (this.copiedData) {
47168
- const sheetId = this.getters.getActiveSheetId();
47169
- for (const handler of this.clipboardHandlers) {
47170
- handler.paste({ zones: target, sheetId }, this.copiedData, {
47171
- isCutOperation: false,
47172
- pasteOption: "onlyFormat",
47173
- });
47174
- }
47175
- }
47176
- if (this.status === "oneOff") {
47177
- this.cancel();
47178
- }
47532
+ this.model.dispatch("PAINT_FORMAT", { target, sheetId: this.getters.getActiveSheetId() });
47179
47533
  }
47180
47534
  get isActive() {
47181
47535
  return this.status !== "inactive";
@@ -47189,6 +47543,19 @@ class PaintFormatStore extends SpreadsheetStore {
47189
47543
  }
47190
47544
  return copiedData;
47191
47545
  }
47546
+ paintFormat(sheetId, target) {
47547
+ if (this.copiedData) {
47548
+ for (const handler of this.clipboardHandlers) {
47549
+ handler.paste({ zones: target, sheetId }, this.copiedData, {
47550
+ isCutOperation: false,
47551
+ pasteOption: "onlyFormat",
47552
+ });
47553
+ }
47554
+ }
47555
+ if (this.status === "oneOff") {
47556
+ this.cancel();
47557
+ }
47558
+ }
47192
47559
  get highlights() {
47193
47560
  const data = this.copiedData;
47194
47561
  if (!data) {
@@ -47565,7 +47932,7 @@ class AbstractResizer extends owl.Component {
47565
47932
  if (index < 0) {
47566
47933
  return;
47567
47934
  }
47568
- if (this.state.waitingForMove === true) {
47935
+ if (this.state.waitingForMove) {
47569
47936
  if (!this.env.model.getters.isGridSelectionActive()) {
47570
47937
  this._selectElement(index, false);
47571
47938
  }
@@ -49124,7 +49491,7 @@ class SidePanelStore extends SpreadsheetStore {
49124
49491
  }
49125
49492
  open(componentTag, panelProps = {}) {
49126
49493
  const state = this.computeState(componentTag, panelProps);
49127
- if (state.isOpen === false) {
49494
+ if (!state.isOpen) {
49128
49495
  return;
49129
49496
  }
49130
49497
  if (this.isOpen && componentTag !== this.componentTag) {
@@ -49463,6 +49830,8 @@ class Grid extends owl.Component {
49463
49830
  },
49464
49831
  "Ctrl+D": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ABOVE"),
49465
49832
  "Ctrl+R": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ON_LEFT"),
49833
+ "Ctrl+H": () => this.sidePanel.open("FindAndReplace", {}),
49834
+ "Ctrl+F": () => this.sidePanel.open("FindAndReplace", {}),
49466
49835
  "Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
49467
49836
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
49468
49837
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
@@ -49870,31 +50239,6 @@ class Grid extends owl.Component {
49870
50239
  }
49871
50240
  }
49872
50241
 
49873
- /** @odoo-module */
49874
- class EditableName extends owl.Component {
49875
- static template = "o-spreadsheet-EditableName";
49876
- static props = {
49877
- name: String,
49878
- displayName: String,
49879
- onChanged: Function,
49880
- };
49881
- state;
49882
- setup() {
49883
- this.state = owl.useState({
49884
- isEditing: false,
49885
- name: "",
49886
- });
49887
- }
49888
- rename() {
49889
- this.state.isEditing = true;
49890
- this.state.name = this.props.name;
49891
- }
49892
- save() {
49893
- this.props.onChanged(this.state.name.trim());
49894
- this.state.isEditing = false;
49895
- }
49896
- }
49897
-
49898
50242
  /**
49899
50243
  * BasePlugin
49900
50244
  *
@@ -53812,7 +54156,7 @@ class SheetPlugin extends CorePlugin {
53812
54156
  this.sheetIdsMapName[sheet.name] = sheet.id;
53813
54157
  }
53814
54158
  for (let sheetData of data.sheets) {
53815
- const name = sheetData.name || _t("Sheet") + (Object.keys(this.sheets).length + 1);
54159
+ const name = sheetData.name || "Sheet" + (Object.keys(this.sheets).length + 1);
53816
54160
  const { colNumber, rowNumber } = this.getImportedSheetSize(sheetData);
53817
54161
  const sheet = {
53818
54162
  id: sheetData.id,
@@ -55431,7 +55775,7 @@ class PivotCorePlugin extends CorePlugin {
55431
55775
  case "DUPLICATE_PIVOT": {
55432
55776
  const { pivotId, newPivotId } = cmd;
55433
55777
  const pivot = deepCopy(this.getPivotCore(pivotId).definition);
55434
- pivot.name = _t("%s (copy)", pivot.name);
55778
+ pivot.name = cmd.duplicatedPivotName ?? pivot.name + " (copy)";
55435
55779
  this.addPivot(newPivotId, pivot);
55436
55780
  break;
55437
55781
  }
@@ -55471,7 +55815,7 @@ class PivotCorePlugin extends CorePlugin {
55471
55815
  return `(#${formulaId}) ${this.getPivotName(pivotId)}`;
55472
55816
  }
55473
55817
  getPivotName(pivotId) {
55474
- return _t(this.getPivotCore(pivotId).definition.name);
55818
+ return this.getPivotCore(pivotId).definition.name;
55475
55819
  }
55476
55820
  /**
55477
55821
  * Returns the pivot core definition of the pivot with the given id.
@@ -57113,7 +57457,7 @@ class Evaluator {
57113
57457
  cellsToCompute.addMany(arrayFormulasPositions);
57114
57458
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
57115
57459
  this.evaluate(cellsToCompute);
57116
- console.info("evaluate Cells", performance.now() - start, "ms");
57460
+ console.debug("evaluate Cells", performance.now() - start, "ms");
57117
57461
  }
57118
57462
  getArrayFormulasImpactedByChangesOf(positions) {
57119
57463
  const impactedPositions = this.createEmptyPositionSet();
@@ -57157,7 +57501,7 @@ class Evaluator {
57157
57501
  const start = performance.now();
57158
57502
  this.evaluatedCells = new PositionMap();
57159
57503
  this.evaluate(this.getAllCells());
57160
- console.info("evaluate all cells", performance.now() - start, "ms");
57504
+ console.debug("evaluate all cells", performance.now() - start, "ms");
57161
57505
  }
57162
57506
  evaluateFormulaResult(sheetId, formulaString) {
57163
57507
  const compiledFormula = compile(formulaString);
@@ -58163,8 +58507,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
58163
58507
  .map((cell) => cell.value);
58164
58508
  switch (threshold.type) {
58165
58509
  case "value":
58166
- const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
58167
- return result;
58510
+ return functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
58168
58511
  case "number":
58169
58512
  return Number(threshold.value);
58170
58513
  case "percentage":
@@ -59368,8 +59711,7 @@ function withPivotPresentationLayer (PivotClass) {
59368
59711
  throw new NotAvailableError();
59369
59712
  }
59370
59713
  const comparedValue = this._getPivotCellValueAndFormat(measure.id, comparedDomain);
59371
- const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
59372
- return comparedValueNumber;
59714
+ return this.strictMeasureValueToNumber(comparedValue);
59373
59715
  }
59374
59716
  getPivotValueCells(measureId) {
59375
59717
  return this.getTableStructure()
@@ -60903,7 +61245,7 @@ class Session extends EventBus {
60903
61245
  this.onMessageReceived(message);
60904
61246
  }
60905
61247
  this.isReplayingInitialRevisions = false;
60906
- console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
61248
+ console.debug("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
60907
61249
  }
60908
61250
  /**
60909
61251
  * Notify the server that the user client left the collaborative session
@@ -61075,7 +61417,6 @@ class Session extends EventBus {
61075
61417
  if (this.waitingAck) {
61076
61418
  return;
61077
61419
  }
61078
- this.waitingAck = true;
61079
61420
  this.sendPendingMessage();
61080
61421
  }
61081
61422
  /**
@@ -61112,6 +61453,7 @@ class Session extends EventBus {
61112
61453
  throw new Error(`Trying to send a new revision while replaying initial revision. This can lead to endless dispatches every time the spreadsheet is open.
61113
61454
  ${JSON.stringify(message)}`);
61114
61455
  }
61456
+ this.waitingAck = true;
61115
61457
  this.transportService.sendMessage({
61116
61458
  ...message,
61117
61459
  serverRevisionId: this.serverRevisionId,
@@ -61271,8 +61613,7 @@ class CollaborativePlugin extends UIPlugin {
61271
61613
  }
61272
61614
  const color = client.color;
61273
61615
  /* Cell background */
61274
- const cellBackgroundColor = `${color}10`;
61275
- ctx.fillStyle = cellBackgroundColor;
61616
+ ctx.fillStyle = `${color}10`;
61276
61617
  ctx.lineWidth = 4 * thinLineWidth;
61277
61618
  ctx.strokeStyle = color;
61278
61619
  ctx.globalCompositeOperation = "multiply";
@@ -61639,8 +61980,7 @@ class HeaderVisibilityUIPlugin extends UIPlugin {
61639
61980
  exportForExcel(data) {
61640
61981
  for (const sheetData of data.sheets) {
61641
61982
  for (const [row, rowData] of Object.entries(sheetData.rows)) {
61642
- const isHidden = this.isRowHidden(sheetData.id, Number(row));
61643
- rowData.isHidden = isHidden;
61983
+ rowData.isHidden = this.isRowHidden(sheetData.id, Number(row));
61644
61984
  }
61645
61985
  }
61646
61986
  }
@@ -61700,6 +62040,7 @@ class InsertPivotPlugin extends UIPlugin {
61700
62040
  this.dispatch("DUPLICATE_PIVOT", {
61701
62041
  pivotId,
61702
62042
  newPivotId,
62043
+ duplicatedPivotName: _t("%s (copy)", this.getters.getPivotCoreDefinition(pivotId).name),
61703
62044
  });
61704
62045
  const activeSheetId = this.getters.getActiveSheetId();
61705
62046
  const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
@@ -62164,7 +62505,6 @@ class SheetUIPlugin extends UIPlugin {
62164
62505
  if (!isEqual(zone, newZone)) {
62165
62506
  hasExpanded = true;
62166
62507
  zone = newZone;
62167
- continue;
62168
62508
  }
62169
62509
  } while (hasExpanded);
62170
62510
  return zone;
@@ -63257,7 +63597,7 @@ class ClipboardPlugin extends UIPlugin {
63257
63597
  case "ADD_COLUMNS_ROWS": {
63258
63598
  this.status = "invisible";
63259
63599
  // If we add a col/row inside or before the cut area, we invalidate the clipboard
63260
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63600
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63261
63601
  return;
63262
63602
  }
63263
63603
  const isClipboardDirty = this.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -63269,7 +63609,7 @@ class ClipboardPlugin extends UIPlugin {
63269
63609
  case "REMOVE_COLUMNS_ROWS": {
63270
63610
  this.status = "invisible";
63271
63611
  // If we remove a col/row inside or before the cut area, we invalidate the clipboard
63272
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63612
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63273
63613
  return;
63274
63614
  }
63275
63615
  for (let el of cmd.elements) {
@@ -63291,7 +63631,7 @@ class ClipboardPlugin extends UIPlugin {
63291
63631
  break;
63292
63632
  }
63293
63633
  case "DELETE_SHEET":
63294
- if (this._isCutOperation !== true) {
63634
+ if (!this._isCutOperation) {
63295
63635
  return;
63296
63636
  }
63297
63637
  if (this.originSheetId === cmd.sheetId) {
@@ -64184,8 +64524,7 @@ class GridSelectionPlugin extends UIPlugin {
64184
64524
  this.setSelectionMixin({ zone, cell: { col, row } }, [zone]);
64185
64525
  }
64186
64526
  setActiveSheet(id) {
64187
- const sheet = this.getters.getSheet(id);
64188
- this.activeSheet = sheet;
64527
+ this.activeSheet = this.getters.getSheet(id);
64189
64528
  }
64190
64529
  activateNextSheet(direction) {
64191
64530
  const sheetIds = this.getters.getSheetIds();
@@ -64879,9 +65218,6 @@ class SheetViewPlugin extends UIPlugin {
64879
65218
  case "UNFREEZE_COLUMNS_ROWS":
64880
65219
  this.resetViewports(this.getters.getActiveSheetId());
64881
65220
  break;
64882
- case "DELETE_SHEET":
64883
- this.sheetsWithDirtyViewports.delete(cmd.sheetId);
64884
- break;
64885
65221
  case "SCROLL_TO_CELL":
64886
65222
  this.refreshViewport(this.getters.getActiveSheetId(), { col: cmd.col, row: cmd.row });
64887
65223
  break;
@@ -66833,10 +67169,9 @@ css /* scss */ `
66833
67169
  user-select: none;
66834
67170
  color: ${TEXT_BODY};
66835
67171
 
66836
- .o-heading-3 {
67172
+ .o-sidePanelTitle {
66837
67173
  line-height: 20px;
66838
67174
  font-size: 16px;
66839
- font-weight: 600;
66840
67175
  }
66841
67176
 
66842
67177
  .o-sidePanelHeader {
@@ -66921,6 +67256,10 @@ css /* scss */ `
66921
67256
  }
66922
67257
  }
66923
67258
  }
67259
+
67260
+ .o-fw-bold {
67261
+ font-weight: 500;
67262
+ }
66924
67263
  `;
66925
67264
  class SidePanel extends owl.Component {
66926
67265
  static template = "o-spreadsheet-SidePanel";
@@ -67752,8 +68091,7 @@ class WebClipboardWrapper {
67752
68091
  for (const item of clipboardItems) {
67753
68092
  for (const type of item.types) {
67754
68093
  const blob = await item.getType(type);
67755
- const text = await blob.text();
67756
- clipboardContent[type] = text;
68094
+ clipboardContent[type] = await blob.text();
67757
68095
  }
67758
68096
  }
67759
68097
  return { status: "ok", content: clipboardContent };
@@ -68065,7 +68403,6 @@ class Spreadsheet extends owl.Component {
68065
68403
  spreadsheetRef = owl.useRef("spreadsheet");
68066
68404
  spreadsheetRect = useSpreadsheetRect();
68067
68405
  _focusGrid;
68068
- keyDownMapping;
68069
68406
  isViewportTooSmall = false;
68070
68407
  notificationStore;
68071
68408
  composerFocusStore;
@@ -68089,10 +68426,6 @@ class Spreadsheet extends owl.Component {
68089
68426
  this.notificationStore = useStore(NotificationStore);
68090
68427
  this.composerFocusStore = useStore(ComposerFocusStore);
68091
68428
  this.sidePanel = useStore(SidePanelStore);
68092
- this.keyDownMapping = {
68093
- "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
68094
- "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
68095
- };
68096
68429
  const fileStore = this.model.config.external.fileStore;
68097
68430
  owl.useSubEnv({
68098
68431
  model: this.model,
@@ -68192,20 +68525,6 @@ class Spreadsheet extends owl.Component {
68192
68525
  }
68193
68526
  this._focusGrid();
68194
68527
  }
68195
- onKeydown(ev) {
68196
- let keyDownString = "";
68197
- if (isCtrlKey(ev)) {
68198
- keyDownString += "CTRL+";
68199
- }
68200
- keyDownString += ev.key.toUpperCase();
68201
- let handler = this.keyDownMapping[keyDownString];
68202
- if (handler) {
68203
- ev.preventDefault();
68204
- ev.stopPropagation();
68205
- handler();
68206
- return;
68207
- }
68208
- }
68209
68528
  get gridHeight() {
68210
68529
  const { height } = this.env.model.getters.getSheetViewDimension();
68211
68530
  return height;
@@ -69620,10 +69939,10 @@ class SelectionStreamProcessorImpl {
69620
69939
  getNextCellPosition(currentPosition, dimension, direction) {
69621
69940
  const dimOfInterest = dimension === "cols" ? "col" : "row";
69622
69941
  const startingPosition = { ...currentPosition };
69623
- const nextCoord = dimension === "cols"
69624
- ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69625
- : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69626
- startingPosition[dimOfInterest] = nextCoord;
69942
+ startingPosition[dimOfInterest] =
69943
+ dimension === "cols"
69944
+ ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69945
+ : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69627
69946
  return { col: startingPosition.col, row: startingPosition.row };
69628
69947
  }
69629
69948
  getPosition() {
@@ -69747,6 +70066,8 @@ function createChart(chart, chartSheetIndex, data) {
69747
70066
  case "pie":
69748
70067
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
69749
70068
  break;
70069
+ case "radar":
70070
+ plot = addRadarChart(chart.data);
69750
70071
  }
69751
70072
  let position = "t";
69752
70073
  switch (chart.data.legendPosition) {
@@ -70205,6 +70526,53 @@ function addScatterChart(chart) {
70205
70526
  `
70206
70527
  : ""}`;
70207
70528
  }
70529
+ function addRadarChart(chart) {
70530
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
70531
+ const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
70532
+ const dataSetsNodes = [];
70533
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
70534
+ const color = toXlsxHexColor(colors.next());
70535
+ const dataShapeProperty = shapeProperty({
70536
+ line: {
70537
+ width: 2.5,
70538
+ style: "solid",
70539
+ color,
70540
+ },
70541
+ });
70542
+ const dataSetNode = escapeXml /*xml*/ `
70543
+ <c:ser>
70544
+ <c:idx val="${dsIndex}"/>
70545
+ <c:order val="${dsIndex}"/>
70546
+ <c:smooth val="0"/>
70547
+ <c:marker>
70548
+ <c:symbol val="circle" />
70549
+ <c:size val="5"/>
70550
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
70551
+ </c:marker>
70552
+ ${extractDataSetLabel(dataset.label)}
70553
+ ${dataShapeProperty}
70554
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
70555
+ <c:val> <!-- x-coordinate values -->
70556
+ ${numberRef(dataset.range)}
70557
+ </c:val>
70558
+ </c:ser>
70559
+ `;
70560
+ dataSetsNodes.push(dataSetNode);
70561
+ }
70562
+ return escapeXml /*xml*/ `
70563
+ ${escapeXml /*xml*/ `
70564
+ <c:radarChart>
70565
+ <c:radarStyle val="marker"/>
70566
+ <c:varyColors val="0"/>
70567
+ ${joinXmlNodes(dataSetsNodes)}
70568
+ <c:axId val="${catAxId}" />
70569
+ <c:axId val="${valAxId}" />
70570
+ </c:radarChart>
70571
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
70572
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
70573
+ `}
70574
+ `;
70575
+ }
70208
70576
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
70209
70577
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
70210
70578
  const colors = new ColorGenerator(maxLength);
@@ -71312,25 +71680,23 @@ function addSheetViews(sheet) {
71312
71680
  ["showGridLines", sheet.areGridLinesVisible ? 1 : 0],
71313
71681
  ["workbookViewId", 0],
71314
71682
  ];
71315
- let sheetView = escapeXml /*xml*/ `
71683
+ return escapeXml /*xml*/ `
71316
71684
  <sheetViews>
71317
71685
  <sheetView ${formatAttributes(sheetViewAttrs)}>
71318
71686
  ${splitPanes}
71319
71687
  </sheetView>
71320
71688
  </sheetViews>
71321
71689
  `;
71322
- return sheetView;
71323
71690
  }
71324
71691
  function addSheetProperties(sheet) {
71325
71692
  if (!sheet.color) {
71326
71693
  return "";
71327
71694
  }
71328
- let sheetView = escapeXml /*xml*/ `
71695
+ return escapeXml /*xml*/ `
71329
71696
  <sheetPr>
71330
71697
  <tabColor ${formatAttributes([["rgb", toXlsxHexColor(sheet.color)]])} />
71331
71698
  </sheetPr>
71332
71699
  `;
71333
- return sheetView;
71334
71700
  }
71335
71701
 
71336
71702
  /**
@@ -71643,9 +72009,7 @@ var Status;
71643
72009
  })(Status || (Status = {}));
71644
72010
  class Model extends EventBus {
71645
72011
  corePlugins = [];
71646
- featurePlugins = [];
71647
72012
  statefulUIPlugins = [];
71648
- coreViewsPlugins = [];
71649
72013
  range;
71650
72014
  session;
71651
72015
  /**
@@ -71690,7 +72054,7 @@ class Model extends EventBus {
71690
72054
  coreHandlers = [];
71691
72055
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = false) {
71692
72056
  const start = performance.now();
71693
- console.group("Model creation");
72057
+ console.debug("##### Model creation #####");
71694
72058
  super();
71695
72059
  setDefaultTranslationMethod();
71696
72060
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
@@ -71731,7 +72095,6 @@ class Model extends EventBus {
71731
72095
  this.session.loadInitialMessages(stateUpdateMessages);
71732
72096
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
71733
72097
  const plugin = this.setupUiPlugin(Plugin);
71734
- this.coreViewsPlugins.push(plugin);
71735
72098
  this.handlers.push(plugin);
71736
72099
  this.uiHandlers.push(plugin);
71737
72100
  this.coreHandlers.push(plugin);
@@ -71744,7 +72107,6 @@ class Model extends EventBus {
71744
72107
  }
71745
72108
  for (let Plugin of featurePluginRegistry.getAll()) {
71746
72109
  const plugin = this.setupUiPlugin(Plugin);
71747
- this.featurePlugins.push(plugin);
71748
72110
  this.handlers.push(plugin);
71749
72111
  this.uiHandlers.push(plugin);
71750
72112
  }
@@ -71761,16 +72123,16 @@ class Model extends EventBus {
71761
72123
  this.joinSession();
71762
72124
  if (config.snapshotRequested) {
71763
72125
  const startSnapshot = performance.now();
71764
- console.info("Snapshot requested");
72126
+ console.debug("Snapshot requested");
71765
72127
  this.session.snapshot(this.exportData());
71766
72128
  this.garbageCollectExternalResources();
71767
- console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
72129
+ console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
71768
72130
  }
71769
72131
  // mark all models as "raw", so they will not be turned into reactive objects
71770
72132
  // by owl, since we do not rely on reactivity
71771
72133
  owl.markRaw(this);
71772
- console.info("Model created in", performance.now() - start, "ms");
71773
- console.groupEnd();
72134
+ console.debug("Model created in", performance.now() - start, "ms");
72135
+ console.debug("######");
71774
72136
  }
71775
72137
  joinSession() {
71776
72138
  this.session.join(this.config.client);
@@ -71829,7 +72191,7 @@ class Model extends EventBus {
71829
72191
  this.finalize();
71830
72192
  }
71831
72193
  setupSession(revisionId) {
71832
- const session = new Session(buildRevisionLog({
72194
+ return new Session(buildRevisionLog({
71833
72195
  initialRevisionId: revisionId,
71834
72196
  recordChanges: this.state.recordChanges.bind(this.state),
71835
72197
  dispatch: (command) => {
@@ -71842,7 +72204,6 @@ class Model extends EventBus {
71842
72204
  this.isReplayingCommand = false;
71843
72205
  },
71844
72206
  }), this.config.transportService, revisionId);
71845
- return session;
71846
72207
  }
71847
72208
  setupSessionEvents() {
71848
72209
  this.session.on("remote-revision-received", this, this.onRemoteRevisionReceived);
@@ -71935,8 +72296,7 @@ class Model extends EventBus {
71935
72296
  return results;
71936
72297
  }
71937
72298
  checkDispatchAllowedLocalCommand(command) {
71938
- const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
71939
- return results;
72299
+ return this.uiHandlers.map((handler) => handler.allowDispatch(command));
71940
72300
  }
71941
72301
  finalize() {
71942
72302
  this.status = 3 /* Status.Finalizing */;
@@ -71993,7 +72353,7 @@ class Model extends EventBus {
71993
72353
  this.finalize();
71994
72354
  const time = performance.now() - start;
71995
72355
  if (time > 5) {
71996
- console.info(type, time, "ms");
72356
+ console.debug(type, time, "ms");
71997
72357
  }
71998
72358
  });
71999
72359
  this.session.save(command, commands, changes);
@@ -72296,7 +72656,6 @@ const components = {
72296
72656
  PivotDimensionOrder,
72297
72657
  PivotDimension,
72298
72658
  PivotLayoutConfigurator,
72299
- EditableName,
72300
72659
  PivotDeferUpdate,
72301
72660
  PivotTitleSection,
72302
72661
  CogWheelMenu,
@@ -72388,6 +72747,6 @@ exports.tokenColors = tokenColors;
72388
72747
  exports.tokenize = tokenize;
72389
72748
 
72390
72749
 
72391
- __info__.version = "18.1.0-alpha.1";
72392
- __info__.date = "2024-10-14T07:53:17.717Z";
72393
- __info__.hash = "e9ce3aa";
72750
+ __info__.version = "18.1.0-alpha.2";
72751
+ __info__.date = "2024-10-24T08:53:21.828Z";
72752
+ __info__.hash = "2a01250";