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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,10 @@
1
1
 
2
- // @odoo-module ignore
3
2
  /**
4
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
4
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.4.0-alpha.5
7
- * @date 2024-06-14T10:01:40.605Z
8
- * @hash 9ceed96
5
+ * @version 17.4.0-alpha.7
6
+ * @date 2024-06-21T08:42:22.370Z
7
+ * @hash a99db9a
9
8
  */
10
9
 
11
10
  'use strict';
@@ -6202,7 +6201,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6202
6201
  };
6203
6202
  }
6204
6203
  isPasteAllowed(sheetId, target, content, clipboardOptions) {
6205
- if (!("cells" in content)) {
6204
+ if (!content.cells) {
6206
6205
  return "Success" /* CommandResult.Success */;
6207
6206
  }
6208
6207
  if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
@@ -6216,6 +6215,17 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6216
6215
  return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
6217
6216
  }
6218
6217
  }
6218
+ const clipboardHeight = content.cells.length;
6219
+ const clipboardWidth = content.cells[0].length;
6220
+ for (const zone of getPasteZones(target, content.cells)) {
6221
+ if (this.getters.doesIntersectMerge(sheetId, zone)) {
6222
+ if (target.length > 1 ||
6223
+ !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6224
+ clipboardHeight * clipboardWidth !== 1) {
6225
+ return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6226
+ }
6227
+ }
6228
+ }
6219
6229
  return "Success" /* CommandResult.Success */;
6220
6230
  }
