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