@odoo/o-spreadsheet 18.1.0-alpha.5 → 18.1.0-alpha.7

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.5
6
- * @date 2024-11-22T14:22:50.899Z
7
- * @hash e13bd67
5
+ * @version 18.1.0-alpha.7
6
+ * @date 2024-12-05T10:40:26.512Z
7
+ * @hash 7b1c39b
8
8
  */
9
9
 
10
10
  'use strict';
@@ -172,10 +172,13 @@ const ALERT_INFO_BG = "#CDEDF1";
172
172
  const ALERT_INFO_BORDER = "#98DBE2";
173
173
  const ALERT_INFO_TEXT_COLOR = "#09414A";
174
174
  const BADGE_SELECTED_COLOR = "#E6F2F3";
175
- const DEFAULT_CHART_PADDING = 20;
176
- const DEFAULT_CHART_FONT_SIZE = 22;
177
- const SCORECARD_GAUGE_CHART_PADDING = 10;
178
- const SCORECARD_GAUGE_CHART_FONT_SIZE = 14;
175
+ const CHART_PADDING$1 = 20;
176
+ const CHART_PADDING_BOTTOM = 10;
177
+ const CHART_PADDING_TOP = 15;
178
+ const CHART_TITLE_FONT_SIZE = 16;
179
+ const CHART_AXIS_TITLE_FONT_SIZE = 12;
180
+ const SCORECARD_CHART_TITLE_FONT_SIZE = 14;
181
+ const PIVOT_TOKEN_COLOR = "#F28C28";
179
182
  // Color picker defaults as upper case HEX to match `toHex`helper
180
183
  const COLOR_PICKER_DEFAULTS = [
181
184
  "#000000",
@@ -335,8 +338,8 @@ const DEFAULT_WINDOW_SIZE = 2;
335
338
  const DEBOUNCE_TIME = 200;
336
339
  const MESSAGE_VERSION = 1;
337
340
  // Sheets
338
- const FORBIDDEN_SHEET_CHARS = ["'", "*", "?", "/", "\\", "[", "]"];
339
- const FORBIDDEN_IN_EXCEL_REGEX = /'|\*|\?|\/|\\|\[|\]/;
341
+ const FORBIDDEN_SHEETNAME_CHARS = ["'", "*", "?", "/", "\\", "[", "]"];
342
+ const FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX = /'|\*|\?|\/|\\|\[|\]/;
340
343
  // Cells
341
344
  const FORMULA_REF_IDENTIFIER = "|";
342
345
  // Components
@@ -389,6 +392,7 @@ const DEFAULT_CURRENCY = {
389
392
  //------------------------------------------------------------------------------
390
393
  // Miscellaneous
391
394
  //------------------------------------------------------------------------------
395
+ const sanitizeSheetNameRegex = new RegExp(FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX, "g");
392
396
  /**
393
397
  * Remove quotes from a quoted string
394
398
  * ```js
@@ -484,6 +488,10 @@ function getCanonicalSymbolName(symbolName) {
484
488
  }
485
489
  return symbolName;
486
490
  }
491
+ /** Replace the excel-excluded characters of a sheetName */
492
+ function sanitizeSheetName(sheetName, replacementChar = " ") {
493
+ return sheetName.replace(sanitizeSheetNameRegex, replacementChar);
494
+ }
487
495
  function clip(val, min, max) {
488
496
  return val < min ? min : val > max ? max : val;
489
497
  }
@@ -3498,7 +3506,9 @@ exports.CommandResult = void 0;
3498
3506
  CommandResult["MaxInvalidFormula"] = "MaxInvalidFormula";
3499
3507
  CommandResult["ValueUpperInvalidFormula"] = "ValueUpperInvalidFormula";
3500
3508
  CommandResult["ValueLowerInvalidFormula"] = "ValueLowerInvalidFormula";
3509
+ CommandResult["InvalidSortAnchor"] = "InvalidSortAnchor";
3501
3510
  CommandResult["InvalidSortZone"] = "InvalidSortZone";
3511
+ CommandResult["SortZoneWithArrayFormulas"] = "SortZoneWithArrayFormulas";
3502
3512
  CommandResult["WaitingSessionConfirmation"] = "WaitingSessionConfirmation";
3503
3513
  CommandResult["MergeOverlap"] = "MergeOverlap";
3504
3514
  CommandResult["TooManyHiddenElements"] = "TooManyHiddenElements";
@@ -3554,7 +3564,6 @@ exports.CommandResult = void 0;
3554
3564
  CommandResult["ValueCellIsInvalidFormula"] = "ValueCellIsInvalidFormula";
3555
3565
  CommandResult["InvalidDefinition"] = "InvalidDefinition";
3556
3566
  CommandResult["InvalidColor"] = "InvalidColor";
3557
- CommandResult["DataBarRangeValuesMismatch"] = "DataBarRangeValuesMismatch";
3558
3567
  })(exports.CommandResult || (exports.CommandResult = {}));
3559
3568
 
3560
3569
  const DEFAULT_LOCALES = [
@@ -9515,6 +9524,12 @@ function chartFontColor(backgroundColor) {
9515
9524
  }
9516
9525
  return relativeLuminance(backgroundColor) < 0.3 ? "#FFFFFF" : "#000000";
9517
9526
  }
9527
+ function chartMutedFontColor(backgroundColor) {
9528
+ if (!backgroundColor) {
9529
+ return "#666666";
9530
+ }
9531
+ return relativeLuminance(backgroundColor) < 0.3 ? "#C8C8C8" : "#666666";
9532
+ }
9518
9533
  function checkDataset(definition) {
9519
9534
  if (definition.dataSets) {
9520
9535
  const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range.dataRange)) !== undefined;
@@ -9650,8 +9665,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
9650
9665
  const yMin = chart.chartArea.top;
9651
9666
  const textsPositions = {};
9652
9667
  for (const dataset of chart._metasets) {
9653
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9654
- return; // ignore trend lines
9668
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID || dataset.hidden) {
9669
+ continue;
9655
9670
  }
