@odoo/o-spreadsheet 18.1.0-alpha.6 → 18.1.0-alpha.8

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.6
6
- * @date 2024-11-28T09:06:59.527Z
7
- * @hash 875c901
5
+ * @version 18.1.0-alpha.8
6
+ * @date 2024-12-19T07:49:54.421Z
7
+ * @hash 87a1567
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';
@@ -170,10 +170,13 @@ const ALERT_INFO_BG = "#CDEDF1";
170
170
  const ALERT_INFO_BORDER = "#98DBE2";
171
171
  const ALERT_INFO_TEXT_COLOR = "#09414A";
172
172
  const BADGE_SELECTED_COLOR = "#E6F2F3";
173
- const DEFAULT_CHART_PADDING = 20;
174
- const DEFAULT_CHART_FONT_SIZE = 22;
175
- const SCORECARD_GAUGE_CHART_PADDING = 10;
176
- const SCORECARD_GAUGE_CHART_FONT_SIZE = 14;
173
+ const CHART_PADDING$1 = 20;
174
+ const CHART_PADDING_BOTTOM = 10;
175
+ const CHART_PADDING_TOP = 15;
176
+ const CHART_TITLE_FONT_SIZE = 16;
177
+ const CHART_AXIS_TITLE_FONT_SIZE = 12;
178
+ const SCORECARD_CHART_TITLE_FONT_SIZE = 14;
179
+ const PIVOT_TOKEN_COLOR = "#F28C28";
177
180
  // Color picker defaults as upper case HEX to match `toHex`helper
178
181
  const COLOR_PICKER_DEFAULTS = [
179
182
  "#000000",
@@ -262,7 +265,6 @@ const MIN_ROW_HEIGHT = 10;
262
265
  const MIN_COL_WIDTH = 5;
263
266
  const HEADER_HEIGHT = 26;
264
267
  const HEADER_WIDTH = 48;
265
- const TOPBAR_HEIGHT = 63;
266
268
  const TOPBAR_TOOLBAR_HEIGHT = 34;
267
269
  const BOTTOMBAR_HEIGHT = 36;
268
270
  const DEFAULT_CELL_WIDTH = 96;
@@ -333,8 +335,8 @@ const DEFAULT_WINDOW_SIZE = 2;
333
335
  const DEBOUNCE_TIME = 200;
334
336
  const MESSAGE_VERSION = 1;
335
337
  // Sheets
336
- const FORBIDDEN_SHEET_CHARS = ["'", "*", "?", "/", "\\", "[", "]"];
337
- const FORBIDDEN_IN_EXCEL_REGEX = /'|\*|\?|\/|\\|\[|\]/;
338
+ const FORBIDDEN_SHEETNAME_CHARS = ["'", "*", "?", "/", "\\", "[", "]"];
339
+ const FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX = /'|\*|\?|\/|\\|\[|\]/;
338
340
  // Cells
339
341
  const FORMULA_REF_IDENTIFIER = "|";
340
342
  // Components
@@ -387,6 +389,7 @@ const DEFAULT_CURRENCY = {
387
389
  //------------------------------------------------------------------------------
388
390
  // Miscellaneous
389
391
  //------------------------------------------------------------------------------
392
+ const sanitizeSheetNameRegex = new RegExp(FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX, "g");
390
393
  /**
391
394
  * Remove quotes from a quoted string
392
395
  * ```js
@@ -482,6 +485,10 @@ function getCanonicalSymbolName(symbolName) {
482
485
  }
483
486
  return symbolName;
484
487
  }
488
+ /** Replace the excel-excluded characters of a sheetName */
489
+ function sanitizeSheetName(sheetName, replacementChar = " ") {
490
+ return sheetName.replace(sanitizeSheetNameRegex, replacementChar);
491
+ }
485
492
  function clip(val, min, max) {
486
493
  return val < min ? min : val > max ? max : val;
487
494
  }
@@ -781,6 +788,7 @@ function removeFalsyAttributes(obj) {
781
788
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
782
789
  */
783
790
  const whiteSpaceSpecialCharacters = [
791
+ " ",
784
792
  "\t",
785
793
  "\f",
786
794
  "\v",
@@ -795,17 +803,15 @@ const whiteSpaceSpecialCharacters = [
795
803
  String.fromCharCode(parseInt("3000", 16)),
796
804
  String.fromCharCode(parseInt("feff", 16)),
797
805
  ];
798
- const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
806
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|"), "g");
807
+ const newLineRegexp = /(\r\n|\r)/g;
799
808
  /**
800
- * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
801
- * different newlines types by \n.
809
+ * Replace all different newlines characters by \n
802
810
  */
803
- function replaceSpecialSpaces(text) {
811
+ function replaceNewLines(text) {
804
812
  if (!text)
805
813
  return "";
806
- if (!whiteSpaceRegexp.test(text))
807
- return text;
808
- return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
814
+ return text.replace(newLineRegexp, NEWLINE);
809
815
  }
810
816
  /**
811
817
  * Determine if the numbers are consecutive.
@@ -971,7 +977,7 @@ function transpose2dPOJO(pojo) {
971
977
 
972
978
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
973
979
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
974
- const colors$1 = [
980
+ const colors = [
975
981
  "#eb6d00",
976
982
  "#0074d9",
977
983
  "#ad8e00",
@@ -994,6 +1000,12 @@ const colors$1 = [
994
1000
  function colorNumberString(color) {
995
1001
  return toHex(color.toString(16).padStart(6, "0"));
996
1002
  }
1003
+ function colorToNumber(color) {
1004
+ if (typeof color === "number") {
1005
+ return color;
1006
+ }
1007
+ return Number.parseInt(toHex(color).slice(1), 16);
1008
+ }
997
1009
  /**
998
1010
  * Converts any CSS color value to a standardized hex6 value.
999
1011
  * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
@@ -1344,9 +1356,9 @@ const COLORS_XL = [
1344
1356
  "#056BD9", // Blue #3
1345
1357
  "#155193", // Blue #4
1346
1358
  "#A76DBC", // Violet #1
1347
- "#7F4295", // Violet #1
1348
- "#6D2387", // Violet #1
1349
- "#4F1565", // Violet #1
1359
+ "#7F4295", // Violet #2
1360
+ "#6D2387", // Violet #3
1361
+ "#4F1565", // Violet #4
1350
1362
  "#EA6175", // Red #1
1351
1363
  "#CE4257", // Red #2
1352
1364
  "#982738", // Red #3
@@ -1372,6 +1384,81 @@ const COLORS_XL = [
1372
1384
  "#C08A16", // Yellow #3
1373
1385
  "#936A12", // Yellow #4
1374
1386
  ];
1387
+ // Same as above but with alternating colors
1388
+ const ALTERNATING_COLORS_MD = [
1389
+ "#4EA7F2", // Blue #1
1390
+ "#43C5B1", // Teal #1
1391
+ "#EA6175", // Red #1
1392
+ "#F4A261", // Orange #1
1393
+ "#8481DD", // Purple #1
1394
+ "#FFD86D", // Yellow #1
1395
+ "#3188E6", // Blue #2
1396
+ "#00A78D", // Teal #2
1397
+ "#CE4257", // Red #2
1398
+ "#F48935", // Orange #2
1399
+ "#5752D1", // Purple #2
1400
+ "#FFBC2C", // Yellow #2
1401
+ ];
1402
+ const ALTERNATING_COLORS_LG = [
1403
+ "#4EA7F2", // Blue #1
1404
+ "#A76DBC", // Violet #1
1405
+ "#EA6175", // Red #1
1406
+ "#43C5B1", // Teal #1
1407
+ "#F4A261", // Orange #1
1408
+ "#8481DD", // Purple #1
1409
+ "#A4A8B6", // Gray #1
1410
+ "#FFD86D", // Yellow #1
1411
+ "#3188E6", // Blue #2
1412
+ "#7F4295", // Violet #2
1413
+ "#CE4257", // Red #2
1414
+ "#00A78D", // Teal #2
1415
+ "#F48935", // Orange #2
1416
+ "#5752D1", // Purple #2
1417
+ "#7E8290", // Gray #2
1418
+ "#FFBC2C", // Yellow #2
1419
+ "#056BD9", // Blue #3
1420
+ "#6D2387", // Violet #3
1421
+ "#982738", // Red #3
1422
+ "#0E8270", // Teal #3
1423
+ "#BE5D10", // Orange #3
1424
+ "#3A3580", // Purple #3
1425
+ "#545B70", // Gray #3
1426
+ "#C08A16", // Yellow #3
1427
+ ];
1428
+ const ALTERNATING_COLORS_XL = [
1429
+ "#4EA7F2", // Blue #1
1430
+ "#A76DBC", // Violet #1
1431
+ "#EA6175", // Red #1
1432
+ "#43C5B1", // Teal #1
1433
+ "#F4A261", // Orange #1
1434
+ "#8481DD", // Purple #1
1435
+ "#A4A8B6", // Grey #1
1436
+ "#FFD86D", // Yellow #1
1437
+ "#3188E6", // Blue #2
1438
+ "#7F4295", // Violet #2
1439
+ "#CE4257", // Red #2
1440
+ "#00A78D", // Teal #2
1441
+ "#F48935", // Orange #2
1442
+ "#5752D1", // Purple #2
1443
+ "#7E8290", // Grey #2
1444
+ "#FFBC2C", // Yellow #2
1445
+ "#056BD9", // Blue #3
1446
+ "#6D2387", // Violet #3
1447
+ "#982738", // Red #3
1448
+ "#0E8270", // Teal #3
1449
+ "#BE5D10", // Orange #3
1450
+ "#3A3580", // Purple #3
1451
+ "#545B70", // Grey #3
1452
+ "#C08A16", // Yellow #3
1453
+ "#155193", // Blue #4
1454
+ "#4F1565", // Violet #4
1455
+ "#791B29", // Red #4
1456
+ "#105F53", // Teal #4
1457
+ "#7D380D", // Orange #4
1458
+ "#26235F", // Purple #4
1459
+ "#3F4250", // Grey #4
1460
+ "#936A12", // Yellow #4
1461
+ ];
1375
1462
  function getNthColor(index, palette) {
1376
1463
  return palette[index % palette.length];
1377
1464
  }
@@ -1389,6 +1476,20 @@ function getColorsPalette(quantity) {
1389
1476
  return COLORS_XL;
1390
1477
  }
1391
1478
  }
1479
+ function getAlternatingColorsPalette(quantity) {
1480
+ if (quantity <= 6) {
1481
+ return COLORS_SM;
1482
+ }
1483
+ else if (quantity <= 12) {
1484
+ return ALTERNATING_COLORS_MD;
1485
+ }
1486
+ else if (quantity <= 24) {
1487
+ return ALTERNATING_COLORS_LG;
1488
+ }
1489
+ else {
1490
+ return ALTERNATING_COLORS_XL;
1491
+ }
1492
+ }
1392
1493
  class ColorGenerator {
1393
1494
  preferredColors;
1394
1495
  currentColorIndex = 0;
@@ -1403,6 +1504,62 @@ class ColorGenerator {
1403
1504
  : getNthColor(this.currentColorIndex++, this.palette);
1404
1505
  }
1405
1506
  }
1507
+ class AlternatingColorGenerator extends ColorGenerator {
1508
+ constructor(paletteSize, preferredColors = []) {
1509
+ super(paletteSize, preferredColors);
1510
+ this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
1511
+ }
1512
+ }
1513
+ /**
1514
+ * Returns a function that maps a value to a color using a color scale defined by the given
1515
+ * color/threshold values pairs.
1516
+ */
1517
+ function getColorScale(colorScalePoints) {
1518
+ if (colorScalePoints.length < 2) {
1519
+ throw new Error("Color scale must have at least 2 points");
1520
+ }
1521
+ const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
1522
+ const thresholds = [];
1523
+ for (let i = 1; i < sortedColorScalePoints.length; i++) {
1524
+ const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
1525
+ const maxColor = colorToNumber(sortedColorScalePoints[i].color);
1526
+ thresholds.push({
1527
+ min: sortedColorScalePoints[i - 1].value,
1528
+ max: sortedColorScalePoints[i].value,
1529
+ minColor,
1530
+ maxColor,
1531
+ colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor),
1532
+ });
1533
+ }
1534
+ return (value) => {
1535
+ if (value < thresholds[0].min) {
1536
+ return colorNumberString(thresholds[0].minColor);
1537
+ }
1538
+ for (const threshold of thresholds) {
1539
+ if (value >= threshold.min && value <= threshold.max) {
1540
+ return colorNumberString(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff));
1541
+ }
1542
+ }
1543
+ return colorNumberString(thresholds[thresholds.length - 1].maxColor);
1544
+ };
1545
+ }
1546
+ function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
1547
+ const deltaValue = maxValue - minValue;
1548
+ const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);
1549
+ const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);
1550
+ const deltaColorB = (minColor % 256) - (maxColor % 256);
1551
+ const colorDiffUnitR = deltaColorR / deltaValue;
1552
+ const colorDiffUnitG = deltaColorG / deltaValue;
1553
+ const colorDiffUnitB = deltaColorB / deltaValue;
1554
+ return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];
1555
+ }
1556
+ function colorCell(value, minValue, minColor, colorDiffUnit) {
1557
+ const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
1558
+ const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));
1559
+ const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));
1560
+ const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
1561
+ return (r << 16) | (g << 8) | b;
1562
+ }
1406
1563
 
1407
1564
  //------------------------------------------------------------------------------
1408
1565
  // Coordinate
@@ -3274,6 +3431,7 @@ const invalidateEvaluationCommands = new Set([
3274
3431
  ]);
