@odoo/o-spreadsheet 17.4.0-alpha.1 → 17.4.0-alpha.2

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.1
7
- * @date 2024-06-03T15:30:28.283Z
8
- * @hash cb56c37
6
+ * @version 17.4.0-alpha.2
7
+ * @date 2024-06-06T13:31:21.327Z
8
+ * @hash e07794d
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -18376,6 +18376,131 @@ stores.inject(MyMetaStore, storeInstance);
18376
18376
  const supportedPivotExplodedFormulaRegistry = new Registry();
18377
18377
  supportedPivotExplodedFormulaRegistry.add("SPREADSHEET", false);
18378
18378
 
18379
+ const AGGREGATOR_NAMES = {
18380
+ count: _t("Count"),
18381
+ count_distinct: _t("Count Distinct"),
18382
+ bool_and: _t("Boolean And"),
18383
+ bool_or: _t("Boolean Or"),
18384
+ max: _t("Maximum"),
18385
+ min: _t("Minimum"),
18386
+ avg: _t("Average"),
18387
+ sum: _t("Sum"),
18388
+ };
18389
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
18390
+ const AGGREGATORS_BY_FIELD_TYPE = {
18391
+ integer: NUMBER_CHAR_AGGREGATORS,
18392
+ char: NUMBER_CHAR_AGGREGATORS,
18393
+ //TODO Support for date and boolean
18394
+ };
18395
+ const AGGREGATORS = {};
18396
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
18397
+ AGGREGATORS[type] = {};
18398
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
18399
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
18400
+ }
18401
+ }
18402
+ const AGGREGATORS_FN = {
18403
+ count: {
18404
+ fn: (args) => countAny([args]),
18405
+ format: () => "0",
18406
+ },
18407
+ count_distinct: {
18408
+ fn: (args) => countUnique([args]),
18409
+ format: () => "0",
18410
+ },
18411
+ bool_and: {
18412
+ fn: (args) => boolAnd([args]).result,
18413
+ format: () => undefined,
18414
+ },
18415
+ bool_or: {
18416
+ fn: (args) => boolOr([args]).result,
18417
+ format: () => undefined,
18418
+ },
18419
+ max: {
18420
+ fn: (args, locale) => max([args], locale),
18421
+ format: inferFormat,
18422
+ },
18423
+ min: {
18424
+ fn: (args, locale) => min([args], locale),
18425
+ format: inferFormat,
18426
+ },
18427
+ avg: {
18428
+ fn: (args, locale) => average([args], locale),
18429
+ format: inferFormat,
18430
+ },
18431
+ sum: {
18432
+ fn: (args, locale) => sum([args], locale),
18433
+ format: inferFormat,
18434
+ },
18435
+ };
18436
+ /**
18437
+ * Build a pivot formula expression
18438
+ */
18439
+ function makePivotFormula(formula, args) {
18440
+ return `=${formula}(${args
18441
+ .map((arg) => {
18442
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
18443
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
18444
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
18445
+ })
18446
+ .join(",")})`;
18447
+ }
18448
+ /**
18449
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
18450
+ * in this object
18451
+ * If the object has no keys, return 0
18452
+ *
18453
+ */
18454
+ function getMaxObjectId(o) {
18455
+ const keys = Object.keys(o);
18456
+ if (!keys.length) {
18457
+ return 0;
18458
+ }
18459
+ const nums = keys.map((id) => parseInt(id, 10));
18460
+ const max = Math.max(...nums);
18461
+ return max;
18462
+ }
18463
+ const ALL_PERIODS = {
18464
+ year: _t("Year"),
18465
+ quarter: _t("Quarter"),
18466
+ month: _t("Month"),
18467
+ week: _t("Week"),
18468
+ day: _t("Day"),
18469
+ year_number: _t("Year"),
18470
+ quarter_number: _t("Quarter"),
18471
+ month_number: _t("Month"),
18472
+ iso_week_number: _t("Week"),
18473
+ day_of_month: _t("Day of Month"),
18474
+ };
18475
+ const DATE_FIELDS = ["date", "datetime"];
18476
+ /**
18477
+ * Parse a dimension string into a pivot dimension definition.
18478
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
18479
+ */
18480
+ function parseDimension(dimension) {
18481
+ const [name, granularity] = dimension.split(":");
18482
+ if (granularity) {
18483
+ return { name, granularity };
18484
+ }
18485
+ return { name };
18486
+ }
18487
+ function isDateField(field) {
18488
+ return DATE_FIELDS.includes(field.type);
18489
+ }
18490
+ function toPivotDomain(domainStr) {
18491
+ if (domainStr.length % 2 !== 0) {
18492
+ throw new Error("Invalid domain: odd number of elements");
18493
+ }
18494
+ const domain = [];
18495
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
18496
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
18497
+ }
18498
+ return domain;
18499
+ }
18500
+ function flatPivotDomain(domain) {
18501
+ return domain.flatMap((arg) => [arg.field, arg.value]);
18502
+ }
18503
+
18379
18504
  /**
18380
18505
  * Get the pivot ID from the formula pivot ID.
18381
18506
  */
@@ -18855,13 +18980,13 @@ stores.inject(MyMetaStore, storeInstance);
18855
18980
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18856
18981
  arg("domain_value (string,optional,repeating)", _t("Value.")),
18857
18982
  ],
