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

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
6
+ * @date 2024-12-26T06:37:07.879Z
7
+ * @hash c520e89
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
  }
@@ -41170,6 +42044,10 @@ css /* scss */ `
41170
42044
  border: 1px solid #d8dadd;
41171
42045
  color: #374151;
41172
42046
  }
42047
+
42048
+ table {
42049
+ table-layout: fixed;
42050
+ }
41173
42051
  }
41174
42052
  `;
41175
42053
  class CustomCurrencyPanel extends Component {
@@ -43643,6 +44521,64 @@ class PivotMeasureEditor extends Component {
43643
44521
  }
43644
44522
  }
43645
44523
 
44524
+ css /* scss */ `
44525
+ .o-pivot-sort {
44526
+ .o-sort-card {
44527
+ width: fit-content;
44528
+ background-color: ${GRAY_100};
44529
+ border: 1px solid ${GRAY_300};
44530
+
44531
+ .o-sort-value {
44532
+ color: ${PRIMARY_BUTTON_BG};
44533
+ }
44534
+ }
44535
+ }
44536
+ `;
44537
+ class PivotSortSection extends Component {
44538
+ static template = "o-spreadsheet-PivotSortSection";
44539
+ static components = {
44540
+ Section,
44541
+ };
44542
+ static props = {
44543
+ definition: Object,
44544
+ pivotId: String,
44545
+ };
44546
+ get hasValidSort() {
44547
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44548
+ return (!!this.props.definition.sortedColumn &&
44549
+ isSortedColumnValid(this.props.definition.sortedColumn, pivot));
44550
+ }
44551
+ get sortDescription() {
44552
+ const sortOrder = this.props.definition.sortedColumn?.order === "asc" ? _t("ascending") : _t("descending");
44553
+ return _t("Sorted on column (%(ascOrDesc)s):", {
44554
+ ascOrDesc: sortOrder,
44555
+ });
44556
+ }
44557
+ get sortValuesAndFields() {
44558
+ const sortedColumn = this.props.definition.sortedColumn;
44559
+ if (!sortedColumn) {
44560
+ return [];
44561
+ }
44562
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44563
+ const locale = this.env.model.getters.getLocale();
44564
+ const currentDomain = [];
44565
+ const sortValues = [];
44566
+ for (const domainItem of sortedColumn.domain) {
44567
+ currentDomain.push(domainItem);
44568
+ const { value, format } = pivot.getPivotHeaderValueAndFormat(currentDomain);
44569
+ const label = formatValue(value, { format, locale });
44570
+ const field = pivot.definition.getDimension(domainItem.field);
44571
+ sortValues.push({ field: getFieldDisplayName(field), value: label });
44572
+ }
44573
+ if (sortedColumn.domain.length === 0) {
44574
+ sortValues.push({ value: _t("Total") });
44575
+ }
44576
+ const measureLabel = pivot.getMeasure(sortedColumn.measure).displayName;
44577
+ sortValues.push({ value: measureLabel, field: _t("Measure") });
44578
+ return sortValues;
44579
+ }
44580
+ }
44581
+
43646
44582
  css /* scss */ `
43647
44583
  .add-calculated-measure {
43648
44584
  cursor: pointer;
@@ -43656,6 +44592,7 @@ class PivotLayoutConfigurator extends Component {
43656
44592
  PivotDimensionOrder,
43657
44593
  PivotDimensionGranularity,
43658
44594
  PivotMeasureEditor,
44595
+ PivotSortSection,
43659
44596
  };
43660
44597
  static props = {
43661
44598
  definition: Object,
@@ -43812,9 +44749,13 @@ class PivotLayoutConfigurator extends Component {
43812
44749
  }
43813
44750
  updateMeasure(measure, newMeasure) {
43814
44751
  const { measures } = this.props.definition;
43815
- this.props.onDimensionsUpdated({
44752
+ const update = {
43816
44753
  measures: measures.map((m) => (m.id === measure.id ? newMeasure : m)),
43817
- });
44754
+ };
44755
+ if (this.props.definition.sortedColumn?.measure === measure.id) {
44756
+ update.sortedColumn = { ...this.props.definition.sortedColumn, measure: newMeasure.id };
44757
+ }
44758
+ this.props.onDimensionsUpdated(update);
43818
44759
  }
43819
44760
  getMeasureId(fieldName, aggregator) {
43820
44761
  const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
@@ -43970,10 +44911,12 @@ class PivotRuntimeDefinition {
43970
44911
  measures;
43971
44912
  columns;
43972
44913
  rows;
44914
+ sortedColumn;
43973
44915
  constructor(definition, fields) {
43974
44916
  this.measures = definition.measures.map((measure) => createMeasure(fields, measure));
43975
44917
  this.columns = definition.columns.map((dimension) => createPivotDimension(fields, dimension));
43976
44918
  this.rows = definition.rows.map((dimension) => createPivotDimension(fields, dimension));
44919
+ this.sortedColumn = definition.sortedColumn;
43977
44920
  }
43978
44921
  getDimension(nameWithGranularity) {
43979
44922
  const dimension = this.columns.find((d) => d.nameWithGranularity === nameWithGranularity) ||
@@ -44127,6 +45070,7 @@ class SpreadsheetPivotTable {
44127
45070
  pivotCells = {};
44128
45071
  rowTree;
44129
45072
  colTree;
45073
+ isSorted = false;
44130
45074
  constructor(columns, rows, measures, fieldsType) {
44131
45075
  this.columns = columns.map((row) => {
44132
45076
  // offset in the pivot table
@@ -44300,6 +45244,7 @@ class SpreadsheetPivotTable {
44300
45244
  value,
44301
45245
  field: row.fields[rowDepth],
44302
45246
  children: [],
45247
+ type: this.fieldsType[fieldName] || "char",
44303
45248
  width: 0, // not used
44304
45249
  };
44305
45250
  treesAtDepth[depth].push(node);
@@ -44322,6 +45267,7 @@ class SpreadsheetPivotTable {
44322
45267
  field: leaf.fields[depth],
44323
45268
  children: [],
44324
45269
  width: leaf.width,
45270
+ type: this.fieldsType[fieldName] || "char",
44325
45271
  };
44326
45272
  if (treesAtDepth[depth]?.at(-1)?.value !== value) {
44327
45273
  treesAtDepth[depth + 1] = [];
@@ -44340,6 +45286,35 @@ class SpreadsheetPivotTable {
44340
45286
  fieldsType: this.fieldsType,
44341
45287
  };
44342
45288
  }
45289
+ sort(measure, sortedColumn, getValue) {
45290
+ if (this.isSorted) {
45291
+ return;
45292
+ }
45293
+ const getSortValue = (measure, domain) => {
45294
+ const rawValue = getValue(measure, domain).value;
45295
+ return typeof rawValue === "number" ? rawValue : -Infinity;
45296
+ };
45297
+ const sortColDomain = sortedColumn.domain;
45298
+ const sortFn = (rowDomain1, rowDomain2) => {
45299
+ const value1 = getSortValue(measure, [...rowDomain1, ...sortColDomain]);
45300
+ const value2 = getSortValue(measure, [...rowDomain2, ...sortColDomain]);
45301
+ return sortedColumn.order === "asc" ? value1 - value2 : value2 - value1;
45302
+ };
45303
+ const sortedRowTree = sortPivotTree(this.rowTree(), [], sortFn);
45304
+ this.rowTree = lazy(sortedRowTree);
45305
+ this.rows = [...this.rowTreeToRows(sortedRowTree), this.rows[this.rows.length - 1]];
45306
+ this.isSorted = true;
45307
+ }
45308
+ rowTreeToRows(tree, parentRow) {
45309
+ return tree.flatMap((node) => {
45310
+ const row = {
45311
+ indent: parentRow ? parentRow.indent + 1 : 0,
45312
+ fields: [...(parentRow?.fields || []), node.field],
45313
+ values: [...(parentRow?.values || []), node.value],
45314
+ };
45315
+ return [row, ...this.rowTreeToRows(node.children, row)];
45316
+ });
45317
+ }
44343
45318
  }
44344
45319
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
44345
45320
 
@@ -44417,6 +45392,7 @@ function dataEntriesToColumnsTree(dataEntries, columns, index) {
44417
45392
  value: groups[key]?.[0]?.[column.nameWithGranularity]?.value ?? null,
44418
45393
  field: colName,
44419
45394
  children: dataEntriesToColumnsTree(groups[key] || [], columns, index + 1),
45395
+ type: column.type,
44420
45396
  width: 0,
44421
45397
  };
44422
45398
  });
@@ -45282,6 +46258,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
45282
46258
  format: measure.format,
45283
46259
  display: measure.display,
45284
46260
  })),
46261
+ sortedColumn: this.shouldKeepSortedColumn(definition) ? definition.sortedColumn : undefined,
45285
46262
  };
45286
46263
  if (!this.draft && deepEquals(coreDefinition, cleanedDefinition)) {
45287
46264
  return;
@@ -45340,6 +46317,19 @@ class PivotSidePanelStore extends SpreadsheetStore {
45340
46317
  }
45341
46318
  return granularitiesPerFields;
45342
46319
  }
46320
+ /**
46321
+ * Check if we want to keep the sorted column when updating the pivot definition. We should remove it if either
46322
+ * the measure is not in the new definition or the columns have changed.
46323
+ */
46324
+ shouldKeepSortedColumn(newDefinition) {
46325
+ const { sortedColumn } = newDefinition;
46326
+ if (!sortedColumn) {
46327
+ return true;
46328
+ }
46329
+ const oldDefinition = this.getters.getPivotCoreDefinition(this.pivotId);
46330
+ return (newDefinition.measures.find((measure) => measure.id === sortedColumn.measure) &&
46331
+ deepEquals(oldDefinition.columns, newDefinition.columns));
46332
+ }
45343
46333
  }
45344
46334
 
45345
46335
  class PivotSpreadsheetSidePanel extends Component {
@@ -46441,6 +47431,7 @@ css /* scss */ `
46441
47431
  }
