@odoo/o-spreadsheet 18.1.0-alpha.7 → 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.7
6
- * @date 2024-12-05T10:40:26.512Z
7
- * @hash 7b1c39b
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';
@@ -265,7 +265,6 @@ const MIN_ROW_HEIGHT = 10;
265
265
  const MIN_COL_WIDTH = 5;
266
266
  const HEADER_HEIGHT = 26;
267
267
  const HEADER_WIDTH = 48;
268
- const TOPBAR_HEIGHT = 63;
269
268
  const TOPBAR_TOOLBAR_HEIGHT = 34;
270
269
  const BOTTOMBAR_HEIGHT = 36;
271
270
  const DEFAULT_CELL_WIDTH = 96;
@@ -789,6 +788,7 @@ function removeFalsyAttributes(obj) {
789
788
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
790
789
  */
791
790
  const whiteSpaceSpecialCharacters = [
791
+ " ",
792
792
  "\t",
793
793
  "\f",
794
794
  "\v",
@@ -803,17 +803,15 @@ const whiteSpaceSpecialCharacters = [
803
803
  String.fromCharCode(parseInt("3000", 16)),
804
804
  String.fromCharCode(parseInt("feff", 16)),
805
805
  ];
806
- 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;
807
808
  /**
808
- * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
809
- * different newlines types by \n.
809
+ * Replace all different newlines characters by \n
810
810
  */
811
- function replaceSpecialSpaces(text) {
811
+ function replaceNewLines(text) {
812
812
  if (!text)
813
813
  return "";
814
- if (!whiteSpaceRegexp.test(text))
815
- return text;
816
- return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
814
+ return text.replace(newLineRegexp, NEWLINE);
817
815
  }
818
816
  /**
819
817
  * Determine if the numbers are consecutive.
@@ -979,7 +977,7 @@ function transpose2dPOJO(pojo) {
979
977
 
980
978
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
981
979
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
982
- const colors$1 = [
980
+ const colors = [
983
981
  "#eb6d00",
984
982
  "#0074d9",
985
983
  "#ad8e00",
@@ -1002,6 +1000,12 @@ const colors$1 = [
1002
1000
  function colorNumberString(color) {
1003
1001
  return toHex(color.toString(16).padStart(6, "0"));
1004
1002
  }
1003
+ function colorToNumber(color) {
1004
+ if (typeof color === "number") {
1005
+ return color;
1006
+ }
1007
+ return Number.parseInt(toHex(color).slice(1), 16);
1008
+ }
1005
1009
  /**
1006
1010
  * Converts any CSS color value to a standardized hex6 value.
1007
1011
  * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
@@ -1352,9 +1356,9 @@ const COLORS_XL = [
1352
1356
  "#056BD9", // Blue #3
1353
1357
  "#155193", // Blue #4
1354
1358
  "#A76DBC", // Violet #1
1355
- "#7F4295", // Violet #1
1356
- "#6D2387", // Violet #1
1357
- "#4F1565", // Violet #1
1359
+ "#7F4295", // Violet #2
1360
+ "#6D2387", // Violet #3
1361
+ "#4F1565", // Violet #4
1358
1362
  "#EA6175", // Red #1
1359
1363
  "#CE4257", // Red #2
1360
1364
  "#982738", // Red #3
@@ -1380,6 +1384,81 @@ const COLORS_XL = [
1380
1384
  "#C08A16", // Yellow #3
1381
1385
  "#936A12", // Yellow #4
1382
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
+ ];
1383
1462
  function getNthColor(index, palette) {
1384
1463
  return palette[index % palette.length];
1385
1464
  }
@@ -1397,6 +1476,20 @@ function getColorsPalette(quantity) {
1397
1476
  return COLORS_XL;
1398
1477
  }
1399
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
+ }
1400
1493
  class ColorGenerator {
1401
1494
  preferredColors;
1402
1495
  currentColorIndex = 0;
@@ -1411,6 +1504,62 @@ class ColorGenerator {
1411
1504
  : getNthColor(this.currentColorIndex++, this.palette);
1412
1505
  }
1413
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
+ }
1414
1563
 
1415
1564
  //------------------------------------------------------------------------------
1416
1565
  // Coordinate
@@ -3282,6 +3431,7 @@ const invalidateEvaluationCommands = new Set([
3282
3431
  ]);
3283
3432
  const invalidateChartEvaluationCommands = new Set([
3284
3433
  "EVALUATE_CELLS",
3434
+ "EVALUATE_CHARTS",
3285
3435
  "UPDATE_CELL",
3286
3436
  "UNHIDE_COLUMNS_ROWS",
3287
3437
  "HIDE_COLUMNS_ROWS",
@@ -3318,6 +3468,7 @@ const readonlyAllowedCommands = new Set([
3318
3468
  "RESIZE_SHEETVIEW",
3319
3469
  "SET_VIEWPORT_OFFSET",
3320
3470
  "EVALUATE_CELLS",
3471
+ "EVALUATE_CHARTS",
3321
3472
  "SET_FORMULA_VISIBILITY",
3322
3473
  "UPDATE_FILTER",
3323
3474
  ]);
@@ -6522,7 +6673,7 @@ class BorderClipboardHandler extends AbstractCellClipboardHandler {
6522
6673
  const POSTFIX_UNARY_OPERATORS = ["%"];
6523
6674
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
6524
6675
  function tokenize(str, locale = DEFAULT_LOCALE) {
6525
- str = replaceSpecialSpaces(str);
6676
+ str = replaceNewLines(str);
6526
6677
  const chars = new TokenizingChars(str);
6527
6678
  const result = [];
6528
6679
  while (!chars.isOver()) {
@@ -6669,12 +6820,12 @@ function tokenizeSpace(chars) {
6669
6820
  if (length) {
6670
6821
  return { type: "SPACE", value: NEWLINE.repeat(length) };
6671
6822
  }
6672
- while (chars.current === " ") {
6673
- length++;
6674
- chars.shift();
6823
+ let spaces = "";
6824
+ while (chars.current && chars.current.match(whiteSpaceRegexp)) {
6825
+ spaces += chars.shift();
6675
6826
  }
6676
- if (length) {
6677
- return { type: "SPACE", value: " ".repeat(length) };
6827
+ if (spaces) {
6828
+ return { type: "SPACE", value: spaces };
6678
6829
  }
6679
6830
  return null;
6680
6831
  }
@@ -7533,6 +7684,16 @@ function getRunningTotalDomainKey(domain, fieldNameWithGranularity) {
7533
7684
  }
7534
7685
  return domainToString([...domain.slice(0, index), ...domain.slice(index + 1)]);
7535
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
+ }
7536
7697
 
7537
7698
  const pivotTimeAdapterRegistry = new Registry();
7538
7699
  function pivotTimeAdapter(granularity) {
@@ -8043,6 +8204,29 @@ function addIndentAndAlignToPivotHeader(pivot, domain, functionResult) {
8043
8204
  format: `${" ".repeat(indent)}${format}* `,
8044
8205
  };
8045
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
+ }
8046
8230
 
8047
8231
  class CellClipboardHandler extends AbstractCellClipboardHandler {
8048
8232
  isCutAllowed(data) {
@@ -14651,7 +14835,7 @@ const SORT_TYPES = [
14651
14835
  CellValueType.boolean,
14652
14836
  ];
14653
14837
  function cellsSortingCriterion(sortingOrder) {
14654
- const inverse = sortingOrder === "ascending" ? 1 : -1;
14838
+ const inverse = sortingOrder === "asc" ? 1 : -1;
14655
14839
  return (left, right) => {
14656
14840
  if (left.type === CellValueType.empty) {
14657
14841
  return right.type === CellValueType.empty ? 0 : 1;
@@ -14743,7 +14927,7 @@ function sortMatrix(matrix, locale, ...criteria) {
14743
14927
  const sortColumns = [];
14744
14928
  const nRows = matrix.length;
14745
14929
  for (let i = 0; i < criteria.length; i += 2) {
14746
- sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "ascending" : "descending");
14930
+ sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "asc" : "desc");
14747
14931
  const sortColumn = criteria[i];
14748
14932
  if (isMatrix(sortColumn) && (sortColumn.length > 1 || sortColumn[0].length > 1)) {
14749
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));
@@ -14760,12 +14944,12 @@ function sortMatrix(matrix, locale, ...criteria) {
14760
14944
  if (sortColumns.length === 0) {
14761
14945
  for (let i = 0; i < matrix[0].length; i++) {
14762
14946
  sortColumns.push(matrix.map((row) => row[i].value));
14763
- sortingOrders.push("ascending");
14947
+ sortingOrders.push("asc");
14764
14948
  }
14765
14949
  }
14766
14950
  const sortingCriteria = {
14767
- descending: cellsSortingCriterion("descending"),
14768
- ascending: cellsSortingCriterion("ascending"),
14951
+ desc: cellsSortingCriterion("desc"),
14952
+ asc: cellsSortingCriterion("asc"),
14769
14953
  };
14770
14954
  const indexes = range(0, matrix.length);
14771
14955
  indexes.sort((a, b) => {
@@ -20489,20 +20673,8 @@ class AbstractComposerStore extends SpreadsheetStore {
20489
20673
  replaceSelectedRange(zone) {
20490
20674
  const ref = this.getZoneReference(zone);
20491
20675
  const currentToken = this.tokenAtCursor;
20492
- let replaceStart = this.selectionStart;
20493
- if (currentToken?.type === "REFERENCE") {
20494
- replaceStart = currentToken.start;
20495
- }
20496
- else if (currentToken?.type === "RIGHT_PAREN") {
20497
- // match left parenthesis
20498
- const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenthesesCode === currentToken.parenthesesCode);
20499
- const functionToken = this.currentTokens[leftParenthesisIndex - 1];
20500
- if (functionToken === undefined) {
20501
- return;
20502
- }
20503
- replaceStart = functionToken.start;
20504
- }
20505
- this.replaceText(ref, replaceStart, this.selectionEnd);
20676
+ const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
20677
+ this.replaceText(ref, start, this.selectionEnd);
20506
20678
  }
20507
20679
  /**
20508
20680
  * Replace the reference of the old zone by the new one.
@@ -20535,17 +20707,6 @@ class AbstractComposerStore extends SpreadsheetStore {
20535
20707
  getZoneReference(zone) {
20536
20708
  const inputSheetId = this.sheetId;
20537
20709
  const sheetId = this.getters.getActiveSheetId();
20538
- if (zone.top === zone.bottom && zone.left === zone.right) {
20539
- const position = { sheetId, col: zone.left, row: zone.top };
20540
- const pivotId = this.getters.getPivotIdFromPosition(position);
20541
- const pivotCell = this.getters.getPivotCellFromPosition(position);
20542
- const cell = this.getters.getCell(position);
20543
- if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
20544
- const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
20545
- const formula = createPivotFormula(formulaPivotId, pivotCell);
20546
- return localizeFormula(formula, this.getters.getLocale()).slice(1); // strip leading =
20547
- }
20548
- }
20549
20710
  const range = this.getters.getRangeFromZone(sheetId, zone);
20550
20711
  return this.getters.getSelectionRangeString(range, inputSheetId);
20551
20712
  }
@@ -20716,37 +20877,21 @@ class AbstractComposerStore extends SpreadsheetStore {
20716
20877
  const editionSheetId = this.sheetId;
20717
20878
  const rangeColor = (rangeString) => {
20718
20879
  const colorIndex = this.colorIndexByRange[rangeString];
20719
- return colors$1[colorIndex % colors$1.length];
20880
+ return colors[colorIndex % colors.length];
20720
20881
  };
20721
- const highlights = [];
20722
- for (const range of this.getReferencedRanges()) {
20882
+ return this.getReferencedRanges().map((range) => {
20723
20883
  const rangeString = this.getters.getRangeString(range, editionSheetId);
20724
20884
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
20725
20885
  const zone = numberOfRows * numberOfCols === 1
20726
20886
  ? this.getters.expandZone(range.sheetId, range.zone)
20727
20887
  : range.zone;
20728
- highlights.push({
20888
+ return {
20729
20889
  zone,
20730
20890
  color: rangeColor(rangeString),
20731
20891
  sheetId: range.sheetId,
20732
20892
  interactive: true,
20733
- });
20734
- }
20735
- const activeSheetId = this.getters.getActiveSheetId();
20736
- const selectionZone = this.model.selection.getAnchor().zone;
20737
- const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
20738
- if (this.editionMode === "selecting" && !isSelectionHightlighted) {
20739
- highlights.push({
20740
- zone: selectionZone,
20741
- color: "#445566",
20742
- sheetId: activeSheetId,
20743
- dashed: true,
20744
- interactive: false,
20745
- noFill: true,
20746
- thinLine: true,
20747
- });
20748
- }
20749
- return highlights;
20893
+ };
20894
+ });
20750
20895
  }
20751
20896
  /**
20752
20897
  * Return ranges currently referenced in the composer
@@ -20974,6 +21119,7 @@ function compileTokens(tokens) {
20974
21119
  return error;
20975
21120
  },
20976
21121
  isBadExpression: true,
21122
+ normalizedFormula: tokens.map((t) => t.value).join(""),
20977
21123
  };
20978
21124
  }
20979
21125
  }
@@ -21101,6 +21247,7 @@ function compileTokensOrThrow(tokens) {
21101
21247
  symbols,
21102
21248
  tokens,
21103
21249
  isBadExpression: false,
21250
+ normalizedFormula: cacheKey,
21104
21251
  };
21105
21252
  return compiledFormula;
21106
21253
  }
@@ -21365,6 +21512,7 @@ autoCompleteProviders.add("pivot_ids", {
21365
21512
  description: definition.name,
21366
21513
  htmlContent: [{ value: str, color: tokenColors.NUMBER }],
21367
21514
  fuzzySearchKey: str + definition.name,
21515
+ alwaysExpanded: true,
21368
21516
  };
21369
21517
  })
21370
21518
  .filter(isDefined);
@@ -27758,6 +27906,19 @@ const ChartTerms = {
27758
27906
  ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
27759
27907
  ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
27760
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
+ },
27761
27922
  };
27762
27923
  const CustomCurrencyTerms = {
27763
27924
  Custom: _t("Custom"),
@@ -28161,6 +28322,26 @@ function getRadarChartData(definition, dataSets, labelRange, getters) {
28161
28322
  locale: getters.getLocale(),
28162
28323
  };
28163
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
+ }
28164
28345
  function getTrendDatasetForBarChart(config, data) {
28165
28346
  const filteredValues = [];
28166
28347
  const filteredLabels = [];
@@ -28713,6 +28894,43 @@ function getRadarChartDatasets(definition, args) {
28713
28894
  }
28714
28895
  return datasets;
28715
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
+ }
28716
28934
  function getTrendingLineDataSet(dataset, config, data) {
28717
28935
  const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
28718
28936
  defaultBorderColor.a = 1;
@@ -29090,6 +29308,44 @@ function getRadarChartScales(definition, args) {
29090
29308
  },
29091
29309
  };
29092
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
+ }
29093
29349
  function getChartAxisTitleRuntime(design) {
29094
29350
  if (design?.title?.text) {
29095
29351
  const { text, color, align, italic, bold } = design.title;
@@ -29153,6 +29409,44 @@ function getChartAxis(definition, position, type, options) {
29153
29409
  };
29154
29410
  }
29155
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
+ }
29156
29450
 
29157
29451
  function getChartShowValues(definition, args) {
29158
29452
  const { axisFormats, locale } = args;
@@ -29312,6 +29606,25 @@ function getRadarChartTooltip(definition, args) {
29312
29606
  },
29313
29607
  };
29314
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
+ }
29315
29628
  function calculatePercentage(dataset, dataIndex) {
29316
29629
  const numericData = dataset.filter((value) => typeof value === "number");
29317
29630
  const total = numericData.reduce((sum, value) => sum + value, 0);
@@ -29338,6 +29651,10 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
29338
29651
  getComboChartDatasets: getComboChartDatasets,
29339
29652
  getComboChartLegend: getComboChartLegend,
29340
29653
  getData: getData,
29654
+ getGeoChartData: getGeoChartData,
29655
+ getGeoChartDatasets: getGeoChartDatasets,
29656
+ getGeoChartScales: getGeoChartScales,
29657
+ getGeoChartTooltip: getGeoChartTooltip,
29341
29658
  getLineChartData: getLineChartData,
29342
29659
  getLineChartDatasets: getLineChartDatasets,
29343
29660
  getLineChartLegend: getLineChartLegend,
@@ -29911,6 +30228,133 @@ function getSectionThresholdValue(threshold, minValue, maxValue) {
29911
30228
  return clip(value, minValue, maxValue);
29912
30229
  }
29913
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
+
29914
30358
  class LineChart extends AbstractChart {
29915
30359
  dataSets;
29916
30360
  labelRange;
@@ -30904,6 +31348,15 @@ chartRegistry.add("radar", {
30904
31348
  getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30905
31349
  sequence: 80,
30906
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
+ });
30907
31360
  const chartComponentRegistry = new Registry();
30908
31361
  chartComponentRegistry.add("line", ChartJsComponent);
30909
31362
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30915,6 +31368,7 @@ chartComponentRegistry.add("scorecard", ScorecardChart);
30915
31368
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30916
31369
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30917
31370
  chartComponentRegistry.add("radar", ChartJsComponent);
31371
+ chartComponentRegistry.add("geo", ChartJsComponent);
30918
31372
  const chartCategories = {
30919
31373
  line: _t("Line"),
30920
31374
  column: _t("Column"),
@@ -31074,6 +31528,13 @@ chartSubtypeRegistry
31074
31528
  subtypeDefinition: { fillArea: true },
31075
31529
  category: "misc",
31076
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",
31077
31538
  });
31078
31539
 
31079
31540
  /**
@@ -31924,18 +32385,66 @@ const ErrorToolTipPopoverBuilder = {
31924
32385
  },
31925
32386
  };
31926
32387
 
31927
- css /*SCSS*/ `
31928
- .o-filter-menu-value {
31929
- padding: 4px;
31930
- line-height: 20px;
31931
- height: 28px;
31932
- .o-filter-menu-value-checked {
31933
- 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
+ }
31934
32424
  }
31935
32425
  }
31936
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
+
31937
32445
  class FilterMenuValueItem extends Component {
31938
32446
  static template = "o-spreadsheet-FilterMenuValueItem";
32447
+ static components = { Checkbox };
31939
32448
  static props = {
31940
32449
  value: String,
31941
32450
  isChecked: Boolean,
@@ -31973,8 +32482,6 @@ const CSS = css /* scss */ `
31973
32482
  .o-filter-menu-item {
31974
32483
  display: flex;
31975
32484
  box-sizing: border-box;
31976
- height: ${MENU_ITEM_HEIGHT}px;
31977
- padding: 4px 4px 4px 0px;
31978
32485
  cursor: pointer;
31979
32486
  user-select: none;
31980
32487
 
@@ -31983,14 +32490,6 @@ const CSS = css /* scss */ `
31983
32490
  }
31984
32491
  }
31985
32492
 
31986
- input {
31987
- box-sizing: border-box;
31988
- margin-bottom: 5px;
31989
- border: 1px solid #949494;
31990
- height: 24px;
31991
- padding-right: 28px;
31992
- }
31993
-
31994
32493
  .o-search-icon {
31995
32494
  right: 5px;
31996
32495
  top: 3px;
@@ -32007,19 +32506,12 @@ const CSS = css /* scss */ `
32007
32506
  display: flex;
32008
32507
  flex-direction: row;
32009
32508
  margin-bottom: 4px;
32010
-
32011
- .o-filter-menu-action-text {
32012
- cursor: pointer;
32013
- margin-right: 10px;
32014
- color: blue;
32015
- text-decoration: underline;
32016
- }
32017
32509
  }
32018
32510
 
32019
32511
  .o-filter-menu-list {
32020
32512
  flex: auto;
32021
32513
  overflow-y: auto;
32022
- border: 1px solid #949494;
32514
+ border: 1px solid ${GRAY_300};
32023
32515
 
32024
32516
  .o-filter-menu-no-values {
32025
32517
  color: #949494;
@@ -34211,6 +34703,21 @@ const pivotProperties = {
34211
34703
  isReadonlyAllowed: true,
34212
34704
  icon: "o-spreadsheet-Icon.PIVOT",
34213
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
+ };
34214
34721
  const FIX_FORMULAS = {
34215
34722
  name: _t("Convert to individual formulas"),
34216
34723
  execute(env) {
@@ -34247,6 +34754,66 @@ const FIX_FORMULAS = {
34247
34754
  },
34248
34755
  icon: "o-spreadsheet-Icon.PIVOT",
34249
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
+ }
34250
34817
 
34251
34818
  //------------------------------------------------------------------------------
34252
34819
  // Context Menu Registry
@@ -34345,14 +34912,33 @@ cellMenuRegistry
34345
34912
  name: INSERT_LINK_NAME,
34346
34913
  sequence: 150,
34347
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,
34348
34921
  })
34349
34922
  .add("pivot_fix_formulas", {
34350
34923
  ...FIX_FORMULAS,
34351
- sequence: 155,
34924
+ sequence: 160,
34352
34925
  })
34353
34926
  .add("pivot_properties", {
34354
34927
  ...pivotProperties,
34355
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,
34356
34942
  });
34357
34943
 
34358
34944
  const sortRange = {
@@ -34365,7 +34951,7 @@ const sortAscending = {
34365
34951
  execute: (env) => {
34366
34952
  const { anchor, zones } = env.model.getters.getSelection();
34367
34953
  const sheetId = env.model.getters.getActiveSheetId();
34368
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "ascending");
34954
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "asc");
34369
34955
  },
34370
34956
  icon: "o-spreadsheet-Icon.SORT_ASCENDING",
34371
34957
  };
@@ -34393,7 +34979,7 @@ const sortDescending = {
34393
34979
  execute: (env) => {
34394
34980
  const { anchor, zones } = env.model.getters.getSelection();
34395
34981
  const sheetId = env.model.getters.getActiveSheetId();
34396
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "descending");
34982
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "desc");
34397
34983
  },
34398
34984
  icon: "o-spreadsheet-Icon.SORT_DESCENDING",
34399
34985
  };
@@ -34876,6 +35462,227 @@ function interactiveFreezeColumnsRows(env, dimension, base) {
34876
35462
  }
34877
35463
  }