3275
3432
  const invalidateChartEvaluationCommands = new Set([
3276
3433
  "EVALUATE_CELLS",
3434
+ "EVALUATE_CHARTS",
3277
3435
  "UPDATE_CELL",
3278
3436
  "UNHIDE_COLUMNS_ROWS",
3279
3437
  "HIDE_COLUMNS_ROWS",
@@ -3310,6 +3468,7 @@ const readonlyAllowedCommands = new Set([
3310
3468
  "RESIZE_SHEETVIEW",
3311
3469
  "SET_VIEWPORT_OFFSET",
3312
3470
  "EVALUATE_CELLS",
3471
+ "EVALUATE_CHARTS",
3313
3472
  "SET_FORMULA_VISIBILITY",
3314
3473
  "UPDATE_FILTER",
3315
3474
  ]);
@@ -6514,7 +6673,7 @@ class BorderClipboardHandler extends AbstractCellClipboardHandler {
6514
6673
  const POSTFIX_UNARY_OPERATORS = ["%"];
6515
6674
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
6516
6675
  function tokenize(str, locale = DEFAULT_LOCALE) {
6517
- str = replaceSpecialSpaces(str);
6676
+ str = replaceNewLines(str);
6518
6677
  const chars = new TokenizingChars(str);
6519
6678
  const result = [];
6520
6679
  while (!chars.isOver()) {
@@ -6661,12 +6820,12 @@ function tokenizeSpace(chars) {
6661
6820
  if (length) {
6662
6821
  return { type: "SPACE", value: NEWLINE.repeat(length) };
6663
6822
  }
6664
- while (chars.current === " ") {
6665
- length++;
6666
- chars.shift();
6823
+ let spaces = "";
6824
+ while (chars.current && chars.current.match(whiteSpaceRegexp)) {
6825
+ spaces += chars.shift();
6667
6826
  }
6668
- if (length) {
6669
- return { type: "SPACE", value: " ".repeat(length) };
6827
+ if (spaces) {
6828
+ return { type: "SPACE", value: spaces };
6670
6829
  }
6671
6830
  return null;
6672
6831
  }
@@ -7525,6 +7684,16 @@ function getRunningTotalDomainKey(domain, fieldNameWithGranularity) {
7525
7684
  }
7526
7685
  return domainToString([...domain.slice(0, index), ...domain.slice(index + 1)]);
7527
7686
  }
7687
+ function sortPivotTree(tree, baseDomain, sortFn) {
7688
+ const sortedTree = [...tree];
7689
+ const domain = [...baseDomain];
7690
+ sortedTree.sort((node1, node2) => sortFn([...domain, node1], [...domain, node2]));
7691
+ for (const node of tree) {
7692
+ const children = sortPivotTree(node.children, [...domain, node], sortFn);
7693
+ node.children = children;
7694
+ }
7695
+ return sortedTree;
7696
+ }
7528
7697
 
7529
7698
  const pivotTimeAdapterRegistry = new Registry();
7530
7699
  function pivotTimeAdapter(granularity) {
@@ -8035,6 +8204,29 @@ function addIndentAndAlignToPivotHeader(pivot, domain, functionResult) {
8035
8204
  format: `${" ".repeat(indent)}${format}* `,
8036
8205
  };
8037
8206
  }
8207
+ function isSortedColumnValid(sortedColumn, pivot) {
8208
+ try {
8209
+ if (!pivot.getMeasure(sortedColumn.measure)) {
8210
+ return false;
8211
+ }
8212
+ const columns = pivot.definition.columns;
8213
+ for (let i = 0; i < sortedColumn.domain.length; i++) {
8214
+ if (columns[i].nameWithGranularity !== sortedColumn.domain[i].field) {
8215
+ return false;
8216
+ }
8217
+ const possibleValues = pivot
8218
+ .getPossibleFieldValues(columns[i])
8219
+ .map((v) => v.value);
8220
+ if (!possibleValues.includes(sortedColumn.domain[i].value)) {
8221
+ return false;
8222
+ }
8223
+ }
8224
+ return true;
8225
+ }
8226
+ catch (e) {
8227
+ return false;
8228
+ }
8229
+ }
8038
8230
 
8039
8231
  class CellClipboardHandler extends AbstractCellClipboardHandler {
8040
8232
  isCutAllowed(data) {
@@ -9514,6 +9706,12 @@ function chartFontColor(backgroundColor) {
9514
9706
  }
9515
9707
  return relativeLuminance(backgroundColor) < 0.3 ? "#FFFFFF" : "#000000";
9516
9708
  }
9709
+ function chartMutedFontColor(backgroundColor) {
9710
+ if (!backgroundColor) {
9711
+ return "#666666";
9712
+ }
9713
+ return relativeLuminance(backgroundColor) < 0.3 ? "#C8C8C8" : "#666666";
9714
+ }
9517
9715
  function checkDataset(definition) {
9518
9716
  if (definition.dataSets) {
9519
9717
  const invalidRanges = definition.dataSets.find((range) => !rangeReference.test(range.dataRange)) !== undefined;
@@ -9649,8 +9847,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
9649
9847
  const yMin = chart.chartArea.top;
9650
9848
  const textsPositions = {};
9651
9849
  for (const dataset of chart._metasets) {
9652
- if (dataset.xAxisID === TREND_LINE_XAXIS_ID) {
9653
- return; // ignore trend lines
9850
+ if (dataset.xAxisID === TREND_LINE_XAXIS_ID || dataset.hidden) {
9851
+ continue;
9654
9852
  }
9655
9853
  for (let i = 0; i < dataset._parsed.length; i++) {
9656
9854
  const parsedValue = dataset._parsed[i];
@@ -10097,7 +10295,7 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
10097
10295
  function drawScoreChart(structure, canvas) {
10098
10296
  const ctx = canvas.getContext("2d");
10099
10297
  canvas.width = structure.canvas.width;
10100
- const availableWidth = canvas.width - DEFAULT_CHART_PADDING;
10298
+ const availableWidth = canvas.width - CHART_PADDING$1 * 2;
10101
10299
  canvas.height = structure.canvas.height;
10102
10300
  ctx.fillStyle = structure.canvas.backgroundColor;
10103
10301
  ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
@@ -10232,10 +10430,9 @@ function createScorecardChartRuntime(chart, getters) {
10232
10430
  }
10233
10431
 
10234
10432
  /* Padding at the border of the chart */
10235
- const CHART_PADDING = SCORECARD_GAUGE_CHART_PADDING;
10433
+ const CHART_PADDING = 10;
10236
10434
  const BOTTOM_PADDING_RATIO = 0.05;
10237
10435
  /* Maximum font sizes of each element */
10238
- const CHART_TITLE_FONT_SIZE = SCORECARD_GAUGE_CHART_FONT_SIZE;
10239
10436
  const KEY_VALUE_FONT_SIZE = 32;
10240
10437
  const BASELINE_MAX_FONT_SIZE = 16;
10241
10438
  function formatBaselineDescr(baselineDescr, baseline) {
@@ -10307,7 +10504,7 @@ class ScorecardChartConfigBuilder {
10307
10504
  : this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING,
10308
10505
  },
10309
10506
  };
10310
- const minimalBaselinePosition = baselineArrowSize + DEFAULT_CHART_PADDING;
10507
+ const minimalBaselinePosition = baselineArrowSize + CHART_PADDING * 2;
10311
10508
  if (structure.baseline.position.x < minimalBaselinePosition) {
10312
10509
  structure.baseline.position.x = minimalBaselinePosition;
10313
10510
  }
@@ -10387,7 +10584,7 @@ class ScorecardChartConfigBuilder {
10387
10584
  return this.runtime.background;
10388
10585
  }
10389
10586
  get secondaryFontColor() {
10390
- return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
10587
+ return chartMutedFontColor(this.backgroundColor);
10391
10588
  }
10392
10589
  getTextDimensions(text, font) {
10393
10590
  this.context.font = font;
@@ -10413,7 +10610,7 @@ class ScorecardChartConfigBuilder {
10413
10610
  }
10414
10611
  return {
10415
10612
  title: {
10416
- font: getDefaultContextFont(CHART_TITLE_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10613
+ font: getDefaultContextFont(this.runtime.title.fontSize ?? SCORECARD_CHART_TITLE_FONT_SIZE, this.runtime.title.bold, this.runtime.title.italic),
10417
10614
  color: this.runtime.title.color ?? this.secondaryFontColor,
10418
10615
  },
10419
10616
  keyValue: {
@@ -14638,7 +14835,7 @@ const SORT_TYPES = [
14638
14835
  CellValueType.boolean,
14639
14836
  ];
14640
14837
  function cellsSortingCriterion(sortingOrder) {
14641
- const inverse = sortingOrder === "ascending" ? 1 : -1;
14838
+ const inverse = sortingOrder === "asc" ? 1 : -1;
14642
14839
  return (left, right) => {
14643
14840
  if (left.type === CellValueType.empty) {
14644
14841
  return right.type === CellValueType.empty ? 0 : 1;
@@ -14730,7 +14927,7 @@ function sortMatrix(matrix, locale, ...criteria) {
14730
14927
  const sortColumns = [];
14731
14928
  const nRows = matrix.length;
14732
14929
  for (let i = 0; i < criteria.length; i += 2) {
14733
- sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "ascending" : "descending");
14930
+ sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "asc" : "desc");
14734
14931
  const sortColumn = criteria[i];
14735
14932
  if (isMatrix(sortColumn) && (sortColumn.length > 1 || sortColumn[0].length > 1)) {
14736
14933
  assert(() => sortColumn.length === 1 && sortColumn[0].length === nRows, _t("Wrong size for %s. Expected a range of size 1x%s. Got %sx%s.", `sort_column${i + 1}`, nRows, sortColumn.length, sortColumn[0].length));
@@ -14747,12 +14944,12 @@ function sortMatrix(matrix, locale, ...criteria) {
14747
14944
  if (sortColumns.length === 0) {
14748
14945
  for (let i = 0; i < matrix[0].length; i++) {
14749
14946
  sortColumns.push(matrix.map((row) => row[i].value));
14750
- sortingOrders.push("ascending");
14947
+ sortingOrders.push("asc");
14751
14948
  }
14752
14949
  }
14753
14950
  const sortingCriteria = {
14754
- descending: cellsSortingCriterion("descending"),
14755
- ascending: cellsSortingCriterion("ascending"),
14951
+ desc: cellsSortingCriterion("desc"),
14952
+ asc: cellsSortingCriterion("asc"),
14756
14953
  };
14757
14954
  const indexes = range(0, matrix.length);
14758
14955
  indexes.sort((a, b) => {
@@ -20476,20 +20673,8 @@ class AbstractComposerStore extends SpreadsheetStore {
20476
20673
  replaceSelectedRange(zone) {
20477
20674
  const ref = this.getZoneReference(zone);
20478
20675
  const currentToken = this.tokenAtCursor;
20479
- let replaceStart = this.selectionStart;
20480
- if (currentToken?.type === "REFERENCE") {
20481
- replaceStart = currentToken.start;
20482
- }
20483
- else if (currentToken?.type === "RIGHT_PAREN") {
20484
- // match left parenthesis
20485
- const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenthesesCode === currentToken.parenthesesCode);
20486
- const functionToken = this.currentTokens[leftParenthesisIndex - 1];
20487
- if (functionToken === undefined) {
20488
- return;
20489
- }
20490
- replaceStart = functionToken.start;
20491
- }
20492
- this.replaceText(ref, replaceStart, this.selectionEnd);
20676
+ const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
20677
+ this.replaceText(ref, start, this.selectionEnd);
20493
20678
  }
20494
20679
  /**
20495
20680
  * Replace the reference of the old zone by the new one.
@@ -20522,17 +20707,6 @@ class AbstractComposerStore extends SpreadsheetStore {
20522
20707
  getZoneReference(zone) {
20523
20708
  const inputSheetId = this.sheetId;
20524
20709
  const sheetId = this.getters.getActiveSheetId();
20525
- if (zone.top === zone.bottom && zone.left === zone.right) {
20526
- const position = { sheetId, col: zone.left, row: zone.top };
20527
- const pivotId = this.getters.getPivotIdFromPosition(position);
20528
- const pivotCell = this.getters.getPivotCellFromPosition(position);
20529
- const cell = this.getters.getCell(position);
20530
- if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
20531
- const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
20532
- const formula = createPivotFormula(formulaPivotId, pivotCell);
20533
- return localizeFormula(formula, this.getters.getLocale()).slice(1); // strip leading =
20534
- }
20535
- }
20536
20710
  const range = this.getters.getRangeFromZone(sheetId, zone);
20537
20711
  return this.getters.getSelectionRangeString(range, inputSheetId);
20538
20712
  }
@@ -20703,37 +20877,21 @@ class AbstractComposerStore extends SpreadsheetStore {
20703
20877
  const editionSheetId = this.sheetId;
20704
20878
  const rangeColor = (rangeString) => {
20705
20879
  const colorIndex = this.colorIndexByRange[rangeString];
20706
- return colors$1[colorIndex % colors$1.length];
20880
+ return colors[colorIndex % colors.length];
20707
20881
  };
20708
- const highlights = [];
20709
- for (const range of this.getReferencedRanges()) {
20882
+ return this.getReferencedRanges().map((range) => {
20710
20883
  const rangeString = this.getters.getRangeString(range, editionSheetId);
20711
20884
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
20712
20885
  const zone = numberOfRows * numberOfCols === 1
20713
20886
  ? this.getters.expandZone(range.sheetId, range.zone)
20714
20887
  : range.zone;
20715
- highlights.push({
20888
+ return {
20716
20889
  zone,
20717
20890
  color: rangeColor(rangeString),
20718
20891
  sheetId: range.sheetId,
20719
20892
  interactive: true,
20720
- });
20721
- }
20722
- const activeSheetId = this.getters.getActiveSheetId();
20723
- const selectionZone = this.model.selection.getAnchor().zone;
20724
- const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
20725
- if (this.editionMode === "selecting" && !isSelectionHightlighted) {
20726
- highlights.push({
20727
- zone: selectionZone,
20728
- color: "#445566",
20729
- sheetId: activeSheetId,
20730
- dashed: true,
20731
- interactive: false,
20732
- noFill: true,
20733
- thinLine: true,
20734
- });
20735
- }
20736
- return highlights;
20893
+ };
20894
+ });
20737
20895
  }
20738
20896
  /**
20739
20897
  * Return ranges currently referenced in the composer
@@ -20961,6 +21119,7 @@ function compileTokens(tokens) {
20961
21119
  return error;
20962
21120
  },
20963
21121
  isBadExpression: true,
21122
+ normalizedFormula: tokens.map((t) => t.value).join(""),
20964
21123
  };
20965
21124
  }
20966
21125
  }
@@ -21088,6 +21247,7 @@ function compileTokensOrThrow(tokens) {
21088
21247
  symbols,
21089
21248
  tokens,
21090
21249
  isBadExpression: false,
21250
+ normalizedFormula: cacheKey,
21091
21251
  };
21092
21252
  return compiledFormula;
21093
21253
  }
@@ -21234,7 +21394,7 @@ function getFunctionsFromAST(ast, functionNames) {
21234
21394
 
21235
21395
  const PIVOT_FUNCTIONS = ["PIVOT.VALUE", "PIVOT.HEADER", "PIVOT"];
21236
21396
  /**
21237
- * Create a proposal entry for the compose autowcomplete
21397
+ * Create a proposal entry for the composer autocomplete
21238
21398
  * to insert a field name string in a formula.
21239
21399
  */
21240
21400
  function makeFieldProposal(field, granularity) {
@@ -21352,6 +21512,7 @@ autoCompleteProviders.add("pivot_ids", {
21352
21512
  description: definition.name,
21353
21513
  htmlContent: [{ value: str, color: tokenColors.NUMBER }],
21354
21514
  fuzzySearchKey: str + definition.name,
21515
+ alwaysExpanded: true,
21355
21516
  };
21356
21517
  })
21357
21518
  .filter(isDefined);
@@ -22017,14 +22178,8 @@ const GAUGE_PADDING_BOTTOM = 20;
22017
22178
  const GAUGE_LABELS_FONT_SIZE = 12;
22018
22179
  const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
22019
22180
  const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
22020
- const GAUGE_TEXT_COLOR = "#666666";
22021
- const GAUGE_TEXT_COLOR_HIGH_CONTRAST = "#C8C8C8";
22022
- const GAUGE_INFLECTION_MARKER_COLOR = "#666666aa";
22023
22181
  const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
22024
22182
  const GAUGE_TITLE_SECTION_HEIGHT = 25;
22025
- const GAUGE_TITLE_FONT_SIZE = SCORECARD_GAUGE_CHART_FONT_SIZE;
22026
- const GAUGE_TITLE_PADDING_LEFT = SCORECARD_GAUGE_CHART_PADDING;
22027
- const GAUGE_TITLE_PADDING_TOP = SCORECARD_GAUGE_CHART_PADDING;
22028
22183
  function drawGaugeChart(canvas, runtime) {
22029
22184
  const canvasBoundingRect = canvas.getBoundingClientRect();
22030
22185
  canvas.width = canvasBoundingRect.width;
@@ -22083,7 +22238,7 @@ function drawInflectionValues(ctx, config) {
22083
22238
  ctx.translate(rectX + width / 2 - 0.5, rectY + height - 0.5); // -0.5 for sharper lines. see RendererPlugin.drawBorders comment
22084
22239
  ctx.rotate(Math.PI / 2 - inflectionValue.rotation);
22085
22240
  ctx.lineWidth = 2;
22086
- ctx.strokeStyle = GAUGE_INFLECTION_MARKER_COLOR;
22241
+ ctx.strokeStyle = chartMutedFontColor(config.backgroundColor) + "aa";
22087
22242
  ctx.beginPath();
22088
22243
  ctx.moveTo(0, -(height - config.gauge.arcWidth));
22089
22244
  ctx.lineTo(0, -height - 3);
@@ -22137,22 +22292,22 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22137
22292
  x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
22138
22293
  y: gaugeRect.y + gaugeRect.height + GAUGE_LABELS_FONT_SIZE,
22139
22294
  };
22140
- const textColor = getContrastedTextColor(runtime.background);
22295
+ const textColor = chartMutedFontColor(runtime.background);
22141
22296
  const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
22142
22297
  let x = 0, titleWidth = 0, titleHeight = 0;
22143
22298
  if (runtime.title.text) {
22144
- ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { ...runtime.title, fontSize: GAUGE_TITLE_FONT_SIZE }, "px"));
22299
+ ({ width: titleWidth, height: titleHeight } = computeTextDimension(ctx, runtime.title.text, { fontSize: CHART_TITLE_FONT_SIZE, ...runtime.title }, "px"));
22145
22300
  }
22146
22301
  switch (runtime.title.align) {
22147
22302
  case "right":
22148
- x = boundingRect.width - titleWidth - GAUGE_TITLE_PADDING_LEFT;
22303
+ x = boundingRect.width - titleWidth - CHART_PADDING$1;
22149
22304
  break;
22150
22305
  case "center":
22151
22306
  x = (boundingRect.width - titleWidth) / 2;
22152
22307
  break;
22153
22308
  case "left":
22154
22309
  default:
22155
- x = GAUGE_TITLE_PADDING_LEFT;
22310
+ x = CHART_PADDING$1;
22156
22311
  break;
22157
22312
  }
22158
22313
  return {
@@ -22160,10 +22315,10 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
22160
22315
  height: boundingRect.height,
22161
22316
  title: {
22162
22317
  label: runtime.title.text ?? "",
22163
- fontSize: GAUGE_TITLE_FONT_SIZE,
22318
+ fontSize: runtime.title.fontSize ?? CHART_TITLE_FONT_SIZE,
22164
22319
  textPosition: {
22165
22320
  x,
22166
- y: GAUGE_TITLE_PADDING_TOP + titleHeight / 2,
22321
+ y: CHART_PADDING_TOP + titleHeight / 2,
22167
22322
  },
22168
22323
  color: runtime.title.color ?? textColor,
22169
22324
  bold: runtime.title.bold,
@@ -22280,11 +22435,6 @@ function getGaugeColor(runtime) {
22280
22435
  }
22281
22436
  return runtime.colors.at(-1);
22282
22437
  }
22283
- function getContrastedTextColor(backgroundColor) {
22284
- return relativeLuminance(backgroundColor) > 0.3
22285
- ? GAUGE_TEXT_COLOR
22286
- : GAUGE_TEXT_COLOR_HIGH_CONTRAST;
22287
- }
22288
22438
  function getSegmentsOfRectangle(rectangle) {
22289
22439
  return [
22290
22440
  { start: rectangle.topLeft, end: rectangle.topRight },
@@ -26998,13 +27148,12 @@ migrationStepRegistry
26998
27148
  versionFrom: "7",
26999
27149
  migrate(data) {
27000
27150
  const namesTaken = [];
27001
- const globalForbiddenInExcel = new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g");
27002
27151
  for (let sheet of data.sheets || []) {
27003
27152
  if (!sheet.name) {
27004
27153
  continue;
27005
27154
  }
27006
27155
  const oldName = sheet.name;
27007
- const escapedName = oldName.replace(globalForbiddenInExcel, "_");
27156
+ const escapedName = sanitizeSheetName(oldName, "_");
27008
27157
  let i = 1;
27009
27158
  let newName = escapedName;
27010
27159
  while (namesTaken.includes(newName)) {
@@ -27757,6 +27906,19 @@ const ChartTerms = {
27757
27906
  ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
27758
27907
  ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
27759
27908
  },
27909
+ GeoChart: {
27910
+ ColorScales: {
27911
+ blues: _t("Blues"),
27912
+ cividis: _t("Cividis"),
27913
+ greens: _t("Greens"),
27914
+ greys: _t("Greys"),
27915
+ oranges: _t("Oranges"),
27916
+ purples: _t("Purples"),
27917
+ rainbow: _t("Rainbow"),
27918
+ reds: _t("Reds"),
27919
+ viridis: _t("Viridis"),
27920
+ },
27921
+ },
27760
27922
  };
27761
27923
  const CustomCurrencyTerms = {
27762
27924
  Custom: _t("Custom"),
@@ -28160,6 +28322,26 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
28160
28322
  locale: getters.getLocale(),
28161
28323
  };
28162
28324
  }
28325
+ function getGeoChartData(definition, dataSets, labelRange, getters) {
28326
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28327
+ let labels = labelValues.formattedValues;
28328
+ if (definition.dataSetsHaveTitle) {
28329
+ labels.shift();
28330
+ }
28331
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
28332
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28333
+ const format = getChartDatasetFormat(getters, dataSets, "left") ||
28334
+ getChartDatasetFormat(getters, dataSets, "right");
28335
+ return {
28336
+ dataSetsValues,
28337
+ axisFormats: { y: format },
28338
+ labels,
28339
+ locale: getters.getLocale(),
28340
+ availableRegions: getters.getGeoChartAvailableRegions(),
28341
+ geoFeatureNameToId: getters.geoFeatureNameToId,
28342
+ getGeoJsonFeatures: getters.getGeoJsonFeatures,
28343
+ };
28344
+ }
28163
28345
  function getTrendDatasetForBarChart(config, data) {
28164
28346
  const filteredValues = [];
28165
28347
  const filteredLabels = [];
@@ -28240,37 +28422,45 @@ function interpolateData(config, values, labels, newLabels) {
28240
28422
  const labelRange = labelMax - labelMin;
28241
28423
  const normalizedLabels = labels.map((v) => (v - labelMin) / labelRange);
28242
28424
  const normalizedNewLabels = newLabels.map((v) => (v - labelMin) / labelRange);
28243
- switch (config.type) {
28244
- case "polynomial": {
28245
- const order = config.order ?? 2;
28246
- if (order === 1) {
28247
- return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
28248
- }
28249
- const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
28250
- return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
28251
- }
28252
- case "exponential": {
28253
- const positiveLogValues = [];
28254
- const filteredLabels = [];
28255
- for (let i = 0; i < values.length; i++) {
28256
- if (values[i] > 0) {
28257
- positiveLogValues.push(Math.log(values[i]));
28258
- filteredLabels.push(normalizedLabels[i]);
28425
+ try {
28426
+ switch (config.type) {
28427
+ case "polynomial": {
28428
+ const order = config.order;
28429
+ if (!order) {
28430
+ return Array.from({ length: newLabels.length }, () => NaN);
28431
+ }
28432
+ if (order === 1) {
28433
+ return predictLinearValues([values], [normalizedLabels], [normalizedNewLabels], true)[0];
28434
+ }
28435
+ const coeffs = polynomialRegression(values, normalizedLabels, order, true).flat();
28436
+ return normalizedNewLabels.map((v) => evaluatePolynomial(coeffs, v, order));
28437
+ }
28438
+ case "exponential": {
28439
+ const positiveLogValues = [];
28440
+ const filteredLabels = [];
28441
+ for (let i = 0; i < values.length; i++) {
28442
+ if (values[i] > 0) {
28443
+ positiveLogValues.push(Math.log(values[i]));
28444
+ filteredLabels.push(normalizedLabels[i]);
28445
+ }
28446
+ }
28447
+ if (!filteredLabels.length) {
28448
+ return Array.from({ length: newLabels.length }, () => NaN);
28259
28449
  }
28450
+ return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
28260
28451
  }
28261
- if (!filteredLabels.length) {
28262
- return [];
28452
+ case "logarithmic": {
28453
+ return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
28263
28454
  }
28264
- return expM(predictLinearValues([positiveLogValues], [filteredLabels], [normalizedNewLabels], true))[0];
28265
- }
28266
- case "logarithmic": {
28267
- return predictLinearValues([values], logM([normalizedLabels]), logM([normalizedNewLabels]), true)[0];
28268
- }
28269
- case "trailingMovingAverage": {
28270
- return getMovingAverageValues(values, config.window);
28455
+ case "trailingMovingAverage": {
28456
+ return getMovingAverageValues(values, config.window);
28457
+ }
28458
+ default:
28459
+ return Array.from({ length: newLabels.length }, () => NaN);
28271
28460
  }
28272
- default:
28273
- return [];
28461
+ }
28462
+ catch (e) {
28463
+ return Array.from({ length: newLabels.length }, () => NaN);
28274
28464
  }
28275
28465
  }
28276
28466
  function getChartAxisType(chart, labelRange, getters) {
@@ -28704,6 +28894,43 @@ function getRadarChartDatasets(definition, args) {
28704
28894
  }
28705
28895
  return datasets;
28706
28896
  }
28897
+ function getGeoChartDatasets(definition, args) {
28898
+ const { availableRegions, dataSetsValues, labels } = args;
28899
+ const regionName = definition.region || availableRegions[0]?.id;
28900
+ const features = regionName ? args.getGeoJsonFeatures(regionName) : undefined;
28901
+ const dataset = {
28902
+ outline: features,
28903
+ showOutline: !!features,
28904
+ data: [],
28905
+ };
28906
+ if (features && regionName) {
28907
+ const labelsAndValues = {};
28908
+ if (dataSetsValues[0]) {
28909
+ for (let i = 0; i < dataSetsValues[0].data.length; i++) {
28910
+ if (!labels[i] || dataSetsValues[0].data[i] === undefined) {
28911
+ continue;
28912
+ }
28913
+ const featureId = args.geoFeatureNameToId(regionName, labels[i]);
28914
+ if (featureId) {
28915
+ labelsAndValues[featureId] = { value: dataSetsValues[0].data[i], label: labels[i] };
28916
+ }
28917
+ }
28918
+ }
28919
+ for (const feature of features) {
28920
+ if (!feature.id) {
28921
+ continue;
28922
+ }
28923
+ dataset.data.push({
28924
+ feature: {
28925
+ ...feature,
28926
+ properties: { name: labelsAndValues[feature.id]?.label },
28927
+ },
28928
+ value: labelsAndValues[feature.id]?.value,
28929
+ });
28930
+ }
28931
+ }
28932
+ return [dataset];
28933
+ }
28707
28934
  function getTrendingLineDataSet(dataset, config, data) {
28708
28935
  const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
28709
28936
  defaultBorderColor.a = 1;
@@ -28741,54 +28968,16 @@ function getChartColorsGenerator(definition, dataSetsSize) {
28741
28968
  return new ColorGenerator(dataSetsSize, definition.dataSets?.map((ds) => ds.backgroundColor) || []);
28742
28969
  }
28743
28970
 
28744
- function getCommonChartLayout(definition) {
28745
- // TODO FIXME: this is unused ATM. All the charts should probably use this instead oh whatever padding they are using now
28746
- // also look into how DEFAULT_CHART_PADDING is used in scorecards, it look strange
28971
+ function getChartLayout(definition) {
28747
28972
  return {
28748
28973
  padding: {
28749
- left: DEFAULT_CHART_PADDING,
28750
- right: DEFAULT_CHART_PADDING,
28751
- top: definition.title?.text ? DEFAULT_CHART_PADDING / 2 : DEFAULT_CHART_PADDING + 5,
28752
- bottom: DEFAULT_CHART_PADDING,
28974
+ left: CHART_PADDING$1,
28975
+ right: CHART_PADDING$1,
28976
+ top: CHART_PADDING_TOP,
28977
+ bottom: CHART_PADDING_BOTTOM,
28753
28978
  },
28754
28979
  };
28755
28980
  }
28756
- function getBarChartLayout(definition) {
28757
- return {
28758
- padding: computeChartPadding({
28759
- displayTitle: !!definition.title?.text,
28760
- displayLegend: definition.legendPosition === "top",
28761
- }),
28762
- };
28763
- }
28764
- function getLineChartLayout(definition) {
28765
- return {
28766
- padding: computeChartPadding({
28767
- displayTitle: !!definition.title?.text,
28768
- displayLegend: definition.legendPosition === "top",
28769
- }),
28770
- };
28771
- }
28772
- function getPieChartLayout(definition) {
28773
- return {
28774
- padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28775
- };
28776
- }
28777
- function getWaterfallChartLayout(definition) {
28778
- return {
28779
- padding: { left: 20, right: 20, top: definition.title ? 10 : 25, bottom: 10 },
28780
- };
28781
- }
28782
- function computeChartPadding({ displayTitle, displayLegend, }) {
28783
- let top = 25;
28784
- if (displayTitle) {
28785
- top = 0;
28786
- }
28787
- else if (displayLegend) {
28788
- top = 10;
28789
- }
28790
- return { left: 20, right: 20, top, bottom: 10 };
28791
- }
28792
28981
 
28793
28982
  function getLegendDisplayOptions(definition, args) {
28794
28983
  return {
@@ -28846,11 +29035,12 @@ function getScatterChartLegend(definition, args) {
28846
29035
  return {
28847
29036
  ...INTERACTIVE_LEGEND_CONFIG,
28848
29037
  ...getLegendDisplayOptions(definition),
28849
- labels: {
28850
- color: chartFontColor(definition.background),
28851
- boxHeight: 6,
28852
- usePointStyle: true,
28853
- },
29038
+ ...getCustomLegendLabels(chartFontColor(definition.background), {
29039
+ pointStyle: "circle",
29040
+ // the stroke is the border around the circle, so increasing its size with the chart's color reduce the size of the circle
29041
+ strokeStyle: definition.background || "#ffffff",
29042
+ lineWidth: 8,
29043
+ }),
28854
29044
  };
28855
29045
  }
28856
29046
  function getComboChartLegend(definition, args) {
@@ -28939,10 +29129,10 @@ const INTERACTIVE_LEGEND_CONFIG = {
28939
29129
  target.style.cursor = "default";
28940
29130
  },
28941
29131
  onClick: (event, legendItem, legend) => {
28942
- if (!legend.legendItems) {
29132
+ const index = legendItem.datasetIndex;
29133
+ if (!legend.legendItems || index === undefined) {
28943
29134
  return;
28944
29135
  }
28945
- const index = legend.legendItems.indexOf(legendItem);
28946
29136
  if (legend.chart.isDatasetVisible(index)) {
28947
29137
  legend.chart.hide(index);
28948
29138
  }
@@ -28958,15 +29148,29 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
28958
29148
  labels: {
28959
29149
  color: fontColor,
28960
29150
  usePointStyle: true,
28961
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => ({
28962
- text: dataset.label ?? "",
28963
- fontColor,
28964
- strokeStyle: dataset.borderColor,
28965
- fillStyle: dataset.backgroundColor,
28966
- hidden: !chart.isDatasetVisible(index),
28967
- pointStyle: dataset.type === "line" ? "line" : "rect",
28968
- ...legendLabelConfig,
28969
- })),
29151
+ generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
29152
+ if (dataset["xAxisID"] === TREND_LINE_XAXIS_ID) {
29153
+ return {
29154
+ text: dataset.label ?? "",
29155
+ fontColor,
29156
+ strokeStyle: dataset.borderColor,
29157
+ hidden: !chart.isDatasetVisible(index),
29158
+ pointStyle: "line",
29159
+ datasetIndex: index,
29160
+ lineWidth: 3,
29161
+ };
29162
+ }
29163
+ return {
29164
+ text: dataset.label ?? "",
29165
+ fontColor,
29166
+ strokeStyle: dataset.borderColor,
29167
+ fillStyle: dataset.backgroundColor,
29168
+ hidden: !chart.isDatasetVisible(index),
29169
+ pointStyle: dataset.type === "line" ? "line" : "rect",
29170
+ datasetIndex: index,
29171
+ ...legendLabelConfig,
29172
+ };
29173
+ }),
28970
29174
  },
28971
29175
  };
28972
29176
  }
@@ -29104,6 +29308,44 @@ function getRadarChartScales(definition, args) {
29104
29308
  },
29105
29309
  };
29106
29310
  }
29311
+ function getGeoChartScales(definition, args) {
29312
+ const { locale, axisFormats, availableRegions } = args;
29313
+ const geoLegendPosition = legendPositionToGeoLegendPosition(definition.legendPosition);
29314
+ const region = definition.region
29315
+ ? availableRegions.find((r) => r.id === definition.region)
29316
+ : availableRegions[0];
29317
+ const format = axisFormats?.y || axisFormats?.y1;
29318
+ return {
29319
+ projection: {
29320
+ // projection: region?.defaultProjection,
29321
+ projection: getGeoChartProjection(region?.defaultProjection || "mercator"),
29322
+ axis: "x",
29323
+ },
29324
+ color: {
29325
+ axis: "x",
29326
+ display: definition.legendPosition !== "none",
29327
+ border: { color: GRAY_300 },
29328
+ grid: { color: GRAY_300 },
29329
+ ticks: {
29330
+ color: chartFontColor(definition.background),
29331
+ callback: formatTickValue({ locale, format }),
29332
+ },
29333
+ legend: {
29334
+ position: geoLegendPosition,
29335
+ align: geoLegendPosition.includes("right") ? "left" : "right",
29336
+ margin: getLegendMargin(definition),
29337
+ },
29338
+ interpolate: getRuntimeColorScale(definition),
29339
+ missing: definition.missingValueColor || "#ffffff",
29340
+ },
29341
+ };
29342
+ }
29343
+ function getGeoChartProjection(projection) {
29344
+ if (projection === "conicConformal") {
29345
+ return window.ChartGeo.geoConicConformal().rotate([100, 0]); // Centered on the US
29346
+ }
29347
+ return projection;
29348
+ }
29107
29349
  function getChartAxisTitleRuntime(design) {
29108
29350
  if (design?.title?.text) {
29109
29351
  const { text, color, align, italic, bold } = design.title;
@@ -29114,6 +29356,7 @@ function getChartAxisTitleRuntime(design) {
29114
29356
  font: {
29115
29357
  style: italic ? "italic" : "normal",
29116
29358
  weight: bold ? "bold" : "normal",
29359
+ size: design.title.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE,
29117
29360
  },
29118
29361
  align: align === "left" ? "start" : align === "right" ? "end" : "center",
29119
29362
  };
@@ -29166,6 +29409,44 @@ function getChartAxis(definition, position, type, options) {
29166
29409
  };
29167
29410
  }
29168
29411
  }
29412
+ function getRuntimeColorScale(definition) {
29413
+ if (!definition.colorScale || typeof definition.colorScale === "string") {
29414
+ return definition.colorScale || "oranges";
29415
+ }
29416
+ const scaleColors = [{ value: 0, color: definition.colorScale.minColor }];
29417
+ if (definition.colorScale.midColor) {
29418
+ scaleColors.push({ value: 0.5, color: definition.colorScale.midColor });
29419
+ }
29420
+ scaleColors.push({ value: 1, color: definition.colorScale.maxColor });
29421
+ return getColorScale(scaleColors);
29422
+ }
29423
+ function getLegendMargin(definition) {
29424
+ switch (definition.legendPosition) {
29425
+ case "top":
29426
+ case "right":
29427
+ const hasTitle = !!definition.title.text;
29428
+ const topMargin = hasTitle ? CHART_PADDING_TOP + 30 : CHART_PADDING_TOP;
29429
+ return { top: topMargin, left: CHART_PADDING$1, right: CHART_PADDING$1 };
29430
+ case "bottom":
29431
+ case "left":
29432
+ case "none":
29433
+ return { left: CHART_PADDING$1, right: CHART_PADDING$1, bottom: CHART_PADDING_BOTTOM };
29434
+ }
29435
+ }
29436
+ function legendPositionToGeoLegendPosition(position) {
29437
+ switch (position) {
29438
+ case "top":
29439
+ return "top-left";
29440
+ case "right":
29441
+ return "top-right";
29442
+ case "bottom":
29443
+ return "bottom-right";
29444
+ case "left":
29445
+ return "bottom-left";
29446
+ case "none":
29447
+ return "bottom-left";
29448
+ }
29449
+ }
29169
29450
 
29170
29451
  function getChartShowValues(definition, args) {
29171
29452
  const { axisFormats, locale } = args;
@@ -29179,17 +29460,22 @@ function getChartShowValues(definition, args) {
29179
29460
 
29180
29461
  function getChartTitle(definition) {
29181
29462
  const chartTitle = definition.title;
29182
- const fontColor = chartFontColor(definition.background);
29463
+ const fontColor = chartMutedFontColor(definition.background);
29183
29464
  return {
29184
29465
  display: !!chartTitle.text,
29185
29466
  text: _t(chartTitle.text),
29186
29467
  color: chartTitle?.color ?? fontColor,
29187
29468
  align: chartTitle.align === "center" ? "center" : chartTitle.align === "right" ? "end" : "start",
29188
29469
  font: {
29189
- size: DEFAULT_CHART_FONT_SIZE,
29470
+ size: definition.title.fontSize ?? CHART_TITLE_FONT_SIZE,
29190
29471
  weight: chartTitle.bold ? "bold" : "normal",
29191
29472
  style: chartTitle.italic ? "italic" : "normal",
29192
29473
  },
29474
+ padding: {
29475
+ // Disable title top/left/right padding to use the chart padding instead.
29476
+ // The legend already has a top padding, so bottom padding is useless for the title there.
29477
+ bottom: definition.legendPosition === "top" ? 0 : CHART_PADDING$1,
29478
+ },
29193
29479
  };
29194
29480
  }
29195
29481
 
@@ -29320,6 +29606,25 @@ function getRadarChartTooltip(definition, args) {
29320
29606
  },
29321
29607
  };
29322
29608
  }
29609
+ function getGeoChartTooltip(definition, args) {
29610
+ const { locale, axisFormats } = args;
29611
+ const format = axisFormats?.y || axisFormats?.y1;
29612
+ return {
29613
+ filter: function (tooltipItem) {
29614
+ return tooltipItem.raw.value !== undefined;
29615
+ },
29616
+ callbacks: {
29617
+ label: function (tooltipItem) {
29618
+ const rawItem = tooltipItem.raw;
29619
+ const xLabel = rawItem.feature.properties.name;
29620
+ const yLabel = rawItem.value;
29621
+ const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
29622
+ const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29623
+ return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29624
+ },
29625
+ },
29626
+ };
29627
+ }
29323
29628
  function calculatePercentage(dataset, dataIndex) {
29324
29629
  const numericData = dataset.filter((value) => typeof value === "number");
29325
29630
  const total = numericData.reduce((sum, value) => sum + value, 0);
@@ -29336,26 +29641,27 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29336
29641
  canChartParseLabels: canChartParseLabels,
29337
29642
  getBarChartData: getBarChartData,
29338
29643
  getBarChartDatasets: getBarChartDatasets,
29339
- getBarChartLayout: getBarChartLayout,
29340
29644
  getBarChartLegend: getBarChartLegend,
29341
29645
  getBarChartScales: getBarChartScales,
29342
29646
  getBarChartTooltip: getBarChartTooltip,
29343
29647
  getChartLabelFormat: getChartLabelFormat,
29648
+ getChartLayout: getChartLayout,
29344
29649
  getChartShowValues: getChartShowValues,
29345
29650
  getChartTitle: getChartTitle,
29346
29651
  getComboChartDatasets: getComboChartDatasets,
29347
29652
  getComboChartLegend: getComboChartLegend,
29348
- getCommonChartLayout: getCommonChartLayout,
29349
29653
  getData: getData,
29654
+ getGeoChartData: getGeoChartData,
29655
+ getGeoChartDatasets: getGeoChartDatasets,
29656
+ getGeoChartScales: getGeoChartScales,
29657
+ getGeoChartTooltip: getGeoChartTooltip,
29350
29658
  getLineChartData: getLineChartData,
29351
29659
  getLineChartDatasets: getLineChartDatasets,
29352
- getLineChartLayout: getLineChartLayout,
29353
29660
  getLineChartLegend: getLineChartLegend,
29354
29661
  getLineChartScales: getLineChartScales,
29355
29662
  getLineChartTooltip: getLineChartTooltip,
29356
29663
  getPieChartData: getPieChartData,
29357
29664
  getPieChartDatasets: getPieChartDatasets,
29358
- getPieChartLayout: getPieChartLayout,
29359
29665
  getPieChartLegend: getPieChartLegend,
29360
29666
  getPieChartTooltip: getPieChartTooltip,
29361
29667
  getPyramidChartData: getPyramidChartData,
@@ -29371,7 +29677,6 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29371
29677
  getScatterChartScales: getScatterChartScales,
29372
29678
  getTrendDatasetForBarChart: getTrendDatasetForBarChart,
29373
29679
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
29374
- getWaterfallChartLayout: getWaterfallChartLayout,
29375
29680
  getWaterfallChartLegend: getWaterfallChartLegend,
29376
29681
  getWaterfallChartScales: getWaterfallChartScales,
29377
29682
  getWaterfallChartTooltip: getWaterfallChartTooltip,
@@ -29519,7 +29824,7 @@ function createBarChartRuntime(chart, getters) {
29519
29824
  options: {
29520
29825
  ...CHART_COMMON_OPTIONS,
29521
29826
  indexAxis: chart.horizontal ? "y" : "x",
29522
- layout: getBarChartLayout(definition),
29827
+ layout: getChartLayout(),
29523
29828
  scales: getBarChartScales(definition, chartData),
29524
29829
  plugins: {
29525
29830
  title: getChartTitle(definition),
@@ -29671,7 +29976,7 @@ function createComboChartRuntime(chart, getters) {
29671
29976
  },
29672
29977
  options: {
29673
29978
  ...CHART_COMMON_OPTIONS,
29674
- layout: getBarChartLayout(definition),
29979
+ layout: getChartLayout(),
29675
29980
  scales: getBarChartScales(definition, chartData),
29676
29981
  plugins: {
29677
29982
  title: getChartTitle(definition),
@@ -29923,6 +30228,133 @@ function getSectionThresholdValue(threshold, minValue, maxValue) {
29923
30228
  return clip(value, minValue, maxValue);
29924
30229
  }
29925
30230
 
30231
+ class GeoChart extends AbstractChart {
30232
+ dataSets;
30233
+ labelRange;
30234
+ background;
30235
+ legendPosition;
30236
+ type = "geo";
30237
+ dataSetsHaveTitle;
30238
+ dataSetDesign;
30239
+ colorScale;
30240
+ missingValueColor;
30241
+ region;
30242
+ constructor(definition, sheetId, getters) {
30243
+ super(definition, sheetId, getters);
30244
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
30245
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
30246
+ this.background = definition.background;
30247
+ this.legendPosition = definition.legendPosition;
30248
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
30249
+ this.dataSetDesign = definition.dataSets;
30250
+ this.colorScale = definition.colorScale;
30251
+ this.missingValueColor = definition.missingValueColor;
30252
+ this.region = definition.region;
30253
+ }
30254
+ static transformDefinition(definition, executed) {
30255
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
30256
+ }
30257
+ static validateChartDefinition(validator, definition) {
30258
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
30259
+ }
30260
+ static getDefinitionFromContextCreation(context) {
30261
+ return {
30262
+ background: context.background,
30263
+ dataSets: context.range ?? [],
30264
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
30265
+ legendPosition: context.legendPosition ?? "top",
30266
+ title: context.title || { text: "" },
30267
+ type: "geo",
30268
+ labelRange: context.auxiliaryRange || undefined,
30269
+ aggregated: context.aggregated,
30270
+ };
30271
+ }
30272
+ getContextCreation() {
30273
+ const range = [];
30274
+ for (const [i, dataSet] of this.dataSets.entries()) {
30275
+ range.push({
30276
+ ...this.dataSetDesign?.[i],
30277
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
30278
+ });
30279
+ }
30280
+ return {
30281
+ ...this,
30282
+ range,
30283
+ auxiliaryRange: this.labelRange
30284
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
30285
+ : undefined,
30286
+ };
30287
+ }
30288
+ copyForSheetId(sheetId) {
30289
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30290
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30291
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30292
+ return new GeoChart(definition, sheetId, this.getters);
30293
+ }
30294
+ copyInSheetId(sheetId) {
30295
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
30296
+ return new GeoChart(definition, sheetId, this.getters);
30297
+ }
30298
+ getDefinition() {
30299
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
30300
+ }
30301
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
30302
+ const ranges = [];
30303
+ for (const [i, dataSet] of dataSets.entries()) {
30304
+ ranges.push({
30305
+ ...this.dataSetDesign?.[i],
30306
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
30307
+ });
30308
+ }
30309
+ return {
30310
+ type: "geo",
30311
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
30312
+ background: this.background,
30313
+ dataSets: ranges,
30314
+ legendPosition: this.legendPosition,
30315
+ labelRange: labelRange
30316
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
30317
+ : undefined,
30318
+ title: this.title,
30319
+ colorScale: this.colorScale,
30320
+ missingValueColor: this.missingValueColor,
30321
+ region: this.region,
30322
+ };
30323
+ }
30324
+ getDefinitionForExcel() {
30325
+ return undefined;
30326
+ }
30327
+ updateRanges(applyChange) {
30328
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
30329
+ if (!isStale) {
30330
+ return this;
30331
+ }
30332
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
30333
+ return new GeoChart(definition, this.sheetId, this.getters);
30334
+ }
30335
+ }
30336
+ function createGeoChartRuntime(chart, getters) {
30337
+ const definition = chart.getDefinition();
30338
+ const chartData = getGeoChartData(definition, chart.dataSets, chart.labelRange, getters);
30339
+ const config = {
30340
+ type: "choropleth",
30341
+ data: {
30342
+ datasets: getGeoChartDatasets(definition, chartData),
30343
+ },
30344
+ options: {
30345
+ ...CHART_COMMON_OPTIONS,
30346
+ layout: getChartLayout(),
30347
+ scales: getGeoChartScales(definition, chartData),
30348
+ plugins: {
30349
+ title: getChartTitle(definition),
30350
+ tooltip: getGeoChartTooltip(definition, chartData),
30351
+ legend: { display: false },
30352
+ },
30353
+ },
30354
+ };
30355
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30356
+ }
30357
+
29926
30358
  class LineChart extends AbstractChart {
29927
30359
  dataSets;
29928
30360
  labelRange;
@@ -30072,7 +30504,7 @@ function createLineChartRuntime(chart, getters) {
30072
30504
  },
30073
30505
  options: {
30074
30506
  ...CHART_COMMON_OPTIONS,
30075
- layout: getLineChartLayout(definition),
30507
+ layout: getChartLayout(),
30076
30508
  scales: getLineChartScales(definition, chartData),
30077
30509
  plugins: {
30078
30510
  title: getChartTitle(definition),
@@ -30207,7 +30639,7 @@ function createPieChartRuntime(chart, getters) {
30207
30639
  },
30208
30640
  options: {
30209
30641
  ...CHART_COMMON_OPTIONS,
30210
- layout: getPieChartLayout(definition),
30642
+ layout: getChartLayout(),
30211
30643
  plugins: {
30212
30644
  title: getChartTitle(definition),
30213
30645
  legend: getPieChartLegend(definition, chartData),
@@ -30344,7 +30776,7 @@ function createPyramidChartRuntime(chart, getters) {
30344
30776
  options: {
30345
30777
  ...CHART_COMMON_OPTIONS,
30346
30778
  indexAxis: "y",
30347
- layout: getBarChartLayout(definition),
30779
+ layout: getChartLayout(),
30348
30780
  scales: getPyramidChartScales(definition, chartData),
30349
30781
  plugins: {
30350
30782
  title: getChartTitle(definition),
@@ -30493,7 +30925,7 @@ function createRadarChartRuntime(chart, getters) {
30493
30925
  },
30494
30926
  options: {
30495
30927
  ...CHART_COMMON_OPTIONS,
30496
- layout: getBarChartLayout(definition),
30928
+ layout: getChartLayout(),
30497
30929
  scales: getRadarChartScales(definition, chartData),
30498
30930
  plugins: {
30499
30931
  title: getChartTitle(definition),
@@ -30646,7 +31078,7 @@ function createScatterChartRuntime(chart, getters) {
30646
31078
  },
30647
31079
  options: {
30648
31080
  ...CHART_COMMON_OPTIONS,
30649
- layout: getLineChartLayout(definition),
31081
+ layout: getChartLayout(),
30650
31082
  scales: getScatterChartScales(definition, chartData),
30651
31083
  plugins: {
30652
31084
  title: getChartTitle(definition),
@@ -30807,7 +31239,7 @@ function createWaterfallChartRuntime(chart, getters) {
30807
31239
  },
30808
31240
  options: {
30809
31241
  ...CHART_COMMON_OPTIONS,
30810
- layout: getWaterfallChartLayout(definition),
31242
+ layout: getChartLayout(),
30811
31243
  scales: getWaterfallChartScales(definition, chartData),
30812
31244
  plugins: {
30813
31245
  title: getChartTitle(definition),
@@ -30916,6 +31348,15 @@ chartRegistry.add("radar", {
30916
31348
  getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30917
31349
  sequence: 80,
30918
31350
  });
31351
+ chartRegistry.add("geo", {
31352
+ match: (type) => type === "geo",
31353
+ createChart: (definition, sheetId, getters) => new GeoChart(definition, sheetId, getters),
31354
+ getChartRuntime: createGeoChartRuntime,
31355
+ validateChartDefinition: GeoChart.validateChartDefinition,
31356
+ transformDefinition: GeoChart.transformDefinition,
31357
+ getChartDefinitionFromContextCreation: GeoChart.getDefinitionFromContextCreation,
31358
+ sequence: 90,
31359
+ });
30919
31360
  const chartComponentRegistry = new Registry();
30920
31361
  chartComponentRegistry.add("line", ChartJsComponent);
30921
31362
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30927,6 +31368,7 @@ chartComponentRegistry.add("scorecard", ScorecardChart);
30927
31368
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30928
31369
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30929
31370
  chartComponentRegistry.add("radar", ChartJsComponent);
31371
+ chartComponentRegistry.add("geo", ChartJsComponent);
30930
31372
  const chartCategories = {
30931
31373
  line: _t("Line"),
30932
31374
  column: _t("Column"),
@@ -31086,6 +31528,13 @@ chartSubtypeRegistry
31086
31528
  subtypeDefinition: { fillArea: true },
31087
31529
  category: "misc",
31088
31530
  preview: "o-spreadsheet-ChartPreview.FILLED_RADAR_CHART",
31531
+ })
31532
+ .add("geo", {
31533
+ displayName: _t("Geo Chart"),
31534
+ chartSubtype: "geo",
31535
+ chartType: "geo",
31536
+ category: "misc",
31537
+ preview: "o-spreadsheet-ChartPreview.GEO_CHART",
31089
31538
  });
31090
31539
 
31091
31540
  /**
@@ -31936,18 +32385,66 @@ const ErrorToolTipPopoverBuilder = {
31936
32385
  },
31937
32386
  };
31938
32387
 
31939
- css /*SCSS*/ `
31940
- .o-filter-menu-value {
31941
- padding: 4px;
31942
- line-height: 20px;
31943
- height: 28px;
31944
- .o-filter-menu-value-checked {
31945
- width: 20px;
32388
+ const CHECK_SVG = /*xml*/ `
32389
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
32390
+ <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
32391
+ </svg>
32392
+ `;
32393
+ const CHECKBOX_WIDTH = 14;
32394
+ css /* scss */ `
32395
+ label.o-checkbox {
32396
+ input {
32397
+ appearance: none;
32398
+ -webkit-appearance: none;
32399
+ -moz-appearance: none;
32400
+ border-radius: 0;
32401
+ width: ${CHECKBOX_WIDTH}px;
32402
+ height: ${CHECKBOX_WIDTH}px;
32403
+ vertical-align: top;
32404
+ box-sizing: border-box;
32405
+ outline: none;
32406
+ border: 1px solid ${GRAY_300};
32407
+ cursor: pointer;
32408
+
32409
+ &:hover {
32410
+ border-color: ${ACTION_COLOR};
32411
+ }
32412
+
32413
+ &:checked {
32414
+ background: url("data:image/svg+xml,${encodeURIComponent(CHECK_SVG)}");
32415
+ background-color: ${ACTION_COLOR};
32416
+ border-color: ${ACTION_COLOR};
32417
+ }
32418
+
32419
+ &:focus {
32420
+ outline: none;
32421
+ box-shadow: 0 0 0 0.25rem rgba(113, 75, 103, 0.25);
32422
+ border-color: ${ACTION_COLOR};
32423
+ }
31946
32424
  }
31947
32425
  }
31948
32426
  `;
32427
+ class Checkbox extends Component {
32428
+ static template = "o-spreadsheet.Checkbox";
32429
+ static props = {
32430
+ label: { type: String, optional: true },
32431
+ value: { type: Boolean, optional: true },
32432
+ className: { type: String, optional: true },
32433
+ name: { type: String, optional: true },
32434
+ title: { type: String, optional: true },
32435
+ disabled: { type: Boolean, optional: true },
32436
+ onChange: Function,
32437
+ };
32438
+ static defaultProps = { value: false };
32439
+ onChange(ev) {
32440
+ const value = ev.target.checked;
32441
+ this.props.onChange(value);
32442
+ }
32443
+ }
32444
+
31949
32445
  class FilterMenuValueItem extends Component {
31950
32446
  static template = "o-spreadsheet-FilterMenuValueItem";
32447
+ static components = { Checkbox };
31951
32448
  static props = {
31952
32449
  value: String,
31953
32450
  isChecked: Boolean,
@@ -31985,8 +32482,6 @@ const CSS = css /* scss */ `
31985
32482
  .o-filter-menu-item {
31986
32483
  display: flex;
31987
32484
  box-sizing: border-box;
31988
- height: ${MENU_ITEM_HEIGHT}px;
31989
- padding: 4px 4px 4px 0px;
31990
32485
  cursor: pointer;
31991
32486
  user-select: none;
31992
32487
 
@@ -31995,14 +32490,6 @@ const CSS = css /* scss */ `
31995
32490
  }
31996
32491
  }
31997
32492
 
31998
- input {
31999
- box-sizing: border-box;
32000
- margin-bottom: 5px;
32001
- border: 1px solid #949494;
32002
- height: 24px;
32003
- padding-right: 28px;
32004
- }
32005
-
32006
32493
  .o-search-icon {
32007
32494
  right: 5px;
32008
32495
  top: 3px;
@@ -32019,19 +32506,12 @@ const CSS = css /* scss */ `
32019
32506
  display: flex;
32020
32507
  flex-direction: row;
32021
32508
  margin-bottom: 4px;
32022
-
32023
- .o-filter-menu-action-text {
32024
- cursor: pointer;
32025
- margin-right: 10px;
32026
- color: blue;
32027
- text-decoration: underline;
32028
- }
32029
32509
  }
32030
32510
 
32031
32511
  .o-filter-menu-list {
32032
32512
  flex: auto;
32033
32513
  overflow-y: auto;
32034
- border: 1px solid #949494;
32514
+ border: 1px solid ${GRAY_300};
32035
32515
 
32036
32516
  .o-filter-menu-no-values {
32037
32517
  color: #949494;
@@ -33117,6 +33597,7 @@ var CHART_HELPERS = /*#__PURE__*/Object.freeze({
33117
33597
  adaptChartRange: adaptChartRange,
33118
33598
  chartFactory: chartFactory,
33119
33599
  chartFontColor: chartFontColor,
33600
+ chartMutedFontColor: chartMutedFontColor,
33120
33601
  chartRuntimeFactory: chartRuntimeFactory,
33121
33602
  chartToImage: chartToImage,
33122
33603
  checkDataset: checkDataset,
@@ -34222,6 +34703,21 @@ const pivotProperties = {
34222
34703
  isReadonlyAllowed: true,
34223
34704
  icon: "o-spreadsheet-Icon.PIVOT",
34224
34705
  };
34706
+ const pivotSortingAsc = {
34707
+ name: _t("Ascending"),
34708
+ execute: (env) => sortPivot(env, "asc"),
34709
+ isActive: (env) => isPivotSortMenuItemActive(env, "asc"),
34710
+ };
34711
+ const pivotSortingDesc = {
34712
+ name: _t("Descending"),
34713
+ execute: (env) => sortPivot(env, "desc"),
34714
+ isActive: (env) => isPivotSortMenuItemActive(env, "desc"),
34715
+ };
34716
+ const noPivotSorting = {
34717
+ name: _t("No sorting"),
34718
+ execute: (env) => sortPivot(env, "none"),
34719
+ isActive: (env) => isPivotSortMenuItemActive(env, "none"),
34720
+ };
34225
34721
  const FIX_FORMULAS = {
34226
34722
  name: _t("Convert to individual formulas"),
34227
34723
  execute(env) {
@@ -34258,6 +34754,66 @@ const FIX_FORMULAS = {
34258
34754
  },
34259
34755
  icon: "o-spreadsheet-Icon.PIVOT",
34260
34756
  };
34757
+ function canSortPivot(env) {
34758
+ const position = env.model.getters.getActivePosition();
34759
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34760
+ if (!pivotId ||
34761
+ !env.model.getters.isExistingPivot(pivotId) ||
34762
+ !env.model.getters.isSpillPivotFormula(position)) {
34763
+ return false;
34764
+ }
34765
+ const pivot = env.model.getters.getPivot(pivotId);
34766
+ if (!pivot.isValid()) {
34767
+ return false;
34768
+ }
34769
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34770
+ return pivotCell.type === "VALUE" || pivotCell.type === "MEASURE_HEADER";
34771
+ }
34772
+ function sortPivot(env, order) {
34773
+ const position = env.model.getters.getActivePosition();
34774
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34775
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34776
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
34777
+ return;
34778
+ }
34779
+ if (order === "none") {
34780
+ env.model.dispatch("UPDATE_PIVOT", {
34781
+ pivotId: pivotId,
34782
+ pivot: {
34783
+ ...env.model.getters.getPivotCoreDefinition(pivotId),
34784
+ sortedColumn: undefined,
34785
+ },
34786
+ });
34787
+ return;
34788
+ }
34789
+ const pivot = env.model.getters.getPivot(pivotId);
34790
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
34791
+ env.model.dispatch("UPDATE_PIVOT", {
34792
+ pivotId: pivotId,
34793
+ pivot: {
34794
+ ...env.model.getters.getPivotCoreDefinition(pivotId),
34795
+ sortedColumn: { domain: colDomain, order, measure: pivotCell.measure },
34796
+ },
34797
+ });
34798
+ }
34799
+ function isPivotSortMenuItemActive(env, order) {
34800
+ const position = env.model.getters.getActivePosition();
34801
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34802
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34803
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
34804
+ return false;
34805
+ }
34806
+ const pivot = env.model.getters.getPivot(pivotId);
34807
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
34808
+ const sortedColumn = pivot.definition.sortedColumn;
34809
+ if (order === "none") {
34810
+ return !sortedColumn;
34811
+ }
34812
+ if (!sortedColumn || sortedColumn.order !== order) {
34813
+ return false;
34814
+ }
34815
+ return sortedColumn.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain);
34816
+ }
34261
34817
 
34262
34818
  //------------------------------------------------------------------------------
34263
34819
  // Context Menu Registry
@@ -34356,14 +34912,33 @@ cellMenuRegistry
34356
34912
  name: INSERT_LINK_NAME,
34357
34913
  sequence: 150,
34358
34914
  separator: true,
34915
+ })
34916
+ .add("pivot_sorting", {
34917
+ name: _t("Sort pivot"),
34918
+ sequence: 155,
34919
+ icon: "o-spreadsheet-Icon.SORT_RANGE",
34920
+ isVisible: canSortPivot,
34359
34921
  })
34360
34922
  .add("pivot_fix_formulas", {
34361
34923
  ...FIX_FORMULAS,
34362
- sequence: 155,
34924
+ sequence: 160,
34363
34925
  })
34364
34926
  .add("pivot_properties", {
34365
34927
  ...pivotProperties,
34366
34928
  sequence: 170,
34929
+ separator: true,
34930
+ })
34931
+ .addChild("pivot_sorting_asc", ["pivot_sorting"], {
34932
+ ...pivotSortingAsc,
34933
+ sequence: 10,
34934
+ })
34935
+ .addChild("pivot_sorting_desc", ["pivot_sorting"], {
34936
+ ...pivotSortingDesc,
34937
+ sequence: 20,
34938
+ })
34939
+ .addChild("pivot_sorting_none", ["pivot_sorting"], {
34940
+ ...noPivotSorting,
34941
+ sequence: 30,
34367
34942
  });
34368
34943
 
34369
34944
  const sortRange = {
@@ -34376,7 +34951,7 @@ const sortAscending = {
34376
34951
  execute: (env) => {
34377
34952
  const { anchor, zones } = env.model.getters.getSelection();
34378
34953
  const sheetId = env.model.getters.getActiveSheetId();
34379
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "ascending");
34954
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "asc");
34380
34955
  },
34381
34956
  icon: "o-spreadsheet-Icon.SORT_ASCENDING",
34382
34957
  };
@@ -34404,7 +34979,7 @@ const sortDescending = {
34404
34979
  execute: (env) => {
34405
34980
  const { anchor, zones } = env.model.getters.getSelection();
34406
34981
  const sheetId = env.model.getters.getActiveSheetId();
34407
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "descending");
34982
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "desc");
34408
34983
  },
34409
34984
  icon: "o-spreadsheet-Icon.SORT_DESCENDING",
34410
34985
  };
@@ -34887,6 +35462,227 @@ function interactiveFreezeColumnsRows(env, dimension, base) {
34887
35462
  }
34888
35463
  }
34889
35464
 
35465
+ class PositionMap {
35466
+ map = {};
35467
+ constructor(entries = []) {
35468
+ for (const [position, value] of entries) {
35469
+ this.set(position, value);
35470
+ }
35471
+ }
35472
+ set({ sheetId, col, row }, value) {
35473
+ const map = this.map;
35474
+ if (!map[sheetId]) {
35475
+ map[sheetId] = {};
35476
+ }
35477
+ if (!map[sheetId][col]) {
35478
+ map[sheetId][col] = {};
35479
+ }
35480
+ map[sheetId][col][row] = value;
35481
+ }
35482
+ get({ sheetId, col, row }) {
35483
+ return this.map[sheetId]?.[col]?.[row];
35484
+ }
35485
+ getSheet(sheetId) {
35486
+ return this.map[sheetId];
35487
+ }
35488
+ has({ sheetId, col, row }) {
35489
+ return this.map[sheetId]?.[col]?.[row] !== undefined;
35490
+ }
35491
+ delete({ sheetId, col, row }) {
35492
+ delete this.map[sheetId]?.[col]?.[row];
35493
+ }
35494
+ keys() {
35495
+ const map = this.map;
35496
+ const keys = [];
35497
+ for (const sheetId in map) {
35498
+ for (const col in map[sheetId]) {
35499
+ for (const row in map[sheetId][col]) {
35500
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
35501
+ }
35502
+ }
35503
+ }
35504
+ return keys;
35505
+ }
35506
+ keysForSheet(sheetId) {
35507
+ const map = this.map[sheetId];
35508
+ if (!map) {
35509
+ return [];
35510
+ }
35511
+ const keys = [];
35512
+ for (const col in map) {
35513
+ for (const row in map[col]) {
35514
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
35515
+ }
35516
+ }
35517
+ return keys;
35518
+ }
35519
+ *entries() {
35520
+ const map = this.map;
35521
+ for (const position of this.keys()) {
35522
+ const { sheetId, col, row } = position;
35523
+ yield [position, map[sheetId][col][row]];
35524
+ }
35525
+ }
35526
+ }
35527
+
35528
+ class FormulaFingerprintStore extends SpreadsheetStore {
35529
+ mutators = ["enable", "disable"];
35530
+ isInvalidated = false;
35531
+ fingerprintColors = {
35532
+ [DATA_FINGERPRINT]: "#D9D9D9",
35533
+ };
35534
+ isEnabled = false;
35535
+ colors = new PositionMap();
35536
+ handle(cmd) {
35537
+ if (isCoreCommand(cmd) && this.isEnabled) {
35538
+ this.isInvalidated = true;
35539
+ }
35540
+ switch (cmd.type) {
35541
+ case "UNDO":
35542
+ case "REDO":
35543
+ case "ACTIVATE_SHEET":
35544
+ if (this.isEnabled) {
35545
+ this.isInvalidated = true;
35546
+ }
35547
+ break;
35548
+ }
35549
+ }
35550
+ finalize() {
35551
+ if (this.isInvalidated) {
35552
+ this.isInvalidated = false;
35553
+ this.computeFingerprints();
35554
+ }
35555
+ }
35556
+ enable() {
35557
+ this.isEnabled = true;
35558
+ this.computeFingerprints();
35559
+ }
35560
+ disable() {
35561
+ this.isEnabled = false;
35562
+ this.colors = new PositionMap();
35563
+ }
35564
+ computeFingerprints() {
35565
+ this.colors = new PositionMap();
35566
+ const fingerprints = new PositionMap();
35567
+ const allFingerprints = new Set();
35568
+ const activeSheetId = this.getters.getActiveSheetId();
35569
+ const cells = this.getters.getCells(activeSheetId);
35570
+ for (const cellId in cells) {
35571
+ const fingerprint = this.computeFingerprint(cells[cellId]);
35572
+ if (!fingerprint) {
35573
+ continue;
35574
+ }
35575
+ allFingerprints.add(fingerprint);
35576
+ const position = this.getters.getCellPosition(cellId);
35577
+ fingerprints.set(position, fingerprint);
35578
+ }
35579
+ this.assignColors(allFingerprints);
35580
+ for (const [position, fingerprint] of fingerprints.entries()) {
35581
+ const color = this.fingerprintColors[fingerprint];
35582
+ this.colors.set(position, color);
35583
+ this.colorSpreadZone(position, color);
35584
+ }
35585
+ }
35586
+ colorSpreadZone(position, fingerprintColor) {
35587
+ const spreadZone = this.getters.getSpreadZone(position);
35588
+ if (!spreadZone) {
35589
+ return;
35590
+ }
35591
+ const sheetId = position.sheetId;
35592
+ for (let row = spreadZone.top; row <= spreadZone.bottom; row++) {
35593
+ for (let col = spreadZone.left; col <= spreadZone.right; col++) {
35594
+ const spreadPosition = { sheetId, col, row };
35595
+ this.colors.set(spreadPosition, fingerprintColor);
35596
+ }
35597
+ }
35598
+ }
35599
+ assignColors(fingerprints) {
35600
+ const colors = new AlternatingColorGenerator(fingerprints.size);
35601
+ Object.keys(this.fingerprintColors).forEach(() => colors.next());
35602
+ for (const fingerprint of fingerprints) {
35603
+ if (!this.fingerprintColors[fingerprint]) {
35604
+ this.fingerprintColors[fingerprint] = setColorAlpha(colors.next(), 0.8);
35605
+ }
35606
+ }
35607
+ }
35608
+ computeFingerprint(cell) {
35609
+ const position = this.getters.getCellPosition(cell.id);
35610
+ if (cell.isFormula) {
35611
+ return this.computeFormulaFingerprint(position, cell);
35612
+ }
35613
+ else {
35614
+ return this.getLiteralFingerprint(position);
35615
+ }
35616
+ }
35617
+ computeFormulaFingerprint(position, cell) {
35618
+ const dependencies = cell.compiledFormula.dependencies;
35619
+ const colCellOffset = position.col;
35620
+ const rowCellOffset = position.row;
35621
+ const positionSheetIndex = this.getters.getSheetIds().indexOf(position.sheetId);
35622
+ // As an optimization, we do not build each reference vector individually
35623
+ // to sum them up, but instead we directly add each component to the resulting
35624
+ // vector. This is equivalent to summing up all reference vectors.
35625
+ const fingerprintVector = {
35626
+ dx: 0,
35627
+ dy: 0,
35628
+ dSheet: 0,
35629
+ };
35630
+ for (const range of dependencies) {
35631
+ const zone = range.zone;
35632
+ const [left, right] = range.parts;
35633
+ const rangeSheetIndex = this.getters.getSheetIds().indexOf(range.sheetId);
35634
+ fingerprintVector.dSheet = rangeSheetIndex - positionSheetIndex;
35635
+ // in relative mode, we offset the col and row by the cell's position
35636
+ // in absolute mode, we offset the col and row relative to the sheet
35637
+ const isLeftUnbounded = range.isFullRow && !range.unboundedZone.hasHeader;
35638
+ const isTopUnbounded = range.isFullCol && !range.unboundedZone.hasHeader;
35639
+ const leftOffset = isLeftUnbounded || left?.colFixed ? 0 : colCellOffset;
35640
+ const topOffset = isTopUnbounded || left?.rowFixed ? 0 : rowCellOffset;
35641
+ const isRightFixed = (!right && left?.colFixed) || right?.colFixed;
35642
+ const isBottomFixed = (!right && left.rowFixed) || right?.rowFixed;
35643
+ const isRightUnbounded = range.unboundedZone.right === undefined;
35644
+ const isBottomUnbounded = range.unboundedZone.bottom === undefined;
35645
+ const rightOffset = isRightUnbounded || isRightFixed ? 0 : colCellOffset;
35646
+ const bottomOffset = isBottomUnbounded || isBottomFixed ? 0 : rowCellOffset;
35647
+ const referenceZone = reorderZone({
35648
+ top: zone.top - topOffset,
35649
+ left: zone.left - leftOffset,
35650
+ right: zone.right - rightOffset,
35651
+ bottom: zone.bottom - bottomOffset,
35652
+ });
35653
+ for (let dy = referenceZone.top; dy <= referenceZone.bottom; dy++) {
35654
+ for (let dx = referenceZone.left; dx <= referenceZone.right; dx++) {
35655
+ fingerprintVector.dx += dx;
35656
+ fingerprintVector.dy += dy;
35657
+ }
35658
+ }
35659
+ }
35660
+ // removes the index placeholders from the normalized formula
35661
+ // =|N0|+|N1|+|N0| -> =|N|+|N|+|N|
35662
+ const normalizedFormula = cell.compiledFormula.normalizedFormula.replace(/(|\w)(\d)(|)/g, "$1$3");
35663
+ return hash(fingerprintVector) + normalizedFormula;
35664
+ }
35665
+ getLiteralFingerprint(position) {
35666
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
35667
+ switch (evaluatedCell.type) {
35668
+ case CellValueType.number:
35669
+ case CellValueType.boolean:
35670
+ return DATA_FINGERPRINT;
35671
+ case CellValueType.text:
35672
+ case CellValueType.empty:
35673
+ case CellValueType.error:
35674
+ return undefined;
35675
+ }
35676
+ }
35677
+ }
35678
+ function hash(vector) {
35679
+ return Object.entries(vector)
35680
+ .sort(([a], [b]) => a.localeCompare(b))
35681
+ .map(([_, value]) => value)
35682
+ .join(",");
35683
+ }
35684
+ const DATA_FINGERPRINT = "DATA_FINGERPRINT";
35685
+
34890
35686
  const hideCols = {
34891
35687
  name: HIDE_COLUMNS_NAME,
34892
35688
  execute: (env) => {
@@ -35058,6 +35854,19 @@ const viewGridlines = {
35058
35854
  return env.model.getters.getGridLinesVisibility(sheetId);
35059
35855
  },
35060
35856
  };
35857
+ const irregularityMap = {
35858
+ name: _t("Irregularity map"),
35859
+ execute: (env) => {
35860
+ const fingerprintStore = env.getStore(FormulaFingerprintStore);
35861
+ if (fingerprintStore.isEnabled) {
35862
+ fingerprintStore.disable();
35863
+ }
35864
+ else {
35865
+ fingerprintStore.enable();
35866
+ }
35867
+ },
35868
+ icon: "o-spreadsheet-Icon.IRREGULARITY_MAP",
35869
+ };
35061
35870
  const viewFormulas = {
35062
35871
  name: _t("Formulas"),
35063
35872
  isActive: (env) => env.model.getters.shouldShowFormulas(),
@@ -35675,6 +36484,11 @@ topbarMenuRegistry
35675
36484
  .addChild("view_formulas", ["view", "show"], {
35676
36485
  ...viewFormulas,
35677
36486
  sequence: 10,
36487
+ })
36488
+ .addChild("view_irregularity_map", ["view"], {
36489
+ ...irregularityMap,
36490
+ sequence: 40,
36491
+ separator: true,
35678
36492
  })
35679
36493
  // ---------------------------------------------------------------------
35680
36494
  // INSERT MENU ITEMS
@@ -35977,62 +36791,6 @@ class OTRegistry extends Registry {
35977
36791
  }
35978
36792
  const otRegistry = new OTRegistry();
35979
36793
 
35980
- const CHECK_SVG = /*xml*/ `
35981
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
35982
- <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
35983
- </svg>
35984
- `;
35985
- const CHECKBOX_WIDTH = 14;
35986
- css /* scss */ `
35987
- label.o-checkbox {
35988
- input {
35989
- appearance: none;
35990
- -webkit-appearance: none;
35991
- -moz-appearance: none;
35992
- border-radius: 0;
35993
- width: ${CHECKBOX_WIDTH}px;
35994
- height: ${CHECKBOX_WIDTH}px;
35995
- vertical-align: top;
35996
- box-sizing: border-box;
35997
- outline: none;
35998
- border: 1px solid ${GRAY_300};
35999
-
36000
- &:hover {
36001
- border-color: ${ACTION_COLOR};
36002
- }
36003
-
36004
- &:checked {
36005
- background: url("data:image/svg+xml,${encodeURIComponent(CHECK_SVG)}");
36006
- background-color: ${ACTION_COLOR};
36007
- border-color: ${ACTION_COLOR};
36008
- }
36009
-
36010
- &:focus {
36011
- outline: none;
36012
- box-shadow: 0 0 0 0.25rem rgba(113, 75, 103, 0.25);
36013
- border-color: ${ACTION_COLOR};
36014
- }
36015
- }
36016
- }
36017
- `;
36018
- class Checkbox extends Component {
36019
- static template = "o-spreadsheet.Checkbox";
36020
- static props = {
36021
- label: { type: String, optional: true },
36022
- value: { type: Boolean, optional: true },
36023
- className: { type: String, optional: true },
36024
- name: { type: String, optional: true },
36025
- title: { type: String, optional: true },
36026
- disabled: { type: Boolean, optional: true },
36027
- onChange: Function,
36028
- };
36029
- static defaultProps = { value: false };
36030
- onChange(ev) {
36031
- const value = ev.target.checked;
36032
- this.props.onChange(value);
36033
- }
36034
- }
36035
-
36036
36794
  class Section extends Component {
36037
36795
  static template = "o_spreadsheet.Section";
36038
36796
  static props = {
@@ -37409,6 +38167,95 @@ class ColorPickerWidget extends Component {
37409
38167
  }
37410
38168
  }
37411
38169
 
38170
+ css /* scss */ `
38171
+ .o-font-size-editor {
38172
+ height: calc(100% - 4px);
38173
+ input.o-font-size {
38174
+ outline-color: ${SELECTION_BORDER_COLOR};
38175
+ height: 20px;
38176
+ width: 23px;
38177
+ }
38178
+ }
38179
+ .o-text-options > div {
38180
+ cursor: pointer;
38181
+ line-height: 26px;
38182
+ padding: 3px 12px;
38183
+ &:hover {
38184
+ background-color: rgba(0, 0, 0, 0.08);
38185
+ }
38186
+ }
38187
+ `;
38188
+ class FontSizeEditor extends Component {
38189
+ static template = "o-spreadsheet-FontSizeEditor";
38190
+ static props = {
38191
+ currentFontSize: Number,
38192
+ onFontSizeChanged: Function,
38193
+ onToggle: { type: Function, optional: true },
38194
+ class: String,
38195
+ };
38196
+ static components = { Popover };
38197
+ fontSizes = FONT_SIZES;
38198
+ dropdown = useState({ isOpen: false });
38199
+ inputRef = useRef("inputFontSize");
38200
+ rootEditorRef = useRef("FontSizeEditor");
38201
+ fontSizeListRef = useRef("fontSizeList");
38202
+ setup() {
38203
+ useExternalListener(window, "click", this.onExternalClick, { capture: true });
38204
+ }
38205
+ get popoverProps() {
38206
+ const { x, y, width, height } = this.rootEditorRef.el.getBoundingClientRect();
38207
+ return {
38208
+ anchorRect: { x, y, width, height },
38209
+ positioning: "BottomLeft",
38210
+ verticalOffset: 0,
38211
+ };
38212
+ }
38213
+ onExternalClick(ev) {
38214
+ if (!isChildEvent(this.fontSizeListRef.el, ev) && !isChildEvent(this.rootEditorRef.el, ev)) {
38215
+ this.closeFontList();
38216
+ }
38217
+ }
38218
+ toggleFontList() {
38219
+ const isOpen = this.dropdown.isOpen;
38220
+ if (!isOpen) {
38221
+ this.props.onToggle?.();
38222
+ this.inputRef.el.focus();
38223
+ }
38224
+ else {
38225
+ this.closeFontList();
38226
+ }
38227
+ }
38228
+ closeFontList() {
38229
+ this.dropdown.isOpen = false;
38230
+ }
38231
+ setSize(fontSizeStr) {
38232
+ const fontSize = clip(Math.floor(parseFloat(fontSizeStr)), 1, 400);
38233
+ this.props.onFontSizeChanged(fontSize);
38234
+ this.closeFontList();
38235
+ }
38236
+ setSizeFromInput(ev) {
38237
+ this.setSize(ev.target.value);
38238
+ }
38239
+ setSizeFromList(fontSizeStr) {
38240
+ this.setSize(fontSizeStr);
38241
+ }
38242
+ onInputFocused(ev) {
38243
+ this.dropdown.isOpen = true;
38244
+ ev.target.select();
38245
+ }
38246
+ onInputKeydown(ev) {
38247
+ if (ev.key === "Enter" || ev.key === "Escape") {
38248
+ this.closeFontList();
38249
+ const target = ev.target;
38250
+ // In the case of a ESCAPE key, we get the previous font size back
38251
+ if (ev.key === "Escape") {
38252
+ target.value = `${this.props.currentFontSize}`;
38253
+ }
38254
+ this.props.onToggle?.();
38255
+ }
38256
+ }
38257
+ }
38258
+
37412
38259
  css /* scss */ `
37413
38260
  .o-chart-title-designer {
37414
38261
  > span {
@@ -37443,7 +38290,7 @@ css /* scss */ `
37443
38290
  `;
37444
38291
  class ChartTitle extends Component {
37445
38292
  static template = "o-spreadsheet.ChartTitle";
37446
- static components = { Section, ColorPickerWidget };
38293
+ static components = { Section, ColorPickerWidget, FontSizeEditor };
37447
38294
  static props = {
37448
38295
  title: { type: String, optional: true },
37449
38296
  updateTitle: Function,
@@ -37452,7 +38299,8 @@ class ChartTitle extends Component {
37452
38299
  toggleBold: { type: Function, optional: true },
37453
38300
  updateAlignment: { type: Function, optional: true },
37454
38301
  updateColor: { type: Function, optional: true },
37455
- style: { type: Object, optional: true },
38302
+ style: Object,
38303
+ onFontSizeChanged: Function,
37456
38304
  };
37457
38305
  static defaultProps = {
37458
38306
  title: "",
@@ -37467,6 +38315,9 @@ class ChartTitle extends Component {
37467
38315
  updateTitle(ev) {
37468
38316
  this.props.updateTitle(ev.target.value);
37469
38317
  }
38318
+ updateFontSize(fontSize) {
38319
+ this.props.onFontSizeChanged(fontSize);
38320
+ }
37470
38321
  toggleDropdownTool(tool, ev) {
37471
38322
  const isOpen = this.state.activeTool === tool;
37472
38323
  this.closeMenus();
@@ -37509,6 +38360,7 @@ class AxisDesignEditor extends Component {
37509
38360
  return {
37510
38361
  color: "",
37511
38362
  align: "center",
38363
+ fontSize: CHART_AXIS_TITLE_FONT_SIZE,
37512
38364
  ...axisDesign.title,
37513
38365
  };
37514
38366
  }
@@ -37526,6 +38378,17 @@ class AxisDesignEditor extends Component {
37526
38378
  };
37527
38379
  this.props.updateChart(this.props.figureId, { axesDesign });
37528
38380
  }
38381
+ updateAxisTitleFontSize(fontSize) {
38382
+ const axesDesign = this.props.definition.axesDesign ?? {};
38383
+ axesDesign[this.state.currentAxis] = {
38384
+ ...axesDesign[this.state.currentAxis],
38385
+ title: {
38386
+ ...(axesDesign[this.state.currentAxis]?.title ?? {}),
38387
+ fontSize,
38388
+ },
38389
+ };
38390
+ this.props.updateChart(this.props.figureId, { axesDesign });
38391
+ }
37529
38392
  toggleBoldAxisTitle() {
37530
38393
  const axesDesign = this.props.definition.axesDesign ?? {};
37531
38394
  const title = axesDesign[this.state.currentAxis]?.title ?? {};
@@ -37645,8 +38508,12 @@ class GeneralDesignEditor extends Component {
37645
38508
  figureId: String,
37646
38509
  definition: Object,
37647
38510
  updateChart: Function,
38511
+ defaultChartTitleFontSize: { type: Number, optional: true },
37648
38512
  slots: { type: Object, optional: true },
37649
38513
  };
38514
+ static defaultProps = {
38515
+ defaultChartTitleFontSize: CHART_TITLE_FONT_SIZE,
38516
+ };
37650
38517
  state;
37651
38518
  setup() {
37652
38519
  this.state = useState({
@@ -37672,6 +38539,7 @@ class GeneralDesignEditor extends Component {
37672
38539
  get titleStyle() {
37673
38540
  return {
37674
38541
  align: "left",
38542
+ fontSize: this.props.defaultChartTitleFontSize,
37675
38543
  ...this.title,
37676
38544
  };
37677
38545
  }
@@ -37680,6 +38548,10 @@ class GeneralDesignEditor extends Component {
37680
38548
  this.props.updateChart(this.props.figureId, { title });
37681
38549
  this.state.activeTool = "";
37682
38550
  }
38551
+ updateChartTitleFontSize(fontSize) {
38552
+ const title = { ...this.title, fontSize };
38553
+ this.props.updateChart(this.props.figureId, { title });
38554
+ }
37683
38555
  toggleBoldChartTitle() {
37684
38556
  let title = this.title;
37685
38557
  title = { ...title, bold: !title.bold };
@@ -37887,7 +38759,7 @@ class SeriesWithAxisDesignEditor extends Component {
37887
38759
  case "polynomial":
37888
38760
  config = {
37889
38761
  type: "polynomial",
37890
- order: type === "linear" ? 1 : 2,
38762
+ order: type === "linear" ? 1 : this.getMaxPolynomialDegree(index),
37891
38763
  };
37892
38764
  break;
37893
38765
  case "exponential":
@@ -38158,6 +39030,104 @@ class GaugeChartDesignPanel extends Component {
38158
39030
  }
38159
39031
  }
38160
39032
 
39033
+ class GeoChartRegionSelectSection extends Component {
39034
+ static template = "o-spreadsheet-GeoChartRegionSelectSection";
39035
+ static components = { Section };
39036
+ static props = {
39037
+ figureId: String,
39038
+ definition: Object,
39039
+ updateChart: Function,
39040
+ };
39041
+ updateSelectedRegion(ev) {
39042
+ const value = ev.target.value;
39043
+ this.props.updateChart(this.props.figureId, { region: value });
39044
+ }
39045
+ get availableRegions() {
39046
+ return this.env.model.getters.getGeoChartAvailableRegions();
39047
+ }
39048
+ get selectedRegion() {
39049
+ return this.props.definition.region || this.availableRegions[0]?.id;
39050
+ }
39051
+ }
39052
+
39053
+ class GeoChartConfigPanel extends GenericChartConfigPanel {
39054
+ static template = "o-spreadsheet-GeoChartConfigPanel";
39055
+ static components = { ...GenericChartConfigPanel.components, GeoChartRegionSelectSection };
39056
+ get dataRanges() {
39057
+ return this.getDataSeriesRanges().slice(0, 1);
39058
+ }
39059
+ onDataSeriesConfirmed() {
39060
+ this.dataSeriesRanges = spreadRange(this.env.model.getters, this.dataSeriesRanges).slice(0, 1);
39061
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
39062
+ dataSets: this.dataSeriesRanges,
39063
+ });
39064
+ }
39065
+ getLabelRangeOptions() {
39066
+ return [
39067
+ {
39068
+ name: "dataSetsHaveTitle",
39069
+ label: this.dataSetsHaveTitleLabel,
39070
+ value: this.props.definition.dataSetsHaveTitle,
39071
+ onChange: this.onUpdateDataSetsHaveTitle.bind(this),
39072
+ },
39073
+ ];
39074
+ }
39075
+ }
39076
+
39077
+ const DEFAULT_CUSTOM_COLOR_SCALE = {
39078
+ minColor: "#FFF5EB",
39079
+ midColor: "#FD8D3C",
39080
+ maxColor: "#7F2704",
39081
+ };
39082
+ class GeoChartDesignPanel extends ChartWithAxisDesignPanel {
39083
+ static template = "o-spreadsheet-GeoChartDesignPanel";
39084
+ static components = { ...ChartWithAxisDesignPanel.components, RoundColorPicker };
39085
+ colorScalesChoices = ChartTerms.GeoChart.ColorScales;
39086
+ updateColorScaleType(ev) {
39087
+ const value = ev.target.value;
39088
+ value === "custom"
39089
+ ? this.updateColorScale(DEFAULT_CUSTOM_COLOR_SCALE)
39090
+ : this.updateColorScale(value);
39091
+ }
39092
+ updateColorScale(colorScale) {
39093
+ this.props.updateChart(this.props.figureId, { colorScale });
39094
+ }
39095
+ updateMissingValueColor(color) {
39096
+ this.props.updateChart(this.props.figureId, { missingValueColor: color });
39097
+ }
39098
+ updateLegendPosition(ev) {
39099
+ const value = ev.target.value;
39100
+ this.props.updateChart(this.props.figureId, { legendPosition: value });
39101
+ }
39102
+ get selectedColorScale() {
39103
+ return typeof this.props.definition.colorScale === "object"
39104
+ ? "custom"
39105
+ : this.props.definition.colorScale || "oranges";
39106
+ }
39107
+ get selectedMissingValueColor() {
39108
+ return this.props.definition.missingValueColor || "#ffffff";
39109
+ }
39110
+ get customColorScale() {
39111
+ if (typeof this.props.definition.colorScale === "object") {
39112
+ return this.props.definition.colorScale;
39113
+ }
39114
+ return undefined;
39115
+ }
39116
+ getCustomColorScaleColor(color) {
39117
+ return this.customColorScale?.[color] ?? "";
39118
+ }
39119
+ setCustomColorScaleColor(colorType, color) {
39120
+ if (!color && colorType !== "midColor") {
39121
+ color = "#fff";
39122
+ }
39123
+ const customColorScale = this.customColorScale;
39124
+ if (!customColorScale) {
39125
+ return;
39126
+ }
39127
+ this.updateColorScale({ ...customColorScale, [colorType]: color });
39128
+ }
39129
+ }
39130
+
38161
39131
  class LineConfigPanel extends GenericChartConfigPanel {
38162
39132
  static template = "o-spreadsheet-LineConfigPanel";
38163
39133
  get canTreatLabelsAsText() {
@@ -38347,6 +39317,9 @@ class ScorecardChartDesignPanel extends Component {
38347
39317
  get humanizeNumbersLabel() {
38348
39318
  return _t("Humanize numbers");
38349
39319
  }
39320
+ get defaultScorecardTitleFontSize() {
39321
+ return SCORECARD_CHART_TITLE_FONT_SIZE;
39322
+ }
38350
39323
  updateHumanizeNumbers(humanize) {
38351
39324
  this.props.updateChart(this.props.figureId, { humanize });
38352
39325
  }
@@ -38468,6 +39441,10 @@ chartSidePanelComponentRegistry
38468
39441
  .add("radar", {
38469
39442
  configuration: GenericChartConfigPanel,
38470
39443
  design: RadarChartDesignPanel,
39444
+ })
39445
+ .add("geo", {
39446
+ configuration: GeoChartConfigPanel,
39447
+ design: GeoChartDesignPanel,
38471
39448
  });
38472
39449
 
38473
39450
  css /* scss */ `
@@ -39810,6 +40787,15 @@ class StandaloneComposerStore extends AbstractComposerStore {
39810
40787
  confirmEdition(content) {
39811
40788
  this.args().onConfirm(content);
39812
40789
  }
40790
+ getTokenColor(token) {
40791
+ if (token.type === "SYMBOL") {
40792
+ const matchedColor = this.args().getContextualColoredSymbolToken?.(token);
40793
+ if (matchedColor) {
40794
+ return matchedColor;
40795
+ }
40796
+ }
40797
+ return super.getTokenColor(token);
40798
+ }
39813
40799
  }
39814
40800
 
39815
40801
  css /* scss */ `
@@ -39822,7 +40808,7 @@ css /* scss */ `
39822
40808
  border-color: ${GRAY_300};
39823
40809
 
39824
40810
  &.active {
39825
- border-color: ${SELECTION_BORDER_COLOR};
40811
+ border-color: ${ACTION_COLOR};
39826
40812
  }
39827
40813
 
39828
40814
  &.o-invalid {
@@ -39847,6 +40833,7 @@ class StandaloneComposer extends Component {
39847
40833
  placeholder: { type: String, optional: true },
39848
40834
  class: { type: String, optional: true },
39849
40835
  invalid: { type: Boolean, optional: true },
40836
+ getContextualColoredSymbolToken: { type: Function, optional: true },
39850
40837
  };
39851
40838
  static components = { Composer };
39852
40839
  static defaultProps = {
@@ -39863,6 +40850,7 @@ class StandaloneComposer extends Component {
39863
40850
  content: this.props.composerContent,
39864
40851
  contextualAutocomplete: this.props.contextualAutocomplete,
39865
40852
  defaultRangeSheetId: this.props.defaultRangeSheetId,
40853
+ getContextualColoredSymbolToken: this.props.getContextualColoredSymbolToken,
39866
40854
  }));
39867
40855
  this.standaloneComposerStore = standaloneComposerStore;
39868
40856
  this.composerInterface = {
@@ -40778,7 +41766,7 @@ class ConditionalFormattingEditor extends Component {
40778
41766
  }
40779
41767
  const point = this.state.rules.colorScale[target];
40780
41768
  if (point) {
40781
- point.color = Number.parseInt(color.slice(1), 16);
41769
+ point.color = colorToNumber(color);
40782
41770
  }
40783
41771
  this.updateConditionalFormat({ rule: this.state.rules.colorScale });
40784
41772
  this.closeMenus();
@@ -40923,6 +41911,9 @@ class ConditionalFormattingEditor extends Component {
40923
41911
  return [this.state.rules.dataBar.rangeValues || ""];
40924
41912
  }
40925
41913
  updateDataBarColor(color) {
41914
+ if (!isColorValid(color)) {
41915
+ return;
41916
+ }
40926
41917
  this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
40927
41918
  this.updateConditionalFormat({ rule: this.state.rules.dataBar });
40928
41919
  }
@@ -43423,7 +44414,7 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
43423
44414
  return {
43424
44415
  text: text,
43425
44416
  description: measure.displayName,
43426
- htmlContent: [{ value: text, color: tokenColors.FUNCTION }],
44417
+ htmlContent: [{ value: text, color: PIVOT_TOKEN_COLOR }],
43427
44418
  fuzzySearchKey: measure.displayName + text + measure.fieldName,
43428
44419
  };
43429
44420
  });
@@ -43432,7 +44423,7 @@ function createMeasureAutoComplete(pivot, forComputedMeasure) {
43432
44423
  return {
43433
44424
  text: text,
43434
44425
  description: dimension.displayName,
43435
- htmlContent: [{ value: text, color: tokenColors.FUNCTION }],
44426
+ htmlContent: [{ value: text, color: PIVOT_TOKEN_COLOR }],
43436
44427
  fuzzySearchKey: dimension.displayName + text + dimension.fieldName,
43437
44428
  };
43438
44429
  });
@@ -43512,6 +44503,76 @@ class PivotMeasureEditor extends Component {
43512
44503
  measure: this.props.measure,
43513
44504
  });
43514
44505
  }
44506
+ getColoredSymbolToken(token) {
44507
+ if (token.type !== "SYMBOL") {
44508
+ return undefined;
44509
+ }
44510
+ const tokenValue = unquote(token.value, "'");
44511
+ if (this.props.definition.columns.some((col) => col.nameWithGranularity === tokenValue) ||
44512
+ this.props.definition.rows.some((row) => row.nameWithGranularity === tokenValue) ||
44513
+ this.props.definition.measures.some((measure) => measure.id === tokenValue && measure.id !== this.props.measure.id)) {
44514
+ return PIVOT_TOKEN_COLOR;
44515
+ }
44516
+ return undefined;
44517
+ }
44518
+ }
44519
+
44520
+ css /* scss */ `
44521
+ .o-pivot-sort {
44522
+ .o-sort-card {
44523
+ width: fit-content;
44524
+ background-color: ${GRAY_100};
44525
+ border: 1px solid ${GRAY_300};
44526
+
44527
+ .o-sort-value {
44528
+ color: ${PRIMARY_BUTTON_BG};
44529
+ }
44530
+ }
44531
+ }
44532
+ `;
44533
+ class PivotSortSection extends Component {
44534
+ static template = "o-spreadsheet-PivotSortSection";
44535
+ static components = {
44536
+ Section,
44537
+ };
44538
+ static props = {
44539
+ definition: Object,
44540
+ pivotId: String,
44541
+ };
44542
+ get hasValidSort() {
44543
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44544
+ return (!!this.props.definition.sortedColumn &&
44545
+ isSortedColumnValid(this.props.definition.sortedColumn, pivot));
44546
+ }
44547
+ get sortDescription() {
44548
+ const sortOrder = this.props.definition.sortedColumn?.order === "asc" ? _t("ascending") : _t("descending");
44549
+ return _t("Sorted on column (%(ascOrDesc)s):", {
44550
+ ascOrDesc: sortOrder,
44551
+ });
44552
+ }
44553
+ get sortValuesAndFields() {
44554
+ const sortedColumn = this.props.definition.sortedColumn;
44555
+ if (!sortedColumn) {
44556
+ return [];
44557
+ }
44558
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44559
+ const locale = this.env.model.getters.getLocale();
44560
+ const currentDomain = [];
44561
+ const sortValues = [];
44562
+ for (const domainItem of sortedColumn.domain) {
44563
+ currentDomain.push(domainItem);
44564
+ const { value, format } = pivot.getPivotHeaderValueAndFormat(currentDomain);
44565
+ const label = formatValue(value, { format, locale });
44566
+ const field = pivot.definition.getDimension(domainItem.field);
44567
+ sortValues.push({ field: getFieldDisplayName(field), value: label });
44568
+ }
44569
+ if (sortedColumn.domain.length === 0) {
44570
+ sortValues.push({ value: _t("Total") });
44571
+ }
44572
+ const measureLabel = pivot.getMeasure(sortedColumn.measure).displayName;
44573
+ sortValues.push({ value: measureLabel, field: _t("Measure") });
44574
+ return sortValues;
44575
+ }
43515
44576
  }
43516
44577
 
43517
44578
  css /* scss */ `
@@ -43527,6 +44588,7 @@ class PivotLayoutConfigurator extends Component {
43527
44588
  PivotDimensionOrder,
43528
44589
  PivotDimensionGranularity,
43529
44590
  PivotMeasureEditor,
44591
+ PivotSortSection,
43530
44592
  };
43531
44593
  static props = {
43532
44594
  definition: Object,
@@ -43683,9 +44745,13 @@ class PivotLayoutConfigurator extends Component {
43683
44745
  }
43684
44746
  updateMeasure(measure, newMeasure) {
43685
44747
  const { measures } = this.props.definition;
43686
- this.props.onDimensionsUpdated({
44748
+ const update = {
43687
44749
  measures: measures.map((m) => (m.id === measure.id ? newMeasure : m)),
43688
- });
44750
+ };
44751
+ if (this.props.definition.sortedColumn?.measure === measure.id) {
44752
+ update.sortedColumn = { ...this.props.definition.sortedColumn, measure: newMeasure.id };
44753
+ }
44754
+ this.props.onDimensionsUpdated(update);
43689
44755
  }
43690
44756
  getMeasureId(fieldName, aggregator) {
43691
44757
  const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
@@ -43841,10 +44907,12 @@ class PivotRuntimeDefinition {
43841
44907
  measures;
43842
44908
  columns;
43843
44909
  rows;
44910
+ sortedColumn;
43844
44911
  constructor(definition, fields) {
43845
44912
  this.measures = definition.measures.map((measure) => createMeasure(fields, measure));
43846
44913
  this.columns = definition.columns.map((dimension) => createPivotDimension(fields, dimension));
43847
44914
  this.rows = definition.rows.map((dimension) => createPivotDimension(fields, dimension));
44915
+ this.sortedColumn = definition.sortedColumn;
43848
44916
  }
43849
44917
  getDimension(nameWithGranularity) {
43850
44918
  const dimension = this.columns.find((d) => d.nameWithGranularity === nameWithGranularity) ||
@@ -43998,6 +45066,7 @@ class SpreadsheetPivotTable {
43998
45066
  pivotCells = {};
43999
45067
  rowTree;
44000
45068
  colTree;
45069
+ isSorted = false;
44001
45070
  constructor(columns, rows, measures, fieldsType) {
44002
45071
  this.columns = columns.map((row) => {
44003
45072
  // offset in the pivot table
@@ -44171,6 +45240,7 @@ class SpreadsheetPivotTable {
44171
45240
  value,
44172
45241
  field: row.fields[rowDepth],
44173
45242
  children: [],
45243
+ type: this.fieldsType[fieldName] || "char",
44174
45244
  width: 0, // not used
44175
45245
  };
44176
45246
  treesAtDepth[depth].push(node);
@@ -44193,6 +45263,7 @@ class SpreadsheetPivotTable {
44193
45263
  field: leaf.fields[depth],
44194
45264
  children: [],
44195
45265
  width: leaf.width,
45266
+ type: this.fieldsType[fieldName] || "char",
44196
45267
  };
44197
45268
  if (treesAtDepth[depth]?.at(-1)?.value !== value) {
44198
45269
  treesAtDepth[depth + 1] = [];
@@ -44211,6 +45282,35 @@ class SpreadsheetPivotTable {
44211
45282
  fieldsType: this.fieldsType,
44212
45283
  };
44213
45284
  }
45285
+ sort(measure, sortedColumn, getValue) {
45286
+ if (this.isSorted) {
45287
+ return;
45288
+ }
45289
+ const getSortValue = (measure, domain) => {
45290
+ const rawValue = getValue(measure, domain).value;
45291
+ return typeof rawValue === "number" ? rawValue : -Infinity;
45292
+ };
45293
+ const sortColDomain = sortedColumn.domain;
45294
+ const sortFn = (rowDomain1, rowDomain2) => {
45295
+ const value1 = getSortValue(measure, [...rowDomain1, ...sortColDomain]);
45296
+ const value2 = getSortValue(measure, [...rowDomain2, ...sortColDomain]);
45297
+ return sortedColumn.order === "asc" ? value1 - value2 : value2 - value1;
45298
+ };
45299
+ const sortedRowTree = sortPivotTree(this.rowTree(), [], sortFn);
45300
+ this.rowTree = lazy(sortedRowTree);
45301
+ this.rows = [...this.rowTreeToRows(sortedRowTree), this.rows[this.rows.length - 1]];
45302
+ this.isSorted = true;
45303
+ }
45304
+ rowTreeToRows(tree, parentRow) {
45305
+ return tree.flatMap((node) => {
45306
+ const row = {
45307
+ indent: parentRow ? parentRow.indent + 1 : 0,
45308
+ fields: [...(parentRow?.fields || []), node.field],
45309
+ values: [...(parentRow?.values || []), node.value],
45310
+ };
45311
+ return [row, ...this.rowTreeToRows(node.children, row)];
45312
+ });
45313
+ }
44214
45314
  }
44215
45315
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
44216
45316
 
@@ -44288,6 +45388,7 @@ function dataEntriesToColumnsTree(dataEntries, columns, index) {
44288
45388
  value: groups[key]?.[0]?.[column.nameWithGranularity]?.value ?? null,
44289
45389
  field: colName,
44290
45390
  children: dataEntriesToColumnsTree(groups[key] || [], columns, index + 1),
45391
+ type: column.type,
44291
45392
  width: 0,
44292
45393
  };
44293
45394
  });
@@ -45153,6 +46254,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
45153
46254
  format: measure.format,
45154
46255
  display: measure.display,
45155
46256
  })),
46257
+ sortedColumn: this.shouldKeepSortedColumn(definition) ? definition.sortedColumn : undefined,
45156
46258
  };
45157
46259
  if (!this.draft && deepEquals(coreDefinition, cleanedDefinition)) {
45158
46260
  return;
@@ -45211,6 +46313,19 @@ class PivotSidePanelStore extends SpreadsheetStore {
45211
46313
  }
45212
46314
  return granularitiesPerFields;
45213
46315
  }
46316
+ /**
46317
+ * Check if we want to keep the sorted column when updating the pivot definition. We should remove it if either
46318
+ * the measure is not in the new definition or the columns have changed.
46319
+ */
46320
+ shouldKeepSortedColumn(newDefinition) {
46321
+ const { sortedColumn } = newDefinition;
46322
+ if (!sortedColumn) {
46323
+ return true;
46324
+ }
46325
+ const oldDefinition = this.getters.getPivotCoreDefinition(this.pivotId);
46326
+ return (newDefinition.measures.find((measure) => measure.id === sortedColumn.measure) &&
46327
+ deepEquals(oldDefinition.columns, newDefinition.columns));
46328
+ }
45214
46329
  }
45215
46330
 
45216
46331
  class PivotSpreadsheetSidePanel extends Component {
@@ -46312,6 +47427,7 @@ css /* scss */ `
46312
47427
  }
46313
47428
  }
46314
47429
  `;
47430
+ const DEFAULT_TABLE_STYLE_COLOR = "#3C78D8";
46315
47431
  class TableStyleEditorPanel extends Component {
46316
47432
  static template = "o-spreadsheet-TableStyleEditorPanel";
46317
47433
  static components = { Section, RoundColorPicker, TableStylePreview };
@@ -46330,7 +47446,7 @@ class TableStyleEditorPanel extends Component {
46330
47446
  : null;
46331
47447
  return {
46332
47448
  pickerOpened: false,
46333
- primaryColor: editedStyle?.primaryColor || "#3C78D8",
47449
+ primaryColor: editedStyle?.primaryColor || DEFAULT_TABLE_STYLE_COLOR,
46334
47450
  selectedTemplateName: editedStyle?.templateName || "lightColoredText",
46335
47451
  styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
46336
47452
  };
@@ -46339,7 +47455,7 @@ class TableStyleEditorPanel extends Component {
46339
47455
  this.state.pickerOpened = !this.state.pickerOpened;
46340
47456
  }
46341
47457
  onColorPicked(color) {
46342
- this.state.primaryColor = color;
47458
+ this.state.primaryColor = isColorValid(color) ? color : DEFAULT_TABLE_STYLE_COLOR;
46343
47459
  this.state.pickerOpened = false;
46344
47460
  }
46345
47461
  onTemplatePicked(templateName) {
@@ -47955,6 +49071,7 @@ class FiguresContainer extends Component {
47955
49071
  draggedFigure: undefined,
47956
49072
  horizontalSnap: undefined,
47957
49073
  verticalSnap: undefined,
49074
+ cancelDnd: undefined,
47958
49075
  });
47959
49076
  setup() {
47960
49077
  onMounted(() => {
@@ -47967,12 +49084,28 @@ class FiguresContainer extends Component {
47967
49084
  // new rendering
47968
49085
  this.render();
47969
49086
  });
49087
+ onWillUpdateProps(() => {
49088
+ const sheetId = this.env.model.getters.getActiveSheetId();
49089
+ const draggedFigureId = this.dnd.draggedFigure?.id;
49090
+ if (draggedFigureId && !this.env.model.getters.getFigure(sheetId, draggedFigureId)) {
49091
+ if (this.dnd.cancelDnd) {
49092
+ this.dnd.cancelDnd();
49093
+ }
49094
+ this.dnd.draggedFigure = undefined;
49095
+ this.dnd.horizontalSnap = undefined;
49096
+ this.dnd.verticalSnap = undefined;
49097
+ this.dnd.cancelDnd = undefined;
49098
+ }
49099
+ });
47970
49100
  }
47971
49101
  getVisibleFigures() {
47972
49102
  const visibleFigures = this.env.model.getters.getVisibleFigures();
47973
49103
  if (this.dnd.draggedFigure &&
47974
49104
  !visibleFigures.some((figure) => figure.id === this.dnd.draggedFigure?.id)) {
47975
- visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id));
49105
+ const draggedFigure = this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id);
49106
+ if (draggedFigure) {
49107
+ visibleFigures.push(draggedFigure);
49108
+ }
47976
49109
  }
47977
49110
  return visibleFigures;
47978
49111
  }
@@ -48091,7 +49224,7 @@ class FiguresContainer extends Component {
48091
49224
  this.dnd.verticalSnap = undefined;
48092
49225
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
48093
49226
  };
48094
- startDnd(onMouseMove, onMouseUp);
49227
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48095
49228
  }
48096
49229
  /**
48097
49230
  * Initialize the resize of a figure with mouse movements
@@ -48139,7 +49272,7 @@ class FiguresContainer extends Component {
48139
49272
  this.dnd.horizontalSnap = undefined;
48140
49273
  this.dnd.verticalSnap = undefined;
48141
49274
  };
48142
- startDnd(onMouseMove, onMouseUp);
49275
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48143
49276
  }
48144
49277
  getOtherFigures(figId) {
48145
49278
  return this.getVisibleFigures().filter((f) => f.id !== figId);
@@ -49246,9 +50379,11 @@ class HeadersOverlay extends Component {
49246
50379
  class GridRenderer {
49247
50380
  getters;
49248
50381
  renderer;
50382
+ fingerprints;
49249
50383
  constructor(get) {
49250
50384
  this.getters = get(ModelStore).getters;
49251
50385
  this.renderer = get(RendererStore);
50386
+ this.fingerprints = get(FormulaFingerprintStore);
49252
50387
  this.renderer.register(this);
49253
50388
  }
49254
50389
  get renderingLayers() {
@@ -49726,14 +50861,22 @@ class GridRenderer {
49726
50861
  const showFormula = this.getters.shouldShowFormulas();
49727
50862
  const { x, y, width, height } = this.getters.getVisibleRect(zone);
49728
50863
  const { verticalAlign } = this.getters.getCellStyle(position);
50864
+ let style = this.getters.getCellComputedStyle(position);
50865
+ if (this.fingerprints.isEnabled) {
50866
+ const fingerprintColor = this.fingerprints.colors.get(position);
50867
+ style = { ...style, fillColor: fingerprintColor };
50868
+ }
50869
+ const dataBarFill = this.fingerprints.isEnabled
50870
+ ? undefined
50871
+ : this.getters.getConditionalDataBar(position);
49729
50872
  const box = {
49730
50873
  x,
49731
50874
  y,
49732
50875
  width,
49733
50876
  height,
49734
50877
  border: this.getters.getCellComputedBorder(position) || undefined,
49735
- style: this.getters.getCellComputedStyle(position),
49736
- dataBarFill: this.getters.getConditionalDataBar(position),
50878
+ style,
50879
+ dataBarFill,
49737
50880
  verticalAlign,
49738
50881
  isError: (cell.type === CellValueType.error && !!cell.message) ||
49739
50882
  this.getters.isDataValidationInvalid(position),
@@ -49758,7 +50901,6 @@ class GridRenderer {
49758
50901
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
49759
50902
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
49760
50903
  /** Content */
49761
- const style = this.getters.getCellComputedStyle(position);
49762
50904
  const wrapping = style.wrapping || "overflow";
49763
50905
  const wrapText = wrapping === "wrap" && !showFormula;
49764
50906
  const maxWidth = width - 2 * MIN_CELL_TEXT_MARGIN;
@@ -51736,62 +52878,6 @@ class BordersPlugin extends CorePlugin {
51736
52878
  }
51737
52879
  }
51738
52880
 
51739
- class PositionMap {
51740
- map = {};
51741
- constructor(entries = []) {
51742
- for (const [position, value] of entries) {
51743
- this.set(position, value);
51744
- }
51745
- }
51746
- set({ sheetId, col, row }, value) {
51747
- const map = this.map;
51748
- if (!map[sheetId]) {
51749
- map[sheetId] = {};
51750
- }
51751
- if (!map[sheetId][col]) {
51752
- map[sheetId][col] = {};
51753
- }
51754
- map[sheetId][col][row] = value;
51755
- }
51756
- get({ sheetId, col, row }) {
51757
- return this.map[sheetId]?.[col]?.[row];
51758
- }
51759
- getSheet(sheetId) {
51760
- return this.map[sheetId];
51761
- }
51762
- has({ sheetId, col, row }) {
51763
- return this.map[sheetId]?.[col]?.[row] !== undefined;
51764
- }
51765
- delete({ sheetId, col, row }) {
51766
- delete this.map[sheetId]?.[col]?.[row];
51767
- }
51768
- keys() {
51769
- const map = this.map;
51770
- const keys = [];
51771
- for (const sheetId in map) {
51772
- for (const col in map[sheetId]) {
51773
- for (const row in map[sheetId][col]) {
51774
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51775
- }
51776
- }
51777
- }
51778
- return keys;
51779
- }
51780
- keysForSheet(sheetId) {
51781
- const map = this.map[sheetId];
51782
- if (!map) {
51783
- return [];
51784
- }
51785
- const keys = [];
51786
- for (const col in map) {
51787
- for (const row in map[col]) {
51788
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51789
- }
51790
- }
51791
- return keys;
51792
- }
51793
- }
51794
-
51795
52881
  /**
51796
52882
  * Core Plugin
51797
52883
  *
@@ -51886,7 +52972,7 @@ class CellPlugin extends CorePlugin {
51886
52972
  for (let col = zone.left; col <= zone.right; col++) {
51887
52973
  for (let row = zone.top; row <= zone.bottom; row++) {
51888
52974
  const cell = this.getters.getCell({ sheetId, col, row });
51889
- if (cell) {
52975
+ if (cell?.isFormula || cell?.content) {
51890
52976
  this.dispatch("UPDATE_CELL", {
51891
52977
  sheetId: sheetId,
51892
52978
  content: "",
@@ -51922,7 +53008,6 @@ class CellPlugin extends CorePlugin {
51922
53008
  for (let zone of recomputeZones(zones)) {
51923
53009
  for (let col = zone.left; col <= zone.right; col++) {
51924
53010
  for (let row = zone.top; row <= zone.bottom; row++) {
51925
- // commandHelpers.updateCell(sheetId, col, row, { style: undefined});
51926
53011
  this.dispatch("UPDATE_CELL", {
51927
53012
  sheetId,
51928
53013
  col,
@@ -52231,7 +53316,7 @@ class CellPlugin extends CorePlugin {
52231
53316
  const before = this.getters.getCell({ sheetId, col, row });
52232
53317
  const hasContent = "content" in after || "formula" in after;
52233
53318
  // Compute the new cell properties
52234
- const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || "";
53319
+ const afterContent = hasContent ? replaceNewLines(after?.content) : before?.content || "";
52235
53320
  let style;
52236
53321
  if (after.style !== undefined) {
52237
53322
  style = after.style || undefined;
@@ -55176,6 +56261,9 @@ class SheetPlugin extends CorePlugin {
55176
56261
  };
55177
56262
  }
55178
56263
  getUnboundedZone(sheetId, zone) {
56264
+ if (zone.bottom === undefined || zone.right === undefined) {
56265
+ return zone;
56266
+ }
55179
56267
  const isFullRow = zone.left === 0 && zone.right === this.getNumberCols(sheetId) - 1;
55180
56268
  const isFullCol = zone.top === 0 && zone.bottom === this.getNumberRows(sheetId) - 1;
55181
56269
  return {
@@ -55341,7 +56429,7 @@ class SheetPlugin extends CorePlugin {
55341
56429
  if (orderedSheetIds.find((id) => sheets[id]?.name.toLowerCase() === name && id !== cmd.sheetId)) {
55342
56430
  return "DuplicatedSheetName" /* CommandResult.DuplicatedSheetName */;
55343
56431
  }
55344
- if (FORBIDDEN_IN_EXCEL_REGEX.test(name)) {
56432
+ if (FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX.test(name)) {
55345
56433
  return "ForbiddenCharactersInSheetName" /* CommandResult.ForbiddenCharactersInSheetName */;
55346
56434
  }
55347
56435
  return "Success" /* CommandResult.Success */;
@@ -59459,66 +60547,25 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59459
60547
  return;
59460
60548
  }
59461
60549
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
59462
- const colorCellArgs = [];
60550
+ const colorThresholds = [{ value: minValue, color: rule.minimum.color }];
59463
60551
  if (rule.midpoint && midValue) {
59464
- colorCellArgs.push({
59465
- minValue,
59466
- minColor: rule.minimum.color,
59467
- colorDiffUnit: this.computeColorDiffUnits(minValue, midValue, rule.minimum.color, rule.midpoint.color),
59468
- });
59469
- colorCellArgs.push({
59470
- minValue: midValue,
59471
- minColor: rule.midpoint.color,
59472
- colorDiffUnit: this.computeColorDiffUnits(midValue, maxValue, rule.midpoint.color, rule.maximum.color),
59473
- });
59474
- }
59475
- else {
59476
- colorCellArgs.push({
59477
- minValue,
59478
- minColor: rule.minimum.color,
59479
- colorDiffUnit: this.computeColorDiffUnits(minValue, maxValue, rule.minimum.color, rule.maximum.color),
59480
- });
60552
+ colorThresholds.push({ value: midValue, color: rule.midpoint.color });
59481
60553
  }
60554
+ colorThresholds.push({ value: maxValue, color: rule.maximum.color });
60555
+ const colorScale = getColorScale(colorThresholds);
59482
60556
  for (let row = zone.top; row <= zone.bottom; row++) {
59483
60557
  for (let col = zone.left; col <= zone.right; col++) {
59484
60558
  const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
59485
60559
  if (cell.type === CellValueType.number) {
59486
60560
  const value = clip(cell.value, minValue, maxValue);
59487
- let color;
59488
- if (colorCellArgs.length === 2 && midValue) {
59489
- color =
59490
- value <= midValue
59491
- ? this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit)
59492
- : this.colorCell(value, colorCellArgs[1].minValue, colorCellArgs[1].minColor, colorCellArgs[1].colorDiffUnit);
59493
- }
59494
- else {
59495
- color = this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit);
59496
- }
59497
60561
  if (!computedStyle[col])
59498
60562
  computedStyle[col] = [];
59499
60563
  computedStyle[col][row] = computedStyle[col]?.[row] || {};
59500
- computedStyle[col][row].fillColor = colorNumberString(color);
60564
+ computedStyle[col][row].fillColor = colorScale(value);
59501
60565
  }
59502
60566
  }
59503
60567
  }
59504
60568
  }
59505
- computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
59506
- const deltaValue = maxValue - minValue;
59507
- const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);
59508
- const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);
59509
- const deltaColorB = (minColor % 256) - (maxColor % 256);
59510
- const colorDiffUnitR = deltaColorR / deltaValue;
59511
- const colorDiffUnitG = deltaColorG / deltaValue;
59512
- const colorDiffUnitB = deltaColorB / deltaValue;
59513
- return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];
59514
- }
59515
- colorCell(value, minValue, minColor, colorDiffUnit) {
59516
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
59517
- const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));
59518
- const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));
59519
- const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
59520
- return (r << 16) | (g << 8) | b;
59521
- }
59522
60569
  /**
59523
60570
  * Execute the predicate to know if a conditional formatting rule should be applied to a cell
59524
60571
  */
@@ -60144,7 +61191,7 @@ function withPivotPresentationLayer (PivotClass) {
60144
61191
  }
60145
61192
  getValuesToAggregate(measure, domain) {
60146
61193
  const { rowDomain, colDomain } = domainToColRowDomain(this, domain);
60147
- const table = this.getTableStructure();
61194
+ const table = super.getTableStructure();
60148
61195
  const values = [];
60149
61196
  if (colDomain.length === 0 &&
60150
61197
  rowDomain.length < this.definition.rows.length &&
@@ -60578,6 +61625,21 @@ function withPivotPresentationLayer (PivotClass) {
60578
61625
  }
60579
61626
  throw new Error(`Value ${result.value} is not a number`);
60580
61627
  }
61628
+ getTableStructure() {
61629
+ const table = super.getTableStructure();
61630
+ this.sortTableStructure(table);
61631
+ return table;
61632
+ }
61633
+ sortTableStructure(table) {
61634
+ if (!this.definition.sortedColumn || table.isSorted) {
61635
+ return;
61636
+ }
61637
+ const measure = this.definition.sortedColumn.measure;
61638
+ const isSortValid = isSortedColumnValid(this.definition.sortedColumn, this);
61639
+ if (isSortValid) {
61640
+ table.sort(measure, this.definition.sortedColumn, (measure, domain) => this._getPivotCellValueAndFormat(measure, domain));
61641
+ }
61642
+ }
60581
61643
  }
60582
61644
  return PivotPresentationLayer;
60583
61645
  }
@@ -60749,11 +61811,13 @@ class PivotUIPlugin extends UIPlugin {
60749
61811
  return EMPTY_PIVOT_CELL;
60750
61812
  }
60751
61813
  if (functionName === "PIVOT") {
60752
- const includeTotal = args[2] === false ? false : undefined;
60753
- const includeColumnHeaders = args[3] === false ? false : undefined;
61814
+ const includeTotal = toScalar(args[2]);
61815
+ const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
61816
+ const includeColumnHeaders = toScalar(args[3]);
61817
+ const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
60754
61818
  const pivotCells = pivot
60755
61819
  .getTableStructure()
60756
- .getPivotCells(includeTotal, includeColumnHeaders);
61820
+ .getPivotCells(shouldIncludeTotal, shouldIncludeColumnHeaders);
60757
61821
  const pivotCol = position.col - mainPosition.col;
60758
61822
  const pivotRow = position.row - mainPosition.row;
60759
61823
  return pivotCells[pivotCol][pivotRow];
@@ -62351,23 +63415,6 @@ class Session extends EventBus {
62351
63415
  }
62352
63416
  }
62353
63417
 
62354
- function randomChoice(arr) {
62355
- return arr[Math.floor(Math.random() * arr.length)];
62356
- }
62357
- const colors = [
62358
- "#ff851b",
62359
- "#0074d9",
62360
- "#7fdbff",
62361
- "#b10dc9",
62362
- "#39cccc",
62363
- "#f012be",
62364
- "#3d9970",
62365
- "#111111",
62366
- "#ff4136",
62367
- "#aaaaaa",
62368
- "#85144b",
62369
- "#001f3f",
62370
- ];
62371
63418
  class CollaborativePlugin extends UIPlugin {
62372
63419
  static getters = [
62373
63420
  "getClientsToDisplay",
@@ -62376,7 +63423,7 @@ class CollaborativePlugin extends UIPlugin {
62376
63423
  "isFullySynchronized",
62377
63424
  ];
62378
63425
  static layers = ["Selection"];
62379
- availableColors = new Set(colors);
63426
+ availableColors = new AlternatingColorGenerator(12);
62380
63427
  colors = {};
62381
63428
  session;
62382
63429
  constructor(config) {
@@ -62387,14 +63434,6 @@ class CollaborativePlugin extends UIPlugin {
62387
63434
  return (position.row < this.getters.getNumberRows(position.sheetId) &&
62388
63435
  position.col < this.getters.getNumberCols(position.sheetId));
62389
63436
  }
62390
- chooseNewColor() {
62391
- if (this.availableColors.size === 0) {
62392
- this.availableColors = new Set(colors);
62393
- }
62394
- const color = randomChoice([...this.availableColors.values()]);
62395
- this.availableColors.delete(color);
62396
- return color;
62397
- }
62398
63437
  getClient() {
62399
63438
  return this.session.getClient();
62400
63439
  }
@@ -62429,7 +63468,7 @@ class CollaborativePlugin extends UIPlugin {
62429
63468
  this.isPositionValid(client.position)) {
62430
63469
  const position = client.position;
62431
63470
  if (!this.colors[client.id]) {
62432
- this.colors[client.id] = this.chooseNewColor();
63471
+ this.colors[client.id] = this.availableColors.next();
62433
63472
  }
62434
63473
  const color = this.colors[client.id];
62435
63474
  clients.push({ ...client, position, color });
@@ -62666,7 +63705,7 @@ class FormatPlugin extends UIPlugin {
62666
63705
  for (let row = zone.top; row <= zone.bottom; row++) {
62667
63706
  const position = { sheetId, col, row };
62668
63707
  const pivotCell = this.getters.getPivotCellFromPosition(position);
62669
- if (pivotCell.type === "VALUE") {
63708
+ if (this.isSpilledPivotValueFormula(position, pivotCell)) {
62670
63709
  measurePositions.push(position);
62671
63710
  const pivotId = this.getters.getPivotIdFromPosition(position) || "";
62672
63711
  measuresByPivotId[pivotId] ??= new Set();
@@ -62703,6 +63742,10 @@ class FormatPlugin extends UIPlugin {
62703
63742
  format,
62704
63743
  });
62705
63744
  }
63745
+ isSpilledPivotValueFormula(position, pivotCell) {
63746
+ const cell = this.getters.getCell(position);
63747
+ return pivotCell.type === "VALUE" && !cell?.isFormula;
63748
+ }
62706
63749
  /**
62707
63750
  * This function allows to adjust the quantity of decimal places after a decimal
62708
63751
  * point on cells containing number value. It does this by changing the cells
@@ -62753,6 +63796,69 @@ class FormatPlugin extends UIPlugin {
62753
63796
  }
62754
63797
  }
62755
63798
 
63799
+ class GeoFeaturePlugin extends UIPlugin {
63800
+ static getters = [
63801
+ "getGeoJsonFeatures",
63802
+ "geoFeatureNameToId",
63803
+ "getGeoChartAvailableRegions",
63804
+ ];
63805
+ geoJsonService;
63806
+ geoJsonCache = {};
63807
+ constructor(config) {
63808
+ super(config);
63809
+ this.geoJsonService = config.external.geoJsonService;
63810
+ }
63811
+ getGeoChartAvailableRegions() {
63812
+ if (!this.geoJsonService) {
63813
+ console.error("No geoJsonService provided to the model");
63814
+ return [];
63815
+ }
63816
+ return this.geoJsonService.getAvailableRegions() || [];
63817
+ }
63818
+ getGeoJsonFeatures(region) {
63819
+ if (!this.geoJsonService) {
63820
+ console.error("No geoJsonService provided to the model");
63821
+ return;
63822
+ }
63823
+ const cachedGeoJson = this.geoJsonCache[region];
63824
+ if (cachedGeoJson instanceof Promise) {
63825
+ return undefined;
63826
+ }
63827
+ if (cachedGeoJson !== undefined) {
63828
+ return cachedGeoJson ?? undefined;
63829
+ }
63830
+ this.geoJsonCache[region] = new Promise(async (resolve) => {
63831
+ const json = await this.geoJsonService?.getTopoJson(region);
63832
+ this.geoJsonCache[region] = this.convertToGeoJson(json);
63833
+ this.dispatch("EVALUATE_CHARTS");
63834
+ resolve();
63835
+ });
63836
+ return undefined;
63837
+ }
63838
+ geoFeatureNameToId(region, featureName) {
63839
+ if (!this.geoJsonService) {
63840
+ console.error("No geoJsonService provided to the model");
63841
+ return;
63842
+ }
63843
+ return this.geoJsonService.geoFeatureNameToId(region, featureName);
63844
+ }
63845
+ convertToGeoJson(json) {
63846
+ if (!json) {
63847
+ return null;
63848
+ }
63849
+ // TopoJSON
63850
+ if (json.type === "Topology") {
63851
+ const features = window.ChartGeo.topojson.feature(json, Object.values(json.objects)[0]);
63852
+ return features.type === "FeatureCollection" ? features.features : [features];
63853
+ }
63854
+ // GeoJSON
63855
+ else if (json.type === "FeatureCollection") {
63856
+ return json.features;
63857
+ }
63858
+ throw new Error("Invalid TopoJSON");
63859
+ }
63860
+ }
63861
+
62756
63862
  class HeaderVisibilityUIPlugin extends UIPlugin {
62757
63863
  static getters = [
62758
63864
  "getNextVisibleCellPosition",
@@ -62907,7 +64013,7 @@ class InsertPivotPlugin extends UIPlugin {
62907
64013
  getPivotDuplicateSheetName(pivotName) {
62908
64014
  let i = 1;
62909
64015
  const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
62910
- const sanitizedName = pivotName.replace(new RegExp(FORBIDDEN_IN_EXCEL_REGEX, "g"), " ");
64016
+ const sanitizedName = sanitizeSheetName(pivotName);
62911
64017
  let name = sanitizedName;
62912
64018
  while (names.includes(name)) {
62913
64019
  name = `${sanitizedName} (${i})`;
@@ -64290,10 +65396,13 @@ class TableResizeUI extends UIPlugin {
64290
65396
  case "RESIZE_TABLE": {
64291
65397
  const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
64292
65398
  this.dispatch("UPDATE_TABLE", { ...cmd });
64293
- if (!table || !table.config.automaticAutofill)
65399
+ if (!table)
64294
65400
  return;
64295
- const oldTableZone = table.range.zone;
64296
65401
  const newTableZone = this.getters.getRangeFromRangeData(cmd.newTableRange).zone;
65402
+ this.selection.selectCell(newTableZone.right, newTableZone.bottom);
65403
+ if (!table.config.automaticAutofill)
65404
+ return;
65405
+ const oldTableZone = table.range.zone;
64297
65406
  if (newTableZone.bottom >= oldTableZone.bottom) {
64298
65407
  for (let col = newTableZone.left; col <= newTableZone.right; col++) {
64299
65408
  const autofillSource = { col, row: oldTableZone.bottom, sheetId: cmd.sheetId };
@@ -65499,15 +66608,23 @@ class GridSelectionPlugin extends UIPlugin {
65499
66608
  handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
65500
66609
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
65501
66610
  let currentIndex = cmd.base;
66611
+ const resizingGroups = {};
65502
66612
  for (const element of toRemove) {
65503
66613
  const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
66614
+ const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
66615
+ if (size != currentSize) {
66616
+ resizingGroups[size] ??= [];
66617
+ resizingGroups[size].push(currentIndex);
66618
+ currentIndex += 1;
66619
+ }
66620
+ }
66621
+ for (const size in resizingGroups) {
65504
66622
  this.dispatch("RESIZE_COLUMNS_ROWS", {
65505
66623
  dimension: cmd.dimension,
65506
66624
  sheetId: cmd.sheetId,
65507
- size,
65508
- elements: [currentIndex],
66625
+ size: parseInt(size, 10),
66626
+ elements: resizingGroups[size],
65509
66627
  });
65510
- currentIndex += 1;
65511
66628
  }
65512
66629
  this.dispatch("REMOVE_COLUMNS_ROWS", {
65513
66630
  dimension: cmd.dimension,
@@ -66089,6 +67206,7 @@ class SheetViewPlugin extends UIPlugin {
66089
67206
  break;
66090
67207
  case "DELETE_SHEET":
66091
67208
  this.cleanViewports();
67209
+ this.sheetsWithDirtyViewports.delete(cmd.sheetId);
66092
67210
  break;
66093
67211
  case "ACTIVATE_SHEET":
66094
67212
  this.sheetsWithDirtyViewports.add(cmd.sheetIdTo);
@@ -66739,7 +67857,8 @@ const featurePluginRegistry = new Registry()
66739
67857
  .add("data_cleanup", DataCleanupPlugin)
66740
67858
  .add("table_autofill", TableAutofillPlugin)
66741
67859
  .add("table_ui_resize", TableResizeUI)
66742
- .add("datavalidation_insert", DataValidationInsertionPlugin);
67860
+ .add("datavalidation_insert", DataValidationInsertionPlugin)
67861
+ .add("geo_features", GeoFeaturePlugin);
66743
67862
  // Plugins which have a state, but which should not be shared in collaborative
66744
67863
  const statefulUIPluginRegistry = new Registry()
66745
67864
  .add("selection", GridSelectionPlugin)
@@ -66983,7 +68102,7 @@ function interactiveRenameSheet(env, sheetId, name, errorCallback) {
66983
68102
  env.raiseError(_t("A sheet with the name %s already exists. Please select another name.", name), errorCallback);
66984
68103
  }
66985
68104
  else if (result.reasons.includes("ForbiddenCharactersInSheetName" /* CommandResult.ForbiddenCharactersInSheetName */)) {
66986
- env.raiseError(_t("Some used characters are not allowed in a sheet name (Forbidden characters are %s).", FORBIDDEN_SHEET_CHARS.join(" ")), errorCallback);
68105
+ env.raiseError(_t("Some used characters are not allowed in a sheet name (Forbidden characters are %s).", FORBIDDEN_SHEETNAME_CHARS.join(" ")), errorCallback);
66987
68106
  }
66988
68107
  }
66989
68108
 
@@ -68213,7 +69332,7 @@ class ActionButton extends Component {
68213
69332
  setup() {
68214
69333
  onWillUpdateProps((nextProps) => {
68215
69334
  if (nextProps.action !== this.props.action) {
68216
- this.actionButton = createAction(this.props.action);
69335
+ this.actionButton = createAction(nextProps.action);
68217
69336
  }
68218
69337
  });
68219
69338
  }
@@ -68483,8 +69602,8 @@ css /* scss */ `
68483
69602
  .o-topbar-composer {
68484
69603
  height: fit-content;
68485
69604
  margin-top: -1px;
69605
+ margin-bottom: -1px;
68486
69606
  border: 1px solid;
68487
- z-index: ${ComponentsImportance.TopBarComposer};
68488
69607
  font-family: ${DEFAULT_FONT};
68489
69608
 
68490
69609
  .o-composer:empty:not(:focus):not(.active)::before {
@@ -68542,6 +69661,7 @@ class TopBarComposer extends Component {
68542
69661
  }
68543
69662
  return cssPropertiesToCss({
68544
69663
  "border-color": SELECTION_BORDER_COLOR,
69664
+ "z-index": String(ComponentsImportance.TopBarComposer),
68545
69665
  });
68546
69666
  }
68547
69667
  onFocus(selection) {
@@ -68549,88 +69669,6 @@ class TopBarComposer extends Component {
68549
69669
  }
68550
69670
  }
68551
69671
 
68552
- css /* scss */ `
68553
- .o-font-size-editor {
68554
- height: calc(100% - 4px);
68555
- input.o-font-size {
68556
- outline-color: ${SELECTION_BORDER_COLOR};
68557
- height: 20px;
68558
- width: 23px;
68559
- }
68560
- }
68561
- .o-text-options > div {
68562
- cursor: pointer;
68563
- line-height: 26px;
68564
- padding: 3px 12px;
68565
- &:hover {
68566
- background-color: rgba(0, 0, 0, 0.08);
68567
- }
68568
- }
68569
- `;
68570
- class FontSizeEditor extends Component {
68571
- static template = "o-spreadsheet-FontSizeEditor";
68572
- static props = {
68573
- onToggle: Function,
68574
- dropdownStyle: String,
68575
- class: String,
68576
- };
68577
- static components = {};
68578
- fontSizes = FONT_SIZES;
68579
- dropdown = useState({ isOpen: false });
68580
- inputRef = useRef("inputFontSize");
68581
- rootEditorRef = useRef("FontSizeEditor");
68582
- setup() {
68583
- useExternalListener(window, "click", this.onExternalClick, { capture: true });
68584
- }
68585
- onExternalClick(ev) {
68586
- if (!isChildEvent(this.rootEditorRef.el, ev)) {
68587
- this.closeFontList();
68588
- }
68589
- }
68590
- get currentFontSize() {
68591
- return this.env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
68592
- }
68593
- toggleFontList() {
68594
- const isOpen = this.dropdown.isOpen;
68595
- if (!isOpen) {
68596
- this.props.onToggle();
68597
- this.inputRef.el.focus();
68598
- }
68599
- else {
68600
- this.closeFontList();
68601
- }
68602
- }
68603
- closeFontList() {
68604
- this.dropdown.isOpen = false;
68605
- }
68606
- setSize(fontSizeStr) {
68607
- const fontSize = clip(Math.floor(parseFloat(fontSizeStr)), 1, 400);
68608
- setStyle(this.env, { fontSize });
68609
- this.closeFontList();
68610
- }
68611
- setSizeFromInput(ev) {
68612
- this.setSize(ev.target.value);
68613
- }
68614
- setSizeFromList(fontSizeStr) {
68615
- this.setSize(fontSizeStr);
68616
- }
68617
- onInputFocused(ev) {
68618
- this.dropdown.isOpen = true;
68619
- ev.target.select();
68620
- }
68621
- onInputKeydown(ev) {
68622
- if (ev.key === "Enter" || ev.key === "Escape") {
68623
- this.closeFontList();
68624
- const target = ev.target;
68625
- // In the case of a ESCAPE key, we get the previous font size back
68626
- if (ev.key === "Escape") {
68627
- target.value = `${this.currentFontSize}`;
68628
- }
68629
- this.props.onToggle();
68630
- }
68631
- }
68632
- }
68633
-
68634
69672
  class TableDropdownButton extends Component {
68635
69673
  static template = "o-spreadsheet-TableDropdownButton";
68636
69674
  static components = { TableStylesPopover, ActionButton };
@@ -68732,6 +69770,15 @@ css /* scss */ `
68732
69770
  }
68733
69771
  }
68734
69772
 
69773
+ .irregularity-map {
69774
+ border-top: 1px solid ${SEPARATOR_COLOR};
69775
+ height: ${TOPBAR_TOOLBAR_HEIGHT}px;
69776
+
69777
+ .alert-info {
69778
+ border-left: 3px solid ${ALERT_INFO_BORDER};
69779
+ }
69780
+ }
69781
+
68735
69782
  .o-topbar-composer {
68736
69783
  flex-grow: 1;
68737
69784
  }
@@ -68799,9 +69846,6 @@ class TopBar extends Component {
68799
69846
  onClick: Function,
68800
69847
  dropdownMaxHeight: Number,
68801
69848
  };
68802
- get dropdownStyle() {
68803
- return `max-height:${this.props.dropdownMaxHeight}px`;
68804
- }
68805
69849
  static components = {
68806
69850
  ColorPickerWidget,
68807
69851
  ColorPicker,
@@ -68828,8 +69872,10 @@ class TopBar extends Component {
68828
69872
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
68829
69873
  isntToolbarMenu = false;
68830
69874
  composerFocusStore;
69875
+ fingerprints;
68831
69876
  setup() {
68832
69877
  this.composerFocusStore = useStore(ComposerFocusStore);
69878
+ this.fingerprints = useStore(FormulaFingerprintStore);
68833
69879
  useExternalListener(window, "click", this.onExternalClick);
68834
69880
  onWillStart(() => this.updateCellState());
68835
69881
  onWillUpdateProps(() => this.updateCellState());
@@ -68839,6 +69885,9 @@ class TopBar extends Component {
68839
69885
  .getAllOrdered()
68840
69886
  .filter((item) => !item.isVisible || item.isVisible(this.env));
68841
69887
  }
69888
+ get currentFontSize() {
69889
+ return this.env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
69890
+ }
68842
69891
  onExternalClick(ev) {
68843
69892
  // TODO : manage click events better. We need this piece of code
68844
69893
  // otherwise the event opening the menu would close it on the same frame.
@@ -68917,6 +69966,9 @@ class TopBar extends Component {
68917
69966
  setStyle(this.env, { [target]: color });
68918
69967
  this.onClick();
68919
69968
  }
69969
+ setFontSize(fontSize) {
69970
+ setStyle(this.env, { fontSize });
69971
+ }
68920
69972
  }
68921
69973
 
68922
69974
  function instantiateClipboard() {
@@ -69294,7 +70346,7 @@ class Spreadsheet extends Component {
69294
70346
  properties["grid-template-rows"] = `auto`;
69295
70347
  }
69296
70348
  else {
69297
- properties["grid-template-rows"] = `${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
70349
+ properties["grid-template-rows"] = `max-content auto ${BOTTOMBAR_HEIGHT + 1}px`;
69298
70350
  }
69299
70351
  properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
69300
70352
  return cssPropertiesToCss(properties);
@@ -70372,9 +71424,6 @@ class SelectionStreamProcessorImpl {
70372
71424
  getBackToDefault() {
70373
71425
  this.stream.getBackToDefault();
70374
71426
  }
70375
- getAnchor() {
70376
- return this.anchor;
70377
- }
70378
71427
  modifyAnchor(anchor, mode, options) {
70379
71428
  const sheetId = this.getters.getActiveSheetId();
70380
71429
  anchor = {
@@ -70917,12 +71966,11 @@ function createChart(chart, chartSheetIndex, data) {
70917
71966
  // <manualLayout/> to manually position the chart in the figure container
70918
71967
  let title = escapeXml ``;
70919
71968
  if (chart.data.title?.text) {
70920
- const color = chart.data.title.color
70921
- ? toXlsxHexColor(chart.data.title.color)
70922
- : chart.data.fontColor;
71969
+ const titleColor = toXlsxHexColor(chartMutedFontColor(chart.data.backgroundColor));
71970
+ const fontSize = chart.data.title.fontSize ?? CHART_TITLE_FONT_SIZE;
70923
71971
  title = escapeXml /*xml*/ `
70924
71972
  <c:title>
70925
- ${insertText(chart.data.title.text, color, DEFAULT_CHART_FONT_SIZE, chart.data.title)}
71973
+ ${insertText(chart.data.title.text, titleColor, fontSize, chart.data.title)}
70926
71974
  <c:overlay val="0" />
70927
71975
  </c:title>
70928
71976
  `;
@@ -71012,7 +72060,7 @@ function lineAttributes(params) {
71012
72060
  </a:ln>
71013
72061
  `;
71014
72062
  }
71015
- function insertText(text, fontColor = "000000", fontsize = DEFAULT_CHART_FONT_SIZE, style = {}) {
72063
+ function insertText(text, fontColor = "000000", fontsize = CHART_TITLE_FONT_SIZE, style = {}) {
71016
72064
  return escapeXml /*xml*/ `
71017
72065
  <c:tx>
71018
72066
  <c:rich>
@@ -71513,6 +72561,7 @@ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, del
71513
72561
  // Each Axis present inside a graph needs to be identified by an unsigned integer in order to be referenced by its crossAxis.
71514
72562
  // I.e. x-axis, will reference y-axis and vice-versa.
71515
72563
  const color = title?.color ? toXlsxHexColor(title.color) : defaultFontColor;
72564
+ const fontSize = title?.fontSize ?? CHART_AXIS_TITLE_FONT_SIZE;
71516
72565
  return escapeXml /*xml*/ `
71517
72566
  <${axisName}>
71518
72567
  <c:axId val="${axId}"/>
@@ -71528,7 +72577,7 @@ function addAx(position, axisName, axId, crossAxId, title, defaultFontColor, del
71528
72577
  <c:minorTickMark val="none" />
71529
72578
  <c:numFmt formatCode="General" sourceLinked="1" />
71530
72579
  <c:title>
71531
- ${insertText(title?.text ?? "", color, 10, title)}
72580
+ ${insertText(title?.text ?? "", color, fontSize, title)}
71532
72581
  </c:title>
71533
72582
  ${insertTextProperties(10, defaultFontColor)}
71534
72583
  </${axisName}>
@@ -73306,6 +74355,7 @@ class Model extends EventBus {
73306
74355
  session: this.session,
73307
74356
  defaultCurrency: this.config.defaultCurrency,
73308
74357
  customColors: this.config.customColors || [],
74358
+ external: this.config.external,
73309
74359
  };
73310
74360
  }
73311
74361
  // ---------------------------------------------------------------------------
@@ -73537,7 +74587,6 @@ const SPREADSHEET_DIMENSIONS = {
73537
74587
  MIN_COL_WIDTH,
73538
74588
  HEADER_HEIGHT,
73539
74589
  HEADER_WIDTH,
73540
- TOPBAR_HEIGHT,
73541
74590
  BOTTOMBAR_HEIGHT,
73542
74591
  DEFAULT_CELL_WIDTH,
73543
74592
  DEFAULT_CELL_HEIGHT,
@@ -73639,6 +74688,7 @@ const helpers = {
73639
74688
  createPivotFormula,
73640
74689
  areDomainArgsFieldsValid,
73641
74690
  splitReference,
74691
+ sanitizeSheetName,
73642
74692
  };
73643
74693
  const links = {
73644
74694
  isMarkdownLink,
@@ -73733,6 +74783,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73733
74783
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, 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 };
73734
74784
 
73735
74785
 
73736
- __info__.version = "18.1.0-alpha.6";
73737
- __info__.date = "2024-11-28T09:06:59.527Z";
73738
- __info__.hash = "875c901";
74786
+ __info__.version = "18.1.0-alpha.8";
74787
+ __info__.date = "2024-12-19T07:49:54.421Z";
74788
+ __info__.hash = "87a1567";