@odoo/o-spreadsheet 17.4.0-alpha.6 → 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.
@@ -1,11 +1,10 @@
1
1
 
2
- // @odoo-module ignore
3
2
  /**
4
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
4
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.4.0-alpha.6
7
- * @date 2024-06-19T13:46:27.157Z
8
- * @hash a4f22e4
5
+ * @version 17.4.0-alpha.8
6
+ * @date 2024-06-24T19:51:16.144Z
7
+ * @hash ccd30df
9
8
  */
10
9
 
11
10
  'use strict';
@@ -1904,9 +1903,10 @@ function percentile(values, percent, isInclusive) {
1904
1903
  sortedValues[indexLow] * (indexSup - percentIndex));
1905
1904
  }
1906
1905
 
1907
- // define a mock translation function, when o-spreadsheet runs in standalone it doesn't translate any string
1908
- let _translate = (s) => s;
1909
- let _loaded = () => false;
1906
+ const defaultTranslate = (s) => s;
1907
+ const defaultLoaded = () => false;
1908
+ let _translate = defaultTranslate;
1909
+ let _loaded = defaultLoaded;
1910
1910
  function sprintf(s, ...values) {
1911
1911
  if (values.length === 1 && typeof values[0] === "object" && !(values[0] instanceof String)) {
1912
1912
  const valuesDict = values[0];
@@ -1918,7 +1918,8 @@ function sprintf(s, ...values) {
1918
1918
  return s;
1919
1919
  }
1920
1920
  /***
1921
- * 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.
1922
1923
  * @param tfn the function that will do the translation
1923
1924
  * @param loaded a function that returns true when the translation is loaded
1924
1925
  */
@@ -1926,6 +1927,18 @@ function setTranslationMethod(tfn, loaded = () => true) {
1926
1927
  _translate = tfn;
1927
1928
  _loaded = loaded;
1928
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
+ }
1929
1942
  const _t = function (s, ...values) {
1930
1943
  if (!_loaded()) {
1931
1944
  return new LazyTranslatedString(s, values);
@@ -5621,18 +5634,12 @@ class BorderClipboardHandler extends AbstractCellClipboardHandler {
5621
5634
  return { borders };
5622
5635
  }
5623
5636
  paste(target, content, options) {
5624
- if (!content) {
5625
- return;
5626
- }
5627
5637
  const sheetId = target.sheetId;
5628
- if (options?.pasteOption === "asValue") {
5629
- return;
5630
- }
5631
- if (!("borders" in content) || !("zones" in target) || !target.zones.length) {
5638
+ if (options.pasteOption === "asValue") {
5632
5639
  return;
5633
5640
  }
5634
5641
  const zones = target.zones;
5635
- if (!options?.isCutOperation) {
5642
+ if (!options.isCutOperation) {
5636
5643
  this.pasteFromCopy(sheetId, zones, content.borders);
5637
5644
  }
5638
5645
  else {
@@ -6143,6 +6150,530 @@ function _localizeFormula(formula, fromLocale, toLocale) {
6143
6150
  return localizedFormula;
6144
6151
  }
6145
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
+
6146
6677
  class CellClipboardHandler extends AbstractCellClipboardHandler {
6147
6678
  isCutAllowed(data) {
6148
6679
  if (data.zones.length !== 1) {
@@ -6151,40 +6682,48 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6151
6682
  return "Success" /* CommandResult.Success */;
6152
6683
  }
6153
6684
  copy(data) {
6154
- if (!("zones" in data) || !data.zones.length) {
6155
- return;
6156
- }
6157
6685
  const sheetId = data.sheetId;
6158
- const zones = data.zones;
6159
- if (!zones.length) {
6160
- return {
6161
- cells: [[]],
6162
- zones: [],
6163
- sheetId,
6164
- };
6165
- }
6166
6686
  const { clippedZones, rowsIndexes, columnsIndexes } = data;
6167
6687
  const clippedCells = [];
6688
+ const isCopyingOneCell = rowsIndexes.length == 1 && columnsIndexes.length == 1;
6168
6689
  for (let row of rowsIndexes) {
6169
6690
  let cellsInRow = [];
6170
6691
  for (let col of columnsIndexes) {
6171
6692
  const position = { col, row, sheetId };
6172
- const spreader = this.getters.getArrayFormulaSpreadingOn(position);
6173
6693
  let cell = this.getters.getCell(position);
6174
6694
  const evaluatedCell = this.getters.getEvaluatedCell(position);
6175
- if (spreader && !deepEquals(spreader, position)) {
6176
- const isSpreaderCopied = rowsIndexes.includes(spreader.row) && columnsIndexes.includes(spreader.col);
6177
- const content = isSpreaderCopied
6178
- ? ""
6179
- : formatValue(evaluatedCell.value, { locale: this.getters.getLocale() });
6180
- cell = {
6181
- id: cell?.id || "",
6182
- style: cell?.style,
6183
- format: evaluatedCell.format,
6184
- content,
6185
- isFormula: false,
6186
- parsedValue: evaluatedCell.value,
6187
- };
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
+ }
6188
6727
  }
6189
6728
  cellsInRow.push({
6190
6729
  cell,
@@ -6233,12 +6772,9 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6233
6772
  * Paste the clipboard content in the given target
6234
6773
  */
6235
6774
  paste(target, content, options) {
6236
- if (!("cells" in content) || !("zones" in target) || !target.zones.length) {
6237
- return;
6238
- }
6239
6775
  const zones = target.zones;
6240
6776
  const sheetId = target.sheetId;
6241
- if (!options?.isCutOperation) {
6777
+ if (!options.isCutOperation) {
6242
6778
  this.pasteFromCopy(sheetId, zones, content.cells, options);
6243
6779
  }
6244
6780
  else {
@@ -6408,14 +6944,11 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
6408
6944
  };
6409
6945
  }
6410
6946
  getPasteTarget(sheetId, target, content, options) {
6411
- if (!content?.copiedFigure || !content?.copiedChart) {
6412
- return { zones: [], sheetId };
6413
- }
6414
6947
  const newId = new UuidGenerator().uuidv4();
6415
6948
  return { zones: [], figureId: newId, sheetId };
6416
6949
  }
6417
6950
  paste(target, clippedContent, options) {
6418
- if (!clippedContent?.copiedFigure || !clippedContent?.copiedChart || !target.figureId) {
6951
+ if (!target.figureId) {
6419
6952
  return;
6420
6953
  }
6421
6954
  const { zones, figureId } = target;
@@ -6439,7 +6972,7 @@ class ChartClipboardHandler extends AbstractFigureClipboardHandler {
6439
6972
  size: { height, width },
6440
6973
  definition: copy.getDefinition(),
6441
6974
  });
6442
- if (options?.isCutOperation) {
6975
+ if (options.isCutOperation) {
6443
6976
  this.dispatch("DELETE_FIGURE", {
6444
6977
  sheetId: clippedContent.copiedChart.sheetId,
6445
6978
  id: clippedContent.copiedFigure.id,
@@ -6481,15 +7014,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6481
7014
  return { cfRules };
6482
7015
  }
6483
7016
  paste(target, clippedContent, options) {
6484
- if (!clippedContent?.cfRules ||
6485
- options?.pasteOption === "asValue" ||
6486
- !("zones" in target) ||
6487
- !target.zones.length) {
7017
+ if (options.pasteOption === "asValue") {
6488
7018
  return;
6489
7019
  }
6490
7020
  const zones = target.zones;
6491
7021
  const sheetId = target.sheetId;
6492
- if (!options?.isCutOperation) {
7022
+ if (!options.isCutOperation) {
6493
7023
  this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6494
7024
  }
6495
7025
  else {
@@ -6563,9 +7093,6 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6563
7093
  class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6564
7094
  uuidGenerator = new UuidGenerator();
6565
7095
  copy(data) {
6566
- if (!data.zones.length) {
6567
- return;
6568
- }
6569
7096
  const { rowsIndexes, columnsIndexes } = data;
6570
7097
  const sheetId = data.sheetId;
6571
7098
  const dvRules = [];
@@ -6581,18 +7108,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6581
7108
  return { dvRules };
6582
7109
  }
6583
7110
  paste(target, clippedContent, options) {
6584
- if (!clippedContent?.dvRules) {
6585
- return;
6586
- }
6587
- if (options?.pasteOption) {
6588
- return;
6589
- }
6590
- if (!("zones" in target) || !target.zones.length) {
7111
+ if (options.pasteOption) {
6591
7112
  return;
6592
7113
  }
6593
7114
  const zones = target.zones;
6594
7115
  const sheetId = target.sheetId;
6595
- if (!options?.isCutOperation) {
7116
+ if (!options.isCutOperation) {
6596
7117
  this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6597
7118
  }
6598
7119
  else {
@@ -6691,14 +7212,11 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6691
7212
  };
6692
7213
  }
6693
7214
  getPasteTarget(sheetId, target, content, options) {
6694
- if (!content?.copiedFigure || !content?.copiedImage) {
6695
- return { zones: [], sheetId };
6696
- }
6697
7215
  const newId = new UuidGenerator().uuidv4();
6698
7216
  return { sheetId, zones: [], figureId: newId };
6699
7217
  }
6700
7218
  paste(target, clippedContent, options) {
6701
- if (!clippedContent?.copiedFigure || !clippedContent?.copiedImage || !target.figureId) {
7219
+ if (!target.figureId) {
6702
7220
  return;
6703
7221
  }
6704
7222
  const { zones, figureId } = target;
@@ -6722,7 +7240,7 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6722
7240
  size: { height, width },
6723
7241
  definition: copy,
6724
7242
  });
6725
- if (options?.isCutOperation) {
7243
+ if (options.isCutOperation) {
6726
7244
  this.dispatch("DELETE_FIGURE", {
6727
7245
  sheetId: clippedContent.sheetId,
6728
7246
  id: clippedContent.copiedFigure.id,
@@ -6743,9 +7261,6 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6743
7261
 
6744
7262
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6745
7263
  copy(data) {
6746
- if (!data.zones.length) {
6747
- return;
6748
- }
6749
7264
  const sheetId = this.getters.getActiveSheetId();
6750
7265
  const { rowsIndexes, columnsIndexes } = data;
6751
7266
  const merges = [];
@@ -6763,10 +7278,7 @@ class MergeClipboardHandler extends AbstractCellClipboardHandler {
6763
7278
  * Paste the clipboard content in the given target
6764
7279
  */
6765
7280
  paste(target, content, options) {
6766
- if (!content.merges ||
6767
- options?.isCutOperation ||
6768
- !("zones" in target) ||
6769
- !target.zones.length) {
7281
+ if (options.isCutOperation) {
6770
7282
  return;
6771
7283
  }
6772
7284
  this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
@@ -6822,9 +7334,6 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6822
7334
  copy(data) {
6823
7335
  const sheetId = data.sheetId;
6824
7336
  const { rowsIndexes, columnsIndexes, zones } = data;
6825
- if (!zones || !rowsIndexes.length || !columnsIndexes.length) {
6826
- return { tableCells: [[]], sheetId };
6827
- }
6828
7337
  const copiedTablesIds = new Set();
6829
7338
  const tableCells = [];
6830
7339
  for (let row of rowsIndexes) {
@@ -6880,12 +7389,9 @@ class TableClipboardHandler extends AbstractCellClipboardHandler {
6880
7389
  };
6881
7390
  }
6882
7391
  paste(target, content, options) {
6883
- if (!content || !content.tableCells) {
6884
- return;
6885
- }
6886
7392
  const zones = target.zones;
6887
7393
  const sheetId = target.sheetId;
6888
- if (!options?.isCutOperation) {
7394
+ if (!options.isCutOperation) {
6889
7395
  this.pasteFromCopy(sheetId, zones, content.tableCells, options);
6890
7396
  }
6891
7397
  else {
@@ -7778,486 +8284,6 @@ function errorCell(value, message) {
7778
8284
  };
7779
8285
  }
7780
8286
 
7781
- function boolAnd(args) {
7782
- let foundBoolean = false;
7783
- let acc = true;
7784
- conditionalVisitBoolean(args, (arg) => {
7785
- foundBoolean = true;
7786
- acc = acc && arg;
7787
- return acc;
7788
- });
7789
- return {
7790
- foundBoolean,
7791
- result: acc,
7792
- };
7793
- }
7794
- function boolOr(args) {
7795
- let foundBoolean = false;
7796
- let acc = false;
7797
- conditionalVisitBoolean(args, (arg) => {
7798
- foundBoolean = true;
7799
- acc = acc || arg;
7800
- return !acc;
7801
- });
7802
- return {
7803
- foundBoolean,
7804
- result: acc,
7805
- };
7806
- }
7807
-
7808
- function sum(values, locale) {
7809
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7810
- }
7811
- function countUnique(args) {
7812
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7813
- }
7814
-
7815
- function assertSameNumberOfElements(...args) {
7816
- const dims = args[0].length;
7817
- 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())));
7818
- }
7819
- function average(values, locale) {
7820
- let count = 0;
7821
- const sum = reduceNumbers(values, (acc, a) => {
7822
- count += 1;
7823
- return acc + a;
7824
- }, 0, locale);
7825
- assertNotZero(count);
7826
- return sum / count;
7827
- }
7828
- function countNumbers(values, locale) {
7829
- let count = 0;
7830
- for (let n of values) {
7831
- if (isMatrix(n)) {
7832
- for (let i of n) {
7833
- for (let j of i) {
7834
- if (typeof j.value === "number") {
7835
- count += 1;
7836
- }
7837
- }
7838
- }
7839
- }
7840
- else {
7841
- const value = n?.value;
7842
- if (!isEvaluationError(value) &&
7843
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7844
- count += 1;
7845
- }
7846
- }
7847
- }
7848
- return count;
7849
- }
7850
- function countAny(values) {
7851
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7852
- }
7853
- function max(values, locale) {
7854
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7855
- return result === -Infinity ? 0 : result;
7856
- }
7857
- function min(values, locale) {
7858
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7859
- return result === Infinity ? 0 : result;
7860
- }
7861
-
7862
- const pivotTimeAdapterRegistry = new Registry();
7863
- function pivotTimeAdapter(granularity) {
7864
- return pivotTimeAdapterRegistry.get(granularity);
7865
- }
7866
- /**
7867
- * The Time Adapter: Managing Time Periods for Pivot Functions
7868
- *
7869
- * Overview:
7870
- * A time adapter is responsible for managing time periods associated with pivot functions.
7871
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7872
- * The adapter's primary role is to normalize period values between spreadsheet functions,
7873
- * and the pivot.
7874
- * By normalizing the period value, it can be stored consistently in the pivot.
7875
- *
7876
- * Normalization Process:
7877
- * When working with functions in the spreadsheet, the time adapter normalizes
7878
- * the provided period to facilitate accurate lookup of values in the pivot.
7879
- * For instance, if the spreadsheet function represents a day period as a number generated
7880
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7881
- *
7882
- */
7883
- /**
7884
- * Normalized value: "12/25/2023"
7885
- *
7886
- * Note: Those two format are equivalent:
7887
- * - "MM/dd/yyyy" (luxon format)
7888
- * - "mm/dd/yyyy" (spreadsheet format)
7889
- **/
7890
- const dayAdapter = {
7891
- normalizeFunctionValue(value) {
7892
- const date = toNumber(value, DEFAULT_LOCALE);
7893
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7894
- },
7895
- getFormat(locale) {
7896
- return (locale ?? DEFAULT_LOCALE).dateFormat;
7897
- },
7898
- formatValue(normalizedValue, locale) {
7899
- locale = locale ?? DEFAULT_LOCALE;
7900
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7901
- return formatValue(value, { locale, format: this.getFormat(locale) });
7902
- },
7903
- toCellValue(normalizedValue) {
7904
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7905
- },
7906
- };
7907
- /**
7908
- * normalizes day of month number
7909
- */
7910
- const dayOfMonthAdapter = {
7911
- normalizeFunctionValue(value) {
7912
- const day = toNumber(value, DEFAULT_LOCALE);
7913
- if (day < 1 || day > 31) {
7914
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7915
- }
7916
- return day;
7917
- },
7918
- getFormat() {
7919
- return "0";
7920
- },
7921
- formatValue(normalizedValue, locale) {
7922
- locale = locale ?? DEFAULT_LOCALE;
7923
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7924
- return formatValue(value, { locale, format: this.getFormat(locale) });
7925
- },
7926
- toCellValue(normalizedValue) {
7927
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7928
- },
7929
- };
7930
- /**
7931
- * Normalized value: "2/2023" for week 2 of 2023
7932
- */
7933
- const weekAdapter = {
7934
- normalizeFunctionValue(value) {
7935
- const [week, year] = value.split("/");
7936
- return `${Number(week)}/${Number(year)}`;
7937
- },
7938
- getFormat() {
7939
- return undefined;
7940
- },
7941
- formatValue(normalizedValue) {
7942
- const [week, year] = normalizedValue.split("/");
7943
- return _t("W%(week)s %(year)s", { week, year });
7944
- },
7945
- toCellValue(normalizedValue) {
7946
- return this.formatValue(normalizedValue);
7947
- },
7948
- };
7949
- /**
7950
- * normalizes iso week number
7951
- */
7952
- const isoWeekNumberAdapter = {
7953
- normalizeFunctionValue(value) {
7954
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
7955
- if (isoWeek < 0 || isoWeek > 53) {
7956
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7957
- }
7958
- return isoWeek;
7959
- },
7960
- getFormat() {
7961
- return "0";
7962
- },
7963
- formatValue(normalizedValue, locale) {
7964
- locale = locale ?? DEFAULT_LOCALE;
7965
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7966
- return formatValue(value, { locale, format: this.getFormat(locale) });
7967
- },
7968
- toCellValue(normalizedValue) {
7969
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7970
- },
7971
- };
7972
- /**
7973
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7974
- * e.g. "01/2020" for January 2020
7975
- */
7976
- const monthAdapter = {
7977
- normalizeFunctionValue(value) {
7978
- const date = toNumber(value, DEFAULT_LOCALE);
7979
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7980
- },
7981
- getFormat() {
7982
- return "mmmm yyyy";
7983
- },
7984
- formatValue(normalizedValue, locale) {
7985
- locale = locale ?? DEFAULT_LOCALE;
7986
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7987
- return formatValue(value, { locale, format: this.getFormat(locale) });
7988
- },
7989
- toCellValue(normalizedValue) {
7990
- return toNumber(normalizedValue, DEFAULT_LOCALE);
7991
- },
7992
- };
7993
- /**
7994
- * normalizes month number
7995
- */
7996
- const monthNumberAdapter = {
7997
- normalizeFunctionValue(value) {
7998
- const month = toNumber(value, DEFAULT_LOCALE);
7999
- if (month < 1 || month > 12) {
8000
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
8001
- }
8002
- return month;
8003
- },
8004
- getFormat() {
8005
- return "0";
8006
- },
8007
- formatValue(normalizedValue, locale) {
8008
- locale = locale ?? DEFAULT_LOCALE;
8009
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8010
- return formatValue(value, { locale, format: this.getFormat(locale) });
8011
- },
8012
- toCellValue(normalizedValue) {
8013
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8014
- },
8015
- };
8016
- /**
8017
- * normalized quarter value is "quarter/year"
8018
- * e.g. "1/2020" for Q1 2020
8019
- */
8020
- const quarterAdapter = {
8021
- normalizeFunctionValue(value) {
8022
- const [quarter, year] = value.split("/");
8023
- return `${quarter}/${year}`;
8024
- },
8025
- getFormat() {
8026
- return undefined;
8027
- },
8028
- formatValue(normalizedValue) {
8029
- const [quarter, year] = normalizedValue.split("/");
8030
- return _t("Q%(quarter)s %(year)s", { quarter, year });
8031
- },
8032
- toCellValue(normalizedValue) {
8033
- return this.formatValue(normalizedValue);
8034
- },
8035
- };
8036
- /**
8037
- * normalizes quarter number
8038
- */
8039
- const quarterNumberAdapter = {
8040
- normalizeFunctionValue(value) {
8041
- const quarter = toNumber(value, DEFAULT_LOCALE);
8042
- if (quarter < 1 || quarter > 4) {
8043
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8044
- }
8045
- return quarter;
8046
- },
8047
- getFormat() {
8048
- return "0";
8049
- },
8050
- formatValue(normalizedValue, locale) {
8051
- locale = locale ?? DEFAULT_LOCALE;
8052
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8053
- return formatValue(value, { locale, format: this.getFormat(locale) });
8054
- },
8055
- toCellValue(normalizedValue) {
8056
- return toNumber(normalizedValue, DEFAULT_LOCALE);
8057
- },
8058
- };
8059
- const yearAdapter = {
8060
- normalizeFunctionValue(value) {
8061
- return toNumber(value, DEFAULT_LOCALE);
8062
- },
8063
- getFormat() {
8064
- return "0";
8065
- },
8066
- formatValue(normalizedValue, locale) {
8067
- locale = locale ?? DEFAULT_LOCALE;
8068
- return formatValue(normalizedValue, { locale, format: "0" });
8069
- },
8070
- toCellValue(normalizedValue) {
8071
- return toNumber(normalizedValue, DEFAULT_LOCALE);
8072
- },
8073
- };
8074
- pivotTimeAdapterRegistry
8075
- .add("day", dayAdapter)
8076
- .add("week", weekAdapter)
8077
- .add("month", monthAdapter)
8078
- .add("quarter", quarterAdapter)
8079
- .add("year", yearAdapter)
8080
- .add("day_of_month", dayOfMonthAdapter)
8081
- .add("iso_week_number", isoWeekNumberAdapter)
8082
- .add("month_number", monthNumberAdapter)
8083
- .add("quarter_number", quarterNumberAdapter)
8084
- .add("year_number", yearAdapter);
8085
-
8086
- const AGGREGATOR_NAMES = {
8087
- count: _t("Count"),
8088
- count_distinct: _t("Count Distinct"),
8089
- bool_and: _t("Boolean And"),
8090
- bool_or: _t("Boolean Or"),
8091
- max: _t("Maximum"),
8092
- min: _t("Minimum"),
8093
- avg: _t("Average"),
8094
- sum: _t("Sum"),
8095
- };
8096
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8097
- const AGGREGATORS_BY_FIELD_TYPE = {
8098
- integer: NUMBER_CHAR_AGGREGATORS,
8099
- char: NUMBER_CHAR_AGGREGATORS,
8100
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8101
- };
8102
- const AGGREGATORS = {};
8103
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8104
- AGGREGATORS[type] = {};
8105
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8106
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8107
- }
8108
- }
8109
- const AGGREGATORS_FN = {
8110
- count: {
8111
- fn: (args) => countAny([args]),
8112
- format: () => "0",
8113
- },
8114
- count_distinct: {
8115
- fn: (args) => countUnique([args]),
8116
- format: () => "0",
8117
- },
8118
- bool_and: {
8119
- fn: (args) => boolAnd([args]).result,
8120
- format: () => undefined,
8121
- },
8122
- bool_or: {
8123
- fn: (args) => boolOr([args]).result,
8124
- format: () => undefined,
8125
- },
8126
- max: {
8127
- fn: (args, locale) => max([args], locale),
8128
- format: inferFormat,
8129
- },
8130
- min: {
8131
- fn: (args, locale) => min([args], locale),
8132
- format: inferFormat,
8133
- },
8134
- avg: {
8135
- fn: (args, locale) => average([args], locale),
8136
- format: inferFormat,
8137
- },
8138
- sum: {
8139
- fn: (args, locale) => sum([args], locale),
8140
- format: inferFormat,
8141
- },
8142
- };
8143
- function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8144
- switch (pivotCell.type) {
8145
- case "HEADER":
8146
- return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8147
- case "MEASURE_HEADER":
8148
- return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8149
- case "VALUE":
8150
- return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8151
- case "EMPTY":
8152
- return "";
8153
- }
8154
- }
8155
- /**
8156
- * Build a pivot formula expression
8157
- */
8158
- function makePivotFormula(formula, args) {
8159
- return `=${formula}(${args
8160
- .map((arg) => {
8161
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8162
- const convertToNumber = typeof arg == "number" || stringIsNumber;
8163
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8164
- })
8165
- .join(",")})`;
8166
- }
8167
- /**
8168
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8169
- * in this object
8170
- * If the object has no keys, return 0
8171
- *
8172
- */
8173
- function getMaxObjectId(o) {
8174
- const keys = Object.keys(o);
8175
- if (!keys.length) {
8176
- return 0;
8177
- }
8178
- const nums = keys.map((id) => parseInt(id, 10));
8179
- const max = Math.max(...nums);
8180
- return max;
8181
- }
8182
- const ALL_PERIODS = {
8183
- year: _t("Year"),
8184
- quarter: _t("Quarter"),
8185
- month: _t("Month"),
8186
- week: _t("Week"),
8187
- day: _t("Day"),
8188
- year_number: _t("Year"),
8189
- quarter_number: _t("Quarter"),
8190
- month_number: _t("Month"),
8191
- iso_week_number: _t("Week"),
8192
- day_of_month: _t("Day of Month"),
8193
- };
8194
- const DATE_FIELDS = ["date", "datetime"];
8195
- /**
8196
- * Parse a dimension string into a pivot dimension definition.
8197
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8198
- */
8199
- function parseDimension(dimension) {
8200
- const [name, granularity] = dimension.split(":");
8201
- if (granularity) {
8202
- return { name, granularity };
8203
- }
8204
- return { name };
8205
- }
8206
- function isDateField(field) {
8207
- return DATE_FIELDS.includes(field.type);
8208
- }
8209
- function toPivotDomain(domainStr) {
8210
- if (domainStr.length % 2 !== 0) {
8211
- throw new Error("Invalid domain: odd number of elements");
8212
- }
8213
- const domain = [];
8214
- for (let i = 0; i < domainStr.length - 1; i += 2) {
8215
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8216
- }
8217
- return domain;
8218
- }
8219
- function flatPivotDomain(domain) {
8220
- return domain.flatMap((arg) => [arg.field, arg.value]);
8221
- }
8222
- /**
8223
- * Parses the value defining a pivot group in a PIVOT formula
8224
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8225
- * the two group values are "42" and "won".
8226
- */
8227
- function toNormalizedPivotValue(dimension, groupValue) {
8228
- if (groupValue === null || groupValue === "null") {
8229
- return null;
8230
- }
8231
- const groupValueString = typeof groupValue === "boolean"
8232
- ? toString(groupValue).toLocaleLowerCase()
8233
- : toString(groupValue);
8234
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8235
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8236
- field: dimension.displayName,
8237
- type: dimension.type,
8238
- }));
8239
- }
8240
- // represents a field which is not set (=False server side)
8241
- if (groupValueString === "false") {
8242
- return false;
8243
- }
8244
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8245
- return normalizer(groupValueString, dimension.granularity);
8246
- }
8247
- function normalizeDateTime(value, granularity) {
8248
- if (!granularity) {
8249
- throw "";
8250
- }
8251
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8252
- }
8253
- const pivotNormalizationValueRegistry = new Registry();
8254
- pivotNormalizationValueRegistry
8255
- .add("date", normalizeDateTime)
8256
- .add("datetime", normalizeDateTime)
8257
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8258
- .add("boolean", (value) => toBoolean(value))
8259
- .add("char", (value) => toString(value));
8260
-
8261
8287
  /**
8262
8288
  * Change the reference types inside the given token, if the token represent a range or a cell
8263
8289
  *
@@ -9997,7 +10023,7 @@ class ComposerStore extends SpreadsheetStore {
9997
10023
  const cell = this.getters.getCell(position);
9998
10024
  if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9999
10025
  const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
10000
- const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
10026
+ const formula = createPivotFormula(formulaPivotId, pivotCell);
10001
10027
  return formula.slice(1); // strip leading =
10002
10028
  }
10003
10029
  }
@@ -19089,10 +19115,9 @@ const PIVOT_VALUE = {
19089
19115
  compute: function (formulaId, measureName, ...domainArgs) {
19090
19116
  const _pivotFormulaId = toString(formulaId);
19091
19117
  const _measure = toString(measureName);
19092
- const _domainArgs = domainArgs.map(toString);
19093
19118
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
19094
19119
  assertMeasureExist(pivotId, _measure, this.getters);
19095
- assertDomainLength(_domainArgs);
19120
+ assertDomainLength(domainArgs);
19096
19121
  const pivot = this.getters.getPivot(pivotId);
19097
19122
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
19098
19123
  addPivotDependencies(this, coreDefinition);
@@ -19100,15 +19125,14 @@ const PIVOT_VALUE = {
19100
19125
  if (error) {
19101
19126
  return error;
19102
19127
  }
19103
- const domain = toPivotDomain(_domainArgs);
19104
- const { value, format } = pivot.getPivotCellValueAndFormat(_measure, domain);
19105
- if (!value && !this.getters.areDomainArgsFieldsValid(pivotId, domain)) {
19128
+ if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19106
19129
  return {
19107
19130
  value: CellErrorType.GenericError,
19108
19131
  message: _t("Dimensions don't match the pivot definition"),
19109
19132
  };
19110
19133
  }
19111
- return { value, format };
19134
+ const domain = pivot.parseArgsToPivotDomain(domainArgs);
19135
+ return pivot.getPivotCellValueAndFormat(_measure, domain);
19112
19136
  },
19113
19137
  };
19114
19138
  const PIVOT_HEADER = {
@@ -19120,9 +19144,8 @@ const PIVOT_HEADER = {
19120
19144
  ],
19121
19145
  compute: function (pivotId, ...domainArgs) {
19122
19146
  const _pivotFormulaId = toString(pivotId);
19123
- const _domainArgs = domainArgs.map(toString);
19124
19147
  const _pivotId = getPivotId(_pivotFormulaId, this.getters);
19125
- assertDomainLength(_domainArgs);
19148
+ assertDomainLength(domainArgs);
19126
19149
  const pivot = this.getters.getPivot(_pivotId);
19127
19150
  const coreDefinition = this.getters.getPivotCoreDefinition(_pivotId);
19128
19151
  addPivotDependencies(this, coreDefinition);
@@ -19130,14 +19153,14 @@ const PIVOT_HEADER = {
19130
19153
  if (error) {
19131
19154
  return error;
19132
19155
  }
19133
- const domain = toPivotDomain(_domainArgs);
19134
- const lastNode = domain.at(-1);
19135
- if (!this.getters.areDomainArgsFieldsValid(_pivotId, lastNode?.field === "measure" ? domain.slice(0, -1) : domain)) {
19156
+ if (!pivot.areDomainArgsFieldsValid(domainArgs)) {
19136
19157
  return {
19137
19158
  value: CellErrorType.GenericError,
19138
19159
  message: _t("Dimensions don't match the pivot definition"),
19139
19160
  };
19140
19161
  }
19162
+ const domain = pivot.parseArgsToPivotDomain(domainArgs);
19163
+ const lastNode = domain.at(-1);
19141
19164
  if (lastNode?.field === "measure") {
19142
19165
  return pivot.getPivotMeasureValue(toString(lastNode.value), domain);
19143
19166
  }
@@ -19154,13 +19177,21 @@ const PIVOT = {
19154
19177
  description: _t("Get a pivot table."),
19155
19178
  args: [
19156
19179
  arg("pivot_id (string)", _t("ID of the pivot.")),
19157
- arg("row_count (number, optional, default=10000)", _t("number of rows")),
19180
+ arg("row_count (number, optional)", _t("number of rows")),
19158
19181
  arg("include_total (boolean, default=TRUE)", _t("Whether to include total/sub-totals or not.")),
19159
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")),
19160
19184
  ],
19161
- 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 }) {
19162
19186
  const _pivotFormulaId = toString(pivotFormulaId);
19163
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
+ }
19164
19195
  const _includeColumnHeaders = toBoolean(includeColumnHeaders);
19165
19196
  const _includedTotal = toBoolean(includeTotal);
19166
19197
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
@@ -19176,19 +19207,15 @@ const PIVOT = {
19176
19207
  const cells = table.getPivotCells(_includedTotal, _includeColumnHeaders);
19177
19208
  const headerRows = _includeColumnHeaders ? table.columns.length : 0;
19178
19209
  const pivotTitle = this.getters.getPivotDisplayName(pivotId);
19179
- if (_rowCount < 0) {
19180
- throw new EvaluationError(_t("The number of rows must be positive."));
19181
- }
19182
- const end = Math.min(headerRows + _rowCount, cells[0].length);
19183
- if (end === 0) {
19210
+ const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
19211
+ if (tableHeight === 0) {
19184
19212
  return [[{ value: pivotTitle }]];
19185
19213
  }
19186
- const tableWidth = cells.length;
19187
- const tableRows = range(0, end);
19214
+ const tableWidth = Math.min(1 + _columnCount, cells.length);
19188
19215
  const result = [];
19189
19216
  for (const col of range(0, tableWidth)) {
19190
19217
  result[col] = [];
19191
- for (const row of tableRows) {
19218
+ for (const row of range(0, tableHeight)) {
19192
19219
  const pivotCell = cells[col][row];
19193
19220
  switch (pivotCell.type) {
19194
19221
  case "EMPTY":
@@ -21878,9 +21905,6 @@ autoCompleteProviders.add("pivot_measures", {
21878
21905
  const pivot = this.getters.getPivot(pivotId);
21879
21906
  pivot.init();
21880
21907
  const fields = pivot.getFields();
21881
- if (!fields) {
21882
- return [];
21883
- }
21884
21908
  const definition = this.getters.getPivotCoreDefinition(pivotId);
21885
21909
  return definition.measures
21886
21910
  .map((measure) => {
@@ -21920,9 +21944,6 @@ autoCompleteProviders.add("pivot_group_fields", {
21920
21944
  const pivot = this.getters.getPivot(pivotId);
21921
21945
  pivot.init();
21922
21946
  const fields = pivot.getFields();
21923
- if (!fields) {
21924
- return;
21925
- }
21926
21947
  const { columns, rows } = pivot.definition;
21927
21948
  let args = functionContext.args;
21928
21949
  if (functionContext?.parent.toUpperCase() === "PIVOT.VALUE") {
@@ -27071,7 +27092,7 @@ function getSmartChartDefinition(zone, getters) {
27071
27092
  if (getZoneArea(zone) === 1 && topLeftCell?.content) {
27072
27093
  return {
27073
27094
  type: "scorecard",
27074
- title: { text: "" },
27095
+ title: {},
27075
27096
  background: topLeftCell.style?.fillColor || undefined,
27076
27097
  keyValue: zoneToXc(zone),
27077
27098
  baselineMode: DEFAULT_SCORECARD_BASELINE_MODE,
@@ -27079,22 +27100,11 @@ function getSmartChartDefinition(zone, getters) {
27079
27100
  baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
27080
27101
  };
27081
27102
  }
27082
- let title = "";
27083
27103
  const cellsInFirstRow = getters.getEvaluatedCellsInZone(sheetId, {
27084
27104
  ...dataSetZone,
27085
27105
  bottom: dataSetZone.top,
27086
27106
  });
27087
27107
  const dataSetsHaveTitle = !!cellsInFirstRow.find((cell) => cell.type !== CellValueType.empty && cell.type !== CellValueType.number);
27088
- if (dataSetsHaveTitle) {
27089
- const texts = cellsInFirstRow
27090
- .filter((cell) => cell.type !== CellValueType.error && cell.type !== CellValueType.empty)
27091
- .map((cell) => cell.formattedValue);
27092
- const lastElement = texts.splice(-1)[0];
27093
- title = texts.join(", ");
27094
- if (lastElement) {
27095
- title += (title ? " " + _t("and") + " " : "") + lastElement;
27096
- }
27097
- }
27098
27108
  let labelRangeXc;
27099
27109
  if (!singleColumn) {
27100
27110
  labelRangeXc = zoneToXc({
@@ -27107,7 +27117,7 @@ function getSmartChartDefinition(zone, getters) {
27107
27117
  const labelRange = labelRangeXc ? getters.getRangeFromSheetXC(sheetId, labelRangeXc) : undefined;
27108
27118
  if (canChartParseLabels(labelRange, getters)) {
27109
27119
  return {
27110
- title: { text: title },
27120
+ title: {},
27111
27121
  dataSets,
27112
27122
  labelsAsText: false,
27113
27123
  stacked: false,
@@ -27123,7 +27133,7 @@ function getSmartChartDefinition(zone, getters) {
27123
27133
  if (singleColumn &&
27124
27134
  getData(getters, _dataSets[0]).every((e) => typeof e === "string" && !isEvaluationError(e))) {
27125
27135
  return {
27126
- title: { text: "" },
27136
+ title: {},
27127
27137
  dataSets: [{ dataRange }],
27128
27138
  aggregated: true,
27129
27139
  labelRange: dataRange,
@@ -27133,7 +27143,7 @@ function getSmartChartDefinition(zone, getters) {
27133
27143
  };
27134
27144
  }
27135
27145
  return {
27136
- title: { text: title },
27146
+ title: {},
27137
27147
  dataSets,
27138
27148
  labelRange: labelRangeXc,
27139
27149
  type: "bar",
@@ -34556,6 +34566,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
34556
34566
  currentSearchRegex = null;
34557
34567
  isSearchDirty = false;
34558
34568
  initialShowFormulaState;
34569
+ preserveSelectedMatchIndex = false;
34559
34570
  // fixme: why do we make selectedMatchIndex on top of a selected
34560
34571
  // property in the matches?
34561
34572
  selectedMatchIndex = null;
@@ -34628,6 +34639,10 @@ class FindAndReplaceStore extends SpreadsheetStore {
34628
34639
  case "ACTIVATE_SHEET":
34629
34640
  this.isSearchDirty = true;
34630
34641
  break;
34642
+ case "REPLACE_SEARCH":
34643
+ for (const match of cmd.matches) {
34644
+ this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
34645
+ }
34631
34646
  }
34632
34647
  }
34633
34648
  finalize() {
@@ -34665,7 +34680,9 @@ class FindAndReplaceStore extends SpreadsheetStore {
34665
34680
  * refresh the matches according to the current search options
34666
34681
  */
34667
34682
  refreshSearch(jumpToMatchSheet = true) {
34668
- this.selectedMatchIndex = null;
34683
+ if (!this.preserveSelectedMatchIndex) {
34684
+ this.selectedMatchIndex = null;
34685
+ }
34669
34686
  this.findMatches();
34670
34687
  this.selectNextCell(Direction.current, jumpToMatchSheet);
34671
34688
  }
@@ -34764,10 +34781,16 @@ class FindAndReplaceStore extends SpreadsheetStore {
34764
34781
  const selectedMatch = matches[nextIndex];
34765
34782
  // Switch to the sheet where the match is located
34766
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;
34767
34789
  this.model.dispatch("ACTIVATE_SHEET", {
34768
34790
  sheetIdFrom: this.getters.getActiveSheetId(),
34769
34791
  sheetIdTo: selectedMatch.sheetId,
34770
34792
  });
34793
+ this.preserveSelectedMatchIndex = false;
34771
34794
  // We do not want to reset the selection at finalize in this case
34772
34795
  this.isSearchDirty = false;
34773
34796
  }
@@ -34801,6 +34824,21 @@ class FindAndReplaceStore extends SpreadsheetStore {
34801
34824
  searchOptions: this.searchOptions,
34802
34825
  });
34803
34826
  }
34827
+ replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
34828
+ const cell = this.getters.getCell(selectedMatch);
34829
+ if (!cell?.content) {
34830
+ return;
34831
+ }
34832
+ if (cell?.isFormula && !searchOptions.searchFormulas) {
34833
+ return;
34834
+ }
34835
+ const searchRegex = getSearchRegex(searchString, searchOptions);
34836
+ const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
34837
+ const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
34838
+ const content = toReplace.replace(replaceRegex, replaceWith);
34839
+ const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
34840
+ this.model.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
34841
+ }
34804
34842
  getSearchableString(position) {
34805
34843
  return this.getters.getCellText(position, this.searchOptions.searchFormulas);
34806
34844
  }
@@ -35393,6 +35431,10 @@ class CogWheelMenu extends owl.Component {
35393
35431
  }
35394
35432
  });
35395
35433
  }
35434
+ onClick(item) {
35435
+ item.onClick();
35436
+ this.popover.isOpen = false;
35437
+ }
35396
35438
  get popoverProps() {
35397
35439
  const { x, y, width, height } = this.buttonRef.el.getBoundingClientRect();
35398
35440
  return {
@@ -35435,16 +35477,22 @@ class PivotTitleSection extends owl.Component {
35435
35477
  static components = { CogWheelMenu, Section, EditableName };
35436
35478
  static props = {
35437
35479
  pivotId: String,
35480
+ flipAxis: Function,
35438
35481
  };
35439
35482
  get cogWheelMenuItems() {
35440
35483
  return [
35441
35484
  {
35442
- name: "Duplicate",
35485
+ name: _t("Flip axes"),
35486
+ icon: "fa-exchange",
35487
+ onClick: this.props.flipAxis,
35488
+ },
35489
+ {
35490
+ name: _t("Duplicate"),
35443
35491
  icon: "fa-copy",
35444
35492
  onClick: () => this.duplicatePivot(),
35445
35493
  },
35446
35494
  {
35447
- name: "Delete",
35495
+ name: _t("Delete"),
35448
35496
  icon: "fa-trash",
35449
35497
  onClick: () => this.delete(),
35450
35498
  },
@@ -35552,7 +35600,7 @@ function createMeasure(fields, measure) {
35552
35600
  function createPivotDimension(fields, dimension) {
35553
35601
  const field = fields[dimension.name];
35554
35602
  const type = field?.type ?? "integer";
35555
- const granularity = field && isDateField(field) ? dimension.granularity ?? "month_number" : undefined;
35603
+ const granularity = field && isDateField(field) ? dimension.granularity : undefined;
35556
35604
  return {
35557
35605
  /**
35558
35606
  * Get the display name of the dimension
@@ -35641,9 +35689,10 @@ class SpreadsheetPivotTable {
35641
35689
  columns;
35642
35690
  rows;
35643
35691
  measures;
35692
+ fieldsType;
35644
35693
  maxIndent;
35645
35694
  pivotCells = {};
35646
- constructor(columns, rows, measures) {
35695
+ constructor(columns, rows, measures, fieldsType) {
35647
35696
  this.columns = columns.map((row) => {
35648
35697
  // offset in the pivot table
35649
35698
  // starts at 1 because the first column is the row title
@@ -35656,6 +35705,7 @@ class SpreadsheetPivotTable {
35656
35705
  });
35657
35706
  this.rows = rows;
35658
35707
  this.measures = measures;
35708
+ this.fieldsType = fieldsType;
35659
35709
  this.maxIndent = Math.max(...this.rows.map((row) => row.indent));
35660
35710
  }
35661
35711
  /**
@@ -35702,7 +35752,7 @@ class SpreadsheetPivotTable {
35702
35752
  if (!domain) {
35703
35753
  return EMPTY_PIVOT_CELL;
35704
35754
  }
35705
- const measure = domain.at(-1)?.value.toString() || "";
35755
+ const measure = domain.at(-1)?.value?.toString() || "";
35706
35756
  return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35707
35757
  }
35708
35758
  else if (row <= colHeadersHeight - 1) {
@@ -35734,9 +35784,13 @@ class SpreadsheetPivotTable {
35734
35784
  return undefined;
35735
35785
  }
35736
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";
35737
35790
  domain.push({
35738
- field: pivotCol.fields[i],
35739
- value: pivotCol.values[i],
35791
+ type,
35792
+ field: fieldWithGranularity,
35793
+ value: toNormalizedPivotValue({ displayName: name, type, granularity }, pivotCol.values[i]),
35740
35794
  });
35741
35795
  }
35742
35796
  return domain;
@@ -35748,17 +35802,21 @@ class SpreadsheetPivotTable {
35748
35802
  getColMeasure(col) {
35749
35803
  const domain = this.getColHeaderDomain(col, this.columns.length - 1);
35750
35804
  const measure = domain?.at(-1)?.value;
35751
- if (measure === undefined) {
35752
- throw new Error("Measure isd missing");
35805
+ if (measure === undefined || measure === null) {
35806
+ throw new Error("Measure is missing");
35753
35807
  }
35754
35808
  return measure.toString();
35755
35809
  }
35756
35810
  getRowDomain(row) {
35757
35811
  const domain = [];
35758
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";
35759
35816
  domain.push({
35760
- field: this.rows[row].fields[i],
35761
- value: this.rows[row].values[i],
35817
+ type,
35818
+ field: fieldWithGranularity,
35819
+ value: toNormalizedPivotValue({ displayName: name, type, granularity }, this.rows[row].values[i]),
35762
35820
  });
35763
35821
  }
35764
35822
  return domain;
@@ -35768,6 +35826,7 @@ class SpreadsheetPivotTable {
35768
35826
  cols: this.columns,
35769
35827
  rows: this.rows,
35770
35828
  measures: this.measures,
35829
+ fieldsType: this.fieldsType,
35771
35830
  };
35772
35831
  }
35773
35832
  }
@@ -35788,7 +35847,14 @@ function dataEntriesToSpreadsheetPivotTable(dataEntries, definition) {
35788
35847
  indent: 0,
35789
35848
  });
35790
35849
  const measureNames = definition.measures.map((m) => m.name);
35791
- 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);
35792
35858
  }
35793
35859
  // -----------------------------------------------------------------------------
35794
35860
  // ROWS
@@ -35964,41 +36030,42 @@ function compareDimensionValues(dimension, a, b) {
35964
36030
  return dimension.order === "asc" ? a.localeCompare(b) : b.localeCompare(a);
35965
36031
  }
35966
36032
 
36033
+ const NULL_SYMBOL = Symbol("NULL");
35967
36034
  function createDate(dimension, value, locale) {
35968
- const granularity = dimension.granularity || "month_number";
35969
- if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
36035
+ const granularity = dimension.granularity;
36036
+ if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
35970
36037
  throw new Error(`Unknown date granularity: ${granularity}`);
35971
36038
  }
35972
- if (value === null) {
35973
- return null;
35974
- }
36039
+ const keyInMap = typeof value === "number" || typeof value === "string" ? value : NULL_SYMBOL;
35975
36040
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35976
36041
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35977
- const date = toJsDate(value, locale);
35978
- let number = 0;
35979
- switch (granularity) {
35980
- case "year_number":
35981
- number = date.getFullYear();
35982
- break;
35983
- case "quarter_number":
35984
- number = Math.floor(date.getMonth() / 3) + 1;
35985
- break;
35986
- case "month_number":
35987
- number = date.getMonth() + 1;
35988
- break;
35989
- case "iso_week_number":
35990
- number = date.getIsoWeek();
35991
- break;
35992
- case "day_of_month":
35993
- number = date.getDate();
35994
- break;
35995
- case "day":
35996
- number = Math.floor(toNumber(value, locale));
35997
- 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
+ }
35998
36065
  }
35999
- MAP_VALUE_DIMENSION_DATE[granularity].values[`${value}`] = toNormalizedPivotValue(dimension, number);
36066
+ MAP_VALUE_DIMENSION_DATE[granularity].values[keyInMap] = toNormalizedPivotValue(dimension, number);
36000
36067
  }
36001
- return MAP_VALUE_DIMENSION_DATE[granularity].values[`${value}`];
36068
+ return MAP_VALUE_DIMENSION_DATE[granularity].values[keyInMap];
36002
36069
  }
36003
36070
  /**
36004
36071
  * This map is used to cache the different values of a pivot date value
@@ -36180,6 +36247,25 @@ class SpreadsheetPivot {
36180
36247
  }
36181
36248
  return undefined;
36182
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
+ }
36183
36269
  markAsDirtyForEvaluation() {
36184
36270
  this.needsReevaluation = true;
36185
36271
  }
@@ -36201,18 +36287,12 @@ class SpreadsheetPivot {
36201
36287
  const finalCell = cells[0]?.[dimension.nameWithGranularity];
36202
36288
  if (dimension.type === "date") {
36203
36289
  const adapter = pivotTimeAdapter(dimension.granularity);
36204
- return {
36205
- value: lastNode.value !== "null"
36206
- ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36207
- : _t("(Undefined)"),
36208
- format: adapter.getFormat(this.getters.getLocale()),
36209
- };
36290
+ return adapter.toValueAndFormat(lastNode.value, this.getters.getLocale());
36210
36291
  }
36211
36292
  if (!finalCell) {
36212
36293
  return { value: "" };
36213
36294
  }
36214
- // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36215
- if (finalCell.value === null || finalCell.value === `${null}`) {
36295
+ if (finalCell.value === null) {
36216
36296
  return { value: _t("(Undefined)") };
36217
36297
  }
36218
36298
  return {
@@ -36263,14 +36343,24 @@ class SpreadsheetPivot {
36263
36343
  getFields() {
36264
36344
  return this.fields;
36265
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
+ }
36266
36357
  filterDataEntriesFromDomain(dataEntries, domain) {
36267
36358
  return domain.reduce((current, acc) => this.filterDataEntriesFromDomainNode(current, acc), dataEntries);
36268
36359
  }
36269
36360
  filterDataEntriesFromDomainNode(dataEntries, domain) {
36270
36361
  const { field, value } = domain;
36271
- const dimension = this.getDimension(field);
36272
- return dataEntries.filter((entry) => `${entry[dimension.nameWithGranularity]?.value}` ===
36273
- `${toNormalizedPivotValue(dimension, value)}`);
36362
+ const { nameWithGranularity } = this.getDimension(field);
36363
+ return dataEntries.filter((entry) => entry[nameWithGranularity]?.value === value);
36274
36364
  }
36275
36365
  getDimension(nameWithGranularity) {
36276
36366
  return this.definition.getDimension(nameWithGranularity);
@@ -36375,7 +36465,7 @@ class SpreadsheetPivot {
36375
36465
  for (const entry of dataEntries) {
36376
36466
  for (const dimension of dateDimensions) {
36377
36467
  entry[dimension.nameWithGranularity] = {
36378
- value: `${createDate(dimension, entry[dimension.name]?.value || null, this.getters.getLocale())}`,
36468
+ value: createDate(dimension, entry[dimension.name]?.value || null, this.getters.getLocale()),
36379
36469
  type: entry[dimension.name]?.type || CellValueType.empty,
36380
36470
  format: entry[dimension.name]?.format,
36381
36471
  };
@@ -36422,11 +36512,7 @@ class PivotSidePanelStore extends SpreadsheetStore {
36422
36512
  }
36423
36513
  }
36424
36514
  get fields() {
36425
- const fields = this.pivot.getFields();
36426
- if (!fields) {
36427
- throw new Error("Fields not found");
36428
- }
36429
- return fields;
36515
+ return this.pivot.getFields();
36430
36516
  }
36431
36517
  get pivot() {
36432
36518
  return this.getters.getPivot(this.pivotId);
@@ -36660,6 +36746,13 @@ class PivotSpreadsheetSidePanel extends owl.Component {
36660
36746
  this.store.applyUpdate();
36661
36747
  }
36662
36748
  }
36749
+ flipAxis() {
36750
+ const { rows, columns } = this.definition;
36751
+ this.onDimensionsUpdated({
36752
+ rows: columns,
36753
+ columns: rows,
36754
+ });
36755
+ }
36663
36756
  onDimensionsUpdated(definition) {
36664
36757
  this.store.update(definition);
36665
36758
  }
@@ -51531,8 +51624,8 @@ class PivotCorePlugin extends CorePlugin {
51531
51624
  case "INSERT_PIVOT": {
51532
51625
  const { sheetId, col, row, pivotId, table } = cmd;
51533
51626
  const position = { sheetId, col, row };
51534
- const { cols, rows, measures } = table;
51535
- const spTable = new SpreadsheetPivotTable(cols, rows, measures);
51627
+ const { cols, rows, measures, fieldsType } = table;
51628
+ const spTable = new SpreadsheetPivotTable(cols, rows, measures, fieldsType || {});
51536
51629
  const formulaId = this.getPivotFormulaId(pivotId);
51537
51630
  this.insertPivot(position, formulaId, spTable);
51538
51631
  break;
@@ -51613,7 +51706,7 @@ class PivotCorePlugin extends CorePlugin {
51613
51706
  sheetId: position.sheetId,
51614
51707
  col: position.col + col,
51615
51708
  row: position.row + row,
51616
- content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51709
+ content: createPivotFormula(formulaId, pivotCell),
51617
51710
  });
51618
51711
  }
51619
51712
  }
@@ -54879,7 +54972,6 @@ class PivotUIPlugin extends UIPlugin {
54879
54972
  "getPivotIdFromPosition",
54880
54973
  "getPivotCellFromPosition",
54881
54974
  "isPivotUnused",
54882
- "areDomainArgsFieldsValid",
54883
54975
  "isSpillPivotFormula",
54884
54976
  ];
54885
54977
  pivots = {};
@@ -55011,19 +55103,19 @@ class PivotUIPlugin extends UIPlugin {
55011
55103
  return EMPTY_PIVOT_CELL;
55012
55104
  }
55013
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
+ }
55014
55118
  if (functionName === "PIVOT") {
55015
- const formulaId = args[0];
55016
- if (!formulaId) {
55017
- return EMPTY_PIVOT_CELL;
55018
- }
55019
- const pivotId = this.getters.getPivotId(formulaId.toString());
55020
- if (!pivotId) {
55021
- return EMPTY_PIVOT_CELL;
55022
- }
55023
- const pivot = this.getPivot(pivotId);
55024
- if (!pivot.isValid()) {
55025
- return EMPTY_PIVOT_CELL;
55026
- }
55027
55119
  const includeTotal = args[2] === false ? false : undefined;
55028
55120
  const includeColumnHeaders = args[3] === false ? false : undefined;
55029
55121
  const pivotCells = pivot
@@ -55034,7 +55126,7 @@ class PivotUIPlugin extends UIPlugin {
55034
55126
  return pivotCells[pivotCol][pivotRow];
55035
55127
  }
55036
55128
  if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55037
- const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55129
+ const domain = pivot.parseArgsToPivotDomain(args.slice(1, -2).map((value) => ({ value })));
55038
55130
  return {
55039
55131
  type: "MEASURE_HEADER",
55040
55132
  domain,
@@ -55042,15 +55134,17 @@ class PivotUIPlugin extends UIPlugin {
55042
55134
  };
55043
55135
  }
55044
55136
  else if (functionName === "PIVOT.HEADER") {
55137
+ const domain = pivot.parseArgsToPivotDomain(args.slice(1).map((value) => ({ value })));
55045
55138
  return {
55046
55139
  type: "HEADER",
55047
- domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55140
+ domain,
55048
55141
  };
55049
55142
  }
55050
55143
  const [measure, ...domainArgs] = args.slice(1);
55144
+ const domain = pivot.parseArgsToPivotDomain(domainArgs.map((value) => ({ value })));
55051
55145
  return {
55052
55146
  type: "VALUE",
55053
- domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55147
+ domain,
55054
55148
  measure: measure?.toString() || "",
55055
55149
  };
55056
55150
  }
@@ -55060,32 +55154,6 @@ class PivotUIPlugin extends UIPlugin {
55060
55154
  isPivotUnused(pivotId) {
55061
55155
  return this._getUnusedPivots().includes(pivotId);
55062
55156
  }
55063
- /**
55064
- * Check if the fields in the domain part of
55065
- * a pivot function are valid according to the pivot definition.
55066
- * e.g. =PIVOT.VALUE(1,"revenue","country_id",...,"create_date:month",...,"source_id",...)
55067
- */
55068
- areDomainArgsFieldsValid(pivotId, domain) {
55069
- const dimensions = domain
55070
- .map((node) => node.field)
55071
- .map((name) => (name.startsWith("#") ? name.slice(1) : name));
55072
- let argIndex = 0;
55073
- let definitionIndex = 0;
55074
- const pivot = this.getPivot(pivotId);
55075
- const definition = pivot.definition;
55076
- const cols = definition.columns.map((col) => col.nameWithGranularity);
55077
- const rows = definition.rows.map((row) => row.nameWithGranularity);
55078
- while (dimensions[argIndex] !== undefined && dimensions[argIndex] === rows[definitionIndex]) {
55079
- argIndex++;
55080
- definitionIndex++;
55081
- }
55082
- definitionIndex = 0;
55083
- while (dimensions[argIndex] !== undefined && dimensions[argIndex] === cols[definitionIndex]) {
55084
- argIndex++;
55085
- definitionIndex++;
55086
- }
55087
- return dimensions.length === argIndex;
55088
- }
55089
55157
  // ---------------------------------------------------------------------
55090
55158
  // Private
55091
55159
  // ---------------------------------------------------------------------
@@ -56923,43 +56991,6 @@ class DataCleanupPlugin extends UIPlugin {
56923
56991
  }
56924
56992
  }
56925
56993
 
56926
- /**
56927
- * Find and Replace Plugin
56928
- *
56929
- * This plugin is used in combination with the find_and_replace sidePanel
56930
- * It is used to 'highlight' cells that match an input string according to
56931
- * the given searchOptions. The second part of this plugin makes it possible
56932
- * (again with the find_and_replace sidePanel), to replace the values that match
56933
- * the search with a new value.
56934
- */
56935
- class FindAndReplacePlugin extends UIPlugin {
56936
- static getters = [];
56937
- handle(cmd) {
56938
- switch (cmd.type) {
56939
- case "REPLACE_SEARCH":
56940
- for (const match of cmd.matches) {
56941
- this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
56942
- }
56943
- break;
56944
- }
56945
- }
56946
- replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
56947
- const cell = this.getters.getCell(selectedMatch);
56948
- if (!cell?.content) {
56949
- return;
56950
- }
56951
- if (cell?.isFormula && !searchOptions.searchFormulas) {
56952
- return;
56953
- }
56954
- const searchRegex = getSearchRegex(searchString, searchOptions);
56955
- const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
56956
- const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
56957
- const content = toReplace.replace(replaceRegex, replaceWith);
56958
- const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
56959
- this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
56960
- }
56961
- }
56962
-
56963
56994
  class FormatPlugin extends UIPlugin {
56964
56995
  // ---------------------------------------------------------------------------
56965
56996
  // Command Handling
@@ -58605,32 +58636,32 @@ class ClipboardPlugin extends UIPlugin {
58605
58636
  }
58606
58637
  }
58607
58638
  convertOSClipboardData(clipboardData) {
58608
- const handlers = clipboardHandlersRegistries.figureHandlers
58609
- .getAll()
58610
- .map((handler) => new handler(this.getters, this.dispatch));
58611
- clipboardHandlersRegistries.cellHandlers
58612
- .getAll()
58613
- .forEach((handler) => handlers.push(new handler(this.getters, this.dispatch)));
58639
+ const handlers = this.selectClipboardHandlers({ figureId: true }).concat(this.selectClipboardHandlers({}));
58614
58640
  let copiedData = {};
58615
- for (const handler of handlers) {
58641
+ for (const { handlerName, handler } of handlers) {
58616
58642
  const data = handler.convertOSClipboardData(clipboardData);
58617
- 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
+ }
58618
58650
  }
58619
58651
  return copiedData;
58620
58652
  }
58621
58653
  selectClipboardHandlers(data) {
58622
- if ("figureId" in data) {
58623
- return clipboardHandlersRegistries.figureHandlers
58624
- .getAll()
58625
- .map((handler) => new handler(this.getters, this.dispatch));
58626
- }
58627
- return clipboardHandlersRegistries.cellHandlers
58628
- .getAll()
58629
- .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
+ });
58630
58661
  }
58631
58662
  isCutAllowedOn(zones) {
58632
58663
  const clipboardData = this.getClipboardData(zones);
58633
- for (const handler of this.selectClipboardHandlers(clipboardData)) {
58664
+ for (const { handler } of this.selectClipboardHandlers(clipboardData)) {
58634
58665
  const result = handler.isCutAllowed(clipboardData);
58635
58666
  if (result !== "Success" /* CommandResult.Success */) {
58636
58667
  return result;
@@ -58639,7 +58670,7 @@ class ClipboardPlugin extends UIPlugin {
58639
58670
  return "Success" /* CommandResult.Success */;
58640
58671
  }
58641
58672
  isPasteAllowed(target, copiedData, options) {
58642
- for (const handler of this.selectClipboardHandlers(copiedData)) {
58673
+ for (const { handler } of this.selectClipboardHandlers(copiedData)) {
58643
58674
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
58644
58675
  ...options,
58645
58676
  });
@@ -58667,9 +58698,15 @@ class ClipboardPlugin extends UIPlugin {
58667
58698
  copy(zones) {
58668
58699
  let copiedData = {};
58669
58700
  const clipboardData = this.getClipboardData(zones);
58670
- for (const handler of this.selectClipboardHandlers(clipboardData)) {
58701
+ for (const { handlerName, handler } of this.selectClipboardHandlers(clipboardData)) {
58671
58702
  const data = handler.copy(clipboardData);
58672
- 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
+ }
58673
58710
  }
58674
58711
  return copiedData;
58675
58712
  }
@@ -58685,8 +58722,12 @@ class ClipboardPlugin extends UIPlugin {
58685
58722
  zones,
58686
58723
  };
58687
58724
  const handlers = this.selectClipboardHandlers(copiedData);
58688
- for (const handler of handlers) {
58689
- 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);
58690
58731
  if (currentTarget.figureId) {
58691
58732
  target.figureId = currentTarget.figureId;
58692
58733
  }
@@ -58702,7 +58743,12 @@ class ClipboardPlugin extends UIPlugin {
58702
58743
  if (zone !== undefined) {
58703
58744
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
58704
58745
  }
58705
- 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
+ });
58706
58752
  if (!options?.selectTarget) {
58707
58753
  return;
58708
58754
  }
@@ -60774,7 +60820,6 @@ const featurePluginRegistry = new Registry()
60774
60820
  .add("ui_sheet", SheetUIPlugin)
60775
60821
  .add("ui_options", UIOptionsPlugin)
60776
60822
  .add("autofill", AutofillPlugin)
60777
- .add("find_and_replace", FindAndReplacePlugin)
60778
60823
  .add("sort", SortPlugin)
60779
60824
  .add("automatic_sum", AutomaticSumPlugin)
60780
60825
  .add("format", FormatPlugin)
@@ -66823,6 +66868,7 @@ class Model extends EventBus {
66823
66868
  const start = performance.now();
66824
66869
  console.group("Model creation");
66825
66870
  super();
66871
+ setDefaultTranslationMethod();
66826
66872
  stateUpdateMessages = repairInitialMessages(data, stateUpdateMessages);
66827
66873
  const workbookData = load(data, verboseImport);
66828
66874
  this.state = new StateObserver();
@@ -67318,6 +67364,7 @@ const registries = {
67318
67364
  pivotSidePanelRegistry,
67319
67365
  pivotNormalizationValueRegistry,
67320
67366
  supportedPivotPositionalFormulaRegistry,
67367
+ pivotToFunctionValueRegistry,
67321
67368
  };
67322
67369
  const helpers = {
67323
67370
  arg,
@@ -67363,7 +67410,6 @@ const helpers = {
67363
67410
  expandZoneOnInsertion,
67364
67411
  reduceZoneOnDeletion,
67365
67412
  unquote,
67366
- makePivotFormula,
67367
67413
  getMaxObjectId,
67368
67414
  getFunctionsFromTokens,
67369
67415
  getFirstPivotFunction,
@@ -67375,10 +67421,10 @@ const helpers = {
67375
67421
  insertTokenAfterLeftParenthesis,
67376
67422
  mergeContiguousZones,
67377
67423
  getPivotHighlights,
67378
- toPivotDomain,
67379
- flatPivotDomain,
67380
67424
  pivotTimeAdapter,
67381
67425
  UNDO_REDO_PIVOT_COMMANDS,
67426
+ createPivotFormula,
67427
+ areDomainArgsFieldsValid,
67382
67428
  };
67383
67429
  const links = {
67384
67430
  isMarkdownLink,
@@ -67509,6 +67555,6 @@ exports.tokenColors = tokenColors;
67509
67555
  exports.tokenize = tokenize;
67510
67556
 
67511
67557
 
67512
- __info__.version = "17.4.0-alpha.6";
67513
- __info__.date = "2024-06-19T13:46:27.157Z";
67514
- __info__.hash = "a4f22e4";
67558
+ __info__.version = "17.4.0-alpha.8";
67559
+ __info__.date = "2024-06-24T19:51:16.144Z";
67560
+ __info__.hash = "ccd30df";