18858
- compute: function (formulaId, measureName, ...domain) {
18983
+ compute: function (formulaId, measureName, ...domainArgs) {
18859
18984
  const _pivotFormulaId = toString(formulaId);
18860
- const measure = toString(measureName);
18861
- const domainArgs = domain.map(toString);
18985
+ const _measure = toString(measureName);
18986
+ const _domainArgs = domainArgs.map(toString);
18862
18987
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
18863
- assertMeasureExist(pivotId, measure, this.getters);
18864
- assertDomainLength(domainArgs);
18988
+ assertMeasureExist(pivotId, _measure, this.getters);
18989
+ assertDomainLength(_domainArgs);
18865
18990
  const pivot = this.getters.getPivot(pivotId);
18866
18991
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18867
18992
  if (!supportedPivotExplodedFormulaRegistry.get(coreDefinition.type)) {
@@ -18875,8 +19000,9 @@ stores.inject(MyMetaStore, storeInstance);
18875
19000
  if (error) {
18876
19001
  return error;
18877
19002
  }
18878
- const { value, format } = pivot.getPivotCellValueAndFormat(measure, domainArgs);
18879
- if (!value && !this.getters.areDomainArgsFieldsValid(pivotId, domainArgs)) {
19003
+ const domain = toPivotDomain(_domainArgs);
19004
+ const { value, format } = pivot.getPivotCellValueAndFormat(_measure, domain);
19005
+ if (!value && !this.getters.areDomainArgsFieldsValid(pivotId, domain)) {
18880
19006
  return {
18881
19007
  value: CellErrorType.GenericError,
18882
19008
  message: _t("Dimensions don't match the pivot definition"),
@@ -18893,11 +19019,11 @@ stores.inject(MyMetaStore, storeInstance);
18893
19019
  arg("domain_field_name (string,optional,repeating)", _t("Field name.")),
18894
19020
  arg("domain_value (string,optional,repeating)", _t("Value.")),
18895
19021
  ],
18896
- compute: function (pivotId, ...domain) {
19022
+ compute: function (pivotId, ...domainArgs) {
18897
19023
  const _pivotFormulaId = toString(pivotId);
18898
- const domainArgs = domain.map(toString);
19024
+ const _domainArgs = domainArgs.map(toString);
18899
19025
  const _pivotId = getPivotId(_pivotFormulaId, this.getters);
18900
- assertDomainLength(domainArgs);
19026
+ assertDomainLength(_domainArgs);
18901
19027
  const pivot = this.getters.getPivot(_pivotId);
18902
19028
  const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
18903
19029
  if (!supportedPivotExplodedFormulaRegistry.get(coreDefinition.type)) {
@@ -18911,18 +19037,23 @@ stores.inject(MyMetaStore, storeInstance);
18911
19037
  if (error) {
18912
19038
  return error;
18913
19039
  }
18914
- const fieldName = domainArgs.at(-2);
18915
- const valueArg = domainArgs.at(-1);
18916
- if (!this.getters.areDomainArgsFieldsValid(_pivotId, fieldName === "measure" ? domainArgs.slice(0, -2) : domainArgs)) {
19040
+ const domain = toPivotDomain(_domainArgs);
19041
+ const lastNode = domain.at(-1);
19042
+ if (!this.getters.areDomainArgsFieldsValid(_pivotId, lastNode?.field === "measure" ? domain.slice(0, -1) : domain)) {
18917
19043
  return {
18918
19044
  value: CellErrorType.GenericError,
18919
19045
  message: _t("Dimensions don't match the pivot definition"),
18920
19046
  };
18921
19047
  }
18922
- const { value, format } = pivot.getPivotHeaderValueAndFormat(domainArgs);
19048
+ if (lastNode?.field === "measure") {
19049
+ return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
19050
+ }
19051
+ const { value, format } = pivot.getPivotHeaderValueAndFormat(domain);
18923
19052
  return {
18924
19053
  value,
18925
- format: !fieldName || fieldName === "measure" || valueArg === "false" ? undefined : format,
19054
+ format: !lastNode || lastNode.field === "measure" || lastNode.value === "false"
19055
+ ? undefined
19056
+ : format,
18926
19057
  };
18927
19058
  },
18928
19059
  returns: ["NUMBER", "STRING"],
@@ -18935,11 +19066,14 @@ stores.inject(MyMetaStore, storeInstance);
18935
19066
  arg("include_total (boolean, default=TRUE)", _t("Whether to include total/sub-totals or not.")),
18936
19067
  arg("include_column_titles (boolean, default=TRUE)", _t("Whether to include the column titles or not.")),
18937
19068
  ],
18938
- compute: function (pivotId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }) {
18939
- const _pivotFormulaId = toString(pivotId);
18940
- const _pivotId = getPivotId(_pivotFormulaId, this.getters);
18941
- const pivot = this.getters.getPivot(_pivotId);
18942
- const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
19069
+ compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }) {
19070
+ const _pivotFormulaId = toString(pivotFormulaId);
19071
+ const _rowCount = toNumber(rowCount, this.locale);
19072
+ const _includeColumnHeaders = toBoolean(includeColumnHeaders);
19073
+ const _includedTotal = toBoolean(includeTotal);
19074
+ const pivotId = getPivotId(_pivotFormulaId, this.getters);
19075
+ const pivot = this.getters.getPivot(pivotId);
19076
+ const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
18943
19077
  addPivotDependencies(this, coreDefinition);
18944
19078
  pivot.init({ reload: pivot.needsReevaluation });
18945
19079
  const error = pivot.assertIsValid({ throwOnError: false });
@@ -18947,11 +19081,9 @@ stores.inject(MyMetaStore, storeInstance);
18947
19081
  return error;
18948
19082
  }
18949
19083
  const table = pivot.getTableStructure();
18950
- const _includeColumnHeaders = toBoolean(includeColumnHeaders);
18951
- const cells = table.getPivotCells(toBoolean(includeTotal), _includeColumnHeaders);
19084
+ const cells = table.getPivotCells(_includedTotal, _includeColumnHeaders);
18952
19085
  const headerRows = _includeColumnHeaders ? table.columns.length : 0;
18953
- const pivotTitle = this.getters.getPivotDisplayName(_pivotId);
18954
- const _rowCount = toNumber(rowCount, this.locale);
19086
+ const pivotTitle = this.getters.getPivotDisplayName(pivotId);
18955
19087
  if (_rowCount < 0) {
18956
19088
  throw new EvaluationError(_t("The number of rows must be positive."));
18957
19089
  }
@@ -18966,17 +19098,23 @@ stores.inject(MyMetaStore, storeInstance);
18966
19098
  result[col] = [];
18967
19099
  for (const row of tableRows) {
18968
19100
  const pivotCell = cells[col][row];
18969
- if (!pivotCell.domain) {
18970
- result[col].push({ value: "", format: undefined });
18971
- }
18972
- else if (pivotCell.isHeader) {
18973
- result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
18974
- }
18975
- else {
18976
- if (!pivotCell.measure) {
18977
- throw new Error("Measure is missing");
18978
- }
18979
- result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
19101
+ switch (pivotCell.type) {
19102
+ case "EMPTY":
19103
+ result[col].push({ value: "" });
19104
+ break;
19105
+ case "HEADER":
19106
+ const domain = pivotCell.domain;
19107
+ const lastNode = domain.at(-1);
19108
+ if (lastNode?.field === "measure") {
19109
+ result[col].push(pivot.getPivotMeasureValue(toString(lastNode.value), domain));
19110
+ }
19111
+ else {
19112
+ result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
19113
+ }
19114
+ break;
19115
+ case "VALUE":
19116
+ result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
19117
+ break;
18980
19118
  }
18981
19119
  }
18982
19120
  }
@@ -20901,8 +21039,15 @@ stores.inject(MyMetaStore, storeInstance);
20901
21039
  }
20902
21040
  onPaste(ev) {
20903
21041
  if (this.composerStore.editionMode !== "inactive") {
21042
+ // let the browser clipboard work
20904
21043
  ev.stopPropagation();
20905
21044
  }
21045
+ else {
21046
+ // the user meant to paste in the sheet, not open the composer with the pasted content
21047
+ // While we're not editing, we still have the focus and should therefore prevent
21048
+ // the native "paste" to occur.
21049
+ ev.preventDefault();
21050
+ }
20906
21051
  }
20907
21052
  /*
20908
21053
  * Triggered automatically by the content-editable between the keydown and key up
@@ -20911,9 +21056,6 @@ stores.inject(MyMetaStore, storeInstance);
20911
21056
  if (!this.shouldProcessInputEvents) {
20912
21057
  return;
20913
21058
  }
20914
- if (ev.inputType === "insertFromPaste" && this.composerStore.editionMode === "inactive") {
20915
- return;
20916
- }
20917
21059
  ev.stopPropagation();
20918
21060
  let content;
20919
21061
  if (this.composerStore.editionMode === "inactive") {
@@ -21566,132 +21708,8 @@ stores.inject(MyMetaStore, storeInstance);
21566
21708
  }
21567
21709
 
21568
21710
  const PIVOT_FUNCTIONS = ["PIVOT.VALUE", "PIVOT.HEADER", "PIVOT"];
21569
- const AGGREGATOR_NAMES = {
21570
- count: _t("Count"),
21571
- count_distinct: _t("Count Distinct"),
21572
- bool_and: _t("Boolean And"),
21573
- bool_or: _t("Boolean Or"),
21574
- max: _t("Maximum"),
21575
- min: _t("Minimum"),
21576
- avg: _t("Average"),
21577
- sum: _t("Sum"),
21578
- };
21579
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
21580
- const AGGREGATORS_BY_FIELD_TYPE = {
21581
- integer: NUMBER_CHAR_AGGREGATORS,
21582
- char: NUMBER_CHAR_AGGREGATORS,
21583
- //TODO Support for date and boolean
21584
- };
21585
- const AGGREGATORS = {};
21586
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
21587
- AGGREGATORS[type] = {};
21588
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
21589
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
21590
- }
21591
- }
21592
- const AGGREGATORS_FN = {
21593
- count: {
21594
- fn: (args) => countAny([args]),
21595
- format: () => "0",
21596
- },
21597
- count_distinct: {
21598
- fn: (args) => countUnique([args]),
21599
- format: () => "0",
21600
- },
21601
- bool_and: {
21602
- fn: (args) => boolAnd([args]).result,
21603
- format: () => undefined,
21604
- },
21605
- bool_or: {
21606
- fn: (args) => boolOr([args]).result,
21607
- format: () => undefined,
21608
- },
21609
- max: {
21610
- fn: (args, locale) => max([args], locale),
21611
- format: inferFormat,
21612
- },
21613
- min: {
21614
- fn: (args, locale) => min([args], locale),
21615
- format: inferFormat,
21616
- },
21617
- avg: {
21618
- fn: (args, locale) => average([args], locale),
21619
- format: inferFormat,
21620
- },
21621
- sum: {
21622
- fn: (args, locale) => sum([args], locale),
21623
- format: inferFormat,
21624
- },
21625
- };
21626
- /**
21627
- * Build a pivot formula expression
21628
- */
21629
- function makePivotFormula(formula, args) {
21630
- return `=${formula}(${args
21631
- .map((arg) => {
21632
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
21633
- const convertToNumber = typeof arg == "number" || stringIsNumber;
21634
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
21635
- })
21636
- .join(",")})`;
21637
- }
21638
- /**
21639
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
21640
- * in this object
21641
- * If the object has no keys, return 0
21642
- *
21643
- */
21644
- function getMaxObjectId(o) {
21645
- const keys = Object.keys(o);
21646
- if (!keys.length) {
21647
- return 0;
21648
- }
21649
- const nums = keys.map((id) => parseInt(id, 10));
21650
- const max = Math.max(...nums);
21651
- return max;
21652
- }
21653
- /**
21654
- * Get the first Pivot function description of the given formula.
21655
- */
21656
- function getFirstPivotFunction(tokens) {
21657
- return getFunctionsFromTokens(tokens, PIVOT_FUNCTIONS)[0];
21658
- }
21659
- /**
21660
- * Parse a spreadsheet formula and detect the number of PIVOT functions that are
21661
- * present in the given formula.
21662
- */
21663
- function getNumberOfPivotFunctions(tokens) {
21664
- return getFunctionsFromTokens(tokens, PIVOT_FUNCTIONS).length;
21665
- }
21666
- const ALL_PERIODS = {
21667
- year: _t("Year"),
21668
- quarter: _t("Quarter"),
21669
- month: _t("Month"),
21670
- week: _t("Week"),
21671
- day: _t("Day"),
21672
- year_number: _t("Year"),
21673
- quarter_number: _t("Quarter"),
21674
- month_number: _t("Month"),
21675
- iso_week_number: _t("Week"),
21676
- day_of_month: _t("Day of Month"),
21677
- };
21678
- const DATE_FIELDS = ["date", "datetime"];
21679
- /**
21680
- * Parse a dimension string into a pivot dimension definition.
21681
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
21682
- */
21683
- function parseDimension(dimension) {
21684
- const [name, granularity] = dimension.split(":");
21685
- if (granularity) {
21686
- return { name, granularity };
21687
- }
21688
- return { name };
21689
- }
21690
- function isDateField(field) {
21691
- return DATE_FIELDS.includes(field.type);
21692
- }
21693
21711
  /**
21694
- * Create a proposal entry for the compose autocomplete
21712
+ * Create a proposal entry for the compose autowcomplete
21695
21713
  * to insert a field name string in a formula.
21696
21714
  */
21697
21715
  function makeFieldProposal(field, granularity) {
@@ -21747,15 +21765,18 @@ stores.inject(MyMetaStore, storeInstance);
21747
21765
  }
21748
21766
  return idAst.value;
21749
21767
  }
21750
- function toDomainArgs(domainStr) {
21751
- if (domainStr.length % 2 !== 0) {
21752
- throw new Error("Invalid domain: odd number of elements");
21753
- }
21754
- const domain = [];
21755
- for (let i = 0; i < domainStr.length - 1; i += 2) {
21756
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
21757
- }
21758
- return domain;
21768
+ /**
21769
+ * Get the first Pivot function description of the given formula.
21770
+ */
21771
+ function getFirstPivotFunction(tokens) {
21772
+ return getFunctionsFromTokens(tokens, PIVOT_FUNCTIONS)[0];
21773
+ }
21774
+ /**
21775
+ * Parse a spreadsheet formula and detect the number of PIVOT functions that are
21776
+ * present in the given formula.
21777
+ */
21778
+ function getNumberOfPivotFunctions(tokens) {
21779
+ return getFunctionsFromTokens(tokens, PIVOT_FUNCTIONS).length;
21759
21780
  }
21760
21781
 
21761
21782
  autoCompleteProviders.add("pivot_ids", {
@@ -32783,7 +32804,7 @@ stores.inject(MyMetaStore, storeInstance);
32783
32804
  vertical-align: middle;
32784
32805
  }
32785
32806
  .o_cf_radio_item {
32786
- margin-right: 10%;
32807
+ margin-right: 30px;
32787
32808
  }
32788
32809
  .radio input:checked {
32789
32810
  color: #e9ecef;
@@ -32803,6 +32824,9 @@ stores.inject(MyMetaStore, storeInstance);
32803
32824
  }
32804
32825
  margin-top: 10px;
32805
32826
  display: flex;
32827
+ .form-check {
32828
+ padding-left: 1rem;
32829
+ }
32806
32830
  }
32807
32831
  .o-section-subtitle:first-child {
32808
32832
  margin-top: 0px;
@@ -32811,7 +32835,7 @@ stores.inject(MyMetaStore, storeInstance);
32811
32835
  font-size: 12px;
32812
32836
  line-height: 1.5;
32813
32837
  .o-selection-cf {
32814
- margin-bottom: 3%;
32838
+ margin-bottom: 9px;
32815
32839
  }
32816
32840
  .o-cell-content {
32817
32841
  font-size: 12px;
@@ -32852,7 +32876,7 @@ stores.inject(MyMetaStore, storeInstance);
32852
32876
  width: 100%;
32853
32877
  }
32854
32878
  .o-threshold-value {
32855
- margin-left: 2%;
32879
+ margin-left: 6px;
32856
32880
  width: 20%;
32857
32881
  min-width: 0px; // input overflows in Firefox otherwise
32858
32882
  }
@@ -32881,8 +32905,8 @@ stores.inject(MyMetaStore, storeInstance);
32881
32905
  justify-content: space-between;
32882
32906
  .o-cf-icon {
32883
32907
  display: inline;
32884
- margin-left: 1%;
32885
- margin-right: 1%;
32908
+ margin-left: 3px;
32909
+ margin-right: 3px;
32886
32910
  }
32887
32911
  svg {
32888
32912
  vertical-align: baseline;
@@ -32905,7 +32929,7 @@ stores.inject(MyMetaStore, storeInstance);
32905
32929
  }
32906
32930
  table {
32907
32931
  table-layout: fixed;
32908
- margin-top: 2%;
32932
+ margin-top: 6px;
32909
32933
  display: table;
32910
32934
  text-align: left;
32911
32935
  font-size: 12px;
@@ -32935,8 +32959,8 @@ stores.inject(MyMetaStore, storeInstance);
32935
32959
  }
32936
32960
  }
32937
32961
  .o-cf-iconset-reverse {
32938
- margin-bottom: 2%;
32939
- margin-top: 2%;
32962
+ margin-bottom: 6px;
32963
+ margin-top: 6px;
32940
32964
  .o-cf-label {
32941
32965
  display: inline-block;
32942
32966
  vertical-align: bottom;
@@ -34643,6 +34667,10 @@ stores.inject(MyMetaStore, storeInstance);
34643
34667
  select > option {
34644
34668
  background-color: white;
34645
34669
  }
34670
+
34671
+ .pivot-dim-operator-label {
34672
+ min-width: 120px;
34673
+ }
34646
34674
  }
34647
34675
  `;
34648
34676
  class PivotDimension extends owl.Component {
@@ -34883,7 +34911,7 @@ stores.inject(MyMetaStore, storeInstance);
34883
34911
  getMeasure(name) {
34884
34912
  const measure = this.measures.find((measure) => measure.name === name);
34885
34913
  if (!measure) {
34886
- throw new EvaluationError(_t("Field %s does not exist", name));
34914
+ throw new EvaluationError(_t("Field %s is not a measure", name));
34887
34915
  }
34888
34916
  return measure;
34889
34917
  }
@@ -35009,10 +35037,9 @@ stores.inject(MyMetaStore, storeInstance);
35009
35037
  columns;
35010
35038
  rows;
35011
35039
  measures;
35012
- rowTitle;
35013
35040
  maxIndent;
35014
35041
  pivotCells = {};
35015
- constructor(columns, rows, measures, rowTitle = "") {
35042
+ constructor(columns, rows, measures) {
35016
35043
  this.columns = columns.map((row) => {
35017
35044
  // offset in the pivot table
35018
35045
  // starts at 1 because the first column is the row title
@@ -35025,7 +35052,6 @@ stores.inject(MyMetaStore, storeInstance);
35025
35052
  });
35026
35053
  this.rows = rows;
35027
35054
  this.measures = measures;
35028
- this.rowTitle = rowTitle;
35029
35055
  this.maxIndent = Math.max(...this.rows.map((row) => row.indent));
35030
35056
  }
35031
35057
  /**
@@ -35067,26 +35093,23 @@ stores.inject(MyMetaStore, storeInstance);
35067
35093
  }
35068
35094
  getPivotCell(col, row, includeTotal = true) {
35069
35095
  const colHeadersHeight = this.columns.length;
35070
- if (col === 0 && row === colHeadersHeight - 1) {
35071
- return { content: this.rowTitle, isHeader: true };
35072
- }
35073
- else if (row <= colHeadersHeight - 1) {
35096
+ if (row <= colHeadersHeight - 1) {
35074
35097
  const domain = this.getColHeaderDomain(col, row);
35075
- return { domain, isHeader: true };
35098
+ return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35076
35099
  }
35077
35100
  else if (col === 0) {
35078
35101
  const rowIndex = row - colHeadersHeight;
35079
35102
  const domain = this.getRowDomain(rowIndex);
35080
- return { domain, isHeader: true };
35103
+ return { type: "HEADER", domain };
35081
35104
  }
35082
35105
  else {
35083
35106
  const rowIndex = row - colHeadersHeight;
35084
35107
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35085
- return { isHeader: false };
35108
+ return { type: "EMPTY" };
35086
35109
  }
35087
35110
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35088
35111
  const measure = this.getColMeasure(col);
35089
- return { domain, isHeader: false, measure };
35112
+ return { type: "VALUE", domain, measure };
35090
35113
  }
35091
35114
  }
35092
35115
  getColHeaderDomain(col, row) {
@@ -35099,24 +35122,32 @@ stores.inject(MyMetaStore, storeInstance);
35099
35122
  return undefined;
35100
35123
  }
35101
35124
  for (let i = 0; i < pivotCol.fields.length; i++) {
35102
- domain.push(pivotCol.fields[i]);
35103
- domain.push(pivotCol.values[i]);
35125
+ domain.push({
35126
+ field: pivotCol.fields[i],
35127
+ value: pivotCol.values[i],
35128
+ });
35104
35129
  }
35105
35130
  return domain;
35106
35131
  }
35107
35132
  getColDomain(col) {
35108
35133
  const domain = this.getColHeaderDomain(col, this.columns.length - 1);
35109
- return domain ? domain.slice(0, -2) : []; // slice: remove measure and value
35134
+ return domain ? domain.slice(0, -1) : []; // slice: remove measure and value
35110
35135
  }
35111
35136
  getColMeasure(col) {
35112
35137
  const domain = this.getColHeaderDomain(col, this.columns.length - 1);
35113
- return domain?.at(-1);
35138
+ const measure = domain?.at(-1)?.value;
35139
+ if (measure === undefined) {
35140
+ throw new Error("Measure isd missing");
35141
+ }
35142
+ return measure.toString();
35114
35143
  }
35115
35144
  getRowDomain(row) {
35116
35145
  const domain = [];
35117
35146
  for (let i = 0; i < this.rows[row].fields.length; i++) {
35118
- domain.push(this.rows[row].fields[i]);
35119
- domain.push(this.rows[row].values[i]);
35147
+ domain.push({
35148
+ field: this.rows[row].fields[i],
35149
+ value: this.rows[row].values[i],
35150
+ });
35120
35151
  }
35121
35152
  return domain;
35122
35153
  }
@@ -35125,7 +35156,6 @@ stores.inject(MyMetaStore, storeInstance);
35125
35156
  cols: this.columns,
35126
35157
  rows: this.rows,
35127
35158
  measures: this.measures,
35128
- rowTitle: this.rowTitle,
35129
35159
  };
35130
35160
  }
35131
35161
  }
@@ -35145,8 +35175,7 @@ stores.inject(MyMetaStore, storeInstance);
35145
35175
  indent: 0,
35146
35176
  });
35147
35177
  const measureNames = definition.measures.map((m) => m.name);
35148
- const rowTitle = rows.length > 0 ? rows[0].values[0] : "";
35149
- return new SpreadsheetPivotTable(cols, rows, measureNames, rowTitle);
35178
+ return new SpreadsheetPivotTable(cols, rows, measureNames);
35150
35179
  }
35151
35180
  // -----------------------------------------------------------------------------
35152
35181
  // ROWS
@@ -35489,7 +35518,10 @@ stores.inject(MyMetaStore, storeInstance);
35489
35518
  }
35490
35519
  get definition() {
35491
35520
  if (!this._definition) {
35492
- throw new Error("Pivot not loaded yet");
35521
+ this.init();
35522
+ }
35523
+ if (!this._definition) {
35524
+ throw new Error("Pivot definition should be defined at this point.");
35493
35525
  }
35494
35526
  return this._definition;
35495
35527
  }
@@ -35538,15 +35570,16 @@ stores.inject(MyMetaStore, storeInstance);
35538
35570
  getMeasure(name) {
35539
35571
  return this.definition.getMeasure(name);
35540
35572
  }
35541
- getPivotHeaderValueAndFormat(domainStr) {
35542
- const domain = toDomainArgs(domainStr);
35573
+ getPivotMeasureValue(name) {
35574
+ return {
35575
+ value: this.getMeasure(name).displayName,
35576
+ };
35577
+ }
35578
+ getPivotHeaderValueAndFormat(domain) {
35543
35579
  const lastNode = domain.at(-1);
35544
35580
  if (!lastNode) {
35545
35581
  return { value: _t("Total") };
35546
35582
  }
35547
- if (lastNode.field === "measure") {
35548
- return { value: this.getMeasure(lastNode.value).displayName };
35549
- }
35550
35583
  const dimension = this.getDimension(lastNode.field);
35551
35584
  const cells = this.filterDataEntriesFromDomain(this.dataEntries, domain);
35552
35585
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
@@ -35574,8 +35607,7 @@ stores.inject(MyMetaStore, storeInstance);
35574
35607
  format: finalCell.format,
35575
35608
  };
35576
35609
  }
35577
- getPivotCellValueAndFormat(measure, domainStr) {
35578
- const domain = toDomainArgs(domainStr);
35610
+ getPivotCellValueAndFormat(measure, domain) {
35579
35611
  const dataEntries = this.filterDataEntriesFromDomain(this.dataEntries, domain);
35580
35612
  if (dataEntries.length === 0) {
35581
35613
  return { value: "" };
@@ -35976,7 +36008,6 @@ stores.inject(MyMetaStore, storeInstance);
35976
36008
  state;
35977
36009
  setup() {
35978
36010
  this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
35979
- this.pivot.init();
35980
36011
  this.state = owl.useState({
35981
36012
  range: undefined,
35982
36013
  rangeHasChanged: false,
@@ -36853,7 +36884,7 @@ stores.inject(MyMetaStore, storeInstance);
36853
36884
  }
36854
36885
 
36855
36886
  .o-table-style-list-item {
36856
- padding: 3px;
36887
+ padding: 3px 2px;
36857
36888
  margin: 2px 1px;
36858
36889
 
36859
36890
  .o-table-style-picker-preview {
@@ -36874,10 +36905,10 @@ stores.inject(MyMetaStore, storeInstance);
36874
36905
  const styles = Object.keys(allStyles).filter((key) => allStyles[key].category === selectedStyleCategory);
36875
36906
  const selectedStyleIndex = styles.indexOf(this.props.table.config.styleId);
36876
36907
  if (selectedStyleIndex === -1) {
36877
- return styles.slice(0, 4);
36908
+ return selectedStyleIndex;
36878
36909
  }
36879
36910
  const index = Math.floor(selectedStyleIndex / 4) * 4;
36880
- return styles.slice(index, index + 4);
36911
+ return styles.slice(index);
36881
36912
  }
36882
36913
  onStylePicked(styleId) {
36883
36914
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -37917,10 +37948,7 @@ stores.inject(MyMetaStore, storeInstance);
37917
37948
  }
37918
37949
  get containerStyle() {
37919
37950
  if (this.composerStore.editionMode === "inactive") {
37920
- return `
37921
- position: absolute;
37922
- z-index: -1000;
37923
- `;
37951
+ return `z-index: -1000;`;
37924
37952
  }
37925
37953
  const isFormula = this.composerStore.currentContent.startsWith("=");
37926
37954
  const cell = this.env.model.getters.getActiveCell();
@@ -40764,10 +40792,13 @@ stores.inject(MyMetaStore, storeInstance);
40764
40792
  }
40765
40793
  }
40766
40794
 
40795
+ const DEFAULT_SIDE_PANEL_SIZE = 350;
40796
+ const MIN_SHEET_VIEW_WIDTH = 150;
40767
40797
  class SidePanelStore extends SpreadsheetStore {
40768
- mutators = ["open", "toggle", "close"];
40798
+ mutators = ["open", "toggle", "close", "changePanelSize", "resetPanelSize"];
40769
40799
  initialPanelProps = {};
40770
40800
  componentTag = "";
40801
+ panelSize = DEFAULT_SIDE_PANEL_SIZE;
40771
40802
  get isOpen() {
40772
40803
  if (!this.componentTag) {
40773
40804
  return false;
@@ -40812,6 +40843,20 @@ stores.inject(MyMetaStore, storeInstance);
40812
40843
  this.initialPanelProps = {};
40813
40844
  this.componentTag = "";
40814
40845
  }
40846
+ changePanelSize(size, spreadsheetElWidth) {
40847
+ if (size < DEFAULT_SIDE_PANEL_SIZE) {
40848
+ this.panelSize = DEFAULT_SIDE_PANEL_SIZE;
40849
+ }
40850
+ else if (size > spreadsheetElWidth - MIN_SHEET_VIEW_WIDTH) {
40851
+ this.panelSize = Math.max(spreadsheetElWidth - MIN_SHEET_VIEW_WIDTH, DEFAULT_SIDE_PANEL_SIZE);
40852
+ }
40853
+ else {
40854
+ this.panelSize = size;
40855
+ }
40856
+ }
40857
+ resetPanelSize() {
40858
+ this.panelSize = DEFAULT_SIDE_PANEL_SIZE;
40859
+ }
40815
40860
  computeState(componentTag, panelProps) {
40816
40861
  const customComputeState = sidePanelRegistry.get(componentTag).computeState;
40817
40862
  if (!customComputeState) {
@@ -41932,7 +41977,7 @@ stores.inject(MyMetaStore, storeInstance);
41932
41977
  }
41933
41978
  }
41934
41979
 
41935
- const SUPPORTED_BORDER_STYLES = ["thin"];
41980
+ const SUPPORTED_BORDER_STYLES = ["thin", "medium", "thick", "dashed", "dotted"];
41936
41981
  const SUPPORTED_HORIZONTAL_ALIGNMENTS = [
41937
41982
  "general",
41938
41983
  "left",
@@ -51071,8 +51116,8 @@ stores.inject(MyMetaStore, storeInstance);
51071
51116
  case "INSERT_PIVOT": {
51072
51117
  const { sheetId, col, row, pivotId, table } = cmd;
51073
51118
  const position = { sheetId, col, row };
51074
- const { cols, rows, measures, rowTitle } = table;
51075
- const spTable = new SpreadsheetPivotTable(cols, rows, measures, rowTitle);
51119
+ const { cols, rows, measures } = table;
51120
+ const spTable = new SpreadsheetPivotTable(cols, rows, measures);
51076
51121
  const formulaId = this.getPivotFormulaId(pivotId);
51077
51122
  this.insertPivot(position, formulaId, spTable);
51078
51123
  break;
@@ -51201,13 +51246,18 @@ stores.inject(MyMetaStore, storeInstance);
51201
51246
  }
51202
51247
  }
51203
51248
  addPivotFormula(position, formulaId, pivotCell) {
51204
- const formula = pivotCell.isHeader ? "PIVOT.HEADER" : "PIVOT.VALUE";
51205
- const args = pivotCell.domain
51206
- ? [formulaId, pivotCell.measure, ...pivotCell.domain].filter(isDefined)
51207
- : undefined;
51249
+ let content = undefined;
51250
+ switch (pivotCell.type) {
51251
+ case "HEADER":
51252
+ content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51253
+ break;
51254
+ case "VALUE":
51255
+ content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51256
+ break;
51257
+ }
51208
51258
  this.dispatch("UPDATE_CELL", {
51209
51259
  ...position,
51210
- content: pivotCell.content || (args ? makePivotFormula(formula, args) : undefined),
51260
+ content,
51211
51261
  });
51212
51262
  }
51213
51263
  getPivotCore(pivotId) {
@@ -51226,7 +51276,7 @@ stores.inject(MyMetaStore, storeInstance);
51226
51276
  import(data) {
51227
51277
  if (data.pivots) {
51228
51278
  for (const [id, pivot] of Object.entries(data.pivots)) {
51229
- this.addPivot(id, deepCopy(pivot), pivot.formulaId);
51279
+ this.addPivot(id, pivot, pivot.formulaId);
51230
51280
  }
51231
51281
  }
51232
51282
  this.history.update("nextFormulaId", data.pivotNextId || getMaxObjectId(this.pivots) + 1);
@@ -54567,15 +54617,18 @@ stores.inject(MyMetaStore, storeInstance);
54567
54617
  const pivotCol = position.col - mainPosition.col;
54568
54618
  const pivotRow = position.row - mainPosition.row;
54569
54619
  const pivotCell = pivotCells[pivotCol][pivotRow];
54620
+ if (pivotCell.type === "EMPTY") {
54621
+ return undefined;
54622
+ }
54570
54623
  const domain = pivotCell.domain;
54571
- if (domain?.at(-2) === "measure") {
54572
- return domain.slice(0, -2);
54624
+ if (domain.at(-1)?.field === "measure") {
54625
+ return domain.slice(0, -1);
54573
54626
  }
54574
54627
  return domain;
54575
54628
  }
54576
- const domain = args.slice(functionName === "PIVOT.VALUE" ? 2 : 1);
54577
- if (domain.at(-2) === "measure") {
54578
- return domain.slice(0, -2);
54629
+ const domain = toPivotDomain(args.slice(functionName === "PIVOT.VALUE" ? 2 : 1).map((x) => `${x}`));
54630
+ if (domain.at(-1)?.field === "measure") {
54631
+ return domain.slice(0, -1);
54579
54632
  }
54580
54633
  return domain;
54581
54634
  }
@@ -54590,9 +54643,9 @@ stores.inject(MyMetaStore, storeInstance);
54590
54643
  * a pivot function are valid according to the pivot definition.
54591
54644
  * e.g. =PIVOT.VALUE(1,"revenue","country_id",...,"create_date:month",...,"source_id",...)
54592
54645
  */
54593
- areDomainArgsFieldsValid(pivotId, domainArgs) {
54594
- const dimensions = domainArgs
54595
- .filter((arg, index) => index % 2 === 0)
54646
+ areDomainArgsFieldsValid(pivotId, domain) {
54647
+ const dimensions = domain
54648
+ .map((node) => node.field)
54596
54649
  .map((name) => (name.startsWith("#") ? name.slice(1) : name));
54597
54650
  let argIndex = 0;
54598
54651
  let definitionIndex = 0;
@@ -61606,6 +61659,10 @@ stores.inject(MyMetaStore, storeInstance);
61606
61659
  }
61607
61660
  }
61608
61661
  }
61662
+ .o-sidePanelBody-container {
61663
+ /* This overwrites the min-height: auto; of flex. Without this, a flex div cannot be smaller than its children */
61664
+ min-height: 0;
61665
+ }
61609
61666
  .o-sidePanelBody {
61610
61667
  overflow: auto;
61611
61668
  width: 100%;
@@ -61684,24 +61741,6 @@ stores.inject(MyMetaStore, storeInstance);
61684
61741
  text-align: left;
61685
61742
  }
61686
61743
 
61687
- .o-inflection {
61688
- table {
61689
- table-layout: fixed;
61690
- margin-top: 2%;
61691
- display: table;
61692
- text-align: left;
61693
- font-size: 12px;
61694
- line-height: 18px;
61695
- width: 100%;
61696
- }
61697
- input,
61698
- select {
61699
- width: 100%;
61700
- height: 100%;
61701
- box-sizing: border-box;
61702
- }
61703
- }
61704
-
61705
61744
  .o-sidePanel-tools {
61706
61745
  color: #333;
61707
61746
  font-size: 13px;
@@ -61720,12 +61759,25 @@ stores.inject(MyMetaStore, storeInstance);
61720
61759
  }
61721
61760
  }
61722
61761
  }
61762
+
61763
+ .o-sidePanel-handle-container {
61764
+ width: 8px;
61765
+ }
61766
+ .o-sidePanel-handle {
61767
+ cursor: col-resize;
61768
+ color: #a9a9a9;
61769
+ .o-icon {
61770
+ height: 25px;
61771
+ margin-left: -5px;
61772
+ }
61773
+ }
61723
61774
  }
61724
61775
  `;
61725
61776
  class SidePanel extends owl.Component {
61726
61777
  static template = "o-spreadsheet-SidePanel";
61727
61778
  static props = {};
61728
61779
  sidePanelStore;
61780
+ spreadsheetRect = useSpreadsheetRect();
61729
61781
  setup() {
61730
61782
  this.sidePanelStore = useStore(SidePanelStore);
61731
61783
  owl.useEffect((isOpen) => {
@@ -61746,6 +61798,20 @@ stores.inject(MyMetaStore, storeInstance);
61746
61798
  ? panel.title(this.env, this.sidePanelStore.panelProps)
61747
61799
  : panel.title;
61748
61800
  }
61801
+ startHandleDrag(ev) {
61802
+ const startingCursor = document.body.style.cursor;
61803
+ const startSize = this.sidePanelStore.panelSize;
61804
+ const startPosition = ev.clientX;
61805
+ const onMouseMove = (ev) => {
61806
+ document.body.style.cursor = "col-resize";
61807
+ const newSize = startSize + startPosition - ev.clientX;
61808
+ this.sidePanelStore.changePanelSize(newSize, this.spreadsheetRect.width);
61809
+ };
61810
+ const cleanUp = () => {
61811
+ document.body.style.cursor = startingCursor;
61812
+ };
61813
+ startDnd(onMouseMove, cleanUp);
61814
+ }
61749
61815
  }
61750
61816
 
61751
61817
  css /* scss */ `
@@ -62529,7 +62595,6 @@ stores.inject(MyMetaStore, storeInstance);
62529
62595
  .o-spreadsheet {
62530
62596
  position: relative;
62531
62597
  display: grid;
62532
- grid-template-columns: auto 350px;
62533
62598
  color: #333;
62534
62599
  font-size: 14px;
62535
62600
 
@@ -62727,6 +62792,7 @@ stores.inject(MyMetaStore, storeInstance);
62727
62792
  };
62728
62793
  sidePanel;
62729
62794
  spreadsheetRef = owl.useRef("spreadsheet");
62795
+ spreadsheetRect = useSpreadsheetRect();
62730
62796
  _focusGrid;
62731
62797
  keyDownMapping;
62732
62798
  isViewportTooSmall = false;
@@ -62736,10 +62802,15 @@ stores.inject(MyMetaStore, storeInstance);
62736
62802
  return this.props.model;
62737
62803
  }
62738
62804
  getStyle() {
62805
+ const properties = {};
62739
62806
  if (this.env.isDashboard()) {
62740
- return `grid-template-rows: auto;`;
62807
+ properties["grid-template-rows"] = `auto`;
62741
62808
  }
62742
- return `grid-template-rows: ${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
62809
+ else {
62810
+ properties["grid-template-rows"] = `${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
62811
+ }
62812
+ properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
62813
+ return cssPropertiesToCss(properties);
62743
62814
  }
62744
62815
  setup() {
62745
62816
  const stores = useStoreProvider();
@@ -62799,14 +62870,19 @@ stores.inject(MyMetaStore, storeInstance);
62799
62870
  owl.onMounted(() => {
62800
62871
  this.checkViewportSize();
62801
62872
  stores.on("store-updated", this, render);
62873
+ resizeObserver.observe(this.spreadsheetRef.el);
62802
62874
  });
62803
62875
  owl.onWillUnmount(() => {
62804
62876
  this.unbindModelEvents();
62805
62877
  stores.off("store-updated", this);
62878
+ resizeObserver.disconnect();
62806
62879
  });
62807
62880
  owl.onPatched(() => {
62808
62881
  this.checkViewportSize();
62809
62882
  });
62883
+ const resizeObserver = new ResizeObserver(() => {
62884
+ this.sidePanel.changePanelSize(this.sidePanel.panelSize, this.spreadsheetRect.width);
62885
+ });
62810
62886
  }
62811
62887
  bindModelEvents() {
62812
62888
  this.model.on("update", this, () => this.render(true));
@@ -66861,6 +66937,8 @@ stores.inject(MyMetaStore, storeInstance);
66861
66937
  insertTokenAfterLeftParenthesis,
66862
66938
  mergeContiguousZones,
66863
66939
  getPivotHighlights,
66940
+ toPivotDomain,
66941
+ flatPivotDomain,
66864
66942
  pivotTimeAdapter,
66865
66943
  UNDO_REDO_PIVOT_COMMANDS,
66866
66944
  };
@@ -66988,9 +67066,9 @@ stores.inject(MyMetaStore, storeInstance);
66988
67066
  exports.tokenize = tokenize;
66989
67067
 
66990
67068
 
66991
- __info__.version = "17.4.0-alpha.1";
66992
- __info__.date = "2024-06-03T15:30:28.283Z";
66993
- __info__.hash = "cb56c37";
67069
+ __info__.version = "17.4.0-alpha.2";
67070
+ __info__.date = "2024-06-06T13:31:21.327Z";
67071
+ __info__.hash = "e07794d";
66994
67072
 
66995
67073
 
66996
67074
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);