34878
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
+
34879
35686
  const hideCols = {
34880
35687
  name: HIDE_COLUMNS_NAME,
34881
35688
  execute: (env) => {
@@ -35047,6 +35854,19 @@ const viewGridlines = {
35047
35854
  return env.model.getters.getGridLinesVisibility(sheetId);
35048
35855
  },
35049
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
+ };
35050
35870
  const viewFormulas = {
35051
35871
  name: _t("Formulas"),
35052
35872
  isActive: (env) => env.model.getters.shouldShowFormulas(),
@@ -35664,6 +36484,11 @@ topbarMenuRegistry
35664
36484
  .addChild("view_formulas", ["view", "show"], {
35665
36485
  ...viewFormulas,
35666
36486
  sequence: 10,
36487
+ })
36488
+ .addChild("view_irregularity_map", ["view"], {
36489
+ ...irregularityMap,
36490
+ sequence: 40,
36491
+ separator: true,
35667
36492
  })
35668
36493
  // ---------------------------------------------------------------------
35669
36494
  // INSERT MENU ITEMS
@@ -35966,62 +36791,6 @@ class OTRegistry extends Registry {
35966
36791
  }
35967
36792
  const otRegistry = new OTRegistry();
35968
36793
 
35969
- const CHECK_SVG = /*xml*/ `
35970
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
35971
- <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
35972
- </svg>
35973
- `;
35974
- const CHECKBOX_WIDTH = 14;
35975
- css /* scss */ `
35976
- label.o-checkbox {
35977
- input {
35978
- appearance: none;
35979
- -webkit-appearance: none;
35980
- -moz-appearance: none;
35981
- border-radius: 0;
35982
- width: ${CHECKBOX_WIDTH}px;
35983
- height: ${CHECKBOX_WIDTH}px;
35984
- vertical-align: top;
35985
- box-sizing: border-box;
35986
- outline: none;
35987
- border: 1px solid ${GRAY_300};
35988
-
35989
- &:hover {
35990
- border-color: ${ACTION_COLOR};
35991
- }
35992
-
35993
- &:checked {
35994
- background: url("data:image/svg+xml,${encodeURIComponent(CHECK_SVG)}");
35995
- background-color: ${ACTION_COLOR};
35996
- border-color: ${ACTION_COLOR};
35997
- }
35998
-
35999
- &:focus {
36000
- outline: none;
36001
- box-shadow: 0 0 0 0.25rem rgba(113, 75, 103, 0.25);
36002
- border-color: ${ACTION_COLOR};
36003
- }
36004
- }
36005
- }
36006
- `;
36007
- class Checkbox extends Component {
36008
- static template = "o-spreadsheet.Checkbox";
36009
- static props = {
36010
- label: { type: String, optional: true },
36011
- value: { type: Boolean, optional: true },
36012
- className: { type: String, optional: true },
36013
- name: { type: String, optional: true },
36014
- title: { type: String, optional: true },
36015
- disabled: { type: Boolean, optional: true },
36016
- onChange: Function,
36017
- };
36018
- static defaultProps = { value: false };
36019
- onChange(ev) {
36020
- const value = ev.target.checked;
36021
- this.props.onChange(value);
36022
- }
36023
- }
36024
-
36025
36794
  class Section extends Component {
36026
36795
  static template = "o_spreadsheet.Section";
36027
36796
  static props = {
@@ -38261,6 +39030,104 @@ class GaugeChartDesignPanel extends Component {
38261
39030
  }
38262
39031
  }
38263
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
+
38264
39131
  class LineConfigPanel extends GenericChartConfigPanel {
38265
39132
  static template = "o-spreadsheet-LineConfigPanel";
38266
39133
  get canTreatLabelsAsText() {
@@ -38574,6 +39441,10 @@ chartSidePanelComponentRegistry
38574
39441
  .add("radar", {
38575
39442
  configuration: GenericChartConfigPanel,
38576
39443
  design: RadarChartDesignPanel,
39444
+ })
39445
+ .add("geo", {
39446
+ configuration: GeoChartConfigPanel,
39447
+ design: GeoChartDesignPanel,
38577
39448
  });
38578
39449
 
38579
39450
  css /* scss */ `
@@ -39937,7 +40808,7 @@ css /* scss */ `
39937
40808
  border-color: ${GRAY_300};
39938
40809
 
39939
40810
  &.active {
39940
- border-color: ${SELECTION_BORDER_COLOR};
40811
+ border-color: ${ACTION_COLOR};
39941
40812
  }
39942
40813
 
39943
40814
  &.o-invalid {
@@ -40895,7 +41766,7 @@ class ConditionalFormattingEditor extends Component {
40895
41766
  }
40896
41767
  const point = this.state.rules.colorScale[target];
40897
41768
  if (point) {
40898
- point.color = Number.parseInt(color.slice(1), 16);
41769
+ point.color = colorToNumber(color);
40899
41770
  }
40900
41771
  this.updateConditionalFormat({ rule: this.state.rules.colorScale });
40901
41772
  this.closeMenus();
@@ -41040,6 +41911,9 @@ class ConditionalFormattingEditor extends Component {
41040
41911
  return [this.state.rules.dataBar.rangeValues || ""];
41041
41912
  }
41042
41913
  updateDataBarColor(color) {
41914
+ if (!isColorValid(color)) {
41915
+ return;
41916
+ }
41043
41917
  this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
41044
41918
  this.updateConditionalFormat({ rule: this.state.rules.dataBar });
41045
41919
  }
@@ -43643,6 +44517,64 @@ class PivotMeasureEditor extends Component {
43643
44517
  }
43644
44518
  }
43645
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
+ }
44576
+ }
44577
+
43646
44578
  css /* scss */ `
43647
44579
  .add-calculated-measure {
43648
44580
  cursor: pointer;
@@ -43656,6 +44588,7 @@ class PivotLayoutConfigurator extends Component {
43656
44588
  PivotDimensionOrder,
43657
44589
  PivotDimensionGranularity,
43658
44590
  PivotMeasureEditor,
44591
+ PivotSortSection,
43659
44592
  };
43660
44593
  static props = {
43661
44594
  definition: Object,
@@ -43812,9 +44745,13 @@ class PivotLayoutConfigurator extends Component {
43812
44745
  }
43813
44746
  updateMeasure(measure, newMeasure) {
43814
44747
  const { measures } = this.props.definition;
43815
- this.props.onDimensionsUpdated({
44748
+ const update = {
43816
44749
  measures: measures.map((m) => (m.id === measure.id ? newMeasure : m)),
43817
- });
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);
43818
44755
  }
43819
44756
  getMeasureId(fieldName, aggregator) {
43820
44757
  const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
@@ -43970,10 +44907,12 @@ class PivotRuntimeDefinition {
43970
44907
  measures;
43971
44908
  columns;
43972
44909
  rows;
44910
+ sortedColumn;
43973
44911
  constructor(definition, fields) {
43974
44912
  this.measures = definition.measures.map((measure) => createMeasure(fields, measure));
43975
44913
  this.columns = definition.columns.map((dimension) => createPivotDimension(fields, dimension));
43976
44914
  this.rows = definition.rows.map((dimension) => createPivotDimension(fields, dimension));
44915
+ this.sortedColumn = definition.sortedColumn;
43977
44916
  }
43978
44917
  getDimension(nameWithGranularity) {
43979
44918
  const dimension = this.columns.find((d) => d.nameWithGranularity === nameWithGranularity) ||
@@ -44127,6 +45066,7 @@ class SpreadsheetPivotTable {
44127
45066
  pivotCells = {};
44128
45067
  rowTree;
44129
45068
  colTree;
45069
+ isSorted = false;
44130
45070
  constructor(columns, rows, measures, fieldsType) {
44131
45071
  this.columns = columns.map((row) => {
44132
45072
  // offset in the pivot table
@@ -44300,6 +45240,7 @@ class SpreadsheetPivotTable {
44300
45240
  value,
44301
45241
  field: row.fields[rowDepth],
44302
45242
  children: [],
45243
+ type: this.fieldsType[fieldName] || "char",
44303
45244
  width: 0, // not used
44304
45245
  };
44305
45246
  treesAtDepth[depth].push(node);
@@ -44322,6 +45263,7 @@ class SpreadsheetPivotTable {
44322
45263
  field: leaf.fields[depth],
44323
45264
  children: [],
44324
45265
  width: leaf.width,
45266
+ type: this.fieldsType[fieldName] || "char",
44325
45267
  };
44326
45268
  if (treesAtDepth[depth]?.at(-1)?.value !== value) {
44327
45269
  treesAtDepth[depth + 1] = [];
@@ -44340,6 +45282,35 @@ class SpreadsheetPivotTable {
44340
45282
  fieldsType: this.fieldsType,
44341
45283
  };
44342
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
+ }
44343
45314
  }
44344
45315
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
44345
45316
 
@@ -44417,6 +45388,7 @@ function dataEntriesToColumnsTree(dataEntries, columns, index) {
44417
45388
  value: groups[key]?.[0]?.[column.nameWithGranularity]?.value ?? null,
44418
45389
  field: colName,
44419
45390
  children: dataEntriesToColumnsTree(groups[key] || [], columns, index + 1),
45391
+ type: column.type,
44420
45392
  width: 0,
44421
45393
  };
44422
45394
  });
@@ -45282,6 +46254,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
45282
46254
  format: measure.format,
45283
46255
  display: measure.display,
45284
46256
  })),
46257
+ sortedColumn: this.shouldKeepSortedColumn(definition) ? definition.sortedColumn : undefined,
45285
46258
  };
45286
46259
  if (!this.draft && deepEquals(coreDefinition, cleanedDefinition)) {
45287
46260
  return;
@@ -45340,6 +46313,19 @@ class PivotSidePanelStore extends SpreadsheetStore {
45340
46313
  }
45341
46314
  return granularitiesPerFields;
45342
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
+ }
45343
46329
  }
45344
46330
 
45345
46331
  class PivotSpreadsheetSidePanel extends Component {
@@ -46441,6 +47427,7 @@ css /* scss */ `
46441
47427
  }
46442
47428
  }
46443
47429
  `;
47430
+ const DEFAULT_TABLE_STYLE_COLOR = "#3C78D8";
46444
47431
  class TableStyleEditorPanel extends Component {
46445
47432
  static template = "o-spreadsheet-TableStyleEditorPanel";
46446
47433
  static components = { Section, RoundColorPicker, TableStylePreview };
@@ -46459,7 +47446,7 @@ class TableStyleEditorPanel extends Component {
46459
47446
  : null;
46460
47447
  return {
46461
47448
  pickerOpened: false,
46462
- primaryColor: editedStyle?.primaryColor || "#3C78D8",
47449
+ primaryColor: editedStyle?.primaryColor || DEFAULT_TABLE_STYLE_COLOR,
46463
47450
  selectedTemplateName: editedStyle?.templateName || "lightColoredText",
46464
47451
  styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
46465
47452
  };
@@ -46468,7 +47455,7 @@ class TableStyleEditorPanel extends Component {
46468
47455
  this.state.pickerOpened = !this.state.pickerOpened;
46469
47456
  }
46470
47457
  onColorPicked(color) {
46471
- this.state.primaryColor = color;
47458
+ this.state.primaryColor = isColorValid(color) ? color : DEFAULT_TABLE_STYLE_COLOR;
46472
47459
  this.state.pickerOpened = false;
46473
47460
  }
46474
47461
  onTemplatePicked(templateName) {
@@ -48084,6 +49071,7 @@ class FiguresContainer extends Component {
48084
49071
  draggedFigure: undefined,
48085
49072
  horizontalSnap: undefined,
48086
49073
  verticalSnap: undefined,
49074
+ cancelDnd: undefined,
48087
49075
  });
48088
49076
  setup() {
48089
49077
  onMounted(() => {
@@ -48096,12 +49084,28 @@ class FiguresContainer extends Component {
48096
49084
  // new rendering
48097
49085
  this.render();
48098
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
+ });
48099
49100
  }
48100
49101
  getVisibleFigures() {
48101
49102
  const visibleFigures = this.env.model.getters.getVisibleFigures();
48102
49103
  if (this.dnd.draggedFigure &&
48103
49104
  !visibleFigures.some((figure) => figure.id === this.dnd.draggedFigure?.id)) {
48104
- 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
+ }
48105
49109
  }
48106
49110
  return visibleFigures;
48107
49111
  }
@@ -48220,7 +49224,7 @@ class FiguresContainer extends Component {
48220
49224
  this.dnd.verticalSnap = undefined;
48221
49225
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
48222
49226
  };
48223
- startDnd(onMouseMove, onMouseUp);
49227
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48224
49228
  }
48225
49229
  /**
48226
49230
  * Initialize the resize of a figure with mouse movements
@@ -48268,7 +49272,7 @@ class FiguresContainer extends Component {
48268
49272
  this.dnd.horizontalSnap = undefined;
48269
49273
  this.dnd.verticalSnap = undefined;
48270
49274
  };
48271
- startDnd(onMouseMove, onMouseUp);
49275
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48272
49276
  }
48273
49277
  getOtherFigures(figId) {
48274
49278
  return this.getVisibleFigures().filter((f) => f.id !== figId);
@@ -49375,9 +50379,11 @@ class HeadersOverlay extends Component {
49375
50379
  class GridRenderer {
49376
50380
  getters;
49377
50381
  renderer;
50382
+ fingerprints;
49378
50383
  constructor(get) {
49379
50384
  this.getters = get(ModelStore).getters;
49380
50385
  this.renderer = get(RendererStore);
50386
+ this.fingerprints = get(FormulaFingerprintStore);
49381
50387
  this.renderer.register(this);
49382
50388
  }
49383
50389
  get renderingLayers() {
@@ -49855,14 +50861,22 @@ class GridRenderer {
49855
50861
  const showFormula = this.getters.shouldShowFormulas();
49856
50862
  const { x, y, width, height } = this.getters.getVisibleRect(zone);
49857
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);
49858
50872
  const box = {
49859
50873
  x,
49860
50874
  y,
49861
50875
  width,
49862
50876
  height,
49863
50877
  border: this.getters.getCellComputedBorder(position) || undefined,
49864
- style: this.getters.getCellComputedStyle(position),
49865
- dataBarFill: this.getters.getConditionalDataBar(position),
50878
+ style,
50879
+ dataBarFill,
49866
50880
  verticalAlign,
49867
50881
  isError: (cell.type === CellValueType.error && !!cell.message) ||
49868
50882
  this.getters.isDataValidationInvalid(position),
@@ -49887,7 +50901,6 @@ class GridRenderer {
49887
50901
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
49888
50902
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
49889
50903
  /** Content */
49890
- const style = this.getters.getCellComputedStyle(position);
49891
50904
  const wrapping = style.wrapping || "overflow";
49892
50905
  const wrapText = wrapping === "wrap" && !showFormula;
49893
50906
  const maxWidth = width - 2 * MIN_CELL_TEXT_MARGIN;
@@ -51865,62 +52878,6 @@ class BordersPlugin extends CorePlugin {
51865
52878
  }
51866
52879
  }
51867
52880
 
51868
- class PositionMap {
51869
- map = {};
51870
- constructor(entries = []) {
51871
- for (const [position, value] of entries) {
51872
- this.set(position, value);
51873
- }
51874
- }
51875
- set({ sheetId, col, row }, value) {
51876
- const map = this.map;
51877
- if (!map[sheetId]) {
51878
- map[sheetId] = {};
51879
- }
51880
- if (!map[sheetId][col]) {
51881
- map[sheetId][col] = {};
51882
- }
51883
- map[sheetId][col][row] = value;
51884
- }
51885
- get({ sheetId, col, row }) {
51886
- return this.map[sheetId]?.[col]?.[row];
51887
- }
51888
- getSheet(sheetId) {
51889
- return this.map[sheetId];
51890
- }
51891
- has({ sheetId, col, row }) {
51892
- return this.map[sheetId]?.[col]?.[row] !== undefined;
51893
- }
51894
- delete({ sheetId, col, row }) {
51895
- delete this.map[sheetId]?.[col]?.[row];
51896
- }
51897
- keys() {
51898
- const map = this.map;
51899
- const keys = [];
51900
- for (const sheetId in map) {
51901
- for (const col in map[sheetId]) {
51902
- for (const row in map[sheetId][col]) {
51903
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51904
- }
51905
- }
51906
- }
51907
- return keys;
51908
- }
51909
- keysForSheet(sheetId) {
51910
- const map = this.map[sheetId];
51911
- if (!map) {
51912
- return [];
51913
- }
51914
- const keys = [];
51915
- for (const col in map) {
51916
- for (const row in map[col]) {
51917
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51918
- }
51919
- }
51920
- return keys;
51921
- }
51922
- }
51923
-
51924
52881
  /**
51925
52882
  * Core Plugin
51926
52883
  *
@@ -52359,7 +53316,7 @@ class CellPlugin extends CorePlugin {
52359
53316
  const before = this.getters.getCell({ sheetId, col, row });
52360
53317
  const hasContent = "content" in after || "formula" in after;
52361
53318
  // Compute the new cell properties
52362
- const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || "";
53319
+ const afterContent = hasContent ? replaceNewLines(after?.content) : before?.content || "";
52363
53320
  let style;
52364
53321
  if (after.style !== undefined) {
52365
53322
  style = after.style || undefined;
@@ -55304,6 +56261,9 @@ class SheetPlugin extends CorePlugin {
55304
56261
  };
55305
56262
  }
55306
56263
  getUnboundedZone(sheetId, zone) {
56264
+ if (zone.bottom === undefined || zone.right === undefined) {
56265
+ return zone;
56266
+ }
55307
56267
  const isFullRow = zone.left === 0 && zone.right === this.getNumberCols(sheetId) - 1;
55308
56268
  const isFullCol = zone.top === 0 && zone.bottom === this.getNumberRows(sheetId) - 1;
55309
56269
  return {
@@ -59587,66 +60547,25 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59587
60547
  return;
59588
60548
  }
59589
60549
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
59590
- const colorCellArgs = [];
60550
+ const colorThresholds = [{ value: minValue, color: rule.minimum.color }];
59591
60551
  if (rule.midpoint && midValue) {
59592
- colorCellArgs.push({
59593
- minValue,
59594
- minColor: rule.minimum.color,
59595
- colorDiffUnit: this.computeColorDiffUnits(minValue, midValue, rule.minimum.color, rule.midpoint.color),
59596
- });
59597
- colorCellArgs.push({
59598
- minValue: midValue,
59599
- minColor: rule.midpoint.color,
59600
- colorDiffUnit: this.computeColorDiffUnits(midValue, maxValue, rule.midpoint.color, rule.maximum.color),
59601
- });
59602
- }
59603
- else {
59604
- colorCellArgs.push({
59605
- minValue,
59606
- minColor: rule.minimum.color,
59607
- colorDiffUnit: this.computeColorDiffUnits(minValue, maxValue, rule.minimum.color, rule.maximum.color),
59608
- });
60552
+ colorThresholds.push({ value: midValue, color: rule.midpoint.color });
59609
60553
  }
60554
+ colorThresholds.push({ value: maxValue, color: rule.maximum.color });
60555
+ const colorScale = getColorScale(colorThresholds);
59610
60556
  for (let row = zone.top; row <= zone.bottom; row++) {
59611
60557
  for (let col = zone.left; col <= zone.right; col++) {
59612
60558
  const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
59613
60559
  if (cell.type === CellValueType.number) {
59614
60560
  const value = clip(cell.value, minValue, maxValue);
59615
- let color;
59616
- if (colorCellArgs.length === 2 && midValue) {
59617
- color =
59618
- value <= midValue
59619
- ? this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit)
59620
- : this.colorCell(value, colorCellArgs[1].minValue, colorCellArgs[1].minColor, colorCellArgs[1].colorDiffUnit);
59621
- }
59622
- else {
59623
- color = this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit);
59624
- }
59625
60561
  if (!computedStyle[col])
59626
60562
  computedStyle[col] = [];
59627
60563
  computedStyle[col][row] = computedStyle[col]?.[row] || {};
59628
- computedStyle[col][row].fillColor = colorNumberString(color);
60564
+ computedStyle[col][row].fillColor = colorScale(value);
59629
60565
  }
59630
60566
  }
59631
60567
  }
59632
60568
  }
59633
- computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
59634
- const deltaValue = maxValue - minValue;
59635
- const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);
59636
- const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);
59637
- const deltaColorB = (minColor % 256) - (maxColor % 256);
59638
- const colorDiffUnitR = deltaColorR / deltaValue;
59639
- const colorDiffUnitG = deltaColorG / deltaValue;
59640
- const colorDiffUnitB = deltaColorB / deltaValue;
59641
- return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];
59642
- }
59643
- colorCell(value, minValue, minColor, colorDiffUnit) {
59644
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
59645
- const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));
59646
- const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));
59647
- const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
59648
- return (r << 16) | (g << 8) | b;
59649
- }
59650
60569
  /**
59651
60570
  * Execute the predicate to know if a conditional formatting rule should be applied to a cell
59652
60571
  */
@@ -60272,7 +61191,7 @@ function withPivotPresentationLayer (PivotClass) {
60272
61191
  }
60273
61192
  getValuesToAggregate(measure, domain) {
60274
61193
  const { rowDomain, colDomain } = domainToColRowDomain(this, domain);
60275
- const table = this.getTableStructure();
61194
+ const table = super.getTableStructure();
60276
61195
  const values = [];
60277
61196
  if (colDomain.length === 0 &&
60278
61197
  rowDomain.length < this.definition.rows.length &&
@@ -60706,6 +61625,21 @@ function withPivotPresentationLayer (PivotClass) {
60706
61625
  }
60707
61626
  throw new Error(`Value ${result.value} is not a number`);
60708
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
+ }
60709
61643
  }
60710
61644
  return PivotPresentationLayer;
60711
61645
  }
@@ -62481,23 +63415,6 @@ class Session extends EventBus {
62481
63415
  }
62482
63416
  }
62483
63417
 
62484
- function randomChoice(arr) {
62485
- return arr[Math.floor(Math.random() * arr.length)];
62486
- }
62487
- const colors = [
62488
- "#ff851b",
62489
- "#0074d9",
62490
- "#7fdbff",
62491
- "#b10dc9",
62492
- "#39cccc",
62493
- "#f012be",
62494
- "#3d9970",
62495
- "#111111",
62496
- "#ff4136",
62497
- "#aaaaaa",
62498
- "#85144b",
62499
- "#001f3f",
62500
- ];
62501
63418
  class CollaborativePlugin extends UIPlugin {
62502
63419
  static getters = [
62503
63420
  "getClientsToDisplay",
@@ -62506,7 +63423,7 @@ class CollaborativePlugin extends UIPlugin {
62506
63423
  "isFullySynchronized",
62507
63424
  ];
62508
63425
  static layers = ["Selection"];
62509
- availableColors = new Set(colors);
63426
+ availableColors = new AlternatingColorGenerator(12);
62510
63427
  colors = {};
62511
63428
  session;
62512
63429
  constructor(config) {
@@ -62517,14 +63434,6 @@ class CollaborativePlugin extends UIPlugin {
62517
63434
  return (position.row < this.getters.getNumberRows(position.sheetId) &&
62518
63435
  position.col < this.getters.getNumberCols(position.sheetId));
62519
63436
  }
62520
- chooseNewColor() {
62521
- if (this.availableColors.size === 0) {
62522
- this.availableColors = new Set(colors);
62523
- }
62524
- const color = randomChoice([...this.availableColors.values()]);
62525
- this.availableColors.delete(color);
62526
- return color;
62527
- }
62528
63437
  getClient() {
62529
63438
  return this.session.getClient();
62530
63439
  }
@@ -62559,7 +63468,7 @@ class CollaborativePlugin extends UIPlugin {
62559
63468
  this.isPositionValid(client.position)) {
62560
63469
  const position = client.position;
62561
63470
  if (!this.colors[client.id]) {
62562
- this.colors[client.id] = this.chooseNewColor();
63471
+ this.colors[client.id] = this.availableColors.next();
62563
63472
  }
62564
63473
  const color = this.colors[client.id];
62565
63474
  clients.push({ ...client, position, color });
@@ -62796,7 +63705,7 @@ class FormatPlugin extends UIPlugin {
62796
63705
  for (let row = zone.top; row <= zone.bottom; row++) {
62797
63706
  const position = { sheetId, col, row };
62798
63707
  const pivotCell = this.getters.getPivotCellFromPosition(position);
62799
- if (pivotCell.type === "VALUE") {
63708
+ if (this.isSpilledPivotValueFormula(position, pivotCell)) {
62800
63709
  measurePositions.push(position);
62801
63710
  const pivotId = this.getters.getPivotIdFromPosition(position) || "";
62802
63711
  measuresByPivotId[pivotId] ??= new Set();
@@ -62833,6 +63742,10 @@ class FormatPlugin extends UIPlugin {
62833
63742
  format,
62834
63743
  });
62835
63744
  }
63745
+ isSpilledPivotValueFormula(position, pivotCell) {
63746
+ const cell = this.getters.getCell(position);
63747
+ return pivotCell.type === "VALUE" && !cell?.isFormula;
63748
+ }
62836
63749
  /**
62837
63750
  * This function allows to adjust the quantity of decimal places after a decimal
62838
63751
  * point on cells containing number value. It does this by changing the cells
@@ -62883,6 +63796,69 @@ class FormatPlugin extends UIPlugin {
62883
63796
  }
62884
63797
  }
62885
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
+
62886
63862
  class HeaderVisibilityUIPlugin extends UIPlugin {
62887
63863
  static getters = [
62888
63864
  "getNextVisibleCellPosition",
@@ -64420,10 +65396,13 @@ class TableResizeUI extends UIPlugin {
64420
65396
  case "RESIZE_TABLE": {
64421
65397
  const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
64422
65398
  this.dispatch("UPDATE_TABLE", { ...cmd });
64423
- if (!table || !table.config.automaticAutofill)
65399
+ if (!table)
64424
65400
  return;
64425
- const oldTableZone = table.range.zone;
64426
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;
64427
65406
  if (newTableZone.bottom >= oldTableZone.bottom) {
64428
65407
  for (let col = newTableZone.left; col <= newTableZone.right; col++) {
64429
65408
  const autofillSource = { col, row: oldTableZone.bottom, sheetId: cmd.sheetId };
@@ -65629,15 +66608,23 @@ class GridSelectionPlugin extends UIPlugin {
65629
66608
  handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
65630
66609
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
65631
66610
  let currentIndex = cmd.base;
66611
+ const resizingGroups = {};
65632
66612
  for (const element of toRemove) {
65633
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) {
65634
66622
  this.dispatch("RESIZE_COLUMNS_ROWS", {
65635
66623
  dimension: cmd.dimension,
65636
66624
  sheetId: cmd.sheetId,
65637
- size,
65638
- elements: [currentIndex],
66625
+ size: parseInt(size, 10),
66626
+ elements: resizingGroups[size],
65639
66627
  });
65640
- currentIndex += 1;
65641
66628
  }
65642
66629
  this.dispatch("REMOVE_COLUMNS_ROWS", {
65643
66630
  dimension: cmd.dimension,
@@ -66219,6 +67206,7 @@ class SheetViewPlugin extends UIPlugin {
66219
67206
  break;
66220
67207
  case "DELETE_SHEET":
66221
67208
  this.cleanViewports();
67209
+ this.sheetsWithDirtyViewports.delete(cmd.sheetId);
66222
67210
  break;
66223
67211
  case "ACTIVATE_SHEET":
66224
67212
  this.sheetsWithDirtyViewports.add(cmd.sheetIdTo);
@@ -66869,7 +67857,8 @@ const featurePluginRegistry = new Registry()
66869
67857
  .add("data_cleanup", DataCleanupPlugin)
66870
67858
  .add("table_autofill", TableAutofillPlugin)
66871
67859
  .add("table_ui_resize", TableResizeUI)
66872
- .add("datavalidation_insert", DataValidationInsertionPlugin);
67860
+ .add("datavalidation_insert", DataValidationInsertionPlugin)
67861
+ .add("geo_features", GeoFeaturePlugin);
66873
67862
  // Plugins which have a state, but which should not be shared in collaborative
66874
67863
  const statefulUIPluginRegistry = new Registry()
66875
67864
  .add("selection", GridSelectionPlugin)
@@ -68613,8 +69602,8 @@ css /* scss */ `
68613
69602
  .o-topbar-composer {
68614
69603
  height: fit-content;
68615
69604
  margin-top: -1px;
69605
+ margin-bottom: -1px;
68616
69606
  border: 1px solid;
68617
- z-index: ${ComponentsImportance.TopBarComposer};
68618
69607
  font-family: ${DEFAULT_FONT};
68619
69608
 
68620
69609
  .o-composer:empty:not(:focus):not(.active)::before {
@@ -68672,6 +69661,7 @@ class TopBarComposer extends Component {
68672
69661
  }
68673
69662
  return cssPropertiesToCss({
68674
69663
  "border-color": SELECTION_BORDER_COLOR,
69664
+ "z-index": String(ComponentsImportance.TopBarComposer),
68675
69665
  });
68676
69666
  }
68677
69667
  onFocus(selection) {
@@ -68780,6 +69770,15 @@ css /* scss */ `
68780
69770
  }
68781
69771
  }
68782
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
+
68783
69782
  .o-topbar-composer {
68784
69783
  flex-grow: 1;
68785
69784
  }
@@ -68873,8 +69872,10 @@ class TopBar extends Component {
68873
69872
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
68874
69873
  isntToolbarMenu = false;
68875
69874
  composerFocusStore;
69875
+ fingerprints;
68876
69876
  setup() {
68877
69877
  this.composerFocusStore = useStore(ComposerFocusStore);
69878
+ this.fingerprints = useStore(FormulaFingerprintStore);
68878
69879
  useExternalListener(window, "click", this.onExternalClick);
68879
69880
  onWillStart(() => this.updateCellState());
68880
69881
  onWillUpdateProps(() => this.updateCellState());
@@ -69345,7 +70346,7 @@ class Spreadsheet extends Component {
69345
70346
  properties["grid-template-rows"] = `auto`;
69346
70347
  }
69347
70348
  else {
69348
- properties["grid-template-rows"] = `${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
70349
+ properties["grid-template-rows"] = `max-content auto ${BOTTOMBAR_HEIGHT + 1}px`;
69349
70350
  }
69350
70351
  properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
69351
70352
  return cssPropertiesToCss(properties);
@@ -70423,9 +71424,6 @@ class SelectionStreamProcessorImpl {
70423
71424
  getBackToDefault() {
70424
71425
  this.stream.getBackToDefault();
70425
71426
  }
70426
- getAnchor() {
70427
- return this.anchor;
70428
- }
70429
71427
  modifyAnchor(anchor, mode, options) {
70430
71428
  const sheetId = this.getters.getActiveSheetId();
70431
71429
  anchor = {
@@ -73357,6 +74355,7 @@ class Model extends EventBus {
73357
74355
  session: this.session,
73358
74356
  defaultCurrency: this.config.defaultCurrency,
73359
74357
  customColors: this.config.customColors || [],
74358
+ external: this.config.external,
73360
74359
  };
73361
74360
  }
73362
74361
  // ---------------------------------------------------------------------------
@@ -73588,7 +74587,6 @@ const SPREADSHEET_DIMENSIONS = {
73588
74587
  MIN_COL_WIDTH,
73589
74588
  HEADER_HEIGHT,
73590
74589
  HEADER_WIDTH,
73591
- TOPBAR_HEIGHT,
73592
74590
  BOTTOMBAR_HEIGHT,
73593
74591
  DEFAULT_CELL_WIDTH,
73594
74592
  DEFAULT_CELL_HEIGHT,
@@ -73785,6 +74783,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73785
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 };
73786
74784
 
73787
74785
 
73788
- __info__.version = "18.1.0-alpha.7";
73789
- __info__.date = "2024-12-05T10:40:26.512Z";
73790
- __info__.hash = "7b1c39b";
74786
+ __info__.version = "18.1.0-alpha.8";
74787
+ __info__.date = "2024-12-19T07:49:54.421Z";
74788
+ __info__.hash = "87a1567";