6221
6231
  /**
@@ -6455,13 +6465,22 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6455
6465
  }
6456
6466
  const { rowsIndexes, columnsIndexes } = data;
6457
6467
  const sheetId = data.sheetId;
6458
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6459
- return {
6460
- cellPositions,
6461
- };
6468
+ const cfRules = [];
6469
+ for (const row of rowsIndexes) {
6470
+ const cfRuleInRow = [];
6471
+ for (const col of columnsIndexes) {
6472
+ const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
6473
+ cfRuleInRow.push({
6474
+ position: { col, row, sheetId },
6475
+ rules: cfRules,
6476
+ });
6477
+ }
6478
+ cfRules.push(cfRuleInRow);
6479
+ }
6480
+ return { cfRules };
6462
6481
  }
6463
6482
  paste(target, clippedContent, options) {
6464
- if (!clippedContent?.cellPositions ||
6483
+ if (!clippedContent?.cfRules ||
6465
6484
  options?.pasteOption === "asValue" ||
6466
6485
  !("zones" in target) ||
6467
6486
  !target.zones.length) {
@@ -6470,7 +6489,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6470
6489
  const zones = target.zones;
6471
6490
  const sheetId = target.sheetId;
6472
6491
  if (!options?.isCutOperation) {
6473
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6492
+ this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6474
6493
  }
6475
6494
  else {
6476
6495
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6478,12 +6497,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6478
6497
  }
6479
6498
  pasteFromCut(sheetId, target, content) {
6480
6499
  const selection = target[0];
6481
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6500
+ this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
6482
6501
  isCutOperation: true,
6483
6502
  });
6484
6503
  }
6485
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6486
- for (const [r, rowCells] of positions.entries()) {
6504
+ pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
6505
+ for (const [r, rowCells] of cfRules.entries()) {
6487
6506
  for (const [c, origin] of rowCells.entries()) {
6488
6507
  const position = { col: col + c, row: row + r, sheetId };
6489
6508
  this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
@@ -6491,23 +6510,21 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6491
6510
  }
6492
6511
  }
6493
6512
  pasteCf(origin, target, isCutOperation) {
6494
- const zone = positionToZone(target);
6495
- for (const rule of this.getters.getConditionalFormats(origin.sheetId)) {
6496
- for (const range of rule.ranges) {
6497
- if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
6498
- const toRemoveZones = [];
6499
- if (isCutOperation) {
6500
- //remove from current rule
6501
- toRemoveZones.push(positionToZone(origin));
6502
- }
6503
- if (origin.sheetId === target.sheetId) {
6504
- this.adaptCFRules(origin.sheetId, rule, [zone], toRemoveZones);
6505
- }
6506
- else {
6507
- this.adaptCFRules(origin.sheetId, rule, [], toRemoveZones);
6508
- const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6509
- this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6510
- }
6513
+ if (origin?.rules && origin.rules.length > 0) {
6514
+ const zone = positionToZone(target);
6515
+ for (const rule of origin.rules) {
6516
+ const toRemoveZones = [];
6517
+ if (isCutOperation) {
6518
+ //remove from current rule
6519
+ toRemoveZones.push(positionToZone(origin.position));
6520
+ }
6521
+ if (origin.position.sheetId === target.sheetId) {
6522
+ this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
6523
+ }
6524
+ else {
6525
+ this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
6526
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6527
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6511
6528
  }
6512
6529
  }
6513
6530
  }
@@ -6550,13 +6567,20 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6550
6567
  }
6551
6568
  const { rowsIndexes, columnsIndexes } = data;
6552
6569
  const sheetId = data.sheetId;
6553
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6554
- return {
6555
- cellPositions,
6556
- };
6570
+ const dvRules = [];
6571
+ for (const row of rowsIndexes) {
6572
+ const dvRuleInRow = [];
6573
+ for (const col of columnsIndexes) {
6574
+ const position = { sheetId, col, row };
6575
+ const rule = this.getters.getValidationRuleForCell(position);
6576
+ dvRuleInRow.push({ position, rule });
6577
+ }
6578
+ dvRules.push(dvRuleInRow);
6579
+ }
6580
+ return { dvRules };
6557
6581
  }
6558
6582
  paste(target, clippedContent, options) {
6559
- if (!clippedContent?.cellPositions) {
6583
+ if (!clippedContent?.dvRules) {
6560
6584
  return;
6561
6585
  }
6562
6586
  if (options?.pasteOption) {
@@ -6568,7 +6592,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6568
6592
  const zones = target.zones;
6569
6593
  const sheetId = target.sheetId;
6570
6594
  if (!options?.isCutOperation) {
6571
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6595
+ this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6572
6596
  }
6573
6597
  else {
6574
6598
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6576,12 +6600,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6576
6600
  }
6577
6601
  pasteFromCut(sheetId, target, content) {
6578
6602
  const selection = target[0];
6579
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6603
+ this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
6580
6604
  isCutOperation: true,
6581
6605
  });
6582
6606
  }
6583
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6584
- for (const [r, rowCells] of positions.entries()) {
6607
+ pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
6608
+ for (const [r, rowCells] of dvRules.entries()) {
6585
6609
  for (const [c, origin] of rowCells.entries()) {
6586
6610
  const position = { col: col + c, row: row + r, sheetId };
6587
6611
  this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
@@ -6589,41 +6613,43 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6589
6613
  }
6590
6614
  }
6591
6615
  pasteDataValidation(origin, target, isCutOperation) {
6592
- const rule = this.getters.getValidationRuleForCell(origin);
6593
- if (!rule) {
6594
- const targetRule = this.getters.getValidationRuleForCell(target);
6595
- if (targetRule) {
6596
- // Remove the data validation rule on the target cell
6597
- this.adaptDataValidationRule(target.sheetId, targetRule, [], [positionToZone(target)]);
6598
- }
6599
- return;
6600
- }
6601
- const zone = positionToZone(target);
6602
- for (const range of rule.ranges) {
6603
- if (isInside(origin.col, origin.row, range.zone)) {
6604
- const toRemoveZone = [];
6605
- if (isCutOperation) {
6606
- toRemoveZone.push(positionToZone(origin));
6607
- }
6608
- if (origin.sheetId === target.sheetId) {
6609
- this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
6616
+ if (origin) {
6617
+ const zone = positionToZone(target);
6618
+ const rule = origin.rule;
6619
+ if (!rule) {
6620
+ const targetRule = this.getters.getValidationRuleForCell(target);
6621
+ if (targetRule) {
6622
+ // Remove the data validation rule on the target cell
6623
+ this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
6610
6624
  }
6611
- else {
6612
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveZone);
6613
- const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6614
- this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6625
+ return;
6626
+ }
6627
+ const toRemoveZone = [];
6628
+ if (isCutOperation) {
6629
+ toRemoveZone.push(positionToZone(origin.position));
6630
+ }
6631
+ if (origin.position.sheetId === target.sheetId) {
6632
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
6633
+ this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
6634
+ }
6635
+ else {
6636
+ const originRule = this.getters.getValidationRuleForCell(origin.position);
6637
+ if (originRule) {
6638
+ this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
6615
6639
  }
6640
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6641
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6616
6642
  }
6617
6643
  }
6618
6644
  }
6619
- getDataValidationRuleToCopyTo(targetSheetId, originRule) {
6645
+ getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
6620
6646
  const ruleInTargetSheet = this.getters
6621
6647
  .getDataValidationRules(targetSheetId)
6622
6648
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
6623
6649
  originRule.isBlocking === rule.isBlocking);
6624
6650
  return ruleInTargetSheet
6625
6651
  ? ruleInTargetSheet
6626
- : { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
6652
+ : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
6627
6653
  }
6628
6654
  /**
6629
6655
  * Add or remove XCs to a given data validation rule.
@@ -6715,69 +6741,63 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6715
6741
  }
6716
6742
 
6717
6743
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6718
- isPasteAllowed(sheetId, target, content) {
6719
- if (!("cells" in content)) {
6720
- return "Success" /* CommandResult.Success */;
6744
+ copy(data) {
6745
+ if (!data.zones.length) {
6746
+ return;
6721
6747
  }
6722
- const clipboardHeight = content.cells.length;
6723
- const clipboardWidth = content.cells[0].length;
6724
- for (const zone of getPasteZones(target, content.cells)) {
6725
- if (this.getters.doesIntersectMerge(sheetId, zone)) {
6726
- if (target.length > 1 ||
6727
- !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6728
- clipboardHeight * clipboardWidth !== 1) {
6729
- return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6730
- }
6748
+ const sheetId = this.getters.getActiveSheetId();
6749
+ const { rowsIndexes, columnsIndexes } = data;
6750
+ const merges = [];
6751
+ for (const row of rowsIndexes) {
6752
+ const mergesInRow = [];
6753
+ for (const col of columnsIndexes) {
6754
+ const position = { col, row, sheetId };
6755
+ mergesInRow.push(this.getters.getMerge(position));
6731
6756
  }
6757
+ merges.push(mergesInRow);
6732
6758
  }
6733
- return "Success" /* CommandResult.Success */;
6759
+ return { merges };
6734
6760
  }
6735
6761
  /**
6736
6762
  * Paste the clipboard content in the given target
6737
6763
  */
6738
6764
  paste(target, content, options) {
6739
- if (options?.isCutOperation || !("zones" in target) || !target.zones.length) {
6765
+ if (!content.merges ||
6766
+ options?.isCutOperation ||
6767
+ !("zones" in target) ||
6768
+ !target.zones.length) {
6740
6769
  return;
6741
6770
  }
6742
- this.pasteFromCopy(target.sheetId, target.zones, content.cells, options);
6771
+ this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
6743
6772
  }
6744
- pasteZone(sheetId, col, row, cells) {
6745
- for (const [r, rowCells] of cells.entries()) {
6746
- for (const [c, origin] of rowCells.entries()) {
6747
- if (!origin.position) {
6748
- continue;
6749
- }
6773
+ pasteZone(sheetId, col, row, merges) {
6774
+ for (const [r, rowMerges] of merges.entries()) {
6775
+ for (const [c, originMerge] of rowMerges.entries()) {
6750
6776
  const position = { col: col + c, row: row + r, sheetId };
6751
- this.pasteMergeIfExist(origin.position, position);
6777
+ this.pasteMerge(originMerge, position);
6752
6778
  }
6753
6779
  }
6754
6780
  }
6755
- /**
6756
- * If the origin position given is the top left of a merge, merge the target
6757
- * position.
6758
- */
6759
- pasteMergeIfExist(origin, target) {
6760
- let { sheetId, col, row } = origin;
6761
- const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
6762
- if (mainCellColOrigin === col && mainCellRowOrigin === row) {
6763
- const merge = this.getters.getMerge(origin);
6764
- if (!merge) {
6765
- return;
6766
- }
6767
- ({ sheetId, col, row } = target);
6768
- this.dispatch("ADD_MERGE", {
6769
- sheetId,
6770
- force: true,
6771
- target: [
6772
- {
6773
- left: col,
6774
- top: row,
6775
- right: col + merge.right - merge.left,
6776
- bottom: row + merge.bottom - merge.top,
6777
- },
6778
- ],
6779
- });
6781
+ pasteMerge(originMerge, target) {
6782
+ if (!originMerge) {
6783
+ return;
6784
+ }
6785
+ if (this.getters.isInMerge(target)) {
6786
+ return;
6780
6787
  }
6788
+ const { sheetId, col, row } = target;
6789
+ this.dispatch("ADD_MERGE", {
6790
+ sheetId,
6791
+ force: true,
6792
+ target: [
6793
+ {
6794
+ left: col,
6795
+ top: row,
6796
+ right: col + originMerge.right - originMerge.left,
6797
+ bottom: row + originMerge.bottom - originMerge.top,
6798
+ },
6799
+ ],
6800
+ });
6781
6801
  }
6782
6802
  }
6783
6803
 
@@ -7757,6 +7777,486 @@ function errorCell(value, message) {
7757
7777
  };
7758
7778
  }
7759
7779
 
7780
+ function boolAnd(args) {
7781
+ let foundBoolean = false;
7782
+ let acc = true;
7783
+ conditionalVisitBoolean(args, (arg) => {
7784
+ foundBoolean = true;
7785
+ acc = acc && arg;
7786
+ return acc;
7787
+ });
7788
+ return {
7789
+ foundBoolean,
7790
+ result: acc,
7791
+ };
7792
+ }
7793
+ function boolOr(args) {
7794
+ let foundBoolean = false;
7795
+ let acc = false;
7796
+ conditionalVisitBoolean(args, (arg) => {
7797
+ foundBoolean = true;
7798
+ acc = acc || arg;
7799
+ return !acc;
7800
+ });
7801
+ return {
7802
+ foundBoolean,
7803
+ result: acc,
7804
+ };
7805
+ }
7806
+
7807
+ function sum(values, locale) {
7808
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7809
+ }
7810
+ function countUnique(args) {
7811
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7812
+ }
7813
+
7814
+ function assertSameNumberOfElements(...args) {
7815
+ const dims = args[0].length;
7816
+ args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
7817
+ }
7818
+ function average(values, locale) {
7819
+ let count = 0;
7820
+ const sum = reduceNumbers(values, (acc, a) => {
7821
+ count += 1;
7822
+ return acc + a;
7823
+ }, 0, locale);
7824
+ assertNotZero(count);
7825
+ return sum / count;
7826
+ }
7827
+ function countNumbers(values, locale) {
7828
+ let count = 0;
7829
+ for (let n of values) {
7830
+ if (isMatrix(n)) {
7831
+ for (let i of n) {
7832
+ for (let j of i) {
7833
+ if (typeof j.value === "number") {
7834
+ count += 1;
7835
+ }
7836
+ }
7837
+ }
7838
+ }
7839
+ else {
7840
+ const value = n?.value;
7841
+ if (!isEvaluationError(value) &&
7842
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7843
+ count += 1;
7844
+ }
7845
+ }
7846
+ }
7847
+ return count;
7848
+ }
7849
+ function countAny(values) {
7850
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7851
+ }
7852
+ function max(values, locale) {
7853
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7854
+ return result === -Infinity ? 0 : result;
7855
+ }
7856
+ function min(values, locale) {
7857
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7858
+ return result === Infinity ? 0 : result;
7859
+ }
7860
+
7861
+ const pivotTimeAdapterRegistry = new Registry();
7862
+ function pivotTimeAdapter(granularity) {
7863
+ return pivotTimeAdapterRegistry.get(granularity);
7864
+ }
7865
+ /**
7866
+ * The Time Adapter: Managing Time Periods for Pivot Functions
7867
+ *
7868
+ * Overview:
7869
+ * A time adapter is responsible for managing time periods associated with pivot functions.
7870
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7871
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
7872
+ * and the pivot.
7873
+ * By normalizing the period value, it can be stored consistently in the pivot.
7874
+ *
7875
+ * Normalization Process:
7876
+ * When working with functions in the spreadsheet, the time adapter normalizes
7877
+ * the provided period to facilitate accurate lookup of values in the pivot.
7878
+ * For instance, if the spreadsheet function represents a day period as a number generated
7879
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7880
+ *
7881
+ */
7882
+ /**
7883
+ * Normalized value: "12/25/2023"
7884
+ *
7885
+ * Note: Those two format are equivalent:
7886
+ * - "MM/dd/yyyy" (luxon format)
7887
+ * - "mm/dd/yyyy" (spreadsheet format)
7888
+ **/
7889
+ const dayAdapter = {
7890
+ normalizeFunctionValue(value) {
7891
+ const date = toNumber(value, DEFAULT_LOCALE);
7892
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7893
+ },
7894
+ getFormat(locale) {
7895
+ return (locale ?? DEFAULT_LOCALE).dateFormat;
7896
+ },
7897
+ formatValue(normalizedValue, locale) {
7898
+ locale = locale ?? DEFAULT_LOCALE;
7899
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7900
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7901
+ },
7902
+ toCellValue(normalizedValue) {
7903
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7904
+ },
7905
+ };
7906
+ /**
7907
+ * normalizes day of month number
7908
+ */
7909
+ const dayOfMonthAdapter = {
7910
+ normalizeFunctionValue(value) {
7911
+ const day = toNumber(value, DEFAULT_LOCALE);
7912
+ if (day < 1 || day > 31) {
7913
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7914
+ }
7915
+ return day;
7916
+ },
7917
+ getFormat() {
7918
+ return "0";
7919
+ },
7920
+ formatValue(normalizedValue, locale) {
7921
+ locale = locale ?? DEFAULT_LOCALE;
7922
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7923
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7924
+ },
7925
+ toCellValue(normalizedValue) {
7926
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7927
+ },
7928
+ };
7929
+ /**
7930
+ * Normalized value: "2/2023" for week 2 of 2023
7931
+ */
7932
+ const weekAdapter = {
7933
+ normalizeFunctionValue(value) {
7934
+ const [week, year] = value.split("/");
7935
+ return `${Number(week)}/${Number(year)}`;
7936
+ },
7937
+ getFormat() {
7938
+ return undefined;
7939
+ },
7940
+ formatValue(normalizedValue) {
7941
+ const [week, year] = normalizedValue.split("/");
7942
+ return _t("W%(week)s %(year)s", { week, year });
7943
+ },
7944
+ toCellValue(normalizedValue) {
7945
+ return this.formatValue(normalizedValue);
7946
+ },
7947
+ };
7948
+ /**
7949
+ * normalizes iso week number
7950
+ */
7951
+ const isoWeekNumberAdapter = {
7952
+ normalizeFunctionValue(value) {
7953
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
7954
+ if (isoWeek < 0 || isoWeek > 53) {
7955
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7956
+ }
7957
+ return isoWeek;
7958
+ },
7959
+ getFormat() {
7960
+ return "0";
7961
+ },
7962
+ formatValue(normalizedValue, locale) {
7963
+ locale = locale ?? DEFAULT_LOCALE;
7964
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7965
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7966
+ },
7967
+ toCellValue(normalizedValue) {
7968
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7969
+ },
7970
+ };
7971
+ /**
7972
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7973
+ * e.g. "01/2020" for January 2020
7974
+ */
7975
+ const monthAdapter = {
7976
+ normalizeFunctionValue(value) {
7977
+ const date = toNumber(value, DEFAULT_LOCALE);
7978
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7979
+ },
7980
+ getFormat() {
7981
+ return "mmmm yyyy";
7982
+ },
7983
+ formatValue(normalizedValue, locale) {
7984
+ locale = locale ?? DEFAULT_LOCALE;
7985
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7986
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7987
+ },
7988
+ toCellValue(normalizedValue) {
7989
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7990
+ },
7991
+ };
7992
+ /**
7993
+ * normalizes month number
7994
+ */
7995
+ const monthNumberAdapter = {
7996
+ normalizeFunctionValue(value) {
7997
+ const month = toNumber(value, DEFAULT_LOCALE);
7998
+ if (month < 1 || month > 12) {
7999
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
8000
+ }
8001
+ return month;
8002
+ },
8003
+ getFormat() {
8004
+ return "0";
8005
+ },
8006
+ formatValue(normalizedValue, locale) {
8007
+ locale = locale ?? DEFAULT_LOCALE;
8008
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8009
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8010
+ },
8011
+ toCellValue(normalizedValue) {
8012
+ return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8013
+ },
8014
+ };
8015
+ /**
8016
+ * normalized quarter value is "quarter/year"
8017
+ * e.g. "1/2020" for Q1 2020
8018
+ */
8019
+ const quarterAdapter = {
8020
+ normalizeFunctionValue(value) {
8021
+ const [quarter, year] = value.split("/");
8022
+ return `${quarter}/${year}`;
8023
+ },
8024
+ getFormat() {
8025
+ return undefined;
8026
+ },
8027
+ formatValue(normalizedValue) {
8028
+ const [quarter, year] = normalizedValue.split("/");
8029
+ return _t("Q%(quarter)s %(year)s", { quarter, year });
8030
+ },
8031
+ toCellValue(normalizedValue) {
8032
+ return this.formatValue(normalizedValue);
8033
+ },
8034
+ };
8035
+ /**
8036
+ * normalizes quarter number
8037
+ */
8038
+ const quarterNumberAdapter = {
8039
+ normalizeFunctionValue(value) {
8040
+ const quarter = toNumber(value, DEFAULT_LOCALE);
8041
+ if (quarter < 1 || quarter > 4) {
8042
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8043
+ }
8044
+ return quarter;
8045
+ },
8046
+ getFormat() {
8047
+ return "0";
8048
+ },
8049
+ formatValue(normalizedValue, locale) {
8050
+ locale = locale ?? DEFAULT_LOCALE;
8051
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8052
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8053
+ },
8054
+ toCellValue(normalizedValue) {
8055
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8056
+ },
8057
+ };
8058
+ const yearAdapter = {
8059
+ normalizeFunctionValue(value) {
8060
+ return toNumber(value, DEFAULT_LOCALE);
8061
+ },
8062
+ getFormat() {
8063
+ return "0";
8064
+ },
8065
+ formatValue(normalizedValue, locale) {
8066
+ locale = locale ?? DEFAULT_LOCALE;
8067
+ return formatValue(normalizedValue, { locale, format: "0" });
8068
+ },
8069
+ toCellValue(normalizedValue) {
8070
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8071
+ },
8072
+ };
8073
+ pivotTimeAdapterRegistry
8074
+ .add("day", dayAdapter)
8075
+ .add("week", weekAdapter)
8076
+ .add("month", monthAdapter)
8077
+ .add("quarter", quarterAdapter)
8078
+ .add("year", yearAdapter)
8079
+ .add("day_of_month", dayOfMonthAdapter)
8080
+ .add("iso_week_number", isoWeekNumberAdapter)
8081
+ .add("month_number", monthNumberAdapter)
8082
+ .add("quarter_number", quarterNumberAdapter)
8083
+ .add("year_number", yearAdapter);
8084
+
8085
+ const AGGREGATOR_NAMES = {
8086
+ count: _t("Count"),
8087
+ count_distinct: _t("Count Distinct"),
8088
+ bool_and: _t("Boolean And"),
8089
+ bool_or: _t("Boolean Or"),
8090
+ max: _t("Maximum"),
8091
+ min: _t("Minimum"),
8092
+ avg: _t("Average"),
8093
+ sum: _t("Sum"),
8094
+ };
8095
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8096
+ const AGGREGATORS_BY_FIELD_TYPE = {
8097
+ integer: NUMBER_CHAR_AGGREGATORS,
8098
+ char: NUMBER_CHAR_AGGREGATORS,
8099
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8100
+ };
8101
+ const AGGREGATORS = {};
8102
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8103
+ AGGREGATORS[type] = {};
8104
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8105
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8106
+ }
8107
+ }
8108
+ const AGGREGATORS_FN = {
8109
+ count: {
8110
+ fn: (args) => countAny([args]),
8111
+ format: () => "0",
8112
+ },
8113
+ count_distinct: {
8114
+ fn: (args) => countUnique([args]),
8115
+ format: () => "0",
8116
+ },
8117
+ bool_and: {
8118
+ fn: (args) => boolAnd([args]).result,
8119
+ format: () => undefined,
8120
+ },
8121
+ bool_or: {
8122
+ fn: (args) => boolOr([args]).result,
8123
+ format: () => undefined,
8124
+ },
8125
+ max: {
8126
+ fn: (args, locale) => max([args], locale),
8127
+ format: inferFormat,
8128
+ },
8129
+ min: {
8130
+ fn: (args, locale) => min([args], locale),
8131
+ format: inferFormat,
8132
+ },
8133
+ avg: {
8134
+ fn: (args, locale) => average([args], locale),
8135
+ format: inferFormat,
8136
+ },
8137
+ sum: {
8138
+ fn: (args, locale) => sum([args], locale),
8139
+ format: inferFormat,
8140
+ },
8141
+ };
8142
+ function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8143
+ switch (pivotCell.type) {
8144
+ case "HEADER":
8145
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8146
+ case "MEASURE_HEADER":
8147
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8148
+ case "VALUE":
8149
+ return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8150
+ case "EMPTY":
8151
+ return "";
8152
+ }
8153
+ }
8154
+ /**
8155
+ * Build a pivot formula expression
8156
+ */
8157
+ function makePivotFormula(formula, args) {
8158
+ return `=${formula}(${args
8159
+ .map((arg) => {
8160
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8161
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
8162
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8163
+ })
8164
+ .join(",")})`;
8165
+ }
8166
+ /**
8167
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8168
+ * in this object
8169
+ * If the object has no keys, return 0
8170
+ *
8171
+ */
8172
+ function getMaxObjectId(o) {
8173
+ const keys = Object.keys(o);
8174
+ if (!keys.length) {
8175
+ return 0;
8176
+ }
8177
+ const nums = keys.map((id) => parseInt(id, 10));
8178
+ const max = Math.max(...nums);
8179
+ return max;
8180
+ }
8181
+ const ALL_PERIODS = {
8182
+ year: _t("Year"),
8183
+ quarter: _t("Quarter"),
8184
+ month: _t("Month"),
8185
+ week: _t("Week"),
8186
+ day: _t("Day"),
8187
+ year_number: _t("Year"),
8188
+ quarter_number: _t("Quarter"),
8189
+ month_number: _t("Month"),
8190
+ iso_week_number: _t("Week"),
8191
+ day_of_month: _t("Day of Month"),
8192
+ };
8193
+ const DATE_FIELDS = ["date", "datetime"];
8194
+ /**
8195
+ * Parse a dimension string into a pivot dimension definition.
8196
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8197
+ */
8198
+ function parseDimension(dimension) {
8199
+ const [name, granularity] = dimension.split(":");
8200
+ if (granularity) {
8201
+ return { name, granularity };
8202
+ }
8203
+ return { name };
8204
+ }
8205
+ function isDateField(field) {
8206
+ return DATE_FIELDS.includes(field.type);
8207
+ }
8208
+ function toPivotDomain(domainStr) {
8209
+ if (domainStr.length % 2 !== 0) {
8210
+ throw new Error("Invalid domain: odd number of elements");
8211
+ }
8212
+ const domain = [];
8213
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
8214
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8215
+ }
8216
+ return domain;
8217
+ }
8218
+ function flatPivotDomain(domain) {
8219
+ return domain.flatMap((arg) => [arg.field, arg.value]);
8220
+ }
8221
+ /**
8222
+ * Parses the value defining a pivot group in a PIVOT formula
8223
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8224
+ * the two group values are "42" and "won".
8225
+ */
8226
+ function toNormalizedPivotValue(dimension, groupValue) {
8227
+ if (groupValue === null || groupValue === "null") {
8228
+ return null;
8229
+ }
8230
+ const groupValueString = typeof groupValue === "boolean"
8231
+ ? toString(groupValue).toLocaleLowerCase()
8232
+ : toString(groupValue);
8233
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8234
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8235
+ field: dimension.displayName,
8236
+ type: dimension.type,
8237
+ }));
8238
+ }
8239
+ // represents a field which is not set (=False server side)
8240
+ if (groupValueString === "false") {
8241
+ return false;
8242
+ }
8243
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8244
+ return normalizer(groupValueString, dimension.granularity);
8245
+ }
8246
+ function normalizeDateTime(value, granularity) {
8247
+ if (!granularity) {
8248
+ throw "";
8249
+ }
8250
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8251
+ }
8252
+ const pivotNormalizationValueRegistry = new Registry();
8253
+ pivotNormalizationValueRegistry
8254
+ .add("date", normalizeDateTime)
8255
+ .add("datetime", normalizeDateTime)
8256
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8257
+ .add("boolean", (value) => toBoolean(value))
8258
+ .add("char", (value) => toString(value));
8259
+
7760
8260
  /**
7761
8261
  * Change the reference types inside the given token, if the token represent a range or a cell
7762
8262
  *
@@ -7955,6 +8455,11 @@ const CellIsOperators = {
7955
8455
  const ChartTerms = {
7956
8456
  Series: _t("Series"),
7957
8457
  BackgroundColor: _t("Background color"),
8458
+ StackedBarChart: _t("Stacked bar chart"),
8459
+ StackedLineChart: _t("Stacked line chart"),
8460
+ CumulativeData: _t("Cumulative data"),
8461
+ TreatLabelsAsText: _t("Treat labels as text"),
8462
+ AggregatedChart: _t("Aggregate"),
7958
8463
  Errors: {
7959
8464
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7960
8465
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -8887,6 +9392,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8887
9392
  }
8888
9393
  const color = highlight.color || HIGHLIGHT_COLOR;
8889
9394
  const { ctx } = renderingContext;
9395
+ ctx.save();
8890
9396
  if (!highlight.noBorder) {
8891
9397
  if (highlight.dashed) {
8892
9398
  ctx.setLineDash([5, 3]);
@@ -8906,6 +9412,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8906
9412
  ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8907
9413
  ctx.fillRect(x, y, width, height);
8908
9414
  }
9415
+ ctx.restore();
8909
9416
  }
8910
9417
 
8911
9418
  class HighlightStore extends SpreadsheetStore {
@@ -9436,8 +9943,20 @@ class ComposerStore extends SpreadsheetStore {
9436
9943
  replaceSelectedRange(zone) {
9437
9944
  const ref = this.getZoneReference(zone);
9438
9945
  const currentToken = this.tokenAtCursor;
9439
- const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
9440
- this.replaceText(ref, start, this.selectionEnd);
9946
+ let replaceStart = this.selectionStart;
9947
+ if (currentToken?.type === "REFERENCE") {
9948
+ replaceStart = currentToken.start;
9949
+ }
9950
+ else if (currentToken?.type === "RIGHT_PAREN") {
9951
+ // match left parenthesis
9952
+ const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
9953
+ const functionToken = this.currentTokens[leftParenthesisIndex - 1];
9954
+ if (functionToken === undefined) {
9955
+ return;
9956
+ }
9957
+ replaceStart = functionToken.start;
9958
+ }
9959
+ this.replaceText(ref, replaceStart, this.selectionEnd);
9441
9960
  }
9442
9961
  /**
9443
9962
  * Replace the reference of the old zone by the new one.
@@ -9457,10 +9976,8 @@ class ComposerStore extends SpreadsheetStore {
9457
9976
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
9458
9977
  return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
9459
9978
  });
9460
- // this function assumes that the previous range is always found because
9461
- // it's called when changing a highlight, which exists by definition
9462
9979
  if (!previousRefToken) {
9463
- throw new Error("Previous range not found");
9980
+ return;
9464
9981
  }
9465
9982
  const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
9466
9983
  this.selectionStart = previousRefToken.start;
@@ -9472,6 +9989,17 @@ class ComposerStore extends SpreadsheetStore {
9472
9989
  getZoneReference(zone) {
9473
9990
  const inputSheetId = this.currentEditedCell.sheetId;
9474
9991
  const sheetId = this.getters.getActiveSheetId();
9992
+ if (zone.top === zone.bottom && zone.left === zone.right) {
9993
+ const position = { sheetId, col: zone.left, row: zone.top };
9994
+ const pivotId = this.getters.getPivotIdFromPosition(position);
9995
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
9996
+ const cell = this.getters.getCell(position);
9997
+ if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9998
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
9999
+ const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
10000
+ return formula.slice(1); // strip leading =
10001
+ }
10002
+ }
9475
10003
  const range = this.getters.getRangeFromZone(sheetId, zone);
9476
10004
  return this.getters.getSelectionRangeString(range, inputSheetId);
9477
10005
  }
@@ -9574,19 +10102,35 @@ class ComposerStore extends SpreadsheetStore {
9574
10102
  const colorIndex = this.colorIndexByRange[rangeString];
9575
10103
  return colors$1[colorIndex % colors$1.length];
9576
10104
  };
9577
- return this.getReferencedRanges().map((range) => {
10105
+ const highlights = [];
10106
+ for (const range of this.getReferencedRanges()) {
9578
10107
  const rangeString = this.getters.getRangeString(range, editionSheetId);
9579
10108
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
9580
10109
  const zone = numberOfRows * numberOfCols === 1
9581
10110
  ? this.getters.expandZone(range.sheetId, range.zone)
9582
10111
  : range.zone;
9583
- return {
10112
+ highlights.push({
9584
10113
  zone,
9585
10114
  color: rangeColor(rangeString),
9586
10115
  sheetId: range.sheetId,
9587
10116
  interactive: true,
9588
- };
9589
- });
10117
+ });
10118
+ }
10119
+ const activeSheetId = this.getters.getActiveSheetId();
10120
+ const selectionZone = this.model.selection.getAnchor().zone;
10121
+ const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
10122
+ if (this.editionMode === "selecting" && !isSelectionHightlighted) {
10123
+ highlights.push({
10124
+ zone: selectionZone,
10125
+ color: "#445566",
10126
+ sheetId: activeSheetId,
10127
+ dashed: true,
10128
+ interactive: false,
10129
+ noFill: true,
10130
+ thinLine: true,
10131
+ });
10132
+ }
10133
+ return highlights;
9590
10134
  }
9591
10135
  /**
9592
10136
  * Return ranges currently referenced in the composer
@@ -9845,8 +10389,14 @@ class ChartJsComponent extends owl.Component {
9845
10389
  owl.useEffect(() => {
9846
10390
  const runtime = this.chartRuntime;
9847
10391
  if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10392
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10393
+ this.chart?.destroy();
10394
+ this.createChart(deepCopy(runtime.chartJsConfig));
10395
+ }
10396
+ else {
10397
+ this.updateChartJs(deepCopy(runtime));
10398
+ }
9848
10399
  this.currentRuntime = runtime;
9849
- this.updateChartJs(deepCopy(runtime));
9850
10400
  }
9851
10401
  });
9852
10402
  }
@@ -10214,6 +10764,9 @@ function getChartAxisTitleRuntime(design) {
10214
10764
  }
10215
10765
  function getDefinedAxis(definition) {
10216
10766
  let useLeftAxis = false, useRightAxis = false;
10767
+ if ("horizontal" in definition && definition.horizontal) {
10768
+ return { useLeftAxis: true, useRightAxis: false };
10769
+ }
10217
10770
  for (const design of definition.dataSets || []) {
10218
10771
  if (design.yAxisId === "y1") {
10219
10772
  useRightAxis = true;
@@ -11781,13 +12334,6 @@ var misc = /*#__PURE__*/Object.freeze({
11781
12334
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11782
12335
  });