9656
9671
  for (let i = 0; i < dataset._parsed.length; i++) {
9657
9672
  const parsedValue = dataset._parsed[i];
@@ -10098,7 +10113,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10098
10113
  function drawScoreChart(structure, canvas) {
10099
10114
  const ctx = canvas.getContext("2d");
10100
10115
  canvas.width = structure.canvas.width;
10101
- const availableWidth = canvas.width - DEFAULT_CHART_PADDING;
10116
+ const availableWidth = canvas.width - CHART_PADDING$1 * 2;
10102
10117
  canvas.height = structure.canvas.height;
10103
10118
  ctx.fillStyle = structure.canvas.backgroundColor;
10104
10119
  ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
@@ -10233,10 +10248,9 @@ function createScorecardChartRuntime(chart, getters) {
10233
10248
  }
10234
10249
 
10235
10250
  /* Padding at the border of the chart */
10236
- const CHART_PADDING = SCORECARD_GAUGE_CHART_PADDING;
10251
+ const CHART_PADDING = 10;
10237
10252
  const BOTTOM_PADDING_RATIO = 0.05;
10238
10253
  /* Maximum font sizes of each element */
10239
- const CHART_TITLE_FONT_SIZE = SCORECARD_GAUGE_CHART_FONT_SIZE;
10240
10254
  const KEY_VALUE_FONT_SIZE = 32;
10241
10255
  const BASELINE_MAX_FONT_SIZE = 16;
10242
10256
  function formatBaselineDescr(baselineDescr, baseline) {
@@ -10308,7 +10322,7 @@ class ScorecardChartConfigBuilder {
10308
10322
  : this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING,
10309
10323
  },
10310
10324
  };
10311
- const minimalBaselinePosition = baselineArrowSize + DEFAULT_CHART_PADDING;
10325
+ const minimalBaselinePosition = baselineArrowSize + CHART_PADDING * 2;
10312
10326
  if (structure.baseline.position.x < minimalBaselinePosition) {
10313
10327
  structure.baseline.position.x = minimalBaselinePosition;
10314
10328
  }
@@ -10388,7 +10402,7 @@ class ScorecardChartConfigBuilder {
10388
10402
  return this.runtime.background;
10389
10403
  }
10390
10404
  get secondaryFontColor() {
10391
- return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
10405
+ return chartMutedFontColor(this.backgroundColor);
10392
10406
  }
10393
10407
  getTextDimensions(text, font) {
10394
10408
  this.context.font = font;
@@ -10414,7 +10428,7 @@ class ScorecardChartConfigBuilder {
10414
10428
  }
10415
10429
  return {
10416
10430
  title: {
10417
- font: getDefaultContextFont(CHART_TITLE_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10431
+ font: getDefaultContextFont(this.runtime.title.fontSize ?? SCORECARD_CHART_TITLE_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10418
10432
  color: this.runtime.title.color ?? this.secondaryFontColor,
10419
10433
  },
10420
10434
  keyValue: {
@@ -14671,7 +14685,6 @@ function sortCells(cells, sortDirection, emptyCellAsZero) {
14671
14685
  return cellsToSort.sort(cellsSortingCriterion(sortDirection));
14672
14686
  }
14673
14687
  function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
14674
- let result = DispatchResult.Success;
14675
14688
  //several columns => bypass the contiguity check
14676
14689
  let multiColumns = zone.right > zone.left;
14677
14690
  if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
@@ -14691,49 +14704,37 @@ function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
14691
14704
  }
14692
14705
  }
14693
14706
  }
14694
- const { col, row } = anchor;
14695
14707
  if (multiColumns) {
14696
- result = env.model.dispatch("SORT_CELLS", { sheetId, col, row, zone, sortDirection });
14708
+ interactiveSort(env, sheetId, anchor, zone, sortDirection);
14709
+ return;
14710
+ }
14711
+ const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
14712
+ if (isEqual(contiguousZone, zone)) {
14713
+ interactiveSort(env, sheetId, anchor, zone, sortDirection);
14697
14714
  }
14698
14715
  else {
14699
- // check contiguity
14700
- const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
14701
- if (isEqual(contiguousZone, zone)) {
14702
- // merge as it is
14703
- result = env.model.dispatch("SORT_CELLS", {
14704
- sheetId,
14705
- col,
14706
- row,
14707
- zone,
14708
- sortDirection,
14709
- });
14710
- }
14711
- else {
14712
- env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => {
14713
- zone = contiguousZone;
14714
- result = env.model.dispatch("SORT_CELLS", {
14715
- sheetId,
14716
- col,
14717
- row,
14718
- zone,
14719
- sortDirection,
14720
- });
14721
- }, () => {
14722
- result = env.model.dispatch("SORT_CELLS", {
14723
- sheetId,
14724
- col,
14725
- row,
14726
- zone,
14727
- sortDirection,
14728
- });
14729
- });
14730
- }
14716
+ env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
14731
14717
  }
14718
+ }
14719
+ function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
14720
+ const result = env.model.dispatch("SORT_CELLS", {
14721
+ sheetId,
14722
+ col: anchor.col,
14723
+ row: anchor.row,
14724
+ zone,
14725
+ sortDirection,
14726
+ sortOptions,
14727
+ });
14732
14728
  if (result.isCancelledBecause("InvalidSortZone" /* CommandResult.InvalidSortZone */)) {
14733
14729
  const { col, row } = anchor;
14734
14730
  env.model.selection.selectZone({ cell: { col, row }, zone });
14735
14731
  env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
14736
14732
  }
14733
+ if (result.isCancelledBecause("SortZoneWithArrayFormulas" /* CommandResult.SortZoneWithArrayFormulas */)) {
14734
+ const { col, row } = anchor;
14735
+ env.model.selection.selectZone({ cell: { col, row }, zone });
14736
+ env.raiseError(_t("Cannot sort a zone with array formulas."));
14737
+ }
14737
14738
  }
14738
14739
 
14739
14740
  function sortMatrix(matrix, locale, ...criteria) {
@@ -21248,7 +21249,7 @@ function getFunctionsFromAST(ast, functionNames) {
21248
21249
 
21249
21250
  const PIVOT_FUNCTIONS = ["PIVOT.VALUE", "PIVOT.HEADER", "PIVOT"];
21250
21251
  /**
21251
- * Create a proposal entry for the compose autowcomplete
21252
+ * Create a proposal entry for the composer autocomplete
21252
21253
  * to insert a field name string in a formula.
21253
21254
  */
21254
21255
  function makeFieldProposal(field, granularity) {
@@ -22031,14 +22032,8 @@ const GAUGE_PADDING_BOTTOM = 20;
22031
22032
  const GAUGE_LABELS_FONT_SIZE = 12;
22032
22033
  const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
22033
22034
  const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
22034
- const GAUGE_TEXT_COLOR = "#666666";
22035
- const GAUGE_TEXT_COLOR_HIGH_CONTRAST = "#C8C8C8";
22036
- const GAUGE_INFLECTION_MARKER_COLOR = "#666666aa";
22037
22035
  const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
22038
22036
  const GAUGE_TITLE_SECTION_HEIGHT = 25;
22039
- const GAUGE_TITLE_FONT_SIZE = SCORECARD_GAUGE_CHART_FONT_SIZE;
22040
- const GAUGE_TITLE_PADDING_LEFT = SCORECARD_GAUGE_CHART_PADDING;
22041
- const GAUGE_TITLE_PADDING_TOP = SCORECARD_GAUGE_CHART_PADDING;
22042
22037
  function drawGaugeChart(canvas, runtime) {
22043
22038
  const canvasBoundingRect = canvas.getBoundingClientRect();
22044
22039
  canvas.width = canvasBoundingRect.width;
@@ -22097,7 +22092,7 @@ function drawInflectionValues(ctx, config) {
22097
22092
  ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
22098
22093
  ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
22099
22094
  ctx.lineWidth = 2;
22100
- ctx.strokeStyle = GAUGE_INFLECTION_MARKER_COLOR;
22095
+ ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
22101
22096
  ctx.beginPath();
22102
22097
  ctx.moveTo(0, -(height - config.gauge.arcWidth));
22103
22098
  ctx.lineTo(0, -height - 3);
@@ -22151,22 +22146,22 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22151
22146
  x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
22152
22147
  y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
22153
22148
  };
22154
- const textColor = getContrastedTextColor(runtime.background);
22149
+ const textColor = chartMutedFontColor(runtime.background);
22155
22150
  const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
22156
22151
  let x = 0, titleWidth = 0, titleHeight = 0;
22157
22152
  if (runtime.title.text) {
22158
- ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { ...runtime.title, fontSize: GAUGE_TITLE_FONT_SIZE }, "px"));
22153
+ ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
22159
22154
  }
22160
22155
  switch (runtime.title.align) {
22161
22156
  case "right":
22162
- x = boundingRect.width - titleWidth - GAUGE_TITLE_PADDING_LEFT;
22157
+ x = boundingRect.width - titleWidth - CHART_PADDING$1;
22163
22158
  break;
22164
22159
  case "center":
22165
22160
  x = (boundingRect.width - titleWidth) / 2;
22166
22161
  break;
22167
22162
  case "left":
22168
22163
  default:
22169
- x = GAUGE_TITLE_PADDING_LEFT;
22164
+ x = CHART_PADDING$1;
22170
22165
  break;
22171
22166
  }
22172
22167
  return {
@@ -22174,10 +22169,10 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22174
22169
  height: boundingRect.height,
22175
22170
  title: {
22176
22171
  label: runtime.title.text ?? "",
22177
- fontSize: GAUGE_TITLE_FONT_SIZE,
22172
+ fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
22178
22173
  textPosition: {
22179
22174
  x,
22180
- y: GAUGE_TITLE_PADDING_TOP + titleHeight / 2,
22175
+ y: CHART_PADDING_TOP + titleHeight / 2,
22181
22176
  },
22182
22177
  color: runtime.title.color ?? textColor,
22183
22178
  bold: runtime.title.bold,
@@ -22294,11 +22289,6 @@ function getGaugeColor(runtime) {
22294
22289
  }
22295
22290
  return runtime.colors.at(-1);
22296
22291
  }
22297
- function getContrastedTextColor(backgroundColor) {
22298
- return relativeLuminance(backgroundColor) > 0.3
22299
- ? GAUGE_TEXT_COLOR
22300
- : GAUGE_TEXT_COLOR_HIGH_CONTRAST;
22301
- }
22302
22292
  function getSegmentsOfRectangle(rectangle) {
22303
22293
  return [
22304
22294
  { start: rectangle.topLeft, end: rectangle.topRight },
@@ -27012,13 +27002,12 @@ migrationStepRegistry
27012
27002
  versionFrom: "7",
27013
27003
  migrate(data) {
27014
27004
  const namesTaken = [];
27015
- const globalForbiddenInExcel = new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g");
27016
27005
  for (let sheet of data.sheets || []) {
27017
27006
  if (!sheet.name) {
27018
27007
  continue;
27019
27008
  }
27020
27009
  const oldName = sheet.name;
27021
- const escapedName = oldName.replace(globalForbiddenInExcel, "_");
27010
+ const escapedName = sanitizeSheetName(oldName, "_");
27022
27011
  let i = 1;
27023
27012
  let newName = escapedName;
27024
27013
  while (namesTaken.includes(newName)) {
@@ -27721,7 +27710,6 @@ const CfTerms = {
27721
27710
  ["ValueLowerInvalidFormula" /* CommandResult.ValueLowerInvalidFormula */]: _t("Invalid lower inflection point formula"),
27722
27711
  ["EmptyRange" /* CommandResult.EmptyRange */]: _t("A range needs to be defined"),
27723
27712
  ["ValueCellIsInvalidFormula" /* CommandResult.ValueCellIsInvalidFormula */]: _t("At least one of the provided values is an invalid formula"),
27724
- ["DataBarRangeValuesMismatch" /* CommandResult.DataBarRangeValuesMismatch */]: _t("All the ranges and the range values must have the same size"),
27725
27713
  Unexpected: _t("The rule is invalid for an unknown reason"),
27726
27714
  },
27727
27715
  ColorScale: _t("Color scale"),
@@ -28255,37 +28243,45 @@ function interpolateData(config, values, labels, newLabels) {
28255
28243
  const labelRange = labelMax - labelMin;
28256
28244
  const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
28257
28245
  const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
28258
- switch (config.type) {
28259
- case "polynomial": {
28260
- const order = config.order ?? 2;
28261
- if (order === 1) {
28262
- return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
28263
- }
28264
- const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
28265
- return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
28266
- }
28267
- case "exponential": {
28268
- const positiveLogValues = [];
28269
- const filteredLabels = [];
28270
- for (let i = 0; i < values.length; i++) {
28271
- if (values[i] > 0) {
28272
- positiveLogValues.push(Math.log(values[i]));
28273
- filteredLabels.push(normalizedLabels[i]);
28246
+ try {
28247
+ switch (config.type) {
28248
+ case "polynomial": {
28249
+ const order = config.order;
28250
+ if (!order) {
28251
+ return Array.from({ length: newLabels.length }, () => NaN);
28252
+ }
28253
+ if (order === 1) {
28254
+ return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
28255
+ }
28256
+ const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
28257
+ return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
28258
+ }
28259
+ case "exponential": {
28260
+ const positiveLogValues = [];
28261
+ const filteredLabels = [];
28262
+ for (let i = 0; i < values.length; i++) {
28263
+ if (values[i] > 0) {
28264
+ positiveLogValues.push(Math.log(values[i]));
28265
+ filteredLabels.push(normalizedLabels[i]);
28266
+ }
28267
+ }
28268
+ if (!filteredLabels.length) {
28269
+ return Array.from({ length: newLabels.length }, () => NaN);
28274
28270
  }
28271
+ return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
28275
28272
  }
28276
- if (!filteredLabels.length) {
28277
- return [];
28273
+ case "logarithmic": {
28274
+ return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
28278
28275
  }
28279
- return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
28280
- }
28281
- case "logarithmic": {
28282
- return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
28283
- }
28284
- case "trailingMovingAverage": {
28285
- return getMovingAverageValues(values, config.window);
28276
+ case "trailingMovingAverage": {
28277
+ return getMovingAverageValues(values, config.window);
28278
+ }
28279
+ default:
28280
+ return Array.from({ length: newLabels.length }, () => NaN);
28286
28281
  }
28287
- default:
28288
- return [];
28282
+ }
28283
+ catch (e) {
28284
+ return Array.from({ length: newLabels.length }, () => NaN);
28289
28285
  }
28290
28286
  }
28291
28287
  function getChartAxisType(chart, labelRange, getters) {
@@ -28756,54 +28752,16 @@ function getChartColorsGenerator(definition, dataSetsSize) {
28756
28752
  return new ColorGenerator(dataSetsSize, definition.dataSets?.map((ds) => ds.backgroundColor) || []);
28757
28753
  }
28758
28754
 
28759
- function getCommonChartLayout(definition) {
28760
- // TODO FIXME: this is unused ATM. All the charts should probably use this instead oh whatever padding they are using now
28761
- // also look into how DEFAULT_CHART_PADDING is used in scorecards, it look strange
28755
+ function getChartLayout(definition) {
28762
28756
  return {
28763
28757
  padding: {
28764
- left: DEFAULT_CHART_PADDING,
28765
- right: DEFAULT_CHART_PADDING,
28766
- top: definition.title?.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
28767
- bottom: DEFAULT_CHART_PADDING,
28758
+ left: CHART_PADDING$1,
28759
+ right: CHART_PADDING$1,
28760
+ top: CHART_PADDING_TOP,
28761
+ bottom: CHART_PADDING_BOTTOM,
28768
28762
  },
28769
28763
  };
28770
28764
  }
28771
- function getBarChartLayout(definition) {
28772
- return {
28773
- padding: computeChartPadding({
28774
- displayTitle: !!definition.title?.text,
28775
- displayLegend: definition.legendPosition === "top",
28776
- }),
28777
- };
28778
- }
28779
- function getLineChartLayout(definition) {
28780
- return {
28781
- padding: computeChartPadding({
28782
- displayTitle: !!definition.title?.text,
28783
- displayLegend: definition.legendPosition === "top",
28784
- }),
28785
- };
28786
- }
28787
- function getPieChartLayout(definition) {
28788
- return {
28789
- padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28790
- };
28791
- }
28792
- function getWaterfallChartLayout(definition) {
28793
- return {
28794
- padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28795
- };
28796
- }
28797
- function computeChartPadding({ displayTitle, displayLegend, }) {
28798
- let top = 25;
28799
- if (displayTitle) {
28800
- top = 0;
28801
- }
28802
- else if (displayLegend) {
28803
- top = 10;
28804
- }
28805
- return { left: 20, right: 20, top, bottom: 10 };
28806
- }
28807
28765
 
28808
28766
  function getLegendDisplayOptions(definition, args) {
28809
28767
  return {
@@ -28861,11 +28819,12 @@ function getScatterChartLegend(definition, args) {
28861
28819
  return {
28862
28820
  ...INTERACTIVE_LEGEND_CONFIG,
28863
28821
  ...getLegendDisplayOptions(definition),
28864
- labels: {
28865
- color: chartFontColor(definition.background),
28866
- boxHeight: 6,
28867
- usePointStyle: true,
28868
- },
28822
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
28823
+ pointStyle: "circle",
28824
+ // the stroke is the border around the circle, so increasing its size with the chart's color reduce the size of the circle
28825
+ strokeStyle: definition.background || "#ffffff",
28826
+ lineWidth: 8,
28827
+ }),
28869
28828
  };
28870
28829
  }
28871
28830
  function getComboChartLegend(definition, args) {
@@ -28954,10 +28913,10 @@ const INTERACTIVE_LEGEND_CONFIG = {
28954
28913
  target.style.cursor = "default";
28955
28914
  },
28956
28915
  onClick: (event, legendItem, legend) => {
28957
- if (!legend.legendItems) {
28916
+ const index = legendItem.datasetIndex;
28917
+ if (!legend.legendItems || index === undefined) {
28958
28918
  return;
28959
28919
  }
28960
- const index = legend.legendItems.indexOf(legendItem);
28961
28920
  if (legend.chart.isDatasetVisible(index)) {
28962
28921
  legend.chart.hide(index);
28963
28922
  }
@@ -28973,15 +28932,29 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
28973
28932
  labels: {
28974
28933
  color: fontColor,
28975
28934
  usePointStyle: true,
28976
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => ({
28977
- text: dataset.label ?? "",
28978
- fontColor,
28979
- strokeStyle: dataset.borderColor,
28980
- fillStyle: dataset.backgroundColor,
28981
- hidden: !chart.isDatasetVisible(index),
28982
- pointStyle: dataset.type === "line" ? "line" : "rect",
28983
- ...legendLabelConfig,
28984
- })),
28935
+ generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
28936
+ if (dataset["xAxisID"] === TREND_LINE_XAXIS_ID) {
28937
+ return {
28938
+ text: dataset.label ?? "",
28939
+ fontColor,
28940
+ strokeStyle: dataset.borderColor,
28941
+ hidden: !chart.isDatasetVisible(index),
28942
+ pointStyle: "line",
28943
+ datasetIndex: index,
28944
+ lineWidth: 3,
28945
+ };
28946
+ }
28947
+ return {
28948
+ text: dataset.label ?? "",
28949
+ fontColor,
28950
+ strokeStyle: dataset.borderColor,
28951
+ fillStyle: dataset.backgroundColor,
28952
+ hidden: !chart.isDatasetVisible(index),
28953
+ pointStyle: dataset.type === "line" ? "line" : "rect",
28954
+ datasetIndex: index,
28955
+ ...legendLabelConfig,
28956
+ };
28957
+ }),
28985
28958
  },
28986
28959
  };
28987
28960
  }
@@ -29095,20 +29068,27 @@ function getWaterfallChartScales(definition, args) {
29095
29068
  return scales;
29096
29069
  }
29097
29070
  function getPyramidChartScales(definition, args) {
29071
+ const { dataSetsValues } = args;
29098
29072
  const scales = getBarChartScales(definition, args);
29099
29073
  const scalesXCallback = scales.x.ticks.callback;
29100
29074
  scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
29075
+ const maxValue = Math.max(...dataSetsValues.map((dataSet) => Math.max(...dataSet.data.map(Math.abs))));
29076
+ scales.x.suggestedMin = -maxValue;
29077
+ scales.x.suggestedMax = maxValue;
29101
29078
  return scales;
29102
29079
  }
29103
29080
  function getRadarChartScales(definition, args) {
29104
- const { locale, axisFormats } = args;
29081
+ const { locale, axisFormats, dataSetsValues } = args;
29082
+ const minValue = Math.min(...dataSetsValues.map((ds) => Math.min(...ds.data.filter((x) => !isNaN(x)))));
29105
29083
  return {
29106
29084
  r: {
29085
+ beginAtZero: true,
29107
29086
  ticks: {
29108
29087
  callback: formatTickValue({ format: axisFormats?.r, locale }),
29109
29088
  backdropColor: definition.background || "#FFFFFF",
29110
29089
  },
29111
29090
  pointLabels: { color: chartFontColor(definition.background) },
29091
+ suggestedMin: minValue < 0 ? minValue - 1 : 0,
29112
29092
  },
29113
29093
  };
29114
29094
  }
@@ -29122,6 +29102,7 @@ function getChartAxisTitleRuntime(design) {
29122
29102
  font: {
29123
29103
  style: italic ? "italic" : "normal",
29124
29104
  weight: bold ? "bold" : "normal",
29105
+ size: design.title.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE,
29125
29106
  },
29126
29107
  align: align === "left" ? "start" : align === "right" ? "end" : "center",
29127
29108
  };
@@ -29187,17 +29168,22 @@ function getChartShowValues(definition, args) {
29187
29168
 
29188
29169
  function getChartTitle(definition) {
29189
29170
  const chartTitle = definition.title;
29190
- const fontColor = chartFontColor(definition.background);
29171
+ const fontColor = chartMutedFontColor(definition.background);
29191
29172
  return {
29192
29173
  display: !!chartTitle.text,
29193
29174
  text: _t(chartTitle.text),
29194
29175
  color: chartTitle?.color ?? fontColor,
29195
29176
  align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
29196
29177
  font: {
29197
- size: DEFAULT_CHART_FONT_SIZE,
29178
+ size: definition.title.fontSize ?? CHART_TITLE_FONT_SIZE,
29198
29179
  weight: chartTitle.bold ? "bold" : "normal",
29199
29180
  style: chartTitle.italic ? "italic" : "normal",
29200
29181
  },
29182
+ padding: {
29183
+ // Disable title top/left/right padding to use the chart padding instead.
29184
+ // The legend already has a top padding, so bottom padding is useless for the title there.
29185
+ bottom: definition.legendPosition === "top" ? 0 : CHART_PADDING$1,
29186
+ },
29201
29187
  };
29202
29188
  }
29203
29189
 
@@ -29344,26 +29330,23 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29344
29330
  canChartParseLabels: canChartParseLabels,
29345
29331
  getBarChartData: getBarChartData,
29346
29332
  getBarChartDatasets: getBarChartDatasets,
29347
- getBarChartLayout: getBarChartLayout,
29348
29333
  getBarChartLegend: getBarChartLegend,
29349
29334
  getBarChartScales: getBarChartScales,
29350
29335
  getBarChartTooltip: getBarChartTooltip,
29351
29336
  getChartLabelFormat: getChartLabelFormat,
29337
+ getChartLayout: getChartLayout,
29352
29338
  getChartShowValues: getChartShowValues,
29353
29339
  getChartTitle: getChartTitle,
29354
29340
  getComboChartDatasets: getComboChartDatasets,
29355
29341
  getComboChartLegend: getComboChartLegend,
29356
- getCommonChartLayout: getCommonChartLayout,
29357
29342
  getData: getData,
29358
29343
  getLineChartData: getLineChartData,
29359
29344
  getLineChartDatasets: getLineChartDatasets,
29360
- getLineChartLayout: getLineChartLayout,
29361
29345
  getLineChartLegend: getLineChartLegend,
29362
29346
  getLineChartScales: getLineChartScales,
29363
29347
  getLineChartTooltip: getLineChartTooltip,
29364
29348
  getPieChartData: getPieChartData,
29365
29349
  getPieChartDatasets: getPieChartDatasets,
29366
- getPieChartLayout: getPieChartLayout,
29367
29350
  getPieChartLegend: getPieChartLegend,
29368
29351
  getPieChartTooltip: getPieChartTooltip,
29369
29352
  getPyramidChartData: getPyramidChartData,
@@ -29379,7 +29362,6 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29379
29362
  getScatterChartScales: getScatterChartScales,
29380
29363
  getTrendDatasetForBarChart: getTrendDatasetForBarChart,
29381
29364
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
29382
- getWaterfallChartLayout: getWaterfallChartLayout,
29383
29365
  getWaterfallChartLegend: getWaterfallChartLegend,
29384
29366
  getWaterfallChartScales: getWaterfallChartScales,
29385
29367
  getWaterfallChartTooltip: getWaterfallChartTooltip,
@@ -29527,7 +29509,7 @@ function createBarChartRuntime(chart, getters) {
29527
29509
  options: {
29528
29510
  ...CHART_COMMON_OPTIONS,
29529
29511
  indexAxis: chart.horizontal ? "y" : "x",
29530
- layout: getBarChartLayout(definition),
29512
+ layout: getChartLayout(),
29531
29513
  scales: getBarChartScales(definition, chartData),
29532
29514
  plugins: {
29533
29515
  title: getChartTitle(definition),
@@ -29679,7 +29661,7 @@ function createComboChartRuntime(chart, getters) {
29679
29661
  },
29680
29662
  options: {
29681
29663
  ...CHART_COMMON_OPTIONS,
29682
- layout: getBarChartLayout(definition),
29664
+ layout: getChartLayout(),
29683
29665
  scales: getBarChartScales(definition, chartData),
29684
29666
  plugins: {
29685
29667
  title: getChartTitle(definition),
@@ -30080,7 +30062,7 @@ function createLineChartRuntime(chart, getters) {
30080
30062
  },
30081
30063
  options: {
30082
30064
  ...CHART_COMMON_OPTIONS,
30083
- layout: getLineChartLayout(definition),
30065
+ layout: getChartLayout(),
30084
30066
  scales: getLineChartScales(definition, chartData),
30085
30067
  plugins: {
30086
30068
  title: getChartTitle(definition),
@@ -30215,7 +30197,7 @@ function createPieChartRuntime(chart, getters) {
30215
30197
  },
30216
30198
  options: {
30217
30199
  ...CHART_COMMON_OPTIONS,
30218
- layout: getPieChartLayout(definition),
30200
+ layout: getChartLayout(),
30219
30201
  plugins: {
30220
30202
  title: getChartTitle(definition),
30221
30203
  legend: getPieChartLegend(definition, chartData),
@@ -30352,7 +30334,7 @@ function createPyramidChartRuntime(chart, getters) {
30352
30334
  options: {
30353
30335
  ...CHART_COMMON_OPTIONS,
30354
30336
  indexAxis: "y",
30355
- layout: getBarChartLayout(definition),
30337
+ layout: getChartLayout(),
30356
30338
  scales: getPyramidChartScales(definition, chartData),
30357
30339
  plugins: {
30358
30340
  title: getChartTitle(definition),
@@ -30501,7 +30483,7 @@ function createRadarChartRuntime(chart, getters) {
30501
30483
  },
30502
30484
  options: {
30503
30485
  ...CHART_COMMON_OPTIONS,
30504
- layout: getBarChartLayout(definition),
30486
+ layout: getChartLayout(),
30505
30487
  scales: getRadarChartScales(definition, chartData),
30506
30488
  plugins: {
30507
30489
  title: getChartTitle(definition),
@@ -30654,7 +30636,7 @@ function createScatterChartRuntime(chart, getters) {
30654
30636
  },
30655
30637
  options: {
30656
30638
  ...CHART_COMMON_OPTIONS,
30657
- layout: getLineChartLayout(definition),
30639
+ layout: getChartLayout(),
30658
30640
  scales: getScatterChartScales(definition, chartData),
30659
30641
  plugins: {
30660
30642
  title: getChartTitle(definition),
@@ -30815,7 +30797,7 @@ function createWaterfallChartRuntime(chart, getters) {
30815
30797
  },
30816
30798
  options: {
30817
30799
  ...CHART_COMMON_OPTIONS,
30818
- layout: getWaterfallChartLayout(definition),
30800
+ layout: getChartLayout(),
30819
30801
  scales: getWaterfallChartScales(definition, chartData),
30820
30802
  plugins: {
30821
30803
  title: getChartTitle(definition),
@@ -32209,14 +32191,9 @@ class FilterMenu extends owl.Component {
32209
32191
  }
32210
32192
  const sheetId = this.env.model.getters.getActiveSheetId();
32211
32193
  const contentZone = { ...tableZone, top: tableZone.top + 1 };
32212
- this.env.model.dispatch("SORT_CELLS", {
32213
- sheetId,
32214
- col: filterPosition.col,
32215
- row: contentZone.top,
32216
- zone: contentZone,
32217
- sortDirection,
32218
- sortOptions: { emptyCellAsZero: true, sortHeaders: true },
32219
- });
32194
+ const sortAnchor = { col: filterPosition.col, row: contentZone.top };
32195
+ const sortOptions = { emptyCellAsZero: true, sortHeaders: true };
32196
+ interactiveSort(this.env, sheetId, sortAnchor, contentZone, sortDirection, sortOptions);
32220
32197
  this.props.onClosed?.();
32221
32198
  }
32222
32199
  }
@@ -33130,6 +33107,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
33130
33107
  adaptChartRange: adaptChartRange,
33131
33108
  chartFactory: chartFactory,
33132
33109
  chartFontColor: chartFontColor,
33110
+ chartMutedFontColor: chartMutedFontColor,
33133
33111
  chartRuntimeFactory: chartRuntimeFactory,
33134
33112
  chartToImage: chartToImage,
33135
33113
  checkDataset: checkDataset,
@@ -34232,6 +34210,7 @@ const pivotProperties = {
34232
34210
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
34233
34211
  return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
34234
34212
  },
34213
+ isReadonlyAllowed: true,
34235
34214
  icon: "o-spreadsheet-Icon.PIVOT",
34236
34215
  };
34237
34216
  const FIX_FORMULAS = {
@@ -35949,6 +35928,7 @@ topbarMenuRegistry
35949
35928
  id: `item_pivot_${env.model.getters.getPivotFormulaId(pivotId)}`,
35950
35929
  name: env.model.getters.getPivotDisplayName(pivotId),
35951
35930
  sequence: sequence + index,
35931
+ isReadonlyAllowed: true,
35952
35932
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
35953
35933
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
35954
35934
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
@@ -37420,6 +37400,95 @@ class ColorPickerWidget extends owl.Component {
37420
37400
  }
37421
37401
  }
37422
37402
 
37403
+ css /* scss */ `
37404
+ .o-font-size-editor {
37405
+ height: calc(100% - 4px);
37406
+ input.o-font-size {
37407
+ outline-color: ${SELECTION_BORDER_COLOR};
37408
+ height: 20px;
37409
+ width: 23px;
37410
+ }
37411
+ }
37412
+ .o-text-options > div {
37413
+ cursor: pointer;
37414
+ line-height: 26px;
37415
+ padding: 3px 12px;
37416
+ &:hover {
37417
+ background-color: rgba(0, 0, 0, 0.08);
37418
+ }
37419
+ }
37420
+ `;
37421
+ class FontSizeEditor extends owl.Component {
37422
+ static template = "o-spreadsheet-FontSizeEditor";
37423
+ static props = {
37424
+ currentFontSize: Number,
37425
+ onFontSizeChanged: Function,
37426
+ onToggle: { type: Function, optional: true },
37427
+ class: String,
37428
+ };
37429
+ static components = { Popover };
37430
+ fontSizes = FONT_SIZES;
37431
+ dropdown = owl.useState({ isOpen: false });
37432
+ inputRef = owl.useRef("inputFontSize");
37433
+ rootEditorRef = owl.useRef("FontSizeEditor");
37434
+ fontSizeListRef = owl.useRef("fontSizeList");
37435
+ setup() {
37436
+ owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
37437
+ }
37438
+ get popoverProps() {
37439
+ const { x, y, width, height } = this.rootEditorRef.el.getBoundingClientRect();
37440
+ return {
37441
+ anchorRect: { x, y, width, height },
37442
+ positioning: "BottomLeft",
37443
+ verticalOffset: 0,
37444
+ };
37445
+ }
37446
+ onExternalClick(ev) {
37447
+ if (!isChildEvent(this.fontSizeListRef.el, ev) && !isChildEvent(this.rootEditorRef.el, ev)) {
37448
+ this.closeFontList();
37449
+ }
37450
+ }
37451
+ toggleFontList() {
37452
+ const isOpen = this.dropdown.isOpen;
37453
+ if (!isOpen) {
37454
+ this.props.onToggle?.();
37455
+ this.inputRef.el.focus();
37456
+ }
37457
+ else {
37458
+ this.closeFontList();
37459
+ }
37460
+ }
37461
+ closeFontList() {
37462
+ this.dropdown.isOpen = false;
37463
+ }
37464
+ setSize(fontSizeStr) {
37465
+ const fontSize = clip(Math.floor(parseFloat(fontSizeStr)), 1, 400);
37466
+ this.props.onFontSizeChanged(fontSize);
37467
+ this.closeFontList();
37468
+ }
37469
+ setSizeFromInput(ev) {
37470
+ this.setSize(ev.target.value);
37471
+ }
37472
+ setSizeFromList(fontSizeStr) {
37473
+ this.setSize(fontSizeStr);
37474
+ }
37475
+ onInputFocused(ev) {
37476
+ this.dropdown.isOpen = true;
37477
+ ev.target.select();
37478
+ }
37479
+ onInputKeydown(ev) {
37480
+ if (ev.key === "Enter" || ev.key === "Escape") {
37481
+ this.closeFontList();
37482
+ const target = ev.target;
37483
+ // In the case of a ESCAPE key, we get the previous font size back
37484
+ if (ev.key === "Escape") {
37485
+ target.value = `${this.props.currentFontSize}`;
37486
+ }
37487
+ this.props.onToggle?.();
37488
+ }
37489
+ }
37490
+ }
37491
+
37423
37492
  css /* scss */ `
37424
37493
  .o-chart-title-designer {
37425
37494
  > span {
@@ -37454,7 +37523,7 @@ css /* scss */ `
37454
37523
  `;
37455
37524
  class ChartTitle extends owl.Component {
37456
37525
  static template = "o-spreadsheet.ChartTitle";
37457
- static components = { Section, ColorPickerWidget };
37526
+ static components = { Section, ColorPickerWidget, FontSizeEditor };
37458
37527
  static props = {
37459
37528
  title: { type: String, optional: true },
37460
37529
  updateTitle: Function,
@@ -37463,7 +37532,8 @@ class ChartTitle extends owl.Component {
37463
37532
  toggleBold: { type: Function, optional: true },
37464
37533
  updateAlignment: { type: Function, optional: true },
37465
37534
  updateColor: { type: Function, optional: true },
37466
- style: { type: Object, optional: true },
37535
+ style: Object,
37536
+ onFontSizeChanged: Function,
37467
37537
  };
37468
37538
  static defaultProps = {
37469
37539
  title: "",
@@ -37478,6 +37548,9 @@ class ChartTitle extends owl.Component {
37478
37548
  updateTitle(ev) {
37479
37549
  this.props.updateTitle(ev.target.value);
37480
37550
  }
37551
+ updateFontSize(fontSize) {
37552
+ this.props.onFontSizeChanged(fontSize);
37553
+ }
37481
37554
  toggleDropdownTool(tool, ev) {
37482
37555
  const isOpen = this.state.activeTool === tool;
37483
37556
  this.closeMenus();
@@ -37520,6 +37593,7 @@ class AxisDesignEditor extends owl.Component {
37520
37593
  return {
37521
37594
  color: "",
37522
37595
  align: "center",
37596
+ fontSize: CHART_AXIS_TITLE_FONT_SIZE,
37523
37597
  ...axisDesign.title,
37524
37598
  };
37525
37599
  }
@@ -37537,6 +37611,17 @@ class AxisDesignEditor extends owl.Component {
37537
37611
  };
37538
37612
  this.props.updateChart(this.props.figureId, { axesDesign });
37539
37613
  }
37614
+ updateAxisTitleFontSize(fontSize) {
37615
+ const axesDesign = this.props.definition.axesDesign ?? {};
37616
+ axesDesign[this.state.currentAxis] = {
37617
+ ...axesDesign[this.state.currentAxis],
37618
+ title: {
37619
+ ...(axesDesign[this.state.currentAxis]?.title ?? {}),
37620
+ fontSize,
37621
+ },
37622
+ };
37623
+ this.props.updateChart(this.props.figureId, { axesDesign });
37624
+ }
37540
37625
  toggleBoldAxisTitle() {
37541
37626
  const axesDesign = this.props.definition.axesDesign ?? {};
37542
37627
  const title = axesDesign[this.state.currentAxis]?.title ?? {};
@@ -37656,8 +37741,12 @@ class GeneralDesignEditor extends owl.Component {
37656
37741
  figureId: String,
37657
37742
  definition: Object,
37658
37743
  updateChart: Function,
37744
+ defaultChartTitleFontSize: { type: Number, optional: true },
37659
37745
  slots: { type: Object, optional: true },
37660
37746
  };
37747
+ static defaultProps = {
37748
+ defaultChartTitleFontSize: CHART_TITLE_FONT_SIZE,
37749
+ };
37661
37750
  state;
37662
37751
  setup() {
37663
37752
  this.state = owl.useState({
@@ -37683,6 +37772,7 @@ class GeneralDesignEditor extends owl.Component {
37683
37772
  get titleStyle() {
37684
37773
  return {
37685
37774
  align: "left",
37775
+ fontSize: this.props.defaultChartTitleFontSize,
37686
37776
  ...this.title,
37687
37777
  };
37688
37778
  }
@@ -37691,6 +37781,10 @@ class GeneralDesignEditor extends owl.Component {
37691
37781
  this.props.updateChart(this.props.figureId, { title });
37692
37782
  this.state.activeTool = "";
37693
37783
  }
37784
+ updateChartTitleFontSize(fontSize) {
37785
+ const title = { ...this.title, fontSize };
37786
+ this.props.updateChart(this.props.figureId, { title });
37787
+ }
37694
37788
  toggleBoldChartTitle() {
37695
37789
  let title = this.title;
37696
37790
  title = { ...title, bold: !title.bold };
@@ -37898,7 +37992,7 @@ class SeriesWithAxisDesignEditor extends owl.Component {
37898
37992
  case "polynomial":
37899
37993
  config = {
37900
37994
  type: "polynomial",
37901
- order: type === "linear" ? 1 : 2,
37995
+ order: type === "linear" ? 1 : this.getMaxPolynomialDegree(index),
37902
37996
  };
37903
37997
  break;
37904
37998
  case "exponential":
@@ -38358,6 +38452,9 @@ class ScorecardChartDesignPanel extends owl.Component {
38358
38452
  get humanizeNumbersLabel() {
38359
38453
  return _t("Humanize numbers");
38360
38454
  }
38455
+ get defaultScorecardTitleFontSize() {
38456
+ return SCORECARD_CHART_TITLE_FONT_SIZE;
38457
+ }
38361
38458
  updateHumanizeNumbers(humanize) {
38362
38459
  this.props.updateChart(this.props.figureId, { humanize });
38363
38460
  }
@@ -39821,6 +39918,15 @@ class StandaloneComposerStore extends AbstractComposerStore {
39821
39918
  confirmEdition(content) {
39822
39919
  this.args().onConfirm(content);
39823
39920
  }
39921
+ getTokenColor(token) {
39922
+ if (token.type === "SYMBOL") {
39923
+ const matchedColor = this.args().getContextualColoredSymbolToken?.(token);
39924
+ if (matchedColor) {
39925
+ return matchedColor;
39926
+ }
39927
+ }
39928
+ return super.getTokenColor(token);
39929
+ }
39824
39930
  }
39825
39931
 
39826
39932
  css /* scss */ `
@@ -39858,6 +39964,7 @@ class StandaloneComposer extends owl.Component {
39858
39964
  placeholder: { type: String, optional: true },
39859
39965
  class: { type: String, optional: true },
39860
39966
  invalid: { type: Boolean, optional: true },
39967
+ getContextualColoredSymbolToken: { type: Function, optional: true },
39861
39968
  };
39862
39969
  static components = { Composer };
39863
39970
  static defaultProps = {
@@ -39874,6 +39981,7 @@ class StandaloneComposer extends owl.Component {
39874
39981
  content: this.props.composerContent,
39875
39982
  contextualAutocomplete: this.props.contextualAutocomplete,
39876
39983
  defaultRangeSheetId: this.props.defaultRangeSheetId,
39984
+ getContextualColoredSymbolToken: this.props.getContextualColoredSymbolToken,
39877
39985
  }));
39878
39986
  this.standaloneComposerStore = standaloneComposerStore;
39879
39987
  this.composerInterface = {
@@ -43434,7 +43542,7 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
43434
43542
  return {
43435
43543
  text: text,
43436
43544
  description: measure.displayName,
43437
- htmlContent: [{ value: text, color: tokenColors.FUNCTION }],
43545
+ htmlContent: [{ value: text, color: PIVOT_TOKEN_COLOR }],
43438
43546
  fuzzySearchKey: measure.displayName + text + measure.fieldName,
43439
43547
  };
43440
43548
  });
@@ -43443,7 +43551,7 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
43443
43551
  return {
43444
43552
  text: text,
43445
43553
  description: dimension.displayName,
43446
- htmlContent: [{ value: text, color: tokenColors.FUNCTION }],
43554
+ htmlContent: [{ value: text, color: PIVOT_TOKEN_COLOR }],
43447
43555
  fuzzySearchKey: dimension.displayName + text + dimension.fieldName,
43448
43556
  };
43449
43557
  });
@@ -43523,6 +43631,18 @@ class PivotMeasureEditor extends owl.Component {
43523
43631
  measure: this.props.measure,
43524
43632
  });
43525
43633
  }
43634
+ getColoredSymbolToken(token) {
43635
+ if (token.type !== "SYMBOL") {
43636
+ return undefined;
43637
+ }
43638
+ const tokenValue = unquote(token.value, "'");
43639
+ if (this.props.definition.columns.some((col) => col.nameWithGranularity === tokenValue) ||
43640
+ this.props.definition.rows.some((row) => row.nameWithGranularity === tokenValue) ||
43641
+ this.props.definition.measures.some((measure) => measure.id === tokenValue && measure.id !== this.props.measure.id)) {
43642
+ return PIVOT_TOKEN_COLOR;
43643
+ }
43644
+ return undefined;
43645
+ }
43526
43646
  }
43527
43647
 
43528
43648
  css /* scss */ `
@@ -51897,7 +52017,7 @@ class CellPlugin extends CorePlugin {
51897
52017
  for (let col = zone.left; col <= zone.right; col++) {
51898
52018
  for (let row = zone.top; row <= zone.bottom; row++) {
51899
52019
  const cell = this.getters.getCell({ sheetId, col, row });
51900
- if (cell) {
52020
+ if (cell?.isFormula || cell?.content) {
51901
52021
  this.dispatch("UPDATE_CELL", {
51902
52022
  sheetId: sheetId,
51903
52023
  content: "",
@@ -51933,7 +52053,6 @@ class CellPlugin extends CorePlugin {
51933
52053
  for (let zone of recomputeZones(zones)) {
51934
52054
  for (let col = zone.left; col <= zone.right; col++) {
51935
52055
  for (let row = zone.top; row <= zone.bottom; row++) {
51936
- // commandHelpers.updateCell(sheetId, col, row, { style: undefined});
51937
52056
  this.dispatch("UPDATE_CELL", {
51938
52057
  sheetId,
51939
52058
  col,
@@ -52904,8 +53023,6 @@ class ConditionalFormatPlugin extends CorePlugin {
52904
53023
  case "IconSetRule": {
52905
53024
  return this.checkValidations(rule, this.chainValidations(this.checkInflectionPoints(this.checkNaN), this.checkLowerBiggerThanUpper), this.chainValidations(this.checkInflectionPoints(this.checkFormulaCompilation)));
52906
53025
  }
52907
- case "DataBarRule":
52908
- return this.checkDataBarRangeValues(rule, cmd.ranges, cmd.sheetId);
52909
53026
  }
52910
53027
  return "Success" /* CommandResult.Success */;
52911
53028
  }
@@ -53036,18 +53153,6 @@ class ConditionalFormatPlugin extends CorePlugin {
53036
53153
  }
53037
53154
  return "Success" /* CommandResult.Success */;
53038
53155
  }
53039
- checkDataBarRangeValues(rule, ranges, sheetId) {
53040
- if (rule.rangeValues) {
53041
- const { numberOfCols, numberOfRows } = zoneToDimension(this.getters.getRangeFromSheetXC(sheetId, rule.rangeValues).zone);
53042
- for (const range of ranges) {
53043
- const dimensions = zoneToDimension(this.getters.getRangeFromRangeData(range).zone);
53044
- if (numberOfCols !== dimensions.numberOfCols || numberOfRows !== dimensions.numberOfRows) {
53045
- return "DataBarRangeValuesMismatch" /* CommandResult.DataBarRangeValuesMismatch */;
53046
- }
53047
- }
53048
- }
53049
- return "Success" /* CommandResult.Success */;
53050
- }
53051
53156
  removeConditionalFormatting(id, sheet) {
53052
53157
  const cfIndex = this.cfRules[sheet].findIndex((s) => s.id === id);
53053
53158
  if (cfIndex !== -1) {
@@ -55366,7 +55471,7 @@ class SheetPlugin extends CorePlugin {
55366
55471
  if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
55367
55472
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55368
55473
  }
55369
- if (FORBIDDEN_IN_EXCEL_REGEX.test(name)) {
55474
+ if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
55370
55475
  return "ForbiddenCharactersInSheetName" /* CommandResult.ForbiddenCharactersInSheetName */;
55371
55476
  }
55372
55477
  return "Success" /* CommandResult.Success */;
@@ -59452,8 +59557,12 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59452
59557
  const zoneOfValues = rangeValues.zone;
59453
59558
  for (let row = zone.top; row <= zone.bottom; row++) {
59454
59559
  for (let col = zone.left; col <= zone.right; col++) {
59455
- const cell = this.getEvaluatedCellInZone(sheetId, zone, col, row, zoneOfValues);
59456
- if (cell.type !== CellValueType.number || cell.value <= 0) {
59560
+ const targetCol = col - zone.left + zoneOfValues.left;
59561
+ const targetRow = row - zone.top + zoneOfValues.top;
59562
+ const cell = this.getters.getEvaluatedCell({ sheetId, col: targetCol, row: targetRow });
59563
+ if (!isInside(targetCol, targetRow, zoneOfValues) ||
59564
+ cell.type !== CellValueType.number ||
59565
+ cell.value <= 0) {
59457
59566
  // values negatives or 0 are ignored
59458
59567
  continue;
59459
59568
  }
@@ -59466,11 +59575,6 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59466
59575
  }
59467
59576
  }
59468
59577
  }
59469
- getEvaluatedCellInZone(sheetId, zone, col, row, targetZone) {
59470
- const targetCol = col - zone.left + targetZone.left;
59471
- const targetRow = row - zone.top + targetZone.top;
59472
- return this.getters.getEvaluatedCell({ sheetId, col: targetCol, row: targetRow });
59473
- }
59474
59578
  /** Compute the color scale for the given range and CF rule, and apply in in the given computedStyle object */
59475
59579
  applyColorScale(sheetId, range, rule, computedStyle) {
59476
59580
  const minValue = this.parsePoint(sheetId, range, rule.minimum, "min");
@@ -60775,11 +60879,13 @@ class PivotUIPlugin extends UIPlugin {
60775
60879
  return EMPTY_PIVOT_CELL;
60776
60880
  }
60777
60881
  if (functionName === "PIVOT") {
60778
- const includeTotal = args[2] === false ? false : undefined;
60779
- const includeColumnHeaders = args[3] === false ? false : undefined;
60882
+ const includeTotal = toScalar(args[2]);
60883
+ const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
60884
+ const includeColumnHeaders = toScalar(args[3]);
60885
+ const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
60780
60886
  const pivotCells = pivot
60781
60887
  .getTableStructure()
60782
- .getPivotCells(includeTotal, includeColumnHeaders);
60888
+ .getPivotCells(shouldIncludeTotal, shouldIncludeColumnHeaders);
60783
60889
  const pivotCol = position.col - mainPosition.col;
60784
60890
  const pivotRow = position.row - mainPosition.row;
60785
60891
  return pivotCells[pivotCol][pivotRow];
@@ -62933,7 +63039,7 @@ class InsertPivotPlugin extends UIPlugin {
62933
63039
  getPivotDuplicateSheetName(pivotName) {
62934
63040
  let i = 1;
62935
63041
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
62936
- const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
63042
+ const sanitizedName = sanitizeSheetName(pivotName);
62937
63043
  let name = sanitizedName;
62938
63044
  while (names.includes(name)) {
62939
63045
  name = `${sanitizedName} (${i})`;
@@ -63048,9 +63154,9 @@ class SortPlugin extends UIPlugin {
63048
63154
  switch (cmd.type) {
63049
63155
  case "SORT_CELLS":
63050
63156
  if (!isInside(cmd.col, cmd.row, cmd.zone)) {
63051
- throw new Error(_t("The anchor must be part of the provided zone"));
63157
+ return "InvalidSortAnchor" /* CommandResult.InvalidSortAnchor */;
63052
63158
  }
63053
- return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes);
63159
+ return this.checkValidations(cmd, this.checkMerge, this.checkMergeSizes, this.checkArrayFormulaInSortZone);
63054
63160
  }
63055
63161
  return "Success" /* CommandResult.Success */;
63056
63162
  }
@@ -63091,6 +63197,10 @@ class SortPlugin extends UIPlugin {
63091
63197
  }
63092
63198
  return "Success" /* CommandResult.Success */;
63093
63199
  }
63200
+ checkArrayFormulaInSortZone({ sheetId, zone }) {
63201
+ const arrayFormulaInZone = positions(zone).some(({ col, row }) => this.getters.getArrayFormulaSpreadingOn({ sheetId, col, row }));
63202
+ return arrayFormulaInZone ? "SortZoneWithArrayFormulas" /* CommandResult.SortZoneWithArrayFormulas */ : "Success" /* CommandResult.Success */;
63203
+ }
63094
63204
  /**
63095
63205
  * This function evaluates if the top row of a provided zone can be considered as a `header`
63096
63206
  * by checking the following criteria:
@@ -67005,7 +67115,7 @@ function interactiveRenameSheet(env, sheetId, name, errorCallback) {
67005
67115
  env.raiseError(_t("A sheet with the name %s already exists. Please select another name.", name), errorCallback);
67006
67116
  }
67007
67117
  else if (result.reasons.includes("ForbiddenCharactersInSheetName" /* CommandResult.ForbiddenCharactersInSheetName */)) {
67008
- env.raiseError(_t("Some used characters are not allowed in a sheet name (Forbidden characters are %s).", FORBIDDEN_SHEET_CHARS.join(" ")), errorCallback);
67118
+ env.raiseError(_t("Some used characters are not allowed in a sheet name (Forbidden characters are %s).", FORBIDDEN_SHEETNAME_CHARS.join(" ")), errorCallback);
67009
67119
  }
67010
67120
  }
67011
67121
 
@@ -68235,7 +68345,7 @@ class ActionButton extends owl.Component {
68235
68345
  setup() {
68236
68346
  owl.onWillUpdateProps((nextProps) => {
68237
68347
  if (nextProps.action !== this.props.action) {
68238
- this.actionButton = createAction(this.props.action);
68348
+ this.actionButton = createAction(nextProps.action);
68239
68349
  }
68240
68350
  });
68241
68351
  }
@@ -68571,88 +68681,6 @@ class TopBarComposer extends owl.Component {
68571
68681
  }
68572
68682
  }
68573
68683
 
68574
- css /* scss */ `
68575
- .o-font-size-editor {
68576
- height: calc(100% - 4px);
68577
- input.o-font-size {
68578
- outline-color: ${SELECTION_BORDER_COLOR};
68579
- height: 20px;
68580
- width: 23px;
68581
- }
68582
- }
68583
- .o-text-options > div {
68584
- cursor: pointer;
68585
- line-height: 26px;
68586
- padding: 3px 12px;
68587
- &:hover {
68588
- background-color: rgba(0, 0, 0, 0.08);
68589
- }
68590
- }
68591
- `;
68592
- class FontSizeEditor extends owl.Component {
68593
- static template = "o-spreadsheet-FontSizeEditor";
68594
- static props = {
68595
- onToggle: Function,
68596
- dropdownStyle: String,
68597
- class: String,
68598
- };
68599
- static components = {};
68600
- fontSizes = FONT_SIZES;
68601
- dropdown = owl.useState({ isOpen: false });
68602
- inputRef = owl.useRef("inputFontSize");
68603
- rootEditorRef = owl.useRef("FontSizeEditor");
68604
- setup() {
68605
- owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
68606
- }
68607
- onExternalClick(ev) {
68608
- if (!isChildEvent(this.rootEditorRef.el, ev)) {
68609
- this.closeFontList();
68610
- }
68611
- }
68612
- get currentFontSize() {
68613
- return this.env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
68614
- }
68615
- toggleFontList() {
68616
- const isOpen = this.dropdown.isOpen;
68617
- if (!isOpen) {
68618
- this.props.onToggle();
68619
- this.inputRef.el.focus();
68620
- }
68621
- else {
68622
- this.closeFontList();
68623
- }
68624
- }
68625
- closeFontList() {
68626
- this.dropdown.isOpen = false;
68627
- }
68628
- setSize(fontSizeStr) {
68629
- const fontSize = clip(Math.floor(parseFloat(fontSizeStr)), 1, 400);
68630
- setStyle(this.env, { fontSize });
68631
- this.closeFontList();
68632
- }
68633
- setSizeFromInput(ev) {
68634
- this.setSize(ev.target.value);
68635
- }
68636
- setSizeFromList(fontSizeStr) {
68637
- this.setSize(fontSizeStr);
68638
- }
68639
- onInputFocused(ev) {
68640
- this.dropdown.isOpen = true;
68641
- ev.target.select();
68642
- }
68643
- onInputKeydown(ev) {
68644
- if (ev.key === "Enter" || ev.key === "Escape") {
68645
- this.closeFontList();
68646
- const target = ev.target;
68647
- // In the case of a ESCAPE key, we get the previous font size back
68648
- if (ev.key === "Escape") {
68649
- target.value = `${this.currentFontSize}`;
68650
- }
68651
- this.props.onToggle();
68652
- }
68653
- }
68654
- }
68655
-
68656
68684
  class TableDropdownButton extends owl.Component {
68657
68685
  static template = "o-spreadsheet-TableDropdownButton";
68658
68686
  static components = { TableStylesPopover, ActionButton };
@@ -68821,9 +68849,6 @@ class TopBar extends owl.Component {
68821
68849
  onClick: Function,
68822
68850
  dropdownMaxHeight: Number,
68823
68851
  };
68824
- get dropdownStyle() {
68825
- return `max-height:${this.props.dropdownMaxHeight}px`;
68826
- }
68827
68852
  static components = {
68828
68853
  ColorPickerWidget,
68829
68854
  ColorPicker,
@@ -68861,6 +68886,9 @@ class TopBar extends owl.Component {
68861
68886
  .getAllOrdered()
68862
68887
  .filter((item) => !item.isVisible || item.isVisible(this.env));
68863
68888
  }
68889
+ get currentFontSize() {
68890
+ return this.env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
68891
+ }
68864
68892
  onExternalClick(ev) {
68865
68893
  // TODO : manage click events better. We need this piece of code
68866
68894
  // otherwise the event opening the menu would close it on the same frame.
@@ -68939,6 +68967,9 @@ class TopBar extends owl.Component {
68939
68967
  setStyle(this.env, { [target]: color });
68940
68968
  this.onClick();
68941
68969
  }
68970
+ setFontSize(fontSize) {
68971
+ setStyle(this.env, { fontSize });
68972
+ }
68942
68973
  }
68943
68974
 
68944
68975
  function instantiateClipboard() {
@@ -70939,12 +70970,11 @@ function createChart(chart, chartSheetIndex, data) {
70939
70970
  // <manualLayout/> to manually position the chart in the figure container
70940
70971
  let title = escapeXml ``;
70941
70972
  if (chart.data.title?.text) {
70942
- const color = chart.data.title.color
70943
- ? toXlsxHexColor(chart.data.title.color)
70944
- : chart.data.fontColor;
70973
+ const titleColor = toXlsxHexColor(chartMutedFontColor(chart.data.backgroundColor));
70974
+ const fontSize = chart.data.title.fontSize ?? CHART_TITLE_FONT_SIZE;
70945
70975
  title = escapeXml /*xml*/ `
70946
70976
  <c:title>
70947
- ${insertText(chart.data.title.text, color, DEFAULT_CHART_FONT_SIZE, chart.data.title)}
70977
+ ${insertText(chart.data.title.text, titleColor, fontSize, chart.data.title)}
70948
70978
  <c:overlay val="0" />
70949
70979
  </c:title>
70950
70980
  `;
@@ -71034,7 +71064,7 @@ function lineAttributes(params) {
71034
71064
  </a:ln>
71035
71065
  `;
71036
71066
  }
71037
- function insertText(text, fontColor = "000000", fontsize = DEFAULT_CHART_FONT_SIZE, style = {}) {
71067
+ function insertText(text, fontColor = "000000", fontsize = CHART_TITLE_FONT_SIZE, style = {}) {
71038
71068
  return escapeXml /*xml*/ `
71039
71069
  <c:tx>
71040
71070
  <c:rich>
@@ -71535,6 +71565,7 @@ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, del
71535
71565
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
71536
71566
  // I.e. x-axis, will reference y-axis and vice-versa.
71537
71567
  const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
71568
+ const fontSize = title?.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE;
71538
71569
  return escapeXml /*xml*/ `
71539
71570
  <${axisName}>
71540
71571
  <c:axId val="${axId}"/>
@@ -71550,7 +71581,7 @@ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, del
71550
71581
  <c:minorTickMark val="none" />
71551
71582
  <c:numFmt formatCode="General" sourceLinked="1" />
71552
71583
  <c:title>
71553
- ${insertText(title?.text ?? "", color, 10, title)}
71584
+ ${insertText(title?.text ?? "", color, fontSize, title)}
71554
71585
  </c:title>
71555
71586
  ${insertTextProperties(10, defaultFontColor)}
71556
71587
  </${axisName}>
@@ -73661,6 +73692,7 @@ const helpers = {
73661
73692
  createPivotFormula,
73662
73693
  areDomainArgsFieldsValid,
73663
73694
  splitReference,
73695
+ sanitizeSheetName,
73664
73696
  };
73665
73697
  const links = {
73666
73698
  isMarkdownLink,
@@ -73692,6 +73724,9 @@ const components = {
73692
73724
  GaugeChartDesignPanel,
73693
73725
  ScorecardChartConfigPanel,
73694
73726
  ScorecardChartDesignPanel,
73727
+ RadarChartDesignPanel,
73728
+ WaterfallChartDesignPanel,
73729
+ ComboChartDesignPanel,
73695
73730
  ChartTypePicker,
73696
73731
  FigureComponent,
73697
73732
  Menu,
@@ -73745,6 +73780,7 @@ const constants = {
73745
73780
  DEFAULT_LOCALE,
73746
73781
  HIGHLIGHT_COLOR,
73747
73782
  PIVOT_TABLE_CONFIG,
73783
+ ChartTerms,
73748
73784
  };
73749
73785
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73750
73786
 
@@ -73795,6 +73831,6 @@ exports.tokenColors = tokenColors;
73795
73831
  exports.tokenize = tokenize;
73796
73832
 
73797
73833
 
73798
- __info__.version = "18.1.0-alpha.5";
73799
- __info__.date = "2024-11-22T14:22:50.899Z";
73800
- __info__.hash = "e13bd67";
73834
+ __info__.version = "18.1.0-alpha.7";
73835
+ __info__.date = "2024-12-05T10:40:26.512Z";
73836
+ __info__.hash = "7b1c39b";