@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
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -329,6 +329,7 @@ const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
329
329
  const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#43C5B1";
330
330
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#EA6175";
331
331
  const LINE_FILL_TRANSPARENCY = 0.4;
332
+ const DEFAULT_WINDOW_SIZE = 2;
332
333
  // session
333
334
  const DEBOUNCE_TIME = 200;
334
335
  const MESSAGE_VERSION = 1;
@@ -374,7 +375,7 @@ const PIVOT_TABLE_CONFIG = {
374
375
  bandedRows: true,
375
376
  bandedColumns: false,
376
377
  styleId: "TableStyleMedium5",
377
- automaticAutofill: true,
378
+ automaticAutofill: false,
378
379
  };
379
380
  const DEFAULT_CURRENCY = {
380
381
  symbol: "$",
@@ -590,7 +591,7 @@ function buildSheetLink(sheetId) {
590
591
  */
591
592
  function parseSheetUrl(sheetLink) {
592
593
  if (sheetLink.startsWith(O_SPREADSHEET_LINK_PREFIX)) {
593
- return sheetLink.substr(O_SPREADSHEET_LINK_PREFIX.length);
594
+ return sheetLink.slice(O_SPREADSHEET_LINK_PREFIX.length);
594
595
  }
595
596
  throw new Error(`${sheetLink} is not a valid sheet link`);
596
597
  }
@@ -3155,8 +3156,7 @@ const getNumberRegex = memoize(function getNumberRegex(locale) {
3155
3156
  const p2 = pMinus + pNumber + pCurrencyFormat;
3156
3157
  const p3 = pCurrencyFormat + pMinus + pNumber;
3157
3158
  const pNumberExp = "^(?:(?:" + [p1, p2, p3].join(")|(?:") + "))$";
3158
- const numberRegexp = new RegExp(pNumberExp, "i");
3159
- return numberRegexp;
3159
+ return new RegExp(pNumberExp, "i");
3160
3160
  });
3161
3161
  /**
3162
3162
  * Return true if the argument is a "number string".
@@ -5830,8 +5830,7 @@ function computeCachedTextWidth(context, text) {
5830
5830
  textWidthCache[font] = {};
5831
5831
  }
5832
5832
  if (textWidthCache[font][text] === undefined) {
5833
- const textWidth = context.measureText(text).width;
5834
- textWidthCache[font][text] = textWidth;
5833
+ textWidthCache[font][text] = context.measureText(text).width;
5835
5834
  }
5836
5835
  return textWidthCache[font][text];
5837
5836
  }
@@ -6083,7 +6082,7 @@ class UuidGenerator {
6083
6082
  else {
6084
6083
  // mainly for jest and other browsers that do not have the crypto functionality
6085
6084
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
6086
- var r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
6085
+ const r = (Math.random() * 16) | 0, v = c === "x" ? r : (r & 0x3) | 0x8;
6087
6086
  return v.toString(16);
6088
6087
  });
6089
6088
  }
@@ -6580,10 +6579,9 @@ function localizeNumberLiteral(literal, locale) {
6580
6579
  return literal;
6581
6580
  }
6582
6581
  const decimalNumberRegex = getDecimalNumberRegex(DEFAULT_LOCALE);
6583
- const localized = literal.replace(decimalNumberRegex, (match) => {
6582
+ return literal.replace(decimalNumberRegex, (match) => {
6584
6583
  return match.replace(".", locale.decimalSeparator);
6585
6584
  });
6586
- return localized;
6587
6585
  }
6588
6586
  /**
6589
6587
  * Change a literal string from its canonical form (en_US locale) to the given locale. Also convert date string.
@@ -7068,6 +7066,21 @@ function predictLinearValues(Y, X, newX, computeIntercept) {
7068
7066
  });
7069
7067
  return newY.length === newX.length ? newY : transposeMatrix(newY);
7070
7068
  }
7069
+ function getMovingAverageValues(dataset, windowSize = DEFAULT_WINDOW_SIZE) {
7070
+ const values = [];
7071
+ // Fill the starting values with null until we have a full window
7072
+ for (let i = 0; i < windowSize - 1; i++) {
7073
+ values.push(null);
7074
+ }
7075
+ for (let i = 0; i <= dataset.length - windowSize; i++) {
7076
+ let sum = 0;
7077
+ for (let j = i; j < i + windowSize; j++) {
7078
+ sum += dataset[j];
7079
+ }
7080
+ values.push(sum / windowSize);
7081
+ }
7082
+ return values;
7083
+ }
7071
7084
 
7072
7085
  const PREVIOUS_VALUE = "(previous)";
7073
7086
  const NEXT_VALUE = "(next)";
@@ -7603,8 +7616,7 @@ function getMaxObjectId(o) {
7603
7616
  return 0;
7604
7617
  }
7605
7618
  const nums = keys.map((id) => parseInt(id, 10));
7606
- const max = Math.max(...nums);
7607
- return max;
7619
+ return Math.max(...nums);
7608
7620
  }
7609
7621
  const ALL_PERIODS = {
7610
7622
  year: _t("Year"),
@@ -8457,10 +8469,6 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
8457
8469
  tableZone &&
8458
8470
  zones.some((z) => isZoneInside(tableZone, z))) {
8459
8471
  copiedTablesIds.add(table.id);
8460
- const values = [];
8461
- for (const col of range(tableZone.left, tableZone.right + 1)) {
8462
- values.push(this.getters.getFilterHiddenValues({ sheetId, col, row: tableZone.top }));
8463
- }
8464
8472
  copiedTable = {
8465
8473
  range: coreTable.range.rangeData,
8466
8474
  config: coreTable.config,
@@ -9241,11 +9249,10 @@ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9241
9249
  const { x, y } = getters.getMainViewportCoordinates();
9242
9250
  const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9243
9251
  const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9244
- const position = {
9252
+ return {
9245
9253
  x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9246
9254
  y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9247
9255
  }; // Position at the center of the scrollable viewport
9248
- return position;
9249
9256
  }
9250
9257
  function getChartAxisTitleRuntime(design) {
9251
9258
  if (design?.title?.text) {
@@ -9362,7 +9369,7 @@ function getFullTrendingLineDataSet(dataset, config, data) {
9362
9369
  return {
9363
9370
  ...dataset,
9364
9371
  type: "line",
9365
- xAxisID: TREND_LINE_XAXIS_ID,
9372
+ xAxisID: config.type !== "trailingMovingAverage" ? TREND_LINE_XAXIS_ID : "x",
9366
9373
  label: dataset.label ? _t("Trend line for %s", dataset.label) : "",
9367
9374
  data,
9368
9375
  order: -1,
@@ -9404,6 +9411,9 @@ function interpolateData(config, values, labels, newLabels) {
9404
9411
  case "logarithmic": {
9405
9412
  return predictLinearValues([values], logM([labels]), logM([newLabels]), true)[0];
9406
9413
  }
9414
+ case "trailingMovingAverage": {
9415
+ return getMovingAverageValues(values, config.window);
9416
+ }
9407
9417
  default:
9408
9418
  return [];
9409
9419
  }
@@ -9449,72 +9459,115 @@ const chartShowValuesPlugin = {
9449
9459
  ctx.save();
9450
9460
  ctx.textAlign = "center";
9451
9461
  ctx.textBaseline = "middle";
9452
- ctx.fillStyle = chartFontColor(options.background);
9453
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9454
- chart._metasets.forEach(function (dataset) {
9455
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9456
- return; // ignore trend lines
9457
- }
9458
- switch (dataset.type) {
9459
- case "doughnut":
9460
- case "pie": {
9461
- for (let i = 0; i < dataset._parsed.length; i++) {
9462
- const bar = dataset.data[i];
9463
- const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9464
- const midAngle = (startAngle + endAngle) / 2;
9465
- const midRadius = (innerRadius + outerRadius) / 2;
9466
- const x = bar.x + midRadius * Math.cos(midAngle);
9467
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9468
- ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
9469
- ctx.strokeStyle = chartFontColor(ctx.fillStyle);
9470
- const value = options.callback(dataset._parsed[i]);
9471
- ctx.strokeText(value, x, y);
9472
- ctx.fillText(value, x, y);
9473
- }
9474
- break;
9475
- }
9476
- case "bar":
9477
- case "line": {
9478
- const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
9479
- const horizontalChart = dataset.type === "bar" && options.horizontal;
9480
- const axisId = horizontalChart ? dataset.xAxisID : dataset.yAxisID;
9481
- for (let i = 0; i < dataset._parsed.length; i++) {
9482
- const point = dataset.data[i];
9483
- const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
9484
- const displayedValue = options.callback(value - 0, axisId);
9485
- let xPosition = 0, yPosition = 0;
9486
- if (options.horizontal) {
9487
- yPosition = point.y;
9488
- if (value < 0) {
9489
- ctx.textAlign = "right";
9490
- xPosition = point.x - yOffset;
9491
- }
9492
- else {
9493
- ctx.textAlign = "left";
9494
- xPosition = point.x + yOffset;
9495
- }
9496
- }
9497
- else {
9498
- xPosition = point.x;
9499
- if (value < 0) {
9500
- ctx.textBaseline = "top";
9501
- yPosition = point.y + yOffset;
9502
- }
9503
- else {
9504
- ctx.textBaseline = "bottom";
9505
- yPosition = point.y - yOffset;
9506
- }
9507
- }
9508
- ctx.strokeText(displayedValue, xPosition, yPosition);
9509
- ctx.fillText(displayedValue, xPosition, yPosition);
9510
- }
9511
- break;
9512
- }
9513
- }
9514
- });
9462
+ ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
9463
+ switch (chart.config.type) {
9464
+ case "pie":
9465
+ case "doughnut":
9466
+ drawPieChartValues(chart, options, ctx);
9467
+ break;
9468
+ case "bar":
9469
+ case "line":
9470
+ options.horizontal
9471
+ ? drawHorizontalBarChartValues(chart, options, ctx)
9472
+ : drawLineOrBarChartValues(chart, options, ctx);
9473
+ break;
9474
+ }
9515
9475
  ctx.restore();
9516
9476
  },
9517
9477
  };
9478
+ function drawTextWithBackground(text, x, y, ctx) {
9479
+ ctx.lineWidth = 3; // Stroke the text with a big lineWidth width to have some kind of background
9480
+ ctx.strokeText(text, x, y);
9481
+ ctx.lineWidth = 1;
9482
+ ctx.fillText(text, x, y);
9483
+ }
9484
+ function drawLineOrBarChartValues(chart, options, ctx) {
9485
+ const yMax = chart.chartArea.bottom;
9486
+ const yMin = chart.chartArea.top;
9487
+ const textsPositions = {};
9488
+ for (const dataset of chart._metasets) {
9489
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9490
+ return; // ignore trend lines
9491
+ }
9492
+ for (let i = 0; i < dataset._parsed.length; i++) {
9493
+ const value = dataset._parsed[i].y;
9494
+ const displayValue = options.callback(value - 0, dataset.yAxisID);
9495
+ const point = dataset.data[i];
9496
+ const xPosition = point.x;
9497
+ let yPosition = 0;
9498
+ if (chart.config.type === "line") {
9499
+ yPosition = point.y - 10;
9500
+ }
9501
+ else {
9502
+ yPosition = value < 0 ? point.y - point.height / 2 : point.y + point.height / 2;
9503
+ }
9504
+ yPosition = Math.min(yPosition, yMax);
9505
+ yPosition = Math.max(yPosition, yMin);
9506
+ // Avoid overlapping texts with same X
9507
+ if (!textsPositions[xPosition]) {
9508
+ textsPositions[xPosition] = [];
9509
+ }
9510
+ for (const otherPosition of textsPositions[xPosition] || []) {
9511
+ if (Math.abs(otherPosition - yPosition) < 13) {
9512
+ yPosition = otherPosition - 13;
9513
+ }
9514
+ }
9515
+ textsPositions[xPosition].push(yPosition);
9516
+ ctx.fillStyle = point.options.backgroundColor;
9517
+ ctx.strokeStyle = options.background || "#ffffff";
9518
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9519
+ }
9520
+ }
9521
+ }
9522
+ function drawHorizontalBarChartValues(chart, options, ctx) {
9523
+ const xMax = chart.chartArea.right;
9524
+ const xMin = chart.chartArea.left;
9525
+ const textsPositions = {};
9526
+ for (const dataset of chart._metasets) {
9527
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9528
+ return; // ignore trend lines
9529
+ }
9530
+ for (let i = 0; i < dataset._parsed.length; i++) {
9531
+ const value = dataset._parsed[i].x;
9532
+ const displayValue = options.callback(value - 0, dataset.xAxisID);
9533
+ const point = dataset.data[i];
9534
+ const yPosition = point.y;
9535
+ let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
9536
+ xPosition = Math.min(xPosition, xMax);
9537
+ xPosition = Math.max(xPosition, xMin);
9538
+ // Avoid overlapping texts with same Y
9539
+ if (!textsPositions[yPosition]) {
9540
+ textsPositions[yPosition] = [];
9541
+ }
9542
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: 12 }, "px");
9543
+ for (const otherPosition of textsPositions[yPosition]) {
9544
+ if (Math.abs(otherPosition - xPosition) < textWidth) {
9545
+ xPosition = otherPosition + textWidth + 3;
9546
+ }
9547
+ }
9548
+ textsPositions[yPosition].push(xPosition);
9549
+ ctx.fillStyle = point.options.backgroundColor;
9550
+ ctx.strokeStyle = options.background || "#ffffff";
9551
+ drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
9552
+ }
9553
+ }
9554
+ }
9555
+ function drawPieChartValues(chart, options, ctx) {
9556
+ for (const dataset of chart._metasets) {
9557
+ for (let i = 0; i < dataset._parsed.length; i++) {
9558
+ const bar = dataset.data[i];
9559
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
9560
+ const midAngle = (startAngle + endAngle) / 2;
9561
+ const midRadius = (innerRadius + outerRadius) / 2;
9562
+ const x = bar.x + midRadius * Math.cos(midAngle);
9563
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
9564
+ ctx.fillStyle = chartFontColor(options.background);
9565
+ ctx.strokeStyle = options.background || "#ffffff";
9566
+ const value = options.callback(dataset._parsed[i]);
9567
+ drawTextWithBackground(value, x, y, ctx);
9568
+ }
9569
+ }
9570
+ }
9518
9571
 
9519
9572
  /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
9520
9573
  const waterfallLinesPlugin = {
@@ -10292,8 +10345,7 @@ function getHtmlContentFromPattern(pattern, value, highlightColor, className) {
10292
10345
  value = value.slice(index + 1);
10293
10346
  }
10294
10347
  pendingHtmlContent.push({ value });
10295
- const htmlContent = pendingHtmlContent.filter((content) => content.value);
10296
- return htmlContent;
10348
+ return pendingHtmlContent.filter((content) => content.value);
10297
10349
  }
10298
10350
 
10299
10351
  //------------------------------------------------------------------------------
@@ -10697,7 +10749,7 @@ const MINVERSE = {
10697
10749
  assertSquareMatrix(_t("The argument square_matrix must have the same number of columns and rows."), _matrix);
10698
10750
  const { inverted } = invertMatrix(_matrix);
10699
10751
  if (!inverted) {
10700
- throw new EvaluationError(_t("The matrix is not invertible."));
10752
+ return new EvaluationError(_t("The matrix is not invertible."));
10701
10753
  }
10702
10754
  return inverted;
10703
10755
  },
@@ -10776,7 +10828,7 @@ function getSumXAndY(arrayX, arrayY, cb) {
10776
10828
  }
10777
10829
  }
10778
10830
  if (!validPairFound) {
10779
- throw new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10831
+ return new EvaluationError(_t("The arguments array_x and array_y must contain at least one pair of numbers."));
10780
10832
  }
10781
10833
  return result;
10782
10834
  }
@@ -10857,7 +10909,7 @@ const TOCOL = {
10857
10909
  .flat()
10858
10910
  .filter(shouldKeepValue(_ignore));
10859
10911
  if (result.length === 0) {
10860
- throw new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10912
+ return new NotAvailableError(_t("No results for the given arguments of TOCOL."));
10861
10913
  }
10862
10914
  return [result];
10863
10915
  },
@@ -10878,7 +10930,7 @@ const TOROW = {
10878
10930
  .filter(shouldKeepValue(_ignore))
10879
10931
  .map((item) => [item]);
10880
10932
  if (result.length === 0 || result[0].length === 0) {
10881
- throw new NotAvailableError(_t("No results for the given arguments of TOROW."));
10933
+ return new NotAvailableError(_t("No results for the given arguments of TOROW."));
10882
10934
  }
10883
10935
  return result;
10884
10936
  },
@@ -11438,7 +11490,7 @@ const DECIMAL = {
11438
11490
  * Return error if 'value' is positive.
11439
11491
  * Remove '-?' in the next regex to catch this error.
11440
11492
  */
11441
- assert(() => !!DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11493
+ assert(() => DECIMAL_REPRESENTATION.test(_value), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11442
11494
  const deci = parseInt(_value, _base);
11443
11495
  assert(() => !isNaN(deci), _t("The value (%s) must be a valid base %s representation.", _value, _base.toString()));
11444
11496
  return deci;
@@ -11706,7 +11758,7 @@ const PRODUCT = {
11706
11758
  count += 1;
11707
11759
  }
11708
11760
  if (isEvaluationError(f)) {
11709
- throw j;
11761
+ return j;
11710
11762
  }
11711
11763
  }
11712
11764
  }
@@ -12209,9 +12261,8 @@ function covariance(dataY, dataX, isSample) {
12209
12261
  }
12210
12262
  function variance(args, isSample, textAs0, locale) {
12211
12263
  let count = 0;
12212
- let sum = 0;
12213
12264
  const reduceFunction = textAs0 ? reduceNumbersTextAs0 : reduceNumbers;
12214
- sum = reduceFunction(args, (acc, a) => {
12265
+ const sum = reduceFunction(args, (acc, a) => {
12215
12266
  count += 1;
12216
12267
  return acc + a;
12217
12268
  }, 0, locale);
@@ -12608,7 +12659,7 @@ const MATTHEWS = {
12608
12659
  const flatY = dataY.flat();
12609
12660
  assertSameNumberOfElements(flatX, flatY);
12610
12661
  if (flatX.length === 0) {
12611
- throw new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12662
+ return new EvaluationError(_t("[[FUNCTION_NAME]] expects non-empty ranges for both parameters."));
12612
12663
  }
12613
12664
  const n = flatX.length;
12614
12665
  let trueN = 0, trueP = 0, falseP = 0, falseN = 0;
@@ -13408,9 +13459,8 @@ function getMatchingCells(database, field, criteria, locale) {
13408
13459
  // 4 - return for each database row corresponding, the cells corresponding to the field parameter
13409
13460
  const fieldCol = database[index];
13410
13461
  // Example continuation:: fieldCol = ["C", "j", "k", 7]
13411
- const matchingCells = [...matchingRows].map((x) => fieldCol[x + 1]);
13412
13462
  // Example continuation:: matchingCells = ["j", 7]
13413
- return matchingCells;
13463
+ return [...matchingRows].map((x) => fieldCol[x + 1]);
13414
13464
  }
13415
13465
  const databaseArgs = [
13416
13466
  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.")),
@@ -14595,7 +14645,7 @@ const FILTER = {
14595
14645
  }
14596
14646
  }
14597
14647
  if (!result.length) {
14598
- throw new NotAvailableError(_t("No match found in FILTER evaluation"));
14648
+ return new NotAvailableError(_t("No match found in FILTER evaluation"));
14599
14649
  }
14600
14650
  return mode === "row" ? transposeMatrix(result) : result;
14601
14651
  },
@@ -17293,7 +17343,7 @@ function mapParentFunction(tokens) {
17293
17343
  argsTokens[argPosition].push({ value: token.value, type: token.type });
17294
17344
  }
17295
17345
  }
17296
- const res = tokens.map((token, i) => {
17346
+ return tokens.map((token, i) => {
17297
17347
  if (!["SPACE", "LEFT_PAREN"].includes(token.type)) {
17298
17348
  functionStarted = "";
17299
17349
  }
@@ -17331,7 +17381,6 @@ function mapParentFunction(tokens) {
17331
17381
  }
17332
17382
  return token;
17333
17383
  });
17334
- return res;
17335
17384
  }
17336
17385
  /**
17337
17386
  * Parse the list of tokens that compose the arguments of a function to
@@ -17787,7 +17836,7 @@ const IFS = {
17787
17836
  return result;
17788
17837
  }
17789
17838
  }
17790
- throw new EvaluationError(_t("No match."));
17839
+ return new EvaluationError(_t("No match."));
17791
17840
  },
17792
17841
  isExported: true,
17793
17842
  };
@@ -17951,8 +18000,8 @@ const ADDRESS = {
17951
18000
  let cellReference;
17952
18001
  if (_useA1Notation) {
17953
18002
  const rangePart = {
17954
- rowFixed: [1, 2].includes(_absoluteRelativeMode) ? true : false,
17955
- colFixed: [1, 3].includes(_absoluteRelativeMode) ? true : false,
18003
+ rowFixed: [1, 2].includes(_absoluteRelativeMode),
18004
+ colFixed: [1, 3].includes(_absoluteRelativeMode),
17956
18005
  };
17957
18006
  cellReference = toXC(colNumber - 1, rowNumber - 1, rangePart);
17958
18007
  }
@@ -17978,7 +18027,7 @@ const COLUMN = {
17978
18027
  ],
17979
18028
  compute: function (cellReference) {
17980
18029
  if (isEvaluationError(cellReference?.value)) {
17981
- throw cellReference;
18030
+ return cellReference;
17982
18031
  }
17983
18032
  const column = cellReference === undefined
17984
18033
  ? this.__originCellPosition?.col
@@ -17996,7 +18045,7 @@ const COLUMNS = {
17996
18045
  args: [arg("range (meta)", _t("The range whose column count will be returned."))],
17997
18046
  compute: function (range) {
17998
18047
  if (isEvaluationError(range?.value)) {
17999
- throw range;
18048
+ return range;
18000
18049
  }
18001
18050
  const zone = toZone(range.value);
18002
18051
  return zone.right - zone.left + 1;
@@ -18076,11 +18125,11 @@ const INDIRECT = {
18076
18125
  compute: function (reference, useA1Notation = { value: true }) {
18077
18126
  let _reference = reference?.value?.toString();
18078
18127
  if (!_reference) {
18079
- throw new InvalidReferenceError(_t("Reference should be defined."));
18128
+ return new InvalidReferenceError(_t("Reference should be defined."));
18080
18129
  }
18081
18130
  const _useA1Notation = toBoolean(useA1Notation);
18082
18131
  if (!_useA1Notation) {
18083
- throw new EvaluationError(_t("R1C1 notation is not supported."));
18132
+ return new EvaluationError(_t("R1C1 notation is not supported."));
18084
18133
  }
18085
18134
  const sheetId = this.__originSheetId;
18086
18135
  const originPosition = this.__originCellPosition;
@@ -18092,7 +18141,7 @@ const INDIRECT = {
18092
18141
  }
18093
18142
  const range = this.getters.getRangeFromSheetXC(sheetId, _reference);
18094
18143
  if (range === undefined || range.invalidXc || range.invalidSheetName) {
18095
- throw new InvalidReferenceError();
18144
+ return new InvalidReferenceError();
18096
18145
  }
18097
18146
  if (originPosition) {
18098
18147
  this.addDependencies?.(originPosition, [range]);
@@ -18200,7 +18249,7 @@ const ROW = {
18200
18249
  ],
18201
18250
  compute: function (cellReference) {
18202
18251
  if (isEvaluationError(cellReference?.value)) {
18203
- throw cellReference;
18252
+ return cellReference;
18204
18253
  }
18205
18254
  const row = cellReference === undefined
18206
18255
  ? this.__originCellPosition?.row
@@ -18218,7 +18267,7 @@ const ROWS = {
18218
18267
  args: [arg("range (meta)", _t("The range whose row count will be returned."))],
18219
18268
  compute: function (range) {
18220
18269
  if (isEvaluationError(range?.value)) {
18221
- throw range;
18270
+ return range;
18222
18271
  }
18223
18272
  const zone = toZone(range.value);
18224
18273
  return zone.bottom - zone.top + 1;
@@ -18405,11 +18454,11 @@ const PIVOT = {
18405
18454
  const _pivotFormulaId = toString(pivotFormulaId);
18406
18455
  const _rowCount = toNumber(rowCount, this.locale);
18407
18456
  if (_rowCount < 0) {
18408
- throw new EvaluationError(_t("The number of rows must be positive."));
18457
+ return new EvaluationError(_t("The number of rows must be positive."));
18409
18458
  }
18410
18459
  const _columnCount = toNumber(columnCount, this.locale);
18411
18460
  if (_columnCount < 0) {
18412
- throw new EvaluationError(_t("The number of columns must be positive."));
18461
+ return new EvaluationError(_t("The number of columns must be positive."));
18413
18462
  }
18414
18463
  const _includeColumnHeaders = toBoolean(includeColumnHeaders);
18415
18464
  const _includedTotal = toBoolean(includeTotal);
@@ -18608,6 +18657,12 @@ const EQ = {
18608
18657
  arg("value2 (any)", _t("The value to test against value1 for equality.")),
18609
18658
  ],
18610
18659
  compute: function (value1, value2) {
18660
+ if (isEvaluationError(value1?.value)) {
18661
+ return value1;
18662
+ }
18663
+ if (isEvaluationError(value2?.value)) {
18664
+ return value2;
18665
+ }
18611
18666
  let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18612
18667
  let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18613
18668
  if (typeof _value1 === "string") {
@@ -18616,27 +18671,21 @@ const EQ = {
18616
18671
  if (typeof _value2 === "string") {
18617
18672
  _value2 = _value2.toUpperCase();
18618
18673
  }
18619
- if (isEvaluationError(_value1)) {
18620
- throw value1;
18621
- }
18622
- if (isEvaluationError(_value2)) {
18623
- throw value2;
18624
- }
18625
- return _value1 === _value2;
18674
+ return { value: _value1 === _value2 };
18626
18675
  },
18627
18676
  };
18628
18677
  // -----------------------------------------------------------------------------
18629
18678
  // GT
18630
18679
  // -----------------------------------------------------------------------------
18631
18680
  function applyRelationalOperator(value1, value2, cb) {
18632
- let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18633
- let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18634
- if (isEvaluationError(_value1)) {
18635
- throw value1;
18681
+ if (isEvaluationError(value1?.value)) {
18682
+ return value1;
18636
18683
  }
18637
- if (isEvaluationError(_value2)) {
18638
- throw value2;
18684
+ if (isEvaluationError(value2?.value)) {
18685
+ return value2;
18639
18686
  }
18687
+ let _value1 = isEmpty(value1) ? getNeutral[typeof value2?.value] : value1?.value;
18688
+ let _value2 = isEmpty(value2) ? getNeutral[typeof value1?.value] : value2?.value;
18640
18689
  if (typeof _value1 !== "number") {
18641
18690
  _value1 = toString(_value1).toUpperCase();
18642
18691
  }
@@ -18646,12 +18695,12 @@ function applyRelationalOperator(value1, value2, cb) {
18646
18695
  const tV1 = typeof _value1;
18647
18696
  const tV2 = typeof _value2;
18648
18697
  if (tV1 === "string" && tV2 === "number") {
18649
- return true;
18698
+ return { value: true };
18650
18699
  }
18651
18700
  if (tV2 === "string" && tV1 === "number") {
18652
- return false;
18701
+ return { value: false };
18653
18702
  }
18654
- return cb(_value1, _value2);
18703
+ return { value: cb(_value1, _value2) };
18655
18704
  }
18656
18705
  const GT = {
18657
18706
  description: _t("Strictly greater than."),
@@ -18690,7 +18739,11 @@ const LT = {
18690
18739
  arg("value2 (any)", _t("The second value.")),
18691
18740
  ],
18692
18741
  compute: function (value1, value2) {
18693
- return !GTE.compute.bind(this)(value1, value2);
18742
+ const result = GTE.compute.bind(this)(value1, value2);
18743
+ if (isEvaluationError(result.value)) {
18744
+ return result;
18745
+ }
18746
+ return { value: !result.value };
18694
18747
  },
18695
18748
  };
18696
18749
  // -----------------------------------------------------------------------------
@@ -18703,7 +18756,11 @@ const LTE = {
18703
18756
  arg("value2 (any)", _t("The second value.")),
18704
18757
  ],
18705
18758
  compute: function (value1, value2) {
18706
- return !GT.compute.bind(this)(value1, value2);
18759
+ const result = GT.compute.bind(this)(value1, value2);
18760
+ if (isEvaluationError(result.value)) {
18761
+ return result;
18762
+ }
18763
+ return { value: !result.value };
18707
18764
  },
18708
18765
  };
18709
18766
  // -----------------------------------------------------------------------------
@@ -18748,7 +18805,11 @@ const NE = {
18748
18805
  arg("value2 (any)", _t("The value to test against value1 for inequality.")),
18749
18806
  ],
18750
18807
  compute: function (value1, value2) {
18751
- return !EQ.compute.bind(this)(value1, value2);
18808
+ const result = EQ.compute.bind(this)(value1, value2);
18809
+ if (isEvaluationError(result.value)) {
18810
+ return result;
18811
+ }
18812
+ return { value: !result.value };
18752
18813
  },
18753
18814
  };
18754
18815
  // -----------------------------------------------------------------------------
@@ -19907,13 +19968,12 @@ function cssPropertiesToCss(attributes) {
19907
19968
  }
19908
19969
  function getElementMargins(el) {
19909
19970
  const style = window.getComputedStyle(el);
19910
- const margins = {
19971
+ return {
19911
19972
  top: parseInt(style.marginTop, 10) || 0,
19912
19973
  bottom: parseInt(style.marginBottom, 10) || 0,
19913
19974
  left: parseInt(style.marginLeft, 10) || 0,
19914
19975
  right: parseInt(style.marginRight, 10) || 0,
19915
19976
  };
19916
- return margins;
19917
19977
  }
19918
19978
 
19919
19979
  const macRegex = /Mac/i;
@@ -21788,9 +21848,13 @@ autoCompleteProviders.add("pivot_group_fields", {
21788
21848
  const colFields = columns.map((groupBy) => groupBy.nameWithGranularity);
21789
21849
  const rowFields = rows.map((groupBy) => groupBy.nameWithGranularity);
21790
21850
  const proposals = [];
21791
- const previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21851
+ let previousGroupBy = ["ARG_SEPARATOR", "SPACE"].includes(tokenAtCursor.type)
21792
21852
  ? argGroupBys.at(-1)
21793
21853
  : argGroupBys.at(-2);
21854
+ const isPositionalSupported = supportedPivotPositionalFormulaRegistry.get(pivot.type);
21855
+ if (isPositionalSupported && previousGroupBy?.startsWith("#")) {
21856
+ previousGroupBy = previousGroupBy.slice(1);
21857
+ }
21794
21858
  if (previousGroupBy === undefined) {
21795
21859
  proposals.push(colFields[0]);
21796
21860
  proposals.push(rowFields[0]);
@@ -21812,7 +21876,7 @@ autoCompleteProviders.add("pivot_group_fields", {
21812
21876
  return field ? makeFieldProposal(field, granularity) : undefined;
21813
21877
  })
21814
21878
  .concat(groupBys.map((groupBy) => {
21815
- if (!supportedPivotPositionalFormulaRegistry.get(pivot.type)) {
21879
+ if (!isPositionalSupported) {
21816
21880
  return undefined;
21817
21881
  }
21818
21882
  const fieldName = groupBy.split(":")[0];
@@ -21821,13 +21885,12 @@ autoCompleteProviders.add("pivot_group_fields", {
21821
21885
  return undefined;
21822
21886
  }
21823
21887
  const positionalFieldArg = `"#${groupBy}"`;
21824
- const positionalProposal = {
21888
+ return {
21825
21889
  text: positionalFieldArg,
21826
21890
  description: _t("%s (positional)", field.string) + (field.help ? ` (${field.help})` : ""),
21827
21891
  htmlContent: [{ value: positionalFieldArg, color: tokenColors.STRING }],
21828
21892
  fuzzySearchKey: field.string + positionalFieldArg, // search on translated name and on technical name
21829
21893
  };
21830
- return positionalProposal;
21831
21894
  }))
21832
21895
  .filter(isDefined);
21833
21896
  },
@@ -22096,7 +22159,7 @@ function parseLiteral(content, locale) {
22096
22159
  return internalDate.value;
22097
22160
  }
22098
22161
  if (isBoolean(content)) {
22099
- return content.toUpperCase() === "TRUE" ? true : false;
22162
+ return content.toUpperCase() === "TRUE";
22100
22163
  }
22101
22164
  return content;
22102
22165
  }
@@ -22437,8 +22500,7 @@ function calculateDateIncrementBasedOnGroup(group) {
22437
22500
  return 0;
22438
22501
  }
22439
22502
  const previous = jsDates[index - 1];
22440
- const days = Math.floor(date.getTime()) - Math.floor(previous.getTime());
22441
- return days;
22503
+ return Math.floor(date.getTime()) - Math.floor(previous.getTime());
22442
22504
  })
22443
22505
  .slice(1);
22444
22506
  const equidistantDates = timeIntervals.every((interval) => interval === timeIntervals[0]);
@@ -22997,6 +23059,7 @@ const XLSX_CHART_TYPES = [
22997
23059
  "surface3DChart",
22998
23060
  "bubbleChart",
22999
23061
  "comboChart",
23062
+ "radarChart",
23000
23063
  ];
23001
23064
 
23002
23065
  /** In XLSX color format (no #) */
@@ -23537,7 +23600,7 @@ const CHART_TYPE_CONVERSION_MAP = {
23537
23600
  lineChart: "line",
23538
23601
  line3DChart: undefined,
23539
23602
  stockChart: undefined,
23540
- radarChart: undefined,
23603
+ radarChart: "radar",
23541
23604
  scatterChart: "scatter",
23542
23605
  pieChart: "pie",
23543
23606
  pie3DChart: undefined,
@@ -24950,7 +25013,7 @@ const TABLE_STYLE_CATEGORIES = {
24950
25013
  custom: _t("Custom"),
24951
25014
  };
24952
25015
  const DEFAULT_TABLE_CONFIG = {
24953
- hasFilters: true,
25016
+ hasFilters: false,
24954
25017
  totalRow: false,
24955
25018
  firstColumn: false,
24956
25019
  lastColumn: false,
@@ -25619,12 +25682,11 @@ class XlsxBaseExtractor {
25619
25682
  * Get the list of all the XLSX files in the XLSX file structure
25620
25683
  */
25621
25684
  getListOfXMLFiles() {
25622
- const XMLFiles = Object.entries(this.xlsxFileStructure)
25685
+ return Object.entries(this.xlsxFileStructure)
25623
25686
  .filter(([key]) => key !== "images")
25624
25687
  .map(([_, value]) => value)
25625
25688
  .flat()
25626
25689
  .filter(isDefined);
25627
- return XMLFiles;
25628
25690
  }
25629
25691
  /**
25630
25692
  * Return an array containing the return value of the given function applied to all the XML elements
@@ -25788,13 +25850,12 @@ class XlsxBaseExtractor {
25788
25850
  rgb = this.extractAttr(colorElement, "rgb")?.asString();
25789
25851
  rgb = rgb === DEFAULT_SYSTEM_COLOR ? undefined : rgb;
25790
25852
  }
25791
- const color = {
25853
+ return {
25792
25854
  rgb: rgb || defaultColor,
25793
25855
  auto: this.extractAttr(colorElement, "auto")?.asBool(),
25794
25856
  indexed: this.extractAttr(colorElement, "indexed")?.asNum(),
25795
25857
  tint: this.extractAttr(colorElement, "tint")?.asNum(),
25796
25858
  };
25797
- return color;
25798
25859
  }
25799
25860
  /**
25800
25861
  * Returns the xml file targeted by a relationship.
@@ -26386,7 +26447,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26386
26447
  hyperlinks: this.extractHyperLinks(sheetElement),
26387
26448
  tables: this.extractTables(sheetElement),
26388
26449
  pivotTables: this.extractPivotTables(),
26389
- isVisible: sheetWorkbookInfo.state === "visible" ? true : false,
26450
+ isVisible: sheetWorkbookInfo.state === "visible",
26390
26451
  };
26391
26452
  })[0];
26392
26453
  }
@@ -26457,8 +26518,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26457
26518
  const figures = this.mapOnElements({ parent: worksheet, query: "drawing" }, (drawingElement) => {
26458
26519
  const drawingId = this.extractAttr(drawingElement, "r:id", { required: true })?.asString();
26459
26520
  const drawingFile = this.getTargetXmlFile(this.relationships[drawingId]);
26460
- const figures = new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26461
- return figures;
26521
+ return new XlsxFigureExtractor(drawingFile, this.xlsxFileStructure, this.warningManager).extractFigures();
26462
26522
  })[0];
26463
26523
  return figures || [];
26464
26524
  }
@@ -26476,8 +26536,7 @@ class XlsxSheetExtractor extends XlsxBaseExtractor {
26476
26536
  .filter((relationship) => relationship.type.endsWith("pivotTable"))
26477
26537
  .map((pivotRelationship) => {
26478
26538
  const pivotFile = this.getTargetXmlFile(pivotRelationship);
26479
- const pivot = new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26480
- return pivot;
26539
+ return new XlsxPivotExtractor(pivotFile, this.xlsxFileStructure, this.warningManager).getPivotTable();
26481
26540
  });
26482
26541
  }
26483
26542
  catch (e) {
@@ -26876,8 +26935,7 @@ class XlsxReader {
26876
26935
  }
26877
26936
  convertXlsx() {
26878
26937
  const xlsxData = this.getXlsxData();
26879
- const convertedData = this.convertImportedData(xlsxData);
26880
- return convertedData;
26938
+ return this.convertImportedData(xlsxData);
26881
26939
  }
26882
26940
  // ---------------------------------------------------------------------------
26883
26941
  // Parsing XMLs
@@ -27468,6 +27526,20 @@ migrationStepRegistry
27468
27526
  }
27469
27527
  return data;
27470
27528
  },
27529
+ })
27530
+ .add("migration_22", {
27531
+ // "tables are no longer inserted with filters by default",
27532
+ versionFrom: "22",
27533
+ migrate(data) {
27534
+ for (const sheet of data.sheets || []) {
27535
+ for (const table of sheet.tables || []) {
27536
+ if (!table.config) {
27537
+ table.config = { ...DEFAULT_TABLE_CONFIG, hasFilters: true };
27538
+ }
27539
+ }
27540
+ }
27541
+ return data;
27542
+ },
27471
27543
  });
27472
27544
  function fixOverlappingFilters(data) {
27473
27545
  for (let sheet of data.sheets || []) {
@@ -27495,7 +27567,7 @@ function fixOverlappingFilters(data) {
27495
27567
  * a breaking change is made in the way the state is handled, and an upgrade
27496
27568
  * function should be defined
27497
27569
  */
27498
- const CURRENT_VERSION = 22;
27570
+ const CURRENT_VERSION = 23;
27499
27571
  const INITIAL_SHEET_ID = "Sheet1";
27500
27572
  /**
27501
27573
  * This function tries to load anything that could look like a valid
@@ -27508,7 +27580,7 @@ function load(data, verboseImport) {
27508
27580
  if (!data) {
27509
27581
  return createEmptyWorkbookData();
27510
27582
  }
27511
- console.group("Loading data");
27583
+ console.debug("### Loading data ###");
27512
27584
  const start = performance.now();
27513
27585
  if (data["[Content_Types].xml"]) {
27514
27586
  const reader = new XlsxReader(data);
@@ -27522,13 +27594,13 @@ function load(data, verboseImport) {
27522
27594
  // apply migrations, if needed
27523
27595
  if ("version" in data) {
27524
27596
  if (data.version < CURRENT_VERSION) {
27525
- console.info("Migrating data from version", data.version);
27597
+ console.debug("Migrating data from version", data.version);
27526
27598
  data = migrate(data);
27527
27599
  }
27528
27600
  }
27529
27601
  data = repairData(data);
27530
- console.info("Data loaded in", performance.now() - start, "ms");
27531
- console.groupEnd();
27602
+ console.debug("Data loaded in", performance.now() - start, "ms");
27603
+ console.debug("###");
27532
27604
  return data;
27533
27605
  }
27534
27606
  // -----------------------------------------------------------------------------
@@ -27558,7 +27630,7 @@ function migrate(data) {
27558
27630
  for (let i = index; i < steps.length; i++) {
27559
27631
  data = steps[i].migrate(data);
27560
27632
  }
27561
- console.info("Data migrated in", performance.now() - start, "ms");
27633
+ console.debug("Data migrated in", performance.now() - start, "ms");
27562
27634
  return data;
27563
27635
  }
27564
27636
  /**
@@ -27740,7 +27812,7 @@ function createEmptySheet(sheetId, name) {
27740
27812
  };
27741
27813
  }
27742
27814
  function createEmptyWorkbookData(sheetName = "Sheet1") {
27743
- const data = {
27815
+ return {
27744
27816
  version: CURRENT_VERSION,
27745
27817
  sheets: [createEmptySheet(INITIAL_SHEET_ID, sheetName)],
27746
27818
  styles: {},
@@ -27753,7 +27825,6 @@ function createEmptyWorkbookData(sheetName = "Sheet1") {
27753
27825
  pivotNextId: 1,
27754
27826
  customTableStyles: {},
27755
27827
  };
27756
- return data;
27757
27828
  }
27758
27829
  function createEmptyExcelSheet(sheetId, name) {
27759
27830
  return {
@@ -28137,7 +28208,7 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { axisFormats, local
28137
28208
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
28138
28209
  // tooltipItem.parsed can be an object or a number for pie charts
28139
28210
  let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
28140
- if (!yLabel) {
28211
+ if (yLabel === undefined || yLabel === null) {
28141
28212
  yLabel = tooltipItem.parsed;
28142
28213
  }
28143
28214
  const axisId = horizontalChart
@@ -28531,8 +28602,7 @@ function createBarChartRuntime(chart, getters) {
28531
28602
  };
28532
28603
  config.data.datasets.push(dataset);
28533
28604
  if (definition.dataSets?.[index]?.label) {
28534
- const label = definition.dataSets[index].label;
28535
- dataset.label = label;
28605
+ dataset.label = definition.dataSets[index].label;
28536
28606
  }
28537
28607
  dataset.yAxisID = chart.horizontal ? "y" : definition.dataSets[index].yAxisId || "y";
28538
28608
  dataset.xAxisID = "x";
@@ -28560,13 +28630,10 @@ function createBarChartRuntime(chart, getters) {
28560
28630
  * datasets to ensure the way we distinguish the originals and trendLine datasets after
28561
28631
  */
28562
28632
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28563
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28564
28633
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28565
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28566
- // @ts-expect-error
28567
- return originalTooltipTitle?.(tooltipItems);
28568
- }
28569
- return "";
28634
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
28635
+ ? undefined
28636
+ : "";
28570
28637
  };
28571
28638
  }
28572
28639
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -28904,7 +28971,6 @@ function createLineOrScatterChartRuntime(chart, getters) {
28904
28971
  else if (axisType === "linear") {
28905
28972
  config.options.scales.x.type = "linear";
28906
28973
  config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
28907
- config.options.plugins.tooltip.callbacks.title = () => "";
28908
28974
  config.options.plugins.tooltip.callbacks.label = (tooltipItem) => {
28909
28975
  const dataSetPoint = dataSetsValues[tooltipItem.datasetIndex].data[tooltipItem.dataIndex];
28910
28976
  let label = tooltipItem.label || labelValues.values[tooltipItem.dataIndex];
@@ -28959,8 +29025,7 @@ function createLineOrScatterChartRuntime(chart, getters) {
28959
29025
  const trendDatasets = [];
28960
29026
  for (const [index, dataset] of config.data.datasets.entries()) {
28961
29027
  if (definition.dataSets?.[index]?.label) {
28962
- const label = definition.dataSets[index].label;
28963
- dataset.label = label;
29028
+ dataset.label = definition.dataSets[index].label;
28964
29029
  }
28965
29030
  dataset["yAxisID"] = definition.dataSets[index].yAxisId || "y";
28966
29031
  const trend = definition.dataSets?.[index].trend;
@@ -28989,15 +29054,12 @@ function createLineOrScatterChartRuntime(chart, getters) {
28989
29054
  * distinguish the originals and trendLine datasets after
28990
29055
  */
28991
29056
  trendDatasets.forEach((x) => config.data.datasets.push(x));
28992
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
28993
- config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
28994
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
28995
- // @ts-expect-error
28996
- return originalTooltipTitle?.(tooltipItems);
28997
- }
28998
- return "";
28999
- };
29000
29057
  }
29058
+ config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29059
+ const displayTooltipTitle = axisType !== "linear" &&
29060
+ tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID);
29061
+ return displayTooltipTitle ? undefined : "";
29062
+ };
29001
29063
  return {
29002
29064
  chartJsConfig: config,
29003
29065
  background: chart.background || BACKGROUND_CHART_COLOR,
@@ -29220,13 +29282,10 @@ function createComboChartRuntime(chart, getters) {
29220
29282
  * distinguish the originals and trendLine datasets after
29221
29283
  */
29222
29284
  trendDatasets.forEach((x) => config.data.datasets.push(x));
29223
- const originalTooltipTitle = config.options.plugins.tooltip.callbacks.title;
29224
29285
  config.options.plugins.tooltip.callbacks.title = function (tooltipItems) {
29225
- if (tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)) {
29226
- // @ts-expect-error
29227
- return originalTooltipTitle?.(tooltipItems);
29228
- }
29229
- return "";
29286
+ return tooltipItems.some((item) => item.dataset.xAxisID !== TREND_LINE_XAXIS_ID)
29287
+ ? undefined
29288
+ : "";
29230
29289
  };
29231
29290
  }
29232
29291
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
@@ -29965,6 +30024,198 @@ function createPyramidChartRuntime(chart, getters) {
29965
30024
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
29966
30025
  }
29967
30026
 
30027
+ class RadarChart extends AbstractChart {
30028
+ dataSets;
30029
+ labelRange;
30030
+ background;
30031
+ legendPosition;
30032
+ stacked;
30033
+ aggregated;
30034
+ type = "radar";
30035
+ dataSetsHaveTitle;
30036
+ dataSetDesign;
30037
+ fillArea;
30038
+ constructor(definition, sheetId, getters) {
30039
+ super(definition, sheetId, getters);
30040
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
30041
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
30042
+ this.background = definition.background;
30043
+ this.legendPosition = definition.legendPosition;
30044
+ this.stacked = definition.stacked;
30045
+ this.aggregated = definition.aggregated;
30046
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
30047
+ this.dataSetDesign = definition.dataSets;
30048
+ this.fillArea = definition.fillArea;
30049
+ }
30050
+ static transformDefinition(definition, executed) {
30051
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
30052
+ }
30053
+ static validateChartDefinition(validator, definition) {
30054
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
30055
+ }
30056
+ static getDefinitionFromContextCreation(context) {
30057
+ return {
30058
+ background: context.background,
30059
+ dataSets: context.range ?? [],
30060
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
30061
+ stacked: context.stacked ?? false,
30062
+ aggregated: context.aggregated ?? false,
30063
+ legendPosition: context.legendPosition ?? "top",
30064
+ title: context.title || { text: "" },
30065
+ type: "radar",
30066
+ labelRange: context.auxiliaryRange || undefined,
30067
+ fillArea: context.fillArea ?? false,
30068
+ };
30069
+ }
30070
+ getContextCreation() {
30071
+ const range = [];
30072
+ for (const [i, dataSet] of this.dataSets.entries()) {
30073
+ range.push({
30074
+ ...this.dataSetDesign?.[i],
30075
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
30076
+ });
30077
+ }
30078
+ return {
30079
+ ...this,
30080
+ range,
30081
+ auxiliaryRange: this.labelRange
30082
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
30083
+ : undefined,
30084
+ };
30085
+ }
30086
+ copyForSheetId(sheetId) {
30087
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30088
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30089
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30090
+ return new RadarChart(definition, sheetId, this.getters);
30091
+ }
30092
+ copyInSheetId(sheetId) {
30093
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
30094
+ return new RadarChart(definition, sheetId, this.getters);
30095
+ }
30096
+ getDefinition() {
30097
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
30098
+ }
30099
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
30100
+ const ranges = [];
30101
+ for (const [i, dataSet] of dataSets.entries()) {
30102
+ ranges.push({
30103
+ ...this.dataSetDesign?.[i],
30104
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
30105
+ });
30106
+ }
30107
+ return {
30108
+ type: "radar",
30109
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
30110
+ background: this.background,
30111
+ dataSets: ranges,
30112
+ legendPosition: this.legendPosition,
30113
+ labelRange: labelRange
30114
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
30115
+ : undefined,
30116
+ title: this.title,
30117
+ stacked: this.stacked,
30118
+ aggregated: this.aggregated,
30119
+ fillArea: this.fillArea,
30120
+ };
30121
+ }
30122
+ getDefinitionForExcel() {
30123
+ if (this.aggregated) {
30124
+ return undefined;
30125
+ }
30126
+ const dataSets = this.dataSets
30127
+ .map((ds) => toExcelDataset(this.getters, ds))
30128
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
30129
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
30130
+ const definition = this.getDefinition();
30131
+ return {
30132
+ ...definition,
30133
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
30134
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
30135
+ dataSets,
30136
+ labelRange,
30137
+ };
30138
+ }
30139
+ updateRanges(applyChange) {
30140
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
30141
+ if (!isStale) {
30142
+ return this;
30143
+ }
30144
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
30145
+ return new RadarChart(definition, this.sheetId, this.getters);
30146
+ }
30147
+ }
30148
+ function createRadarChartRuntime(chart, getters) {
30149
+ const definition = chart.getDefinition();
30150
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
30151
+ let labels = labelValues.formattedValues;
30152
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
30153
+ if (chart.dataSetsHaveTitle &&
30154
+ dataSetsValues[0] &&
30155
+ labels.length > dataSetsValues[0].data.length) {
30156
+ labels.shift();
30157
+ }
30158
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
30159
+ if (chart.aggregated) {
30160
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
30161
+ }
30162
+ const leftAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "left");
30163
+ const rightAxisFormat = getChartDatasetFormat(getters, chart.dataSets, "right");
30164
+ const axisFormats = { y: leftAxisFormat, y1: rightAxisFormat };
30165
+ const locale = getters.getLocale();
30166
+ const fontColor = chartFontColor(chart.background);
30167
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
30168
+ axisFormats,
30169
+ locale,
30170
+ });
30171
+ const legend = {
30172
+ labels: { color: fontColor },
30173
+ };
30174
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
30175
+ legend.display = false;
30176
+ }
30177
+ else {
30178
+ legend.position = chart.legendPosition;
30179
+ }
30180
+ const fill = definition.fillArea ?? false;
30181
+ if (!fill) {
30182
+ legend.labels["boxHeight"] = 0;
30183
+ }
30184
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
30185
+ config.options.plugins.tooltip = {
30186
+ ...config.options.plugins?.tooltip,
30187
+ callbacks: {
30188
+ label: function (tooltipItem) {
30189
+ const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
30190
+ const yLabel = tooltipItem.parsed.r;
30191
+ return xLabel ? `${xLabel}: ${yLabel}` : yLabel.toString();
30192
+ },
30193
+ },
30194
+ };
30195
+ config.options.layout = {
30196
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
30197
+ };
30198
+ const colorGenerator = getChartColorsGenerator(definition, dataSetsValues.length);
30199
+ for (let i = 0; i < dataSetsValues.length; i++) {
30200
+ let { label, data } = dataSetsValues[i];
30201
+ if (definition.dataSets?.[i]?.label) {
30202
+ label = definition.dataSets[i].label;
30203
+ }
30204
+ const borderColor = colorGenerator.next();
30205
+ const dataset = {
30206
+ label,
30207
+ data,
30208
+ borderColor,
30209
+ };
30210
+ if (fill) {
30211
+ dataset.backgroundColor = setColorAlpha(borderColor, 0.3);
30212
+ dataset["fill"] = true;
30213
+ }
30214
+ config.data.datasets.push(dataset);
30215
+ }
30216
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30217
+ }
30218
+
29968
30219
  class ScatterChart extends AbstractChart {
29969
30220
  dataSets;
29970
30221
  labelRange;
@@ -30487,6 +30738,15 @@ chartRegistry.add("pyramid", {
30487
30738
  getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
30488
30739
  sequence: 80,
30489
30740
  });
30741
+ chartRegistry.add("radar", {
30742
+ match: (type) => type === "radar",
30743
+ createChart: (definition, sheetId, getters) => new RadarChart(definition, sheetId, getters),
30744
+ getChartRuntime: createRadarChartRuntime,
30745
+ validateChartDefinition: RadarChart.validateChartDefinition,
30746
+ transformDefinition: RadarChart.transformDefinition,
30747
+ getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30748
+ sequence: 80,
30749
+ });
30490
30750
  const chartComponentRegistry = new Registry();
30491
30751
  chartComponentRegistry.add("line", ChartJsComponent);
30492
30752
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30497,6 +30757,7 @@ chartComponentRegistry.add("scatter", ChartJsComponent);
30497
30757
  chartComponentRegistry.add("scorecard", ScorecardChart);
30498
30758
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30499
30759
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30760
+ chartComponentRegistry.add("radar", ChartJsComponent);
30500
30761
  const chartCategories = {
30501
30762
  line: _t("Line"),
30502
30763
  column: _t("Column"),
@@ -30638,6 +30899,24 @@ chartSubtypeRegistry
30638
30899
  chartType: "pyramid",
30639
30900
  category: "misc",
30640
30901
  preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
30902
+ })
30903
+ .add("radar", {
30904
+ matcher: (definition) => definition.type === "radar" && !definition.fillArea,
30905
+ displayName: _t("Radar"),
30906
+ chartSubtype: "radar",
30907
+ chartType: "radar",
30908
+ subtypeDefinition: { fillArea: false },
30909
+ category: "misc",
30910
+ preview: "o-spreadsheet-ChartPreview.RADAR_CHART",
30911
+ })
30912
+ .add("filled_radar", {
30913
+ matcher: (definition) => definition.type === "radar" && !!definition.fillArea,
30914
+ displayName: _t("Filled Radar"),
30915
+ chartType: "radar",
30916
+ chartSubtype: "filled_radar",
30917
+ subtypeDefinition: { fillArea: true },
30918
+ category: "misc",
30919
+ preview: "o-spreadsheet-ChartPreview.FILLED_RADAR_CHART",
30641
30920
  });
30642
30921
 
30643
30922
  /**
@@ -30705,11 +30984,10 @@ function centerFigurePosition(getters, size) {
30705
30984
  const rect = getters.getVisibleRect(getters.getActiveMainViewport());
30706
30985
  const scrollableViewportWidth = Math.min(rect.width, dim.width - offsetCorrectionX);
30707
30986
  const scrollableViewportHeight = Math.min(rect.height, dim.height - offsetCorrectionY);
30708
- const position = {
30987
+ return {
30709
30988
  x: offsetCorrectionX + scrollX + Math.max(0, (scrollableViewportWidth - size.width) / 2),
30710
30989
  y: offsetCorrectionY + scrollY + Math.max(0, (scrollableViewportHeight - size.height) / 2),
30711
30990
  }; // Position at the center of the scrollable viewport
30712
- return position;
30713
30991
  }
30714
30992
  function getMaxFigureSize(getters, figureSize) {
30715
30993
  const size = deepCopy(figureSize);
@@ -31282,14 +31560,13 @@ class PopoverPositionContext {
31282
31560
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
31283
31561
  const shouldRenderAtRight = this.shouldRenderAtRight(elDims.width);
31284
31562
  verticalOffset = shouldRenderAtBottom ? verticalOffset : -verticalOffset;
31285
- const cssProperties = {
31563
+ return {
31286
31564
  top: this.getTopCoordinate(actualHeight, shouldRenderAtBottom) -
31287
31565
  this.spreadsheetOffset.y -
31288
31566
  verticalOffset +
31289
31567
  "px",
31290
31568
  left: this.getLeftCoordinate(actualWidth, shouldRenderAtRight) - this.spreadsheetOffset.x + "px",
31291
31569
  };
31292
- return cssProperties;
31293
31570
  }
31294
31571
  getCurrentPosition(elDims) {
31295
31572
  const shouldRenderAtBottom = this.shouldRenderAtBottom(elDims.height);
@@ -32517,7 +32794,7 @@ function getSmartChartDefinition(zone, getters) {
32517
32794
  * Create a table on the selected zone, with UI warnings to the user if the creation fails.
32518
32795
  * If a single cell is selected, expand the selection to non-empty adjacent cells to create a table.
32519
32796
  */
32520
- function interactiveCreateTable(env, sheetId, tableConfig) {
32797
+ function interactiveCreateTable(env, sheetId, tableConfig = DEFAULT_TABLE_CONFIG) {
32521
32798
  let target = env.model.getters.getSelectedZones();
32522
32799
  let isDynamic = env.model.getters.canCreateDynamicTableOnZones(sheetId, target);
32523
32800
  if (target.length === 1 && !isDynamic && getZoneArea(target[0]) === 1) {
@@ -33576,7 +33853,7 @@ function getColumnsNumber(env) {
33576
33853
  }
33577
33854
 
33578
33855
  const pivotProperties = {
33579
- name: _t("Edit Pivot"),
33856
+ name: _t("See pivot properties"),
33580
33857
  execute(env) {
33581
33858
  const position = env.model.getters.getActivePosition();
33582
33859
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
@@ -33730,8 +34007,7 @@ cellMenuRegistry
33730
34007
  })
33731
34008
  .add("pivot_properties", {
33732
34009
  ...pivotProperties,
33733
- sequence: 160,
33734
- separator: true,
34010
+ sequence: 170,
33735
34011
  });
33736
34012
 
33737
34013
  const sortRange = {
@@ -35478,6 +35754,7 @@ class Section extends Component {
35478
35754
  static template = "o_spreadsheet.Section";
35479
35755
  static props = {
35480
35756
  class: { type: String, optional: true },
35757
+ title: { type: String, optional: true },
35481
35758
  slots: Object,
35482
35759
  };
35483
35760
  }
@@ -36207,17 +36484,11 @@ class BarConfigPanel extends GenericChartConfigPanel {
36207
36484
  stacked,
36208
36485
  });
36209
36486
  }
36210
- onUpdateAggregated(aggregated) {
36211
- this.props.updateChart(this.props.figureId, {
36212
- aggregated,
36213
- });
36214
- }
36215
36487
  }
36216
36488
 
36217
36489
  css /* scss */ `
36218
36490
  .o_side_panel_collapsible_title {
36219
36491
  font-size: 16px;
36220
- font-weight: bold;
36221
36492
  cursor: pointer;
36222
36493
  padding: 6px 0px 6px 6px !important;
36223
36494
 
@@ -36254,49 +36525,40 @@ class SidePanelCollapsible extends Component {
36254
36525
  static template = "o-spreadsheet-SidePanelCollapsible";
36255
36526
  static props = {
36256
36527
  slots: Object,
36528
+ title: { type: String, optional: true },
36257
36529
  collapsedAtInit: { type: Boolean, optional: true },
36258
36530
  class: { type: String, optional: true },
36259
36531
  };
36260
36532
  currentId = (CURRENT_COLLAPSIBLE_ID++).toString();
36261
36533
  }
36262
36534
 
36263
- const CIRCLE_SVG = /*xml*/ `
36264
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
36265
- <circle r="2" fill="#FFF"/>
36266
- </svg>
36267
- `;
36268
36535
  css /* scss */ `
36269
- .o-radio {
36270
- input {
36271
- appearance: none;
36272
- -webkit-appearance: none;
36273
- -moz-appearance: none;
36274
- width: 14px;
36275
- height: 14px;
36276
- border: 1px solid ${GRAY_300};
36277
- box-sizing: border-box;
36278
- outline: none;
36279
- border-radius: 8px;
36280
-
36281
- &:checked {
36282
- background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
36283
- background-color: ${ACTION_COLOR};
36536
+ .o-badge-selection {
36537
+ gap: 1px;
36538
+ button.o-button {
36539
+ border-radius: 0;
36540
+ &.selected {
36541
+ color: ${GRAY_900};
36284
36542
  border-color: ${ACTION_COLOR};
36543
+ background: ${BADGE_SELECTED_COLOR};
36544
+ font-weight: 600;
36545
+ }
36546
+
36547
+ &:first-child {
36548
+ border-radius: 4px 0 0 4px;
36549
+ }
36550
+ &:last-child {
36551
+ border-radius: 0 4px 4px 0;
36285
36552
  }
36286
36553
  }
36287
36554
  }
36288
36555
  `;
36289
- class RadioSelection extends Component {
36290
- static template = "o-spreadsheet.RadioSelection";
36556
+ class BadgeSelection extends Component {
36557
+ static template = "o-spreadsheet.BadgeSelection";
36291
36558
  static props = {
36292
36559
  choices: Array,
36293
36560
  onChange: Function,
36294
- selectedValue: { optional: false },
36295
- name: String,
36296
- direction: { type: String, optional: true },
36297
- };
36298
- static defaultProps = {
36299
- direction: "horizontal",
36561
+ selectedValue: String,
36300
36562
  };
36301
36563
  }
36302
36564
 
@@ -36841,86 +37103,6 @@ class ColorPickerWidget extends Component {
36841
37103
  }
36842
37104
  }
36843
37105
 
36844
- const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
36845
- <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
36846
- <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
36847
- </svg>
36848
- `;
36849
- css /* scss */ `
36850
- .o-round-color-picker-button {
36851
- width: 18px;
36852
- height: 18px;
36853
- cursor: pointer;
36854
- border: 1px solid ${GRAY_300};
36855
- background-position: 1px 1px;
36856
- background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
36857
- }
36858
- `;
36859
- class RoundColorPicker extends Component {
36860
- static template = "o-spreadsheet.RoundColorPicker";
36861
- static components = { ColorPickerWidget, Section, ColorPicker };
36862
- static props = {
36863
- currentColor: { type: String, optional: true },
36864
- title: { type: String, optional: true },
36865
- onColorPicked: Function,
36866
- };
36867
- colorPickerButtonRef = useRef("colorPickerButton");
36868
- state;
36869
- setup() {
36870
- this.state = useState({ pickerOpened: false });
36871
- useExternalListener(window, "click", this.closePicker);
36872
- }
36873
- closePicker() {
36874
- this.state.pickerOpened = false;
36875
- }
36876
- togglePicker() {
36877
- this.state.pickerOpened = !this.state.pickerOpened;
36878
- }
36879
- onColorPicked(color) {
36880
- this.props.onColorPicked(color);
36881
- this.state.pickerOpened = false;
36882
- }
36883
- get colorPickerAnchorRect() {
36884
- const button = this.colorPickerButtonRef.el;
36885
- return getBoundingRectAsPOJO(button);
36886
- }
36887
- get buttonStyle() {
36888
- return cssPropertiesToCss({
36889
- background: this.props.currentColor,
36890
- });
36891
- }
36892
- }
36893
-
36894
- css /* scss */ `
36895
- .o-badge-selection {
36896
- gap: 1px;
36897
- button.o-button {
36898
- border-radius: 0;
36899
- &.selected {
36900
- color: ${GRAY_900};
36901
- border-color: ${ACTION_COLOR};
36902
- background: ${BADGE_SELECTED_COLOR};
36903
- font-weight: 600;
36904
- }
36905
-
36906
- &:first-child {
36907
- border-radius: 4px 0 0 4px;
36908
- }
36909
- &:last-child {
36910
- border-radius: 0 4px 4px 0;
36911
- }
36912
- }
36913
- }
36914
- `;
36915
- class BadgeSelection extends Component {
36916
- static template = "o-spreadsheet.BadgeSelection";
36917
- static props = {
36918
- choices: Array,
36919
- onChange: Function,
36920
- selectedValue: String,
36921
- };
36922
- }
36923
-
36924
37106
  css /* scss */ `
36925
37107
  .o-chart-title-designer {
36926
37108
  > span {
@@ -37075,8 +37257,7 @@ class AxisDesignEditor extends Component {
37075
37257
  this.props.updateChart(this.props.figureId, { axesDesign });
37076
37258
  }
37077
37259
  updateAxisEditor(ev) {
37078
- const axis = ev.target.value;
37079
- this.state.currentAxis = axis;
37260
+ this.state.currentAxis = ev.target.value;
37080
37261
  }
37081
37262
  getAxisTitle() {
37082
37263
  const axesDesign = this.props.definition.axesDesign ?? {};
@@ -37095,6 +37276,56 @@ class AxisDesignEditor extends Component {
37095
37276
  }
37096
37277
  }
37097
37278
 
37279
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
37280
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
37281
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
37282
+ </svg>
37283
+ `;
37284
+ css /* scss */ `
37285
+ .o-round-color-picker-button {
37286
+ width: 18px;
37287
+ height: 18px;
37288
+ cursor: pointer;
37289
+ border: 1px solid ${GRAY_300};
37290
+ background-position: 1px 1px;
37291
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
37292
+ }
37293
+ `;
37294
+ class RoundColorPicker extends Component {
37295
+ static template = "o-spreadsheet.RoundColorPicker";
37296
+ static components = { Section, ColorPicker };
37297
+ static props = {
37298
+ currentColor: { type: String, optional: true },
37299
+ title: { type: String, optional: true },
37300
+ onColorPicked: Function,
37301
+ };
37302
+ colorPickerButtonRef = useRef("colorPickerButton");
37303
+ state;
37304
+ setup() {
37305
+ this.state = useState({ pickerOpened: false });
37306
+ useExternalListener(window, "click", this.closePicker);
37307
+ }
37308
+ closePicker() {
37309
+ this.state.pickerOpened = false;
37310
+ }
37311
+ togglePicker() {
37312
+ this.state.pickerOpened = !this.state.pickerOpened;
37313
+ }
37314
+ onColorPicked(color) {
37315
+ this.props.onColorPicked(color);
37316
+ this.state.pickerOpened = false;
37317
+ }
37318
+ get colorPickerAnchorRect() {
37319
+ const button = this.colorPickerButtonRef.el;
37320
+ return getBoundingRectAsPOJO(button);
37321
+ }
37322
+ get buttonStyle() {
37323
+ return cssPropertiesToCss({
37324
+ background: this.props.currentColor,
37325
+ });
37326
+ }
37327
+ }
37328
+
37098
37329
  class GeneralDesignEditor extends Component {
37099
37330
  static template = "o-spreadsheet-GeneralDesignEditor";
37100
37331
  static components = {
@@ -37159,58 +37390,75 @@ class GeneralDesignEditor extends Component {
37159
37390
  }
37160
37391
  }
37161
37392
 
37162
- class ChartWithAxisDesignPanel extends Component {
37163
- static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37393
+ const CIRCLE_SVG = /*xml*/ `
37394
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'>
37395
+ <circle r="2" fill="#FFF"/>
37396
+ </svg>
37397
+ `;
37398
+ css /* scss */ `
37399
+ .o-radio {
37400
+ input {
37401
+ appearance: none;
37402
+ -webkit-appearance: none;
37403
+ -moz-appearance: none;
37404
+ width: 14px;
37405
+ height: 14px;
37406
+ border: 1px solid ${GRAY_300};
37407
+ box-sizing: border-box;
37408
+ outline: none;
37409
+ border-radius: 8px;
37410
+
37411
+ &:checked {
37412
+ background: url("data:image/svg+xml,${encodeURIComponent(CIRCLE_SVG)}");
37413
+ background-color: ${ACTION_COLOR};
37414
+ border-color: ${ACTION_COLOR};
37415
+ }
37416
+ }
37417
+ }
37418
+ `;
37419
+ class RadioSelection extends Component {
37420
+ static template = "o-spreadsheet.RadioSelection";
37421
+ static props = {
37422
+ choices: Array,
37423
+ onChange: Function,
37424
+ selectedValue: { optional: false },
37425
+ name: String,
37426
+ direction: { type: String, optional: true },
37427
+ };
37428
+ static defaultProps = {
37429
+ direction: "horizontal",
37430
+ };
37431
+ }
37432
+
37433
+ class SeriesDesignEditor extends Component {
37434
+ static template = "o-spreadsheet-SeriesDesignEditor";
37164
37435
  static components = {
37165
- GeneralDesignEditor,
37166
37436
  SidePanelCollapsible,
37167
37437
  Section,
37168
- AxisDesignEditor,
37169
37438
  RoundColorPicker,
37170
- Checkbox,
37171
- RadioSelection,
37172
37439
  };
37173
37440
  static props = {
37174
37441
  figureId: String,
37175
37442
  definition: Object,
37176
- canUpdateChart: Function,
37177
37443
  updateChart: Function,
37444
+ canUpdateChart: Function,
37445
+ slots: { type: Object, optional: true },
37178
37446
  };
37179
- axisChoices = CHART_AXIS_CHOICES;
37180
37447
  state = useState({ index: 0 });
37181
- get axesList() {
37182
- const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37183
- let axes = [{ id: "x", name: _t("Horizontal axis") }];
37184
- if (useLeftAxis) {
37185
- axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37186
- }
37187
- if (useRightAxis) {
37188
- axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37189
- }
37190
- return axes;
37191
- }
37192
- updateLegendPosition(ev) {
37193
- this.props.updateChart(this.props.figureId, {
37194
- legendPosition: ev.target.value,
37195
- });
37196
- }
37197
37448
  getDataSeries() {
37198
- return this.props.definition.dataSets.map((d, i) => d.label ?? `${ChartTerms.Series} ${i + 1}`);
37449
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figureId);
37450
+ if (!runtime || !("chartJsConfig" in runtime)) {
37451
+ return [];
37452
+ }
37453
+ return runtime.chartJsConfig.data.datasets.map((d) => d.label);
37199
37454
  }
37200
37455
  updateSerieEditor(ev) {
37201
- const chartId = this.props.figureId;
37202
- const selectedIndex = ev.target.selectedIndex;
37203
- const runtime = this.env.model.getters.getChartRuntime(chartId);
37204
- if (!runtime) {
37205
- return;
37206
- }
37207
- this.state.index = selectedIndex;
37456
+ this.state.index = ev.target.selectedIndex;
37208
37457
  }
37209
37458
  updateDataSeriesColor(color) {
37210
- const dataSets = [...this.props.definition.dataSets];
37211
- if (!dataSets?.[this.state.index]) {
37459
+ const dataSets = this.props.definition.dataSets;
37460
+ if (!dataSets?.[this.state.index])
37212
37461
  return;
37213
- }
37214
37462
  dataSets[this.state.index] = {
37215
37463
  ...dataSets[this.state.index],
37216
37464
  backgroundColor: color,
@@ -37219,71 +37467,87 @@ class ChartWithAxisDesignPanel extends Component {
37219
37467
  }
37220
37468
  getDataSerieColor() {
37221
37469
  const dataSets = this.props.definition.dataSets;
37222
- if (!dataSets?.[this.state.index]) {
37470
+ if (!dataSets?.[this.state.index])
37223
37471
  return "";
37224
- }
37225
37472
  const color = dataSets[this.state.index].backgroundColor;
37226
- return color ? toHex(color) : getNthColor(this.state.index, getColorsPalette(dataSets.length));
37473
+ return color
37474
+ ? toHex(color)
37475
+ : getNthColor(this.state.index, getColorsPalette(this.props.definition.dataSets.length));
37227
37476
  }
37228
- updateDataSeriesAxis(axis) {
37229
- const dataSets = [...this.props.definition.dataSets];
37230
- if (!dataSets?.[this.state.index]) {
37477
+ updateDataSeriesLabel(ev) {
37478
+ const label = ev.target.value;
37479
+ const dataSets = this.props.definition.dataSets;
37480
+ if (!dataSets?.[this.state.index])
37231
37481
  return;
37232
- }
37233
37482
  dataSets[this.state.index] = {
37234
37483
  ...dataSets[this.state.index],
37235
- yAxisId: axis === "left" ? "y" : "y1",
37484
+ label,
37236
37485
  };
37237
37486
  this.props.updateChart(this.props.figureId, { dataSets });
37238
37487
  }
37239
- getDataSerieAxis() {
37488
+ getDataSerieLabel() {
37240
37489
  const dataSets = this.props.definition.dataSets;
37241
- if (!dataSets?.[this.state.index]) {
37242
- return "left";
37243
- }
37244
- return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
37245
- }
37246
- get canHaveTwoVerticalAxis() {
37247
- return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
37490
+ return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37248
37491
  }
37249
- updateDataSeriesLabel(ev) {
37250
- const label = ev.target.value;
37492
+ }
37493
+
37494
+ class SeriesWithAxisDesignEditor extends Component {
37495
+ static template = "o-spreadsheet-SeriesWithAxisDesignEditor";
37496
+ static components = {
37497
+ SeriesDesignEditor,
37498
+ Checkbox,
37499
+ RadioSelection,
37500
+ Section,
37501
+ RoundColorPicker,
37502
+ };
37503
+ static props = {
37504
+ figureId: String,
37505
+ definition: Object,
37506
+ canUpdateChart: Function,
37507
+ updateChart: Function,
37508
+ slots: { type: Object, optional: true },
37509
+ };
37510
+ axisChoices = CHART_AXIS_CHOICES;
37511
+ updateDataSeriesAxis(index, axis) {
37251
37512
  const dataSets = [...this.props.definition.dataSets];
37252
- if (!dataSets?.[this.state.index]) {
37513
+ if (!dataSets?.[index]) {
37253
37514
  return;
37254
37515
  }
37255
- dataSets[this.state.index] = {
37256
- ...dataSets[this.state.index],
37257
- label,
37516
+ dataSets[index] = {
37517
+ ...dataSets[index],
37518
+ yAxisId: axis === "left" ? "y" : "y1",
37258
37519
  };
37259
37520
  this.props.updateChart(this.props.figureId, { dataSets });
37260
37521
  }
37261
- getDataSerieLabel() {
37522
+ getDataSerieAxis(index) {
37262
37523
  const dataSets = this.props.definition.dataSets;
37263
- return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
37524
+ if (!dataSets?.[index]) {
37525
+ return "left";
37526
+ }
37527
+ return dataSets[index].yAxisId === "y1" ? "right" : "left";
37264
37528
  }
37265
- updateShowValues(showValues) {
37266
- this.props.updateChart(this.props.figureId, { showValues });
37529
+ get canHaveTwoVerticalAxis() {
37530
+ return !("horizontal" in this.props.definition && this.props.definition.horizontal);
37267
37531
  }
37268
- toggleDataTrend(display) {
37532
+ toggleDataTrend(index, display) {
37269
37533
  const dataSets = [...this.props.definition.dataSets];
37270
- if (!dataSets?.[this.state.index]) {
37534
+ if (!dataSets?.[index]) {
37271
37535
  return;
37272
37536
  }
37273
- dataSets[this.state.index] = {
37274
- ...dataSets[this.state.index],
37537
+ dataSets[index] = {
37538
+ ...dataSets[index],
37275
37539
  trend: {
37276
37540
  type: "polynomial",
37277
37541
  order: 1,
37278
- ...dataSets[this.state.index].trend,
37542
+ ...dataSets[index].trend,
37279
37543
  display,
37280
37544
  },
37281
37545
  };
37282
37546
  this.props.updateChart(this.props.figureId, { dataSets });
37283
37547
  }
37284
- getTrendLineConfiguration() {
37548
+ getTrendLineConfiguration(index) {
37285
37549
  const dataSets = this.props.definition.dataSets;
37286
- return dataSets?.[this.state.index]?.trend;
37550
+ return dataSets?.[index]?.trend;
37287
37551
  }
37288
37552
  getTrendType(config) {
37289
37553
  if (!config) {
@@ -37291,7 +37555,7 @@ class ChartWithAxisDesignPanel extends Component {
37291
37555
  }
37292
37556
  return config.type === "polynomial" && config.order === 1 ? "linear" : config.type;
37293
37557
  }
37294
- onChangeTrendType(ev) {
37558
+ onChangeTrendType(index, ev) {
37295
37559
  const type = ev.target.value;
37296
37560
  let config;
37297
37561
  switch (type) {
@@ -37304,37 +37568,59 @@ class ChartWithAxisDesignPanel extends Component {
37304
37568
  break;
37305
37569
  case "exponential":
37306
37570
  case "logarithmic":
37571
+ case "trailingMovingAverage":
37307
37572
  config = { type };
37308
37573
  break;
37309
37574
  default:
37310
37575
  return;
37311
37576
  }
37312
- this.updateTrendLineValue(config);
37577
+ this.updateTrendLineValue(index, config);
37313
37578
  }
37314
- onChangePolynomialDegree(ev) {
37579
+ onChangePolynomialDegree(index, ev) {
37315
37580
  const element = ev.target;
37316
37581
  const order = parseInt(element.value || "1");
37317
37582
  if (order < 2) {
37318
- element.value = `${this.getTrendLineConfiguration()?.order ?? 2}`;
37583
+ element.value = `${this.getTrendLineConfiguration(index)?.order ?? 2}`;
37319
37584
  return;
37320
37585
  }
37321
- this.updateTrendLineValue({ order });
37586
+ this.updateTrendLineValue(index, { order });
37322
37587
  }
37323
- getTrendLineColor() {
37324
- return this.getTrendLineConfiguration()?.color ?? setColorAlpha(this.getDataSerieColor(), 0.5);
37588
+ get defaultWindowSize() {
37589
+ return DEFAULT_WINDOW_SIZE;
37325
37590
  }
37326
- updateTrendLineColor(color) {
37327
- this.updateTrendLineValue({ color });
37591
+ onChangeMovingAverageWindow(index, ev) {
37592
+ const element = ev.target;
37593
+ let window = parseInt(element.value) || DEFAULT_WINDOW_SIZE;
37594
+ if (window <= 1) {
37595
+ window = DEFAULT_WINDOW_SIZE;
37596
+ }
37597
+ this.updateTrendLineValue(index, { window });
37328
37598
  }
37329
- updateTrendLineValue(config) {
37599
+ getDataSerieColor(index) {
37600
+ const dataSets = this.props.definition.dataSets;
37601
+ if (!dataSets?.[index])
37602
+ return "";
37603
+ const color = dataSets[index].backgroundColor;
37604
+ return color
37605
+ ? toHex(color)
37606
+ : getNthColor(index, getColorsPalette(this.props.definition.dataSets.length));
37607
+ }
37608
+ getTrendLineColor(index) {
37609
+ return (this.getTrendLineConfiguration(index)?.color ??
37610
+ setColorAlpha(this.getDataSerieColor(index), 0.5));
37611
+ }
37612
+ updateTrendLineColor(index, color) {
37613
+ this.updateTrendLineValue(index, { color });
37614
+ }
37615
+ updateTrendLineValue(index, config) {
37330
37616
  const dataSets = [...this.props.definition.dataSets];
37331
- if (!dataSets?.[this.state.index]) {
37617
+ if (!dataSets?.[index]) {
37332
37618
  return;
37333
37619
  }
37334
- dataSets[this.state.index] = {
37335
- ...dataSets[this.state.index],
37620
+ dataSets[index] = {
37621
+ ...dataSets[index],
37336
37622
  trend: {
37337
- ...dataSets[this.state.index].trend,
37623
+ ...dataSets[index].trend,
37338
37624
  ...config,
37339
37625
  },
37340
37626
  };
@@ -37342,29 +37628,67 @@ class ChartWithAxisDesignPanel extends Component {
37342
37628
  }
37343
37629
  }
37344
37630
 
37631
+ class ChartWithAxisDesignPanel extends Component {
37632
+ static template = "o-spreadsheet-ChartWithAxisDesignPanel";
37633
+ static components = {
37634
+ GeneralDesignEditor,
37635
+ SidePanelCollapsible,
37636
+ Section,
37637
+ AxisDesignEditor,
37638
+ Checkbox,
37639
+ SeriesWithAxisDesignEditor,
37640
+ };
37641
+ static props = {
37642
+ figureId: String,
37643
+ definition: Object,
37644
+ canUpdateChart: Function,
37645
+ updateChart: Function,
37646
+ };
37647
+ get axesList() {
37648
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(this.props.definition);
37649
+ let axes = [{ id: "x", name: _t("Horizontal axis") }];
37650
+ if (useLeftAxis) {
37651
+ axes.push({ id: "y", name: useRightAxis ? _t("Left axis") : _t("Vertical axis") });
37652
+ }
37653
+ if (useRightAxis) {
37654
+ axes.push({ id: "y1", name: useLeftAxis ? _t("Right axis") : _t("Vertical axis") });
37655
+ }
37656
+ return axes;
37657
+ }
37658
+ updateLegendPosition(ev) {
37659
+ this.props.updateChart(this.props.figureId, {
37660
+ legendPosition: ev.target.value,
37661
+ });
37662
+ }
37663
+ }
37664
+
37345
37665
  class ComboChartDesignPanel extends ChartWithAxisDesignPanel {
37346
37666
  static template = "o-spreadsheet-ComboChartDesignPanel";
37667
+ static components = {
37668
+ ...ChartWithAxisDesignPanel.components,
37669
+ RadioSelection,
37670
+ };
37347
37671
  seriesTypeChoices = [
37348
37672
  { value: "bar", label: _t("Bar") },
37349
37673
  { value: "line", label: _t("Line") },
37350
37674
  ];
37351
- updateDataSeriesType(type) {
37675
+ updateDataSeriesType(index, type) {
37352
37676
  const dataSets = [...this.props.definition.dataSets];
37353
- if (!dataSets?.[this.state.index]) {
37677
+ if (!dataSets?.[index]) {
37354
37678
  return;
37355
37679
  }
37356
- dataSets[this.state.index] = {
37357
- ...dataSets[this.state.index],
37680
+ dataSets[index] = {
37681
+ ...dataSets[index],
37358
37682
  type,
37359
37683
  };
37360
37684
  this.props.updateChart(this.props.figureId, { dataSets });
37361
37685
  }
37362
- getDataSeriesType() {
37686
+ getDataSeriesType(index) {
37363
37687
  const dataSets = this.props.definition.dataSets;
37364
- if (!dataSets?.[this.state.index]) {
37688
+ if (!dataSets?.[index]) {
37365
37689
  return "bar";
37366
37690
  }
37367
- return dataSets[this.state.index].type ?? "line";
37691
+ return dataSets[index].type ?? "line";
37368
37692
  }
37369
37693
  }
37370
37694
 
@@ -37547,11 +37871,6 @@ class LineConfigPanel extends GenericChartConfigPanel {
37547
37871
  stacked,
37548
37872
  });
37549
37873
  }
37550
- onUpdateAggregated(aggregated) {
37551
- this.props.updateChart(this.props.figureId, {
37552
- aggregated,
37553
- });
37554
- }
37555
37874
  onUpdateCumulative(cumulative) {
37556
37875
  this.props.updateChart(this.props.figureId, {
37557
37876
  cumulative,
@@ -37579,6 +37898,27 @@ class PieChartDesignPanel extends Component {
37579
37898
  }
37580
37899
  }
37581
37900
 
37901
+ class RadarChartDesignPanel extends Component {
37902
+ static template = "o-spreadsheet-RadarChartDesignPanel";
37903
+ static components = {
37904
+ GeneralDesignEditor,
37905
+ SeriesDesignEditor,
37906
+ Section,
37907
+ Checkbox,
37908
+ };
37909
+ static props = {
37910
+ figureId: String,
37911
+ definition: Object,
37912
+ canUpdateChart: Function,
37913
+ updateChart: Function,
37914
+ };
37915
+ updateLegendPosition(ev) {
37916
+ this.props.updateChart(this.props.figureId, {
37917
+ legendPosition: ev.target.value,
37918
+ });
37919
+ }
37920
+ }
37921
+
37582
37922
  class ScatterConfigPanel extends GenericChartConfigPanel {
37583
37923
  static template = "o-spreadsheet-ScatterConfigPanel";
37584
37924
  get canTreatLabelsAsText() {
@@ -37773,9 +38113,6 @@ class WaterfallChartDesignPanel extends Component {
37773
38113
  verticalAxisPosition: value,
37774
38114
  });
37775
38115
  }
37776
- updateShowValues(showValues) {
37777
- this.props.updateChart(this.props.figureId, { showValues });
37778
- }
37779
38116
  }
37780
38117
 
37781
38118
  const chartSidePanelComponentRegistry = new Registry();
@@ -37815,6 +38152,10 @@ chartSidePanelComponentRegistry
37815
38152
  .add("pyramid", {
37816
38153
  configuration: GenericChartConfigPanel,
37817
38154
  design: ChartWithAxisDesignPanel,
38155
+ })
38156
+ .add("radar", {
38157
+ configuration: GenericChartConfigPanel,
38158
+ design: RadarChartDesignPanel,
37818
38159
  });
37819
38160
 
37820
38161
  css /* scss */ `
@@ -39017,13 +39358,6 @@ class DOMDndHelper {
39017
39358
  return;
39018
39359
  this.edgeScrollIntervalId = window.setInterval(() => {
39019
39360
  const offset = direction * 3;
39020
- let newPosition = this.currentMousePosition + offset;
39021
- if (newPosition < Math.min(this.container.start, this.minPosition)) {
39022
- newPosition = Math.min(this.container.start, this.minPosition);
39023
- }
39024
- else if (newPosition > Math.max(this.container.end, this.maxPosition)) {
39025
- newPosition = Math.max(this.container.end, this.maxPosition);
39026
- }
39027
39361
  this.container.scroll += offset;
39028
39362
  }, 5);
39029
39363
  }
@@ -39200,7 +39534,6 @@ css /* scss */ `
39200
39534
  width: 142px;
39201
39535
  .o-cf-preview-description-rule {
39202
39536
  margin-bottom: 4px;
39203
- font-weight: 600;
39204
39537
  max-height: 2.8em;
39205
39538
  line-height: 1.4em;
39206
39539
  }
@@ -39672,7 +40005,7 @@ class ConditionalFormattingEditor extends Component {
39672
40005
  setColorScaleColor(target, color) {
39673
40006
  const point = this.state.rules.colorScale[target];
39674
40007
  if (point) {
39675
- point.color = Number.parseInt(color.substr(1), 16);
40008
+ point.color = Number.parseInt(color.slice(1), 16);
39676
40009
  }
39677
40010
  this.closeMenus();
39678
40011
  }
@@ -39783,7 +40116,7 @@ class ConditionalFormattingEditor extends Component {
39783
40116
  return [this.state.rules.dataBar.rangeValues || ""];
39784
40117
  }
39785
40118
  updateDataBarColor(color) {
39786
- this.state.rules.dataBar.color = Number.parseInt(color.substr(1), 16);
40119
+ this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
39787
40120
  }
39788
40121
  onDataBarRangeUpdate(ranges) {
39789
40122
  this.state.rules.dataBar.rangeValues = ranges[0];
@@ -41196,10 +41529,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41196
41529
  activeSheetMatches = [];
41197
41530
  specificRangeMatches = [];
41198
41531
  currentSearchRegex = null;
41199
- isSearchDirty = false;
41200
41532
  initialShowFormulaState;
41201
41533
  preserveSelectedMatchIndex = false;
41202
41534
  irreplaceableMatchCount = 0;
41535
+ isSearchDirty = false;
41536
+ shouldFinalizeUpdateSelection = false;
41203
41537
  notificationStore = this.get(NotificationStore);
41204
41538
  // fixme: why do we make selectedMatchIndex on top of a selected
41205
41539
  // property in the matches?
@@ -41245,10 +41579,13 @@ class FindAndReplaceStore extends SpreadsheetStore {
41245
41579
  this.updateSearchOptions({ searchFormulas: showFormula });
41246
41580
  }
41247
41581
  selectPreviousMatch() {
41248
- this.selectNextCell(Direction.previous);
41582
+ this.selectNextCell(Direction.previous, {
41583
+ jumpToMatchSheet: true,
41584
+ updateSelection: true,
41585
+ });
41249
41586
  }
41250
41587
  selectNextMatch() {
41251
- this.selectNextCell(Direction.next);
41588
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41252
41589
  }
41253
41590
  handle(cmd) {
41254
41591
  switch (cmd.type) {
@@ -41265,8 +41602,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41265
41602
  case "ADD_COLUMNS_ROWS":
41266
41603
  case "EVALUATE_CELLS":
41267
41604
  case "UPDATE_CELL":
41605
+ this.isSearchDirty = true;
41606
+ break;
41268
41607
  case "ACTIVATE_SHEET":
41269
41608
  this.isSearchDirty = true;
41609
+ this.shouldFinalizeUpdateSelection = true;
41270
41610
  break;
41271
41611
  case "REPLACE_SEARCH":
41272
41612
  for (const match of cmd.matches) {
@@ -41281,7 +41621,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
41281
41621
  }
41282
41622
  finalize() {
41283
41623
  if (this.isSearchDirty) {
41284
- this.refreshSearch(false);
41624
+ this.refreshSearch({
41625
+ jumpToMatchSheet: false,
41626
+ updateSelection: this.shouldFinalizeUpdateSelection,
41627
+ });
41628
+ this.shouldFinalizeUpdateSelection = false;
41285
41629
  this.isSearchDirty = false;
41286
41630
  }
41287
41631
  }
@@ -41308,17 +41652,17 @@ class FindAndReplaceStore extends SpreadsheetStore {
41308
41652
  }
41309
41653
  this.toSearch = toSearch;
41310
41654
  this.currentSearchRegex = getSearchRegex(this.toSearch, this.searchOptions);
41311
- this.refreshSearch();
41655
+ this.refreshSearch({ jumpToMatchSheet: true, updateSelection: true });
41312
41656
  }
41313
41657
  /**
41314
41658
  * refresh the matches according to the current search options
41315
41659
  */
41316
- refreshSearch(jumpToMatchSheet = true) {
41660
+ refreshSearch(options) {
41317
41661
  if (!this.preserveSelectedMatchIndex) {
41318
41662
  this.selectedMatchIndex = null;
41319
41663
  }
41320
41664
  this.findMatches();
41321
- this.selectNextCell(Direction.current, jumpToMatchSheet);
41665
+ this.selectNextCell(Direction.current, options);
41322
41666
  }
41323
41667
  getSheetsInSearchOrder() {
41324
41668
  switch (this.searchOptions.searchScope) {
@@ -41388,7 +41732,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41388
41732
  * It is also used to keep coherence between the selected searchMatch
41389
41733
  * and selectedMatchIndex.
41390
41734
  */
41391
- selectNextCell(indexChange, jumpToMatchSheet = true) {
41735
+ selectNextCell(indexChange, options) {
41392
41736
  const matches = this.searchMatches;
41393
41737
  if (!matches.length) {
41394
41738
  this.selectedMatchIndex = null;
@@ -41414,7 +41758,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41414
41758
  this.selectedMatchIndex = nextIndex;
41415
41759
  const selectedMatch = matches[nextIndex];
41416
41760
  // Switch to the sheet where the match is located
41417
- if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41761
+ if (options.jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
41418
41762
  // We set `preserveSelectedMatchIndex` to true to avoid resetting the selected search
41419
41763
  // index in the `refreshSearch` function when a new sheet is activated. The reason being
41420
41764
  // that, when we automatically go back to previous sheet while performing a search, the
@@ -41430,7 +41774,9 @@ class FindAndReplaceStore extends SpreadsheetStore {
41430
41774
  }
41431
41775
  // we want grid selection to capture the selection stream
41432
41776
  this.model.selection.getBackToDefault();
41433
- this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41777
+ if (options.updateSelection) {
41778
+ this.model.selection.selectCell(selectedMatch.col, selectedMatch.row);
41779
+ }
41434
41780
  }
41435
41781
  /**
41436
41782
  * Replace the value of the currently selected match
@@ -41445,7 +41791,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
41445
41791
  matches: [this.searchMatches[this.selectedMatchIndex]],
41446
41792
  searchOptions: this.searchOptions,
41447
41793
  });
41448
- this.selectNextCell(Direction.next);
41794
+ this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
41449
41795
  }
41450
41796
  /**
41451
41797
  * Apply the replace function to all the matches one time.
@@ -41627,6 +41973,14 @@ class FindAndReplacePanel extends Component {
41627
41973
  onMounted(() => this.searchInput.el?.focus());
41628
41974
  onWillUnmount(() => this.updateSearchContent.stopDebounce());
41629
41975
  this.updateSearchContent = debounce(this.store.updateSearchContent, 200);
41976
+ useExternalListener(window, "keydown", (ev) => {
41977
+ const code = keyboardEventToShortcutString(ev);
41978
+ if (code === "Ctrl+F" || code === "Ctrl+H") {
41979
+ this.searchInput.el?.focus();
41980
+ ev.preventDefault();
41981
+ ev.stopPropagation();
41982
+ }
41983
+ }, { capture: true });
41630
41984
  }
41631
41985
  onFocusSearch() {
41632
41986
  this.updateDataRange();
@@ -42202,7 +42556,6 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
42202
42556
  sequence: 0,
42203
42557
  autoSelectFirstProposal: true,
42204
42558
  getProposals(tokenAtCursor) {
42205
- // return []
42206
42559
  const measureProposals = pivot.measures
42207
42560
  .filter((m) => m !== forComputedMeasure)
42208
42561
  .map((measure) => {
@@ -43783,13 +44136,15 @@ pivotRegistry.add("SPREADSHEET", {
43783
44136
  class PivotSidePanelStore extends SpreadsheetStore {
43784
44137
  pivotId;
43785
44138
  mutators = ["reset", "deferUpdates", "applyUpdate", "discardPendingUpdate", "update"];
43786
- updatesAreDeferred = false;
44139
+ updatesAreDeferred;
43787
44140
  draft = null;
43788
44141
  notification = this.get(NotificationStore);
43789
44142
  alreadyNotified = false;
43790
44143
  constructor(get, pivotId) {
43791
44144
  super(get);
43792
44145
  this.pivotId = pivotId;
44146
+ this.updatesAreDeferred =
44147
+ this.getters.getPivotCoreDefinition(this.pivotId).deferUpdates ?? false;
43793
44148
  }
43794
44149
  handle(cmd) {
43795
44150
  switch (cmd.type) {
@@ -43877,10 +44232,14 @@ class PivotSidePanelStore extends SpreadsheetStore {
43877
44232
  this.draft = null;
43878
44233
  }
43879
44234
  deferUpdates(shouldDefer) {
43880
- this.updatesAreDeferred = shouldDefer;
43881
44235
  if (shouldDefer === false && this.draft) {
44236
+ this.draft.deferUpdates = false;
43882
44237
  this.applyUpdate();
43883
44238
  }
44239
+ else {
44240
+ this.update({ deferUpdates: shouldDefer });
44241
+ }
44242
+ this.updatesAreDeferred = shouldDefer;
43884
44243
  }
43885
44244
  applyUpdate() {
43886
44245
  if (this.draft) {
@@ -44138,7 +44497,7 @@ class RemoveDuplicatesPanel extends Component {
44138
44497
  return colLabel;
44139
44498
  }
44140
44499
  get isEveryColumnSelected() {
44141
- return Object.values(this.state.columns).every((value) => value === true);
44500
+ return Object.values(this.state.columns).every((value) => value);
44142
44501
  }
44143
44502
  get errorMessages() {
44144
44503
  const cancelledReasons = this.env.model.canDispatch("REMOVE_DUPLICATES", {
@@ -44238,8 +44597,7 @@ class SettingsPanel extends Component {
44238
44597
  const currentLocale = this.currentLocale;
44239
44598
  const localeInLoadedLocales = this.loadedLocales.find((l) => l.code === currentLocale.code);
44240
44599
  if (!localeInLoadedLocales) {
44241
- const locales = [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44242
- return locales;
44600
+ return [...this.loadedLocales, currentLocale].sort((a, b) => a.name.localeCompare(b.name));
44243
44601
  }
44244
44602
  else if (!deepEquals(currentLocale, localeInLoadedLocales)) {
44245
44603
  const index = this.loadedLocales.indexOf(localeInLoadedLocales);
@@ -46133,10 +46491,9 @@ class GridComposer extends Component {
46133
46491
  });
46134
46492
  }
46135
46493
  get focus() {
46136
- const focus = this.composerFocusStore.activeComposer === this.composerInterface
46494
+ return this.composerFocusStore.activeComposer === this.composerInterface
46137
46495
  ? this.composerFocusStore.focusMode
46138
46496
  : "inactive";
46139
- return focus;
46140
46497
  }
46141
46498
  get composerProps() {
46142
46499
  const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
@@ -47143,6 +47500,7 @@ class PaintFormatStore extends SpreadsheetStore {
47143
47500
  new CellClipboardHandler(this.getters, this.model.dispatch),
47144
47501
  new BorderClipboardHandler(this.getters, this.model.dispatch),
47145
47502
  new TableClipboardHandler(this.getters, this.model.dispatch),
47503
+ new ConditionalFormatClipboardHandler(this.getters, this.model.dispatch),
47146
47504
  ];
47147
47505
  status = "inactive";
47148
47506
  copiedData;
@@ -47153,6 +47511,13 @@ class PaintFormatStore extends SpreadsheetStore {
47153
47511
  this.highlightStore.unRegister(this);
47154
47512
  });
47155
47513
  }
47514
+ handle(cmd) {
47515
+ switch (cmd.type) {
47516
+ case "PAINT_FORMAT":
47517
+ this.paintFormat(cmd.sheetId, cmd.target);
47518
+ break;
47519
+ }
47520
+ }
47156
47521
  activate(args) {
47157
47522
  this.copiedData = this.copyFormats();
47158
47523
  this.status = args.persistent ? "persistent" : "oneOff";
@@ -47162,18 +47527,7 @@ class PaintFormatStore extends SpreadsheetStore {
47162
47527
  this.copiedData = undefined;
47163
47528
  }
47164
47529
  pasteFormat(target) {
47165
- if (this.copiedData) {
47166
- const sheetId = this.getters.getActiveSheetId();
47167
- for (const handler of this.clipboardHandlers) {
47168
- handler.paste({ zones: target, sheetId }, this.copiedData, {
47169
- isCutOperation: false,
47170
- pasteOption: "onlyFormat",
47171
- });
47172
- }
47173
- }
47174
- if (this.status === "oneOff") {
47175
- this.cancel();
47176
- }
47530
+ this.model.dispatch("PAINT_FORMAT", { target, sheetId: this.getters.getActiveSheetId() });
47177
47531
  }
47178
47532
  get isActive() {
47179
47533
  return this.status !== "inactive";
@@ -47187,6 +47541,19 @@ class PaintFormatStore extends SpreadsheetStore {
47187
47541
  }
47188
47542
  return copiedData;
47189
47543
  }
47544
+ paintFormat(sheetId, target) {
47545
+ if (this.copiedData) {
47546
+ for (const handler of this.clipboardHandlers) {
47547
+ handler.paste({ zones: target, sheetId }, this.copiedData, {
47548
+ isCutOperation: false,
47549
+ pasteOption: "onlyFormat",
47550
+ });
47551
+ }
47552
+ }
47553
+ if (this.status === "oneOff") {
47554
+ this.cancel();
47555
+ }
47556
+ }
47190
47557
  get highlights() {
47191
47558
  const data = this.copiedData;
47192
47559
  if (!data) {
@@ -47563,7 +47930,7 @@ class AbstractResizer extends Component {
47563
47930
  if (index < 0) {
47564
47931
  return;
47565
47932
  }
47566
- if (this.state.waitingForMove === true) {
47933
+ if (this.state.waitingForMove) {
47567
47934
  if (!this.env.model.getters.isGridSelectionActive()) {
47568
47935
  this._selectElement(index, false);
47569
47936
  }
@@ -49122,7 +49489,7 @@ class SidePanelStore extends SpreadsheetStore {
49122
49489
  }
49123
49490
  open(componentTag, panelProps = {}) {
49124
49491
  const state = this.computeState(componentTag, panelProps);
49125
- if (state.isOpen === false) {
49492
+ if (!state.isOpen) {
49126
49493
  return;
49127
49494
  }
49128
49495
  if (this.isOpen && componentTag !== this.componentTag) {
@@ -49461,6 +49828,8 @@ class Grid extends Component {
49461
49828
  },
49462
49829
  "Ctrl+D": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ABOVE"),
49463
49830
  "Ctrl+R": async () => this.env.model.dispatch("COPY_PASTE_CELLS_ON_LEFT"),
49831
+ "Ctrl+H": () => this.sidePanel.open("FindAndReplace", {}),
49832
+ "Ctrl+F": () => this.sidePanel.open("FindAndReplace", {}),
49464
49833
  "Ctrl+Shift+E": () => this.setHorizontalAlign("center"),
49465
49834
  "Ctrl+Shift+L": () => this.setHorizontalAlign("left"),
49466
49835
  "Ctrl+Shift+R": () => this.setHorizontalAlign("right"),
@@ -49868,31 +50237,6 @@ class Grid extends Component {
49868
50237
  }
49869
50238
  }
49870
50239
 
49871
- /** @odoo-module */
49872
- class EditableName extends Component {
49873
- static template = "o-spreadsheet-EditableName";
49874
- static props = {
49875
- name: String,
49876
- displayName: String,
49877
- onChanged: Function,
49878
- };
49879
- state;
49880
- setup() {
49881
- this.state = useState({
49882
- isEditing: false,
49883
- name: "",
49884
- });
49885
- }
49886
- rename() {
49887
- this.state.isEditing = true;
49888
- this.state.name = this.props.name;
49889
- }
49890
- save() {
49891
- this.props.onChanged(this.state.name.trim());
49892
- this.state.isEditing = false;
49893
- }
49894
- }
49895
-
49896
50240
  /**
49897
50241
  * BasePlugin
49898
50242
  *
@@ -53810,7 +54154,7 @@ class SheetPlugin extends CorePlugin {
53810
54154
  this.sheetIdsMapName[sheet.name] = sheet.id;
53811
54155
  }
53812
54156
  for (let sheetData of data.sheets) {
53813
- const name = sheetData.name || _t("Sheet") + (Object.keys(this.sheets).length + 1);
54157
+ const name = sheetData.name || "Sheet" + (Object.keys(this.sheets).length + 1);
53814
54158
  const { colNumber, rowNumber } = this.getImportedSheetSize(sheetData);
53815
54159
  const sheet = {
53816
54160
  id: sheetData.id,
@@ -55429,7 +55773,7 @@ class PivotCorePlugin extends CorePlugin {
55429
55773
  case "DUPLICATE_PIVOT": {
55430
55774
  const { pivotId, newPivotId } = cmd;
55431
55775
  const pivot = deepCopy(this.getPivotCore(pivotId).definition);
55432
- pivot.name = _t("%s (copy)", pivot.name);
55776
+ pivot.name = cmd.duplicatedPivotName ?? pivot.name + " (copy)";
55433
55777
  this.addPivot(newPivotId, pivot);
55434
55778
  break;
55435
55779
  }
@@ -55469,7 +55813,7 @@ class PivotCorePlugin extends CorePlugin {
55469
55813
  return `(#${formulaId}) ${this.getPivotName(pivotId)}`;
55470
55814
  }
55471
55815
  getPivotName(pivotId) {
55472
- return _t(this.getPivotCore(pivotId).definition.name);
55816
+ return this.getPivotCore(pivotId).definition.name;
55473
55817
  }
55474
55818
  /**
55475
55819
  * Returns the pivot core definition of the pivot with the given id.
@@ -57111,7 +57455,7 @@ class Evaluator {
57111
57455
  cellsToCompute.addMany(arrayFormulasPositions);
57112
57456
  cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositions));
57113
57457
  this.evaluate(cellsToCompute);
57114
- console.info("evaluate Cells", performance.now() - start, "ms");
57458
+ console.debug("evaluate Cells", performance.now() - start, "ms");
57115
57459
  }
57116
57460
  getArrayFormulasImpactedByChangesOf(positions) {
57117
57461
  const impactedPositions = this.createEmptyPositionSet();
@@ -57155,7 +57499,7 @@ class Evaluator {
57155
57499
  const start = performance.now();
57156
57500
  this.evaluatedCells = new PositionMap();
57157
57501
  this.evaluate(this.getAllCells());
57158
- console.info("evaluate all cells", performance.now() - start, "ms");
57502
+ console.debug("evaluate all cells", performance.now() - start, "ms");
57159
57503
  }
57160
57504
  evaluateFormulaResult(sheetId, formulaString) {
57161
57505
  const compiledFormula = compile(formulaString);
@@ -58161,8 +58505,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
58161
58505
  .map((cell) => cell.value);
58162
58506
  switch (threshold.type) {
58163
58507
  case "value":
58164
- const result = functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
58165
- return result;
58508
+ return functionName === "max" ? largeMax(rangeValues) : largeMin(rangeValues);
58166
58509
  case "number":
58167
58510
  return Number(threshold.value);
58168
58511
  case "percentage":
@@ -59366,8 +59709,7 @@ function withPivotPresentationLayer (PivotClass) {
59366
59709
  throw new NotAvailableError();
59367
59710
  }
59368
59711
  const comparedValue = this._getPivotCellValueAndFormat(measure.id, comparedDomain);
59369
- const comparedValueNumber = this.strictMeasureValueToNumber(comparedValue);
59370
- return comparedValueNumber;
59712
+ return this.strictMeasureValueToNumber(comparedValue);
59371
59713
  }
59372
59714
  getPivotValueCells(measureId) {
59373
59715
  return this.getTableStructure()
@@ -60901,7 +61243,7 @@ class Session extends EventBus {
60901
61243
  this.onMessageReceived(message);
60902
61244
  }
60903
61245
  this.isReplayingInitialRevisions = false;
60904
- console.info("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
61246
+ console.debug("Replayed", numberOfCommands, "commands in", performance.now() - start, "ms");
60905
61247
  }
60906
61248
  /**
60907
61249
  * Notify the server that the user client left the collaborative session
@@ -61073,7 +61415,6 @@ class Session extends EventBus {
61073
61415
  if (this.waitingAck) {
61074
61416
  return;
61075
61417
  }
61076
- this.waitingAck = true;
61077
61418
  this.sendPendingMessage();
61078
61419
  }
61079
61420
  /**
@@ -61110,6 +61451,7 @@ class Session extends EventBus {
61110
61451
  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.
61111
61452
  ${JSON.stringify(message)}`);
61112
61453
  }
61454
+ this.waitingAck = true;
61113
61455
  this.transportService.sendMessage({
61114
61456
  ...message,
61115
61457
  serverRevisionId: this.serverRevisionId,
@@ -61269,8 +61611,7 @@ class CollaborativePlugin extends UIPlugin {
61269
61611
  }
61270
61612
  const color = client.color;
61271
61613
  /* Cell background */
61272
- const cellBackgroundColor = `${color}10`;
61273
- ctx.fillStyle = cellBackgroundColor;
61614
+ ctx.fillStyle = `${color}10`;
61274
61615
  ctx.lineWidth = 4 * thinLineWidth;
61275
61616
  ctx.strokeStyle = color;
61276
61617
  ctx.globalCompositeOperation = "multiply";
@@ -61637,8 +61978,7 @@ class HeaderVisibilityUIPlugin extends UIPlugin {
61637
61978
  exportForExcel(data) {
61638
61979
  for (const sheetData of data.sheets) {
61639
61980
  for (const [row, rowData] of Object.entries(sheetData.rows)) {
61640
- const isHidden = this.isRowHidden(sheetData.id, Number(row));
61641
- rowData.isHidden = isHidden;
61981
+ rowData.isHidden = this.isRowHidden(sheetData.id, Number(row));
61642
61982
  }
61643
61983
  }
61644
61984
  }
@@ -61698,6 +62038,7 @@ class InsertPivotPlugin extends UIPlugin {
61698
62038
  this.dispatch("DUPLICATE_PIVOT", {
61699
62039
  pivotId,
61700
62040
  newPivotId,
62041
+ duplicatedPivotName: _t("%s (copy)", this.getters.getPivotCoreDefinition(pivotId).name),
61701
62042
  });
61702
62043
  const activeSheetId = this.getters.getActiveSheetId();
61703
62044
  const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
@@ -62162,7 +62503,6 @@ class SheetUIPlugin extends UIPlugin {
62162
62503
  if (!isEqual(zone, newZone)) {
62163
62504
  hasExpanded = true;
62164
62505
  zone = newZone;
62165
- continue;
62166
62506
  }
62167
62507
  } while (hasExpanded);
62168
62508
  return zone;
@@ -63255,7 +63595,7 @@ class ClipboardPlugin extends UIPlugin {
63255
63595
  case "ADD_COLUMNS_ROWS": {
63256
63596
  this.status = "invisible";
63257
63597
  // If we add a col/row inside or before the cut area, we invalidate the clipboard
63258
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63598
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63259
63599
  return;
63260
63600
  }
63261
63601
  const isClipboardDirty = this.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -63267,7 +63607,7 @@ class ClipboardPlugin extends UIPlugin {
63267
63607
  case "REMOVE_COLUMNS_ROWS": {
63268
63608
  this.status = "invisible";
63269
63609
  // If we remove a col/row inside or before the cut area, we invalidate the clipboard
63270
- if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
63610
+ if (!this._isCutOperation || cmd.sheetId !== this.copiedData?.sheetId) {
63271
63611
  return;
63272
63612
  }
63273
63613
  for (let el of cmd.elements) {
@@ -63289,7 +63629,7 @@ class ClipboardPlugin extends UIPlugin {
63289
63629
  break;
63290
63630
  }
63291
63631
  case "DELETE_SHEET":
63292
- if (this._isCutOperation !== true) {
63632
+ if (!this._isCutOperation) {
63293
63633
  return;
63294
63634
  }
63295
63635
  if (this.originSheetId === cmd.sheetId) {
@@ -64182,8 +64522,7 @@ class GridSelectionPlugin extends UIPlugin {
64182
64522
  this.setSelectionMixin({ zone, cell: { col, row } }, [zone]);
64183
64523
  }
64184
64524
  setActiveSheet(id) {
64185
- const sheet = this.getters.getSheet(id);
64186
- this.activeSheet = sheet;
64525
+ this.activeSheet = this.getters.getSheet(id);
64187
64526
  }
64188
64527
  activateNextSheet(direction) {
64189
64528
  const sheetIds = this.getters.getSheetIds();
@@ -64877,9 +65216,6 @@ class SheetViewPlugin extends UIPlugin {
64877
65216
  case "UNFREEZE_COLUMNS_ROWS":
64878
65217
  this.resetViewports(this.getters.getActiveSheetId());
64879
65218
  break;
64880
- case "DELETE_SHEET":
64881
- this.sheetsWithDirtyViewports.delete(cmd.sheetId);
64882
- break;
64883
65219
  case "SCROLL_TO_CELL":
64884
65220
  this.refreshViewport(this.getters.getActiveSheetId(), { col: cmd.col, row: cmd.row });
64885
65221
  break;
@@ -66831,10 +67167,9 @@ css /* scss */ `
66831
67167
  user-select: none;
66832
67168
  color: ${TEXT_BODY};
66833
67169
 
66834
- .o-heading-3 {
67170
+ .o-sidePanelTitle {
66835
67171
  line-height: 20px;
66836
67172
  font-size: 16px;
66837
- font-weight: 600;
66838
67173
  }
66839
67174
 
66840
67175
  .o-sidePanelHeader {
@@ -66919,6 +67254,10 @@ css /* scss */ `
66919
67254
  }
66920
67255
  }
66921
67256
  }
67257
+
67258
+ .o-fw-bold {
67259
+ font-weight: 500;
67260
+ }
66922
67261
  `;
66923
67262
  class SidePanel extends Component {
66924
67263
  static template = "o-spreadsheet-SidePanel";
@@ -67750,8 +68089,7 @@ class WebClipboardWrapper {
67750
68089
  for (const item of clipboardItems) {
67751
68090
  for (const type of item.types) {
67752
68091
  const blob = await item.getType(type);
67753
- const text = await blob.text();
67754
- clipboardContent[type] = text;
68092
+ clipboardContent[type] = await blob.text();
67755
68093
  }
67756
68094
  }
67757
68095
  return { status: "ok", content: clipboardContent };
@@ -68063,7 +68401,6 @@ class Spreadsheet extends Component {
68063
68401
  spreadsheetRef = useRef("spreadsheet");
68064
68402
  spreadsheetRect = useSpreadsheetRect();
68065
68403
  _focusGrid;
68066
- keyDownMapping;
68067
68404
  isViewportTooSmall = false;
68068
68405
  notificationStore;
68069
68406
  composerFocusStore;
@@ -68087,10 +68424,6 @@ class Spreadsheet extends Component {
68087
68424
  this.notificationStore = useStore(NotificationStore);
68088
68425
  this.composerFocusStore = useStore(ComposerFocusStore);
68089
68426
  this.sidePanel = useStore(SidePanelStore);
68090
- this.keyDownMapping = {
68091
- "CTRL+H": () => this.sidePanel.toggle("FindAndReplace", {}),
68092
- "CTRL+F": () => this.sidePanel.toggle("FindAndReplace", {}),
68093
- };
68094
68427
  const fileStore = this.model.config.external.fileStore;
68095
68428
  useSubEnv({
68096
68429
  model: this.model,
@@ -68190,20 +68523,6 @@ class Spreadsheet extends Component {
68190
68523
  }
68191
68524
  this._focusGrid();
68192
68525
  }
68193
- onKeydown(ev) {
68194
- let keyDownString = "";
68195
- if (isCtrlKey(ev)) {
68196
- keyDownString += "CTRL+";
68197
- }
68198
- keyDownString += ev.key.toUpperCase();
68199
- let handler = this.keyDownMapping[keyDownString];
68200
- if (handler) {
68201
- ev.preventDefault();
68202
- ev.stopPropagation();
68203
- handler();
68204
- return;
68205
- }
68206
- }
68207
68526
  get gridHeight() {
68208
68527
  const { height } = this.env.model.getters.getSheetViewDimension();
68209
68528
  return height;
@@ -69618,10 +69937,10 @@ class SelectionStreamProcessorImpl {
69618
69937
  getNextCellPosition(currentPosition, dimension, direction) {
69619
69938
  const dimOfInterest = dimension === "cols" ? "col" : "row";
69620
69939
  const startingPosition = { ...currentPosition };
69621
- const nextCoord = dimension === "cols"
69622
- ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69623
- : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69624
- startingPosition[dimOfInterest] = nextCoord;
69940
+ startingPosition[dimOfInterest] =
69941
+ dimension === "cols"
69942
+ ? this.getNextAvailableCol(direction, startingPosition.col, startingPosition.row)
69943
+ : this.getNextAvailableRow(direction, startingPosition.col, startingPosition.row);
69625
69944
  return { col: startingPosition.col, row: startingPosition.row };
69626
69945
  }
69627
69946
  getPosition() {
@@ -69745,6 +70064,8 @@ function createChart(chart, chartSheetIndex, data) {
69745
70064
  case "pie":
69746
70065
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
69747
70066
  break;
70067
+ case "radar":
70068
+ plot = addRadarChart(chart.data);
69748
70069
  }
69749
70070
  let position = "t";
69750
70071
  switch (chart.data.legendPosition) {
@@ -70203,6 +70524,53 @@ function addScatterChart(chart) {
70203
70524
  `
70204
70525
  : ""}`;
70205
70526
  }
70527
+ function addRadarChart(chart) {
70528
+ const dataSetsColors = chart.dataSets.map((ds) => ds.backgroundColor ?? "");
70529
+ const colors = new ColorGenerator(chart.dataSets.length, dataSetsColors);
70530
+ const dataSetsNodes = [];
70531
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
70532
+ const color = toXlsxHexColor(colors.next());
70533
+ const dataShapeProperty = shapeProperty({
70534
+ line: {
70535
+ width: 2.5,
70536
+ style: "solid",
70537
+ color,
70538
+ },
70539
+ });
70540
+ const dataSetNode = escapeXml /*xml*/ `
70541
+ <c:ser>
70542
+ <c:idx val="${dsIndex}"/>
70543
+ <c:order val="${dsIndex}"/>
70544
+ <c:smooth val="0"/>
70545
+ <c:marker>
70546
+ <c:symbol val="circle" />
70547
+ <c:size val="5"/>
70548
+ ${shapeProperty({ backgroundColor: color, line: { color } })}
70549
+ </c:marker>
70550
+ ${extractDataSetLabel(dataset.label)}
70551
+ ${dataShapeProperty}
70552
+ ${chart.labelRange ? escapeXml `<c:cat>${stringRef(chart.labelRange)}</c:cat>` : ""} <!-- x-coordinate values -->
70553
+ <c:val> <!-- x-coordinate values -->
70554
+ ${numberRef(dataset.range)}
70555
+ </c:val>
70556
+ </c:ser>
70557
+ `;
70558
+ dataSetsNodes.push(dataSetNode);
70559
+ }
70560
+ return escapeXml /*xml*/ `
70561
+ ${escapeXml /*xml*/ `
70562
+ <c:radarChart>
70563
+ <c:radarStyle val="marker"/>
70564
+ <c:varyColors val="0"/>
70565
+ ${joinXmlNodes(dataSetsNodes)}
70566
+ <c:axId val="${catAxId}" />
70567
+ <c:axId val="${valAxId}" />
70568
+ </c:radarChart>
70569
+ ${addAx("b", "c:catAx", catAxId, valAxId, chart.axesDesign?.x?.title, chart.fontColor)}
70570
+ ${addAx("l", "c:valAx", valAxId, catAxId, chart.axesDesign?.y?.title, chart.fontColor)}
70571
+ `}
70572
+ `;
70573
+ }
70206
70574
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
70207
70575
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
70208
70576
  const colors = new ColorGenerator(maxLength);
@@ -71310,25 +71678,23 @@ function addSheetViews(sheet) {
71310
71678
  ["showGridLines", sheet.areGridLinesVisible ? 1 : 0],
71311
71679
  ["workbookViewId", 0],
71312
71680
  ];
71313
- let sheetView = escapeXml /*xml*/ `
71681
+ return escapeXml /*xml*/ `
71314
71682
  <sheetViews>
71315
71683
  <sheetView ${formatAttributes(sheetViewAttrs)}>
71316
71684
  ${splitPanes}
71317
71685
  </sheetView>
71318
71686
  </sheetViews>
71319
71687
  `;
71320
- return sheetView;
71321
71688
  }
71322
71689
  function addSheetProperties(sheet) {
71323
71690
  if (!sheet.color) {
71324
71691
  return "";
71325
71692
  }
71326
- let sheetView = escapeXml /*xml*/ `
71693
+ return escapeXml /*xml*/ `
71327
71694
  <sheetPr>
71328
71695
  <tabColor ${formatAttributes([["rgb", toXlsxHexColor(sheet.color)]])} />
71329
71696
  </sheetPr>
71330
71697
  `;
71331
- return sheetView;
71332
71698
  }
71333
71699
 
71334
71700
  /**
@@ -71641,9 +72007,7 @@ var Status;
71641
72007
  })(Status || (Status = {}));
71642
72008
  class Model extends EventBus {
71643
72009
  corePlugins = [];
71644
- featurePlugins = [];
71645
72010
  statefulUIPlugins = [];
71646
- coreViewsPlugins = [];
71647
72011
  range;
71648
72012
  session;
71649
72013
  /**
@@ -71688,7 +72052,7 @@ class Model extends EventBus {
71688
72052
  coreHandlers = [];
71689
72053
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = false) {
71690
72054
  const start = performance.now();
71691
- console.group("Model creation");
72055
+ console.debug("##### Model creation #####");
71692
72056
  super();
71693
72057
  setDefaultTranslationMethod();
71694
72058
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
@@ -71729,7 +72093,6 @@ class Model extends EventBus {
71729
72093
  this.session.loadInitialMessages(stateUpdateMessages);
71730
72094
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
71731
72095
  const plugin = this.setupUiPlugin(Plugin);
71732
- this.coreViewsPlugins.push(plugin);
71733
72096
  this.handlers.push(plugin);
71734
72097
  this.uiHandlers.push(plugin);
71735
72098
  this.coreHandlers.push(plugin);
@@ -71742,7 +72105,6 @@ class Model extends EventBus {
71742
72105
  }
71743
72106
  for (let Plugin of featurePluginRegistry.getAll()) {
71744
72107
  const plugin = this.setupUiPlugin(Plugin);
71745
- this.featurePlugins.push(plugin);
71746
72108
  this.handlers.push(plugin);
71747
72109
  this.uiHandlers.push(plugin);
71748
72110
  }
@@ -71759,16 +72121,16 @@ class Model extends EventBus {
71759
72121
  this.joinSession();
71760
72122
  if (config.snapshotRequested) {
71761
72123
  const startSnapshot = performance.now();
71762
- console.info("Snapshot requested");
72124
+ console.debug("Snapshot requested");
71763
72125
  this.session.snapshot(this.exportData());
71764
72126
  this.garbageCollectExternalResources();
71765
- console.info("Snapshot taken in", performance.now() - startSnapshot, "ms");
72127
+ console.debug("Snapshot taken in", performance.now() - startSnapshot, "ms");
71766
72128
  }
71767
72129
  // mark all models as "raw", so they will not be turned into reactive objects
71768
72130
  // by owl, since we do not rely on reactivity
71769
72131
  markRaw(this);
71770
- console.info("Model created in", performance.now() - start, "ms");
71771
- console.groupEnd();
72132
+ console.debug("Model created in", performance.now() - start, "ms");
72133
+ console.debug("######");
71772
72134
  }
71773
72135
  joinSession() {
71774
72136
  this.session.join(this.config.client);
@@ -71827,7 +72189,7 @@ class Model extends EventBus {
71827
72189
  this.finalize();
71828
72190
  }
71829
72191
  setupSession(revisionId) {
71830
- const session = new Session(buildRevisionLog({
72192
+ return new Session(buildRevisionLog({
71831
72193
  initialRevisionId: revisionId,
71832
72194
  recordChanges: this.state.recordChanges.bind(this.state),
71833
72195
  dispatch: (command) => {
@@ -71840,7 +72202,6 @@ class Model extends EventBus {
71840
72202
  this.isReplayingCommand = false;
71841
72203
  },
71842
72204
  }), this.config.transportService, revisionId);
71843
- return session;
71844
72205
  }
71845
72206
  setupSessionEvents() {
71846
72207
  this.session.on("remote-revision-received", this, this.onRemoteRevisionReceived);
@@ -71933,8 +72294,7 @@ class Model extends EventBus {
71933
72294
  return results;
71934
72295
  }
71935
72296
  checkDispatchAllowedLocalCommand(command) {
71936
- const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
71937
- return results;
72297
+ return this.uiHandlers.map((handler) => handler.allowDispatch(command));
71938
72298
  }
71939
72299
  finalize() {
71940
72300
  this.status = 3 /* Status.Finalizing */;
@@ -71991,7 +72351,7 @@ class Model extends EventBus {
71991
72351
  this.finalize();
71992
72352
  const time = performance.now() - start;
71993
72353
  if (time > 5) {
71994
- console.info(type, time, "ms");
72354
+ console.debug(type, time, "ms");
71995
72355
  }
71996
72356
  });
71997
72357
  this.session.save(command, commands, changes);
@@ -72294,7 +72654,6 @@ const components = {
72294
72654
  PivotDimensionOrder,
72295
72655
  PivotDimension,
72296
72656
  PivotLayoutConfigurator,
72297
- EditableName,
72298
72657
  PivotDeferUpdate,
72299
72658
  PivotTitleSection,
72300
72659
  CogWheelMenu,
@@ -72343,6 +72702,6 @@ const constants = {
72343
72702
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
72344
72703
 
72345
72704
 
72346
- __info__.version = "18.1.0-alpha.1";
72347
- __info__.date = "2024-10-14T07:53:17.717Z";
72348
- __info__.hash = "e9ce3aa";
72705
+ __info__.version = "18.1.0-alpha.2";
72706
+ __info__.date = "2024-10-24T08:53:21.828Z";
72707
+ __info__.hash = "2a01250";