46442
47432
  }
46443
47433
  `;
47434
+ const DEFAULT_TABLE_STYLE_COLOR = "#3C78D8";
46444
47435
  class TableStyleEditorPanel extends Component {
46445
47436
  static template = "o-spreadsheet-TableStyleEditorPanel";
46446
47437
  static components = { Section, RoundColorPicker, TableStylePreview };
@@ -46459,7 +47450,7 @@ class TableStyleEditorPanel extends Component {
46459
47450
  : null;
46460
47451
  return {
46461
47452
  pickerOpened: false,
46462
- primaryColor: editedStyle?.primaryColor || "#3C78D8",
47453
+ primaryColor: editedStyle?.primaryColor || DEFAULT_TABLE_STYLE_COLOR,
46463
47454
  selectedTemplateName: editedStyle?.templateName || "lightColoredText",
46464
47455
  styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
46465
47456
  };
@@ -46468,7 +47459,7 @@ class TableStyleEditorPanel extends Component {
46468
47459
  this.state.pickerOpened = !this.state.pickerOpened;
46469
47460
  }
46470
47461
  onColorPicked(color) {
46471
- this.state.primaryColor = color;
47462
+ this.state.primaryColor = isColorValid(color) ? color : DEFAULT_TABLE_STYLE_COLOR;
46472
47463
  this.state.pickerOpened = false;
46473
47464
  }
46474
47465
  onTemplatePicked(templateName) {
@@ -48084,6 +49075,7 @@ class FiguresContainer extends Component {
48084
49075
  draggedFigure: undefined,
48085
49076
  horizontalSnap: undefined,
48086
49077
  verticalSnap: undefined,
49078
+ cancelDnd: undefined,
48087
49079
  });
48088
49080
  setup() {
48089
49081
  onMounted(() => {
@@ -48096,12 +49088,28 @@ class FiguresContainer extends Component {
48096
49088
  // new rendering
48097
49089
  this.render();
48098
49090
  });
49091
+ onWillUpdateProps(() => {
49092
+ const sheetId = this.env.model.getters.getActiveSheetId();
49093
+ const draggedFigureId = this.dnd.draggedFigure?.id;
49094
+ if (draggedFigureId && !this.env.model.getters.getFigure(sheetId, draggedFigureId)) {
49095
+ if (this.dnd.cancelDnd) {
49096
+ this.dnd.cancelDnd();
49097
+ }
49098
+ this.dnd.draggedFigure = undefined;
49099
+ this.dnd.horizontalSnap = undefined;
49100
+ this.dnd.verticalSnap = undefined;
49101
+ this.dnd.cancelDnd = undefined;
49102
+ }
49103
+ });
48099
49104
  }
48100
49105
  getVisibleFigures() {
48101
49106
  const visibleFigures = this.env.model.getters.getVisibleFigures();
48102
49107
  if (this.dnd.draggedFigure &&
48103
49108
  !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));
49109
+ const draggedFigure = this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id);
49110
+ if (draggedFigure) {
49111
+ visibleFigures.push(draggedFigure);
49112
+ }
48105
49113
  }
48106
49114
  return visibleFigures;
48107
49115
  }
@@ -48220,7 +49228,7 @@ class FiguresContainer extends Component {
48220
49228
  this.dnd.verticalSnap = undefined;
48221
49229
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
48222
49230
  };
48223
- startDnd(onMouseMove, onMouseUp);
49231
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48224
49232
  }
48225
49233
  /**
48226
49234
  * Initialize the resize of a figure with mouse movements
@@ -48268,7 +49276,7 @@ class FiguresContainer extends Component {
48268
49276
  this.dnd.horizontalSnap = undefined;
48269
49277
  this.dnd.verticalSnap = undefined;
48270
49278
  };
48271
- startDnd(onMouseMove, onMouseUp);
49279
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48272
49280
  }
48273
49281
  getOtherFigures(figId) {
48274
49282
  return this.getVisibleFigures().filter((f) => f.id !== figId);
@@ -49375,9 +50383,11 @@ class HeadersOverlay extends Component {
49375
50383
  class GridRenderer {
49376
50384
  getters;
49377
50385
  renderer;
50386
+ fingerprints;
49378
50387
  constructor(get) {
49379
50388
  this.getters = get(ModelStore).getters;
49380
50389
  this.renderer = get(RendererStore);
50390
+ this.fingerprints = get(FormulaFingerprintStore);
49381
50391
  this.renderer.register(this);
49382
50392
  }
49383
50393
  get renderingLayers() {
@@ -49855,14 +50865,22 @@ class GridRenderer {
49855
50865
  const showFormula = this.getters.shouldShowFormulas();
49856
50866
  const { x, y, width, height } = this.getters.getVisibleRect(zone);
49857
50867
  const { verticalAlign } = this.getters.getCellStyle(position);
50868
+ let style = this.getters.getCellComputedStyle(position);
50869
+ if (this.fingerprints.isEnabled) {
50870
+ const fingerprintColor = this.fingerprints.colors.get(position);
50871
+ style = { ...style, fillColor: fingerprintColor };
50872
+ }
50873
+ const dataBarFill = this.fingerprints.isEnabled
50874
+ ? undefined
50875
+ : this.getters.getConditionalDataBar(position);
49858
50876
  const box = {
49859
50877
  x,
49860
50878
  y,
49861
50879
  width,
49862
50880
  height,
49863
50881
  border: this.getters.getCellComputedBorder(position) || undefined,
49864
- style: this.getters.getCellComputedStyle(position),
49865
- dataBarFill: this.getters.getConditionalDataBar(position),
50882
+ style,
50883
+ dataBarFill,
49866
50884
  verticalAlign,
49867
50885
  isError: (cell.type === CellValueType.error && !!cell.message) ||
49868
50886
  this.getters.isDataValidationInvalid(position),
@@ -49887,7 +50905,6 @@ class GridRenderer {
49887
50905
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
49888
50906
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
49889
50907
  /** Content */
49890
- const style = this.getters.getCellComputedStyle(position);
49891
50908
  const wrapping = style.wrapping || "overflow";
49892
50909
  const wrapText = wrapping === "wrap" && !showFormula;
49893
50910
  const maxWidth = width - 2 * MIN_CELL_TEXT_MARGIN;
@@ -51865,62 +52882,6 @@ class BordersPlugin extends CorePlugin {
51865
52882
  }
51866
52883
  }
51867
52884
 
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
52885
  /**
51925
52886
  * Core Plugin
51926
52887
  *
@@ -52359,7 +53320,7 @@ class CellPlugin extends CorePlugin {
52359
53320
  const before = this.getters.getCell({ sheetId, col, row });
52360
53321
  const hasContent = "content" in after || "formula" in after;
52361
53322
  // Compute the new cell properties
52362
- const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || "";
53323
+ const afterContent = hasContent ? replaceNewLines(after?.content) : before?.content || "";
52363
53324
  let style;
52364
53325
  if (after.style !== undefined) {
52365
53326
  style = after.style || undefined;
@@ -55304,6 +56265,9 @@ class SheetPlugin extends CorePlugin {
55304
56265
  };
55305
56266
  }
55306
56267
  getUnboundedZone(sheetId, zone) {
56268
+ if (zone.bottom === undefined || zone.right === undefined) {
56269
+ return zone;
56270
+ }
55307
56271
  const isFullRow = zone.left === 0 && zone.right === this.getNumberCols(sheetId) - 1;
55308
56272
  const isFullCol = zone.top === 0 && zone.bottom === this.getNumberRows(sheetId) - 1;
55309
56273
  return {
@@ -59587,66 +60551,25 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
59587
60551
  return;
59588
60552
  }
59589
60553
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
59590
- const colorCellArgs = [];
60554
+ const colorThresholds = [{ value: minValue, color: rule.minimum.color }];
59591
60555
  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
- });
60556
+ colorThresholds.push({ value: midValue, color: rule.midpoint.color });
59609
60557
  }
60558
+ colorThresholds.push({ value: maxValue, color: rule.maximum.color });
60559
+ const colorScale = getColorScale(colorThresholds);
59610
60560
  for (let row = zone.top; row <= zone.bottom; row++) {
59611
60561
  for (let col = zone.left; col <= zone.right; col++) {
59612
60562
  const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
59613
60563
  if (cell.type === CellValueType.number) {
59614
60564
  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
60565
  if (!computedStyle[col])
59626
60566
  computedStyle[col] = [];
59627
60567
  computedStyle[col][row] = computedStyle[col]?.[row] || {};
59628
- computedStyle[col][row].fillColor = colorNumberString(color);
60568
+ computedStyle[col][row].fillColor = colorScale(value);
59629
60569
  }
59630
60570
  }
59631
60571
  }
59632
60572
  }
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
60573
  /**
59651
60574
  * Execute the predicate to know if a conditional formatting rule should be applied to a cell
59652
60575
  */
@@ -60272,7 +61195,7 @@ function withPivotPresentationLayer (PivotClass) {
60272
61195
  }
60273
61196
  getValuesToAggregate(measure, domain) {
60274
61197
  const { rowDomain, colDomain } = domainToColRowDomain(this, domain);
60275
- const table = this.getTableStructure();
61198
+ const table = super.getTableStructure();
60276
61199
  const values = [];
60277
61200
  if (colDomain.length === 0 &&
60278
61201
  rowDomain.length < this.definition.rows.length &&
@@ -60706,6 +61629,21 @@ function withPivotPresentationLayer (PivotClass) {
60706
61629
  }
60707
61630
  throw new Error(`Value ${result.value} is not a number`);
60708
61631
  }
61632
+ getTableStructure() {
61633
+ const table = super.getTableStructure();
61634
+ this.sortTableStructure(table);
61635
+ return table;
61636
+ }
61637
+ sortTableStructure(table) {
61638
+ if (!this.definition.sortedColumn || table.isSorted) {
61639
+ return;
61640
+ }
61641
+ const measure = this.definition.sortedColumn.measure;
61642
+ const isSortValid = isSortedColumnValid(this.definition.sortedColumn, this);
61643
+ if (isSortValid) {
61644
+ table.sort(measure, this.definition.sortedColumn, (measure, domain) => this._getPivotCellValueAndFormat(measure, domain));
61645
+ }
61646
+ }
60709
61647
  }
60710
61648
  return PivotPresentationLayer;
60711
61649
  }
@@ -62481,23 +63419,6 @@ class Session extends EventBus {
62481
63419
  }
62482
63420
  }
62483
63421
 
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
63422
  class CollaborativePlugin extends UIPlugin {
62502
63423
  static getters = [
62503
63424
  "getClientsToDisplay",
@@ -62506,7 +63427,7 @@ class CollaborativePlugin extends UIPlugin {
62506
63427
  "isFullySynchronized",
62507
63428
  ];
62508
63429
  static layers = ["Selection"];
62509
- availableColors = new Set(colors);
63430
+ availableColors = new AlternatingColorGenerator(12);
62510
63431
  colors = {};
62511
63432
  session;
62512
63433
  constructor(config) {
@@ -62517,14 +63438,6 @@ class CollaborativePlugin extends UIPlugin {
62517
63438
  return (position.row < this.getters.getNumberRows(position.sheetId) &&
62518
63439
  position.col < this.getters.getNumberCols(position.sheetId));
62519
63440
  }
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
63441
  getClient() {
62529
63442
  return this.session.getClient();
62530
63443
  }
@@ -62559,7 +63472,7 @@ class CollaborativePlugin extends UIPlugin {
62559
63472
  this.isPositionValid(client.position)) {
62560
63473
  const position = client.position;
62561
63474
  if (!this.colors[client.id]) {
62562
- this.colors[client.id] = this.chooseNewColor();
63475
+ this.colors[client.id] = this.availableColors.next();
62563
63476
  }
62564
63477
  const color = this.colors[client.id];
62565
63478
  clients.push({ ...client, position, color });
@@ -62796,7 +63709,7 @@ class FormatPlugin extends UIPlugin {
62796
63709
  for (let row = zone.top; row <= zone.bottom; row++) {
62797
63710
  const position = { sheetId, col, row };
62798
63711
  const pivotCell = this.getters.getPivotCellFromPosition(position);
62799
- if (pivotCell.type === "VALUE") {
63712
+ if (this.isSpilledPivotValueFormula(position, pivotCell)) {
62800
63713
  measurePositions.push(position);
62801
63714
  const pivotId = this.getters.getPivotIdFromPosition(position) || "";
62802
63715
  measuresByPivotId[pivotId] ??= new Set();
@@ -62833,6 +63746,10 @@ class FormatPlugin extends UIPlugin {
62833
63746
  format,
62834
63747
  });
62835
63748
  }
63749
+ isSpilledPivotValueFormula(position, pivotCell) {
63750
+ const cell = this.getters.getCell(position);
63751
+ return pivotCell.type === "VALUE" && !cell?.isFormula;
63752
+ }
62836
63753
  /**
62837
63754
  * This function allows to adjust the quantity of decimal places after a decimal
62838
63755
  * point on cells containing number value. It does this by changing the cells
@@ -62883,6 +63800,69 @@ class FormatPlugin extends UIPlugin {
62883
63800
  }
62884
63801
  }
62885
63802
 
63803
+ class GeoFeaturePlugin extends UIPlugin {
63804
+ static getters = [
63805
+ "getGeoJsonFeatures",
63806
+ "geoFeatureNameToId",
63807
+ "getGeoChartAvailableRegions",
63808
+ ];
63809
+ geoJsonService;
63810
+ geoJsonCache = {};
63811
+ constructor(config) {
63812
+ super(config);
63813
+ this.geoJsonService = config.external.geoJsonService;
63814
+ }
63815
+ getGeoChartAvailableRegions() {
63816
+ if (!this.geoJsonService) {
63817
+ console.error("No geoJsonService provided to the model");
63818
+ return [];
63819
+ }
63820
+ return this.geoJsonService.getAvailableRegions() || [];
63821
+ }
63822
+ getGeoJsonFeatures(region) {
63823
+ if (!this.geoJsonService) {
63824
+ console.error("No geoJsonService provided to the model");
63825
+ return;
63826
+ }
63827
+ const cachedGeoJson = this.geoJsonCache[region];
63828
+ if (cachedGeoJson instanceof Promise) {
63829
+ return undefined;
63830
+ }
63831
+ if (cachedGeoJson !== undefined) {
63832
+ return cachedGeoJson ?? undefined;
63833
+ }
63834
+ this.geoJsonCache[region] = new Promise(async (resolve) => {
63835
+ const json = await this.geoJsonService?.getTopoJson(region);
63836
+ this.geoJsonCache[region] = this.convertToGeoJson(json);
63837
+ this.dispatch("EVALUATE_CHARTS");
63838
+ resolve();
63839
+ });
63840
+ return undefined;
63841
+ }
63842
+ geoFeatureNameToId(region, featureName) {
63843
+ if (!this.geoJsonService) {
63844
+ console.error("No geoJsonService provided to the model");
63845
+ return;
63846
+ }
63847
+ return this.geoJsonService.geoFeatureNameToId(region, featureName);
63848
+ }
63849
+ convertToGeoJson(json) {
63850
+ if (!json) {
63851
+ return null;
63852
+ }
63853
+ // TopoJSON
63854
+ if (json.type === "Topology") {
63855
+ const features = window.ChartGeo.topojson.feature(json, Object.values(json.objects)[0]);
63856
+ return features.type === "FeatureCollection" ? features.features : [features];
63857
+ }
63858
+ // GeoJSON
63859
+ else if (json.type === "FeatureCollection") {
63860
+ return json.features;
63861
+ }
63862
+ throw new Error("Invalid TopoJSON");
63863
+ }
63864
+ }
63865
+
62886
63866
  class HeaderVisibilityUIPlugin extends UIPlugin {
62887
63867
  static getters = [
62888
63868
  "getNextVisibleCellPosition",
@@ -64420,10 +65400,13 @@ class TableResizeUI extends UIPlugin {
64420
65400
  case "RESIZE_TABLE": {
64421
65401
  const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
64422
65402
  this.dispatch("UPDATE_TABLE", { ...cmd });
64423
- if (!table || !table.config.automaticAutofill)
65403
+ if (!table)
64424
65404
  return;
64425
- const oldTableZone = table.range.zone;
64426
65405
  const newTableZone = this.getters.getRangeFromRangeData(cmd.newTableRange).zone;
65406
+ this.selection.selectCell(newTableZone.right, newTableZone.bottom);
65407
+ if (!table.config.automaticAutofill)
65408
+ return;
65409
+ const oldTableZone = table.range.zone;
64427
65410
  if (newTableZone.bottom >= oldTableZone.bottom) {
64428
65411
  for (let col = newTableZone.left; col <= newTableZone.right; col++) {
64429
65412
  const autofillSource = { col, row: oldTableZone.bottom, sheetId: cmd.sheetId };
@@ -65629,15 +66612,23 @@ class GridSelectionPlugin extends UIPlugin {
65629
66612
  handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
65630
66613
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
65631
66614
  let currentIndex = cmd.base;
66615
+ const resizingGroups = {};
65632
66616
  for (const element of toRemove) {
65633
66617
  const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
66618
+ const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
66619
+ if (size != currentSize) {
66620
+ resizingGroups[size] ??= [];
66621
+ resizingGroups[size].push(currentIndex);
66622
+ currentIndex += 1;
66623
+ }
66624
+ }
66625
+ for (const size in resizingGroups) {
65634
66626
  this.dispatch("RESIZE_COLUMNS_ROWS", {
65635
66627
  dimension: cmd.dimension,
65636
66628
  sheetId: cmd.sheetId,
65637
- size,
65638
- elements: [currentIndex],
66629
+ size: parseInt(size, 10),
66630
+ elements: resizingGroups[size],
65639
66631
  });
65640
- currentIndex += 1;
65641
66632
  }
65642
66633
  this.dispatch("REMOVE_COLUMNS_ROWS", {
65643
66634
  dimension: cmd.dimension,
@@ -66219,6 +67210,7 @@ class SheetViewPlugin extends UIPlugin {
66219
67210
  break;
66220
67211
  case "DELETE_SHEET":
66221
67212
  this.cleanViewports();
67213
+ this.sheetsWithDirtyViewports.delete(cmd.sheetId);
66222
67214
  break;
66223
67215
  case "ACTIVATE_SHEET":
66224
67216
  this.sheetsWithDirtyViewports.add(cmd.sheetIdTo);
@@ -66869,7 +67861,8 @@ const featurePluginRegistry = new Registry()
66869
67861
  .add("data_cleanup", DataCleanupPlugin)
66870
67862
  .add("table_autofill", TableAutofillPlugin)
66871
67863
  .add("table_ui_resize", TableResizeUI)
66872
- .add("datavalidation_insert", DataValidationInsertionPlugin);
67864
+ .add("datavalidation_insert", DataValidationInsertionPlugin)
67865
+ .add("geo_features", GeoFeaturePlugin);
66873
67866
  // Plugins which have a state, but which should not be shared in collaborative
66874
67867
  const statefulUIPluginRegistry = new Registry()
66875
67868
  .add("selection", GridSelectionPlugin)
@@ -68613,8 +69606,8 @@ css /* scss */ `
68613
69606
  .o-topbar-composer {
68614
69607
  height: fit-content;
68615
69608
  margin-top: -1px;
69609
+ margin-bottom: -1px;
68616
69610
  border: 1px solid;
68617
- z-index: ${ComponentsImportance.TopBarComposer};
68618
69611
  font-family: ${DEFAULT_FONT};
68619
69612
 
68620
69613
  .o-composer:empty:not(:focus):not(.active)::before {
@@ -68672,6 +69665,7 @@ class TopBarComposer extends Component {
68672
69665
  }
68673
69666
  return cssPropertiesToCss({
68674
69667
  "border-color": SELECTION_BORDER_COLOR,
69668
+ "z-index": String(ComponentsImportance.TopBarComposer),
68675
69669
  });
68676
69670
  }
68677
69671
  onFocus(selection) {
@@ -68780,6 +69774,15 @@ css /* scss */ `
68780
69774
  }
68781
69775
  }
68782
69776
 
69777
+ .irregularity-map {
69778
+ border-top: 1px solid ${SEPARATOR_COLOR};
69779
+ height: ${TOPBAR_TOOLBAR_HEIGHT}px;
69780
+
69781
+ .alert-info {
69782
+ border-left: 3px solid ${ALERT_INFO_BORDER};
69783
+ }
69784
+ }
69785
+
68783
69786
  .o-topbar-composer {
68784
69787
  flex-grow: 1;
68785
69788
  }
@@ -68873,8 +69876,10 @@ class TopBar extends Component {
68873
69876
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
68874
69877
  isntToolbarMenu = false;
68875
69878
  composerFocusStore;
69879
+ fingerprints;
68876
69880
  setup() {
68877
69881
  this.composerFocusStore = useStore(ComposerFocusStore);
69882
+ this.fingerprints = useStore(FormulaFingerprintStore);
68878
69883
  useExternalListener(window, "click", this.onExternalClick);
68879
69884
  onWillStart(() => this.updateCellState());
68880
69885
  onWillUpdateProps(() => this.updateCellState());
@@ -69345,7 +70350,7 @@ class Spreadsheet extends Component {
69345
70350
  properties["grid-template-rows"] = `auto`;
69346
70351
  }
69347
70352
  else {
69348
- properties["grid-template-rows"] = `${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
70353
+ properties["grid-template-rows"] = `max-content auto ${BOTTOMBAR_HEIGHT + 1}px`;
69349
70354
  }
69350
70355
  properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
69351
70356
  return cssPropertiesToCss(properties);
@@ -70423,9 +71428,6 @@ class SelectionStreamProcessorImpl {
70423
71428
  getBackToDefault() {
70424
71429
  this.stream.getBackToDefault();
70425
71430
  }
70426
- getAnchor() {
70427
- return this.anchor;
70428
- }
70429
71431
  modifyAnchor(anchor, mode, options) {
70430
71432
  const sheetId = this.getters.getActiveSheetId();
70431
71433
  anchor = {
@@ -73357,6 +74359,7 @@ class Model extends EventBus {
73357
74359
  session: this.session,
73358
74360
  defaultCurrency: this.config.defaultCurrency,
73359
74361
  customColors: this.config.customColors || [],
74362
+ external: this.config.external,
73360
74363
  };
73361
74364
  }
73362
74365
  // ---------------------------------------------------------------------------
@@ -73588,7 +74591,6 @@ const SPREADSHEET_DIMENSIONS = {
73588
74591
  MIN_COL_WIDTH,
73589
74592
  HEADER_HEIGHT,
73590
74593
  HEADER_WIDTH,
73591
- TOPBAR_HEIGHT,
73592
74594
  BOTTOMBAR_HEIGHT,
73593
74595
  DEFAULT_CELL_WIDTH,
73594
74596
  DEFAULT_CELL_HEIGHT,
@@ -73785,6 +74787,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
73785
74787
  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
74788
 
73787
74789
 
73788
- __info__.version = "18.1.0-alpha.7";
73789
- __info__.date = "2024-12-05T10:40:26.512Z";
73790
- __info__.hash = "7b1c39b";
74790
+ __info__.version = "18.1.0";
74791
+ __info__.date = "2024-12-26T06:37:07.879Z";
74792
+ __info__.hash = "c520e89";