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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.4.0-alpha.7
6
- * @date 2024-06-21T08:42:22.370Z
7
- * @hash a99db9a
5
+ * @version 17.4.0-alpha.8
6
+ * @date 2024-06-24T19:51:16.144Z
7
+ * @hash ccd30df
8
8
  */
9
9
 
10
10
  'use strict';
@@ -1903,9 +1903,10 @@ function percentile(values, percent, isInclusive) {
1903
1903
  sortedValues[indexLow] * (indexSup - percentIndex));
1904
1904
  }
1905
1905
 
1906
- // define a mock translation function, when o-spreadsheet runs in standalone it doesn't translate any string
1907
- let _translate = (s) => s;
1908
- let _loaded = () => false;
1906
+ const defaultTranslate = (s) => s;
1907
+ const defaultLoaded = () => false;
1908
+ let _translate = defaultTranslate;
1909
+ let _loaded = defaultLoaded;
1909
1910
  function sprintf(s, ...values) {
1910
1911
  if (values.length === 1 && typeof values[0] === "object" && !(values[0] instanceof String)) {
1911
1912
  const valuesDict = values[0];
@@ -1917,7 +1918,8 @@ function sprintf(s, ...values) {
1917
1918
  return s;
1918
1919
  }
1919
1920
  /***
1920
- * Allow to inject a translation function from outside o-spreadsheet.
1921
+ * Allow to inject a translation function from outside o-spreadsheet. This should be called before instantiating
1922
+ * a model.
1921
1923
  * @param tfn the function that will do the translation
1922
1924
  * @param loaded a function that returns true when the translation is loaded
1923
1925
  */
@@ -1925,6 +1927,18 @@ function setTranslationMethod(tfn, loaded = () => true) {
1925
1927
  _translate = tfn;
1926
1928
  _loaded = loaded;
1927
1929
  }
1930
+ /**
1931
+ * If no translation function has been set, this will mark the translation are loaded.
1932
+ *
1933
+ * By default, the translations should not be set as loaded, otherwise top-level translated constants will never be
1934
+ * translated. But if by the time the model is instantiated no custom translation function has been set, we can set
1935
+ * the default translation function as loaded so o-spreadsheet can be run in standalone with no translations.
1936
+ */
1937
+ function setDefaultTranslationMethod() {
1938
+ if (_translate === defaultTranslate && _loaded === defaultLoaded) {
1939
+ _loaded = () => true;
1940
+ }
1941
+ }
1928
1942
  const _t = function (s, ...values) {
1929
1943
  if (!_loaded()) {
1930
1944
  return new LazyTranslatedString(s, values);
@@ -5620,18 +5634,12 @@ class BorderClipboardHandler extends AbstractCellClipboardHandler {
5620
5634
  return { borders };
5621
5635
  }
5622
5636
  paste(target, content, options) {
5623
- if (!content) {
5624
- return;
5625
- }
5626
5637
  const sheetId = target.sheetId;
5627
- if (options?.pasteOption === "asValue") {
5628
- return;
5629
- }
5630
- if (!("borders" in content) || !("zones" in target) || !target.zones.length) {
5638
+ if (options.pasteOption === "asValue") {
5631
5639
  return;
5632
5640
  }
5633
5641
  const zones = target.zones;
5634
- if (!options?.isCutOperation) {
5642
+ if (!options.isCutOperation) {
5635
5643
  this.pasteFromCopy(sheetId, zones, content.borders);
5636
5644
  }
5637
5645
  else {
@@ -6142,6 +6150,530 @@ function _localizeFormula(formula, fromLocale, toLocale) {
6142
6150
  return localizedFormula;
6143
6151
  }
6144
6152
 
6153
+ function boolAnd(args) {
6154
+ let foundBoolean = false;
6155
+ let acc = true;
6156
+ conditionalVisitBoolean(args, (arg) => {
6157
+ foundBoolean = true;
6158
+ acc = acc && arg;
6159
+ return acc;
6160
+ });
6161
+ return {
6162
+ foundBoolean,
6163
+ result: acc,
6164
+ };
6165
+ }
6166
+ function boolOr(args) {
6167
+ let foundBoolean = false;
6168
+ let acc = false;
6169
+ conditionalVisitBoolean(args, (arg) => {
6170
+ foundBoolean = true;
6171
+ acc = acc || arg;
6172
+ return !acc;
6173
+ });
6174
+ return {
6175
+ foundBoolean,
6176
+ result: acc,
6177
+ };
6178
+ }
6179
+
6180
+ function sum(values, locale) {
6181
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
6182
+ }
6183
+ function countUnique(args) {
6184
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
6185
+ }
6186
+
6187
+ function assertSameNumberOfElements(...args) {
6188
+ const dims = args[0].length;
6189
+ 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())));
6190
+ }
6191
+ function average(values, locale) {
6192
+ let count = 0;
6193
+ const sum = reduceNumbers(values, (acc, a) => {
6194
+ count += 1;
6195
+ return acc + a;
6196
+ }, 0, locale);
6197
+ assertNotZero(count);
6198
+ return sum / count;
6199
+ }
6200
+ function countNumbers(values, locale) {
6201
+ let count = 0;
6202
+ for (let n of values) {
6203
+ if (isMatrix(n)) {
6204
+ for (let i of n) {
6205
+ for (let j of i) {
6206
+ if (typeof j.value === "number") {
6207
+ count += 1;
6208
+ }
6209
+ }
6210
+ }
6211
+ }
6212
+ else {
6213
+ const value = n?.value;
6214
+ if (!isEvaluationError(value) &&
6215
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
6216
+ count += 1;
6217
+ }
6218
+ }
6219
+ }
6220
+ return count;
6221
+ }
6222
+ function countAny(values) {
6223
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
6224
+ }
6225
+ function max(values, locale) {
6226
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
6227
+ return result === -Infinity ? 0 : result;
6228
+ }
6229
+ function min(values, locale) {
6230
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
6231
+ return result === Infinity ? 0 : result;
6232
+ }
6233
+
6234
+ const pivotTimeAdapterRegistry = new Registry();
6235
+ function pivotTimeAdapter(granularity) {
6236
+ return pivotTimeAdapterRegistry.get(granularity);
6237
+ }
6238
+ /**
6239
+ * The Time Adapter: Managing Time Periods for Pivot Functions
6240
+ *
6241
+ * Overview:
6242
+ * A time adapter is responsible for managing time periods associated with pivot functions.
6243
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
6244
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
6245
+ * and the pivot.
6246
+ * By normalizing the period value, it can be stored consistently in the pivot.
6247
+ *
6248
+ * Normalization Process:
6249
+ * When working with functions in the spreadsheet, the time adapter normalizes
6250
+ * the provided period to facilitate accurate lookup of values in the pivot.
6251
+ * For instance, if the spreadsheet function represents a day period as a number generated
6252
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
6253
+ *
6254
+ */
6255
+ /**
6256
+ * Normalized value: "12/25/2023"
6257
+ *
6258
+ * Note: Those two format are equivalent:
6259
+ * - "MM/dd/yyyy" (luxon format)
6260
+ * - "mm/dd/yyyy" (spreadsheet format)
6261
+ **/
6262
+ const dayAdapter = {
6263
+ normalizeFunctionValue(value) {
6264
+ return toNumber(value, DEFAULT_LOCALE);
6265
+ },
6266
+ toValueAndFormat(normalizedValue, locale) {
6267
+ return {
6268
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6269
+ format: (locale ?? DEFAULT_LOCALE).dateFormat,
6270
+ };
6271
+ },
6272
+ toFunctionValue(normalizedValue) {
6273
+ const date = toNumber(normalizedValue, DEFAULT_LOCALE);
6274
+ return `"${formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" })}"`;
6275
+ },
6276
+ };
6277
+ /**
6278
+ * normalizes day of month number
6279
+ */
6280
+ const dayOfMonthAdapter = {
6281
+ normalizeFunctionValue(value) {
6282
+ const day = toNumber(value, DEFAULT_LOCALE);
6283
+ if (day < 1 || day > 31) {
6284
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
6285
+ }
6286
+ return day;
6287
+ },
6288
+ toValueAndFormat(normalizedValue) {
6289
+ return {
6290
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6291
+ format: "0",
6292
+ };
6293
+ },
6294
+ toFunctionValue(normalizedValue) {
6295
+ return `${normalizedValue}`;
6296
+ },
6297
+ };
6298
+ /**
6299
+ * Normalized value: "2/2023" for week 2 of 2023
6300
+ */
6301
+ const weekAdapter = {
6302
+ normalizeFunctionValue(value) {
6303
+ const [week, year] = toString(value).split("/");
6304
+ return `${Number(week)}/${Number(year)}`;
6305
+ },
6306
+ toValueAndFormat(normalizedValue, locale) {
6307
+ const [week, year] = normalizedValue.split("/");
6308
+ return {
6309
+ value: _t("W%(week)s %(year)s", { week, year }),
6310
+ };
6311
+ },
6312
+ toFunctionValue(normalizedValue) {
6313
+ return `"${normalizedValue}"`;
6314
+ },
6315
+ };
6316
+ /**
6317
+ * normalizes iso week number
6318
+ */
6319
+ const isoWeekNumberAdapter = {
6320
+ normalizeFunctionValue(value) {
6321
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
6322
+ if (isoWeek < 0 || isoWeek > 53) {
6323
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
6324
+ }
6325
+ return isoWeek;
6326
+ },
6327
+ toValueAndFormat(normalizedValue) {
6328
+ return {
6329
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6330
+ format: "0",
6331
+ };
6332
+ },
6333
+ toFunctionValue(normalizedValue) {
6334
+ return `${normalizedValue}`;
6335
+ },
6336
+ };
6337
+ /**
6338
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
6339
+ * e.g. "01/2020" for January 2020
6340
+ */
6341
+ const monthAdapter = {
6342
+ normalizeFunctionValue(value) {
6343
+ const date = toNumber(value, DEFAULT_LOCALE);
6344
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
6345
+ },
6346
+ toValueAndFormat(normalizedValue) {
6347
+ return {
6348
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6349
+ format: "mmmm yyyy",
6350
+ };
6351
+ },
6352
+ toFunctionValue(normalizedValue) {
6353
+ return `"${normalizedValue}"`;
6354
+ },
6355
+ };
6356
+ /**
6357
+ * normalizes month number
6358
+ */
6359
+ const monthNumberAdapter = {
6360
+ normalizeFunctionValue(value) {
6361
+ const month = toNumber(value, DEFAULT_LOCALE);
6362
+ if (month < 1 || month > 12) {
6363
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
6364
+ }
6365
+ return month;
6366
+ },
6367
+ toValueAndFormat(normalizedValue) {
6368
+ return {
6369
+ value: MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString(),
6370
+ format: "0",
6371
+ };
6372
+ },
6373
+ toFunctionValue(normalizedValue) {
6374
+ return `${normalizedValue}`;
6375
+ },
6376
+ };
6377
+ /**
6378
+ * normalized quarter value is "quarter/year"
6379
+ * e.g. "1/2020" for Q1 2020
6380
+ */
6381
+ const quarterAdapter = {
6382
+ normalizeFunctionValue(value) {
6383
+ const [quarter, year] = toString(value).split("/");
6384
+ return `${quarter}/${year}`;
6385
+ },
6386
+ toValueAndFormat(normalizedValue) {
6387
+ const [quarter, year] = normalizedValue.split("/");
6388
+ return {
6389
+ value: _t("Q%(quarter)s %(year)s", { quarter, year }),
6390
+ };
6391
+ },
6392
+ toFunctionValue(normalizedValue) {
6393
+ return `"${normalizedValue}"`;
6394
+ },
6395
+ };
6396
+ /**
6397
+ * normalizes quarter number
6398
+ */
6399
+ const quarterNumberAdapter = {
6400
+ normalizeFunctionValue(value) {
6401
+ const quarter = toNumber(value, DEFAULT_LOCALE);
6402
+ if (quarter < 1 || quarter > 4) {
6403
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
6404
+ }
6405
+ return quarter;
6406
+ },
6407
+ toValueAndFormat(normalizedValue) {
6408
+ return {
6409
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6410
+ format: "0",
6411
+ };
6412
+ },
6413
+ toFunctionValue(normalizedValue) {
6414
+ return `${normalizedValue}`;
6415
+ },
6416
+ };
6417
+ const yearAdapter = {
6418
+ normalizeFunctionValue(value) {
6419
+ return toNumber(value, DEFAULT_LOCALE);
6420
+ },
6421
+ toValueAndFormat(normalizedValue) {
6422
+ return {
6423
+ value: toNumber(normalizedValue, DEFAULT_LOCALE),
6424
+ format: "0",
6425
+ };
6426
+ },
6427
+ toFunctionValue(normalizedValue) {
6428
+ return `${normalizedValue}`;
6429
+ },
6430
+ };
6431
+ /**
6432
+ * This function takes an adapter and wraps it with a null handler.
6433
+ * null value means that the value is not set.
6434
+ */
6435
+ function nullHandlerDecorator(adapter) {
6436
+ return {
6437
+ normalizeFunctionValue(value) {
6438
+ if (value === null) {
6439
+ return null;
6440
+ }
6441
+ return adapter.normalizeFunctionValue(value);
6442
+ },
6443
+ toValueAndFormat(normalizedValue, locale) {
6444
+ if (normalizedValue === null) {
6445
+ return { value: _t("(Undefined)") }; //TODO Return NA ?
6446
+ }
6447
+ return adapter.toValueAndFormat(normalizedValue, locale);
6448
+ },
6449
+ toFunctionValue(normalizedValue) {
6450
+ if (normalizedValue === null) {
6451
+ return "false"; //TODO Return NA ?
6452
+ }
6453
+ return adapter.toFunctionValue(normalizedValue);
6454
+ },
6455
+ };
6456
+ }
6457
+ pivotTimeAdapterRegistry
6458
+ .add("day", nullHandlerDecorator(dayAdapter))
6459
+ .add("week", nullHandlerDecorator(weekAdapter))
6460
+ .add("month", nullHandlerDecorator(monthAdapter))
6461
+ .add("quarter", nullHandlerDecorator(quarterAdapter))
6462
+ .add("year", nullHandlerDecorator(yearAdapter))
6463
+ .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
6464
+ .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
6465
+ .add("month_number", nullHandlerDecorator(monthNumberAdapter))
6466
+ .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
6467
+ .add("year_number", nullHandlerDecorator(yearAdapter));
6468
+
6469
+ const AGGREGATOR_NAMES = {
6470
+ count: _t("Count"),
6471
+ count_distinct: _t("Count Distinct"),
6472
+ bool_and: _t("Boolean And"),
6473
+ bool_or: _t("Boolean Or"),
6474
+ max: _t("Maximum"),
6475
+ min: _t("Minimum"),
6476
+ avg: _t("Average"),
6477
+ sum: _t("Sum"),
6478
+ };
6479
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
6480
+ const AGGREGATORS_BY_FIELD_TYPE = {
6481
+ integer: NUMBER_CHAR_AGGREGATORS,
6482
+ char: NUMBER_CHAR_AGGREGATORS,
6483
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
6484
+ };
6485
+ const AGGREGATORS = {};
6486
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
6487
+ AGGREGATORS[type] = {};
6488
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
6489
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
6490
+ }
6491
+ }
6492
+ const AGGREGATORS_FN = {
6493
+ count: {
6494
+ fn: (args) => countAny([args]),
6495
+ format: () => "0",
6496
+ },
6497
+ count_distinct: {
6498
+ fn: (args) => countUnique([args]),
6499
+ format: () => "0",
6500
+ },
6501
+ bool_and: {
6502
+ fn: (args) => boolAnd([args]).result,
6503
+ format: () => undefined,
6504
+ },
6505
+ bool_or: {
6506
+ fn: (args) => boolOr([args]).result,
6507
+ format: () => undefined,
6508
+ },
6509
+ max: {
6510
+ fn: (args, locale) => max([args], locale),
6511
+ format: inferFormat,
6512
+ },
6513
+ min: {
6514
+ fn: (args, locale) => min([args], locale),
6515
+ format: inferFormat,
6516
+ },
6517
+ avg: {
6518
+ fn: (args, locale) => average([args], locale),
6519
+ format: inferFormat,
6520
+ },
6521
+ sum: {
6522
+ fn: (args, locale) => sum([args], locale),
6523
+ format: inferFormat,
6524
+ },
6525
+ };
6526
+ /**
6527
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
6528
+ * in this object
6529
+ * If the object has no keys, return 0
6530
+ *
6531
+ */
6532
+ function getMaxObjectId(o) {
6533
+ const keys = Object.keys(o);
6534
+ if (!keys.length) {
6535
+ return 0;
6536
+ }
6537
+ const nums = keys.map((id) => parseInt(id, 10));
6538
+ const max = Math.max(...nums);
6539
+ return max;
6540
+ }
6541
+ const ALL_PERIODS = {
6542
+ year: _t("Year"),
6543
+ quarter: _t("Quarter"),
6544
+ month: _t("Month"),
6545
+ week: _t("Week"),
6546
+ day: _t("Day"),
6547
+ year_number: _t("Year"),
6548
+ quarter_number: _t("Quarter"),
6549
+ month_number: _t("Month"),
6550
+ iso_week_number: _t("Week"),
6551
+ day_of_month: _t("Day of Month"),
6552
+ };
6553
+ const DATE_FIELDS = ["date", "datetime"];
6554
+ /**
6555
+ * Parse a dimension string into a pivot dimension definition.
6556
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
6557
+ */
6558
+ function parseDimension(dimension) {
6559
+ const [name, granularity] = dimension.split(":");
6560
+ if (granularity) {
6561
+ return { name, granularity };
6562
+ }
6563
+ return { name };
6564
+ }
6565
+ function isDateField(field) {
6566
+ return DATE_FIELDS.includes(field.type);
6567
+ }
6568
+ function generatePivotArgs(formulaId, domain, measure) {
6569
+ const args = [formulaId];
6570
+ if (measure) {
6571
+ args.push(`"${measure}"`);
6572
+ }
6573
+ for (const { field, value, type } of domain) {
6574
+ if (field === "measure") {
6575
+ args.push(`"measure"`, `"${value}"`);
6576
+ continue;
6577
+ }
6578
+ const { granularity } = parseDimension(field);
6579
+ const formattedValue = toFunctionPivotValue(value, { type, granularity });
6580
+ args.push(`"${field}"`, formattedValue);
6581
+ }
6582
+ return args;
6583
+ }
6584
+ /**
6585
+ * Check if the fields in the domain part of
6586
+ * a pivot function are valid according to the pivot definition.
6587
+ * e.g. =PIVOT.VALUE(1,"revenue","country_id",...,"create_date:month",...,"source_id",...)
6588
+ */
6589
+ function areDomainArgsFieldsValid(dimensions, definition) {
6590
+ let argIndex = 0;
6591
+ let definitionIndex = 0;
6592
+ const cols = definition.columns.map((col) => col.nameWithGranularity);
6593
+ const rows = definition.rows.map((row) => row.nameWithGranularity);
6594
+ while (dimensions[argIndex] !== undefined && dimensions[argIndex] === rows[definitionIndex]) {
6595
+ argIndex++;
6596
+ definitionIndex++;
6597
+ }
6598
+ definitionIndex = 0;
6599
+ while (dimensions[argIndex] !== undefined && dimensions[argIndex] === cols[definitionIndex]) {
6600
+ argIndex++;
6601
+ definitionIndex++;
6602
+ }
6603
+ return dimensions.length === argIndex;
6604
+ }
6605
+ function createPivotFormula(formulaId, cell) {
6606
+ switch (cell.type) {
6607
+ case "HEADER":
6608
+ return `=PIVOT.HEADER(${generatePivotArgs(formulaId, cell.domain).join(",")})`;
6609
+ case "VALUE":
6610
+ return `=PIVOT.VALUE(${generatePivotArgs(formulaId, cell.domain, cell.measure).join(",")})`;
6611
+ case "MEASURE_HEADER":
6612
+ return `=PIVOT.HEADER(${generatePivotArgs(formulaId, [
6613
+ ...cell.domain,
6614
+ { field: "measure", value: cell.measure, type: "char" },
6615
+ ]).join(",")})`;
6616
+ }
6617
+ return "";
6618
+ }
6619
+ /**
6620
+ * Parses the value defining a pivot group in a PIVOT formula
6621
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
6622
+ * the two group values are "42" and "won".
6623
+ */
6624
+ function toNormalizedPivotValue(dimension, groupValue) {
6625
+ if (groupValue === null || groupValue === "null") {
6626
+ return null;
6627
+ }
6628
+ const groupValueString = typeof groupValue === "boolean"
6629
+ ? toString(groupValue).toLocaleLowerCase()
6630
+ : toString(groupValue);
6631
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
6632
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
6633
+ field: dimension.displayName,
6634
+ type: dimension.type,
6635
+ }));
6636
+ }
6637
+ // represents a field which is not set (=False server side)
6638
+ if (groupValueString.toLowerCase() === "false") {
6639
+ return false;
6640
+ }
6641
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
6642
+ return normalizer(groupValueString, dimension.granularity);
6643
+ }
6644
+ function normalizeDateTime(value, granularity) {
6645
+ if (!granularity) {
6646
+ throw new Error("Missing granularity");
6647
+ }
6648
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
6649
+ }
6650
+ function toFunctionPivotValue(value, dimension) {
6651
+ if (!pivotToFunctionValueRegistry.contains(dimension.type)) {
6652
+ return `"${value}"`;
6653
+ }
6654
+ return pivotToFunctionValueRegistry.get(dimension.type)(value, dimension.granularity);
6655
+ }
6656
+ function toFunctionValueDateTime(value, granularity) {
6657
+ if (!granularity) {
6658
+ throw new Error("Missing granularity");
6659
+ }
6660
+ return pivotTimeAdapter(granularity).toFunctionValue(value);
6661
+ }
6662
+ const pivotNormalizationValueRegistry = new Registry();
6663
+ pivotNormalizationValueRegistry
6664
+ .add("date", normalizeDateTime)
6665
+ .add("datetime", normalizeDateTime)
6666
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
6667
+ .add("boolean", (value) => toBoolean(value))
6668
+ .add("char", (value) => toString(value));
6669
+ const pivotToFunctionValueRegistry = new Registry();
6670
+ pivotToFunctionValueRegistry
6671
+ .add("date", toFunctionValueDateTime)
6672
+ .add("datetime", toFunctionValueDateTime)
6673
+ .add("integer", (value) => `${toNumber(value, DEFAULT_LOCALE)}`)
6674
+ .add("boolean", (value) => (toBoolean(value) ? "TRUE" : "FALSE"))
6675
+ .add("char", (value) => `"${toString(value).replace(/"/g, '\\"')}"`);
6676
+
6145
6677
  class CellClipboardHandler extends AbstractCellClipboardHandler {
6146
6678
  isCutAllowed(data) {
6147
6679
  if (data.zones.length !== 1) {
@@ -6150,40 +6682,48 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6150
6682
  return "Success" /* CommandResult.Success */;
6151
6683
  }
6152
6684
  copy(data) {
6153
- if (!("zones" in data) || !data.zones.length) {
6154
- return;
6155
- }
6156
6685
  const sheetId = data.sheetId;
6157
- const zones = data.zones;
6158
- if (!zones.length) {
6159
- return {
6160
- cells: [[]],
6161
- zones: [],
6162
- sheetId,
6163
- };
6164
- }
6165
6686
  const { clippedZones, rowsIndexes, columnsIndexes } = data;
6166
6687
  const clippedCells = [];
6688
+ const isCopyingOneCell = rowsIndexes.length == 1 && columnsIndexes.length == 1;
6167
6689
  for (let row of rowsIndexes) {
6168
6690
  let cellsInRow = [];
6169
6691
  for (let col of columnsIndexes) {
6170
6692
  const position = { col, row, sheetId };
6171
- const spreader = this.getters.getArrayFormulaSpreadingOn(position);
6172
6693
  let cell = this.getters.getCell(position);
6173
6694
  const evaluatedCell = this.getters.getEvaluatedCell(position);
6174
- if (spreader && !deepEquals(spreader, position)) {
6175
- const isSpreaderCopied = rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
6176
- const content = isSpreaderCopied
6177
- ? ""
6178
- : formatValue(evaluatedCell.value, { locale: this.getters.getLocale() });
6179
- cell = {
6180
- id: cell?.id || "",
6181
- style: cell?.style,
6182
- format: evaluatedCell.format,
6183
- content,
6184
- isFormula: false,
6185
- parsedValue: evaluatedCell.value,
6186
- };
6695
+ const pivotId = this.getters.getPivotIdFromPosition(position);
6696
+ const spreader = this.getters.getArrayFormulaSpreadingOn(position);
6697
+ if (pivotId) {
6698
+ if (!deepEquals(spreader, position) || !isCopyingOneCell) {
6699
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
6700
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
6701
+ const pivotFormula = createPivotFormula(formulaPivotId, pivotCell);
6702
+ cell = {
6703
+ id: cell?.id || "",
6704
+ style: cell?.style,
6705
+ format: evaluatedCell.format,
6706
+ content: pivotFormula,
6707
+ isFormula: false,
6708
+ parsedValue: evaluatedCell.value,
6709
+ };
6710
+ }
6711
+ }
6712
+ else {
6713
+ if (spreader && !deepEquals(spreader, position)) {
6714
+ const isSpreaderCopied = rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
6715
+ const content = isSpreaderCopied
6716
+ ? ""
6717
+ : formatValue(evaluatedCell.value, { locale: this.getters.getLocale() });
6718
+ cell = {
6719
+ id: cell?.id || "",
6720
+ style: cell?.style,
6721
+ format: evaluatedCell.format,
6722
+ content,
6723
+ isFormula: false,
6724
+ parsedValue: evaluatedCell.value,
6725
+ };
6726
+ }
6187
6727
  }
6188
6728
  cellsInRow.push({
6189
6729
  cell,
@@ -6232,12 +6772,9 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6232
6772
  * Paste the clipboard content in the given target
6233
6773
  */
6234
6774
  paste(target, content, options) {
6235
- if (!("cells" in content) || !("zones" in target) || !target.zones.length) {
6236
- return;
6237
- }
6238
6775
  const zones = target.zones;
6239
6776
  const sheetId = target.sheetId;
6240
- if (!options?.isCutOperation) {
6777
+ if (!options.isCutOperation) {
6241
6778
  this.pasteFromCopy(sheetId, zones, content.cells, options);
6242
6779
  }
6243
6780
  else {
@@ -6407,14 +6944,11 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
6407
6944
  };
6408
6945
  }
6409
6946
  getPasteTarget(sheetId, target, content, options) {
6410
- if (!content?.copiedFigure || !content?.copiedChart) {
6411
- return { zones: [], sheetId };
6412
- }
6413
6947
  const newId = new UuidGenerator().uuidv4();
6414
6948
  return { zones: [], figureId: newId, sheetId };
6415
6949
  }
6416
6950
  paste(target, clippedContent, options) {
6417
- if (!clippedContent?.copiedFigure || !clippedContent?.copiedChart || !target.figureId) {
6951
+ if (!target.figureId) {
6418
6952
  return;
6419
6953
  }
6420
6954
  const { zones, figureId } = target;
@@ -6438,7 +6972,7 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
6438
6972
  size: { height, width },
6439
6973
  definition: copy.getDefinition(),
6440
6974
  });
6441
- if (options?.isCutOperation) {
6975
+ if (options.isCutOperation) {
6442
6976
  this.dispatch("DELETE_FIGURE", {
6443
6977
  sheetId: clippedContent.copiedChart.sheetId,
6444
6978
  id: clippedContent.copiedFigure.id,
@@ -6480,15 +7014,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6480
7014
  return { cfRules };
6481
7015
  }
6482
7016
  paste(target, clippedContent, options) {
6483
- if (!clippedContent?.cfRules ||
6484
- options?.pasteOption === "asValue" ||
6485
- !("zones" in target) ||
6486
- !target.zones.length) {
7017
+ if (options.pasteOption === "asValue") {
6487
7018
  return;
6488
7019
  }
6489
7020
  const zones = target.zones;
6490
7021
  const sheetId = target.sheetId;
6491
- if (!options?.isCutOperation) {
7022
+ if (!options.isCutOperation) {
6492
7023
  this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6493
7024
  }
6494
7025
  else {
@@ -6562,9 +7093,6 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6562
7093
  class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6563
7094
  uuidGenerator = new UuidGenerator();
6564
7095
  copy(data) {
6565
- if (!data.zones.length) {
6566
- return;
6567
- }
6568
7096
  const { rowsIndexes, columnsIndexes } = data;
6569
7097
  const sheetId = data.sheetId;
6570
7098
  const dvRules = [];
@@ -6580,18 +7108,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6580
7108
  return { dvRules };
6581
7109
  }
6582
7110
  paste(target, clippedContent, options) {
6583
- if (!clippedContent?.dvRules) {
6584
- return;
6585
- }
6586
- if (options?.pasteOption) {
6587
- return;
6588
- }
6589
- if (!("zones" in target) || !target.zones.length) {
7111
+ if (options.pasteOption) {
6590
7112
  return;
6591
7113
  }
6592
7114
  const zones = target.zones;
6593
7115
  const sheetId = target.sheetId;
6594
- if (!options?.isCutOperation) {
7116
+ if (!options.isCutOperation) {
6595
7117
  this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6596
7118
  }
6597
7119
  else {
@@ -6690,14 +7212,11 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6690
7212
  };
6691
7213
  }
6692
7214
  getPasteTarget(sheetId, target, content, options) {
6693
- if (!content?.copiedFigure || !content?.copiedImage) {
6694
- return { zones: [], sheetId };
6695
- }
6696
7215
  const newId = new UuidGenerator().uuidv4();
6697
7216
  return { sheetId, zones: [], figureId: newId };
6698
7217
  }
6699
7218
  paste(target, clippedContent, options) {
6700
- if (!clippedContent?.copiedFigure || !clippedContent?.copiedImage || !target.figureId) {
7219
+ if (!target.figureId) {
6701
7220
  return;
6702
7221
  }
6703
7222
  const { zones, figureId } = target;
@@ -6721,7 +7240,7 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6721
7240
  size: { height, width },
6722
7241
  definition: copy,
6723
7242
  });
6724
- if (options?.isCutOperation) {
7243
+ if (options.isCutOperation) {
6725
7244
  this.dispatch("DELETE_FIGURE", {
6726
7245
  sheetId: clippedContent.sheetId,
6727
7246
  id: clippedContent.copiedFigure.id,
@@ -6742,9 +7261,6 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6742
7261
 
6743
7262
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6744
7263
  copy(data) {
6745
- if (!data.zones.length) {
6746
- return;
6747
- }
6748
7264
  const sheetId = this.getters.getActiveSheetId();
6749
7265
  const { rowsIndexes, columnsIndexes } = data;
6750
7266
  const merges = [];
@@ -6762,10 +7278,7 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
6762
7278
  * Paste the clipboard content in the given target
6763
7279
  */
6764
7280
  paste(target, content, options) {
6765
- if (!content.merges ||
6766
- options?.isCutOperation ||
6767
- !("zones" in target) ||
6768
- !target.zones.length) {
7281
+ if (options.isCutOperation) {
6769
7282
  return;
6770
7283
  }
6771
7284
  this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
@@ -6821,9 +7334,6 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6821
7334
  copy(data) {
6822
7335
  const sheetId = data.sheetId;
6823
7336
  const { rowsIndexes, columnsIndexes, zones } = data;
6824
- if (!zones || !rowsIndexes.length || !columnsIndexes.length) {
6825
- return { tableCells: [[]], sheetId };
6826
- }
6827
7337
  const copiedTablesIds = new Set();
6828
7338
  const tableCells = [];
6829
7339
  for (let row of rowsIndexes) {
@@ -6879,12 +7389,9 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6879
7389
  };
6880
7390
  }
6881
7391
  paste(target, content, options) {
6882
- if (!content || !content.tableCells) {
6883
- return;
6884
- }
6885
7392
  const zones = target.zones;
6886
7393
  const sheetId = target.sheetId;
6887
- if (!options?.isCutOperation) {
7394
+ if (!options.isCutOperation) {
6888
7395
  this.pasteFromCopy(sheetId, zones, content.tableCells, options);
6889
7396
  }
6890
7397
  else {
@@ -7777,486 +8284,6 @@ function errorCell(value, message) {
7777
8284
  };
7778
8285
  }
7779
8286
 
7780
- function boolAnd(args) {
7781
- let foundBoolean = false;
7782
- let acc = true;
7783
- conditionalVisitBoolean(args, (arg) => {
7784
- foundBoolean = true;
7785
- acc = acc && arg;
7786
- return acc;
7787
- });
7788
- return {
7789
- foundBoolean,
7790
- result: acc,
7791
- };
7792
- }
7793
- function boolOr(args) {
7794
- let foundBoolean = false;
7795
- let acc = false;
7796
- conditionalVisitBoolean(args, (arg) => {
7797
- foundBoolean = true;
7798
- acc = acc || arg;
7799
- return !acc;
7800
- });
7801
- return {
7802
- foundBoolean,
7803
- result: acc,
7804
- };
7805
- }
7806
-
7807
- function sum(values, locale) {
7808
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7809
- }
7810
- function countUnique(args) {
7811
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7812
- }
7813
-
7814
- function assertSameNumberOfElements(...args) {
7815
- const dims = args[0].length;
7816
- args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
7817
- }
7818
- function average(values, locale) {
7819
- let count = 0;
7820
- const sum = reduceNumbers(values, (acc, a) => {
7821
- count += 1;
7822
- return acc + a;
7823
- }, 0, locale);
7824
- assertNotZero(count);
7825
- return sum / count;
7826
- }
7827
- function countNumbers(values, locale) {
7828
- let count = 0;
7829
- for (let n of values) {
7830
- if (isMatrix(n)) {
7831
- for (let i of n) {
7832
- for (let j of i) {
7833
- if (typeof j.value === "number") {
7834
- count += 1;
7835
- }
7836
- }
7837
- }
7838
- }
7839
- else {
7840
- const value = n?.value;
7841
- if (!isEvaluationError(value) &&
7842
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7843
- count += 1;
7844
- }
7845
- }
7846
- }
7847
- return count;
7848
- }
7849
- function countAny(values) {
7850
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7851
- }
7852
- function max(values, locale) {
7853
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7854
- return result === -Infinity ? 0 : result;
7855
- }
7856
- function min(values, locale) {
7857
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7858
- return result === Infinity ? 0 : result;
7859
- }
7860
-
7861
- const pivotTimeAdapterRegistry = new Registry();
7862
- function pivotTimeAdapter(granularity) {
7863
- return pivotTimeAdapterRegistry.get(granularity);
7864
- }
7865
- /**
7866
- * The Time Adapter: Managing Time Periods for Pivot Functions
7867
- *
7868
- * Overview:
7869
- * A time adapter is responsible for managing time periods associated with pivot functions.
7870
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7871
- * The adapter's primary role is to normalize period values between spreadsheet functions,
7872
- * and the pivot.
7873
- * By normalizing the period value, it can be stored consistently in the pivot.
7874
- *
7875
- * Normalization Process:
7876
- * When working with functions in the spreadsheet, the time adapter normalizes
7877
- * the provided period to facilitate accurate lookup of values in the pivot.
7878
- * For instance, if the spreadsheet function represents a day period as a number generated
7879
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7880
- *
7881
- */
7882
- /**
7883
- * Normalized value: "12/25/2023"
7884
- *
7885
- * Note: Those two format are equivalent:
7886
- * - "MM/dd/yyyy" (luxon format)
7887
- * - "mm/dd/yyyy" (spreadsheet format)
7888
- **/
7889
- const dayAdapter = {
7890
- normalizeFunctionValue(value) {
7891
- const date = toNumber(value, DEFAULT_LOCALE);
7892
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7893
- },
7894
- getFormat(locale) {
7895
- return (locale ?? DEFAULT_LOCALE).dateFormat;
7896
- },
7897
- formatValue(normalizedValue, locale) {
7898
- locale = locale ?? DEFAULT_LOCALE;
7899
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7900
- return formatValue(value, { locale, format: this.getFormat(locale) });
7901
- },
7902
- toCellValue(normalizedValue) {
7903
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7904
- },
7905
- };
7906
- /**
7907
- * normalizes day of month number
7908
- */
7909
- const dayOfMonthAdapter = {
7910
- normalizeFunctionValue(value) {
7911
- const day = toNumber(value, DEFAULT_LOCALE);
7912
- if (day < 1 || day > 31) {
7913
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7914
- }
7915
- return day;
7916
- },
7917
- getFormat() {
7918
- return "0";
7919
- },
7920
- formatValue(normalizedValue, locale) {
7921
- locale = locale ?? DEFAULT_LOCALE;
7922
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7923
- return formatValue(value, { locale, format: this.getFormat(locale) });
7924
- },
7925
- toCellValue(normalizedValue) {
7926
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7927
- },
7928
- };
7929
- /**
7930
- * Normalized value: "2/2023" for week 2 of 2023
7931
- */
7932
- const weekAdapter = {
7933
- normalizeFunctionValue(value) {
7934
- const [week, year] = value.split("/");
7935
- return `${Number(week)}/${Number(year)}`;
7936
- },
7937
- getFormat() {
7938
- return undefined;
7939
- },
7940
- formatValue(normalizedValue) {
7941
- const [week, year] = normalizedValue.split("/");
7942
- return _t("W%(week)s %(year)s", { week, year });
7943
- },
7944
- toCellValue(normalizedValue) {
7945
- return this.formatValue(normalizedValue);
7946
- },
7947
- };
7948
- /**
7949
- * normalizes iso week number
7950
- */
7951
- const isoWeekNumberAdapter = {
7952
- normalizeFunctionValue(value) {
7953
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
7954
- if (isoWeek < 0 || isoWeek > 53) {
7955
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7956
- }
7957
- return isoWeek;
7958
- },
7959
- getFormat() {
7960
- return "0";
7961
- },
7962
- formatValue(normalizedValue, locale) {
7963
- locale = locale ?? DEFAULT_LOCALE;
7964
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7965
- return formatValue(value, { locale, format: this.getFormat(locale) });
7966
- },
7967
- toCellValue(normalizedValue) {
7968
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7969
- },
7970
- };
7971
- /**
7972
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7973
- * e.g. "01/2020" for January 2020
7974
- */
7975
- const monthAdapter = {
7976
- normalizeFunctionValue(value) {
7977
- const date = toNumber(value, DEFAULT_LOCALE);
7978
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7979
- },
7980
- getFormat() {
7981
- return "mmmm yyyy";
7982
- },
7983
- formatValue(normalizedValue, locale) {
7984
- locale = locale ?? DEFAULT_LOCALE;
7985
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7986
- return formatValue(value, { locale, format: this.getFormat(locale) });
7987
- },
7988
- toCellValue(normalizedValue) {
7989
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7990
- },
7991
- };
7992
- /**
7993
- * normalizes month number
7994
- */
7995
- const monthNumberAdapter = {
7996
- normalizeFunctionValue(value) {
7997
- const month = toNumber(value, DEFAULT_LOCALE);
7998
- if (month < 1 || month > 12) {
7999
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
8000
- }
8001
- return month;
8002
- },
8003
- getFormat() {
8004
- return "0";
8005
- },
8006
- formatValue(normalizedValue, locale) {
8007
- locale = locale ?? DEFAULT_LOCALE;
8008
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8009
- return formatValue(value, { locale, format: this.getFormat(locale) });
8010
- },
8011
- toCellValue(normalizedValue) {
8012
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8013
- },
8014
- };
8015
- /**
8016
- * normalized quarter value is "quarter/year"
8017
- * e.g. "1/2020" for Q1 2020
8018
- */
8019
- const quarterAdapter = {
8020
- normalizeFunctionValue(value) {
8021
- const [quarter, year] = value.split("/");
8022
- return `${quarter}/${year}`;
8023
- },
8024
- getFormat() {
8025
- return undefined;
8026
- },
8027
- formatValue(normalizedValue) {
8028
- const [quarter, year] = normalizedValue.split("/");
8029
- return _t("Q%(quarter)s %(year)s", { quarter, year });
8030
- },
8031
- toCellValue(normalizedValue) {
8032
- return this.formatValue(normalizedValue);
8033
- },
8034
- };
8035
- /**
8036
- * normalizes quarter number
8037
- */
8038
- const quarterNumberAdapter = {
8039
- normalizeFunctionValue(value) {
8040
- const quarter = toNumber(value, DEFAULT_LOCALE);
8041
- if (quarter < 1 || quarter > 4) {
8042
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8043
- }
8044
- return quarter;
8045
- },
8046
- getFormat() {
8047
- return "0";
8048
- },
8049
- formatValue(normalizedValue, locale) {
8050
- locale = locale ?? DEFAULT_LOCALE;
8051
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8052
- return formatValue(value, { locale, format: this.getFormat(locale) });
8053
- },
8054
- toCellValue(normalizedValue) {
8055
- return toNumber(normalizedValue, DEFAULT_LOCALE);
8056
- },
8057
- };
8058
- const yearAdapter = {
8059
- normalizeFunctionValue(value) {
8060
- return toNumber(value, DEFAULT_LOCALE);
8061
- },
8062
- getFormat() {
8063
- return "0";
8064
- },
8065
- formatValue(normalizedValue, locale) {
8066
- locale = locale ?? DEFAULT_LOCALE;
8067
- return formatValue(normalizedValue, { locale, format: "0" });
8068
- },
8069
- toCellValue(normalizedValue) {
8070
- return toNumber(normalizedValue, DEFAULT_LOCALE);
8071
- },
8072
- };
8073
- pivotTimeAdapterRegistry
8074
- .add("day", dayAdapter)
8075
- .add("week", weekAdapter)
8076
- .add("month", monthAdapter)
8077
- .add("quarter", quarterAdapter)
8078
- .add("year", yearAdapter)
8079
- .add("day_of_month", dayOfMonthAdapter)
8080
- .add("iso_week_number", isoWeekNumberAdapter)
8081
- .add("month_number", monthNumberAdapter)
8082
- .add("quarter_number", quarterNumberAdapter)
8083
- .add("year_number", yearAdapter);
8084
-
8085
- const AGGREGATOR_NAMES = {
8086
- count: _t("Count"),
8087
- count_distinct: _t("Count Distinct"),
8088
- bool_and: _t("Boolean And"),
8089
- bool_or: _t("Boolean Or"),
8090
- max: _t("Maximum"),
8091
- min: _t("Minimum"),
8092
- avg: _t("Average"),
8093
- sum: _t("Sum"),
8094
- };
8095
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8096
- const AGGREGATORS_BY_FIELD_TYPE = {
8097
- integer: NUMBER_CHAR_AGGREGATORS,
8098
- char: NUMBER_CHAR_AGGREGATORS,
8099
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8100
- };
8101
- const AGGREGATORS = {};
8102
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8103
- AGGREGATORS[type] = {};
8104
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8105
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8106
- }
8107
- }
8108
- const AGGREGATORS_FN = {
8109
- count: {
8110
- fn: (args) => countAny([args]),
8111
- format: () => "0",
8112
- },
8113
- count_distinct: {
8114
- fn: (args) => countUnique([args]),
8115
- format: () => "0",
8116
- },
8117
- bool_and: {
8118
- fn: (args) => boolAnd([args]).result,
8119
- format: () => undefined,
8120
- },
8121
- bool_or: {
8122
- fn: (args) => boolOr([args]).result,
8123
- format: () => undefined,
8124
- },
8125
- max: {
8126
- fn: (args, locale) => max([args], locale),
8127
- format: inferFormat,
8128
- },
8129
- min: {
8130
- fn: (args, locale) => min([args], locale),
8131
- format: inferFormat,
8132
- },
8133
- avg: {
8134
- fn: (args, locale) => average([args], locale),
8135
- format: inferFormat,
8136
- },
8137
- sum: {
8138
- fn: (args, locale) => sum([args], locale),
8139
- format: inferFormat,
8140
- },
8141
- };
8142
- function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8143
- switch (pivotCell.type) {
8144
- case "HEADER":
8145
- return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8146
- case "MEASURE_HEADER":
8147
- return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8148
- case "VALUE":
8149
- return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8150
- case "EMPTY":
8151
- return "";
8152
- }
8153
- }
8154
- /**
8155
- * Build a pivot formula expression
8156
- */
8157
- function makePivotFormula(formula, args) {
8158
- return `=${formula}(${args
8159
- .map((arg) => {
8160
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8161
- const convertToNumber = typeof arg == "number" || stringIsNumber;
8162
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8163
- })
8164
- .join(",")})`;
8165
- }
8166
- /**
8167
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8168
- * in this object
8169
- * If the object has no keys, return 0
8170
- *
8171
- */
8172
- function getMaxObjectId(o) {
8173
- const keys = Object.keys(o);
8174
- if (!keys.length) {
8175
- return 0;
8176
- }
8177
- const nums = keys.map((id) => parseInt(id, 10));
8178
- const max = Math.max(...nums);
8179
- return max;
8180
- }
8181
- const ALL_PERIODS = {
8182
- year: _t("Year"),
8183
- quarter: _t("Quarter"),
8184
- month: _t("Month"),
8185
- week: _t("Week"),
8186
- day: _t("Day"),
8187
- year_number: _t("Year"),
8188
- quarter_number: _t("Quarter"),
8189
- month_number: _t("Month"),
8190
- iso_week_number: _t("Week"),
8191
- day_of_month: _t("Day of Month"),
8192
- };
8193
- const DATE_FIELDS = ["date", "datetime"];
8194
- /**
8195
- * Parse a dimension string into a pivot dimension definition.
8196
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8197
- */
8198
- function parseDimension(dimension) {
8199
- const [name, granularity] = dimension.split(":");
8200
- if (granularity) {
8201
- return { name, granularity };
8202
- }
8203
- return { name };
8204
- }
8205
- function isDateField(field) {
8206
- return DATE_FIELDS.includes(field.type);
8207
- }
8208
- function toPivotDomain(domainStr) {
8209
- if (domainStr.length % 2 !== 0) {
8210
- throw new Error("Invalid domain: odd number of elements");
8211
- }
8212
- const domain = [];
8213
- for (let i = 0; i < domainStr.length - 1; i += 2) {
8214
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8215
- }
8216
- return domain;
8217
- }
8218
- function flatPivotDomain(domain) {
8219
- return domain.flatMap((arg) => [arg.field, arg.value]);
8220
- }
8221
- /**
8222
- * Parses the value defining a pivot group in a PIVOT formula
8223
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8224
- * the two group values are "42" and "won".
8225
- */
8226
- function toNormalizedPivotValue(dimension, groupValue) {
8227
- if (groupValue === null || groupValue === "null") {
8228
- return null;
8229
- }
8230
- const groupValueString = typeof groupValue === "boolean"
8231
- ? toString(groupValue).toLocaleLowerCase()
8232
- : toString(groupValue);
8233
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8234
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8235
- field: dimension.displayName,
8236
- type: dimension.type,
8237
- }));
8238
- }
8239
- // represents a field which is not set (=False server side)
8240
- if (groupValueString === "false") {
8241
- return false;
8242
- }
8243
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8244
- return normalizer(groupValueString, dimension.granularity);
8245
- }
8246
- function normalizeDateTime(value, granularity) {
8247
- if (!granularity) {
8248
- throw "";
8249
- }
8250
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8251
- }
8252
- const pivotNormalizationValueRegistry = new Registry();
8253
- pivotNormalizationValueRegistry
8254
- .add("date", normalizeDateTime)
8255
- .add("datetime", normalizeDateTime)
8256
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8257
- .add("boolean", (value) => toBoolean(value))
8258
- .add("char", (value) => toString(value));
8259
-
8260
8287
  /**
8261
8288
  * Change the reference types inside the given token, if the token represent a range or a cell
8262
8289
  *
@@ -9996,7 +10023,7 @@ class ComposerStore extends SpreadsheetStore {
9996
10023
  const cell = this.getters.getCell(position);
9997
10024
  if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9998
10025
  const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
9999
- const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
10026
+ const formula = createPivotFormula(formulaPivotId, pivotCell);
10000
10027
  return formula.slice(1); // strip leading =
10001
10028
  }
10002
10029
  }
@@ -19088,10 +19115,9 @@ const PIVOT_VALUE = {
19088
19115
  compute: function (formulaId, measureName, ...domainArgs) {
19089
19116
  const _pivotFormulaId = toString(formulaId);
19090
19117
  const _measure = toString(measureName);
19091
- const _domainArgs = domainArgs.map(toString);
19092
19118
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
19093
19119
  assertMeasureExist(pivotId, _measure, this.getters);
19094
- assertDomainLength(_domainArgs);
19120
+ assertDomainLength(domainArgs);
19095
19121
  const pivot = this.getters.getPivot(pivotId);
19096
19122
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
19097
19123
  addPivotDependencies(this, coreDefinition);
@@ -19099,15 +19125,14 @@ const PIVOT_VALUE = {
19099
19125
  if (error) {
19100
19126
  return error;
19101
19127
  }
19102
- const domain = toPivotDomain(_domainArgs);
19103
- const { value, format } = pivot.getPivotCellValueAndFormat(_measure, domain);
19104
- if (!value && !this.getters.areDomainArgsFieldsValid(pivotId, domain)) {
19128
+ if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19105
19129
  return {
19106
19130
  value: CellErrorType.GenericError,
19107
19131
  message: _t("Dimensions don't match the pivot definition"),
19108
19132
  };
19109
19133
  }
19110
- return { value, format };
19134
+ const domain = pivot.parseArgsToPivotDomain(domainArgs);
19135
+ return pivot.getPivotCellValueAndFormat(_measure, domain);
19111
19136
  },
19112
19137
  };
19113
19138
  const PIVOT_HEADER = {
@@ -19119,9 +19144,8 @@ const PIVOT_HEADER = {
19119
19144
  ],
19120
19145
  compute: function (pivotId, ...domainArgs) {
19121
19146
  const _pivotFormulaId = toString(pivotId);
19122
- const _domainArgs = domainArgs.map(toString);
19123
19147
  const _pivotId = getPivotId(_pivotFormulaId, this.getters);
19124
- assertDomainLength(_domainArgs);
19148
+ assertDomainLength(domainArgs);
19125
19149
  const pivot = this.getters.getPivot(_pivotId);
19126
19150
  const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
19127
19151
  addPivotDependencies(this, coreDefinition);
@@ -19129,14 +19153,14 @@ const PIVOT_HEADER = {
19129
19153
  if (error) {
19130
19154
  return error;
19131
19155
  }
19132
- const domain = toPivotDomain(_domainArgs);
19133
- const lastNode = domain.at(-1);
19134
- if (!this.getters.areDomainArgsFieldsValid(_pivotId, lastNode?.field === "measure" ? domain.slice(0, -1) : domain)) {
19156
+ if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19135
19157
  return {
19136
19158
  value: CellErrorType.GenericError,
19137
19159
  message: _t("Dimensions don't match the pivot definition"),
19138
19160
  };
19139
19161
  }
19162
+ const domain = pivot.parseArgsToPivotDomain(domainArgs);
19163
+ const lastNode = domain.at(-1);
19140
19164
  if (lastNode?.field === "measure") {
19141
19165
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
19142
19166
  }
@@ -19153,13 +19177,21 @@ const PIVOT = {
19153
19177
  description: _t("Get a pivot table."),
19154
19178
  args: [
19155
19179
  arg("pivot_id (string)", _t("ID of the pivot.")),
19156
- arg("row_count (number, optional, default=10000)", _t("number of rows")),
19180
+ arg("row_count (number, optional)", _t("number of rows")),
19157
19181
  arg("include_total (boolean, default=TRUE)", _t("Whether to include total/sub-totals or not.")),
19158
19182
  arg("include_column_titles (boolean, default=TRUE)", _t("Whether to include the column titles or not.")),
19183
+ arg("column_count (number, optional)", _t("number of columns")),
19159
19184
  ],
19160
- compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }) {
19185
+ compute: function (pivotFormulaId, rowCount = { value: Number.MAX_VALUE }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19161
19186
  const _pivotFormulaId = toString(pivotFormulaId);
19162
19187
  const _rowCount = toNumber(rowCount, this.locale);
19188
+ if (_rowCount < 0) {
19189
+ throw new EvaluationError(_t("The number of rows must be positive."));
19190
+ }
19191
+ const _columnCount = toNumber(columnCount, this.locale);
19192
+ if (_columnCount < 0) {
19193
+ throw new EvaluationError(_t("The number of columns must be positive."));
19194
+ }
19163
19195
  const _includeColumnHeaders = toBoolean(includeColumnHeaders);
19164
19196
  const _includedTotal = toBoolean(includeTotal);
19165
19197
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
@@ -19175,19 +19207,15 @@ const PIVOT = {
19175
19207
  const cells = table.getPivotCells(_includedTotal, _includeColumnHeaders);
19176
19208
  const headerRows = _includeColumnHeaders ? table.columns.length : 0;
19177
19209
  const pivotTitle = this.getters.getPivotDisplayName(pivotId);
19178
- if (_rowCount < 0) {
19179
- throw new EvaluationError(_t("The number of rows must be positive."));
19180
- }
19181
- const end = Math.min(headerRows + _rowCount, cells[0].length);
19182
- if (end === 0) {
19210
+ const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
19211
+ if (tableHeight === 0) {
19183
19212
  return [[{ value: pivotTitle }]];
19184
19213
  }
19185
- const tableWidth = cells.length;
19186
- const tableRows = range(0, end);
19214
+ const tableWidth = Math.min(1 + _columnCount, cells.length);
19187
19215
  const result = [];
19188
19216
  for (const col of range(0, tableWidth)) {
19189
19217
  result[col] = [];
19190
- for (const row of tableRows) {
19218
+ for (const row of range(0, tableHeight)) {
19191
19219
  const pivotCell = cells[col][row];
19192
19220
  switch (pivotCell.type) {
19193
19221
  case "EMPTY":
@@ -21877,9 +21905,6 @@ autoCompleteProviders.add("pivot_measures", {
21877
21905
  const pivot = this.getters.getPivot(pivotId);
21878
21906
  pivot.init();
21879
21907
  const fields = pivot.getFields();
21880
- if (!fields) {
21881
- return [];
21882
- }
21883
21908
  const definition = this.getters.getPivotCoreDefinition(pivotId);
21884
21909
  return definition.measures
21885
21910
  .map((measure) => {
@@ -21919,9 +21944,6 @@ autoCompleteProviders.add("pivot_group_fields", {
21919
21944
  const pivot = this.getters.getPivot(pivotId);
21920
21945
  pivot.init();
21921
21946
  const fields = pivot.getFields();
21922
- if (!fields) {
21923
- return;
21924
- }
21925
21947
  const { columns, rows } = pivot.definition;
21926
21948
  let args = functionContext.args;
21927
21949
  if (functionContext?.parent.toUpperCase() === "PIVOT.VALUE") {
@@ -27070,7 +27092,7 @@ function getSmartChartDefinition(zone, getters) {
27070
27092
  if (getZoneArea(zone) === 1 && topLeftCell?.content) {
27071
27093
  return {
27072
27094
  type: "scorecard",
27073
- title: { text: "" },
27095
+ title: {},
27074
27096
  background: topLeftCell.style?.fillColor || undefined,
27075
27097
  keyValue: zoneToXc(zone),
27076
27098
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
@@ -27078,22 +27100,11 @@ function getSmartChartDefinition(zone, getters) {
27078
27100
  baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
27079
27101
  };
27080
27102
  }
27081
- let title = "";
27082
27103
  const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
27083
27104
  ...dataSetZone,
27084
27105
  bottom: dataSetZone.top,
27085
27106
  });
27086
27107
  const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
27087
- if (dataSetsHaveTitle) {
27088
- const texts = cellsInFirstRow
27089
- .filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
27090
- .map((cell) => cell.formattedValue);
27091
- const lastElement = texts.splice(-1)[0];
27092
- title = texts.join(", ");
27093
- if (lastElement) {
27094
- title += (title ? " " + _t("and") + " " : "") + lastElement;
27095
- }
27096
- }
27097
27108
  let labelRangeXc;
27098
27109
  if (!singleColumn) {
27099
27110
  labelRangeXc = zoneToXc({
@@ -27106,7 +27117,7 @@ function getSmartChartDefinition(zone, getters) {
27106
27117
  const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
27107
27118
  if (canChartParseLabels(labelRange, getters)) {
27108
27119
  return {
27109
- title: { text: title },
27120
+ title: {},
27110
27121
  dataSets,
27111
27122
  labelsAsText: false,
27112
27123
  stacked: false,
@@ -27122,7 +27133,7 @@ function getSmartChartDefinition(zone, getters) {
27122
27133
  if (singleColumn &&
27123
27134
  getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
27124
27135
  return {
27125
- title: { text: "" },
27136
+ title: {},
27126
27137
  dataSets: [{ dataRange }],
27127
27138
  aggregated: true,
27128
27139
  labelRange: dataRange,
@@ -27132,7 +27143,7 @@ function getSmartChartDefinition(zone, getters) {
27132
27143
  };
27133
27144
  }
27134
27145
  return {
27135
- title: { text: title },
27146
+ title: {},
27136
27147
  dataSets,
27137
27148
  labelRange: labelRangeXc,
27138
27149
  type: "bar",
@@ -34555,6 +34566,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
34555
34566
  currentSearchRegex = null;
34556
34567
  isSearchDirty = false;
34557
34568
  initialShowFormulaState;
34569
+ preserveSelectedMatchIndex = false;
34558
34570
  // fixme: why do we make selectedMatchIndex on top of a selected
34559
34571
  // property in the matches?
34560
34572
  selectedMatchIndex = null;
@@ -34668,7 +34680,9 @@ class FindAndReplaceStore extends SpreadsheetStore {
34668
34680
  * refresh the matches according to the current search options
34669
34681
  */
34670
34682
  refreshSearch(jumpToMatchSheet = true) {
34671
- this.selectedMatchIndex = null;
34683
+ if (!this.preserveSelectedMatchIndex) {
34684
+ this.selectedMatchIndex = null;
34685
+ }
34672
34686
  this.findMatches();
34673
34687
  this.selectNextCell(Direction.current, jumpToMatchSheet);
34674
34688
  }
@@ -34767,10 +34781,16 @@ class FindAndReplaceStore extends SpreadsheetStore {
34767
34781
  const selectedMatch = matches[nextIndex];
34768
34782
  // Switch to the sheet where the match is located
34769
34783
  if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
34784
+ // We set `preserveSelectedMatchIndex` to true to avoid resetting the selected search
34785
+ // index in the `refreshSearch` function when a new sheet is activated. The reason being
34786
+ // that, when we automatically go back to previous sheet while performing a search, the
34787
+ // search index is reset to the first occurrence each time.
34788
+ this.preserveSelectedMatchIndex = true;
34770
34789
  this.model.dispatch("ACTIVATE_SHEET", {
34771
34790
  sheetIdFrom: this.getters.getActiveSheetId(),
34772
34791
  sheetIdTo: selectedMatch.sheetId,
34773
34792
  });
34793
+ this.preserveSelectedMatchIndex = false;
34774
34794
  // We do not want to reset the selection at finalize in this case
34775
34795
  this.isSearchDirty = false;
34776
34796
  }
@@ -35411,6 +35431,10 @@ class CogWheelMenu extends owl.Component {
35411
35431
  }
35412
35432
  });
35413
35433
  }
35434
+ onClick(item) {
35435
+ item.onClick();
35436
+ this.popover.isOpen = false;
35437
+ }
35414
35438
  get popoverProps() {
35415
35439
  const { x, y, width, height } = this.buttonRef.el.getBoundingClientRect();
35416
35440
  return {
@@ -35453,16 +35477,22 @@ class PivotTitleSection extends owl.Component {
35453
35477
  static components = { CogWheelMenu, Section, EditableName };
35454
35478
  static props = {
35455
35479
  pivotId: String,
35480
+ flipAxis: Function,
35456
35481
  };
35457
35482
  get cogWheelMenuItems() {
35458
35483
  return [
35459
35484
  {
35460
- name: "Duplicate",
35485
+ name: _t("Flip axes"),
35486
+ icon: "fa-exchange",
35487
+ onClick: this.props.flipAxis,
35488
+ },
35489
+ {
35490
+ name: _t("Duplicate"),
35461
35491
  icon: "fa-copy",
35462
35492
  onClick: () => this.duplicatePivot(),
35463
35493
  },
35464
35494
  {
35465
- name: "Delete",
35495
+ name: _t("Delete"),
35466
35496
  icon: "fa-trash",
35467
35497
  onClick: () => this.delete(),
35468
35498
  },
@@ -35659,9 +35689,10 @@ class SpreadsheetPivotTable {
35659
35689
  columns;
35660
35690
  rows;
35661
35691
  measures;
35692
+ fieldsType;
35662
35693
  maxIndent;
35663
35694
  pivotCells = {};
35664
- constructor(columns, rows, measures) {
35695
+ constructor(columns, rows, measures, fieldsType) {
35665
35696
  this.columns = columns.map((row) => {
35666
35697
  // offset in the pivot table
35667
35698
  // starts at 1 because the first column is the row title
@@ -35674,6 +35705,7 @@ class SpreadsheetPivotTable {
35674
35705
  });
35675
35706
  this.rows = rows;
35676
35707
  this.measures = measures;
35708
+ this.fieldsType = fieldsType;
35677
35709
  this.maxIndent = Math.max(...this.rows.map((row) => row.indent));
35678
35710
  }
35679
35711
  /**
@@ -35720,7 +35752,7 @@ class SpreadsheetPivotTable {
35720
35752
  if (!domain) {
35721
35753
  return EMPTY_PIVOT_CELL;
35722
35754
  }
35723
- const measure = domain.at(-1)?.value.toString() || "";
35755
+ const measure = domain.at(-1)?.value?.toString() || "";
35724
35756
  return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35725
35757
  }
35726
35758
  else if (row <= colHeadersHeight - 1) {
@@ -35752,9 +35784,13 @@ class SpreadsheetPivotTable {
35752
35784
  return undefined;
35753
35785
  }
35754
35786
  for (let i = 0; i < pivotCol.fields.length; i++) {
35787
+ const fieldWithGranularity = pivotCol.fields[i];
35788
+ const { name, granularity } = parseDimension(fieldWithGranularity);
35789
+ const type = this.fieldsType[name] || "char";
35755
35790
  domain.push({
35756
- field: pivotCol.fields[i],
35757
- value: pivotCol.values[i],
35791
+ type,
35792
+ field: fieldWithGranularity,
35793
+ value: toNormalizedPivotValue({ displayName: name, type, granularity }, pivotCol.values[i]),
35758
35794
  });
35759
35795
  }
35760
35796
  return domain;
@@ -35766,17 +35802,21 @@ class SpreadsheetPivotTable {
35766
35802
  getColMeasure(col) {
35767
35803
  const domain = this.getColHeaderDomain(col, this.columns.length - 1);
35768
35804
  const measure = domain?.at(-1)?.value;
35769
- if (measure === undefined) {
35770
- throw new Error("Measure isd missing");
35805
+ if (measure === undefined || measure === null) {
35806
+ throw new Error("Measure is missing");
35771
35807
  }
35772
35808
  return measure.toString();
35773
35809
  }
35774
35810
  getRowDomain(row) {
35775
35811
  const domain = [];
35776
35812
  for (let i = 0; i < this.rows[row].fields.length; i++) {
35813
+ const fieldWithGranularity = this.rows[row].fields[i];
35814
+ const { name, granularity } = parseDimension(fieldWithGranularity);
35815
+ const type = this.fieldsType[name] || "char";
35777
35816
  domain.push({
35778
- field: this.rows[row].fields[i],
35779
- value: this.rows[row].values[i],
35817
+ type,
35818
+ field: fieldWithGranularity,
35819
+ value: toNormalizedPivotValue({ displayName: name, type, granularity }, this.rows[row].values[i]),
35780
35820
  });
35781
35821
  }
35782
35822
  return domain;
@@ -35786,6 +35826,7 @@ class SpreadsheetPivotTable {
35786
35826
  cols: this.columns,
35787
35827
  rows: this.rows,
35788
35828
  measures: this.measures,
35829
+ fieldsType: this.fieldsType,
35789
35830
  };
35790
35831
  }
35791
35832
  }
@@ -35806,7 +35847,14 @@ function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
35806
35847
  indent: 0,
35807
35848
  });
35808
35849
  const measureNames = definition.measures.map((m) => m.name);
35809
- return new SpreadsheetPivotTable(cols, rows, measureNames);
35850
+ const fieldsType = {};
35851
+ for (const columns of definition.columns) {
35852
+ fieldsType[columns.name] = columns.type;
35853
+ }
35854
+ for (const row of definition.rows) {
35855
+ fieldsType[row.name] = row.type;
35856
+ }
35857
+ return new SpreadsheetPivotTable(cols, rows, measureNames, fieldsType);
35810
35858
  }
35811
35859
  // -----------------------------------------------------------------------------
35812
35860
  // ROWS
@@ -35982,41 +36030,42 @@ function compareDimensionValues(dimension, a, b) {
35982
36030
  return dimension.order === "asc" ? a.localeCompare(b) : b.localeCompare(a);
35983
36031
  }
35984
36032
 
36033
+ const NULL_SYMBOL = Symbol("NULL");
35985
36034
  function createDate(dimension, value, locale) {
35986
36035
  const granularity = dimension.granularity;
35987
36036
  if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
35988
36037
  throw new Error(`Unknown date granularity: ${granularity}`);
35989
36038
  }
35990
- if (value === null) {
35991
- return null;
35992
- }
36039
+ const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
35993
36040
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35994
36041
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35995
- const date = toJsDate(value, locale);
35996
- let number = 0;
35997
- switch (granularity) {
35998
- case "year_number":
35999
- number = date.getFullYear();
36000
- break;
36001
- case "quarter_number":
36002
- number = Math.floor(date.getMonth() / 3) + 1;
36003
- break;
36004
- case "month_number":
36005
- number = date.getMonth() + 1;
36006
- break;
36007
- case "iso_week_number":
36008
- number = date.getIsoWeek();
36009
- break;
36010
- case "day_of_month":
36011
- number = date.getDate();
36012
- break;
36013
- case "day":
36014
- number = Math.floor(toNumber(value, locale));
36015
- break;
36042
+ let number = null;
36043
+ if (typeof value === "number" || typeof value === "string") {
36044
+ const date = toJsDate(value, locale);
36045
+ switch (granularity) {
36046
+ case "year_number":
36047
+ number = date.getFullYear();
36048
+ break;
36049
+ case "quarter_number":
36050
+ number = Math.floor(date.getMonth() / 3) + 1;
36051
+ break;
36052
+ case "month_number":
36053
+ number = date.getMonth() + 1;
36054
+ break;
36055
+ case "iso_week_number":
36056
+ number = date.getIsoWeek();
36057
+ break;
36058
+ case "day_of_month":
36059
+ number = date.getDate();
36060
+ break;
36061
+ case "day":
36062
+ number = Math.floor(toNumber(value, locale));
36063
+ break;
36064
+ }
36016
36065
  }
36017
- MAP_VALUE_DIMENSION_DATE[granularity].values[`${value}`] = toNormalizedPivotValue(dimension, number);
36066
+ MAP_VALUE_DIMENSION_DATE[granularity].values[keyInMap] = toNormalizedPivotValue(dimension, number);
36018
36067
  }
36019
- return MAP_VALUE_DIMENSION_DATE[granularity].values[`${value}`];
36068
+ return MAP_VALUE_DIMENSION_DATE[granularity].values[keyInMap];
36020
36069
  }
36021
36070
  /**
36022
36071
  * This map is used to cache the different values of a pivot date value
@@ -36198,6 +36247,25 @@ class SpreadsheetPivot {
36198
36247
  }
36199
36248
  return undefined;
36200
36249
  }
36250
+ areDomainArgsFieldsValid(args) {
36251
+ let dimensions = args.filter((_, index) => index % 2 === 0).map(toString);
36252
+ if (dimensions.length && dimensions.at(-1) === "measure") {
36253
+ dimensions = dimensions.slice(0, -1);
36254
+ }
36255
+ return areDomainArgsFieldsValid(dimensions, this.definition);
36256
+ }
36257
+ parseArgsToPivotDomain(args) {
36258
+ const domain = [];
36259
+ for (let i = 0; i < args.length - 1; i += 2) {
36260
+ const fieldWithGranularity = toString(args[i]);
36261
+ const type = this.getTypeOfDimension(fieldWithGranularity);
36262
+ const normalizedValue = fieldWithGranularity === "measure"
36263
+ ? toString(args[i + 1])
36264
+ : toNormalizedPivotValue(this.getDimension(fieldWithGranularity), args[i + 1]);
36265
+ domain.push({ field: fieldWithGranularity, value: normalizedValue, type });
36266
+ }
36267
+ return domain;
36268
+ }
36201
36269
  markAsDirtyForEvaluation() {
36202
36270
  this.needsReevaluation = true;
36203
36271
  }
@@ -36219,18 +36287,12 @@ class SpreadsheetPivot {
36219
36287
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
36220
36288
  if (dimension.type === "date") {
36221
36289
  const adapter = pivotTimeAdapter(dimension.granularity);
36222
- return {
36223
- value: lastNode.value !== "null"
36224
- ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36225
- : _t("(Undefined)"),
36226
- format: adapter.getFormat(this.getters.getLocale()),
36227
- };
36290
+ return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
36228
36291
  }
36229
36292
  if (!finalCell) {
36230
36293
  return { value: "" };
36231
36294
  }
36232
- // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36233
- if (finalCell.value === null || finalCell.value === `${null}`) {
36295
+ if (finalCell.value === null) {
36234
36296
  return { value: _t("(Undefined)") };
36235
36297
  }
36236
36298
  return {
@@ -36281,14 +36343,24 @@ class SpreadsheetPivot {
36281
36343
  getFields() {
36282
36344
  return this.fields;
36283
36345
  }
36346
+ getTypeOfDimension(fieldWithGranularity) {
36347
+ if (fieldWithGranularity === "measure") {
36348
+ return "char";
36349
+ }
36350
+ const { name } = parseDimension(fieldWithGranularity);
36351
+ const type = this.fields[name]?.type;
36352
+ if (!type) {
36353
+ throw new Error(`Field ${name} does not exist`);
36354
+ }
36355
+ return type;
36356
+ }
36284
36357
  filterDataEntriesFromDomain(dataEntries, domain) {
36285
36358
  return domain.reduce((current, acc) => this.filterDataEntriesFromDomainNode(current, acc), dataEntries);
36286
36359
  }
36287
36360
  filterDataEntriesFromDomainNode(dataEntries, domain) {
36288
36361
  const { field, value } = domain;
36289
- const dimension = this.getDimension(field);
36290
- return dataEntries.filter((entry) => `${entry[dimension.nameWithGranularity]?.value}` ===
36291
- `${toNormalizedPivotValue(dimension, value)}`);
36362
+ const { nameWithGranularity } = this.getDimension(field);
36363
+ return dataEntries.filter((entry) => entry[nameWithGranularity]?.value === value);
36292
36364
  }
36293
36365
  getDimension(nameWithGranularity) {
36294
36366
  return this.definition.getDimension(nameWithGranularity);
@@ -36393,7 +36465,7 @@ class SpreadsheetPivot {
36393
36465
  for (const entry of dataEntries) {
36394
36466
  for (const dimension of dateDimensions) {
36395
36467
  entry[dimension.nameWithGranularity] = {
36396
- value: `${createDate(dimension, entry[dimension.name]?.value || null, this.getters.getLocale())}`,
36468
+ value: createDate(dimension, entry[dimension.name]?.value || null, this.getters.getLocale()),
36397
36469
  type: entry[dimension.name]?.type || CellValueType.empty,
36398
36470
  format: entry[dimension.name]?.format,
36399
36471
  };
@@ -36440,11 +36512,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
36440
36512
  }
36441
36513
  }
36442
36514
  get fields() {
36443
- const fields = this.pivot.getFields();
36444
- if (!fields) {
36445
- throw new Error("Fields not found");
36446
- }
36447
- return fields;
36515
+ return this.pivot.getFields();
36448
36516
  }
36449
36517
  get pivot() {
36450
36518
  return this.getters.getPivot(this.pivotId);
@@ -36678,6 +36746,13 @@ class PivotSpreadsheetSidePanel extends owl.Component {
36678
36746
  this.store.applyUpdate();
36679
36747
  }
36680
36748
  }
36749
+ flipAxis() {
36750
+ const { rows, columns } = this.definition;
36751
+ this.onDimensionsUpdated({
36752
+ rows: columns,
36753
+ columns: rows,
36754
+ });
36755
+ }
36681
36756
  onDimensionsUpdated(definition) {
36682
36757
  this.store.update(definition);
36683
36758
  }
@@ -51549,8 +51624,8 @@ class PivotCorePlugin extends CorePlugin {
51549
51624
  case "INSERT_PIVOT": {
51550
51625
  const { sheetId, col, row, pivotId, table } = cmd;
51551
51626
  const position = { sheetId, col, row };
51552
- const { cols, rows, measures } = table;
51553
- const spTable = new SpreadsheetPivotTable(cols, rows, measures);
51627
+ const { cols, rows, measures, fieldsType } = table;
51628
+ const spTable = new SpreadsheetPivotTable(cols, rows, measures, fieldsType || {});
51554
51629
  const formulaId = this.getPivotFormulaId(pivotId);
51555
51630
  this.insertPivot(position, formulaId, spTable);
51556
51631
  break;
@@ -51631,7 +51706,7 @@ class PivotCorePlugin extends CorePlugin {
51631
51706
  sheetId: position.sheetId,
51632
51707
  col: position.col + col,
51633
51708
  row: position.row + row,
51634
- content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51709
+ content: createPivotFormula(formulaId, pivotCell),
51635
51710
  });
51636
51711
  }
51637
51712
  }
@@ -54897,7 +54972,6 @@ class PivotUIPlugin extends UIPlugin {
54897
54972
  "getPivotIdFromPosition",
54898
54973
  "getPivotCellFromPosition",
54899
54974
  "isPivotUnused",
54900
- "areDomainArgsFieldsValid",
54901
54975
  "isSpillPivotFormula",
54902
54976
  ];
54903
54977
  pivots = {};
@@ -55029,19 +55103,19 @@ class PivotUIPlugin extends UIPlugin {
55029
55103
  return EMPTY_PIVOT_CELL;
55030
55104
  }
55031
55105
  const { functionName, args } = result;
55106
+ const formulaId = args[0];
55107
+ if (!formulaId) {
55108
+ return EMPTY_PIVOT_CELL;
55109
+ }
55110
+ const pivotId = this.getters.getPivotId(formulaId.toString());
55111
+ if (!pivotId) {
55112
+ return EMPTY_PIVOT_CELL;
55113
+ }
55114
+ const pivot = this.getPivot(pivotId);
55115
+ if (!pivot.isValid()) {
55116
+ return EMPTY_PIVOT_CELL;
55117
+ }
55032
55118
  if (functionName === "PIVOT") {
55033
- const formulaId = args[0];
55034
- if (!formulaId) {
55035
- return EMPTY_PIVOT_CELL;
55036
- }
55037
- const pivotId = this.getters.getPivotId(formulaId.toString());
55038
- if (!pivotId) {
55039
- return EMPTY_PIVOT_CELL;
55040
- }
55041
- const pivot = this.getPivot(pivotId);
55042
- if (!pivot.isValid()) {
55043
- return EMPTY_PIVOT_CELL;
55044
- }
55045
55119
  const includeTotal = args[2] === false ? false : undefined;
55046
55120
  const includeColumnHeaders = args[3] === false ? false : undefined;
55047
55121
  const pivotCells = pivot
@@ -55052,7 +55126,7 @@ class PivotUIPlugin extends UIPlugin {
55052
55126
  return pivotCells[pivotCol][pivotRow];
55053
55127
  }
55054
55128
  if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55055
- const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55129
+ const domain = pivot.parseArgsToPivotDomain(args.slice(1, -2).map((value) => ({ value })));
55056
55130
  return {
55057
55131
  type: "MEASURE_HEADER",
55058
55132
  domain,
@@ -55060,15 +55134,17 @@ class PivotUIPlugin extends UIPlugin {
55060
55134
  };
55061
55135
  }
55062
55136
  else if (functionName === "PIVOT.HEADER") {
55137
+ const domain = pivot.parseArgsToPivotDomain(args.slice(1).map((value) => ({ value })));
55063
55138
  return {
55064
55139
  type: "HEADER",
55065
- domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55140
+ domain,
55066
55141
  };
55067
55142
  }
55068
55143
  const [measure, ...domainArgs] = args.slice(1);
55144
+ const domain = pivot.parseArgsToPivotDomain(domainArgs.map((value) => ({ value })));
55069
55145
  return {
55070
55146
  type: "VALUE",
55071
- domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55147
+ domain,
55072
55148
  measure: measure?.toString() || "",
55073
55149
  };
55074
55150
  }
@@ -55078,32 +55154,6 @@ class PivotUIPlugin extends UIPlugin {
55078
55154
  isPivotUnused(pivotId) {
55079
55155
  return this._getUnusedPivots().includes(pivotId);
55080
55156
  }
55081
- /**
55082
- * Check if the fields in the domain part of
55083
- * a pivot function are valid according to the pivot definition.
55084
- * e.g. =PIVOT.VALUE(1,"revenue","country_id",...,"create_date:month",...,"source_id",...)
55085
- */
55086
- areDomainArgsFieldsValid(pivotId, domain) {
55087
- const dimensions = domain
55088
- .map((node) => node.field)
55089
- .map((name) => (name.startsWith("#") ? name.slice(1) : name));
55090
- let argIndex = 0;
55091
- let definitionIndex = 0;
55092
- const pivot = this.getPivot(pivotId);
55093
- const definition = pivot.definition;
55094
- const cols = definition.columns.map((col) => col.nameWithGranularity);
55095
- const rows = definition.rows.map((row) => row.nameWithGranularity);
55096
- while (dimensions[argIndex] !== undefined && dimensions[argIndex] === rows[definitionIndex]) {
55097
- argIndex++;
55098
- definitionIndex++;
55099
- }
55100
- definitionIndex = 0;
55101
- while (dimensions[argIndex] !== undefined && dimensions[argIndex] === cols[definitionIndex]) {
55102
- argIndex++;
55103
- definitionIndex++;
55104
- }
55105
- return dimensions.length === argIndex;
55106
- }
55107
55157
  // ---------------------------------------------------------------------
55108
55158
  // Private
55109
55159
  // ---------------------------------------------------------------------
@@ -58586,32 +58636,32 @@ class ClipboardPlugin extends UIPlugin {
58586
58636
  }
58587
58637
  }
58588
58638
  convertOSClipboardData(clipboardData) {
58589
- const handlers = clipboardHandlersRegistries.figureHandlers
58590
- .getAll()
58591
- .map((handler) => new handler(this.getters, this.dispatch));
58592
- clipboardHandlersRegistries.cellHandlers
58593
- .getAll()
58594
- .forEach((handler) => handlers.push(new handler(this.getters, this.dispatch)));
58639
+ const handlers = this.selectClipboardHandlers({ figureId: true }).concat(this.selectClipboardHandlers({}));
58595
58640
  let copiedData = {};
58596
- for (const handler of handlers) {
58641
+ for (const { handlerName, handler } of handlers) {
58597
58642
  const data = handler.convertOSClipboardData(clipboardData);
58598
- copiedData = { ...copiedData, ...data };
58643
+ copiedData[handlerName] = data;
58644
+ const minimalKeys = ["sheetId", "cells", "zones", "figureId"];
58645
+ for (const key of minimalKeys) {
58646
+ if (data && key in data) {
58647
+ copiedData[key] = data[key];
58648
+ }
58649
+ }
58599
58650
  }
58600
58651
  return copiedData;
58601
58652
  }
58602
58653
  selectClipboardHandlers(data) {
58603
- if ("figureId" in data) {
58604
- return clipboardHandlersRegistries.figureHandlers
58605
- .getAll()
58606
- .map((handler) => new handler(this.getters, this.dispatch));
58607
- }
58608
- return clipboardHandlersRegistries.cellHandlers
58609
- .getAll()
58610
- .map((handler) => new handler(this.getters, this.dispatch));
58654
+ const handlersRegistry = "figureId" in data
58655
+ ? clipboardHandlersRegistries.figureHandlers
58656
+ : clipboardHandlersRegistries.cellHandlers;
58657
+ return handlersRegistry.getKeys().map((handlerName) => {
58658
+ const Handler = handlersRegistry.get(handlerName);
58659
+ return { handlerName, handler: new Handler(this.getters, this.dispatch) };
58660
+ });
58611
58661
  }
58612
58662
  isCutAllowedOn(zones) {
58613
58663
  const clipboardData = this.getClipboardData(zones);
58614
- for (const handler of this.selectClipboardHandlers(clipboardData)) {
58664
+ for (const { handler } of this.selectClipboardHandlers(clipboardData)) {
58615
58665
  const result = handler.isCutAllowed(clipboardData);
58616
58666
  if (result !== "Success" /* CommandResult.Success */) {
58617
58667
  return result;
@@ -58620,7 +58670,7 @@ class ClipboardPlugin extends UIPlugin {
58620
58670
  return "Success" /* CommandResult.Success */;
58621
58671
  }
58622
58672
  isPasteAllowed(target, copiedData, options) {
58623
- for (const handler of this.selectClipboardHandlers(copiedData)) {
58673
+ for (const { handler } of this.selectClipboardHandlers(copiedData)) {
58624
58674
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
58625
58675
  ...options,
58626
58676
  });
@@ -58648,9 +58698,15 @@ class ClipboardPlugin extends UIPlugin {
58648
58698
  copy(zones) {
58649
58699
  let copiedData = {};
58650
58700
  const clipboardData = this.getClipboardData(zones);
58651
- for (const handler of this.selectClipboardHandlers(clipboardData)) {
58701
+ for (const { handlerName, handler } of this.selectClipboardHandlers(clipboardData)) {
58652
58702
  const data = handler.copy(clipboardData);
58653
- copiedData = { ...copiedData, ...data };
58703
+ copiedData[handlerName] = data;
58704
+ const minimalKeys = ["sheetId", "cells", "zones", "figureId"];
58705
+ for (const key of minimalKeys) {
58706
+ if (data && key in data) {
58707
+ copiedData[key] = data[key];
58708
+ }
58709
+ }
58654
58710
  }
58655
58711
  return copiedData;
58656
58712
  }
@@ -58666,8 +58722,12 @@ class ClipboardPlugin extends UIPlugin {
58666
58722
  zones,
58667
58723
  };
58668
58724
  const handlers = this.selectClipboardHandlers(copiedData);
58669
- for (const handler of handlers) {
58670
- const currentTarget = handler.getPasteTarget(sheetId, zones, copiedData, options);
58725
+ for (const { handlerName, handler } of handlers) {
58726
+ const handlerData = copiedData[handlerName];
58727
+ if (!handlerData) {
58728
+ continue;
58729
+ }
58730
+ const currentTarget = handler.getPasteTarget(sheetId, zones, handlerData, options);
58671
58731
  if (currentTarget.figureId) {
58672
58732
  target.figureId = currentTarget.figureId;
58673
58733
  }
@@ -58683,7 +58743,12 @@ class ClipboardPlugin extends UIPlugin {
58683
58743
  if (zone !== undefined) {
58684
58744
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
58685
58745
  }
58686
- handlers.forEach((handler) => handler.paste(target, copiedData, options));
58746
+ handlers.forEach(({ handlerName, handler }) => {
58747
+ const handlerData = copiedData[handlerName];
58748
+ if (handlerData) {
58749
+ handler.paste(target, handlerData, options);
58750
+ }
58751
+ });
58687
58752
  if (!options?.selectTarget) {
58688
58753
  return;
58689
58754
  }
@@ -66803,6 +66868,7 @@ class Model extends EventBus {
66803
66868
  const start = performance.now();
66804
66869
  console.group("Model creation");
66805
66870
  super();
66871
+ setDefaultTranslationMethod();
66806
66872
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
66807
66873
  const workbookData = load(data, verboseImport);
66808
66874
  this.state = new StateObserver();
@@ -67298,6 +67364,7 @@ const registries = {
67298
67364
  pivotSidePanelRegistry,
67299
67365
  pivotNormalizationValueRegistry,
67300
67366
  supportedPivotPositionalFormulaRegistry,
67367
+ pivotToFunctionValueRegistry,
67301
67368
  };
67302
67369
  const helpers = {
67303
67370
  arg,
@@ -67343,7 +67410,6 @@ const helpers = {
67343
67410
  expandZoneOnInsertion,
67344
67411
  reduceZoneOnDeletion,
67345
67412
  unquote,
67346
- makePivotFormula,
67347
67413
  getMaxObjectId,
67348
67414
  getFunctionsFromTokens,
67349
67415
  getFirstPivotFunction,
@@ -67355,10 +67421,10 @@ const helpers = {
67355
67421
  insertTokenAfterLeftParenthesis,
67356
67422
  mergeContiguousZones,
67357
67423
  getPivotHighlights,
67358
- toPivotDomain,
67359
- flatPivotDomain,
67360
67424
  pivotTimeAdapter,
67361
67425
  UNDO_REDO_PIVOT_COMMANDS,
67426
+ createPivotFormula,
67427
+ areDomainArgsFieldsValid,
67362
67428
  };
67363
67429
  const links = {
67364
67430
  isMarkdownLink,
@@ -67489,6 +67555,6 @@ exports.tokenColors = tokenColors;
67489
67555
  exports.tokenize = tokenize;
67490
67556
 
67491
67557
 
67492
- __info__.version = "17.4.0-alpha.7";
67493
- __info__.date = "2024-06-21T08:42:22.370Z";
67494
- __info__.hash = "a99db9a";
67558
+ __info__.version = "17.4.0-alpha.8";
67559
+ __info__.date = "2024-06-24T19:51:16.144Z";
67560
+ __info__.hash = "ccd30df";