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

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @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
6
+ * @version 17.4.0-alpha.6
7
+ * @date 2024-06-19T13:46:27.157Z
8
+ * @hash a4f22e4
9
9
  */
10
10
 
11
11
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -6200,7 +6200,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6200
6200
  };
6201
6201
  }
6202
6202
  isPasteAllowed(sheetId, target, content, clipboardOptions) {
6203
- if (!("cells" in content)) {
6203
+ if (!content.cells) {
6204
6204
  return "Success" /* CommandResult.Success */;
6205
6205
  }
6206
6206
  if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
@@ -6214,6 +6214,17 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6214
6214
  return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
6215
6215
  }
6216
6216
  }
6217
+ const clipboardHeight = content.cells.length;
6218
+ const clipboardWidth = content.cells[0].length;
6219
+ for (const zone of getPasteZones(target, content.cells)) {
6220
+ if (this.getters.doesIntersectMerge(sheetId, zone)) {
6221
+ if (target.length > 1 ||
6222
+ !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6223
+ clipboardHeight * clipboardWidth !== 1) {
6224
+ return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6225
+ }
6226
+ }
6227
+ }
6217
6228
  return "Success" /* CommandResult.Success */;
6218
6229
  }
6219
6230
  /**
@@ -6453,13 +6464,22 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6453
6464
  }
6454
6465
  const { rowsIndexes, columnsIndexes } = data;
6455
6466
  const sheetId = data.sheetId;
6456
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6457
- return {
6458
- cellPositions,
6459
- };
6467
+ const cfRules = [];
6468
+ for (const row of rowsIndexes) {
6469
+ const cfRuleInRow = [];
6470
+ for (const col of columnsIndexes) {
6471
+ const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
6472
+ cfRuleInRow.push({
6473
+ position: { col, row, sheetId },
6474
+ rules: cfRules,
6475
+ });
6476
+ }
6477
+ cfRules.push(cfRuleInRow);
6478
+ }
6479
+ return { cfRules };
6460
6480
  }
6461
6481
  paste(target, clippedContent, options) {
6462
- if (!clippedContent?.cellPositions ||
6482
+ if (!clippedContent?.cfRules ||
6463
6483
  options?.pasteOption === "asValue" ||
6464
6484
  !("zones" in target) ||
6465
6485
  !target.zones.length) {
@@ -6468,7 +6488,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6468
6488
  const zones = target.zones;
6469
6489
  const sheetId = target.sheetId;
6470
6490
  if (!options?.isCutOperation) {
6471
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6491
+ this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6472
6492
  }
6473
6493
  else {
6474
6494
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6476,12 +6496,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6476
6496
  }
6477
6497
  pasteFromCut(sheetId, target, content) {
6478
6498
  const selection = target[0];
6479
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6499
+ this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
6480
6500
  isCutOperation: true,
6481
6501
  });
6482
6502
  }
6483
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6484
- for (const [r, rowCells] of positions.entries()) {
6503
+ pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
6504
+ for (const [r, rowCells] of cfRules.entries()) {
6485
6505
  for (const [c, origin] of rowCells.entries()) {
6486
6506
  const position = { col: col + c, row: row + r, sheetId };
6487
6507
  this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
@@ -6489,23 +6509,21 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6489
6509
  }
6490
6510
  }
6491
6511
  pasteCf(origin, target, isCutOperation) {
6492
- const zone = positionToZone(target);
6493
- for (const rule of this.getters.getConditionalFormats(origin.sheetId)) {
6494
- for (const range of rule.ranges) {
6495
- if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
6496
- const toRemoveZones = [];
6497
- if (isCutOperation) {
6498
- //remove from current rule
6499
- toRemoveZones.push(positionToZone(origin));
6500
- }
6501
- if (origin.sheetId === target.sheetId) {
6502
- this.adaptCFRules(origin.sheetId, rule, [zone], toRemoveZones);
6503
- }
6504
- else {
6505
- this.adaptCFRules(origin.sheetId, rule, [], toRemoveZones);
6506
- const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6507
- this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6508
- }
6512
+ if (origin?.rules && origin.rules.length > 0) {
6513
+ const zone = positionToZone(target);
6514
+ for (const rule of origin.rules) {
6515
+ const toRemoveZones = [];
6516
+ if (isCutOperation) {
6517
+ //remove from current rule
6518
+ toRemoveZones.push(positionToZone(origin.position));
6519
+ }
6520
+ if (origin.position.sheetId === target.sheetId) {
6521
+ this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
6522
+ }
6523
+ else {
6524
+ this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
6525
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6526
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6509
6527
  }
6510
6528
  }
6511
6529
  }
@@ -6548,13 +6566,20 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6548
6566
  }
6549
6567
  const { rowsIndexes, columnsIndexes } = data;
6550
6568
  const sheetId = data.sheetId;
6551
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6552
- return {
6553
- cellPositions,
6554
- };
6569
+ const dvRules = [];
6570
+ for (const row of rowsIndexes) {
6571
+ const dvRuleInRow = [];
6572
+ for (const col of columnsIndexes) {
6573
+ const position = { sheetId, col, row };
6574
+ const rule = this.getters.getValidationRuleForCell(position);
6575
+ dvRuleInRow.push({ position, rule });
6576
+ }
6577
+ dvRules.push(dvRuleInRow);
6578
+ }
6579
+ return { dvRules };
6555
6580
  }
6556
6581
  paste(target, clippedContent, options) {
6557
- if (!clippedContent?.cellPositions) {
6582
+ if (!clippedContent?.dvRules) {
6558
6583
  return;
6559
6584
  }
6560
6585
  if (options?.pasteOption) {
@@ -6566,7 +6591,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6566
6591
  const zones = target.zones;
6567
6592
  const sheetId = target.sheetId;
6568
6593
  if (!options?.isCutOperation) {
6569
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6594
+ this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6570
6595
  }
6571
6596
  else {
6572
6597
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6574,12 +6599,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6574
6599
  }
6575
6600
  pasteFromCut(sheetId, target, content) {
6576
6601
  const selection = target[0];
6577
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6602
+ this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
6578
6603
  isCutOperation: true,
6579
6604
  });
6580
6605
  }
6581
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6582
- for (const [r, rowCells] of positions.entries()) {
6606
+ pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
6607
+ for (const [r, rowCells] of dvRules.entries()) {
6583
6608
  for (const [c, origin] of rowCells.entries()) {
6584
6609
  const position = { col: col + c, row: row + r, sheetId };
6585
6610
  this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
@@ -6587,41 +6612,43 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6587
6612
  }
6588
6613
  }
6589
6614
  pasteDataValidation(origin, target, isCutOperation) {
6590
- const rule = this.getters.getValidationRuleForCell(origin);
6591
- if (!rule) {
6592
- const targetRule = this.getters.getValidationRuleForCell(target);
6593
- if (targetRule) {
6594
- // Remove the data validation rule on the target cell
6595
- this.adaptDataValidationRule(target.sheetId, targetRule, [], [positionToZone(target)]);
6596
- }
6597
- return;
6598
- }
6599
- const zone = positionToZone(target);
6600
- for (const range of rule.ranges) {
6601
- if (isInside(origin.col, origin.row, range.zone)) {
6602
- const toRemoveZone = [];
6603
- if (isCutOperation) {
6604
- toRemoveZone.push(positionToZone(origin));
6605
- }
6606
- if (origin.sheetId === target.sheetId) {
6607
- this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
6615
+ if (origin) {
6616
+ const zone = positionToZone(target);
6617
+ const rule = origin.rule;
6618
+ if (!rule) {
6619
+ const targetRule = this.getters.getValidationRuleForCell(target);
6620
+ if (targetRule) {
6621
+ // Remove the data validation rule on the target cell
6622
+ this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
6608
6623
  }
6609
- else {
6610
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveZone);
6611
- const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6612
- this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6624
+ return;
6625
+ }
6626
+ const toRemoveZone = [];
6627
+ if (isCutOperation) {
6628
+ toRemoveZone.push(positionToZone(origin.position));
6629
+ }
6630
+ if (origin.position.sheetId === target.sheetId) {
6631
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
6632
+ this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
6633
+ }
6634
+ else {
6635
+ const originRule = this.getters.getValidationRuleForCell(origin.position);
6636
+ if (originRule) {
6637
+ this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
6613
6638
  }
6639
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6640
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6614
6641
  }
6615
6642
  }
6616
6643
  }
6617
- getDataValidationRuleToCopyTo(targetSheetId, originRule) {
6644
+ getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
6618
6645
  const ruleInTargetSheet = this.getters
6619
6646
  .getDataValidationRules(targetSheetId)
6620
6647
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
6621
6648
  originRule.isBlocking === rule.isBlocking);
6622
6649
  return ruleInTargetSheet
6623
6650
  ? ruleInTargetSheet
6624
- : { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
6651
+ : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
6625
6652
  }
6626
6653
  /**
6627
6654
  * Add or remove XCs to a given data validation rule.
@@ -6713,69 +6740,63 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6713
6740
  }
6714
6741
 
6715
6742
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6716
- isPasteAllowed(sheetId, target, content) {
6717
- if (!("cells" in content)) {
6718
- return "Success" /* CommandResult.Success */;
6743
+ copy(data) {
6744
+ if (!data.zones.length) {
6745
+ return;
6719
6746
  }
6720
- const clipboardHeight = content.cells.length;
6721
- const clipboardWidth = content.cells[0].length;
6722
- for (const zone of getPasteZones(target, content.cells)) {
6723
- if (this.getters.doesIntersectMerge(sheetId, zone)) {
6724
- if (target.length > 1 ||
6725
- !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6726
- clipboardHeight * clipboardWidth !== 1) {
6727
- return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6728
- }
6747
+ const sheetId = this.getters.getActiveSheetId();
6748
+ const { rowsIndexes, columnsIndexes } = data;
6749
+ const merges = [];
6750
+ for (const row of rowsIndexes) {
6751
+ const mergesInRow = [];
6752
+ for (const col of columnsIndexes) {
6753
+ const position = { col, row, sheetId };
6754
+ mergesInRow.push(this.getters.getMerge(position));
6729
6755
  }
6756
+ merges.push(mergesInRow);
6730
6757
  }
6731
- return "Success" /* CommandResult.Success */;
6758
+ return { merges };
6732
6759
  }
6733
6760
  /**
6734
6761
  * Paste the clipboard content in the given target
6735
6762
  */
6736
6763
  paste(target, content, options) {
6737
- if (options?.isCutOperation || !("zones" in target) || !target.zones.length) {
6764
+ if (!content.merges ||
6765
+ options?.isCutOperation ||
6766
+ !("zones" in target) ||
6767
+ !target.zones.length) {
6738
6768
  return;
6739
6769
  }
6740
- this.pasteFromCopy(target.sheetId, target.zones, content.cells, options);
6770
+ this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
6741
6771
  }
6742
- pasteZone(sheetId, col, row, cells) {
6743
- for (const [r, rowCells] of cells.entries()) {
6744
- for (const [c, origin] of rowCells.entries()) {
6745
- if (!origin.position) {
6746
- continue;
6747
- }
6772
+ pasteZone(sheetId, col, row, merges) {
6773
+ for (const [r, rowMerges] of merges.entries()) {
6774
+ for (const [c, originMerge] of rowMerges.entries()) {
6748
6775
  const position = { col: col + c, row: row + r, sheetId };
6749
- this.pasteMergeIfExist(origin.position, position);
6776
+ this.pasteMerge(originMerge, position);
6750
6777
  }
6751
6778
  }
6752
6779
  }
6753
- /**
6754
- * If the origin position given is the top left of a merge, merge the target
6755
- * position.
6756
- */
6757
- pasteMergeIfExist(origin, target) {
6758
- let { sheetId, col, row } = origin;
6759
- const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
6760
- if (mainCellColOrigin === col && mainCellRowOrigin === row) {
6761
- const merge = this.getters.getMerge(origin);
6762
- if (!merge) {
6763
- return;
6764
- }
6765
- ({ sheetId, col, row } = target);
6766
- this.dispatch("ADD_MERGE", {
6767
- sheetId,
6768
- force: true,
6769
- target: [
6770
- {
6771
- left: col,
6772
- top: row,
6773
- right: col + merge.right - merge.left,
6774
- bottom: row + merge.bottom - merge.top,
6775
- },
6776
- ],
6777
- });
6780
+ pasteMerge(originMerge, target) {
6781
+ if (!originMerge) {
6782
+ return;
6783
+ }
6784
+ if (this.getters.isInMerge(target)) {
6785
+ return;
6778
6786
  }
6787
+ const { sheetId, col, row } = target;
6788
+ this.dispatch("ADD_MERGE", {
6789
+ sheetId,
6790
+ force: true,
6791
+ target: [
6792
+ {
6793
+ left: col,
6794
+ top: row,
6795
+ right: col + originMerge.right - originMerge.left,
6796
+ bottom: row + originMerge.bottom - originMerge.top,
6797
+ },
6798
+ ],
6799
+ });
6779
6800
  }
6780
6801
  }
6781
6802
 
@@ -7755,6 +7776,486 @@ function errorCell(value, message) {
7755
7776
  };
7756
7777
  }
7757
7778
 
7779
+ function boolAnd(args) {
7780
+ let foundBoolean = false;
7781
+ let acc = true;
7782
+ conditionalVisitBoolean(args, (arg) => {
7783
+ foundBoolean = true;
7784
+ acc = acc && arg;
7785
+ return acc;
7786
+ });
7787
+ return {
7788
+ foundBoolean,
7789
+ result: acc,
7790
+ };
7791
+ }
7792
+ function boolOr(args) {
7793
+ let foundBoolean = false;
7794
+ let acc = false;
7795
+ conditionalVisitBoolean(args, (arg) => {
7796
+ foundBoolean = true;
7797
+ acc = acc || arg;
7798
+ return !acc;
7799
+ });
7800
+ return {
7801
+ foundBoolean,
7802
+ result: acc,
7803
+ };
7804
+ }
7805
+
7806
+ function sum(values, locale) {
7807
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7808
+ }
7809
+ function countUnique(args) {
7810
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7811
+ }
7812
+
7813
+ function assertSameNumberOfElements(...args) {
7814
+ const dims = args[0].length;
7815
+ 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())));
7816
+ }
7817
+ function average(values, locale) {
7818
+ let count = 0;
7819
+ const sum = reduceNumbers(values, (acc, a) => {
7820
+ count += 1;
7821
+ return acc + a;
7822
+ }, 0, locale);
7823
+ assertNotZero(count);
7824
+ return sum / count;
7825
+ }
7826
+ function countNumbers(values, locale) {
7827
+ let count = 0;
7828
+ for (let n of values) {
7829
+ if (isMatrix(n)) {
7830
+ for (let i of n) {
7831
+ for (let j of i) {
7832
+ if (typeof j.value === "number") {
7833
+ count += 1;
7834
+ }
7835
+ }
7836
+ }
7837
+ }
7838
+ else {
7839
+ const value = n?.value;
7840
+ if (!isEvaluationError(value) &&
7841
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7842
+ count += 1;
7843
+ }
7844
+ }
7845
+ }
7846
+ return count;
7847
+ }
7848
+ function countAny(values) {
7849
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7850
+ }
7851
+ function max(values, locale) {
7852
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7853
+ return result === -Infinity ? 0 : result;
7854
+ }
7855
+ function min(values, locale) {
7856
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7857
+ return result === Infinity ? 0 : result;
7858
+ }
7859
+
7860
+ const pivotTimeAdapterRegistry = new Registry();
7861
+ function pivotTimeAdapter(granularity) {
7862
+ return pivotTimeAdapterRegistry.get(granularity);
7863
+ }
7864
+ /**
7865
+ * The Time Adapter: Managing Time Periods for Pivot Functions
7866
+ *
7867
+ * Overview:
7868
+ * A time adapter is responsible for managing time periods associated with pivot functions.
7869
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7870
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
7871
+ * and the pivot.
7872
+ * By normalizing the period value, it can be stored consistently in the pivot.
7873
+ *
7874
+ * Normalization Process:
7875
+ * When working with functions in the spreadsheet, the time adapter normalizes
7876
+ * the provided period to facilitate accurate lookup of values in the pivot.
7877
+ * For instance, if the spreadsheet function represents a day period as a number generated
7878
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7879
+ *
7880
+ */
7881
+ /**
7882
+ * Normalized value: "12/25/2023"
7883
+ *
7884
+ * Note: Those two format are equivalent:
7885
+ * - "MM/dd/yyyy" (luxon format)
7886
+ * - "mm/dd/yyyy" (spreadsheet format)
7887
+ **/
7888
+ const dayAdapter = {
7889
+ normalizeFunctionValue(value) {
7890
+ const date = toNumber(value, DEFAULT_LOCALE);
7891
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7892
+ },
7893
+ getFormat(locale) {
7894
+ return (locale ?? DEFAULT_LOCALE).dateFormat;
7895
+ },
7896
+ formatValue(normalizedValue, locale) {
7897
+ locale = locale ?? DEFAULT_LOCALE;
7898
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7899
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7900
+ },
7901
+ toCellValue(normalizedValue) {
7902
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7903
+ },
7904
+ };
7905
+ /**
7906
+ * normalizes day of month number
7907
+ */
7908
+ const dayOfMonthAdapter = {
7909
+ normalizeFunctionValue(value) {
7910
+ const day = toNumber(value, DEFAULT_LOCALE);
7911
+ if (day < 1 || day > 31) {
7912
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7913
+ }
7914
+ return day;
7915
+ },
7916
+ getFormat() {
7917
+ return "0";
7918
+ },
7919
+ formatValue(normalizedValue, locale) {
7920
+ locale = locale ?? DEFAULT_LOCALE;
7921
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7922
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7923
+ },
7924
+ toCellValue(normalizedValue) {
7925
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7926
+ },
7927
+ };
7928
+ /**
7929
+ * Normalized value: "2/2023" for week 2 of 2023
7930
+ */
7931
+ const weekAdapter = {
7932
+ normalizeFunctionValue(value) {
7933
+ const [week, year] = value.split("/");
7934
+ return `${Number(week)}/${Number(year)}`;
7935
+ },
7936
+ getFormat() {
7937
+ return undefined;
7938
+ },
7939
+ formatValue(normalizedValue) {
7940
+ const [week, year] = normalizedValue.split("/");
7941
+ return _t("W%(week)s %(year)s", { week, year });
7942
+ },
7943
+ toCellValue(normalizedValue) {
7944
+ return this.formatValue(normalizedValue);
7945
+ },
7946
+ };
7947
+ /**
7948
+ * normalizes iso week number
7949
+ */
7950
+ const isoWeekNumberAdapter = {
7951
+ normalizeFunctionValue(value) {
7952
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
7953
+ if (isoWeek < 0 || isoWeek > 53) {
7954
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7955
+ }
7956
+ return isoWeek;
7957
+ },
7958
+ getFormat() {
7959
+ return "0";
7960
+ },
7961
+ formatValue(normalizedValue, locale) {
7962
+ locale = locale ?? DEFAULT_LOCALE;
7963
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7964
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7965
+ },
7966
+ toCellValue(normalizedValue) {
7967
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7968
+ },
7969
+ };
7970
+ /**
7971
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7972
+ * e.g. "01/2020" for January 2020
7973
+ */
7974
+ const monthAdapter = {
7975
+ normalizeFunctionValue(value) {
7976
+ const date = toNumber(value, DEFAULT_LOCALE);
7977
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7978
+ },
7979
+ getFormat() {
7980
+ return "mmmm yyyy";
7981
+ },
7982
+ formatValue(normalizedValue, locale) {
7983
+ locale = locale ?? DEFAULT_LOCALE;
7984
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7985
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7986
+ },
7987
+ toCellValue(normalizedValue) {
7988
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7989
+ },
7990
+ };
7991
+ /**
7992
+ * normalizes month number
7993
+ */
7994
+ const monthNumberAdapter = {
7995
+ normalizeFunctionValue(value) {
7996
+ const month = toNumber(value, DEFAULT_LOCALE);
7997
+ if (month < 1 || month > 12) {
7998
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
7999
+ }
8000
+ return month;
8001
+ },
8002
+ getFormat() {
8003
+ return "0";
8004
+ },
8005
+ formatValue(normalizedValue, locale) {
8006
+ locale = locale ?? DEFAULT_LOCALE;
8007
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8008
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8009
+ },
8010
+ toCellValue(normalizedValue) {
8011
+ return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8012
+ },
8013
+ };
8014
+ /**
8015
+ * normalized quarter value is "quarter/year"
8016
+ * e.g. "1/2020" for Q1 2020
8017
+ */
8018
+ const quarterAdapter = {
8019
+ normalizeFunctionValue(value) {
8020
+ const [quarter, year] = value.split("/");
8021
+ return `${quarter}/${year}`;
8022
+ },
8023
+ getFormat() {
8024
+ return undefined;
8025
+ },
8026
+ formatValue(normalizedValue) {
8027
+ const [quarter, year] = normalizedValue.split("/");
8028
+ return _t("Q%(quarter)s %(year)s", { quarter, year });
8029
+ },
8030
+ toCellValue(normalizedValue) {
8031
+ return this.formatValue(normalizedValue);
8032
+ },
8033
+ };
8034
+ /**
8035
+ * normalizes quarter number
8036
+ */
8037
+ const quarterNumberAdapter = {
8038
+ normalizeFunctionValue(value) {
8039
+ const quarter = toNumber(value, DEFAULT_LOCALE);
8040
+ if (quarter < 1 || quarter > 4) {
8041
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8042
+ }
8043
+ return quarter;
8044
+ },
8045
+ getFormat() {
8046
+ return "0";
8047
+ },
8048
+ formatValue(normalizedValue, locale) {
8049
+ locale = locale ?? DEFAULT_LOCALE;
8050
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8051
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8052
+ },
8053
+ toCellValue(normalizedValue) {
8054
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8055
+ },
8056
+ };
8057
+ const yearAdapter = {
8058
+ normalizeFunctionValue(value) {
8059
+ return toNumber(value, DEFAULT_LOCALE);
8060
+ },
8061
+ getFormat() {
8062
+ return "0";
8063
+ },
8064
+ formatValue(normalizedValue, locale) {
8065
+ locale = locale ?? DEFAULT_LOCALE;
8066
+ return formatValue(normalizedValue, { locale, format: "0" });
8067
+ },
8068
+ toCellValue(normalizedValue) {
8069
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8070
+ },
8071
+ };
8072
+ pivotTimeAdapterRegistry
8073
+ .add("day", dayAdapter)
8074
+ .add("week", weekAdapter)
8075
+ .add("month", monthAdapter)
8076
+ .add("quarter", quarterAdapter)
8077
+ .add("year", yearAdapter)
8078
+ .add("day_of_month", dayOfMonthAdapter)
8079
+ .add("iso_week_number", isoWeekNumberAdapter)
8080
+ .add("month_number", monthNumberAdapter)
8081
+ .add("quarter_number", quarterNumberAdapter)
8082
+ .add("year_number", yearAdapter);
8083
+
8084
+ const AGGREGATOR_NAMES = {
8085
+ count: _t("Count"),
8086
+ count_distinct: _t("Count Distinct"),
8087
+ bool_and: _t("Boolean And"),
8088
+ bool_or: _t("Boolean Or"),
8089
+ max: _t("Maximum"),
8090
+ min: _t("Minimum"),
8091
+ avg: _t("Average"),
8092
+ sum: _t("Sum"),
8093
+ };
8094
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8095
+ const AGGREGATORS_BY_FIELD_TYPE = {
8096
+ integer: NUMBER_CHAR_AGGREGATORS,
8097
+ char: NUMBER_CHAR_AGGREGATORS,
8098
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8099
+ };
8100
+ const AGGREGATORS = {};
8101
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8102
+ AGGREGATORS[type] = {};
8103
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8104
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8105
+ }
8106
+ }
8107
+ const AGGREGATORS_FN = {
8108
+ count: {
8109
+ fn: (args) => countAny([args]),
8110
+ format: () => "0",
8111
+ },
8112
+ count_distinct: {
8113
+ fn: (args) => countUnique([args]),
8114
+ format: () => "0",
8115
+ },
8116
+ bool_and: {
8117
+ fn: (args) => boolAnd([args]).result,
8118
+ format: () => undefined,
8119
+ },
8120
+ bool_or: {
8121
+ fn: (args) => boolOr([args]).result,
8122
+ format: () => undefined,
8123
+ },
8124
+ max: {
8125
+ fn: (args, locale) => max([args], locale),
8126
+ format: inferFormat,
8127
+ },
8128
+ min: {
8129
+ fn: (args, locale) => min([args], locale),
8130
+ format: inferFormat,
8131
+ },
8132
+ avg: {
8133
+ fn: (args, locale) => average([args], locale),
8134
+ format: inferFormat,
8135
+ },
8136
+ sum: {
8137
+ fn: (args, locale) => sum([args], locale),
8138
+ format: inferFormat,
8139
+ },
8140
+ };
8141
+ function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8142
+ switch (pivotCell.type) {
8143
+ case "HEADER":
8144
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8145
+ case "MEASURE_HEADER":
8146
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8147
+ case "VALUE":
8148
+ return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8149
+ case "EMPTY":
8150
+ return "";
8151
+ }
8152
+ }
8153
+ /**
8154
+ * Build a pivot formula expression
8155
+ */
8156
+ function makePivotFormula(formula, args) {
8157
+ return `=${formula}(${args
8158
+ .map((arg) => {
8159
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8160
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
8161
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8162
+ })
8163
+ .join(",")})`;
8164
+ }
8165
+ /**
8166
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8167
+ * in this object
8168
+ * If the object has no keys, return 0
8169
+ *
8170
+ */
8171
+ function getMaxObjectId(o) {
8172
+ const keys = Object.keys(o);
8173
+ if (!keys.length) {
8174
+ return 0;
8175
+ }
8176
+ const nums = keys.map((id) => parseInt(id, 10));
8177
+ const max = Math.max(...nums);
8178
+ return max;
8179
+ }
8180
+ const ALL_PERIODS = {
8181
+ year: _t("Year"),
8182
+ quarter: _t("Quarter"),
8183
+ month: _t("Month"),
8184
+ week: _t("Week"),
8185
+ day: _t("Day"),
8186
+ year_number: _t("Year"),
8187
+ quarter_number: _t("Quarter"),
8188
+ month_number: _t("Month"),
8189
+ iso_week_number: _t("Week"),
8190
+ day_of_month: _t("Day of Month"),
8191
+ };
8192
+ const DATE_FIELDS = ["date", "datetime"];
8193
+ /**
8194
+ * Parse a dimension string into a pivot dimension definition.
8195
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8196
+ */
8197
+ function parseDimension(dimension) {
8198
+ const [name, granularity] = dimension.split(":");
8199
+ if (granularity) {
8200
+ return { name, granularity };
8201
+ }
8202
+ return { name };
8203
+ }
8204
+ function isDateField(field) {
8205
+ return DATE_FIELDS.includes(field.type);
8206
+ }
8207
+ function toPivotDomain(domainStr) {
8208
+ if (domainStr.length % 2 !== 0) {
8209
+ throw new Error("Invalid domain: odd number of elements");
8210
+ }
8211
+ const domain = [];
8212
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
8213
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8214
+ }
8215
+ return domain;
8216
+ }
8217
+ function flatPivotDomain(domain) {
8218
+ return domain.flatMap((arg) => [arg.field, arg.value]);
8219
+ }
8220
+ /**
8221
+ * Parses the value defining a pivot group in a PIVOT formula
8222
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8223
+ * the two group values are "42" and "won".
8224
+ */
8225
+ function toNormalizedPivotValue(dimension, groupValue) {
8226
+ if (groupValue === null || groupValue === "null") {
8227
+ return null;
8228
+ }
8229
+ const groupValueString = typeof groupValue === "boolean"
8230
+ ? toString(groupValue).toLocaleLowerCase()
8231
+ : toString(groupValue);
8232
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8233
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8234
+ field: dimension.displayName,
8235
+ type: dimension.type,
8236
+ }));
8237
+ }
8238
+ // represents a field which is not set (=False server side)
8239
+ if (groupValueString === "false") {
8240
+ return false;
8241
+ }
8242
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8243
+ return normalizer(groupValueString, dimension.granularity);
8244
+ }
8245
+ function normalizeDateTime(value, granularity) {
8246
+ if (!granularity) {
8247
+ throw "";
8248
+ }
8249
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8250
+ }
8251
+ const pivotNormalizationValueRegistry = new Registry();
8252
+ pivotNormalizationValueRegistry
8253
+ .add("date", normalizeDateTime)
8254
+ .add("datetime", normalizeDateTime)
8255
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8256
+ .add("boolean", (value) => toBoolean(value))
8257
+ .add("char", (value) => toString(value));
8258
+
7758
8259
  /**
7759
8260
  * Change the reference types inside the given token, if the token represent a range or a cell
7760
8261
  *
@@ -7953,6 +8454,11 @@ const CellIsOperators = {
7953
8454
  const ChartTerms = {
7954
8455
  Series: _t("Series"),
7955
8456
  BackgroundColor: _t("Background color"),
8457
+ StackedBarChart: _t("Stacked bar chart"),
8458
+ StackedLineChart: _t("Stacked line chart"),
8459
+ CumulativeData: _t("Cumulative data"),
8460
+ TreatLabelsAsText: _t("Treat labels as text"),
8461
+ AggregatedChart: _t("Aggregate"),
7956
8462
  Errors: {
7957
8463
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7958
8464
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -8885,6 +9391,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8885
9391
  }
8886
9392
  const color = highlight.color || HIGHLIGHT_COLOR;
8887
9393
  const { ctx } = renderingContext;
9394
+ ctx.save();
8888
9395
  if (!highlight.noBorder) {
8889
9396
  if (highlight.dashed) {
8890
9397
  ctx.setLineDash([5, 3]);
@@ -8904,6 +9411,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8904
9411
  ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8905
9412
  ctx.fillRect(x, y, width, height);
8906
9413
  }
9414
+ ctx.restore();
8907
9415
  }
8908
9416
 
8909
9417
  class HighlightStore extends SpreadsheetStore {
@@ -9434,8 +9942,20 @@ class ComposerStore extends SpreadsheetStore {
9434
9942
  replaceSelectedRange(zone) {
9435
9943
  const ref = this.getZoneReference(zone);
9436
9944
  const currentToken = this.tokenAtCursor;
9437
- const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
9438
- this.replaceText(ref, start, this.selectionEnd);
9945
+ let replaceStart = this.selectionStart;
9946
+ if (currentToken?.type === "REFERENCE") {
9947
+ replaceStart = currentToken.start;
9948
+ }
9949
+ else if (currentToken?.type === "RIGHT_PAREN") {
9950
+ // match left parenthesis
9951
+ const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
9952
+ const functionToken = this.currentTokens[leftParenthesisIndex - 1];
9953
+ if (functionToken === undefined) {
9954
+ return;
9955
+ }
9956
+ replaceStart = functionToken.start;
9957
+ }
9958
+ this.replaceText(ref, replaceStart, this.selectionEnd);
9439
9959
  }
9440
9960
  /**
9441
9961
  * Replace the reference of the old zone by the new one.
@@ -9455,10 +9975,8 @@ class ComposerStore extends SpreadsheetStore {
9455
9975
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
9456
9976
  return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
9457
9977
  });
9458
- // this function assumes that the previous range is always found because
9459
- // it's called when changing a highlight, which exists by definition
9460
9978
  if (!previousRefToken) {
9461
- throw new Error("Previous range not found");
9979
+ return;
9462
9980
  }
9463
9981
  const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
9464
9982
  this.selectionStart = previousRefToken.start;
@@ -9470,6 +9988,17 @@ class ComposerStore extends SpreadsheetStore {
9470
9988
  getZoneReference(zone) {
9471
9989
  const inputSheetId = this.currentEditedCell.sheetId;
9472
9990
  const sheetId = this.getters.getActiveSheetId();
9991
+ if (zone.top === zone.bottom && zone.left === zone.right) {
9992
+ const position = { sheetId, col: zone.left, row: zone.top };
9993
+ const pivotId = this.getters.getPivotIdFromPosition(position);
9994
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
9995
+ const cell = this.getters.getCell(position);
9996
+ if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9997
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
9998
+ const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
9999
+ return formula.slice(1); // strip leading =
10000
+ }
10001
+ }
9473
10002
  const range = this.getters.getRangeFromZone(sheetId, zone);
9474
10003
  return this.getters.getSelectionRangeString(range, inputSheetId);
9475
10004
  }
@@ -9572,19 +10101,35 @@ class ComposerStore extends SpreadsheetStore {
9572
10101
  const colorIndex = this.colorIndexByRange[rangeString];
9573
10102
  return colors$1[colorIndex % colors$1.length];
9574
10103
  };
9575
- return this.getReferencedRanges().map((range) => {
10104
+ const highlights = [];
10105
+ for (const range of this.getReferencedRanges()) {
9576
10106
  const rangeString = this.getters.getRangeString(range, editionSheetId);
9577
10107
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
9578
10108
  const zone = numberOfRows * numberOfCols === 1
9579
10109
  ? this.getters.expandZone(range.sheetId, range.zone)
9580
10110
  : range.zone;
9581
- return {
10111
+ highlights.push({
9582
10112
  zone,
9583
10113
  color: rangeColor(rangeString),
9584
10114
  sheetId: range.sheetId,
9585
10115
  interactive: true,
9586
- };
9587
- });
10116
+ });
10117
+ }
10118
+ const activeSheetId = this.getters.getActiveSheetId();
10119
+ const selectionZone = this.model.selection.getAnchor().zone;
10120
+ const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
10121
+ if (this.editionMode === "selecting" && !isSelectionHightlighted) {
10122
+ highlights.push({
10123
+ zone: selectionZone,
10124
+ color: "#445566",
10125
+ sheetId: activeSheetId,
10126
+ dashed: true,
10127
+ interactive: false,
10128
+ noFill: true,
10129
+ thinLine: true,
10130
+ });
10131
+ }
10132
+ return highlights;
9588
10133
  }
9589
10134
  /**
9590
10135
  * Return ranges currently referenced in the composer
@@ -9843,8 +10388,14 @@ class ChartJsComponent extends Component {
9843
10388
  useEffect(() => {
9844
10389
  const runtime = this.chartRuntime;
9845
10390
  if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10391
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10392
+ this.chart?.destroy();
10393
+ this.createChart(deepCopy(runtime.chartJsConfig));
10394
+ }
10395
+ else {
10396
+ this.updateChartJs(deepCopy(runtime));
10397
+ }
9846
10398
  this.currentRuntime = runtime;
9847
- this.updateChartJs(deepCopy(runtime));
9848
10399
  }
9849
10400
  });
9850
10401
  }
@@ -10212,6 +10763,9 @@ function getChartAxisTitleRuntime(design) {
10212
10763
  }
10213
10764
  function getDefinedAxis(definition) {
10214
10765
  let useLeftAxis = false, useRightAxis = false;
10766
+ if ("horizontal" in definition && definition.horizontal) {
10767
+ return { useLeftAxis: true, useRightAxis: false };
10768
+ }
10215
10769
  for (const design of definition.dataSets || []) {
10216
10770
  if (design.yAxisId === "y1") {
10217
10771
  useRightAxis = true;
@@ -11779,13 +12333,6 @@ var misc = /*#__PURE__*/Object.freeze({
11779
12333
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11780
12334
  });