11783
12336
 
11784
- function sum(values, locale) {
11785
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11786
- }
11787
- function countUnique(args) {
11788
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
11789
- }
11790
-
11791
12337
  const DEFAULT_FACTOR = 1;
11792
12338
  const DEFAULT_MODE = 0;
11793
12339
  const DEFAULT_PLACES = 0;
@@ -12901,53 +13447,6 @@ var math = /*#__PURE__*/Object.freeze({
12901
13447
  TRUNC: TRUNC
12902
13448
  });
12903
13449
 
12904
- function assertSameNumberOfElements(...args) {
12905
- const dims = args[0].length;
12906
- args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
12907
- }
12908
- function average(values, locale) {
12909
- let count = 0;
12910
- const sum = reduceNumbers(values, (acc, a) => {
12911
- count += 1;
12912
- return acc + a;
12913
- }, 0, locale);
12914
- assertNotZero(count);
12915
- return sum / count;
12916
- }
12917
- function countNumbers(values, locale) {
12918
- let count = 0;
12919
- for (let n of values) {
12920
- if (isMatrix(n)) {
12921
- for (let i of n) {
12922
- for (let j of i) {
12923
- if (typeof j.value === "number") {
12924
- count += 1;
12925
- }
12926
- }
12927
- }
12928
- }
12929
- else {
12930
- const value = n?.value;
12931
- if (!isEvaluationError(value) &&
12932
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
12933
- count += 1;
12934
- }
12935
- }
12936
- }
12937
- return count;
12938
- }
12939
- function countAny(values) {
12940
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
12941
- }
12942
- function max(values, locale) {
12943
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12944
- return result === -Infinity ? 0 : result;
12945
- }
12946
- function min(values, locale) {
12947
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12948
- return result === Infinity ? 0 : result;
12949
- }
12950
-
12951
13450
  function filterAndFlatData(dataY, dataX) {
12952
13451
  const _flatDataY = [];
12953
13452
  const _flatDataX = [];
@@ -13503,7 +14002,7 @@ const LARGE = {
13503
14002
  // LINEST
13504
14003
  // -----------------------------------------------------------------------------
13505
14004
  const LINEST = {
13506
- description: _t("Compute the intercept of the linear regression."),
14005
+ description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
13507
14006
  args: [
13508
14007
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13509
14008
  arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -13519,7 +14018,7 @@ const LINEST = {
13519
14018
  // LOGEST
13520
14019
  // -----------------------------------------------------------------------------
13521
14020
  const LOGEST = {
13522
- description: _t("Compute the intercept of the linear regression."),
14021
+ description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
13523
14022
  args: [
13524
14023
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13525
14024
  arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -17922,33 +18421,6 @@ var info = /*#__PURE__*/Object.freeze({
17922
18421
  NA: NA
17923
18422
  });
17924
18423
 
17925
- function boolAnd(args) {
17926
- let foundBoolean = false;
17927
- let acc = true;
17928
- conditionalVisitBoolean(args, (arg) => {
17929
- foundBoolean = true;
17930
- acc = acc && arg;
17931
- return acc;
17932
- });
17933
- return {
17934
- foundBoolean,
17935
- result: acc,
17936
- };
17937
- }
17938
- function boolOr(args) {
17939
- let foundBoolean = false;
17940
- let acc = false;
17941
- conditionalVisitBoolean(args, (arg) => {
17942
- foundBoolean = true;
17943
- acc = acc || arg;
17944
- return !acc;
17945
- });
17946
- return {
17947
- foundBoolean,
17948
- result: acc,
17949
- };
17950
- }
17951
-
17952
18424
  // -----------------------------------------------------------------------------
17953
18425
  // AND
17954
18426
  // -----------------------------------------------------------------------------
@@ -18146,393 +18618,6 @@ var logical = /*#__PURE__*/Object.freeze({
18146
18618
  XOR: XOR
18147
18619
  });
18148
18620
 
18149
- const pivotTimeAdapterRegistry = new Registry();
18150
- function pivotTimeAdapter(granularity) {
18151
- return pivotTimeAdapterRegistry.get(granularity);
18152
- }
18153
- /**
18154
- * The Time Adapter: Managing Time Periods for Pivot Functions
18155
- *
18156
- * Overview:
18157
- * A time adapter is responsible for managing time periods associated with pivot functions.
18158
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
18159
- * The adapter's primary role is to normalize period values between spreadsheet functions,
18160
- * and the pivot.
18161
- * By normalizing the period value, it can be stored consistently in the pivot.
18162
- *
18163
- * Normalization Process:
18164
- * When working with functions in the spreadsheet, the time adapter normalizes
18165
- * the provided period to facilitate accurate lookup of values in the pivot.
18166
- * For instance, if the spreadsheet function represents a day period as a number generated
18167
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
18168
- *
18169
- */
18170
- /**
18171
- * Normalized value: "12/25/2023"
18172
- *
18173
- * Note: Those two format are equivalent:
18174
- * - "MM/dd/yyyy" (luxon format)
18175
- * - "mm/dd/yyyy" (spreadsheet format)
18176
- **/
18177
- const dayAdapter = {
18178
- normalizeFunctionValue(value) {
18179
- const date = toNumber(value, DEFAULT_LOCALE);
18180
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
18181
- },
18182
- getFormat(locale) {
18183
- return (locale ?? DEFAULT_LOCALE).dateFormat;
18184
- },
18185
- formatValue(normalizedValue, locale) {
18186
- locale = locale ?? DEFAULT_LOCALE;
18187
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18188
- return formatValue(value, { locale, format: this.getFormat(locale) });
18189
- },
18190
- toCellValue(normalizedValue) {
18191
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18192
- },
18193
- };
18194
- /**
18195
- * normalizes day of month number
18196
- */
18197
- const dayOfMonthAdapter = {
18198
- normalizeFunctionValue(value) {
18199
- const day = toNumber(value, DEFAULT_LOCALE);
18200
- if (day < 1 || day > 31) {
18201
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
18202
- }
18203
- return day;
18204
- },
18205
- getFormat() {
18206
- return "0";
18207
- },
18208
- formatValue(normalizedValue, locale) {
18209
- locale = locale ?? DEFAULT_LOCALE;
18210
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18211
- return formatValue(value, { locale, format: this.getFormat(locale) });
18212
- },
18213
- toCellValue(normalizedValue) {
18214
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18215
- },
18216
- };
18217
- /**
18218
- * Normalized value: "2/2023" for week 2 of 2023
18219
- */
18220
- const weekAdapter = {
18221
- normalizeFunctionValue(value) {
18222
- const [week, year] = value.split("/");
18223
- return `${Number(week)}/${Number(year)}`;
18224
- },
18225
- getFormat() {
18226
- return undefined;
18227
- },
18228
- formatValue(normalizedValue) {
18229
- const [week, year] = normalizedValue.split("/");
18230
- return _t("W%(week)s %(year)s", { week, year });
18231
- },
18232
- toCellValue(normalizedValue) {
18233
- return this.formatValue(normalizedValue);
18234
- },
18235
- };
18236
- /**
18237
- * normalizes iso week number
18238
- */
18239
- const isoWeekNumberAdapter = {
18240
- normalizeFunctionValue(value) {
18241
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
18242
- if (isoWeek < 0 || isoWeek > 53) {
18243
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
18244
- }
18245
- return isoWeek;
18246
- },
18247
- getFormat() {
18248
- return "0";
18249
- },
18250
- formatValue(normalizedValue, locale) {
18251
- locale = locale ?? DEFAULT_LOCALE;
18252
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18253
- return formatValue(value, { locale, format: this.getFormat(locale) });
18254
- },
18255
- toCellValue(normalizedValue) {
18256
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18257
- },
18258
- };
18259
- /**
18260
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
18261
- * e.g. "01/2020" for January 2020
18262
- */
18263
- const monthAdapter = {
18264
- normalizeFunctionValue(value) {
18265
- const date = toNumber(value, DEFAULT_LOCALE);
18266
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
18267
- },
18268
- getFormat() {
18269
- return "mmmm yyyy";
18270
- },
18271
- formatValue(normalizedValue, locale) {
18272
- locale = locale ?? DEFAULT_LOCALE;
18273
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18274
- return formatValue(value, { locale, format: this.getFormat(locale) });
18275
- },
18276
- toCellValue(normalizedValue) {
18277
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18278
- },
18279
- };
18280
- /**
18281
- * normalizes month number
18282
- */
18283
- const monthNumberAdapter = {
18284
- normalizeFunctionValue(value) {
18285
- const month = toNumber(value, DEFAULT_LOCALE);
18286
- if (month < 1 || month > 12) {
18287
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
18288
- }
18289
- return month;
18290
- },
18291
- getFormat() {
18292
- return "0";
18293
- },
18294
- formatValue(normalizedValue, locale) {
18295
- locale = locale ?? DEFAULT_LOCALE;
18296
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18297
- return formatValue(value, { locale, format: this.getFormat(locale) });
18298
- },
18299
- toCellValue(normalizedValue) {
18300
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
18301
- },
18302
- };
18303
- /**
18304
- * normalized quarter value is "quarter/year"
18305
- * e.g. "1/2020" for Q1 2020
18306
- */
18307
- const quarterAdapter = {
18308
- normalizeFunctionValue(value) {
18309
- const [quarter, year] = value.split("/");
18310
- return `${quarter}/${year}`;
18311
- },
18312
- getFormat() {
18313
- return undefined;
18314
- },
18315
- formatValue(normalizedValue) {
18316
- const [quarter, year] = normalizedValue.split("/");
18317
- return _t("Q%(quarter)s %(year)s", { quarter, year });
18318
- },
18319
- toCellValue(normalizedValue) {
18320
- return this.formatValue(normalizedValue);
18321
- },
18322
- };
18323
- /**
18324
- * normalizes quarter number
18325
- */
18326
- const quarterNumberAdapter = {
18327
- normalizeFunctionValue(value) {
18328
- const quarter = toNumber(value, DEFAULT_LOCALE);
18329
- if (quarter < 1 || quarter > 4) {
18330
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
18331
- }
18332
- return quarter;
18333
- },
18334
- getFormat() {
18335
- return "0";
18336
- },
18337
- formatValue(normalizedValue, locale) {
18338
- locale = locale ?? DEFAULT_LOCALE;
18339
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18340
- return formatValue(value, { locale, format: this.getFormat(locale) });
18341
- },
18342
- toCellValue(normalizedValue) {
18343
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18344
- },
18345
- };
18346
- const yearAdapter = {
18347
- normalizeFunctionValue(value) {
18348
- return toNumber(value, DEFAULT_LOCALE);
18349
- },
18350
- getFormat() {
18351
- return "0";
18352
- },
18353
- formatValue(normalizedValue, locale) {
18354
- locale = locale ?? DEFAULT_LOCALE;
18355
- return formatValue(normalizedValue, { locale, format: "0" });
18356
- },
18357
- toCellValue(normalizedValue) {
18358
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18359
- },
18360
- };
18361
- pivotTimeAdapterRegistry
18362
- .add("day", dayAdapter)
18363
- .add("week", weekAdapter)
18364
- .add("month", monthAdapter)
18365
- .add("quarter", quarterAdapter)
18366
- .add("year", yearAdapter)
18367
- .add("day_of_month", dayOfMonthAdapter)
18368
- .add("iso_week_number", isoWeekNumberAdapter)
18369
- .add("month_number", monthNumberAdapter)
18370
- .add("quarter_number", quarterNumberAdapter)
18371
- .add("year_number", yearAdapter);
18372
-
18373
- const AGGREGATOR_NAMES = {
18374
- count: _t("Count"),
18375
- count_distinct: _t("Count Distinct"),
18376
- bool_and: _t("Boolean And"),
18377
- bool_or: _t("Boolean Or"),
18378
- max: _t("Maximum"),
18379
- min: _t("Minimum"),
18380
- avg: _t("Average"),
18381
- sum: _t("Sum"),
18382
- };
18383
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
18384
- const AGGREGATORS_BY_FIELD_TYPE = {
18385
- integer: NUMBER_CHAR_AGGREGATORS,
18386
- char: NUMBER_CHAR_AGGREGATORS,
18387
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
18388
- };
18389
- const AGGREGATORS = {};
18390
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
18391
- AGGREGATORS[type] = {};
18392
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
18393
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
18394
- }
18395
- }
18396
- const AGGREGATORS_FN = {
18397
- count: {
18398
- fn: (args) => countAny([args]),
18399
- format: () => "0",
18400
- },
18401
- count_distinct: {
18402
- fn: (args) => countUnique([args]),
18403
- format: () => "0",
18404
- },
18405
- bool_and: {
18406
- fn: (args) => boolAnd([args]).result,
18407
- format: () => undefined,
18408
- },
18409
- bool_or: {
18410
- fn: (args) => boolOr([args]).result,
18411
- format: () => undefined,
18412
- },
18413
- max: {
18414
- fn: (args, locale) => max([args], locale),
18415
- format: inferFormat,
18416
- },
18417
- min: {
18418
- fn: (args, locale) => min([args], locale),
18419
- format: inferFormat,
18420
- },
18421
- avg: {
18422
- fn: (args, locale) => average([args], locale),
18423
- format: inferFormat,
18424
- },
18425
- sum: {
18426
- fn: (args, locale) => sum([args], locale),
18427
- format: inferFormat,
18428
- },
18429
- };
18430
- /**
18431
- * Build a pivot formula expression
18432
- */
18433
- function makePivotFormula(formula, args) {
18434
- return `=${formula}(${args
18435
- .map((arg) => {
18436
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
18437
- const convertToNumber = typeof arg == "number" || stringIsNumber;
18438
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
18439
- })
18440
- .join(",")})`;
18441
- }
18442
- /**
18443
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
18444
- * in this object
18445
- * If the object has no keys, return 0
18446
- *
18447
- */
18448
- function getMaxObjectId(o) {
18449
- const keys = Object.keys(o);
18450
- if (!keys.length) {
18451
- return 0;
18452
- }
18453
- const nums = keys.map((id) => parseInt(id, 10));
18454
- const max = Math.max(...nums);
18455
- return max;
18456
- }
18457
- const ALL_PERIODS = {
18458
- year: _t("Year"),
18459
- quarter: _t("Quarter"),
18460
- month: _t("Month"),
18461
- week: _t("Week"),
18462
- day: _t("Day"),
18463
- year_number: _t("Year"),
18464
- quarter_number: _t("Quarter"),
18465
- month_number: _t("Month"),
18466
- iso_week_number: _t("Week"),
18467
- day_of_month: _t("Day of Month"),
18468
- };
18469
- const DATE_FIELDS = ["date", "datetime"];
18470
- /**
18471
- * Parse a dimension string into a pivot dimension definition.
18472
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
18473
- */
18474
- function parseDimension(dimension) {
18475
- const [name, granularity] = dimension.split(":");
18476
- if (granularity) {
18477
- return { name, granularity };
18478
- }
18479
- return { name };
18480
- }
18481
- function isDateField(field) {
18482
- return DATE_FIELDS.includes(field.type);
18483
- }
18484
- function toPivotDomain(domainStr) {
18485
- if (domainStr.length % 2 !== 0) {
18486
- throw new Error("Invalid domain: odd number of elements");
18487
- }
18488
- const domain = [];
18489
- for (let i = 0; i < domainStr.length - 1; i += 2) {
18490
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
18491
- }
18492
- return domain;
18493
- }
18494
- function flatPivotDomain(domain) {
18495
- return domain.flatMap((arg) => [arg.field, arg.value]);
18496
- }
18497
- /**
18498
- * Parses the value defining a pivot group in a PIVOT formula
18499
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
18500
- * the two group values are "42" and "won".
18501
- */
18502
- function toNormalizedPivotValue(dimension, groupValue) {
18503
- if (groupValue === null || groupValue === "null") {
18504
- return null;
18505
- }
18506
- const groupValueString = typeof groupValue === "boolean"
18507
- ? toString(groupValue).toLocaleLowerCase()
18508
- : toString(groupValue);
18509
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
18510
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
18511
- field: dimension.displayName,
18512
- type: dimension.type,
18513
- }));
18514
- }
18515
- // represents a field which is not set (=False server side)
18516
- if (groupValueString === "false") {
18517
- return false;
18518
- }
18519
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
18520
- return normalizer(groupValueString, dimension.granularity);
18521
- }
18522
- function normalizeDateTime(value, granularity) {
18523
- if (!granularity) {
18524
- throw "";
18525
- }
18526
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
18527
- }
18528
- const pivotNormalizationValueRegistry = new Registry();
18529
- pivotNormalizationValueRegistry
18530
- .add("date", normalizeDateTime)
18531
- .add("datetime", normalizeDateTime)
18532
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
18533
- .add("boolean", (value) => toBoolean(value))
18534
- .add("char", (value) => toString(value));
18535
-
18536
18621
  /**
18537
18622
  * Get the pivot ID from the formula pivot ID.
18538
18623
  */
@@ -19109,14 +19194,10 @@ const PIVOT = {
19109
19194
  result[col].push({ value: "" });
19110
19195
  break;
19111
19196
  case "HEADER":
19112
- const domain = pivotCell.domain;
19113
- const lastNode = domain.at(-1);
19114
- if (lastNode?.field === "measure") {
19115
- result[col].push(pivot.getPivotMeasureValue(toString(lastNode.value), domain));
19116
- }
19117
- else {
19118
- result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
19119
- }
19197
+ result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
19198
+ break;
19199
+ case "MEASURE_HEADER":
19200
+ result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
19120
19201
  break;
19121
19202
  case "VALUE":
19122
19203
  result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
@@ -20155,6 +20236,8 @@ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
20155
20236
  * a child element.
20156
20237
  */
20157
20238
  function isChildEvent(parent, ev) {
20239
+ if (!parent)
20240
+ return false;
20158
20241
  return !!ev.target && parent.contains(ev.target);
20159
20242
  }
20160
20243
  function gridOverlayPosition() {
@@ -22694,7 +22777,8 @@ function truncateLabel(label) {
22694
22777
  /**
22695
22778
  * Get a default chart js configuration
22696
22779
  */
22697
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22780
+ function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22781
+ const { format, locale, truncateLabels, horizontalChart } = args;
22698
22782
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22699
22783
  const options = {
22700
22784
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22738,8 +22822,11 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
22738
22822
  callbacks: {
22739
22823
  label: function (tooltipItem) {
22740
22824
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
22741
- // tooltipItem.parsed.y can be an object or a number for pie charts
22742
- const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
22825
+ // tooltipItem.parsed can be an object or a number for pie charts
22826
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
22827
+ if (!yLabel) {
22828
+ yLabel = tooltipItem.parsed;
22829
+ }
22743
22830
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
22744
22831
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
22745
22832
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
@@ -22913,6 +23000,7 @@ class BarChart extends AbstractChart {
22913
23000
  dataSetsHaveTitle;
22914
23001
  dataSetDesign;
22915
23002
  axesDesign;
23003
+ horizontal;
22916
23004
  constructor(definition, sheetId, getters) {
22917
23005
  super(definition, sheetId, getters);
22918
23006
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -22924,6 +23012,7 @@ class BarChart extends AbstractChart {
22924
23012
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22925
23013
  this.dataSetDesign = definition.dataSets;
22926
23014
  this.axesDesign = definition.axesDesign;
23015
+ this.horizontal = definition.horizontal;
22927
23016
  }
22928
23017
  static transformDefinition(definition, executed) {
22929
23018
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22995,6 +23084,7 @@ class BarChart extends AbstractChart {
22995
23084
  stacked: this.stacked,
22996
23085
  aggregated: this.aggregated,
22997
23086
  axesDesign: this.axesDesign,
23087
+ horizontal: this.horizontal,
22998
23088
  };
22999
23089
  }
23000
23090
  getDefinitionForExcel() {
@@ -23026,7 +23116,10 @@ class BarChart extends AbstractChart {
23026
23116
  }
23027
23117
  function getBarConfiguration(chart, labels, localeFormat) {
23028
23118
  const fontColor = chartFontColor(chart.background);
23029
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23119
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
23120
+ ...localeFormat,
23121
+ horizontalChart: chart.horizontal,
23122
+ });
23030
23123
  const legend = {
23031
23124
  labels: { color: fontColor },
23032
23125
  };
@@ -23040,16 +23133,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23040
23133
  config.options.layout = {
23041
23134
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23042
23135
  };
23043
- config.options.scales = {
23044
- x: {
23045
- ticks: {
23046
- padding: 5,
23047
- color: fontColor,
23048
- },
23049
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23050
- },
23051
- };
23052
- const yAxis = {
23136
+ config.options.indexAxis = chart.horizontal ? "y" : "x";
23137
+ config.options.scales = {};
23138
+ const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23139
+ const valuesAxis = {
23053
23140
  beginAtZero: true, // the origin of the y axis is always zero
23054
23141
  ticks: {
23055
23142
  color: fontColor,
@@ -23065,7 +23152,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23065
23152
  },
23066
23153
  },
23067
23154
  };
23155
+ const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
23156
+ const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
23068
23157
  const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23158
+ config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
23069
23159
  if (useLeftAxis) {
23070
23160
  config.options.scales.y = {
23071
23161
  ...yAxis,
@@ -23132,7 +23222,7 @@ function createBarChartRuntime(chart, getters) {
23132
23222
  const label = definition.dataSets[index].label;
23133
23223
  dataset.label = label;
23134
23224
  }
23135
- if (definition.dataSets?.[index]?.yAxisId) {
23225
+ if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
23136
23226
  dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23137
23227
  }
23138
23228
  }
@@ -24123,6 +24213,7 @@ class PieChart extends AbstractChart {
24123
24213
  type = "pie";
24124
24214
  aggregated;
24125
24215
  dataSetsHaveTitle;
24216
+ isDoughnut;
24126
24217
  constructor(definition, sheetId, getters) {
24127
24218
  super(definition, sheetId, getters);
24128
24219
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24131,6 +24222,7 @@ class PieChart extends AbstractChart {
24131
24222
  this.legendPosition = definition.legendPosition;
24132
24223
  this.aggregated = definition.aggregated;
24133
24224
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24225
+ this.isDoughnut = definition.isDoughnut;
24134
24226
  }
24135
24227
  static transformDefinition(definition, executed) {
24136
24228
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24148,6 +24240,7 @@ class PieChart extends AbstractChart {
24148
24240
  type: "pie",
24149
24241
  labelRange: context.auxiliaryRange || undefined,
24150
24242
  aggregated: context.aggregated ?? false,
24243
+ isDoughnut: false,
24151
24244
  };
24152
24245
  }
24153
24246
  getDefinition() {
@@ -24178,6 +24271,7 @@ class PieChart extends AbstractChart {
24178
24271
  : undefined,
24179
24272
  title: this.title,
24180
24273
  aggregated: this.aggregated,
24274
+ isDoughnut: this.isDoughnut,
24181
24275
  };
24182
24276
  }
24183
24277
  copyForSheetId(sheetId) {
@@ -24312,6 +24406,141 @@ function createPieChartRuntime(chart, getters) {
24312
24406
  };
24313
24407
  config.data.datasets.push(dataset);
24314
24408
  }
24409
+ if (chart.isDoughnut) {
24410
+ config.type = "doughnut";
24411
+ }
24412
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24413
+ }
24414
+
24415
+ class PyramidChart extends AbstractChart {
24416
+ dataSets;
24417
+ labelRange;
24418
+ background;
24419
+ legendPosition;
24420
+ aggregated;
24421
+ type = "pyramid";
24422
+ dataSetsHaveTitle;
24423
+ dataSetDesign;
24424
+ axesDesign;
24425
+ horizontal = true;
24426
+ stacked = true;
24427
+ constructor(definition, sheetId, getters) {
24428
+ super(definition, sheetId, getters);
24429
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
24430
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24431
+ this.background = definition.background;
24432
+ this.legendPosition = definition.legendPosition;
24433
+ this.aggregated = definition.aggregated;
24434
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24435
+ this.dataSetDesign = definition.dataSets;
24436
+ this.axesDesign = definition.axesDesign;
24437
+ }
24438
+ static transformDefinition(definition, executed) {
24439
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24440
+ }
24441
+ static validateChartDefinition(validator, definition) {
24442
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24443
+ }
24444
+ static getDefinitionFromContextCreation(context) {
24445
+ return {
24446
+ background: context.background,
24447
+ dataSets: context.range ?? [],
24448
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24449
+ aggregated: context.aggregated ?? false,
24450
+ legendPosition: context.legendPosition ?? "top",
24451
+ title: context.title || { text: "" },
24452
+ type: "pyramid",
24453
+ labelRange: context.auxiliaryRange || undefined,
24454
+ axesDesign: context.axesDesign,
24455
+ horizontal: true,
24456
+ stacked: true,
24457
+ };
24458
+ }
24459
+ getContextCreation() {
24460
+ const range = [];
24461
+ for (const [i, dataSet] of this.dataSets.entries()) {
24462
+ range.push({
24463
+ ...this.dataSetDesign?.[i],
24464
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24465
+ });
24466
+ }
24467
+ return {
24468
+ ...this,
24469
+ range,
24470
+ auxiliaryRange: this.labelRange
24471
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24472
+ : undefined,
24473
+ };
24474
+ }
24475
+ copyForSheetId(sheetId) {
24476
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24477
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24478
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24479
+ return new PyramidChart(definition, sheetId, this.getters);
24480
+ }
24481
+ copyInSheetId(sheetId) {
24482
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24483
+ return new PyramidChart(definition, sheetId, this.getters);
24484
+ }
24485
+ getDefinition() {
24486
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24487
+ }
24488
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24489
+ const ranges = [];
24490
+ for (const [i, dataSet] of dataSets.entries()) {
24491
+ ranges.push({
24492
+ ...this.dataSetDesign?.[i],
24493
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24494
+ });
24495
+ }
24496
+ return {
24497
+ type: "pyramid",
24498
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24499
+ background: this.background,
24500
+ dataSets: ranges,
24501
+ legendPosition: this.legendPosition,
24502
+ labelRange: labelRange
24503
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24504
+ : undefined,
24505
+ title: this.title,
24506
+ aggregated: this.aggregated,
24507
+ axesDesign: this.axesDesign,
24508
+ horizontal: true,
24509
+ stacked: true,
24510
+ };
24511
+ }
24512
+ getDefinitionForExcel() {
24513
+ return undefined;
24514
+ }
24515
+ updateRanges(applyChange) {
24516
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24517
+ if (!isStale) {
24518
+ return this;
24519
+ }
24520
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24521
+ return new PyramidChart(definition, this.sheetId, this.getters);
24522
+ }
24523
+ }
24524
+ function createPyramidChartRuntime(chart, getters) {
24525
+ const barDef = { ...chart.getDefinition(), type: "bar" };
24526
+ const barChart = new BarChart(barDef, chart.sheetId, getters);
24527
+ const barRuntime = createBarChartRuntime(barChart, getters);
24528
+ const config = barRuntime.chartJsConfig;
24529
+ let datasets = config.data?.datasets;
24530
+ if (datasets && datasets[0]) {
24531
+ datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
24532
+ }
24533
+ if (datasets && datasets[1]) {
24534
+ datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
24535
+ }
24536
+ const scales = config.options.scales;
24537
+ const scalesXCallback = scales.x.ticks.callback;
24538
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
24539
+ const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
24540
+ config.options.plugins.tooltip.callbacks.label = (item) => {
24541
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24542
+ return tooltipLabelCallback(tooltipItem);
24543
+ };
24315
24544
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24316
24545
  }
24317
24546
 
@@ -24754,7 +24983,6 @@ chartRegistry.add("bar", {
24754
24983
  validateChartDefinition: BarChart.validateChartDefinition,
24755
24984
  transformDefinition: BarChart.transformDefinition,
24756
24985
  getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
24757
- name: _t("Bar"),
24758
24986
  sequence: 10,
24759
24987
  });
24760
24988
  chartRegistry.add("combo", {
@@ -24764,7 +24992,6 @@ chartRegistry.add("combo", {
24764
24992
  validateChartDefinition: ComboChart.validateChartDefinition,
24765
24993
  transformDefinition: ComboChart.transformDefinition,
24766
24994
  getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
24767
- name: _t("Combo"),
24768
24995
  sequence: 15,
24769
24996
  });
24770
24997
  chartRegistry.add("line", {
@@ -24774,7 +25001,6 @@ chartRegistry.add("line", {
24774
25001
  validateChartDefinition: LineChart.validateChartDefinition,
24775
25002
  transformDefinition: LineChart.transformDefinition,
24776
25003
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
24777
- name: _t("Line"),
24778
25004
  sequence: 20,
24779
25005
  });
24780
25006
  chartRegistry.add("pie", {
@@ -24784,7 +25010,6 @@ chartRegistry.add("pie", {
24784
25010
  validateChartDefinition: PieChart.validateChartDefinition,
24785
25011
  transformDefinition: PieChart.transformDefinition,
24786
25012
  getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
24787
- name: _t("Pie"),
24788
25013
  sequence: 30,
24789
25014
  });
24790
25015
  chartRegistry.add("scorecard", {
@@ -24794,7 +25019,6 @@ chartRegistry.add("scorecard", {
24794
25019
  validateChartDefinition: ScorecardChart$1.validateChartDefinition,
24795
25020
  transformDefinition: ScorecardChart$1.transformDefinition,
24796
25021
  getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
24797
- name: _t("Scorecard"),
24798
25022
  sequence: 40,
24799
25023
  });
24800
25024
  chartRegistry.add("gauge", {
@@ -24804,7 +25028,6 @@ chartRegistry.add("gauge", {
24804
25028
  validateChartDefinition: GaugeChart.validateChartDefinition,
24805
25029
  transformDefinition: GaugeChart.transformDefinition,
24806
25030
  getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
24807
- name: _t("Gauge"),
24808
25031
  sequence: 50,
24809
25032
  });
24810
25033
  chartRegistry.add("scatter", {
@@ -24814,7 +25037,6 @@ chartRegistry.add("scatter", {
24814
25037
  validateChartDefinition: ScatterChart.validateChartDefinition,
24815
25038
  transformDefinition: ScatterChart.transformDefinition,
24816
25039
  getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
24817
- name: _t("Scatter"),
24818
25040
  sequence: 60,
24819
25041
  });
24820
25042
  chartRegistry.add("waterfall", {
@@ -24824,9 +25046,17 @@ chartRegistry.add("waterfall", {
24824
25046
  validateChartDefinition: WaterfallChart.validateChartDefinition,
24825
25047
  transformDefinition: WaterfallChart.transformDefinition,
24826
25048
  getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
24827
- name: _t("Waterfall"),
24828
25049
  sequence: 70,
24829
25050
  });
25051
+ chartRegistry.add("pyramid", {
25052
+ match: (type) => type === "pyramid",
25053
+ createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
25054
+ getChartRuntime: createPyramidChartRuntime,
25055
+ validateChartDefinition: PyramidChart.validateChartDefinition,
25056
+ transformDefinition: PyramidChart.transformDefinition,
25057
+ getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
25058
+ sequence: 80,
25059
+ });
24830
25060
  const chartComponentRegistry = new Registry();
24831
25061
  chartComponentRegistry.add("line", ChartJsComponent);
24832
25062
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -24836,6 +25066,130 @@ chartComponentRegistry.add("gauge", GaugeChartComponent);
24836
25066
  chartComponentRegistry.add("scatter", ChartJsComponent);
24837
25067
  chartComponentRegistry.add("scorecard", ScorecardChart);
24838
25068
  chartComponentRegistry.add("waterfall", ChartJsComponent);
25069
+ chartComponentRegistry.add("pyramid", ChartJsComponent);
25070
+ const chartCategories = {
25071
+ line: _t("Line"),
25072
+ column: _t("Column"),
25073
+ bar: _t("Bar"),
25074
+ pie: _t("Pie"),
25075
+ misc: _t("Miscellaneous"),
25076
+ };
25077
+ const chartSubtypeRegistry = new Registry();
25078
+ chartSubtypeRegistry
25079
+ .add("line", {
25080
+ matcher: (definition) => definition.type === "line" && !definition.stacked,
25081
+ displayName: _t("Line"),
25082
+ chartType: "line",
25083
+ chartSubtype: "line",
25084
+ subtypeDefinition: { stacked: false },
25085
+ category: "line",
25086
+ preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25087
+ })
25088
+ .add("stacked_line", {
25089
+ matcher: (definition) => definition.type === "line" && definition.stacked,
25090
+ displayName: _t("Stacked Line"),
25091
+ chartType: "line",
25092
+ chartSubtype: "stacked_line",
25093
+ subtypeDefinition: { stacked: true },
25094
+ category: "line",
25095
+ preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25096
+ })
25097
+ .add("scatter", {
25098
+ displayName: _t("Scatter"),
25099
+ chartType: "scatter",
25100
+ chartSubtype: "scatter",
25101
+ category: "misc",
25102
+ preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
25103
+ })
25104
+ .add("column", {
25105
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
25106
+ displayName: _t("Column"),
25107
+ chartType: "bar",
25108
+ chartSubtype: "column",
25109
+ subtypeDefinition: { stacked: false, horizontal: false },
25110
+ category: "column",
25111
+ preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
25112
+ })
25113
+ .add("stacked_column", {
25114
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
25115
+ displayName: _t("Stacked Column"),
25116
+ chartType: "bar",
25117
+ chartSubtype: "stacked_column",
25118
+ subtypeDefinition: { stacked: true, horizontal: false },
25119
+ category: "column",
25120
+ preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
25121
+ })
25122
+ .add("bar", {
25123
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
25124
+ displayName: _t("Bar"),
25125
+ chartType: "bar",
25126
+ chartSubtype: "bar",
25127
+ subtypeDefinition: { horizontal: true, stacked: false },
25128
+ category: "bar",
25129
+ preview: "o-spreadsheet-ChartPreview.BAR_CHART",
25130
+ })
25131
+ .add("stacked_bar", {
25132
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
25133
+ displayName: _t("Stacked Bar"),
25134
+ chartType: "bar",
25135
+ chartSubtype: "stacked_bar",
25136
+ subtypeDefinition: { horizontal: true, stacked: true },
25137
+ category: "bar",
25138
+ preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
25139
+ })
25140
+ .add("combo", {
25141
+ displayName: _t("Combo"),
25142
+ chartSubtype: "combo",
25143
+ chartType: "combo",
25144
+ category: "line",
25145
+ preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
25146
+ })
25147
+ .add("pie", {
25148
+ matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
25149
+ displayName: _t("Pie"),
25150
+ chartSubtype: "pie",
25151
+ chartType: "pie",
25152
+ subtypeDefinition: { isDoughnut: false },
25153
+ category: "pie",
25154
+ preview: "o-spreadsheet-ChartPreview.PIE_CHART",
25155
+ })
25156
+ .add("doughnut", {
25157
+ matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
25158
+ displayName: _t("Doughnut"),
25159
+ chartSubtype: "doughnut",
25160
+ chartType: "pie",
25161
+ subtypeDefinition: { isDoughnut: true },
25162
+ category: "pie",
25163
+ preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
25164
+ })
25165
+ .add("gauge", {
25166
+ displayName: _t("Gauge"),
25167
+ chartSubtype: "gauge",
25168
+ chartType: "gauge",
25169
+ category: "misc",
25170
+ preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
25171
+ })
25172
+ .add("scorecard", {
25173
+ displayName: _t("Scorecard"),
25174
+ chartSubtype: "scorecard",
25175
+ chartType: "scorecard",
25176
+ category: "misc",
25177
+ preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
25178
+ })
25179
+ .add("waterfall", {
25180
+ displayName: _t("Waterfall"),
25181
+ chartSubtype: "waterfall",
25182
+ chartType: "waterfall",
25183
+ category: "misc",
25184
+ preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
25185
+ })
25186
+ .add("pyramid", {
25187
+ displayName: _t("Population Pyramid"),
25188
+ chartSubtype: "pyramid",
25189
+ chartType: "pyramid",
25190
+ category: "misc",
25191
+ preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
25192
+ });
24839
25193
 
24840
25194
  /**
24841
25195
  * Registry intended to support usual currencies. It is mainly used to create
@@ -26692,20 +27046,6 @@ function transformDefinition(definition, executed) {
26692
27046
  }
26693
27047
  return transformation.transformDefinition(definition, executed);
26694
27048
  }
26695
- /**
26696
- * Get an empty definition based on the given context and the given type
26697
- */
26698
- function getChartDefinitionFromContextCreation(context, type) {
26699
- const chartClass = chartRegistry.get(type);
26700
- return chartClass.getChartDefinitionFromContextCreation(context);
26701
- }
26702
- function getChartTypes() {
26703
- const result = {};
26704
- for (const key of chartRegistry.getKeys()) {
26705
- result[key] = chartRegistry.get(key).name;
26706
- }
26707
- return result;
26708
- }
26709
27049
  /**
26710
27050
  * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
26711
27051
  * use the best type of chart to display the data of the zone.
@@ -28165,6 +28505,40 @@ const pivotProperties = {
28165
28505
  },
28166
28506
  icon: "o-spreadsheet-Icon.PIVOT",
28167
28507
  };
28508
+ const FIX_FORMULAS = {
28509
+ name: _t("Convert to individual formulas"),
28510
+ execute(env) {
28511
+ const position = env.model.getters.getActivePosition();
28512
+ const cell = env.model.getters.getCorrespondingFormulaCell(position);
28513
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28514
+ if (!cell || !pivotId) {
28515
+ return;
28516
+ }
28517
+ const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
28518
+ const pivot = env.model.getters.getPivot(pivotId);
28519
+ pivot.init();
28520
+ if (!pivot.isValid()) {
28521
+ return;
28522
+ }
28523
+ env.model.dispatch("INSERT_PIVOT", {
28524
+ sheetId,
28525
+ col,
28526
+ row,
28527
+ pivotId,
28528
+ table: pivot.getTableStructure().export(),
28529
+ });
28530
+ },
28531
+ isVisible: (env) => {
28532
+ const position = env.model.getters.getActivePosition();
28533
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28534
+ if (!pivotId) {
28535
+ return false;
28536
+ }
28537
+ const pivot = env.model.getters.getPivot(pivotId);
28538
+ return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
28539
+ },
28540
+ icon: "o-spreadsheet-Icon.PIVOT",
28541
+ };
28168
28542
 
28169
28543
  //------------------------------------------------------------------------------
28170
28544
  // Context Menu Registry
@@ -28263,6 +28637,10 @@ cellMenuRegistry
28263
28637
  name: INSERT_LINK_NAME,
28264
28638
  sequence: 150,
28265
28639
  separator: true,
28640
+ })
28641
+ .add("pivot_fix_formulas", {
28642
+ ...FIX_FORMULAS,
28643
+ sequence: 155,
28266
28644
  })
28267
28645
  .add("pivot_properties", {
28268
28646
  ...pivotProperties,
@@ -30485,6 +30863,7 @@ class GenericChartConfigPanel extends owl.Component {
30485
30863
  });
30486
30864
  dataSeriesRanges = [];
30487
30865
  labelRange;
30866
+ chartTerms = ChartTerms;
30488
30867
  setup() {
30489
30868
  this.dataSeriesRanges = this.props.definition.dataSets;
30490
30869
  this.labelRange = this.props.definition.labelRange;
@@ -30509,7 +30888,7 @@ class GenericChartConfigPanel extends owl.Component {
30509
30888
  return [
30510
30889
  {
30511
30890
  name: "aggregated",
30512
- label: _t("Aggregate"),
30891
+ label: this.chartTerms.AggregatedChart,
30513
30892
  value: this.props.definition.aggregated ?? false,
30514
30893
  onChange: this.onUpdateAggregated.bind(this),
30515
30894
  },
@@ -30585,9 +30964,6 @@ class GenericChartConfigPanel extends owl.Component {
30585
30964
 
30586
30965
  class BarConfigPanel extends GenericChartConfigPanel {
30587
30966
  static template = "o-spreadsheet-BarConfigPanel";
30588
- get stackedLabel() {
30589
- return _t("Stacked barchart");
30590
- }
30591
30967
  onUpdateStacked(stacked) {
30592
30968
  this.props.updateChart(this.props.figureId, {
30593
30969
  stacked,
@@ -31577,6 +31953,9 @@ class ChartWithAxisDesignPanel extends owl.Component {
31577
31953
  return "left";
31578
31954
  return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31579
31955
  }
31956
+ get canHaveTwoVerticalAxis() {
31957
+ return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
31958
+ }
31580
31959
  updateDataSeriesLabel(ev) {
31581
31960
  const label = ev.target.value;
31582
31961
  const dataSets = this.props.definition.dataSets;
@@ -31741,19 +32120,13 @@ class LineConfigPanel extends GenericChartConfigPanel {
31741
32120
  }
31742
32121
  return false;
31743
32122
  }
31744
- get stackedLabel() {
31745
- return _t("Stacked linechart");
31746
- }
31747
- get cumulativeLabel() {
31748
- return _t("Cumulative data");
31749
- }
31750
32123
  getLabelRangeOptions() {
31751
32124
  const options = super.getLabelRangeOptions();
31752
32125
  if (this.canTreatLabelsAsText) {
31753
32126
  options.push({
31754
32127
  name: "labelsAsText",
31755
32128
  value: this.props.definition.labelsAsText,
31756
- label: _t("Treat labels as text"),
32129
+ label: this.chartTerms.TreatLabelsAsText,
31757
32130
  onChange: this.onUpdateLabelsAsText.bind(this),
31758
32131
  });
31759
32132
  }
@@ -31820,7 +32193,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
31820
32193
  options.push({
31821
32194
  name: "labelsAsText",
31822
32195
  value: this.props.definition.labelsAsText,
31823
- label: _t("Treat labels as text"),
32196
+ label: this.chartTerms.TreatLabelsAsText,
31824
32197
  onChange: this.onUpdateLabelsAsText.bind(this),
31825
32198
  });
31826
32199
  }
@@ -32027,8 +32400,106 @@ chartSidePanelComponentRegistry
32027
32400
  .add("waterfall", {
32028
32401
  configuration: GenericChartConfigPanel,
32029
32402
  design: WaterfallChartDesignPanel,
32403
+ })
32404
+ .add("pyramid", {
32405
+ configuration: GenericChartConfigPanel,
32406
+ design: ChartWithAxisDesignPanel,
32030
32407
  });
32031
32408
 
32409
+ css /* scss */ `
32410
+ .o-section .o-type-selector {
32411
+ height: 30px;
32412
+ padding-left: 30px;
32413
+ }
32414
+ .o-type-selector-preview {
32415
+ left: 5px;
32416
+ top: 3px;
32417
+ .o-chart-preview {
32418
+ width: 24px;
32419
+ height: 24px;
32420
+ }
32421
+ }
32422
+
32423
+ .o-popover .o-chart-select-popover {
32424
+ box-sizing: border-box;
32425
+ background: #fff;
32426
+ .o-chart-type-item {
32427
+ cursor: pointer;
32428
+ padding: 3px 6px;
32429
+ margin: 1px 2px;
32430
+ &.selected,
32431
+ &:hover {
32432
+ background: #f5f5f5;
32433
+ border: 1px solid #ccc;
32434
+ padding: 2px 5px;
32435
+ }
32436
+ .o-chart-preview {
32437
+ width: 48px;
32438
+ height: 48px;
32439
+ }
32440
+ }
32441
+ }
32442
+ `;
32443
+ class ChartTypePicker extends owl.Component {
32444
+ static template = "o-spreadsheet-ChartTypePicker";
32445
+ static components = { Section, Popover };
32446
+ static props = { figureId: String, chartPanelStore: Object };
32447
+ categories = chartCategories;
32448
+ chartTypeByCategories = {};
32449
+ popoverRef = owl.useRef("popoverRef");
32450
+ selectRef = owl.useRef("selectRef");
32451
+ state = owl.useState({ popoverProps: undefined, popoverStyle: "" });
32452
+ setup() {
32453
+ owl.useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
32454
+ for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
32455
+ if (this.chartTypeByCategories[subtypeProperties.category]) {
32456
+ this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
32457
+ }
32458
+ else {
32459
+ this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
32460
+ }
32461
+ }
32462
+ }
32463
+ onExternalClick(ev) {
32464
+ if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
32465
+ isChildEvent(this.selectRef.el, ev)) {
32466
+ return;
32467
+ }
32468
+ this.closePopover();
32469
+ }
32470
+ onTypeChange(type) {
32471
+ this.props.chartPanelStore.changeChartType(this.props.figureId, type);
32472
+ this.closePopover();
32473
+ }
32474
+ getChartDefinition(figureId) {
32475
+ return this.env.model.getters.getChartDefinition(figureId);
32476
+ }
32477
+ getSelectedChartSubtypeProperties() {
32478
+ const definition = this.getChartDefinition(this.props.figureId);
32479
+ const matchedChart = chartSubtypeRegistry
32480
+ .getAll()
32481
+ .find((c) => c.matcher?.(definition) || false);
32482
+ return matchedChart || chartSubtypeRegistry.get(definition.type);
32483
+ }
32484
+ onPointerDown(ev) {
32485
+ if (this.state.popoverProps) {
32486
+ this.closePopover();
32487
+ return;
32488
+ }
32489
+ const target = ev.currentTarget;
32490
+ const { bottom, right, width } = target.getBoundingClientRect();
32491
+ this.state.popoverProps = {
32492
+ anchorRect: { x: right, y: bottom, width: 0, height: 0 },
32493
+ positioning: "TopRight",
32494
+ verticalOffset: 0,
32495
+ };
32496
+ this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
32497
+ }
32498
+ closePopover() {
32499
+ this.state.popoverProps = undefined;
32500
+ }
32501
+ }
32502
+
32032
32503
  class MainChartPanelStore extends SpreadsheetStore {
32033
32504
  mutators = ["activatePanel", "changeChartType"];
32034
32505
  panel = "configuration";
@@ -32036,7 +32507,7 @@ class MainChartPanelStore extends SpreadsheetStore {
32036
32507
  activatePanel(panel) {
32037
32508
  this.panel = panel;
32038
32509
  }
32039
- changeChartType(figureId, type) {
32510
+ changeChartType(figureId, newDisplayType) {
32040
32511
  this.creationContext = {
32041
32512
  ...this.creationContext,
32042
32513
  ...this.getters.getContextCreationChart(figureId),
@@ -32045,13 +32516,25 @@ class MainChartPanelStore extends SpreadsheetStore {
32045
32516
  if (!sheetId) {
32046
32517
  return;
32047
32518
  }
32048
- const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
32519
+ const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
32049
32520
  this.model.dispatch("UPDATE_CHART", {
32050
32521
  definition,
32051
32522
  id: figureId,
32052
32523
  sheetId,
32053
32524
  });
32054
32525
  }
32526
+ getChartDefinitionFromContextCreation(figureId, newDisplayType) {
32527
+ const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
32528
+ const ChartClass = chartRegistry.get(newChartInfo.chartType);
32529
+ const contextCreation = {
32530
+ ...this.creationContext,
32531
+ ...this.getters.getContextCreationChart(figureId),
32532
+ };
32533
+ return {
32534
+ ...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
32535
+ ...newChartInfo.subtypeDefinition,
32536
+ };
32537
+ }
32055
32538
  }
32056
32539
 
32057
32540
  css /* scss */ `
@@ -32080,7 +32563,7 @@ css /* scss */ `
32080
32563
  `;
32081
32564
  class ChartPanel extends owl.Component {
32082
32565
  static template = "o-spreadsheet-ChartPanel";
32083
- static components = { Section };
32566
+ static components = { Section, ChartTypePicker };
32084
32567
  static props = { onCloseSidePanel: Function, figureId: String };
32085
32568
  store;
32086
32569
  get figureId() {
@@ -32140,9 +32623,6 @@ class ChartPanel extends owl.Component {
32140
32623
  getChartDefinition(figureId) {
32141
32624
  return this.env.model.getters.getChartDefinition(figureId);
32142
32625
  }
32143
- get chartTypes() {
32144
- return getChartTypes();
32145
- }
32146
32626
  }
32147
32627
 
32148
32628
  css /* scss */ `
@@ -34147,6 +34627,10 @@ class FindAndReplaceStore extends SpreadsheetStore {
34147
34627
  case "ACTIVATE_SHEET":
34148
34628
  this.isSearchDirty = true;
34149
34629
  break;
34630
+ case "REPLACE_SEARCH":
34631
+ for (const match of cmd.matches) {
34632
+ this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
34633
+ }
34150
34634
  }
34151
34635
  }
34152
34636
  finalize() {
@@ -34320,6 +34804,21 @@ class FindAndReplaceStore extends SpreadsheetStore {
34320
34804
  searchOptions: this.searchOptions,
34321
34805
  });
34322
34806
  }
34807
+ replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
34808
+ const cell = this.getters.getCell(selectedMatch);
34809
+ if (!cell?.content) {
34810
+ return;
34811
+ }
34812
+ if (cell?.isFormula && !searchOptions.searchFormulas) {
34813
+ return;
34814
+ }
34815
+ const searchRegex = getSearchRegex(searchString, searchOptions);
34816
+ const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
34817
+ const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
34818
+ const content = toReplace.replace(replaceRegex, replaceWith);
34819
+ const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
34820
+ this.model.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
34821
+ }
34323
34822
  getSearchableString(position) {
34324
34823
  return this.getters.getCellText(position, this.searchOptions.searchFormulas);
34325
34824
  }
@@ -35071,7 +35570,7 @@ function createMeasure(fields, measure) {
35071
35570
  function createPivotDimension(fields, dimension) {
35072
35571
  const field = fields[dimension.name];
35073
35572
  const type = field?.type ?? "integer";
35074
- const granularity = field && isDateField(field) ? dimension.granularity ?? "month_number" : undefined;
35573
+ const granularity = field && isDateField(field) ? dimension.granularity : undefined;
35075
35574
  return {
35076
35575
  /**
35077
35576
  * Get the display name of the dimension
@@ -35216,9 +35715,17 @@ class SpreadsheetPivotTable {
35216
35715
  }
35217
35716
  getPivotCell(col, row, includeTotal = true) {
35218
35717
  const colHeadersHeight = this.columns.length;
35219
- if (row <= colHeadersHeight - 1) {
35718
+ if (col > 0 && row === colHeadersHeight - 1) {
35719
+ const domain = this.getColHeaderDomain(col, row);
35720
+ if (!domain) {
35721
+ return EMPTY_PIVOT_CELL;
35722
+ }
35723
+ const measure = domain.at(-1)?.value.toString() || "";
35724
+ return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35725
+ }
35726
+ else if (row <= colHeadersHeight - 1) {
35220
35727
  const domain = this.getColHeaderDomain(col, row);
35221
- return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35728
+ return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
35222
35729
  }
35223
35730
  else if (col === 0) {
35224
35731
  const rowIndex = row - colHeadersHeight;
@@ -35228,7 +35735,7 @@ class SpreadsheetPivotTable {
35228
35735
  else {
35229
35736
  const rowIndex = row - colHeadersHeight;
35230
35737
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35231
- return { type: "EMPTY" };
35738
+ return EMPTY_PIVOT_CELL;
35232
35739
  }
35233
35740
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35234
35741
  const measure = this.getColMeasure(col);
@@ -35282,6 +35789,7 @@ class SpreadsheetPivotTable {
35282
35789
  };
35283
35790
  }
35284
35791
  }
35792
+ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
35285
35793
 
35286
35794
  /**
35287
35795
  * This function converts a list of data entry into a spreadsheet pivot table.
@@ -35475,10 +35983,13 @@ function compareDimensionValues(dimension, a, b) {
35475
35983
  }
35476
35984
 
35477
35985
  function createDate(dimension, value, locale) {
35478
- const granularity = dimension.granularity || "month_number";
35479
- if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
35986
+ const granularity = dimension.granularity;
35987
+ if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
35480
35988
  throw new Error(`Unknown date granularity: ${granularity}`);
35481
35989
  }
35990
+ if (value === null) {
35991
+ return null;
35992
+ }
35482
35993
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35483
35994
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35484
35995
  const date = toJsDate(value, locale);
@@ -35709,14 +36220,17 @@ class SpreadsheetPivot {
35709
36220
  if (dimension.type === "date") {
35710
36221
  const adapter = pivotTimeAdapter(dimension.granularity);
35711
36222
  return {
35712
- value: adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value)),
36223
+ value: lastNode.value !== "null"
36224
+ ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36225
+ : _t("(Undefined)"),
35713
36226
  format: adapter.getFormat(this.getters.getLocale()),
35714
36227
  };
35715
36228
  }
35716
36229
  if (!finalCell) {
35717
36230
  return { value: "" };
35718
36231
  }
35719
- if (finalCell.value === null) {
36232
+ // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36233
+ if (finalCell.value === null || finalCell.value === `${null}`) {
35720
36234
  return { value: _t("(Undefined)") };
35721
36235
  }
35722
36236
  return {
@@ -35738,10 +36252,15 @@ class SpreadsheetPivot {
35738
36252
  if (!operator) {
35739
36253
  throw new Error(`Aggregator ${aggregator} does not exist`);
35740
36254
  }
35741
- return {
35742
- value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
35743
- format: operator.format(values[0]),
35744
- };
36255
+ try {
36256
+ return {
36257
+ value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
36258
+ format: operator.format(values[0]),
36259
+ };
36260
+ }
36261
+ catch (e) {
36262
+ return handleError(e, aggregator.toUpperCase());
36263
+ }
35745
36264
  }
35746
36265
  getPossibleFieldValues(dimension) {
35747
36266
  const values = [];
@@ -51108,12 +51627,12 @@ class PivotCorePlugin extends CorePlugin {
51108
51627
  for (let col = 0; col < pivotCells.length; col++) {
51109
51628
  for (let row = 0; row < pivotCells[col].length; row++) {
51110
51629
  const pivotCell = pivotCells[col][row];
51111
- const cellPosition = {
51630
+ this.dispatch("UPDATE_CELL", {
51112
51631
  sheetId: position.sheetId,
51113
51632
  col: position.col + col,
51114
51633
  row: position.row + row,
51115
- };
51116
- this.addPivotFormula(cellPosition, formulaId, pivotCell);
51634
+ content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51635
+ });
51117
51636
  }
51118
51637
  }
51119
51638
  }
@@ -51143,21 +51662,6 @@ class PivotCorePlugin extends CorePlugin {
51143
51662
  });
51144
51663
  }
51145
51664
  }
51146
- addPivotFormula(position, formulaId, pivotCell) {
51147
- let content = undefined;
51148
- switch (pivotCell.type) {
51149
- case "HEADER":
51150
- content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51151
- break;
51152
- case "VALUE":
51153
- content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51154
- break;
51155
- }
51156
- this.dispatch("UPDATE_CELL", {
51157
- ...position,
51158
- content,
51159
- });
51160
- }
51161
51665
  getPivotCore(pivotId) {
51162
51666
  const pivot = this.pivots[pivotId];
51163
51667
  if (!pivot) {
@@ -52822,7 +53326,6 @@ class Evaluator {
52822
53326
  if (!this.blockedArrayFormulas.has(position)) {
52823
53327
  this.invalidateSpreading(position);
52824
53328
  }
52825
- this.spreadingRelations.removeNode(position);
52826
53329
  const cell = this.getters.getCell(position);
52827
53330
  if (cell === undefined) {
52828
53331
  return EMPTY_CELL;
@@ -52864,6 +53367,7 @@ class Evaluator {
52864
53367
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
52865
53368
  const nbColumns = formulaReturn.length;
52866
53369
  const nbRows = formulaReturn[0].length;
53370
+ this.spreadingRelations.removeNode(formulaPosition);
52867
53371
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52868
53372
  this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52869
53373
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
@@ -54391,9 +54895,10 @@ class PivotUIPlugin extends UIPlugin {
54391
54895
  "getPivot",
54392
54896
  "getFirstPivotFunction",
54393
54897
  "getPivotIdFromPosition",
54394
- "getPivotDomainArgsFromPosition",
54898
+ "getPivotCellFromPosition",
54395
54899
  "isPivotUnused",
54396
54900
  "areDomainArgsFieldsValid",
54901
+ "isSpillPivotFormula",
54397
54902
  ];
54398
54903
  pivots = {};
54399
54904
  unusedPivots;
@@ -54472,6 +54977,14 @@ class PivotUIPlugin extends UIPlugin {
54472
54977
  }
54473
54978
  return undefined;
54474
54979
  }
54980
+ isSpillPivotFormula(position) {
54981
+ const cell = this.getters.getCorrespondingFormulaCell(position);
54982
+ if (cell && cell.isFormula) {
54983
+ const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
54984
+ return pivotFunction?.functionName === "PIVOT";
54985
+ }
54986
+ return false;
54987
+ }
54475
54988
  getFirstPivotFunction(tokens) {
54476
54989
  const pivotFunction = getFirstPivotFunction(tokens);
54477
54990
  if (!pivotFunction) {
@@ -54505,29 +55018,29 @@ class PivotUIPlugin extends UIPlugin {
54505
55018
  * If the cell is the result of PIVOT, the result is the domain of the cell
54506
55019
  * as if it was the individual pivot formula
54507
55020
  */
54508
- getPivotDomainArgsFromPosition(position) {
55021
+ getPivotCellFromPosition(position) {
54509
55022
  const cell = this.getters.getCorrespondingFormulaCell(position);
54510
55023
  if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
54511
- return undefined;
55024
+ return EMPTY_PIVOT_CELL;
54512
55025
  }
54513
55026
  const mainPosition = this.getters.getCellPosition(cell.id);
54514
55027
  const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
54515
55028
  if (!result) {
54516
- return undefined;
55029
+ return EMPTY_PIVOT_CELL;
54517
55030
  }
54518
55031
  const { functionName, args } = result;
54519
55032
  if (functionName === "PIVOT") {
54520
55033
  const formulaId = args[0];
54521
55034
  if (!formulaId) {
54522
- return undefined;
55035
+ return EMPTY_PIVOT_CELL;
54523
55036
  }
54524
55037
  const pivotId = this.getters.getPivotId(formulaId.toString());
54525
55038
  if (!pivotId) {
54526
- return undefined;
55039
+ return EMPTY_PIVOT_CELL;
54527
55040
  }
54528
55041
  const pivot = this.getPivot(pivotId);
54529
55042
  if (!pivot.isValid()) {
54530
- return undefined;
55043
+ return EMPTY_PIVOT_CELL;
54531
55044
  }
54532
55045
  const includeTotal = args[2] === false ? false : undefined;
54533
55046
  const includeColumnHeaders = args[3] === false ? false : undefined;
@@ -54536,22 +55049,28 @@ class PivotUIPlugin extends UIPlugin {
54536
55049
  .getPivotCells(includeTotal, includeColumnHeaders);
54537
55050
  const pivotCol = position.col - mainPosition.col;
54538
55051
  const pivotRow = position.row - mainPosition.row;
54539
- const pivotCell = pivotCells[pivotCol][pivotRow];
54540
- if (pivotCell.type === "EMPTY") {
54541
- return undefined;
54542
- }
54543
- let domain = pivotCell.domain;
54544
- if (domain.at(-1)?.field === "measure") {
54545
- domain = domain.slice(0, -1);
54546
- }
54547
- return { domainArgs: domain, isHeader: pivotCell.type === "HEADER" };
55052
+ return pivotCells[pivotCol][pivotRow];
55053
+ }
55054
+ if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55055
+ const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55056
+ return {
55057
+ type: "MEASURE_HEADER",
55058
+ domain,
55059
+ measure: args.at(-1)?.toString() || "",
55060
+ };
54548
55061
  }
54549
- let domain = toPivotDomain(args.slice(functionName === "PIVOT.VALUE" ? 2 : 1).map((x) => `${x}`));
54550
- if (domain.at(-1)?.field === "measure") {
54551
- domain = domain.slice(0, -1);
55062
+ else if (functionName === "PIVOT.HEADER") {
55063
+ return {
55064
+ type: "HEADER",
55065
+ domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55066
+ };
54552
55067
  }
54553
- const isHeader = functionName === "PIVOT.HEADER";
54554
- return { domainArgs: domain, isHeader };
55068
+ const [measure, ...domainArgs] = args.slice(1);
55069
+ return {
55070
+ type: "VALUE",
55071
+ domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55072
+ measure: measure?.toString() || "",
55073
+ };
54555
55074
  }
54556
55075
  getPivot(pivotId) {
54557
55076
  return this.pivots[pivotId];
@@ -56422,43 +56941,6 @@ class DataCleanupPlugin extends UIPlugin {
56422
56941
  }
56423
56942
  }
56424
56943
 
56425
- /**
56426
- * Find and Replace Plugin
56427
- *
56428
- * This plugin is used in combination with the find_and_replace sidePanel
56429
- * It is used to 'highlight' cells that match an input string according to
56430
- * the given searchOptions. The second part of this plugin makes it possible
56431
- * (again with the find_and_replace sidePanel), to replace the values that match
56432
- * the search with a new value.
56433
- */
56434
- class FindAndReplacePlugin extends UIPlugin {
56435
- static getters = [];
56436
- handle(cmd) {
56437
- switch (cmd.type) {
56438
- case "REPLACE_SEARCH":
56439
- for (const match of cmd.matches) {
56440
- this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
56441
- }
56442
- break;
56443
- }
56444
- }
56445
- replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
56446
- const cell = this.getters.getCell(selectedMatch);
56447
- if (!cell?.content) {
56448
- return;
56449
- }
56450
- if (cell?.isFormula && !searchOptions.searchFormulas) {
56451
- return;
56452
- }
56453
- const searchRegex = getSearchRegex(searchString, searchOptions);
56454
- const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
56455
- const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
56456
- const content = toReplace.replace(replaceRegex, replaceWith);
56457
- const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
56458
- this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
56459
- }
56460
- }
56461
-
56462
56944
  class FormatPlugin extends UIPlugin {
56463
56945
  // ---------------------------------------------------------------------------
56464
56946
  // Command Handling
@@ -60273,7 +60755,6 @@ const featurePluginRegistry = new Registry()
60273
60755
  .add("ui_sheet", SheetUIPlugin)
60274
60756
  .add("ui_options", UIOptionsPlugin)
60275
60757
  .add("autofill", AutofillPlugin)
60276
- .add("find_and_replace", FindAndReplacePlugin)
60277
60758
  .add("sort", SortPlugin)
60278
60759
  .add("automatic_sum", AutomaticSumPlugin)
60279
60760
  .add("format", FormatPlugin)
@@ -63834,6 +64315,9 @@ class SelectionStreamProcessorImpl {
63834
64315
  getBackToDefault() {
63835
64316
  this.stream.getBackToDefault();
63836
64317
  }
64318
+ getAnchor() {
64319
+ return this.anchor;
64320
+ }
63837
64321
  modifyAnchor(anchor, mode, options) {
63838
64322
  const sheetId = this.getters.getActiveSheetId();
63839
64323
  anchor = {
@@ -66797,6 +67281,7 @@ const registries = {
66797
67281
  chartSidePanelComponentRegistry,
66798
67282
  chartComponentRegistry,
66799
67283
  chartRegistry,
67284
+ chartSubtypeRegistry,
66800
67285
  topbarMenuRegistry,
66801
67286
  topbarComponentRegistry,
66802
67287
  clickableCellRegistry,
@@ -66905,6 +67390,7 @@ const components = {
66905
67390
  GaugeChartDesignPanel,
66906
67391
  ScorecardChartConfigPanel,
66907
67392
  ScorecardChartDesignPanel,
67393
+ ChartTypePicker,
66908
67394
  FigureComponent,
66909
67395
  Menu,
66910
67396
  Popover,
@@ -66954,6 +67440,7 @@ const constants = {
66954
67440
  DEFAULT_LOCALE,
66955
67441
  HIGHLIGHT_COLOR,
66956
67442
  PIVOT_TABLE_CONFIG,
67443
+ ChartTerms,
66957
67444
  };
66958
67445
 
66959
67446
  exports.AbstractCellClipboardHandler = AbstractCellClipboardHandler;
@@ -67002,6 +67489,6 @@ exports.tokenColors = tokenColors;
67002
67489
  exports.tokenize = tokenize;
67003
67490
 
67004
67491
 
67005
- __info__.version = "17.4.0-alpha.5";
67006
- __info__.date = "2024-06-14T10:01:40.605Z";
67007
- __info__.hash = "9ceed96";
67492
+ __info__.version = "17.4.0-alpha.7";
67493
+ __info__.date = "2024-06-21T08:42:22.370Z";
67494
+ __info__.hash = "a99db9a";