11781
12335
 
11782
- function sum(values, locale) {
11783
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11784
- }
11785
- function countUnique(args) {
11786
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
11787
- }
11788
-
11789
12336
  const DEFAULT_FACTOR = 1;
11790
12337
  const DEFAULT_MODE = 0;
11791
12338
  const DEFAULT_PLACES = 0;
@@ -12899,53 +13446,6 @@ var math = /*#__PURE__*/Object.freeze({
12899
13446
  TRUNC: TRUNC
12900
13447
  });
12901
13448
 
12902
- function assertSameNumberOfElements(...args) {
12903
- const dims = args[0].length;
12904
- 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())));
12905
- }
12906
- function average(values, locale) {
12907
- let count = 0;
12908
- const sum = reduceNumbers(values, (acc, a) => {
12909
- count += 1;
12910
- return acc + a;
12911
- }, 0, locale);
12912
- assertNotZero(count);
12913
- return sum / count;
12914
- }
12915
- function countNumbers(values, locale) {
12916
- let count = 0;
12917
- for (let n of values) {
12918
- if (isMatrix(n)) {
12919
- for (let i of n) {
12920
- for (let j of i) {
12921
- if (typeof j.value === "number") {
12922
- count += 1;
12923
- }
12924
- }
12925
- }
12926
- }
12927
- else {
12928
- const value = n?.value;
12929
- if (!isEvaluationError(value) &&
12930
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
12931
- count += 1;
12932
- }
12933
- }
12934
- }
12935
- return count;
12936
- }
12937
- function countAny(values) {
12938
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
12939
- }
12940
- function max(values, locale) {
12941
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12942
- return result === -Infinity ? 0 : result;
12943
- }
12944
- function min(values, locale) {
12945
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12946
- return result === Infinity ? 0 : result;
12947
- }
12948
-
12949
13449
  function filterAndFlatData(dataY, dataX) {
12950
13450
  const _flatDataY = [];
12951
13451
  const _flatDataX = [];
@@ -13501,7 +14001,7 @@ const LARGE = {
13501
14001
  // LINEST
13502
14002
  // -----------------------------------------------------------------------------
13503
14003
  const LINEST = {
13504
- description: _t("Compute the intercept of the linear regression."),
14004
+ description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
13505
14005
  args: [
13506
14006
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13507
14007
  arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -13517,7 +14017,7 @@ const LINEST = {
13517
14017
  // LOGEST
13518
14018
  // -----------------------------------------------------------------------------
13519
14019
  const LOGEST = {
13520
- description: _t("Compute the intercept of the linear regression."),
14020
+ description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
13521
14021
  args: [
13522
14022
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13523
14023
  arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -17920,33 +18420,6 @@ var info = /*#__PURE__*/Object.freeze({
17920
18420
  NA: NA
17921
18421
  });
17922
18422
 
17923
- function boolAnd(args) {
17924
- let foundBoolean = false;
17925
- let acc = true;
17926
- conditionalVisitBoolean(args, (arg) => {
17927
- foundBoolean = true;
17928
- acc = acc && arg;
17929
- return acc;
17930
- });
17931
- return {
17932
- foundBoolean,
17933
- result: acc,
17934
- };
17935
- }
17936
- function boolOr(args) {
17937
- let foundBoolean = false;
17938
- let acc = false;
17939
- conditionalVisitBoolean(args, (arg) => {
17940
- foundBoolean = true;
17941
- acc = acc || arg;
17942
- return !acc;
17943
- });
17944
- return {
17945
- foundBoolean,
17946
- result: acc,
17947
- };
17948
- }
17949
-
17950
18423
  // -----------------------------------------------------------------------------
17951
18424
  // AND
17952
18425
  // -----------------------------------------------------------------------------
@@ -18144,393 +18617,6 @@ var logical = /*#__PURE__*/Object.freeze({
18144
18617
  XOR: XOR
18145
18618
  });
18146
18619
 
18147
- const pivotTimeAdapterRegistry = new Registry();
18148
- function pivotTimeAdapter(granularity) {
18149
- return pivotTimeAdapterRegistry.get(granularity);
18150
- }
18151
- /**
18152
- * The Time Adapter: Managing Time Periods for Pivot Functions
18153
- *
18154
- * Overview:
18155
- * A time adapter is responsible for managing time periods associated with pivot functions.
18156
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
18157
- * The adapter's primary role is to normalize period values between spreadsheet functions,
18158
- * and the pivot.
18159
- * By normalizing the period value, it can be stored consistently in the pivot.
18160
- *
18161
- * Normalization Process:
18162
- * When working with functions in the spreadsheet, the time adapter normalizes
18163
- * the provided period to facilitate accurate lookup of values in the pivot.
18164
- * For instance, if the spreadsheet function represents a day period as a number generated
18165
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
18166
- *
18167
- */
18168
- /**
18169
- * Normalized value: "12/25/2023"
18170
- *
18171
- * Note: Those two format are equivalent:
18172
- * - "MM/dd/yyyy" (luxon format)
18173
- * - "mm/dd/yyyy" (spreadsheet format)
18174
- **/
18175
- const dayAdapter = {
18176
- normalizeFunctionValue(value) {
18177
- const date = toNumber(value, DEFAULT_LOCALE);
18178
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
18179
- },
18180
- getFormat(locale) {
18181
- return (locale ?? DEFAULT_LOCALE).dateFormat;
18182
- },
18183
- formatValue(normalizedValue, locale) {
18184
- locale = locale ?? DEFAULT_LOCALE;
18185
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18186
- return formatValue(value, { locale, format: this.getFormat(locale) });
18187
- },
18188
- toCellValue(normalizedValue) {
18189
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18190
- },
18191
- };
18192
- /**
18193
- * normalizes day of month number
18194
- */
18195
- const dayOfMonthAdapter = {
18196
- normalizeFunctionValue(value) {
18197
- const day = toNumber(value, DEFAULT_LOCALE);
18198
- if (day < 1 || day > 31) {
18199
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
18200
- }
18201
- return day;
18202
- },
18203
- getFormat() {
18204
- return "0";
18205
- },
18206
- formatValue(normalizedValue, locale) {
18207
- locale = locale ?? DEFAULT_LOCALE;
18208
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18209
- return formatValue(value, { locale, format: this.getFormat(locale) });
18210
- },
18211
- toCellValue(normalizedValue) {
18212
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18213
- },
18214
- };
18215
- /**
18216
- * Normalized value: "2/2023" for week 2 of 2023
18217
- */
18218
- const weekAdapter = {
18219
- normalizeFunctionValue(value) {
18220
- const [week, year] = value.split("/");
18221
- return `${Number(week)}/${Number(year)}`;
18222
- },
18223
- getFormat() {
18224
- return undefined;
18225
- },
18226
- formatValue(normalizedValue) {
18227
- const [week, year] = normalizedValue.split("/");
18228
- return _t("W%(week)s %(year)s", { week, year });
18229
- },
18230
- toCellValue(normalizedValue) {
18231
- return this.formatValue(normalizedValue);
18232
- },
18233
- };
18234
- /**
18235
- * normalizes iso week number
18236
- */
18237
- const isoWeekNumberAdapter = {
18238
- normalizeFunctionValue(value) {
18239
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
18240
- if (isoWeek < 0 || isoWeek > 53) {
18241
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
18242
- }
18243
- return isoWeek;
18244
- },
18245
- getFormat() {
18246
- return "0";
18247
- },
18248
- formatValue(normalizedValue, locale) {
18249
- locale = locale ?? DEFAULT_LOCALE;
18250
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18251
- return formatValue(value, { locale, format: this.getFormat(locale) });
18252
- },
18253
- toCellValue(normalizedValue) {
18254
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18255
- },
18256
- };
18257
- /**
18258
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
18259
- * e.g. "01/2020" for January 2020
18260
- */
18261
- const monthAdapter = {
18262
- normalizeFunctionValue(value) {
18263
- const date = toNumber(value, DEFAULT_LOCALE);
18264
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
18265
- },
18266
- getFormat() {
18267
- return "mmmm yyyy";
18268
- },
18269
- formatValue(normalizedValue, locale) {
18270
- locale = locale ?? DEFAULT_LOCALE;
18271
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18272
- return formatValue(value, { locale, format: this.getFormat(locale) });
18273
- },
18274
- toCellValue(normalizedValue) {
18275
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18276
- },
18277
- };
18278
- /**
18279
- * normalizes month number
18280
- */
18281
- const monthNumberAdapter = {
18282
- normalizeFunctionValue(value) {
18283
- const month = toNumber(value, DEFAULT_LOCALE);
18284
- if (month < 1 || month > 12) {
18285
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
18286
- }
18287
- return month;
18288
- },
18289
- getFormat() {
18290
- return "0";
18291
- },
18292
- formatValue(normalizedValue, locale) {
18293
- locale = locale ?? DEFAULT_LOCALE;
18294
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18295
- return formatValue(value, { locale, format: this.getFormat(locale) });
18296
- },
18297
- toCellValue(normalizedValue) {
18298
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
18299
- },
18300
- };
18301
- /**
18302
- * normalized quarter value is "quarter/year"
18303
- * e.g. "1/2020" for Q1 2020
18304
- */
18305
- const quarterAdapter = {
18306
- normalizeFunctionValue(value) {
18307
- const [quarter, year] = value.split("/");
18308
- return `${quarter}/${year}`;
18309
- },
18310
- getFormat() {
18311
- return undefined;
18312
- },
18313
- formatValue(normalizedValue) {
18314
- const [quarter, year] = normalizedValue.split("/");
18315
- return _t("Q%(quarter)s %(year)s", { quarter, year });
18316
- },
18317
- toCellValue(normalizedValue) {
18318
- return this.formatValue(normalizedValue);
18319
- },
18320
- };
18321
- /**
18322
- * normalizes quarter number
18323
- */
18324
- const quarterNumberAdapter = {
18325
- normalizeFunctionValue(value) {
18326
- const quarter = toNumber(value, DEFAULT_LOCALE);
18327
- if (quarter < 1 || quarter > 4) {
18328
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
18329
- }
18330
- return quarter;
18331
- },
18332
- getFormat() {
18333
- return "0";
18334
- },
18335
- formatValue(normalizedValue, locale) {
18336
- locale = locale ?? DEFAULT_LOCALE;
18337
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18338
- return formatValue(value, { locale, format: this.getFormat(locale) });
18339
- },
18340
- toCellValue(normalizedValue) {
18341
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18342
- },
18343
- };
18344
- const yearAdapter = {
18345
- normalizeFunctionValue(value) {
18346
- return toNumber(value, DEFAULT_LOCALE);
18347
- },
18348
- getFormat() {
18349
- return "0";
18350
- },
18351
- formatValue(normalizedValue, locale) {
18352
- locale = locale ?? DEFAULT_LOCALE;
18353
- return formatValue(normalizedValue, { locale, format: "0" });
18354
- },
18355
- toCellValue(normalizedValue) {
18356
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18357
- },
18358
- };
18359
- pivotTimeAdapterRegistry
18360
- .add("day", dayAdapter)
18361
- .add("week", weekAdapter)
18362
- .add("month", monthAdapter)
18363
- .add("quarter", quarterAdapter)
18364
- .add("year", yearAdapter)
18365
- .add("day_of_month", dayOfMonthAdapter)
18366
- .add("iso_week_number", isoWeekNumberAdapter)
18367
- .add("month_number", monthNumberAdapter)
18368
- .add("quarter_number", quarterNumberAdapter)
18369
- .add("year_number", yearAdapter);
18370
-
18371
- const AGGREGATOR_NAMES = {
18372
- count: _t("Count"),
18373
- count_distinct: _t("Count Distinct"),
18374
- bool_and: _t("Boolean And"),
18375
- bool_or: _t("Boolean Or"),
18376
- max: _t("Maximum"),
18377
- min: _t("Minimum"),
18378
- avg: _t("Average"),
18379
- sum: _t("Sum"),
18380
- };
18381
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
18382
- const AGGREGATORS_BY_FIELD_TYPE = {
18383
- integer: NUMBER_CHAR_AGGREGATORS,
18384
- char: NUMBER_CHAR_AGGREGATORS,
18385
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
18386
- };
18387
- const AGGREGATORS = {};
18388
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
18389
- AGGREGATORS[type] = {};
18390
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
18391
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
18392
- }
18393
- }
18394
- const AGGREGATORS_FN = {
18395
- count: {
18396
- fn: (args) => countAny([args]),
18397
- format: () => "0",
18398
- },
18399
- count_distinct: {
18400
- fn: (args) => countUnique([args]),
18401
- format: () => "0",
18402
- },
18403
- bool_and: {
18404
- fn: (args) => boolAnd([args]).result,
18405
- format: () => undefined,
18406
- },
18407
- bool_or: {
18408
- fn: (args) => boolOr([args]).result,
18409
- format: () => undefined,
18410
- },
18411
- max: {
18412
- fn: (args, locale) => max([args], locale),
18413
- format: inferFormat,
18414
- },
18415
- min: {
18416
- fn: (args, locale) => min([args], locale),
18417
- format: inferFormat,
18418
- },
18419
- avg: {
18420
- fn: (args, locale) => average([args], locale),
18421
- format: inferFormat,
18422
- },
18423
- sum: {
18424
- fn: (args, locale) => sum([args], locale),
18425
- format: inferFormat,
18426
- },
18427
- };
18428
- /**
18429
- * Build a pivot formula expression
18430
- */
18431
- function makePivotFormula(formula, args) {
18432
- return `=${formula}(${args
18433
- .map((arg) => {
18434
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
18435
- const convertToNumber = typeof arg == "number" || stringIsNumber;
18436
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
18437
- })
18438
- .join(",")})`;
18439
- }
18440
- /**
18441
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
18442
- * in this object
18443
- * If the object has no keys, return 0
18444
- *
18445
- */
18446
- function getMaxObjectId(o) {
18447
- const keys = Object.keys(o);
18448
- if (!keys.length) {
18449
- return 0;
18450
- }
18451
- const nums = keys.map((id) => parseInt(id, 10));
18452
- const max = Math.max(...nums);
18453
- return max;
18454
- }
18455
- const ALL_PERIODS = {
18456
- year: _t("Year"),
18457
- quarter: _t("Quarter"),
18458
- month: _t("Month"),
18459
- week: _t("Week"),
18460
- day: _t("Day"),
18461
- year_number: _t("Year"),
18462
- quarter_number: _t("Quarter"),
18463
- month_number: _t("Month"),
18464
- iso_week_number: _t("Week"),
18465
- day_of_month: _t("Day of Month"),
18466
- };
18467
- const DATE_FIELDS = ["date", "datetime"];
18468
- /**
18469
- * Parse a dimension string into a pivot dimension definition.
18470
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
18471
- */
18472
- function parseDimension(dimension) {
18473
- const [name, granularity] = dimension.split(":");
18474
- if (granularity) {
18475
- return { name, granularity };
18476
- }
18477
- return { name };
18478
- }
18479
- function isDateField(field) {
18480
- return DATE_FIELDS.includes(field.type);
18481
- }
18482
- function toPivotDomain(domainStr) {
18483
- if (domainStr.length % 2 !== 0) {
18484
- throw new Error("Invalid domain: odd number of elements");
18485
- }
18486
- const domain = [];
18487
- for (let i = 0; i < domainStr.length - 1; i += 2) {
18488
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
18489
- }
18490
- return domain;
18491
- }
18492
- function flatPivotDomain(domain) {
18493
- return domain.flatMap((arg) => [arg.field, arg.value]);
18494
- }
18495
- /**
18496
- * Parses the value defining a pivot group in a PIVOT formula
18497
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
18498
- * the two group values are "42" and "won".
18499
- */
18500
- function toNormalizedPivotValue(dimension, groupValue) {
18501
- if (groupValue === null || groupValue === "null") {
18502
- return null;
18503
- }
18504
- const groupValueString = typeof groupValue === "boolean"
18505
- ? toString(groupValue).toLocaleLowerCase()
18506
- : toString(groupValue);
18507
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
18508
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
18509
- field: dimension.displayName,
18510
- type: dimension.type,
18511
- }));
18512
- }
18513
- // represents a field which is not set (=False server side)
18514
- if (groupValueString === "false") {
18515
- return false;
18516
- }
18517
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
18518
- return normalizer(groupValueString, dimension.granularity);
18519
- }
18520
- function normalizeDateTime(value, granularity) {
18521
- if (!granularity) {
18522
- throw "";
18523
- }
18524
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
18525
- }
18526
- const pivotNormalizationValueRegistry = new Registry();
18527
- pivotNormalizationValueRegistry
18528
- .add("date", normalizeDateTime)
18529
- .add("datetime", normalizeDateTime)
18530
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
18531
- .add("boolean", (value) => toBoolean(value))
18532
- .add("char", (value) => toString(value));
18533
-
18534
18620
  /**
18535
18621
  * Get the pivot ID from the formula pivot ID.
18536
18622
  */
@@ -19107,14 +19193,10 @@ const PIVOT = {
19107
19193
  result[col].push({ value: "" });
19108
19194
  break;
19109
19195
  case "HEADER":
19110
- const domain = pivotCell.domain;
19111
- const lastNode = domain.at(-1);
19112
- if (lastNode?.field === "measure") {
19113
- result[col].push(pivot.getPivotMeasureValue(toString(lastNode.value), domain));
19114
- }
19115
- else {
19116
- result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
19117
- }
19196
+ result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
19197
+ break;
19198
+ case "MEASURE_HEADER":
19199
+ result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
19118
19200
  break;
19119
19201
  case "VALUE":
19120
19202
  result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
@@ -20153,6 +20235,8 @@ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
20153
20235
  * a child element.
20154
20236
  */
20155
20237
  function isChildEvent(parent, ev) {
20238
+ if (!parent)
20239
+ return false;
20156
20240
  return !!ev.target && parent.contains(ev.target);
20157
20241
  }
20158
20242
  function gridOverlayPosition() {
@@ -22692,7 +22776,8 @@ function truncateLabel(label) {
22692
22776
  /**
22693
22777
  * Get a default chart js configuration
22694
22778
  */
22695
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22779
+ function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22780
+ const { format, locale, truncateLabels, horizontalChart } = args;
22696
22781
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22697
22782
  const options = {
22698
22783
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22736,8 +22821,11 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
22736
22821
  callbacks: {
22737
22822
  label: function (tooltipItem) {
22738
22823
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
22739
- // tooltipItem.parsed.y can be an object or a number for pie charts
22740
- const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
22824
+ // tooltipItem.parsed can be an object or a number for pie charts
22825
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
22826
+ if (!yLabel) {
22827
+ yLabel = tooltipItem.parsed;
22828
+ }
22741
22829
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
22742
22830
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
22743
22831
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
@@ -22911,6 +22999,7 @@ class BarChart extends AbstractChart {
22911
22999
  dataSetsHaveTitle;
22912
23000
  dataSetDesign;
22913
23001
  axesDesign;
23002
+ horizontal;
22914
23003
  constructor(definition, sheetId, getters) {
22915
23004
  super(definition, sheetId, getters);
22916
23005
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -22922,6 +23011,7 @@ class BarChart extends AbstractChart {
22922
23011
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22923
23012
  this.dataSetDesign = definition.dataSets;
22924
23013
  this.axesDesign = definition.axesDesign;
23014
+ this.horizontal = definition.horizontal;
22925
23015
  }
22926
23016
  static transformDefinition(definition, executed) {
22927
23017
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22993,6 +23083,7 @@ class BarChart extends AbstractChart {
22993
23083
  stacked: this.stacked,
22994
23084
  aggregated: this.aggregated,
22995
23085
  axesDesign: this.axesDesign,
23086
+ horizontal: this.horizontal,
22996
23087
  };
22997
23088
  }
22998
23089
  getDefinitionForExcel() {
@@ -23024,7 +23115,10 @@ class BarChart extends AbstractChart {
23024
23115
  }
23025
23116
  function getBarConfiguration(chart, labels, localeFormat) {
23026
23117
  const fontColor = chartFontColor(chart.background);
23027
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23118
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
23119
+ ...localeFormat,
23120
+ horizontalChart: chart.horizontal,
23121
+ });
23028
23122
  const legend = {
23029
23123
  labels: { color: fontColor },
23030
23124
  };
@@ -23038,16 +23132,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23038
23132
  config.options.layout = {
23039
23133
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23040
23134
  };
23041
- config.options.scales = {
23042
- x: {
23043
- ticks: {
23044
- padding: 5,
23045
- color: fontColor,
23046
- },
23047
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23048
- },
23049
- };
23050
- const yAxis = {
23135
+ config.options.indexAxis = chart.horizontal ? "y" : "x";
23136
+ config.options.scales = {};
23137
+ const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23138
+ const valuesAxis = {
23051
23139
  beginAtZero: true, // the origin of the y axis is always zero
23052
23140
  ticks: {
23053
23141
  color: fontColor,
@@ -23063,7 +23151,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23063
23151
  },
23064
23152
  },
23065
23153
  };
23154
+ const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
23155
+ const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
23066
23156
  const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23157
+ config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
23067
23158
  if (useLeftAxis) {
23068
23159
  config.options.scales.y = {
23069
23160
  ...yAxis,
@@ -23130,7 +23221,7 @@ function createBarChartRuntime(chart, getters) {
23130
23221
  const label = definition.dataSets[index].label;
23131
23222
  dataset.label = label;
23132
23223
  }
23133
- if (definition.dataSets?.[index]?.yAxisId) {
23224
+ if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
23134
23225
  dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23135
23226
  }
23136
23227
  }
@@ -24121,6 +24212,7 @@ class PieChart extends AbstractChart {
24121
24212
  type = "pie";
24122
24213
  aggregated;
24123
24214
  dataSetsHaveTitle;
24215
+ isDoughnut;
24124
24216
  constructor(definition, sheetId, getters) {
24125
24217
  super(definition, sheetId, getters);
24126
24218
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24129,6 +24221,7 @@ class PieChart extends AbstractChart {
24129
24221
  this.legendPosition = definition.legendPosition;
24130
24222
  this.aggregated = definition.aggregated;
24131
24223
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24224
+ this.isDoughnut = definition.isDoughnut;
24132
24225
  }
24133
24226
  static transformDefinition(definition, executed) {
24134
24227
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24146,6 +24239,7 @@ class PieChart extends AbstractChart {
24146
24239
  type: "pie",
24147
24240
  labelRange: context.auxiliaryRange || undefined,
24148
24241
  aggregated: context.aggregated ?? false,
24242
+ isDoughnut: false,
24149
24243
  };
24150
24244
  }
24151
24245
  getDefinition() {
@@ -24176,6 +24270,7 @@ class PieChart extends AbstractChart {
24176
24270
  : undefined,
24177
24271
  title: this.title,
24178
24272
  aggregated: this.aggregated,
24273
+ isDoughnut: this.isDoughnut,
24179
24274
  };
24180
24275
  }
24181
24276
  copyForSheetId(sheetId) {
@@ -24310,6 +24405,141 @@ function createPieChartRuntime(chart, getters) {
24310
24405
  };
24311
24406
  config.data.datasets.push(dataset);
24312
24407
  }
24408
+ if (chart.isDoughnut) {
24409
+ config.type = "doughnut";
24410
+ }
24411
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24412
+ }
24413
+
24414
+ class PyramidChart extends AbstractChart {
24415
+ dataSets;
24416
+ labelRange;
24417
+ background;
24418
+ legendPosition;
24419
+ aggregated;
24420
+ type = "pyramid";
24421
+ dataSetsHaveTitle;
24422
+ dataSetDesign;
24423
+ axesDesign;
24424
+ horizontal = true;
24425
+ stacked = true;
24426
+ constructor(definition, sheetId, getters) {
24427
+ super(definition, sheetId, getters);
24428
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
24429
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24430
+ this.background = definition.background;
24431
+ this.legendPosition = definition.legendPosition;
24432
+ this.aggregated = definition.aggregated;
24433
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24434
+ this.dataSetDesign = definition.dataSets;
24435
+ this.axesDesign = definition.axesDesign;
24436
+ }
24437
+ static transformDefinition(definition, executed) {
24438
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24439
+ }
24440
+ static validateChartDefinition(validator, definition) {
24441
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24442
+ }
24443
+ static getDefinitionFromContextCreation(context) {
24444
+ return {
24445
+ background: context.background,
24446
+ dataSets: context.range ?? [],
24447
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24448
+ aggregated: context.aggregated ?? false,
24449
+ legendPosition: context.legendPosition ?? "top",
24450
+ title: context.title || { text: "" },
24451
+ type: "pyramid",
24452
+ labelRange: context.auxiliaryRange || undefined,
24453
+ axesDesign: context.axesDesign,
24454
+ horizontal: true,
24455
+ stacked: true,
24456
+ };
24457
+ }
24458
+ getContextCreation() {
24459
+ const range = [];
24460
+ for (const [i, dataSet] of this.dataSets.entries()) {
24461
+ range.push({
24462
+ ...this.dataSetDesign?.[i],
24463
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24464
+ });
24465
+ }
24466
+ return {
24467
+ ...this,
24468
+ range,
24469
+ auxiliaryRange: this.labelRange
24470
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24471
+ : undefined,
24472
+ };
24473
+ }
24474
+ copyForSheetId(sheetId) {
24475
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24476
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24477
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24478
+ return new PyramidChart(definition, sheetId, this.getters);
24479
+ }
24480
+ copyInSheetId(sheetId) {
24481
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24482
+ return new PyramidChart(definition, sheetId, this.getters);
24483
+ }
24484
+ getDefinition() {
24485
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24486
+ }
24487
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24488
+ const ranges = [];
24489
+ for (const [i, dataSet] of dataSets.entries()) {
24490
+ ranges.push({
24491
+ ...this.dataSetDesign?.[i],
24492
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24493
+ });
24494
+ }
24495
+ return {
24496
+ type: "pyramid",
24497
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24498
+ background: this.background,
24499
+ dataSets: ranges,
24500
+ legendPosition: this.legendPosition,
24501
+ labelRange: labelRange
24502
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24503
+ : undefined,
24504
+ title: this.title,
24505
+ aggregated: this.aggregated,
24506
+ axesDesign: this.axesDesign,
24507
+ horizontal: true,
24508
+ stacked: true,
24509
+ };
24510
+ }
24511
+ getDefinitionForExcel() {
24512
+ return undefined;
24513
+ }
24514
+ updateRanges(applyChange) {
24515
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24516
+ if (!isStale) {
24517
+ return this;
24518
+ }
24519
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24520
+ return new PyramidChart(definition, this.sheetId, this.getters);
24521
+ }
24522
+ }
24523
+ function createPyramidChartRuntime(chart, getters) {
24524
+ const barDef = { ...chart.getDefinition(), type: "bar" };
24525
+ const barChart = new BarChart(barDef, chart.sheetId, getters);
24526
+ const barRuntime = createBarChartRuntime(barChart, getters);
24527
+ const config = barRuntime.chartJsConfig;
24528
+ let datasets = config.data?.datasets;
24529
+ if (datasets && datasets[0]) {
24530
+ datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
24531
+ }
24532
+ if (datasets && datasets[1]) {
24533
+ datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
24534
+ }
24535
+ const scales = config.options.scales;
24536
+ const scalesXCallback = scales.x.ticks.callback;
24537
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
24538
+ const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
24539
+ config.options.plugins.tooltip.callbacks.label = (item) => {
24540
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24541
+ return tooltipLabelCallback(tooltipItem);
24542
+ };
24313
24543
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24314
24544
  }
24315
24545
 
@@ -24752,7 +24982,6 @@ chartRegistry.add("bar", {
24752
24982
  validateChartDefinition: BarChart.validateChartDefinition,
24753
24983
  transformDefinition: BarChart.transformDefinition,
24754
24984
  getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
24755
- name: _t("Bar"),
24756
24985
  sequence: 10,
24757
24986
  });
24758
24987
  chartRegistry.add("combo", {
@@ -24762,7 +24991,6 @@ chartRegistry.add("combo", {
24762
24991
  validateChartDefinition: ComboChart.validateChartDefinition,
24763
24992
  transformDefinition: ComboChart.transformDefinition,
24764
24993
  getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
24765
- name: _t("Combo"),
24766
24994
  sequence: 15,
24767
24995
  });
24768
24996
  chartRegistry.add("line", {
@@ -24772,7 +25000,6 @@ chartRegistry.add("line", {
24772
25000
  validateChartDefinition: LineChart.validateChartDefinition,
24773
25001
  transformDefinition: LineChart.transformDefinition,
24774
25002
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
24775
- name: _t("Line"),
24776
25003
  sequence: 20,
24777
25004
  });
24778
25005
  chartRegistry.add("pie", {
@@ -24782,7 +25009,6 @@ chartRegistry.add("pie", {
24782
25009
  validateChartDefinition: PieChart.validateChartDefinition,
24783
25010
  transformDefinition: PieChart.transformDefinition,
24784
25011
  getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
24785
- name: _t("Pie"),
24786
25012
  sequence: 30,
24787
25013
  });
24788
25014
  chartRegistry.add("scorecard", {
@@ -24792,7 +25018,6 @@ chartRegistry.add("scorecard", {
24792
25018
  validateChartDefinition: ScorecardChart$1.validateChartDefinition,
24793
25019
  transformDefinition: ScorecardChart$1.transformDefinition,
24794
25020
  getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
24795
- name: _t("Scorecard"),
24796
25021
  sequence: 40,
24797
25022
  });
24798
25023
  chartRegistry.add("gauge", {
@@ -24802,7 +25027,6 @@ chartRegistry.add("gauge", {
24802
25027
  validateChartDefinition: GaugeChart.validateChartDefinition,
24803
25028
  transformDefinition: GaugeChart.transformDefinition,
24804
25029
  getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
24805
- name: _t("Gauge"),
24806
25030
  sequence: 50,
24807
25031
  });
24808
25032
  chartRegistry.add("scatter", {
@@ -24812,7 +25036,6 @@ chartRegistry.add("scatter", {
24812
25036
  validateChartDefinition: ScatterChart.validateChartDefinition,
24813
25037
  transformDefinition: ScatterChart.transformDefinition,
24814
25038
  getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
24815
- name: _t("Scatter"),
24816
25039
  sequence: 60,
24817
25040
  });
24818
25041
  chartRegistry.add("waterfall", {
@@ -24822,9 +25045,17 @@ chartRegistry.add("waterfall", {
24822
25045
  validateChartDefinition: WaterfallChart.validateChartDefinition,
24823
25046
  transformDefinition: WaterfallChart.transformDefinition,
24824
25047
  getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
24825
- name: _t("Waterfall"),
24826
25048
  sequence: 70,
24827
25049
  });
25050
+ chartRegistry.add("pyramid", {
25051
+ match: (type) => type === "pyramid",
25052
+ createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
25053
+ getChartRuntime: createPyramidChartRuntime,
25054
+ validateChartDefinition: PyramidChart.validateChartDefinition,
25055
+ transformDefinition: PyramidChart.transformDefinition,
25056
+ getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
25057
+ sequence: 80,
25058
+ });
24828
25059
  const chartComponentRegistry = new Registry();
24829
25060
  chartComponentRegistry.add("line", ChartJsComponent);
24830
25061
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -24834,6 +25065,130 @@ chartComponentRegistry.add("gauge", GaugeChartComponent);
24834
25065
  chartComponentRegistry.add("scatter", ChartJsComponent);
24835
25066
  chartComponentRegistry.add("scorecard", ScorecardChart);
24836
25067
  chartComponentRegistry.add("waterfall", ChartJsComponent);
25068
+ chartComponentRegistry.add("pyramid", ChartJsComponent);
25069
+ const chartCategories = {
25070
+ line: _t("Line"),
25071
+ column: _t("Column"),
25072
+ bar: _t("Bar"),
25073
+ pie: _t("Pie"),
25074
+ misc: _t("Miscellaneous"),
25075
+ };
25076
+ const chartSubtypeRegistry = new Registry();
25077
+ chartSubtypeRegistry
25078
+ .add("line", {
25079
+ matcher: (definition) => definition.type === "line" && !definition.stacked,
25080
+ displayName: _t("Line"),
25081
+ chartType: "line",
25082
+ chartSubtype: "line",
25083
+ subtypeDefinition: { stacked: false },
25084
+ category: "line",
25085
+ preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25086
+ })
25087
+ .add("stacked_line", {
25088
+ matcher: (definition) => definition.type === "line" && definition.stacked,
25089
+ displayName: _t("Stacked Line"),
25090
+ chartType: "line",
25091
+ chartSubtype: "stacked_line",
25092
+ subtypeDefinition: { stacked: true },
25093
+ category: "line",
25094
+ preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25095
+ })
25096
+ .add("scatter", {
25097
+ displayName: _t("Scatter"),
25098
+ chartType: "scatter",
25099
+ chartSubtype: "scatter",
25100
+ category: "misc",
25101
+ preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
25102
+ })
25103
+ .add("column", {
25104
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
25105
+ displayName: _t("Column"),
25106
+ chartType: "bar",
25107
+ chartSubtype: "column",
25108
+ subtypeDefinition: { stacked: false, horizontal: false },
25109
+ category: "column",
25110
+ preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
25111
+ })
25112
+ .add("stacked_column", {
25113
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
25114
+ displayName: _t("Stacked Column"),
25115
+ chartType: "bar",
25116
+ chartSubtype: "stacked_column",
25117
+ subtypeDefinition: { stacked: true, horizontal: false },
25118
+ category: "column",
25119
+ preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
25120
+ })
25121
+ .add("bar", {
25122
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
25123
+ displayName: _t("Bar"),
25124
+ chartType: "bar",
25125
+ chartSubtype: "bar",
25126
+ subtypeDefinition: { horizontal: true, stacked: false },
25127
+ category: "bar",
25128
+ preview: "o-spreadsheet-ChartPreview.BAR_CHART",
25129
+ })
25130
+ .add("stacked_bar", {
25131
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
25132
+ displayName: _t("Stacked Bar"),
25133
+ chartType: "bar",
25134
+ chartSubtype: "stacked_bar",
25135
+ subtypeDefinition: { horizontal: true, stacked: true },
25136
+ category: "bar",
25137
+ preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
25138
+ })
25139
+ .add("combo", {
25140
+ displayName: _t("Combo"),
25141
+ chartSubtype: "combo",
25142
+ chartType: "combo",
25143
+ category: "line",
25144
+ preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
25145
+ })
25146
+ .add("pie", {
25147
+ matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
25148
+ displayName: _t("Pie"),
25149
+ chartSubtype: "pie",
25150
+ chartType: "pie",
25151
+ subtypeDefinition: { isDoughnut: false },
25152
+ category: "pie",
25153
+ preview: "o-spreadsheet-ChartPreview.PIE_CHART",
25154
+ })
25155
+ .add("doughnut", {
25156
+ matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
25157
+ displayName: _t("Doughnut"),
25158
+ chartSubtype: "doughnut",
25159
+ chartType: "pie",
25160
+ subtypeDefinition: { isDoughnut: true },
25161
+ category: "pie",
25162
+ preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
25163
+ })
25164
+ .add("gauge", {
25165
+ displayName: _t("Gauge"),
25166
+ chartSubtype: "gauge",
25167
+ chartType: "gauge",
25168
+ category: "misc",
25169
+ preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
25170
+ })
25171
+ .add("scorecard", {
25172
+ displayName: _t("Scorecard"),
25173
+ chartSubtype: "scorecard",
25174
+ chartType: "scorecard",
25175
+ category: "misc",
25176
+ preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
25177
+ })
25178
+ .add("waterfall", {
25179
+ displayName: _t("Waterfall"),
25180
+ chartSubtype: "waterfall",
25181
+ chartType: "waterfall",
25182
+ category: "misc",
25183
+ preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
25184
+ })
25185
+ .add("pyramid", {
25186
+ displayName: _t("Population Pyramid"),
25187
+ chartSubtype: "pyramid",
25188
+ chartType: "pyramid",
25189
+ category: "misc",
25190
+ preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
25191
+ });
24837
25192
 
24838
25193
  /**
24839
25194
  * Registry intended to support usual currencies. It is mainly used to create
@@ -26690,20 +27045,6 @@ function transformDefinition(definition, executed) {
26690
27045
  }
26691
27046
  return transformation.transformDefinition(definition, executed);
26692
27047
  }
26693
- /**
26694
- * Get an empty definition based on the given context and the given type
26695
- */
26696
- function getChartDefinitionFromContextCreation(context, type) {
26697
- const chartClass = chartRegistry.get(type);
26698
- return chartClass.getChartDefinitionFromContextCreation(context);
26699
- }
26700
- function getChartTypes() {
26701
- const result = {};
26702
- for (const key of chartRegistry.getKeys()) {
26703
- result[key] = chartRegistry.get(key).name;
26704
- }
26705
- return result;
26706
- }
26707
27048
  /**
26708
27049
  * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
26709
27050
  * use the best type of chart to display the data of the zone.
@@ -28163,6 +28504,40 @@ const pivotProperties = {
28163
28504
  },
28164
28505
  icon: "o-spreadsheet-Icon.PIVOT",
28165
28506
  };
28507
+ const FIX_FORMULAS = {
28508
+ name: _t("Convert to individual formulas"),
28509
+ execute(env) {
28510
+ const position = env.model.getters.getActivePosition();
28511
+ const cell = env.model.getters.getCorrespondingFormulaCell(position);
28512
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28513
+ if (!cell || !pivotId) {
28514
+ return;
28515
+ }
28516
+ const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
28517
+ const pivot = env.model.getters.getPivot(pivotId);
28518
+ pivot.init();
28519
+ if (!pivot.isValid()) {
28520
+ return;
28521
+ }
28522
+ env.model.dispatch("INSERT_PIVOT", {
28523
+ sheetId,
28524
+ col,
28525
+ row,
28526
+ pivotId,
28527
+ table: pivot.getTableStructure().export(),
28528
+ });
28529
+ },
28530
+ isVisible: (env) => {
28531
+ const position = env.model.getters.getActivePosition();
28532
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28533
+ if (!pivotId) {
28534
+ return false;
28535
+ }
28536
+ const pivot = env.model.getters.getPivot(pivotId);
28537
+ return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
28538
+ },
28539
+ icon: "o-spreadsheet-Icon.PIVOT",
28540
+ };
28166
28541
 
28167
28542
  //------------------------------------------------------------------------------
28168
28543
  // Context Menu Registry
@@ -28261,6 +28636,10 @@ cellMenuRegistry
28261
28636
  name: INSERT_LINK_NAME,
28262
28637
  sequence: 150,
28263
28638
  separator: true,
28639
+ })
28640
+ .add("pivot_fix_formulas", {
28641
+ ...FIX_FORMULAS,
28642
+ sequence: 155,
28264
28643
  })
28265
28644
  .add("pivot_properties", {
28266
28645
  ...pivotProperties,
@@ -30483,6 +30862,7 @@ class GenericChartConfigPanel extends Component {
30483
30862
  });
30484
30863
  dataSeriesRanges = [];
30485
30864
  labelRange;
30865
+ chartTerms = ChartTerms;
30486
30866
  setup() {
30487
30867
  this.dataSeriesRanges = this.props.definition.dataSets;
30488
30868
  this.labelRange = this.props.definition.labelRange;
@@ -30507,7 +30887,7 @@ class GenericChartConfigPanel extends Component {
30507
30887
  return [
30508
30888
  {
30509
30889
  name: "aggregated",
30510
- label: _t("Aggregate"),
30890
+ label: this.chartTerms.AggregatedChart,
30511
30891
  value: this.props.definition.aggregated ?? false,
30512
30892
  onChange: this.onUpdateAggregated.bind(this),
30513
30893
  },
@@ -30583,9 +30963,6 @@ class GenericChartConfigPanel extends Component {
30583
30963
 
30584
30964
  class BarConfigPanel extends GenericChartConfigPanel {
30585
30965
  static template = "o-spreadsheet-BarConfigPanel";
30586
- get stackedLabel() {
30587
- return _t("Stacked barchart");
30588
- }
30589
30966
  onUpdateStacked(stacked) {
30590
30967
  this.props.updateChart(this.props.figureId, {
30591
30968
  stacked,
@@ -31575,6 +31952,9 @@ class ChartWithAxisDesignPanel extends Component {
31575
31952
  return "left";
31576
31953
  return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31577
31954
  }
31955
+ get canHaveTwoVerticalAxis() {
31956
+ return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
31957
+ }
31578
31958
  updateDataSeriesLabel(ev) {
31579
31959
  const label = ev.target.value;
31580
31960
  const dataSets = this.props.definition.dataSets;
@@ -31739,19 +32119,13 @@ class LineConfigPanel extends GenericChartConfigPanel {
31739
32119
  }
31740
32120
  return false;
31741
32121
  }
31742
- get stackedLabel() {
31743
- return _t("Stacked linechart");
31744
- }
31745
- get cumulativeLabel() {
31746
- return _t("Cumulative data");
31747
- }
31748
32122
  getLabelRangeOptions() {
31749
32123
  const options = super.getLabelRangeOptions();
31750
32124
  if (this.canTreatLabelsAsText) {
31751
32125
  options.push({
31752
32126
  name: "labelsAsText",
31753
32127
  value: this.props.definition.labelsAsText,
31754
- label: _t("Treat labels as text"),
32128
+ label: this.chartTerms.TreatLabelsAsText,
31755
32129
  onChange: this.onUpdateLabelsAsText.bind(this),
31756
32130
  });
31757
32131
  }
@@ -31818,7 +32192,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
31818
32192
  options.push({
31819
32193
  name: "labelsAsText",
31820
32194
  value: this.props.definition.labelsAsText,
31821
- label: _t("Treat labels as text"),
32195
+ label: this.chartTerms.TreatLabelsAsText,
31822
32196
  onChange: this.onUpdateLabelsAsText.bind(this),
31823
32197
  });
31824
32198
  }
@@ -32025,8 +32399,106 @@ chartSidePanelComponentRegistry
32025
32399
  .add("waterfall", {
32026
32400
  configuration: GenericChartConfigPanel,
32027
32401
  design: WaterfallChartDesignPanel,
32402
+ })
32403
+ .add("pyramid", {
32404
+ configuration: GenericChartConfigPanel,
32405
+ design: ChartWithAxisDesignPanel,
32028
32406
  });
32029
32407
 
32408
+ css /* scss */ `
32409
+ .o-section .o-type-selector {
32410
+ height: 30px;
32411
+ padding-left: 30px;
32412
+ }
32413
+ .o-type-selector-preview {
32414
+ left: 5px;
32415
+ top: 3px;
32416
+ .o-chart-preview {
32417
+ width: 24px;
32418
+ height: 24px;
32419
+ }
32420
+ }
32421
+
32422
+ .o-popover .o-chart-select-popover {
32423
+ box-sizing: border-box;
32424
+ background: #fff;
32425
+ .o-chart-type-item {
32426
+ cursor: pointer;
32427
+ padding: 3px 6px;
32428
+ margin: 1px 2px;
32429
+ &.selected,
32430
+ &:hover {
32431
+ background: #f5f5f5;
32432
+ border: 1px solid #ccc;
32433
+ padding: 2px 5px;
32434
+ }
32435
+ .o-chart-preview {
32436
+ width: 48px;
32437
+ height: 48px;
32438
+ }
32439
+ }
32440
+ }
32441
+ `;
32442
+ class ChartTypePicker extends Component {
32443
+ static template = "o-spreadsheet-ChartTypePicker";
32444
+ static components = { Section, Popover };
32445
+ static props = { figureId: String, chartPanelStore: Object };
32446
+ categories = chartCategories;
32447
+ chartTypeByCategories = {};
32448
+ popoverRef = useRef("popoverRef");
32449
+ selectRef = useRef("selectRef");
32450
+ state = useState({ popoverProps: undefined, popoverStyle: "" });
32451
+ setup() {
32452
+ useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
32453
+ for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
32454
+ if (this.chartTypeByCategories[subtypeProperties.category]) {
32455
+ this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
32456
+ }
32457
+ else {
32458
+ this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
32459
+ }
32460
+ }
32461
+ }
32462
+ onExternalClick(ev) {
32463
+ if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
32464
+ isChildEvent(this.selectRef.el, ev)) {
32465
+ return;
32466
+ }
32467
+ this.closePopover();
32468
+ }
32469
+ onTypeChange(type) {
32470
+ this.props.chartPanelStore.changeChartType(this.props.figureId, type);
32471
+ this.closePopover();
32472
+ }
32473
+ getChartDefinition(figureId) {
32474
+ return this.env.model.getters.getChartDefinition(figureId);
32475
+ }
32476
+ getSelectedChartSubtypeProperties() {
32477
+ const definition = this.getChartDefinition(this.props.figureId);
32478
+ const matchedChart = chartSubtypeRegistry
32479
+ .getAll()
32480
+ .find((c) => c.matcher?.(definition) || false);
32481
+ return matchedChart || chartSubtypeRegistry.get(definition.type);
32482
+ }
32483
+ onPointerDown(ev) {
32484
+ if (this.state.popoverProps) {
32485
+ this.closePopover();
32486
+ return;
32487
+ }
32488
+ const target = ev.currentTarget;
32489
+ const { bottom, right, width } = target.getBoundingClientRect();
32490
+ this.state.popoverProps = {
32491
+ anchorRect: { x: right, y: bottom, width: 0, height: 0 },
32492
+ positioning: "TopRight",
32493
+ verticalOffset: 0,
32494
+ };
32495
+ this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
32496
+ }
32497
+ closePopover() {
32498
+ this.state.popoverProps = undefined;
32499
+ }
32500
+ }
32501
+
32030
32502
  class MainChartPanelStore extends SpreadsheetStore {
32031
32503
  mutators = ["activatePanel", "changeChartType"];
32032
32504
  panel = "configuration";
@@ -32034,7 +32506,7 @@ class MainChartPanelStore extends SpreadsheetStore {
32034
32506
  activatePanel(panel) {
32035
32507
  this.panel = panel;
32036
32508
  }
32037
- changeChartType(figureId, type) {
32509
+ changeChartType(figureId, newDisplayType) {
32038
32510
  this.creationContext = {
32039
32511
  ...this.creationContext,
32040
32512
  ...this.getters.getContextCreationChart(figureId),
@@ -32043,13 +32515,25 @@ class MainChartPanelStore extends SpreadsheetStore {
32043
32515
  if (!sheetId) {
32044
32516
  return;
32045
32517
  }
32046
- const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
32518
+ const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
32047
32519
  this.model.dispatch("UPDATE_CHART", {
32048
32520
  definition,
32049
32521
  id: figureId,
32050
32522
  sheetId,
32051
32523
  });
32052
32524
  }
32525
+ getChartDefinitionFromContextCreation(figureId, newDisplayType) {
32526
+ const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
32527
+ const ChartClass = chartRegistry.get(newChartInfo.chartType);
32528
+ const contextCreation = {
32529
+ ...this.creationContext,
32530
+ ...this.getters.getContextCreationChart(figureId),
32531
+ };
32532
+ return {
32533
+ ...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
32534
+ ...newChartInfo.subtypeDefinition,
32535
+ };
32536
+ }
32053
32537
  }
32054
32538
 
32055
32539
  css /* scss */ `
@@ -32078,7 +32562,7 @@ css /* scss */ `
32078
32562
  `;
32079
32563
  class ChartPanel extends Component {
32080
32564
  static template = "o-spreadsheet-ChartPanel";
32081
- static components = { Section };
32565
+ static components = { Section, ChartTypePicker };
32082
32566
  static props = { onCloseSidePanel: Function, figureId: String };
32083
32567
  store;
32084
32568
  get figureId() {
@@ -32138,9 +32622,6 @@ class ChartPanel extends Component {
32138
32622
  getChartDefinition(figureId) {
32139
32623
  return this.env.model.getters.getChartDefinition(figureId);
32140
32624
  }
32141
- get chartTypes() {
32142
- return getChartTypes();
32143
- }
32144
32625
  }
32145
32626
 
32146
32627
  css /* scss */ `
@@ -35214,9 +35695,17 @@ class SpreadsheetPivotTable {
35214
35695
  }
35215
35696
  getPivotCell(col, row, includeTotal = true) {
35216
35697
  const colHeadersHeight = this.columns.length;
35217
- if (row <= colHeadersHeight - 1) {
35698
+ if (col > 0 && row === colHeadersHeight - 1) {
35218
35699
  const domain = this.getColHeaderDomain(col, row);
35219
- return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35700
+ if (!domain) {
35701
+ return EMPTY_PIVOT_CELL;
35702
+ }
35703
+ const measure = domain.at(-1)?.value.toString() || "";
35704
+ return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35705
+ }
35706
+ else if (row <= colHeadersHeight - 1) {
35707
+ const domain = this.getColHeaderDomain(col, row);
35708
+ return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
35220
35709
  }
35221
35710
  else if (col === 0) {
35222
35711
  const rowIndex = row - colHeadersHeight;
@@ -35226,7 +35715,7 @@ class SpreadsheetPivotTable {
35226
35715
  else {
35227
35716
  const rowIndex = row - colHeadersHeight;
35228
35717
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35229
- return { type: "EMPTY" };
35718
+ return EMPTY_PIVOT_CELL;
35230
35719
  }
35231
35720
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35232
35721
  const measure = this.getColMeasure(col);
@@ -35280,6 +35769,7 @@ class SpreadsheetPivotTable {
35280
35769
  };
35281
35770
  }
35282
35771
  }
35772
+ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
35283
35773
 
35284
35774
  /**
35285
35775
  * This function converts a list of data entry into a spreadsheet pivot table.
@@ -35477,6 +35967,9 @@ function createDate(dimension, value, locale) {
35477
35967
  if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
35478
35968
  throw new Error(`Unknown date granularity: ${granularity}`);
35479
35969
  }
35970
+ if (value === null) {
35971
+ return null;
35972
+ }
35480
35973
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35481
35974
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35482
35975
  const date = toJsDate(value, locale);
@@ -35707,14 +36200,17 @@ class SpreadsheetPivot {
35707
36200
  if (dimension.type === "date") {
35708
36201
  const adapter = pivotTimeAdapter(dimension.granularity);
35709
36202
  return {
35710
- value: adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value)),
36203
+ value: lastNode.value !== "null"
36204
+ ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36205
+ : _t("(Undefined)"),
35711
36206
  format: adapter.getFormat(this.getters.getLocale()),
35712
36207
  };
35713
36208
  }
35714
36209
  if (!finalCell) {
35715
36210
  return { value: "" };
35716
36211
  }
35717
- if (finalCell.value === null) {
36212
+ // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36213
+ if (finalCell.value === null || finalCell.value === `${null}`) {
35718
36214
  return { value: _t("(Undefined)") };
35719
36215
  }
35720
36216
  return {
@@ -35736,10 +36232,15 @@ class SpreadsheetPivot {
35736
36232
  if (!operator) {
35737
36233
  throw new Error(`Aggregator ${aggregator} does not exist`);
35738
36234
  }
35739
- return {
35740
- value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
35741
- format: operator.format(values[0]),
35742
- };
36235
+ try {
36236
+ return {
36237
+ value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
36238
+ format: operator.format(values[0]),
36239
+ };
36240
+ }
36241
+ catch (e) {
36242
+ return handleError(e, aggregator.toUpperCase());
36243
+ }
35743
36244
  }
35744
36245
  getPossibleFieldValues(dimension) {
35745
36246
  const values = [];
@@ -51106,12 +51607,12 @@ class PivotCorePlugin extends CorePlugin {
51106
51607
  for (let col = 0; col < pivotCells.length; col++) {
51107
51608
  for (let row = 0; row < pivotCells[col].length; row++) {
51108
51609
  const pivotCell = pivotCells[col][row];
51109
- const cellPosition = {
51610
+ this.dispatch("UPDATE_CELL", {
51110
51611
  sheetId: position.sheetId,
51111
51612
  col: position.col + col,
51112
51613
  row: position.row + row,
51113
- };
51114
- this.addPivotFormula(cellPosition, formulaId, pivotCell);
51614
+ content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51615
+ });
51115
51616
  }
51116
51617
  }
51117
51618
  }
@@ -51141,21 +51642,6 @@ class PivotCorePlugin extends CorePlugin {
51141
51642
  });
51142
51643
  }
51143
51644
  }
51144
- addPivotFormula(position, formulaId, pivotCell) {
51145
- let content = undefined;
51146
- switch (pivotCell.type) {
51147
- case "HEADER":
51148
- content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51149
- break;
51150
- case "VALUE":
51151
- content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51152
- break;
51153
- }
51154
- this.dispatch("UPDATE_CELL", {
51155
- ...position,
51156
- content,
51157
- });
51158
- }
51159
51645
  getPivotCore(pivotId) {
51160
51646
  const pivot = this.pivots[pivotId];
51161
51647
  if (!pivot) {
@@ -52820,7 +53306,6 @@ class Evaluator {
52820
53306
  if (!this.blockedArrayFormulas.has(position)) {
52821
53307
  this.invalidateSpreading(position);
52822
53308
  }
52823
- this.spreadingRelations.removeNode(position);
52824
53309
  const cell = this.getters.getCell(position);
52825
53310
  if (cell === undefined) {
52826
53311
  return EMPTY_CELL;
@@ -52862,6 +53347,7 @@ class Evaluator {
52862
53347
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
52863
53348
  const nbColumns = formulaReturn.length;
52864
53349
  const nbRows = formulaReturn[0].length;
53350
+ this.spreadingRelations.removeNode(formulaPosition);
52865
53351
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52866
53352
  this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52867
53353
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
@@ -54389,9 +54875,10 @@ class PivotUIPlugin extends UIPlugin {
54389
54875
  "getPivot",
54390
54876
  "getFirstPivotFunction",
54391
54877
  "getPivotIdFromPosition",
54392
- "getPivotDomainArgsFromPosition",
54878
+ "getPivotCellFromPosition",
54393
54879
  "isPivotUnused",
54394
54880
  "areDomainArgsFieldsValid",
54881
+ "isSpillPivotFormula",
54395
54882
  ];
54396
54883
  pivots = {};
54397
54884
  unusedPivots;
@@ -54470,6 +54957,14 @@ class PivotUIPlugin extends UIPlugin {
54470
54957
  }
54471
54958
  return undefined;
54472
54959
  }
54960
+ isSpillPivotFormula(position) {
54961
+ const cell = this.getters.getCorrespondingFormulaCell(position);
54962
+ if (cell && cell.isFormula) {
54963
+ const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
54964
+ return pivotFunction?.functionName === "PIVOT";
54965
+ }
54966
+ return false;
54967
+ }
54473
54968
  getFirstPivotFunction(tokens) {
54474
54969
  const pivotFunction = getFirstPivotFunction(tokens);
54475
54970
  if (!pivotFunction) {
@@ -54503,29 +54998,29 @@ class PivotUIPlugin extends UIPlugin {
54503
54998
  * If the cell is the result of PIVOT, the result is the domain of the cell
54504
54999
  * as if it was the individual pivot formula
54505
55000
  */
54506
- getPivotDomainArgsFromPosition(position) {
55001
+ getPivotCellFromPosition(position) {
54507
55002
  const cell = this.getters.getCorrespondingFormulaCell(position);
54508
55003
  if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
54509
- return undefined;
55004
+ return EMPTY_PIVOT_CELL;
54510
55005
  }
54511
55006
  const mainPosition = this.getters.getCellPosition(cell.id);
54512
55007
  const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
54513
55008
  if (!result) {
54514
- return undefined;
55009
+ return EMPTY_PIVOT_CELL;
54515
55010
  }
54516
55011
  const { functionName, args } = result;
54517
55012
  if (functionName === "PIVOT") {
54518
55013
  const formulaId = args[0];
54519
55014
  if (!formulaId) {
54520
- return undefined;
55015
+ return EMPTY_PIVOT_CELL;
54521
55016
  }
54522
55017
  const pivotId = this.getters.getPivotId(formulaId.toString());
54523
55018
  if (!pivotId) {
54524
- return undefined;
55019
+ return EMPTY_PIVOT_CELL;
54525
55020
  }
54526
55021
  const pivot = this.getPivot(pivotId);
54527
55022
  if (!pivot.isValid()) {
54528
- return undefined;
55023
+ return EMPTY_PIVOT_CELL;
54529
55024
  }
54530
55025
  const includeTotal = args[2] === false ? false : undefined;
54531
55026
  const includeColumnHeaders = args[3] === false ? false : undefined;
@@ -54534,22 +55029,28 @@ class PivotUIPlugin extends UIPlugin {
54534
55029
  .getPivotCells(includeTotal, includeColumnHeaders);
54535
55030
  const pivotCol = position.col - mainPosition.col;
54536
55031
  const pivotRow = position.row - mainPosition.row;
54537
- const pivotCell = pivotCells[pivotCol][pivotRow];
54538
- if (pivotCell.type === "EMPTY") {
54539
- return undefined;
54540
- }
54541
- let domain = pivotCell.domain;
54542
- if (domain.at(-1)?.field === "measure") {
54543
- domain = domain.slice(0, -1);
54544
- }
54545
- return { domainArgs: domain, isHeader: pivotCell.type === "HEADER" };
55032
+ return pivotCells[pivotCol][pivotRow];
54546
55033
  }
54547
- let domain = toPivotDomain(args.slice(functionName === "PIVOT.VALUE" ? 2 : 1).map((x) => `${x}`));
54548
- if (domain.at(-1)?.field === "measure") {
54549
- domain = domain.slice(0, -1);
55034
+ if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55035
+ const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55036
+ return {
55037
+ type: "MEASURE_HEADER",
55038
+ domain,
55039
+ measure: args.at(-1)?.toString() || "",
55040
+ };
55041
+ }
55042
+ else if (functionName === "PIVOT.HEADER") {
55043
+ return {
55044
+ type: "HEADER",
55045
+ domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55046
+ };
54550
55047
  }
54551
- const isHeader = functionName === "PIVOT.HEADER";
54552
- return { domainArgs: domain, isHeader };
55048
+ const [measure, ...domainArgs] = args.slice(1);
55049
+ return {
55050
+ type: "VALUE",
55051
+ domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55052
+ measure: measure?.toString() || "",
55053
+ };
54553
55054
  }
54554
55055
  getPivot(pivotId) {
54555
55056
  return this.pivots[pivotId];
@@ -63832,6 +64333,9 @@ class SelectionStreamProcessorImpl {
63832
64333
  getBackToDefault() {
63833
64334
  this.stream.getBackToDefault();
63834
64335
  }
64336
+ getAnchor() {
64337
+ return this.anchor;
64338
+ }
63835
64339
  modifyAnchor(anchor, mode, options) {
63836
64340
  const sheetId = this.getters.getActiveSheetId();
63837
64341
  anchor = {
@@ -66795,6 +67299,7 @@ const registries = {
66795
67299
  chartSidePanelComponentRegistry,
66796
67300
  chartComponentRegistry,
66797
67301
  chartRegistry,
67302
+ chartSubtypeRegistry,
66798
67303
  topbarMenuRegistry,
66799
67304
  topbarComponentRegistry,
66800
67305
  clickableCellRegistry,
@@ -66903,6 +67408,7 @@ const components = {
66903
67408
  GaugeChartDesignPanel,
66904
67409
  ScorecardChartConfigPanel,
66905
67410
  ScorecardChartDesignPanel,
67411
+ ChartTypePicker,
66906
67412
  FigureComponent,
66907
67413
  Menu,
66908
67414
  Popover,
@@ -66952,11 +67458,12 @@ const constants = {
66952
67458
  DEFAULT_LOCALE,
66953
67459
  HIGHLIGHT_COLOR,
66954
67460
  PIVOT_TABLE_CONFIG,
67461
+ ChartTerms,
66955
67462
  };
66956
67463
 
66957
67464
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
66958
67465
 
66959
67466
 
66960
- __info__.version = "17.4.0-alpha.5";
66961
- __info__.date = "2024-06-14T10:01:40.605Z";
66962
- __info__.hash = "9ceed96";
67467
+ __info__.version = "17.4.0-alpha.6";
67468
+ __info__.date = "2024-06-19T13:46:27.157Z";
67469
+ __info__.hash = "a4f22e4";