@odoo/o-spreadsheet 17.4.0-alpha.9 → 17.4.1

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.9
6
- * @date 2024-06-26T11:09:20.284Z
7
- * @hash 526be20
5
+ * @version 17.4.1
6
+ * @date 2024-07-18T14:25:54.421Z
7
+ * @hash fd7deaa
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';
@@ -590,7 +590,7 @@ function getAddHeaderStartIndex(position, base) {
590
590
  /**
591
591
  * Compares two objects.
592
592
  */
593
- function deepEquals(o1, o2, ignoreFunctions) {
593
+ function deepEquals(o1, o2) {
594
594
  if (o1 === o2)
595
595
  return true;
596
596
  if ((o1 && !o2) || (o2 && !o1))
@@ -606,17 +606,13 @@ function deepEquals(o1, o2, ignoreFunctions) {
606
606
  }
607
607
  }
608
608
  for (const key in o1) {
609
- const typeOfO1Key = typeof o1[key];
610
- if (typeOfO1Key !== typeof o2[key])
609
+ if (typeof o1[key] !== typeof o2[key])
611
610
  return false;
612
- if (typeOfO1Key === "object") {
613
- if (!deepEquals(o1[key], o2[key], ignoreFunctions))
611
+ if (typeof o1[key] === "object") {
612
+ if (!deepEquals(o1[key], o2[key]))
614
613
  return false;
615
614
  }
616
615
  else {
617
- if (ignoreFunctions && typeOfO1Key === "function") {
618
- continue;
619
- }
620
616
  if (o1[key] !== o2[key])
621
617
  return false;
622
618
  }
@@ -1154,31 +1150,15 @@ function numberToLetters(n) {
1154
1150
  return numberToLetters(Math.floor(n / 26) - 1) + numberToLetters(n % 26);
1155
1151
  }
1156
1152
  }
1157
- const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1158
- const LETTERS_NUMBER_MAPPING = {};
1159
- for (const letter of LETTERS) {
1160
- const colIndex = letter.charCodeAt(0) - 64;
1161
- LETTERS_NUMBER_MAPPING[letter] = colIndex;
1162
- LETTERS_NUMBER_MAPPING[letter.toLowerCase()] = colIndex;
1163
- }
1164
- /**
1165
- * Convert a string (describing a column) to its number value.
1166
- *
1167
- * Examples:
1168
- * 'A' => 0
1169
- * 'Z' => 25
1170
- * 'AA' => 26
1171
- */
1172
1153
  function lettersToNumber(letters) {
1173
- let result = -1;
1154
+ let result = 0;
1174
1155
  const l = letters.length;
1175
- let pow = 1;
1176
- for (let i = l - 1; i >= 0; i--) {
1177
- const charCode = LETTERS_NUMBER_MAPPING[letters[i]];
1178
- result += charCode * pow;
1179
- pow *= 26;
1156
+ for (let i = 0; i < l; i++) {
1157
+ const charCode = letters.charCodeAt(i);
1158
+ const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1159
+ result = result * 26 + colIndex;
1180
1160
  }
1181
- return result;
1161
+ return result - 1;
1182
1162
  }
1183
1163
  function isCharALetter(char) {
1184
1164
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
@@ -1197,37 +1177,27 @@ function isCharADigit(char) {
1197
1177
  */
1198
1178
  function toCartesian(xc) {
1199
1179
  xc = xc.trim();
1200
- let numberPartStart = undefined;
1201
- // Note: looping by hand is uglier but ~2x faster than using a regex to match number/letter parts
1202
- for (let i = 0; i < xc.length; i++) {
1203
- const char = xc[i];
1204
- // as long as we haven't found the number part, keep advancing
1205
- if (!numberPartStart) {
1206
- if ((char === "$" && i === 0) || isCharALetter(char)) {
1207
- continue;
1208
- }
1209
- numberPartStart = i;
1210
- }
1211
- // Number part
1212
- if (!isCharADigit(char)) {
1213
- if (char === "$" && i === numberPartStart) {
1214
- continue;
1215
- }
1216
- throw new Error(`Invalid cell description: ${xc}`);
1217
- }
1180
+ let letterPart = "";
1181
+ let numberPart = "";
1182
+ let i = 0;
1183
+ // Process letter part
1184
+ if (xc[i] === "$")
1185
+ i++;
1186
+ while (i < xc.length && isCharALetter(xc[i])) {
1187
+ letterPart += xc[i++];
1218
1188
  }
1219
- if (!numberPartStart || numberPartStart === xc.length) {
1189
+ if (letterPart.length === 0 || letterPart.length > 3) {
1190
+ // limit to max 3 letters for performance reasons
1220
1191
  throw new Error(`Invalid cell description: ${xc}`);
1221
1192
  }
1222
- const letterPart = xc[0] === "$" ? xc.slice(1, numberPartStart) : xc.slice(0, numberPartStart);
1223
- const numberPart = xc[numberPartStart] === "$" ? xc.slice(numberPartStart + 1) : xc.slice(numberPartStart);
1224
- // limit to max 3 letters and 7 numbers to avoid
1225
- // gigantic numbers that would be a performance killer
1226
- // down the road
1227
- if (letterPart.length < 1 ||
1228
- letterPart.length > 3 ||
1229
- numberPart.length < 1 ||
1230
- numberPart.length > 7) {
1193
+ // Process number part
1194
+ if (xc[i] === "$")
1195
+ i++;
1196
+ while (i < xc.length && isCharADigit(xc[i])) {
1197
+ numberPart += xc[i++];
1198
+ }
1199
+ if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1200
+ // limit to max 7 numbers for performance reasons
1231
1201
  throw new Error(`Invalid cell description: ${xc}`);
1232
1202
  }
1233
1203
  const col = lettersToNumber(letterPart);
@@ -1829,7 +1799,7 @@ const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSepa
1829
1799
  });
1830
1800
  const getNumberRegex = memoize(function getNumberRegex(locale) {
1831
1801
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
1832
- const thousandsSeparator = escapeRegExp(locale.thousandsSeparator);
1802
+ const thousandsSeparator = escapeRegExp(locale.thousandsSeparator || "");
1833
1803
  const pIntegerAndDecimals = `(?:\\d+(?:${thousandsSeparator}\\d{3,})*(?:${decimalSeparator}\\d*)?)`; // pattern that match integer number with or without decimal digits
1834
1804
  const pOnlyDecimals = `(?:${decimalSeparator}\\d+)`; // pattern that match only expression with decimal digits
1835
1805
  const pScientificFormat = "(?:e(?:\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -1862,7 +1832,7 @@ function isNumber(value, locale) {
1862
1832
  return getNumberRegex(locale).test(value.trim());
1863
1833
  }
1864
1834
  const getInvaluableSymbolsRegexp = memoize(function getInvaluableSymbolsRegexp(locale) {
1865
- return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator)}]`, "g");
1835
+ return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator || "")}]`, "g");
1866
1836
  });
1867
1837
  /**
1868
1838
  * Convert a string into a number. It assumes that the string actually represents
@@ -2665,11 +2635,11 @@ function generateMatrix(nColumns, nRows, callback) {
2665
2635
  }
2666
2636
  return returned;
2667
2637
  }
2668
- function matrixMap(matrix, fn) {
2638
+ function matrixMap(matrix, callback) {
2669
2639
  if (matrix.length === 0) {
2670
2640
  return [];
2671
2641
  }
2672
- return generateMatrix(matrix.length, matrix[0].length, (col, row) => fn(matrix[col][row]));
2642
+ return generateMatrix(matrix.length, matrix[0].length, (col, row) => callback(matrix[col][row]));
2673
2643
  }
2674
2644
  function matrixForEach(matrix, fn) {
2675
2645
  const numberOfCols = matrix.length;
@@ -2752,7 +2722,7 @@ function getPredicate(descr, locale) {
2752
2722
  operand = descr;
2753
2723
  }
2754
2724
  }
2755
- if (isNumber(operand, locale)) {
2725
+ if (isNumber(operand, locale) || isDateTime(operand, locale)) {
2756
2726
  operand = toNumber(operand, locale);
2757
2727
  }
2758
2728
  else if (operand === "TRUE" || operand === "FALSE") {
@@ -2769,6 +2739,9 @@ function getPredicate(descr, locale) {
2769
2739
  * If the character is a special regular expression character, it is escaped with "\\".
2770
2740
  */
2771
2741
  const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2742
+ if (operand === "*") {
2743
+ return /.+/;
2744
+ }
2772
2745
  let exp = "";
2773
2746
  let predecessor = "";
2774
2747
  for (let char of operand) {
@@ -2792,9 +2765,9 @@ const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2792
2765
  }
2793
2766
  return new RegExp("^" + exp + "$", "i");
2794
2767
  });
2795
- function evaluatePredicate(value, criterion) {
2768
+ function evaluatePredicate(value = "", criterion) {
2796
2769
  const { operator, operand } = criterion;
2797
- if (value === undefined || operand === undefined || value === null || operand === null) {
2770
+ if (operand === undefined || value === null || operand === null) {
2798
2771
  return false;
2799
2772
  }
2800
2773
  if (typeof operand === "number" && operator === "=") {
@@ -5886,19 +5859,22 @@ class TokenizingChars {
5886
5859
  }
5887
5860
 
5888
5861
  function isValidLocale(locale) {
5889
- if (!(locale &&
5890
- typeof locale === "object" &&
5891
- typeof locale.name === "string" &&
5892
- typeof locale.code === "string" &&
5893
- typeof locale.thousandsSeparator === "string" &&
5894
- typeof locale.decimalSeparator === "string" &&
5895
- typeof locale.dateFormat === "string" &&
5896
- typeof locale.timeFormat === "string" &&
5897
- typeof locale.formulaArgSeparator === "string")) {
5862
+ if (!locale ||
5863
+ typeof locale !== "object" ||
5864
+ !(!locale.thousandsSeparator || typeof locale.thousandsSeparator === "string")) {
5898
5865
  return false;
5899
5866
  }
5900
- if (!Object.values(locale).every((v) => v)) {
5901
- return false;
5867
+ for (const property of [
5868
+ "code",
5869
+ "name",
5870
+ "decimalSeparator",
5871
+ "dateFormat",
5872
+ "timeFormat",
5873
+ "formulaArgSeparator",
5874
+ ]) {
5875
+ if (!locale[property] || typeof locale[property] !== "string") {
5876
+ return false;
5877
+ }
5902
5878
  }
5903
5879
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
5904
5880
  return false;
@@ -5996,7 +5972,10 @@ function canonicalizeNumberLiteral(content, locale) {
5996
5972
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
5997
5973
  return content;
5998
5974
  }
5999
- return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
5975
+ if (locale.thousandsSeparator) {
5976
+ content = content.replaceAll(locale.thousandsSeparator, "");
5977
+ }
5978
+ return content.replace(locale.decimalSeparator, ".");
6000
5979
  }
6001
5980
  /**
6002
5981
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -6293,24 +6272,6 @@ const dayOfMonthAdapter = {
6293
6272
  return `${normalizedValue}`;
6294
6273
  },
6295
6274
  };
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
6275
  /**
6315
6276
  * normalizes iso week number
6316
6277
  */
@@ -6332,25 +6293,6 @@ const isoWeekNumberAdapter = {
6332
6293
  return `${normalizedValue}`;
6333
6294
  },
6334
6295
  };
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
6296
  /**
6355
6297
  * normalizes month number
6356
6298
  */
@@ -6372,25 +6314,6 @@ const monthNumberAdapter = {
6372
6314
  return `${normalizedValue}`;
6373
6315
  },
6374
6316
  };
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
6317
  /**
6395
6318
  * normalizes quarter number
6396
6319
  */
@@ -6404,7 +6327,7 @@ const quarterNumberAdapter = {
6404
6327
  },
6405
6328
  toValueAndFormat(normalizedValue) {
6406
6329
  return {
6407
- value: toNumber(normalizedValue, DEFAULT_LOCALE),
6330
+ value: _t("Q%(quarter_number)s", { quarter_number: normalizedValue }),
6408
6331
  format: "0",
6409
6332
  };
6410
6333
  },
@@ -6454,15 +6377,11 @@ function nullHandlerDecorator(adapter) {
6454
6377
  }
6455
6378
  pivotTimeAdapterRegistry
6456
6379
  .add("day", nullHandlerDecorator(dayAdapter))
6457
- .add("week", nullHandlerDecorator(weekAdapter))
6458
- .add("month", nullHandlerDecorator(monthAdapter))
6459
- .add("quarter", nullHandlerDecorator(quarterAdapter))
6460
6380
  .add("year", nullHandlerDecorator(yearAdapter))
6461
6381
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
6462
6382
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
6463
6383
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
6464
- .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
6465
- .add("year_number", nullHandlerDecorator(yearAdapter));
6384
+ .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter));
6466
6385
 
6467
6386
  const AGGREGATOR_NAMES = {
6468
6387
  count: _t("Count"),
@@ -6538,11 +6457,10 @@ function getMaxObjectId(o) {
6538
6457
  }
6539
6458
  const ALL_PERIODS = {
6540
6459
  year: _t("Year"),
6541
- quarter: _t("Quarter"),
6542
- month: _t("Month"),
6543
- week: _t("Week"),
6460
+ quarter: _t("Quarter & Year"),
6461
+ month: _t("Month & Year"),
6462
+ week: _t("Week & Year"),
6544
6463
  day: _t("Day"),
6545
- year_number: _t("Year"),
6546
6464
  quarter_number: _t("Quarter"),
6547
6465
  month_number: _t("Month"),
6548
6466
  iso_week_number: _t("Week"),
@@ -6693,7 +6611,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6693
6611
  const pivotId = this.getters.getPivotIdFromPosition(position);
6694
6612
  const spreader = this.getters.getArrayFormulaSpreadingOn(position);
6695
6613
  if (pivotId) {
6696
- if (!deepEquals(spreader, position) || !isCopyingOneCell) {
6614
+ if (spreader && (!deepEquals(spreader, position) || !isCopyingOneCell)) {
6697
6615
  const pivotCell = this.getters.getPivotCellFromPosition(position);
6698
6616
  const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
6699
6617
  const pivotFormula = createPivotFormula(formulaPivotId, pivotCell);
@@ -7498,6 +7416,146 @@ function transformRangeData(range, executed) {
7498
7416
  return undefined;
7499
7417
  }
7500
7418
 
7419
+ var State;
7420
+ (function (State) {
7421
+ /**
7422
+ * Initial state.
7423
+ * Expecting any reference for the left part of a range
7424
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7425
+ */
7426
+ State[State["LeftRef"] = 0] = "LeftRef";
7427
+ /**
7428
+ * Expecting any reference for the right part of a range
7429
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7430
+ */
7431
+ State[State["RightRef"] = 1] = "RightRef";
7432
+ /**
7433
+ * Expecting the separator without any constraint on the right part
7434
+ */
7435
+ State[State["Separator"] = 2] = "Separator";
7436
+ /**
7437
+ * Expecting the separator for a full column range
7438
+ */
7439
+ State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7440
+ /**
7441
+ * Expecting the separator for a full row range
7442
+ */
7443
+ State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7444
+ /**
7445
+ * Expecting the right part of a full column range
7446
+ * e.g. "1", "A1"
7447
+ */
7448
+ State[State["RightColumnRef"] = 5] = "RightColumnRef";
7449
+ /**
7450
+ * Expecting the right part of a full row range
7451
+ * e.g. "A", "A1"
7452
+ */
7453
+ State[State["RightRowRef"] = 6] = "RightRowRef";
7454
+ /**
7455
+ * Final state. A range has been matched
7456
+ */
7457
+ State[State["Found"] = 7] = "Found";
7458
+ })(State || (State = {}));
7459
+ const goTo = (state, guard = () => true) => [
7460
+ {
7461
+ goTo: state,
7462
+ guard,
7463
+ },
7464
+ ];
7465
+ const goToMulti = (state, guard = () => true) => ({
7466
+ goTo: state,
7467
+ guard,
7468
+ });
7469
+ const machine = {
7470
+ [State.LeftRef]: {
7471
+ REFERENCE: goTo(State.Separator),
7472
+ NUMBER: goTo(State.FullRowSeparator),
7473
+ SYMBOL: [
7474
+ goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7475
+ goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7476
+ ],
7477
+ },
7478
+ [State.FullColumnSeparator]: {
7479
+ SPACE: goTo(State.FullColumnSeparator),
7480
+ OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7481
+ },
7482
+ [State.FullRowSeparator]: {
7483
+ SPACE: goTo(State.FullRowSeparator),
7484
+ OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7485
+ },
7486
+ [State.Separator]: {
7487
+ SPACE: goTo(State.Separator),
7488
+ OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7489
+ },
7490
+ [State.RightRef]: {
7491
+ SPACE: goTo(State.RightRef),
7492
+ NUMBER: goTo(State.Found),
7493
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7494
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7495
+ },
7496
+ [State.RightColumnRef]: {
7497
+ SPACE: goTo(State.RightColumnRef),
7498
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7499
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7500
+ },
7501
+ [State.RightRowRef]: {
7502
+ SPACE: goTo(State.RightRowRef),
7503
+ NUMBER: goTo(State.Found),
7504
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7505
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7506
+ },
7507
+ [State.Found]: {},
7508
+ };
7509
+ /**
7510
+ * Check if the list of tokens starts with a sequence of tokens representing
7511
+ * a range.
7512
+ * If a range is found, the sequence is removed from the list and is returned
7513
+ * as a single token.
7514
+ */
7515
+ function matchReference(tokens) {
7516
+ let head = 0;
7517
+ let transitions = machine[State.LeftRef];
7518
+ let matchedTokens = "";
7519
+ while (transitions !== undefined) {
7520
+ const token = tokens[head++];
7521
+ if (!token) {
7522
+ return null;
7523
+ }
7524
+ const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7525
+ const nextState = transition ? transition.goTo : undefined;
7526
+ switch (nextState) {
7527
+ case undefined:
7528
+ return null;
7529
+ case State.Found:
7530
+ matchedTokens += token.value;
7531
+ tokens.splice(0, head);
7532
+ return {
7533
+ type: "REFERENCE",
7534
+ value: matchedTokens,
7535
+ };
7536
+ default:
7537
+ transitions = machine[nextState];
7538
+ matchedTokens += token.value;
7539
+ break;
7540
+ }
7541
+ }
7542
+ return null;
7543
+ }
7544
+ /**
7545
+ * Take the result of the tokenizer and transform it to be usable in the
7546
+ * manipulations of range
7547
+ *
7548
+ * @param formula
7549
+ */
7550
+ function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7551
+ const tokens = tokenize(formula, locale);
7552
+ const result = [];
7553
+ while (tokens.length) {
7554
+ result.push(matchReference(tokens) || tokens.shift());
7555
+ }
7556
+ return result;
7557
+ }
7558
+
7501
7559
  const functionRegex = /[a-zA-Z0-9\_]+(\.[a-zA-Z0-9\_]+)*/;
7502
7560
  const UNARY_OPERATORS_PREFIX = ["-", "+"];
7503
7561
  const UNARY_OPERATORS_POSTFIX = ["%"];
@@ -7646,7 +7704,7 @@ function parseExpression(tokens, parent_priority = 0) {
7646
7704
  * Parse an expression (as a string) into an AST.
7647
7705
  */
7648
7706
  function parse(str) {
7649
- return parseTokens(tokenize(str));
7707
+ return parseTokens(rangeTokenize(str));
7650
7708
  }
7651
7709
  function parseTokens(tokens) {
7652
7710
  tokens = tokens.filter((x) => x.type !== "SPACE");
@@ -7782,146 +7840,6 @@ function rightOperandToFormula(operationAST) {
7782
7840
  return needParenthesis ? `(${astToFormula(rightOperation)})` : astToFormula(rightOperation);
7783
7841
  }
7784
7842
 
7785
- var State;
7786
- (function (State) {
7787
- /**
7788
- * Initial state.
7789
- * Expecting any reference for the left part of a range
7790
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7791
- */
7792
- State[State["LeftRef"] = 0] = "LeftRef";
7793
- /**
7794
- * Expecting any reference for the right part of a range
7795
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7796
- */
7797
- State[State["RightRef"] = 1] = "RightRef";
7798
- /**
7799
- * Expecting the separator without any constraint on the right part
7800
- */
7801
- State[State["Separator"] = 2] = "Separator";
7802
- /**
7803
- * Expecting the separator for a full column range
7804
- */
7805
- State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7806
- /**
7807
- * Expecting the separator for a full row range
7808
- */
7809
- State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7810
- /**
7811
- * Expecting the right part of a full column range
7812
- * e.g. "1", "A1"
7813
- */
7814
- State[State["RightColumnRef"] = 5] = "RightColumnRef";
7815
- /**
7816
- * Expecting the right part of a full row range
7817
- * e.g. "A", "A1"
7818
- */
7819
- State[State["RightRowRef"] = 6] = "RightRowRef";
7820
- /**
7821
- * Final state. A range has been matched
7822
- */
7823
- State[State["Found"] = 7] = "Found";
7824
- })(State || (State = {}));
7825
- const goTo = (state, guard = () => true) => [
7826
- {
7827
- goTo: state,
7828
- guard,
7829
- },
7830
- ];
7831
- const goToMulti = (state, guard = () => true) => ({
7832
- goTo: state,
7833
- guard,
7834
- });
7835
- const machine = {
7836
- [State.LeftRef]: {
7837
- REFERENCE: goTo(State.Separator),
7838
- NUMBER: goTo(State.FullRowSeparator),
7839
- SYMBOL: [
7840
- goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7841
- goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7842
- ],
7843
- },
7844
- [State.FullColumnSeparator]: {
7845
- SPACE: goTo(State.FullColumnSeparator),
7846
- OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7847
- },
7848
- [State.FullRowSeparator]: {
7849
- SPACE: goTo(State.FullRowSeparator),
7850
- OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7851
- },
7852
- [State.Separator]: {
7853
- SPACE: goTo(State.Separator),
7854
- OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7855
- },
7856
- [State.RightRef]: {
7857
- SPACE: goTo(State.RightRef),
7858
- NUMBER: goTo(State.Found),
7859
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7860
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7861
- },
7862
- [State.RightColumnRef]: {
7863
- SPACE: goTo(State.RightColumnRef),
7864
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7865
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7866
- },
7867
- [State.RightRowRef]: {
7868
- SPACE: goTo(State.RightRowRef),
7869
- NUMBER: goTo(State.Found),
7870
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7871
- SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7872
- },
7873
- [State.Found]: {},
7874
- };
7875
- /**
7876
- * Check if the list of tokens starts with a sequence of tokens representing
7877
- * a range.
7878
- * If a range is found, the sequence is removed from the list and is returned
7879
- * as a single token.
7880
- */
7881
- function matchReference(tokens) {
7882
- let head = 0;
7883
- let transitions = machine[State.LeftRef];
7884
- let matchedTokens = "";
7885
- while (transitions !== undefined) {
7886
- const token = tokens[head++];
7887
- if (!token) {
7888
- return null;
7889
- }
7890
- const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7891
- const nextState = transition ? transition.goTo : undefined;
7892
- switch (nextState) {
7893
- case undefined:
7894
- return null;
7895
- case State.Found:
7896
- matchedTokens += token.value;
7897
- tokens.splice(0, head);
7898
- return {
7899
- type: "REFERENCE",
7900
- value: matchedTokens,
7901
- };
7902
- default:
7903
- transitions = machine[nextState];
7904
- matchedTokens += token.value;
7905
- break;
7906
- }
7907
- }
7908
- return null;
7909
- }
7910
- /**
7911
- * Take the result of the tokenizer and transform it to be usable in the
7912
- * manipulations of range
7913
- *
7914
- * @param formula
7915
- */
7916
- function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7917
- const tokens = tokenize(formula, locale);
7918
- const result = [];
7919
- while (tokens.length) {
7920
- result.push(matchReference(tokens) || tokens.shift());
7921
- }
7922
- return result;
7923
- }
7924
-
7925
7843
  /**
7926
7844
  * Add the following information on tokens:
7927
7845
  * - length
@@ -8152,8 +8070,8 @@ function detectLink(value) {
8152
8070
 
8153
8071
  function evaluateLiteral(literalCell, localeFormat) {
8154
8072
  const value = localeFormat.format === PLAIN_TEXT_FORMAT ? literalCell.content : literalCell.parsedValue;
8155
- const fPayload = { value, format: localeFormat.format };
8156
- return createEvaluatedCell(fPayload, localeFormat.locale);
8073
+ const functionResult = { value, format: localeFormat.format };
8074
+ return createEvaluatedCell(functionResult, localeFormat.locale);
8157
8075
  }
8158
8076
  function parseLiteral(content, locale) {
8159
8077
  if (content.startsWith("=")) {
@@ -8174,13 +8092,13 @@ function parseLiteral(content, locale) {
8174
8092
  }
8175
8093
  return content;
8176
8094
  }
8177
- function createEvaluatedCell(fPayload, locale = DEFAULT_LOCALE, cell) {
8178
- const link = detectLink(fPayload.value);
8095
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
8096
+ const link = detectLink(functionResult.value);
8179
8097
  if (!link) {
8180
- return _createEvaluatedCell(fPayload, locale, cell);
8098
+ return _createEvaluatedCell(functionResult, locale, cell);
8181
8099
  }
8182
8100
  const value = parseLiteral(link.label, locale);
8183
- const format = fPayload.format ||
8101
+ const format = functionResult.format ||
8184
8102
  (typeof value === "number"
8185
8103
  ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
8186
8104
  : undefined);
@@ -8193,8 +8111,8 @@ function createEvaluatedCell(fPayload, locale = DEFAULT_LOCALE, cell) {
8193
8111
  link,
8194
8112
  };
8195
8113
  }
8196
- function _createEvaluatedCell(fPayload, locale, cell) {
8197
- let { value, format, message } = fPayload;
8114
+ function _createEvaluatedCell(functionResult, locale, cell) {
8115
+ let { value, format, message } = functionResult;
8198
8116
  format = cell?.format || format;
8199
8117
  const formattedValue = formatValue(value, { format, locale });
8200
8118
  if (isEvaluationError(value)) {
@@ -8482,9 +8400,11 @@ const ChartTerms = {
8482
8400
  BackgroundColor: _t("Background color"),
8483
8401
  StackedBarChart: _t("Stacked bar chart"),
8484
8402
  StackedLineChart: _t("Stacked line chart"),
8403
+ StackedAreaChart: _t("Stacked area chart"),
8485
8404
  CumulativeData: _t("Cumulative data"),
8486
8405
  TreatLabelsAsText: _t("Treat labels as text"),
8487
8406
  AggregatedChart: _t("Aggregate"),
8407
+ ShowValues: _t("Show values"),
8488
8408
  Errors: {
8489
8409
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
8490
8410
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -10326,162 +10246,6 @@ class ComposerFocusStore extends SpreadsheetStore {
10326
10246
  }
10327
10247
  }
10328
10248
 
10329
- /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
10330
- const waterfallLinesPlugin = {
10331
- id: "waterfallLinesPlugin",
10332
- beforeDraw(chart, args, options) {
10333
- if (!options.showConnectorLines) {
10334
- return;
10335
- }
10336
- // Note: private properties are not in the typing of chartJS (and some of the existing types are missing properties)
10337
- // so we don't type anything in this file
10338
- const drawData = chart._metasets?.[0]?.data;
10339
- if (!drawData) {
10340
- return;
10341
- }
10342
- const ctx = chart.ctx;
10343
- ctx.save();
10344
- ctx.setLineDash([3, 2]);
10345
- for (let i = 0; i < drawData.length; i++) {
10346
- const bar = drawData[i];
10347
- if (bar.height === 0) {
10348
- continue;
10349
- }
10350
- const nextBar = getNextNonEmptyBar(drawData, i);
10351
- if (!nextBar) {
10352
- break;
10353
- }
10354
- const rect = getBarElementRect(bar);
10355
- const nextBarRect = getBarElementRect(nextBar);
10356
- const rawBarValues = bar.$context.raw;
10357
- const value = rawBarValues[1] - rawBarValues[0];
10358
- const lineY = Math.round(value < 0 ? rect.bottom - 1 : rect.top);
10359
- const lineStart = Math.round(rect.right);
10360
- const lineEnd = Math.round(nextBarRect.left);
10361
- ctx.strokeStyle = "#999";
10362
- ctx.beginPath();
10363
- ctx.moveTo(lineStart + 1, lineY + 0.5);
10364
- ctx.lineTo(lineEnd, lineY + 0.5);
10365
- ctx.stroke();
10366
- }
10367
- ctx.restore();
10368
- },
10369
- };
10370
- function getBarElementRect(bar) {
10371
- const flipped = bar.base < bar.y; // Bar are flipped for negative values in the dataset
10372
- return {
10373
- left: bar.x - bar.width / 2,
10374
- right: bar.x + bar.width / 2,
10375
- bottom: flipped ? bar.base + bar.height : bar.y + bar.height,
10376
- top: flipped ? bar.base : bar.y,
10377
- };
10378
- }
10379
- function getNextNonEmptyBar(bars, startIndex) {
10380
- return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10381
- }
10382
-
10383
- // @ts-ignore
10384
- window.Chart?.register(waterfallLinesPlugin);
10385
- class ChartJsComponent extends Component {
10386
- static template = "o-spreadsheet-ChartJsComponent";
10387
- static props = {
10388
- figure: Object,
10389
- };
10390
- canvas = useRef("graphContainer");
10391
- chart;
10392
- currentRuntime;
10393
- get background() {
10394
- return this.chartRuntime.background;
10395
- }
10396
- get canvasStyle() {
10397
- return `background-color: ${this.background}`;
10398
- }
10399
- get chartRuntime() {
10400
- const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
10401
- if (!("chartJsConfig" in runtime)) {
10402
- throw new Error("Unsupported chart runtime");
10403
- }
10404
- return runtime;
10405
- }
10406
- setup() {
10407
- onMounted(() => {
10408
- const runtime = this.chartRuntime;
10409
- this.currentRuntime = runtime;
10410
- // Note: chartJS modify the runtime in place, so it's important to give it a copy
10411
- this.createChart(deepCopy(runtime.chartJsConfig));
10412
- });
10413
- onWillUnmount(() => this.chart?.destroy());
10414
- useEffect(() => {
10415
- const runtime = this.chartRuntime;
10416
- if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10417
- if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10418
- this.chart?.destroy();
10419
- this.createChart(deepCopy(runtime.chartJsConfig));
10420
- }
10421
- else {
10422
- this.updateChartJs(deepCopy(runtime));
10423
- }
10424
- this.currentRuntime = runtime;
10425
- }
10426
- });
10427
- }
10428
- createChart(chartData) {
10429
- const canvas = this.canvas.el;
10430
- const ctx = canvas.getContext("2d");
10431
- // @ts-ignore
10432
- this.chart = new window.Chart(ctx, chartData);
10433
- }
10434
- updateChartJs(chartRuntime) {
10435
- const chartData = chartRuntime.chartJsConfig;
10436
- if (chartData.data && chartData.data.datasets) {
10437
- this.chart.data = chartData.data;
10438
- if (chartData.options?.plugins?.title) {
10439
- this.chart.config.options.plugins.title = chartData.options.plugins.title;
10440
- }
10441
- }
10442
- else {
10443
- this.chart.data.datasets = [];
10444
- }
10445
- this.chart.config.options = chartData.options;
10446
- this.chart.update();
10447
- }
10448
- }
10449
-
10450
- /**
10451
- * AbstractChart is the class from which every Chart should inherit.
10452
- * The role of this class is to maintain the state of each chart.
10453
- */
10454
- class AbstractChart {
10455
- sheetId;
10456
- title;
10457
- getters;
10458
- constructor(definition, sheetId, getters) {
10459
- this.title = definition.title;
10460
- this.sheetId = sheetId;
10461
- this.getters = getters;
10462
- }
10463
- /**
10464
- * Validate the chart definition given as arguments. This function will be
10465
- * called from allowDispatch function
10466
- */
10467
- static validateChartDefinition(validator, definition) {
10468
- throw new Error("This method should be implemented by sub class");
10469
- }
10470
- /**
10471
- * Get a new chart definition transformed with the executed command. This
10472
- * functions will be called during operational transform process
10473
- */
10474
- static transformDefinition(definition, executed) {
10475
- throw new Error("This method should be implemented by sub class");
10476
- }
10477
- /**
10478
- * Get an empty definition based on the given context
10479
- */
10480
- static getDefinitionFromContextCreation(context) {
10481
- throw new Error("This method should be implemented by sub class");
10482
- }
10483
- }
10484
-
10485
10249
  /**
10486
10250
  * This file contains helpers that are common to different charts (mainly
10487
10251
  * line, bar and pie charts)
@@ -10804,6 +10568,238 @@ function getDefinedAxis(definition) {
10804
10568
  return { useLeftAxis, useRightAxis };
10805
10569
  }
10806
10570
 
10571
+ /** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
10572
+ const chartShowValuesPlugin = {
10573
+ id: "chartShowValuesPlugin",
10574
+ afterDatasetsDraw(chart, args, options) {
10575
+ if (!options.showValues) {
10576
+ return;
10577
+ }
10578
+ const drawData = chart._metasets?.[0]?.data;
10579
+ if (!drawData) {
10580
+ return;
10581
+ }
10582
+ const ctx = chart.ctx;
10583
+ ctx.save();
10584
+ ctx.textAlign = "center";
10585
+ ctx.textBaseline = "middle";
10586
+ ctx.fillStyle = chartFontColor(options.background);
10587
+ ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10588
+ chart._metasets.forEach(function (dataset) {
10589
+ switch (dataset.type) {
10590
+ case "doughnut":
10591
+ case "pie": {
10592
+ for (let i = 0; i < dataset._parsed.length; i++) {
10593
+ const bar = dataset.data[i];
10594
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10595
+ const midAngle = (startAngle + endAngle) / 2;
10596
+ const midRadius = (innerRadius + outerRadius) / 2;
10597
+ const x = bar.x + midRadius * Math.cos(midAngle);
10598
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10599
+ ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
10600
+ ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10601
+ const value = options.callback(dataset._parsed[i]);
10602
+ ctx.strokeText(value, x, y);
10603
+ ctx.fillText(value, x, y);
10604
+ }
10605
+ break;
10606
+ }
10607
+ case "bar":
10608
+ case "line": {
10609
+ const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
10610
+ for (let i = 0; i < dataset._parsed.length; i++) {
10611
+ const point = dataset.data[i];
10612
+ const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
10613
+ const displayedValue = options.callback(value - 0);
10614
+ let xPosition = 0, yPosition = 0;
10615
+ if (options.horizontal) {
10616
+ yPosition = point.y;
10617
+ if (value < 0) {
10618
+ ctx.textAlign = "right";
10619
+ xPosition = point.x - yOffset;
10620
+ }
10621
+ else {
10622
+ ctx.textAlign = "left";
10623
+ xPosition = point.x + yOffset;
10624
+ }
10625
+ }
10626
+ else {
10627
+ xPosition = point.x;
10628
+ if (value < 0) {
10629
+ ctx.textBaseline = "top";
10630
+ yPosition = point.y + yOffset;
10631
+ }
10632
+ else {
10633
+ ctx.textBaseline = "bottom";
10634
+ yPosition = point.y - yOffset;
10635
+ }
10636
+ }
10637
+ ctx.strokeText(displayedValue, xPosition, yPosition);
10638
+ ctx.fillText(displayedValue, xPosition, yPosition);
10639
+ }
10640
+ break;
10641
+ }
10642
+ }
10643
+ });
10644
+ ctx.restore();
10645
+ },
10646
+ };
10647
+
10648
+ /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
10649
+ const waterfallLinesPlugin = {
10650
+ id: "waterfallLinesPlugin",
10651
+ beforeDraw(chart, args, options) {
10652
+ if (!options.showConnectorLines) {
10653
+ return;
10654
+ }
10655
+ // Note: private properties are not in the typing of chartJS (and some of the existing types are missing properties)
10656
+ // so we don't type anything in this file
10657
+ const drawData = chart._metasets?.[0]?.data;
10658
+ if (!drawData) {
10659
+ return;
10660
+ }
10661
+ const ctx = chart.ctx;
10662
+ ctx.save();
10663
+ ctx.setLineDash([3, 2]);
10664
+ for (let i = 0; i < drawData.length; i++) {
10665
+ const bar = drawData[i];
10666
+ if (bar.height === 0) {
10667
+ continue;
10668
+ }
10669
+ const nextBar = getNextNonEmptyBar(drawData, i);
10670
+ if (!nextBar) {
10671
+ break;
10672
+ }
10673
+ const rect = getBarElementRect(bar);
10674
+ const nextBarRect = getBarElementRect(nextBar);
10675
+ const rawBarValues = bar.$context.raw;
10676
+ const value = rawBarValues[1] - rawBarValues[0];
10677
+ const lineY = Math.round(value < 0 ? rect.bottom - 1 : rect.top);
10678
+ const lineStart = Math.round(rect.right);
10679
+ const lineEnd = Math.round(nextBarRect.left);
10680
+ ctx.strokeStyle = "#999";
10681
+ ctx.beginPath();
10682
+ ctx.moveTo(lineStart + 1, lineY + 0.5);
10683
+ ctx.lineTo(lineEnd, lineY + 0.5);
10684
+ ctx.stroke();
10685
+ }
10686
+ ctx.restore();
10687
+ },
10688
+ };
10689
+ function getBarElementRect(bar) {
10690
+ const flipped = bar.base < bar.y; // Bar are flipped for negative values in the dataset
10691
+ return {
10692
+ left: bar.x - bar.width / 2,
10693
+ right: bar.x + bar.width / 2,
10694
+ bottom: flipped ? bar.base + bar.height : bar.y + bar.height,
10695
+ top: flipped ? bar.base : bar.y,
10696
+ };
10697
+ }
10698
+ function getNextNonEmptyBar(bars, startIndex) {
10699
+ return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10700
+ }
10701
+
10702
+ window.Chart?.register(waterfallLinesPlugin);
10703
+ window.Chart?.register(chartShowValuesPlugin);
10704
+ class ChartJsComponent extends Component {
10705
+ static template = "o-spreadsheet-ChartJsComponent";
10706
+ static props = {
10707
+ figure: Object,
10708
+ };
10709
+ canvas = useRef("graphContainer");
10710
+ chart;
10711
+ currentRuntime;
10712
+ get background() {
10713
+ return this.chartRuntime.background;
10714
+ }
10715
+ get canvasStyle() {
10716
+ return `background-color: ${this.background}`;
10717
+ }
10718
+ get chartRuntime() {
10719
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
10720
+ if (!("chartJsConfig" in runtime)) {
10721
+ throw new Error("Unsupported chart runtime");
10722
+ }
10723
+ return runtime;
10724
+ }
10725
+ setup() {
10726
+ onMounted(() => {
10727
+ const runtime = this.chartRuntime;
10728
+ this.currentRuntime = runtime;
10729
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
10730
+ this.createChart(deepCopy(runtime.chartJsConfig));
10731
+ });
10732
+ onWillUnmount(() => this.chart?.destroy());
10733
+ useEffect(() => {
10734
+ const runtime = this.chartRuntime;
10735
+ if (runtime !== this.currentRuntime) {
10736
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10737
+ this.chart?.destroy();
10738
+ this.createChart(deepCopy(runtime.chartJsConfig));
10739
+ }
10740
+ else {
10741
+ this.updateChartJs(deepCopy(runtime));
10742
+ }
10743
+ this.currentRuntime = runtime;
10744
+ }
10745
+ });
10746
+ }
10747
+ createChart(chartData) {
10748
+ const canvas = this.canvas.el;
10749
+ const ctx = canvas.getContext("2d");
10750
+ this.chart = new window.Chart(ctx, chartData);
10751
+ }
10752
+ updateChartJs(chartRuntime) {
10753
+ const chartData = chartRuntime.chartJsConfig;
10754
+ if (chartData.data && chartData.data.datasets) {
10755
+ this.chart.data = chartData.data;
10756
+ if (chartData.options?.plugins?.title) {
10757
+ this.chart.config.options.plugins.title = chartData.options.plugins.title;
10758
+ }
10759
+ }
10760
+ else {
10761
+ this.chart.data.datasets = [];
10762
+ }
10763
+ this.chart.config.options = chartData.options;
10764
+ this.chart.update();
10765
+ }
10766
+ }
10767
+
10768
+ /**
10769
+ * AbstractChart is the class from which every Chart should inherit.
10770
+ * The role of this class is to maintain the state of each chart.
10771
+ */
10772
+ class AbstractChart {
10773
+ sheetId;
10774
+ title;
10775
+ getters;
10776
+ constructor(definition, sheetId, getters) {
10777
+ this.title = definition.title;
10778
+ this.sheetId = sheetId;
10779
+ this.getters = getters;
10780
+ }
10781
+ /**
10782
+ * Validate the chart definition given as arguments. This function will be
10783
+ * called from allowDispatch function
10784
+ */
10785
+ static validateChartDefinition(validator, definition) {
10786
+ throw new Error("This method should be implemented by sub class");
10787
+ }
10788
+ /**
10789
+ * Get a new chart definition transformed with the executed command. This
10790
+ * functions will be called during operational transform process
10791
+ */
10792
+ static transformDefinition(definition, executed) {
10793
+ throw new Error("This method should be implemented by sub class");
10794
+ }
10795
+ /**
10796
+ * Get an empty definition based on the given context
10797
+ */
10798
+ static getDefinitionFromContextCreation(context) {
10799
+ throw new Error("This method should be implemented by sub class");
10800
+ }
10801
+ }
10802
+
10807
10803
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
10808
10804
  if (!baseline) {
10809
10805
  return "";
@@ -18842,7 +18838,7 @@ const INDEX = {
18842
18838
  isExported: true,
18843
18839
  };
18844
18840
  // -----------------------------------------------------------------------------
18845
- // INDEX
18841
+ // INDIRECT
18846
18842
  // -----------------------------------------------------------------------------
18847
18843
  const INDIRECT = {
18848
18844
  description: _t("Returns the content of a cell, specified by a string."),
@@ -19180,7 +19176,7 @@ const PIVOT = {
19180
19176
  arg("include_column_titles (boolean, default=TRUE)", _t("Whether to include the column titles or not.")),
19181
19177
  arg("column_count (number, optional)", _t("number of columns")),
19182
19178
  ],
19183
- compute: function (pivotFormulaId, rowCount = { value: Number.MAX_VALUE }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19179
+ compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19184
19180
  const _pivotFormulaId = toString(pivotFormulaId);
19185
19181
  const _rowCount = toNumber(rowCount, this.locale);
19186
19182
  if (_rowCount < 0) {
@@ -19237,6 +19233,79 @@ const PIVOT = {
19237
19233
  return result;
19238
19234
  },
19239
19235
  };
19236
+ //--------------------------------------------------------------------------
19237
+ // OFFSET
19238
+ //--------------------------------------------------------------------------
19239
+ const OFFSET = {
19240
+ description: _t("Returns a range reference shifted by a specified number of rows and columns from a starting cell reference."),
19241
+ args: [
19242
+ arg("cell_reference (meta)", _t("The starting point from which to count the offset rows and columns.")),
19243
+ arg("offset_rows (number)", _t("The number of rows to offset by.")),
19244
+ arg("offset_columns (number)", _t("The number of columns to offset by.")),
19245
+ arg("height (number, default='height of cell_reference')", _t("The number of rows of the range to return starting at the offset target.")),
19246
+ arg("width (number, default='width of cell_reference')", _t("The number of columns of the range to return starting at the offset target.")),
19247
+ ],
19248
+ compute: function (cellReference, offsetRows, offsetColumns, height, width) {
19249
+ if (isEvaluationError(cellReference?.value)) {
19250
+ return cellReference;
19251
+ }
19252
+ const _cellReference = cellReference?.value;
19253
+ if (!_cellReference) {
19254
+ throw new Error("In this context, the function OFFSET needs to have a cell or range in parameter.");
19255
+ }
19256
+ const zone = toZone(_cellReference);
19257
+ let offsetHeight = zone.bottom - zone.top + 1;
19258
+ let offsetWidth = zone.right - zone.left + 1;
19259
+ if (height) {
19260
+ const _height = toNumber(height, this.locale);
19261
+ assertPositive(_t("Height value is %(_height)s. It should be greater than or equal to 1.", { _height }), _height);
19262
+ offsetHeight = _height;
19263
+ }
19264
+ if (width) {
19265
+ const _width = toNumber(width, this.locale);
19266
+ assertPositive(_t("Width value is %(_width)s. It should be greater than or equal to 1.", { _width }), _width);
19267
+ offsetWidth = _width;
19268
+ }
19269
+ const { sheetName } = splitReference(_cellReference);
19270
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.getters.getActiveSheetId();
19271
+ const _offsetRows = toNumber(offsetRows, this.locale);
19272
+ const _offsetColumns = toNumber(offsetColumns, this.locale);
19273
+ let originPosition;
19274
+ const __originCellXC = this.__originCellXC?.();
19275
+ if (__originCellXC) {
19276
+ const cellZone = toZone(__originCellXC);
19277
+ originPosition = {
19278
+ sheetId: this.__originSheetId,
19279
+ col: cellZone.left,
19280
+ row: cellZone.top,
19281
+ };
19282
+ this.updateDependencies?.(originPosition);
19283
+ }
19284
+ const startingCol = zone.left + _offsetColumns;
19285
+ const startingRow = zone.top + _offsetRows;
19286
+ if (startingCol < 0 || startingRow < 0) {
19287
+ return new InvalidReferenceError(_t("OFFSET evaluates to an out of bounds range."));
19288
+ }
19289
+ const dependencyZone = {
19290
+ left: startingCol,
19291
+ top: startingRow,
19292
+ right: startingCol + offsetWidth - 1,
19293
+ bottom: startingRow + offsetHeight - 1,
19294
+ };
19295
+ const range = this.getters.getRangeFromZone(this.__originSheetId, dependencyZone);
19296
+ if (range.invalidXc || range.invalidSheetName) {
19297
+ return new InvalidReferenceError();
19298
+ }
19299
+ if (originPosition) {
19300
+ this.addDependencies?.(originPosition, [range]);
19301
+ }
19302
+ return generateMatrix(offsetWidth, offsetHeight, (col, row) => this.getters.getEvaluatedCell({
19303
+ sheetId,
19304
+ col: startingCol + col,
19305
+ row: startingRow + row,
19306
+ }));
19307
+ },
19308
+ };
19240
19309
 
19241
19310
  var lookup = /*#__PURE__*/Object.freeze({
19242
19311
  __proto__: null,
@@ -19248,6 +19317,7 @@ var lookup = /*#__PURE__*/Object.freeze({
19248
19317
  INDIRECT: INDIRECT,
19249
19318
  LOOKUP: LOOKUP,
19250
19319
  MATCH: MATCH,
19320
+ OFFSET: OFFSET,
19251
19321
  PIVOT: PIVOT,
19252
19322
  PIVOT_HEADER: PIVOT_HEADER,
19253
19323
  PIVOT_VALUE: PIVOT_VALUE,
@@ -19530,6 +19600,320 @@ var operators = /*#__PURE__*/Object.freeze({
19530
19600
  UPLUS: UPLUS
19531
19601
  });
19532
19602
 
19603
+ const transformFromFactor = (factor) => ({
19604
+ transform: (x) => x * factor,
19605
+ inverseTransform: (x) => x / factor,
19606
+ });
19607
+ const standard = { transform: (x) => x, inverseTransform: (x) => x };
19608
+ const ANG2M = 1e-10;
19609
+ const IN2M = 0.0254;
19610
+ const PICAPT2M = IN2M / 72;
19611
+ const FT2M = 0.3048;
19612
+ const YD2M = 0.9144;
19613
+ const MI2M = 1609.34;
19614
+ const NMI2M = 1852;
19615
+ const LY2M = 9.46073047258e15;
19616
+ const UNITS = {
19617
+ // WEIGHT UNITs : Standard = gramme
19618
+ g: { ...standard, category: "weight" },
19619
+ u: { ...transformFromFactor(1.66053e-24), category: "weight" },
19620
+ grain: { ...transformFromFactor(0.0647989), category: "weight" },
19621
+ ozm: { ...transformFromFactor(28.3495), category: "weight" },
19622
+ lbm: { ...transformFromFactor(453.592), category: "weight" },
19623
+ stone: { ...transformFromFactor(6350.29), category: "weight" },
19624
+ sg: { ...transformFromFactor(14593.90294), category: "weight" },
19625
+ cwt: { ...transformFromFactor(45359.237), category: "weight" },
19626
+ uk_cwt: { ...transformFromFactor(50802.3), category: "weight" },
19627
+ ton: { ...transformFromFactor(907184.74), category: "weight" },
19628
+ uk_ton: { ...transformFromFactor(1016046.9), category: "weight" },
19629
+ // DISTANCE UNITS : Standard = meter
19630
+ m: { ...standard, category: "distance" },
19631
+ km: { ...transformFromFactor(1000), category: "distance" },
19632
+ ang: { ...transformFromFactor(ANG2M), category: "distance" },
19633
+ Picapt: { ...transformFromFactor(PICAPT2M), category: "distance" },
19634
+ pica: { ...transformFromFactor(IN2M / 6), category: "distance" },
19635
+ in: { ...transformFromFactor(IN2M), category: "distance" },
19636
+ ft: { ...transformFromFactor(FT2M), category: "distance" },
19637
+ yd: { ...transformFromFactor(YD2M), category: "distance" },
19638
+ ell: { ...transformFromFactor(1.143), category: "distance" },
19639
+ mi: { ...transformFromFactor(MI2M), category: "distance" },
19640
+ survey_mi: { ...transformFromFactor(1609.34), category: "distance" },
19641
+ Nmi: { ...transformFromFactor(NMI2M), category: "distance" },
19642
+ ly: { ...transformFromFactor(LY2M), category: "distance" },
19643
+ parsec: { ...transformFromFactor(3.0856775814914e16), category: "distance" },
19644
+ // TIME UNITS : Standard = second
19645
+ sec: { ...standard, category: "time" },
19646
+ min: { ...transformFromFactor(60), category: "time" },
19647
+ hr: { ...transformFromFactor(3600), category: "time" },
19648
+ day: { ...transformFromFactor(86400), category: "time" },
19649
+ yr: { ...transformFromFactor(31556952), category: "time" },
19650
+ // PRESSURE UNITS : Standard = Pascal
19651
+ Pa: { ...standard, category: "pressure" },
19652
+ bar: { ...transformFromFactor(100000), category: "pressure" },
19653
+ mmHg: { ...transformFromFactor(133.322), category: "pressure" },
19654
+ Torr: { ...transformFromFactor(133.322), category: "pressure" },
19655
+ psi: { ...transformFromFactor(6894.76), category: "pressure" },
19656
+ atm: { ...transformFromFactor(101325), category: "pressure" },
19657
+ // FORCE UNITS : Standard = Newton
19658
+ N: { ...standard, category: "force" },
19659
+ dyn: { ...transformFromFactor(1e-5), category: "force" },
19660
+ pond: { ...transformFromFactor(0.00980665), category: "force" },
19661
+ lbf: { ...transformFromFactor(4.44822), category: "force" },
19662
+ // ENERGY UNITS : Standard = Joule
19663
+ J: { ...standard, category: "energy" },
19664
+ eV: { ...transformFromFactor(1.60218e-19), category: "energy" },
19665
+ e: { ...transformFromFactor(1e-7), category: "energy" },
19666
+ flb: { ...transformFromFactor(1.3558179483), category: "energy" },
19667
+ c: { ...transformFromFactor(4.184), category: "energy" },
19668
+ cal: { ...transformFromFactor(4.1868), category: "energy" },
19669
+ BTU: { ...transformFromFactor(1055.06), category: "energy" },
19670
+ Wh: { ...transformFromFactor(3600), category: "energy" },
19671
+ HPh: { ...transformFromFactor(2684520), category: "energy" },
19672
+ // POWER UNITS : Standard = Watt
19673
+ W: { ...standard, category: "power" },
19674
+ PS: { ...transformFromFactor(735.499), category: "power" },
19675
+ HP: { ...transformFromFactor(745.7), category: "power" },
19676
+ // MAGNETISM UNITS : Standard = Tesla
19677
+ T: { ...standard, category: "magnetism" },
19678
+ ga: { ...transformFromFactor(1e-4), category: "magnetism" },
19679
+ // TEMPERATURE UNITS : Standard = Kelvin
19680
+ K: { ...standard, category: "temperature" },
19681
+ C: {
19682
+ transform: (T) => T + 273.15,
19683
+ inverseTransform: (T) => T - 273.15,
19684
+ category: "temperature",
19685
+ },
19686
+ F: {
19687
+ transform: (T) => ((T - 32) * 5) / 9 + 273.15,
19688
+ inverseTransform: (T) => ((T - 273.15) * 9) / 5 + 32,
19689
+ category: "temperature",
19690
+ },
19691
+ Rank: { ...transformFromFactor(5 / 9), category: "temperature" },
19692
+ Reau: {
19693
+ transform: (T) => T * 1.25 + 273.15,
19694
+ inverseTransform: (T) => (T - 273.15) / 1.25,
19695
+ category: "temperature",
19696
+ },
19697
+ // VOLUME UNITS : Standard = cubic meter
19698
+ "m^3": { ...standard, category: "volume", order: 3 },
19699
+ "ang^3": { ...transformFromFactor(Math.pow(ANG2M, 3)), category: "volume", order: 3 },
19700
+ "Picapt^3": { ...transformFromFactor(Math.pow(PICAPT2M, 3)), category: "volume", order: 3 },
19701
+ tsp: { ...transformFromFactor(4.92892e-6), category: "volume" },
19702
+ tspm: { ...transformFromFactor(5e-6), category: "volume" },
19703
+ tbs: { ...transformFromFactor(1.4786764825785619e-5), category: "volume" },
19704
+ "in^3": { ...transformFromFactor(Math.pow(IN2M, 3)), category: "volume", order: 3 },
19705
+ oz: { ...transformFromFactor(2.95735295625e-5), category: "volume" },
19706
+ cup: { ...transformFromFactor(0.000237), category: "volume" },
19707
+ pt: { ...transformFromFactor(0.0004731765), category: "volume" },
19708
+ uk_pt: { ...transformFromFactor(0.000568261), category: "volume" },
19709
+ qt: { ...transformFromFactor(0.0009463529), category: "volume" },
19710
+ l: { ...transformFromFactor(1e-3), category: "volume" },
19711
+ uk_qt: { ...transformFromFactor(0.0011365225), category: "volume" },
19712
+ gal: { ...transformFromFactor(0.0037854118), category: "volume" },
19713
+ uk_gal: { ...transformFromFactor(0.00454609), category: "volume" },
19714
+ "ft^3": { ...transformFromFactor(Math.pow(FT2M, 3)), category: "volume", order: 3 },
19715
+ bushel: { ...transformFromFactor(0.0352390704), category: "volume" },
19716
+ barrel: { ...transformFromFactor(0.158987295), category: "volume" },
19717
+ "yd^3": { ...transformFromFactor(Math.pow(YD2M, 3)), category: "volume", order: 3 },
19718
+ MTON: { ...transformFromFactor(1.13267386368), category: "volume" },
19719
+ GRT: { ...transformFromFactor(2.83168), category: "volume" },
19720
+ "mi^3": { ...transformFromFactor(Math.pow(MI2M, 3)), category: "volume", order: 3 },
19721
+ "Nmi^3": { ...transformFromFactor(Math.pow(NMI2M, 3)), category: "volume", order: 3 },
19722
+ "ly^3": { ...transformFromFactor(Math.pow(LY2M, 3)), category: "volume", order: 3 },
19723
+ // AREA UNITS : Standard = square meter
19724
+ "m^2": { ...standard, category: "area", order: 2 },
19725
+ "ang^2": { ...transformFromFactor(Math.pow(ANG2M, 2)), category: "area", order: 2 },
19726
+ "Picapt^2": { ...transformFromFactor(Math.pow(PICAPT2M, 2)), category: "area", order: 2 },
19727
+ "in^2": { ...transformFromFactor(Math.pow(IN2M, 2)), category: "area", order: 2 },
19728
+ "ft^2": { ...transformFromFactor(Math.pow(FT2M, 2)), category: "area", order: 2 },
19729
+ "yd^2": { ...transformFromFactor(Math.pow(YD2M, 2)), category: "area", order: 2 },
19730
+ ar: { ...transformFromFactor(100), category: "area" },
19731
+ Morgen: { ...transformFromFactor(2500), category: "area" },
19732
+ uk_acre: { ...transformFromFactor(4046.8564224), category: "area" },
19733
+ us_acre: { ...transformFromFactor(4046.8726098743), category: "area" },
19734
+ ha: { ...transformFromFactor(1e4), category: "area" },
19735
+ "mi^2": { ...transformFromFactor(Math.pow(MI2M, 2)), category: "area", order: 2 },
19736
+ "Nmi^2": { ...transformFromFactor(Math.pow(NMI2M, 2)), category: "area", order: 2 },
19737
+ "ly^2": { ...transformFromFactor(Math.pow(LY2M, 2)), category: "area", order: 2 },
19738
+ // INFORMATION UNITS : Standard = bit
19739
+ bit: { ...standard, category: "information" },
19740
+ byte: { ...transformFromFactor(8), category: "information" },
19741
+ // SPEED UNITS : Standard = m/s
19742
+ "m/s": { ...standard, category: "speed" },
19743
+ "m/hr": { ...transformFromFactor(1 / 3600), category: "speed" },
19744
+ "km/hr": { ...transformFromFactor(1 / 3.6), category: "speed" },
19745
+ mph: { ...transformFromFactor(0.44704), category: "speed" },
19746
+ kn: { ...transformFromFactor(0.5144444444), category: "speed" },
19747
+ admkn: { ...transformFromFactor(0.5147733333), category: "speed" },
19748
+ };
19749
+ const UNITS_ALIASES = {
19750
+ shweight: "cwt",
19751
+ lcwt: "uk_cwt",
19752
+ hweight: "uk_cwt",
19753
+ LTON: "uk_ton",
19754
+ brton: "uk_ton",
19755
+ pc: "parsec",
19756
+ Pica: "Picapt",
19757
+ d: "day",
19758
+ mn: "min",
19759
+ s: "sec",
19760
+ p: "Pa",
19761
+ at: "atm",
19762
+ dy: "dyn",
19763
+ ev: "eV",
19764
+ hh: "HPh",
19765
+ wh: "Wh",
19766
+ btu: "BTU",
19767
+ h: "HP",
19768
+ cel: "C",
19769
+ fah: "F",
19770
+ kel: "K",
19771
+ us_pt: "pt",
19772
+ L: "l",
19773
+ lt: "l",
19774
+ ang3: "ang^3",
19775
+ ft3: "ft^3",
19776
+ in3: "in^3",
19777
+ ly3: "ly^3",
19778
+ m3: "m^3",
19779
+ mi3: "mi^3",
19780
+ yd3: "yd^3",
19781
+ Nmi3: "Nmi^3",
19782
+ Picapt3: "Picapt^3",
19783
+ "Pica^3": "Picapt^3",
19784
+ Pica3: "Picapt^3",
19785
+ regton: "GRT",
19786
+ ang2: "ang^2",
19787
+ ft2: "ft^2",
19788
+ in2: "in^2",
19789
+ ly2: "ly^2",
19790
+ m2: "m^2",
19791
+ mi2: "mi^2",
19792
+ Nmi2: "Nmi^2",
19793
+ Picapt2: "Picapt^2",
19794
+ "Pica^2": "Picapt^2",
19795
+ Pica2: "Picapt^2",
19796
+ yd2: "yd^2",
19797
+ "m/h": "m/hr",
19798
+ "m/sec": "m/s",
19799
+ };
19800
+ const UNIT_PREFIXES = {
19801
+ "": 1,
19802
+ Y: 1e24,
19803
+ Z: 1e21,
19804
+ E: 1e18,
19805
+ P: 1e15,
19806
+ T: 1e12,
19807
+ G: 1e9,
19808
+ M: 1e6,
19809
+ k: 1e3,
19810
+ h: 1e2,
19811
+ da: 1e1,
19812
+ e: 1e1,
19813
+ d: 1e-1,
19814
+ c: 1e-2,
19815
+ m: 1e-3,
19816
+ u: 1e-6,
19817
+ n: 1e-9,
19818
+ p: 1e-12,
19819
+ f: 1e-15,
19820
+ a: 1e-18,
19821
+ z: 1e-21,
19822
+ y: 1e-21,
19823
+ Yi: Math.pow(2, 80),
19824
+ Zi: Math.pow(2, 70),
19825
+ Ei: Math.pow(2, 60),
19826
+ Pi: Math.pow(2, 50),
19827
+ Ti: Math.pow(2, 40),
19828
+ Gi: Math.pow(2, 30),
19829
+ Mi: Math.pow(2, 20),
19830
+ ki: Math.pow(2, 10),
19831
+ };
19832
+ const TRANSLATED_CATEGORIES = {
19833
+ weight: _t("Weight"),
19834
+ distance: _t("Distance"),
19835
+ time: _t("Time"),
19836
+ pressure: _t("Pressure"),
19837
+ force: _t("Force"),
19838
+ energy: _t("Energy"),
19839
+ power: _t("Power"),
19840
+ magnetism: _t("Magnetism"),
19841
+ temperature: _t("Temperature"),
19842
+ volume: _t("Volume"),
19843
+ area: _t("Area"),
19844
+ information: _t("Information"),
19845
+ speed: _t("Speed"),
19846
+ };
19847
+ function getTranslatedCategory(key) {
19848
+ return TRANSLATED_CATEGORIES[key] ?? "";
19849
+ }
19850
+ function getTransformation(key) {
19851
+ for (const [prefix, value] of Object.entries(UNIT_PREFIXES)) {
19852
+ if (prefix && !key.startsWith(prefix))
19853
+ continue;
19854
+ const _key = key.slice(prefix.length);
19855
+ let conversion = UNITS[_key];
19856
+ if (!conversion && UNITS_ALIASES[_key]) {
19857
+ conversion = UNITS[UNITS_ALIASES[_key]];
19858
+ }
19859
+ if (conversion) {
19860
+ return {
19861
+ ...conversion,
19862
+ factor: conversion.order ? Math.pow(value, conversion.order) : value,
19863
+ };
19864
+ }
19865
+ }
19866
+ return;
19867
+ }
19868
+
19869
+ // -----------------------------------------------------------------------------
19870
+ // CONVERT
19871
+ // -----------------------------------------------------------------------------
19872
+ const CONVERT = {
19873
+ description: _t("Converts a numeric value to a different unit of measure."),
19874
+ args: [
19875
+ arg("value (number)", _t("the numeric value in start_unit to convert to end_unit")),
19876
+ arg("start_unit (string)", _t("The starting unit, the unit currently assigned to value")),
19877
+ arg("end_unit (string)", _t("The unit of measure into which to convert value")),
19878
+ ],
19879
+ compute: function (value, startUnit, endUnit) {
19880
+ const _value = toNumber(value, this.locale);
19881
+ const _startUnit = toString(startUnit);
19882
+ const _endUnit = toString(endUnit);
19883
+ const startConversion = getTransformation(_startUnit);
19884
+ const endConversion = getTransformation(_endUnit);
19885
+ if (!startConversion) {
19886
+ return {
19887
+ value: CellErrorType.GenericError,
19888
+ message: _t("Invalid units of measure ('%s')", _startUnit),
19889
+ };
19890
+ }
19891
+ if (!endConversion) {
19892
+ return {
19893
+ value: CellErrorType.GenericError,
19894
+ message: _t("Invalid units of measure ('%s')", _endUnit),
19895
+ };
19896
+ }
19897
+ if (startConversion.category !== endConversion.category) {
19898
+ return {
19899
+ value: CellErrorType.GenericError,
19900
+ message: _t("Incompatible units of measure ('%s' vs '%s')", getTranslatedCategory(startConversion.category), getTranslatedCategory(endConversion.category)),
19901
+ };
19902
+ }
19903
+ return {
19904
+ value: endConversion.inverseTransform(startConversion.factor * startConversion.transform(_value)) /
19905
+ endConversion.factor,
19906
+ format: value?.format,
19907
+ };
19908
+ },
19909
+ isExported: true,
19910
+ };
19911
+
19912
+ var parser = /*#__PURE__*/Object.freeze({
19913
+ __proto__: null,
19914
+ CONVERT: CONVERT
19915
+ });
19916
+
19533
19917
  const DEFAULT_STARTING_AT = 1;
19534
19918
  /** Regex matching all the words in a string */
19535
19919
  const wordRegex = /[A-Za-zÀ-ÖØ-öø-ÿ]+/g;
@@ -19947,6 +20331,7 @@ const categories = [
19947
20331
  { name: _t("Text"), functions: text },
19948
20332
  { name: _t("Engineering"), functions: engineering },
19949
20333
  { name: _t("Web"), functions: web },
20334
+ { name: _t("Parser"), functions: parser },
19950
20335
  ];
19951
20336
  const functionNameRegex = /^[A-Z0-9\_\.]+$/;
19952
20337
  class FunctionRegistry extends Registry {
@@ -19958,41 +20343,127 @@ class FunctionRegistry extends Registry {
19958
20343
  }
19959
20344
  const descr = addMetaInfoFromArg(addDescr);
19960
20345
  validateArguments(descr.args);
19961
- this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr), name), name);
20346
+ this.mapping[name] = createComputeFunction(descr, name);
19962
20347
  super.add(name, descr);
19963
20348
  return this;
19964
20349
  }
19965
20350
  }
19966
- function addInputHandling(descr) {
19967
- function computeWithInputHandling(...args) {
20351
+ const functionRegistry = new FunctionRegistry();
20352
+ for (let category of categories) {
20353
+ const fns = category.functions;
20354
+ for (let name in fns) {
20355
+ const addDescr = fns[name];
20356
+ addDescr.category = addDescr.category || category.name;
20357
+ name = name.replace(/_/g, ".");
20358
+ functionRegistry.add(name, { isExported: false, ...addDescr });
20359
+ }
20360
+ }
20361
+ const notAvailableError = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
20362
+ function createComputeFunction(descr, functionName) {
20363
+ function runtimeCompute(...args) {
20364
+ try {
20365
+ return vectorizedCompute.apply(this, args);
20366
+ }
20367
+ catch (e) {
20368
+ return handleError(e, functionName);
20369
+ }
20370
+ }
20371
+ function vectorizedCompute(...args) {
20372
+ let countVectorizableCol = 1;
20373
+ let countVectorizableRow = 1;
20374
+ let vectorizableColLimit = Infinity;
20375
+ let vectorizableRowLimit = Infinity;
20376
+ let vectorArgsType = undefined;
20377
+ //#region Compute vectorisation limits
19968
20378
  for (let i = 0; i < args.length; i++) {
19969
20379
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
19970
20380
  const arg = args[i];
19971
20381
  if (isMatrix(arg) && !argDefinition.acceptMatrix) {
19972
- if (arg.length !== 1 || arg[0].length !== 1) {
19973
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be a single value or a single cell reference, not a range.", argDefinition.name));
20382
+ // if argDefinition does not accept a matrix but arg is still a matrix
20383
+ // --> triggers the arguments vectorization
20384
+ const nColumns = arg.length;
20385
+ const nRows = arg[0].length;
20386
+ if (nColumns !== 1 || nRows !== 1) {
20387
+ vectorArgsType ??= new Array(args.length);
20388
+ if (nColumns !== 1 && nRows !== 1) {
20389
+ vectorArgsType[i] = "matrix";
20390
+ countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20391
+ countVectorizableRow = Math.max(countVectorizableRow, nRows);
20392
+ vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20393
+ vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20394
+ }
20395
+ else if (nColumns !== 1) {
20396
+ vectorArgsType[i] = "horizontal";
20397
+ countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20398
+ vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20399
+ }
20400
+ else if (nRows !== 1) {
20401
+ vectorArgsType[i] = "vertical";
20402
+ countVectorizableRow = Math.max(countVectorizableRow, nRows);
20403
+ vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20404
+ }
20405
+ }
20406
+ else {
20407
+ args[i] = arg[0][0];
19974
20408
  }
19975
- args[i] = arg[0][0];
19976
20409
  }
19977
20410
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
19978
20411
  throw new BadExpressionError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be reference to a cell or range.", (i + 1).toString()));
19979
20412
  }
19980
20413
  }
19981
- return descr.compute.apply(this, args);
20414
+ //#endregion
20415
+ if (countVectorizableCol === 1 && countVectorizableRow === 1) {
20416
+ // either this function is not vectorized or it ends up with a 1x1 dimension
20417
+ return computeFunctionToObject.apply(this, args);
20418
+ }
20419
+ const getArgOffset = (i, j) => args.map((arg, index) => {
20420
+ switch (vectorArgsType?.[index]) {
20421
+ case "matrix":
20422
+ return arg[i][j];
20423
+ case "horizontal":
20424
+ return arg[i][0];
20425
+ case "vertical":
20426
+ return arg[0][j];
20427
+ case undefined:
20428
+ return arg;
20429
+ }
20430
+ });
20431
+ return generateMatrix(countVectorizableCol, countVectorizableRow, (col, row) => {
20432
+ if (col > vectorizableColLimit - 1 || row > vectorizableRowLimit - 1) {
20433
+ return notAvailableError;
20434
+ }
20435
+ const singleCellComputeResult = computeFunctionToObject.apply(this, getArgOffset(col, row));
20436
+ // In the case where the user tries to vectorize arguments of an array formula, we will get an
20437
+ // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
20438
+ // we won't be able to return the values.
20439
+ // In this case, we keep the first element of each spreading part, just as Excel does, and
20440
+ // create an array with these parts.
20441
+ // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
20442
+ // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
20443
+ // for the value in A2). In this case, we will simply take the first value of each matrix and
20444
+ // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
20445
+ return isMatrix(singleCellComputeResult)
20446
+ ? singleCellComputeResult[0][0]
20447
+ : singleCellComputeResult;
20448
+ });
19982
20449
  }
19983
- return computeWithInputHandling;
19984
- }
19985
- function addErrorHandling(compute, functionName) {
19986
- return function (...args) {
19987
- try {
19988
- return compute.apply(this, args);
20450
+ function computeFunctionToObject(...args) {
20451
+ const result = descr.compute.apply(this, args);
20452
+ if (!isMatrix(result)) {
20453
+ if (typeof result === "object" && result !== null && "value" in result) {
20454
+ replaceFunctionNamePlaceholder(result, functionName);
20455
+ return result;
20456
+ }
20457
+ return { value: result };
19989
20458
  }
19990
- catch (e) {
19991
- return handleError(e, functionName);
20459
+ if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
20460
+ matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
20461
+ return result;
19992
20462
  }
19993
- };
20463
+ return matrixMap(result, (row) => ({ value: row }));
20464
+ }
20465
+ return runtimeCompute;
19994
20466
  }
19995
- const implementationErrorMessage = _t("An unexpected error occurred. Submit a support ticket at odoo.com/help.");
19996
20467
  function handleError(e, functionName) {
19997
20468
  // the error could be an user error (instance of EvaluationError)
19998
20469
  // or a javascript error (instance of Error)
@@ -20011,42 +20482,16 @@ function hasStringValue(obj) {
20011
20482
  return (obj?.value !== undefined &&
20012
20483
  typeof obj.value === "string");
20013
20484
  }
20014
- function hasStringMessage(obj) {
20015
- return (obj?.message !== undefined &&
20016
- typeof obj.message === "string");
20017
- }
20018
- function addResultHandling(compute, functionName) {
20019
- return function computeWithResultHandling(...args) {
20020
- const result = compute.apply(this, args);
20021
- if (!isMatrix(result)) {
20022
- if (typeof result === "object" && result !== null && "value" in result) {
20023
- replaceFunctionNamePlaceholder(result, functionName);
20024
- return result;
20025
- }
20026
- return { value: result };
20027
- }
20028
- if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
20029
- matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
20030
- return result;
20031
- }
20032
- return matrixMap(result, (row) => ({ value: row }));
20033
- };
20034
- }
20035
- function replaceFunctionNamePlaceholder(fPayload, functionName) {
20485
+ function replaceFunctionNamePlaceholder(functionResult, functionName) {
20036
20486
  // for performance reasons: change in place and only if needed
20037
- if (fPayload.message?.includes("[[FUNCTION_NAME]]")) {
20038
- fPayload.message = fPayload.message.replace("[[FUNCTION_NAME]]", functionName);
20487
+ if (functionResult.message?.includes("[[FUNCTION_NAME]]")) {
20488
+ functionResult.message = functionResult.message.replace("[[FUNCTION_NAME]]", functionName);
20039
20489
  }
20040
20490
  }
20041
- const functionRegistry = new FunctionRegistry();
20042
- for (let category of categories) {
20043
- const fns = category.functions;
20044
- for (let name in fns) {
20045
- const addDescr = fns[name];
20046
- addDescr.category = addDescr.category || category.name;
20047
- name = name.replace(/_/g, ".");
20048
- functionRegistry.add(name, { isExported: false, ...addDescr });
20049
- }
20491
+ const implementationErrorMessage = _t("An unexpected error occurred. Submit a support ticket at odoo.com/help.");
20492
+ function hasStringMessage(obj) {
20493
+ return (obj?.message !== undefined &&
20494
+ typeof obj.message === "string");
20050
20495
  }
20051
20496
 
20052
20497
  autoCompleteProviders.add("functions", {
@@ -21495,6 +21940,7 @@ function indentCode(code) {
21495
21940
 
21496
21941
  const functions$1 = functionRegistry.content;
21497
21942
  const OPERATOR_MAP = {
21943
+ // export for test
21498
21944
  "=": "EQ",
21499
21945
  "+": "ADD",
21500
21946
  "-": "MINUS",
@@ -21509,6 +21955,7 @@ const OPERATOR_MAP = {
21509
21955
  "&": "CONCATENATE",
21510
21956
  };
21511
21957
  const UNARY_OPERATOR_MAP = {
21958
+ // export for test
21512
21959
  "-": "UMINUS",
21513
21960
  "+": "UPLUS",
21514
21961
  "%": "UNARY.PERCENT",
@@ -21579,7 +22026,7 @@ function compileTokens(tokens) {
21579
22026
  }
21580
22027
  /**
21581
22028
  * This function compiles all the information extracted by the parser into an
21582
- * executable code for the evaluation of the cells content. It uses a cash to
22029
+ * executable code for the evaluation of the cells content. It uses a cache to
21583
22030
  * not reevaluate identical code structures.
21584
22031
  *
21585
22032
  * The function is sensitive to parameter “isMeta”. This
@@ -22797,8 +23244,7 @@ function truncateLabel(label) {
22797
23244
  /**
22798
23245
  * Get a default chart js configuration
22799
23246
  */
22800
- function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22801
- const { format, locale, truncateLabels, horizontalChart } = args;
23247
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels = true, horizontalChart, }) {
22802
23248
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22803
23249
  const options = {
22804
23250
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22947,14 +23393,17 @@ function getChartDatasetValues(getters, dataSets) {
22947
23393
  }
22948
23394
  return datasetValues;
22949
23395
  }
22950
- /** See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes */
22951
- function getFillingMode(index) {
22952
- if (index === 0) {
23396
+ /**
23397
+ * If the chart is a stacked area chart, we want to fill until the next dataset.
23398
+ * If the chart is a simple area chart, we want to fill until the origin (bottom axis).
23399
+ *
23400
+ * See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes
23401
+ */
23402
+ function getFillingMode(index, stackedChart) {
23403
+ if (!stackedChart) {
22953
23404
  return "origin";
22954
23405
  }
22955
- else {
22956
- return index - 1;
22957
- }
23406
+ return index === 0 ? "origin" : "-1";
22958
23407
  }
22959
23408
  function chartToImage(runtime, figure, type) {
22960
23409
  // wrap the canvas in a div with a fixed size because chart.js would
@@ -22971,7 +23420,6 @@ function chartToImage(runtime, figure, type) {
22971
23420
  if ("chartJsConfig" in runtime) {
22972
23421
  const config = deepCopy(runtime.chartJsConfig);
22973
23422
  config.plugins = [backgroundColorChartJSPlugin];
22974
- // @ts-ignore
22975
23423
  const chart = new window.Chart(canvas, config);
22976
23424
  const imgContent = chart.toBase64Image();
22977
23425
  chart.destroy();
@@ -23021,6 +23469,7 @@ class BarChart extends AbstractChart {
23021
23469
  dataSetDesign;
23022
23470
  axesDesign;
23023
23471
  horizontal;
23472
+ showValues;
23024
23473
  constructor(definition, sheetId, getters) {
23025
23474
  super(definition, sheetId, getters);
23026
23475
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -23033,6 +23482,7 @@ class BarChart extends AbstractChart {
23033
23482
  this.dataSetDesign = definition.dataSets;
23034
23483
  this.axesDesign = definition.axesDesign;
23035
23484
  this.horizontal = definition.horizontal;
23485
+ this.showValues = definition.showValues;
23036
23486
  }
23037
23487
  static transformDefinition(definition, executed) {
23038
23488
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -23052,6 +23502,7 @@ class BarChart extends AbstractChart {
23052
23502
  type: "bar",
23053
23503
  labelRange: context.auxiliaryRange || undefined,
23054
23504
  axesDesign: context.axesDesign,
23505
+ showValues: context.showValues,
23055
23506
  };
23056
23507
  }
23057
23508
  getContextCreation() {
@@ -23105,6 +23556,7 @@ class BarChart extends AbstractChart {
23105
23556
  aggregated: this.aggregated,
23106
23557
  axesDesign: this.axesDesign,
23107
23558
  horizontal: this.horizontal,
23559
+ showValues: this.showValues,
23108
23560
  };
23109
23561
  }
23110
23562
  getDefinitionForExcel() {
@@ -23154,22 +23606,23 @@ function getBarConfiguration(chart, labels, localeFormat) {
23154
23606
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23155
23607
  };
23156
23608
  config.options.indexAxis = chart.horizontal ? "y" : "x";
23609
+ const formatCallback = (value) => {
23610
+ value = Number(value);
23611
+ if (isNaN(value))
23612
+ return value;
23613
+ const { locale, format } = localeFormat;
23614
+ return formatValue(value, {
23615
+ locale,
23616
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23617
+ });
23618
+ };
23157
23619
  config.options.scales = {};
23158
23620
  const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23159
23621
  const valuesAxis = {
23160
23622
  beginAtZero: true, // the origin of the y axis is always zero
23161
23623
  ticks: {
23162
23624
  color: fontColor,
23163
- callback: (value) => {
23164
- value = Number(value);
23165
- if (isNaN(value))
23166
- return value;
23167
- const { locale, format } = localeFormat;
23168
- return formatValue(value, {
23169
- locale,
23170
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23171
- });
23172
- },
23625
+ callback: formatCallback,
23173
23626
  },
23174
23627
  };
23175
23628
  const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
@@ -23202,6 +23655,12 @@ function getBarConfiguration(chart, labels, localeFormat) {
23202
23655
  config.options.scales.y1.stacked = true;
23203
23656
  }
23204
23657
  }
23658
+ config.options.plugins.chartShowValuesPlugin = {
23659
+ showValues: chart.showValues,
23660
+ background: chart.background,
23661
+ horizontal: chart.horizontal,
23662
+ callback: formatCallback,
23663
+ };
23205
23664
  return config;
23206
23665
  }
23207
23666
  function createBarChartRuntime(chart, getters) {
@@ -23249,467 +23708,6 @@ function createBarChartRuntime(chart, getters) {
23249
23708
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
23250
23709
  }
23251
23710
 
23252
- class ComboChart extends AbstractChart {
23253
- dataSets;
23254
- labelRange;
23255
- background;
23256
- legendPosition;
23257
- aggregated;
23258
- dataSetsHaveTitle;
23259
- dataSetDesign;
23260
- axesDesign;
23261
- type = "combo";
23262
- constructor(definition, sheetId, getters) {
23263
- super(definition, sheetId, getters);
23264
- this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23265
- this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
23266
- this.background = definition.background;
23267
- this.legendPosition = definition.legendPosition;
23268
- this.aggregated = definition.aggregated;
23269
- this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
23270
- this.dataSetDesign = definition.dataSets;
23271
- this.axesDesign = definition.axesDesign;
23272
- }
23273
- static transformDefinition(definition, executed) {
23274
- return transformChartDefinitionWithDataSetsWithZone(definition, executed);
23275
- }
23276
- static validateChartDefinition(validator, definition) {
23277
- return validator.checkValidations(definition, checkDataset, checkLabelRange);
23278
- }
23279
- getContextCreation() {
23280
- const range = [];
23281
- for (const [i, dataSet] of this.dataSets.entries()) {
23282
- range.push({
23283
- ...this.dataSetDesign?.[i],
23284
- dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
23285
- });
23286
- }
23287
- return {
23288
- ...this,
23289
- range,
23290
- auxiliaryRange: this.labelRange
23291
- ? this.getters.getRangeString(this.labelRange, this.sheetId)
23292
- : undefined,
23293
- };
23294
- }
23295
- getDefinition() {
23296
- return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
23297
- }
23298
- getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
23299
- const ranges = [];
23300
- for (const [i, dataSet] of dataSets.entries()) {
23301
- ranges.push({
23302
- ...this.dataSetDesign?.[i],
23303
- dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
23304
- });
23305
- }
23306
- return {
23307
- type: "combo",
23308
- dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23309
- background: this.background,
23310
- dataSets: ranges,
23311
- legendPosition: this.legendPosition,
23312
- labelRange: labelRange
23313
- ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
23314
- : undefined,
23315
- title: this.title,
23316
- aggregated: this.aggregated,
23317
- axesDesign: this.axesDesign,
23318
- };
23319
- }
23320
- getDefinitionForExcel() {
23321
- // Excel does not support aggregating labels
23322
- if (this.aggregated) {
23323
- return undefined;
23324
- }
23325
- const dataSets = this.dataSets
23326
- .map((ds) => toExcelDataset(this.getters, ds))
23327
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
23328
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
23329
- const definition = this.getDefinition();
23330
- return {
23331
- ...definition,
23332
- backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23333
- fontColor: toXlsxHexColor(chartFontColor(this.background)),
23334
- dataSets,
23335
- labelRange,
23336
- verticalAxis: getDefinedAxis(definition),
23337
- };
23338
- }
23339
- updateRanges(applyChange) {
23340
- const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
23341
- if (!isStale) {
23342
- return this;
23343
- }
23344
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
23345
- return new ComboChart(definition, this.sheetId, this.getters);
23346
- }
23347
- static getDefinitionFromContextCreation(context) {
23348
- return {
23349
- background: context.background,
23350
- dataSets: context.range ?? [],
23351
- dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23352
- aggregated: context.aggregated,
23353
- legendPosition: context.legendPosition ?? "top",
23354
- title: context.title || { text: "" },
23355
- labelRange: context.auxiliaryRange || undefined,
23356
- type: "combo",
23357
- axesDesign: context.axesDesign,
23358
- };
23359
- }
23360
- copyForSheetId(sheetId) {
23361
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
23362
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
23363
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
23364
- return new ComboChart(definition, sheetId, this.getters);
23365
- }
23366
- copyInSheetId(sheetId) {
23367
- const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
23368
- return new ComboChart(definition, sheetId, this.getters);
23369
- }
23370
- }
23371
- function createComboChartRuntime(chart, getters) {
23372
- const mainDataSetFormat = chart.dataSets.length
23373
- ? getChartDatasetFormat(getters, [chart.dataSets[0]])
23374
- : undefined;
23375
- const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
23376
- const locale = getters.getLocale();
23377
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23378
- let labels = labelValues.formattedValues;
23379
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23380
- if (chart.dataSetsHaveTitle &&
23381
- dataSetsValues[0] &&
23382
- labels.length > dataSetsValues[0].data.length) {
23383
- labels.shift();
23384
- }
23385
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
23386
- if (chart.aggregated) {
23387
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
23388
- }
23389
- const localeFormat = { format: mainDataSetFormat, locale };
23390
- const fontColor = chartFontColor(chart.background);
23391
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23392
- const legend = {
23393
- labels: { color: fontColor },
23394
- reverse: true,
23395
- };
23396
- if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
23397
- legend.display = false;
23398
- }
23399
- else {
23400
- legend.position = chart.legendPosition;
23401
- }
23402
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
23403
- config.options.layout = {
23404
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23405
- };
23406
- config.options.scales = {
23407
- x: {
23408
- ticks: {
23409
- padding: 5,
23410
- color: fontColor,
23411
- },
23412
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23413
- },
23414
- };
23415
- const formatCallback = (format) => {
23416
- return (value) => {
23417
- value = Number(value);
23418
- if (isNaN(value))
23419
- return value;
23420
- const { locale } = localeFormat;
23421
- return formatValue(value, {
23422
- locale,
23423
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23424
- });
23425
- };
23426
- };
23427
- const leftVerticalAxis = {
23428
- beginAtZero: true, // the origin of the y axis is always zero
23429
- ticks: {
23430
- color: fontColor,
23431
- callback: formatCallback(mainDataSetFormat),
23432
- },
23433
- };
23434
- const rightVerticalAxis = {
23435
- beginAtZero: true, // the origin of the y axis is always zero
23436
- ticks: {
23437
- color: fontColor,
23438
- callback: formatCallback(lineDataSetsFormat),
23439
- },
23440
- };
23441
- const definition = chart.getDefinition();
23442
- const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23443
- if (useLeftAxis) {
23444
- config.options.scales.y = {
23445
- ...leftVerticalAxis,
23446
- position: "left",
23447
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23448
- };
23449
- }
23450
- if (useRightAxis) {
23451
- config.options.scales.y1 = {
23452
- ...rightVerticalAxis,
23453
- position: "right",
23454
- grid: {
23455
- display: false,
23456
- },
23457
- title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23458
- };
23459
- }
23460
- const colors = new ColorGenerator();
23461
- for (let [index, { label, data }] of dataSetsValues.entries()) {
23462
- const design = definition.dataSets[index];
23463
- const color = colors.next();
23464
- const dataset = {
23465
- label: design?.label ?? label,
23466
- data,
23467
- borderColor: design?.backgroundColor ?? color,
23468
- backgroundColor: design.backgroundColor ?? color,
23469
- yAxisID: design?.yAxisId ?? "y",
23470
- type: index === 0 ? "bar" : "line",
23471
- order: -index,
23472
- };
23473
- config.data.datasets.push(dataset);
23474
- }
23475
- return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
23476
- }
23477
-
23478
- function isDataRangeValid(definition) {
23479
- return definition.dataRange && !rangeReference.test(definition.dataRange)
23480
- ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
23481
- : "Success" /* CommandResult.Success */;
23482
- }
23483
- function checkRangeLimits(check, batchValidations) {
23484
- return batchValidations((definition) => {
23485
- if (definition.sectionRule) {
23486
- return check(definition.sectionRule.rangeMin, "rangeMin");
23487
- }
23488
- return "Success" /* CommandResult.Success */;
23489
- }, (definition) => {
23490
- if (definition.sectionRule) {
23491
- return check(definition.sectionRule.rangeMax, "rangeMax");
23492
- }
23493
- return "Success" /* CommandResult.Success */;
23494
- });
23495
- }
23496
- function checkInflectionPointsValue(check, batchValidations) {
23497
- return batchValidations((definition) => {
23498
- if (definition.sectionRule) {
23499
- return check(definition.sectionRule.lowerInflectionPoint.value, "lowerInflectionPointValue");
23500
- }
23501
- return "Success" /* CommandResult.Success */;
23502
- }, (definition) => {
23503
- if (definition.sectionRule) {
23504
- return check(definition.sectionRule.upperInflectionPoint.value, "upperInflectionPointValue");
23505
- }
23506
- return "Success" /* CommandResult.Success */;
23507
- });
23508
- }
23509
- function checkRangeMinBiggerThanRangeMax(definition) {
23510
- if (definition.sectionRule) {
23511
- if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) {
23512
- return "GaugeRangeMinBiggerThanRangeMax" /* CommandResult.GaugeRangeMinBiggerThanRangeMax */;
23513
- }
23514
- }
23515
- return "Success" /* CommandResult.Success */;
23516
- }
23517
- function checkEmpty(value, valueName) {
23518
- if (value === "") {
23519
- switch (valueName) {
23520
- case "rangeMin":
23521
- return "EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */;
23522
- case "rangeMax":
23523
- return "EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */;
23524
- }
23525
- }
23526
- return "Success" /* CommandResult.Success */;
23527
- }
23528
- function checkNaN(value, valueName) {
23529
- if (isNaN(value)) {
23530
- switch (valueName) {
23531
- case "rangeMin":
23532
- return "GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */;
23533
- case "rangeMax":
23534
- return "GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */;
23535
- case "lowerInflectionPointValue":
23536
- return "GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */;
23537
- case "upperInflectionPointValue":
23538
- return "GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */;
23539
- }
23540
- }
23541
- return "Success" /* CommandResult.Success */;
23542
- }
23543
- class GaugeChart extends AbstractChart {
23544
- dataRange;
23545
- sectionRule;
23546
- background;
23547
- type = "gauge";
23548
- constructor(definition, sheetId, getters) {
23549
- super(definition, sheetId, getters);
23550
- this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
23551
- this.sectionRule = definition.sectionRule;
23552
- this.background = definition.background;
23553
- }
23554
- static validateChartDefinition(validator, definition) {
23555
- return validator.checkValidations(definition, isDataRangeValid, validator.chainValidations(checkRangeLimits(checkEmpty, validator.batchValidations), checkRangeLimits(checkNaN, validator.batchValidations), checkRangeMinBiggerThanRangeMax), validator.chainValidations(checkInflectionPointsValue(checkNaN, validator.batchValidations)));
23556
- }
23557
- static transformDefinition(definition, executed) {
23558
- let dataRangeZone;
23559
- if (definition.dataRange) {
23560
- dataRangeZone = transformZone(toUnboundedZone(definition.dataRange), executed);
23561
- }
23562
- return {
23563
- ...definition,
23564
- dataRange: dataRangeZone ? zoneToXc(dataRangeZone) : undefined,
23565
- };
23566
- }
23567
- static getDefinitionFromContextCreation(context) {
23568
- return {
23569
- background: context.background,
23570
- title: context.title || { text: "" },
23571
- type: "gauge",
23572
- dataRange: context.range ? context.range[0].dataRange : undefined,
23573
- sectionRule: {
23574
- colors: {
23575
- lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
23576
- middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
23577
- upperColor: DEFAULT_GAUGE_UPPER_COLOR,
23578
- },
23579
- rangeMin: "0",
23580
- rangeMax: "100",
23581
- lowerInflectionPoint: {
23582
- type: "percentage",
23583
- value: "15",
23584
- },
23585
- upperInflectionPoint: {
23586
- type: "percentage",
23587
- value: "40",
23588
- },
23589
- },
23590
- };
23591
- }
23592
- copyForSheetId(sheetId) {
23593
- const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);
23594
- const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);
23595
- return new GaugeChart(definition, sheetId, this.getters);
23596
- }
23597
- copyInSheetId(sheetId) {
23598
- const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);
23599
- return new GaugeChart(definition, sheetId, this.getters);
23600
- }
23601
- getDefinition() {
23602
- return this.getDefinitionWithSpecificRanges(this.dataRange);
23603
- }
23604
- getDefinitionWithSpecificRanges(dataRange, targetSheetId) {
23605
- return {
23606
- background: this.background,
23607
- sectionRule: this.sectionRule,
23608
- title: this.title,
23609
- type: "gauge",
23610
- dataRange: dataRange
23611
- ? this.getters.getRangeString(dataRange, targetSheetId || this.sheetId)
23612
- : undefined,
23613
- };
23614
- }
23615
- getDefinitionForExcel() {
23616
- // This kind of graph is not exportable in Excel
23617
- return undefined;
23618
- }
23619
- getContextCreation() {
23620
- return {
23621
- ...this,
23622
- range: this.dataRange
23623
- ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
23624
- : undefined,
23625
- };
23626
- }
23627
- updateRanges(applyChange) {
23628
- const range = adaptChartRange(this.dataRange, applyChange);
23629
- if (this.dataRange === range) {
23630
- return this;
23631
- }
23632
- const definition = this.getDefinitionWithSpecificRanges(range);
23633
- return new GaugeChart(definition, this.sheetId, this.getters);
23634
- }
23635
- }
23636
- function createGaugeChartRuntime(chart, getters) {
23637
- const locale = getters.getLocale();
23638
- const chartColors = chart.sectionRule.colors;
23639
- let gaugeValue = undefined;
23640
- let formattedValue = undefined;
23641
- let format = undefined;
23642
- const dataRange = chart.dataRange;
23643
- if (dataRange !== undefined) {
23644
- const cell = getters.getEvaluatedCell({
23645
- sheetId: dataRange.sheetId,
23646
- col: dataRange.zone.left,
23647
- row: dataRange.zone.top,
23648
- });
23649
- if (cell.type === CellValueType.number) {
23650
- gaugeValue = cell.value;
23651
- formattedValue = cell.formattedValue;
23652
- format = cell.format;
23653
- }
23654
- }
23655
- const minValue = Number(chart.sectionRule.rangeMin);
23656
- const maxValue = Number(chart.sectionRule.rangeMax);
23657
- const lowerPoint = chart.sectionRule.lowerInflectionPoint;
23658
- const upperPoint = chart.sectionRule.upperInflectionPoint;
23659
- const lowerPointValue = getSectionThresholdValue(lowerPoint, minValue, maxValue);
23660
- const upperPointValue = getSectionThresholdValue(upperPoint, minValue, maxValue);
23661
- const inflectionValues = [];
23662
- const colors = [];
23663
- if (lowerPointValue !== undefined) {
23664
- inflectionValues.push({
23665
- value: lowerPointValue,
23666
- label: formatValue(lowerPointValue, { locale, format }),
23667
- });
23668
- colors.push(chartColors.lowerColor);
23669
- }
23670
- if (upperPointValue !== undefined && upperPointValue !== lowerPointValue) {
23671
- inflectionValues.push({
23672
- value: upperPointValue,
23673
- label: formatValue(upperPointValue, { locale, format }),
23674
- });
23675
- colors.push(chartColors.middleColor);
23676
- }
23677
- if (upperPointValue !== undefined &&
23678
- lowerPointValue !== undefined &&
23679
- lowerPointValue > upperPointValue) {
23680
- inflectionValues.reverse();
23681
- colors.reverse();
23682
- }
23683
- colors.push(chartColors.upperColor);
23684
- return {
23685
- background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
23686
- title: chart.title ?? { text: "" },
23687
- minValue: {
23688
- value: minValue,
23689
- label: formatValue(minValue, { locale, format }),
23690
- },
23691
- maxValue: {
23692
- value: maxValue,
23693
- label: formatValue(maxValue, { locale, format }),
23694
- },
23695
- gaugeValue: gaugeValue !== undefined && formattedValue
23696
- ? { value: gaugeValue, label: formattedValue }
23697
- : undefined,
23698
- inflectionValues,
23699
- colors,
23700
- };
23701
- }
23702
- function getSectionThresholdValue(threshold, minValue, maxValue) {
23703
- if (threshold.value === "" || isNaN(Number(threshold.value))) {
23704
- return undefined;
23705
- }
23706
- const numberValue = Number(threshold.value);
23707
- const value = threshold.type === "number"
23708
- ? numberValue
23709
- : minValue + ((maxValue - minValue) * numberValue) / 100;
23710
- return clip(value, minValue, maxValue);
23711
- }
23712
-
23713
23711
  const UNIT_LENGTH = {
23714
23712
  second: 1000,
23715
23713
  minute: 1000 * 60,
@@ -23892,13 +23890,11 @@ function canBeLinearChart(labelRange, getters) {
23892
23890
  }
23893
23891
  let missingTimeAdapterAlreadyWarned = false;
23894
23892
  function isLuxonTimeAdapterInstalled() {
23895
- // @ts-ignore
23896
23893
  if (!window.Chart) {
23897
23894
  return false;
23898
23895
  }
23899
23896
  // @ts-ignore
23900
23897
  const adapter = new window.Chart._adapters._date({});
23901
- // @ts-ignore
23902
23898
  const isInstalled = adapter._id === "luxon";
23903
23899
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
23904
23900
  missingTimeAdapterAlreadyWarned = true;
@@ -23915,9 +23911,7 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23915
23911
  generateLabels(chart) {
23916
23912
  // color the legend labels with the dataset color, without any transparency
23917
23913
  const { data } = chart;
23918
- /** @ts-ignore */
23919
- const labels = window.Chart.defaults.plugins.legend.labels
23920
- .generateLabels(chart);
23914
+ const labels = window.Chart.defaults.plugins.legend.labels.generateLabels(chart);
23921
23915
  for (const [index, label] of labels.entries()) {
23922
23916
  label.fillStyle = data.datasets[index].borderColor;
23923
23917
  }
@@ -23944,151 +23938,628 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23944
23938
  title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23945
23939
  },
23946
23940
  };
23947
- const yAxis = {
23941
+ const formatCallback = (value) => {
23942
+ value = Number(value);
23943
+ if (isNaN(value))
23944
+ return value;
23945
+ const { locale, format } = options;
23946
+ return formatValue(value, {
23947
+ locale,
23948
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23949
+ });
23950
+ };
23951
+ const yAxis = {
23952
+ beginAtZero: true, // the origin of the y axis is always zero
23953
+ ticks: {
23954
+ color: fontColor,
23955
+ callback: formatCallback,
23956
+ },
23957
+ };
23958
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23959
+ if (useLeftAxis) {
23960
+ config.options.scales.y = {
23961
+ ...yAxis,
23962
+ position: "left",
23963
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23964
+ };
23965
+ }
23966
+ if (useRightAxis) {
23967
+ config.options.scales.y1 = {
23968
+ ...yAxis,
23969
+ position: "right",
23970
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23971
+ };
23972
+ }
23973
+ if ("stacked" in chart && chart.stacked) {
23974
+ if (useLeftAxis) {
23975
+ // @ts-ignore chart.js type is broken
23976
+ config.options.scales.y.stacked = true;
23977
+ }
23978
+ if (useRightAxis) {
23979
+ // @ts-ignore chart.js type is broken
23980
+ config.options.scales.y1.stacked = true;
23981
+ }
23982
+ }
23983
+ config.options.plugins.chartShowValuesPlugin = {
23984
+ showValues: chart.showValues,
23985
+ background: chart.background,
23986
+ callback: formatCallback,
23987
+ };
23988
+ return config;
23989
+ }
23990
+ function createLineOrScatterChartRuntime(chart, getters) {
23991
+ const axisType = getChartAxisType(chart, getters);
23992
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23993
+ let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
23994
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23995
+ if (chart.dataSetsHaveTitle &&
23996
+ dataSetsValues[0] &&
23997
+ labels.length > dataSetsValues[0].data.length) {
23998
+ labels.shift();
23999
+ }
24000
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24001
+ if (axisType === "time") {
24002
+ ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
24003
+ }
24004
+ if (chart.aggregated) {
24005
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24006
+ }
24007
+ const locale = getters.getLocale();
24008
+ const truncateLabels = axisType === "category";
24009
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
24010
+ const options = { format: dataSetFormat, locale, truncateLabels };
24011
+ const config = getLineOrScatterConfiguration(chart, labels, options);
24012
+ const labelFormat = getChartLabelFormat(getters, chart.labelRange);
24013
+ if (axisType === "time") {
24014
+ const axis = {
24015
+ type: "time",
24016
+ time: getChartTimeOptions(labels, labelFormat, locale),
24017
+ };
24018
+ Object.assign(config.options.scales.x, axis);
24019
+ config.options.scales.x.ticks.maxTicksLimit = 15;
24020
+ }
24021
+ else if (axisType === "linear") {
24022
+ config.options.scales.x.type = "linear";
24023
+ config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
24024
+ config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
24025
+ return formatValue(tooltipItem[0].parsed.x || tooltipItem[0].label, {
24026
+ locale,
24027
+ format: labelFormat,
24028
+ });
24029
+ };
24030
+ }
24031
+ const areaChart = "fillArea" in chart ? chart.fillArea : false;
24032
+ const stackedChart = "stacked" in chart ? chart.stacked : false;
24033
+ const cumulative = "cumulative" in chart ? chart.cumulative : false;
24034
+ const colors = new ColorGenerator();
24035
+ const definition = chart.getDefinition();
24036
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
24037
+ const color = colors.next();
24038
+ let backgroundRGBA = colorToRGBA(color);
24039
+ if (areaChart) {
24040
+ backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
24041
+ }
24042
+ if (cumulative) {
24043
+ let accumulator = 0;
24044
+ data = data.map((value) => {
24045
+ if (!isNaN(value)) {
24046
+ accumulator += parseFloat(value);
24047
+ return accumulator;
24048
+ }
24049
+ return value;
24050
+ });
24051
+ }
24052
+ if (["linear", "time"].includes(axisType)) {
24053
+ // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
24054
+ data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
24055
+ }
24056
+ const backgroundColor = rgbaToHex(backgroundRGBA);
24057
+ const dataset = {
24058
+ label,
24059
+ data,
24060
+ tension: 0, // 0 -> render straight lines, which is much faster
24061
+ borderColor: color,
24062
+ backgroundColor,
24063
+ pointBackgroundColor: color,
24064
+ fill: areaChart ? getFillingMode(index, stackedChart) : false,
24065
+ };
24066
+ config.data.datasets.push(dataset);
24067
+ }
24068
+ for (const [index, dataset] of config.data.datasets.entries()) {
24069
+ if (definition.dataSets?.[index]?.backgroundColor) {
24070
+ const color = definition.dataSets[index].backgroundColor;
24071
+ dataset.backgroundColor = color;
24072
+ dataset.borderColor = color;
24073
+ //@ts-ignore
24074
+ dataset.pointBackgroundColor = color;
24075
+ }
24076
+ if (definition.dataSets?.[index]?.label) {
24077
+ const label = definition.dataSets[index].label;
24078
+ dataset.label = label;
24079
+ }
24080
+ if (definition.dataSets?.[index]?.yAxisId) {
24081
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
24082
+ }
24083
+ }
24084
+ return {
24085
+ chartJsConfig: config,
24086
+ background: chart.background || BACKGROUND_CHART_COLOR,
24087
+ dataSetsValues,
24088
+ labelValues,
24089
+ dataSetFormat,
24090
+ labelFormat,
24091
+ };
24092
+ }
24093
+
24094
+ class ComboChart extends AbstractChart {
24095
+ dataSets;
24096
+ labelRange;
24097
+ background;
24098
+ legendPosition;
24099
+ aggregated;
24100
+ dataSetsHaveTitle;
24101
+ dataSetDesign;
24102
+ axesDesign;
24103
+ type = "combo";
24104
+ showValues;
24105
+ constructor(definition, sheetId, getters) {
24106
+ super(definition, sheetId, getters);
24107
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
24108
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24109
+ this.background = definition.background;
24110
+ this.legendPosition = definition.legendPosition;
24111
+ this.aggregated = definition.aggregated;
24112
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24113
+ this.dataSetDesign = definition.dataSets;
24114
+ this.axesDesign = definition.axesDesign;
24115
+ this.showValues = definition.showValues;
24116
+ }
24117
+ static transformDefinition(definition, executed) {
24118
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24119
+ }
24120
+ static validateChartDefinition(validator, definition) {
24121
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24122
+ }
24123
+ getContextCreation() {
24124
+ const range = [];
24125
+ for (const [i, dataSet] of this.dataSets.entries()) {
24126
+ range.push({
24127
+ ...this.dataSetDesign?.[i],
24128
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24129
+ });
24130
+ }
24131
+ return {
24132
+ ...this,
24133
+ range,
24134
+ auxiliaryRange: this.labelRange
24135
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24136
+ : undefined,
24137
+ };
24138
+ }
24139
+ getDefinition() {
24140
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24141
+ }
24142
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24143
+ const ranges = [];
24144
+ for (const [i, dataSet] of dataSets.entries()) {
24145
+ ranges.push({
24146
+ ...this.dataSetDesign?.[i],
24147
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24148
+ });
24149
+ }
24150
+ return {
24151
+ type: "combo",
24152
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24153
+ background: this.background,
24154
+ dataSets: ranges,
24155
+ legendPosition: this.legendPosition,
24156
+ labelRange: labelRange
24157
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24158
+ : undefined,
24159
+ title: this.title,
24160
+ aggregated: this.aggregated,
24161
+ axesDesign: this.axesDesign,
24162
+ showValues: this.showValues,
24163
+ };
24164
+ }
24165
+ getDefinitionForExcel() {
24166
+ // Excel does not support aggregating labels
24167
+ if (this.aggregated) {
24168
+ return undefined;
24169
+ }
24170
+ const dataSets = this.dataSets
24171
+ .map((ds) => toExcelDataset(this.getters, ds))
24172
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
24173
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24174
+ const definition = this.getDefinition();
24175
+ return {
24176
+ ...definition,
24177
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
24178
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
24179
+ dataSets,
24180
+ labelRange,
24181
+ verticalAxis: getDefinedAxis(definition),
24182
+ };
24183
+ }
24184
+ updateRanges(applyChange) {
24185
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24186
+ if (!isStale) {
24187
+ return this;
24188
+ }
24189
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24190
+ return new ComboChart(definition, this.sheetId, this.getters);
24191
+ }
24192
+ static getDefinitionFromContextCreation(context) {
24193
+ return {
24194
+ background: context.background,
24195
+ dataSets: context.range ?? [],
24196
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24197
+ aggregated: context.aggregated,
24198
+ legendPosition: context.legendPosition ?? "top",
24199
+ title: context.title || { text: "" },
24200
+ labelRange: context.auxiliaryRange || undefined,
24201
+ type: "combo",
24202
+ axesDesign: context.axesDesign,
24203
+ showValues: context.showValues,
24204
+ };
24205
+ }
24206
+ copyForSheetId(sheetId) {
24207
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24208
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24209
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24210
+ return new ComboChart(definition, sheetId, this.getters);
24211
+ }
24212
+ copyInSheetId(sheetId) {
24213
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24214
+ return new ComboChart(definition, sheetId, this.getters);
24215
+ }
24216
+ }
24217
+ function createComboChartRuntime(chart, getters) {
24218
+ const mainDataSetFormat = chart.dataSets.length
24219
+ ? getChartDatasetFormat(getters, [chart.dataSets[0]])
24220
+ : undefined;
24221
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
24222
+ const locale = getters.getLocale();
24223
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
24224
+ let labels = labelValues.formattedValues;
24225
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
24226
+ if (chart.dataSetsHaveTitle &&
24227
+ dataSetsValues[0] &&
24228
+ labels.length > dataSetsValues[0].data.length) {
24229
+ labels.shift();
24230
+ }
24231
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24232
+ if (chart.aggregated) {
24233
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24234
+ }
24235
+ const localeFormat = { format: mainDataSetFormat, locale };
24236
+ const fontColor = chartFontColor(chart.background);
24237
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
24238
+ const legend = {
24239
+ labels: { color: fontColor },
24240
+ reverse: true,
24241
+ };
24242
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
24243
+ legend.display = false;
24244
+ }
24245
+ else {
24246
+ legend.position = chart.legendPosition;
24247
+ }
24248
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
24249
+ config.options.layout = {
24250
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
24251
+ };
24252
+ config.options.scales = {
24253
+ x: {
24254
+ ticks: {
24255
+ padding: 5,
24256
+ color: fontColor,
24257
+ },
24258
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
24259
+ },
24260
+ };
24261
+ const formatCallback = (format) => {
24262
+ return (value) => {
24263
+ value = Number(value);
24264
+ if (isNaN(value))
24265
+ return value;
24266
+ const { locale } = localeFormat;
24267
+ return formatValue(value, {
24268
+ locale,
24269
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
24270
+ });
24271
+ };
24272
+ };
24273
+ const leftVerticalAxis = {
24274
+ beginAtZero: true, // the origin of the y axis is always zero
24275
+ ticks: {
24276
+ color: fontColor,
24277
+ callback: formatCallback(mainDataSetFormat),
24278
+ },
24279
+ };
24280
+ const rightVerticalAxis = {
23948
24281
  beginAtZero: true, // the origin of the y axis is always zero
23949
24282
  ticks: {
23950
24283
  color: fontColor,
23951
- callback: (value) => {
23952
- value = Number(value);
23953
- if (isNaN(value))
23954
- return value;
23955
- const { locale, format } = options;
23956
- return formatValue(value, {
23957
- locale,
23958
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23959
- });
23960
- },
24284
+ callback: formatCallback(lineDataSetsFormat),
23961
24285
  },
23962
24286
  };
23963
- const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
24287
+ const definition = chart.getDefinition();
24288
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23964
24289
  if (useLeftAxis) {
23965
24290
  config.options.scales.y = {
23966
- ...yAxis,
24291
+ ...leftVerticalAxis,
23967
24292
  position: "left",
23968
24293
  title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23969
24294
  };
23970
24295
  }
23971
24296
  if (useRightAxis) {
23972
24297
  config.options.scales.y1 = {
23973
- ...yAxis,
24298
+ ...rightVerticalAxis,
23974
24299
  position: "right",
24300
+ grid: {
24301
+ display: false,
24302
+ },
23975
24303
  title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23976
24304
  };
23977
24305
  }
23978
- if ("stacked" in chart && chart.stacked) {
23979
- if (useLeftAxis) {
23980
- // @ts-ignore chart.js type is broken
23981
- config.options.scales.y.stacked = true;
24306
+ config.options.plugins.chartShowValuesPlugin = {
24307
+ showValues: chart.showValues,
24308
+ background: chart.background,
24309
+ callback: formatCallback(mainDataSetFormat),
24310
+ };
24311
+ const colors = new ColorGenerator();
24312
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
24313
+ const design = definition.dataSets[index];
24314
+ const color = colors.next();
24315
+ const dataset = {
24316
+ label: design?.label ?? label,
24317
+ data,
24318
+ borderColor: design?.backgroundColor ?? color,
24319
+ backgroundColor: design.backgroundColor ?? color,
24320
+ yAxisID: design?.yAxisId ?? "y",
24321
+ type: index === 0 ? "bar" : "line",
24322
+ order: -index,
24323
+ };
24324
+ config.data.datasets.push(dataset);
24325
+ }
24326
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24327
+ }
24328
+
24329
+ function isDataRangeValid(definition) {
24330
+ return definition.dataRange && !rangeReference.test(definition.dataRange)
24331
+ ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
24332
+ : "Success" /* CommandResult.Success */;
24333
+ }
24334
+ function checkRangeLimits(check, batchValidations) {
24335
+ return batchValidations((definition) => {
24336
+ if (definition.sectionRule) {
24337
+ return check(definition.sectionRule.rangeMin, "rangeMin");
23982
24338
  }
23983
- if (useRightAxis) {
23984
- // @ts-ignore chart.js type is broken
23985
- config.options.scales.y1.stacked = true;
24339
+ return "Success" /* CommandResult.Success */;
24340
+ }, (definition) => {
24341
+ if (definition.sectionRule) {
24342
+ return check(definition.sectionRule.rangeMax, "rangeMax");
24343
+ }
24344
+ return "Success" /* CommandResult.Success */;
24345
+ });
24346
+ }
24347
+ function checkInflectionPointsValue(check, batchValidations) {
24348
+ return batchValidations((definition) => {
24349
+ if (definition.sectionRule) {
24350
+ return check(definition.sectionRule.lowerInflectionPoint.value, "lowerInflectionPointValue");
24351
+ }
24352
+ return "Success" /* CommandResult.Success */;
24353
+ }, (definition) => {
24354
+ if (definition.sectionRule) {
24355
+ return check(definition.sectionRule.upperInflectionPoint.value, "upperInflectionPointValue");
24356
+ }
24357
+ return "Success" /* CommandResult.Success */;
24358
+ });
24359
+ }
24360
+ function checkRangeMinBiggerThanRangeMax(definition) {
24361
+ if (definition.sectionRule) {
24362
+ if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) {
24363
+ return "GaugeRangeMinBiggerThanRangeMax" /* CommandResult.GaugeRangeMinBiggerThanRangeMax */;
23986
24364
  }
23987
24365
  }
23988
- return config;
24366
+ return "Success" /* CommandResult.Success */;
23989
24367
  }
23990
- function createLineOrScatterChartRuntime(chart, getters) {
23991
- const axisType = getChartAxisType(chart, getters);
23992
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23993
- let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
23994
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23995
- if (chart.dataSetsHaveTitle &&
23996
- dataSetsValues[0] &&
23997
- labels.length > dataSetsValues[0].data.length) {
23998
- labels.shift();
24368
+ function checkEmpty(value, valueName) {
24369
+ if (value === "") {
24370
+ switch (valueName) {
24371
+ case "rangeMin":
24372
+ return "EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */;
24373
+ case "rangeMax":
24374
+ return "EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */;
24375
+ }
23999
24376
  }
24000
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24001
- if (axisType === "time") {
24002
- ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
24377
+ return "Success" /* CommandResult.Success */;
24378
+ }
24379
+ function checkNaN(value, valueName) {
24380
+ if (isNaN(value)) {
24381
+ switch (valueName) {
24382
+ case "rangeMin":
24383
+ return "GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */;
24384
+ case "rangeMax":
24385
+ return "GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */;
24386
+ case "lowerInflectionPointValue":
24387
+ return "GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */;
24388
+ case "upperInflectionPointValue":
24389
+ return "GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */;
24390
+ }
24003
24391
  }
24004
- if (chart.aggregated) {
24005
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24392
+ return "Success" /* CommandResult.Success */;
24393
+ }
24394
+ class GaugeChart extends AbstractChart {
24395
+ dataRange;
24396
+ sectionRule;
24397
+ background;
24398
+ type = "gauge";
24399
+ constructor(definition, sheetId, getters) {
24400
+ super(definition, sheetId, getters);
24401
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
24402
+ this.sectionRule = definition.sectionRule;
24403
+ this.background = definition.background;
24006
24404
  }
24007
- const locale = getters.getLocale();
24008
- const truncateLabels = axisType === "category";
24009
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
24010
- const options = { format: dataSetFormat, locale, truncateLabels };
24011
- const config = getLineOrScatterConfiguration(chart, labels, options);
24012
- const labelFormat = getChartLabelFormat(getters, chart.labelRange);
24013
- if (axisType === "time") {
24014
- const axis = {
24015
- type: "time",
24016
- time: getChartTimeOptions(labels, labelFormat, locale),
24405
+ static validateChartDefinition(validator, definition) {
24406
+ return validator.checkValidations(definition, isDataRangeValid, validator.chainValidations(checkRangeLimits(checkEmpty, validator.batchValidations), checkRangeLimits(checkNaN, validator.batchValidations), checkRangeMinBiggerThanRangeMax), validator.chainValidations(checkInflectionPointsValue(checkNaN, validator.batchValidations)));
24407
+ }
24408
+ static transformDefinition(definition, executed) {
24409
+ let dataRangeZone;
24410
+ if (definition.dataRange) {
24411
+ dataRangeZone = transformZone(toUnboundedZone(definition.dataRange), executed);
24412
+ }
24413
+ return {
24414
+ ...definition,
24415
+ dataRange: dataRangeZone ? zoneToXc(dataRangeZone) : undefined,
24017
24416
  };
24018
- Object.assign(config.options.scales.x, axis);
24019
- config.options.scales.x.ticks.maxTicksLimit = 15;
24020
24417
  }
24021
- else if (axisType === "linear") {
24022
- config.options.scales.x.type = "linear";
24023
- config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
24024
- config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
24025
- return formatValue(tooltipItem[0].parsed.x || tooltipItem[0].label, {
24026
- locale,
24027
- format: labelFormat,
24028
- });
24418
+ static getDefinitionFromContextCreation(context) {
24419
+ return {
24420
+ background: context.background,
24421
+ title: context.title || { text: "" },
24422
+ type: "gauge",
24423
+ dataRange: context.range ? context.range[0].dataRange : undefined,
24424
+ sectionRule: {
24425
+ colors: {
24426
+ lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
24427
+ middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
24428
+ upperColor: DEFAULT_GAUGE_UPPER_COLOR,
24429
+ },
24430
+ rangeMin: "0",
24431
+ rangeMax: "100",
24432
+ lowerInflectionPoint: {
24433
+ type: "percentage",
24434
+ value: "15",
24435
+ },
24436
+ upperInflectionPoint: {
24437
+ type: "percentage",
24438
+ value: "40",
24439
+ },
24440
+ },
24029
24441
  };
24030
24442
  }
24031
- const stacked = "stacked" in chart ? chart.stacked : false;
24032
- const cumulative = "cumulative" in chart ? chart.cumulative : false;
24033
- const colors = new ColorGenerator();
24034
- const definition = chart.getDefinition();
24035
- for (let [index, { label, data }] of dataSetsValues.entries()) {
24036
- if (["linear", "time"].includes(axisType)) {
24037
- // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
24038
- data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
24039
- }
24040
- const color = colors.next();
24041
- let backgroundRGBA = colorToRGBA(color);
24042
- if (stacked) {
24043
- backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
24044
- }
24045
- if (cumulative) {
24046
- let accumulator = 0;
24047
- data = data.map((value) => {
24048
- if (!isNaN(value)) {
24049
- accumulator += parseFloat(value);
24050
- return accumulator;
24051
- }
24052
- return value;
24053
- });
24054
- }
24055
- const backgroundColor = rgbaToHex(backgroundRGBA);
24056
- const dataset = {
24057
- label,
24058
- data,
24059
- tension: 0, // 0 -> render straight lines, which is much faster
24060
- borderColor: color,
24061
- backgroundColor,
24062
- pointBackgroundColor: color,
24063
- fill: stacked ? getFillingMode(index) : false,
24443
+ copyForSheetId(sheetId) {
24444
+ const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);
24445
+ const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);
24446
+ return new GaugeChart(definition, sheetId, this.getters);
24447
+ }
24448
+ copyInSheetId(sheetId) {
24449
+ const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);
24450
+ return new GaugeChart(definition, sheetId, this.getters);
24451
+ }
24452
+ getDefinition() {
24453
+ return this.getDefinitionWithSpecificRanges(this.dataRange);
24454
+ }
24455
+ getDefinitionWithSpecificRanges(dataRange, targetSheetId) {
24456
+ return {
24457
+ background: this.background,
24458
+ sectionRule: this.sectionRule,
24459
+ title: this.title,
24460
+ type: "gauge",
24461
+ dataRange: dataRange
24462
+ ? this.getters.getRangeString(dataRange, targetSheetId || this.sheetId)
24463
+ : undefined,
24064
24464
  };
24065
- config.data.datasets.push(dataset);
24066
24465
  }
24067
- for (const [index, dataset] of config.data.datasets.entries()) {
24068
- if (definition.dataSets?.[index]?.backgroundColor) {
24069
- const color = definition.dataSets[index].backgroundColor;
24070
- dataset.backgroundColor = color;
24071
- dataset.borderColor = color;
24072
- //@ts-ignore
24073
- dataset.pointBackgroundColor = color;
24074
- }
24075
- if (definition.dataSets?.[index]?.label) {
24076
- const label = definition.dataSets[index].label;
24077
- dataset.label = label;
24466
+ getDefinitionForExcel() {
24467
+ // This kind of graph is not exportable in Excel
24468
+ return undefined;
24469
+ }
24470
+ getContextCreation() {
24471
+ return {
24472
+ ...this,
24473
+ range: this.dataRange
24474
+ ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
24475
+ : undefined,
24476
+ };
24477
+ }
24478
+ updateRanges(applyChange) {
24479
+ const range = adaptChartRange(this.dataRange, applyChange);
24480
+ if (this.dataRange === range) {
24481
+ return this;
24078
24482
  }
24079
- if (definition.dataSets?.[index]?.yAxisId) {
24080
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
24483
+ const definition = this.getDefinitionWithSpecificRanges(range);
24484
+ return new GaugeChart(definition, this.sheetId, this.getters);
24485
+ }
24486
+ }
24487
+ function createGaugeChartRuntime(chart, getters) {
24488
+ const locale = getters.getLocale();
24489
+ const chartColors = chart.sectionRule.colors;
24490
+ let gaugeValue = undefined;
24491
+ let formattedValue = undefined;
24492
+ let format = undefined;
24493
+ const dataRange = chart.dataRange;
24494
+ if (dataRange !== undefined) {
24495
+ const cell = getters.getEvaluatedCell({
24496
+ sheetId: dataRange.sheetId,
24497
+ col: dataRange.zone.left,
24498
+ row: dataRange.zone.top,
24499
+ });
24500
+ if (cell.type === CellValueType.number) {
24501
+ gaugeValue = cell.value;
24502
+ formattedValue = cell.formattedValue;
24503
+ format = cell.format;
24081
24504
  }
24082
24505
  }
24506
+ const minValue = Number(chart.sectionRule.rangeMin);
24507
+ const maxValue = Number(chart.sectionRule.rangeMax);
24508
+ const lowerPoint = chart.sectionRule.lowerInflectionPoint;
24509
+ const upperPoint = chart.sectionRule.upperInflectionPoint;
24510
+ const lowerPointValue = getSectionThresholdValue(lowerPoint, minValue, maxValue);
24511
+ const upperPointValue = getSectionThresholdValue(upperPoint, minValue, maxValue);
24512
+ const inflectionValues = [];
24513
+ const colors = [];
24514
+ if (lowerPointValue !== undefined) {
24515
+ inflectionValues.push({
24516
+ value: lowerPointValue,
24517
+ label: formatValue(lowerPointValue, { locale, format }),
24518
+ });
24519
+ colors.push(chartColors.lowerColor);
24520
+ }
24521
+ if (upperPointValue !== undefined && upperPointValue !== lowerPointValue) {
24522
+ inflectionValues.push({
24523
+ value: upperPointValue,
24524
+ label: formatValue(upperPointValue, { locale, format }),
24525
+ });
24526
+ colors.push(chartColors.middleColor);
24527
+ }
24528
+ if (upperPointValue !== undefined &&
24529
+ lowerPointValue !== undefined &&
24530
+ lowerPointValue > upperPointValue) {
24531
+ inflectionValues.reverse();
24532
+ colors.reverse();
24533
+ }
24534
+ colors.push(chartColors.upperColor);
24083
24535
  return {
24084
- chartJsConfig: config,
24085
- background: chart.background || BACKGROUND_CHART_COLOR,
24086
- dataSetsValues,
24087
- labelValues,
24088
- dataSetFormat,
24089
- labelFormat,
24536
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
24537
+ title: chart.title ?? { text: "" },
24538
+ minValue: {
24539
+ value: minValue,
24540
+ label: formatValue(minValue, { locale, format }),
24541
+ },
24542
+ maxValue: {
24543
+ value: maxValue,
24544
+ label: formatValue(maxValue, { locale, format }),
24545
+ },
24546
+ gaugeValue: gaugeValue !== undefined && formattedValue
24547
+ ? { value: gaugeValue, label: formattedValue }
24548
+ : undefined,
24549
+ inflectionValues,
24550
+ colors,
24090
24551
  };
24091
24552
  }
24553
+ function getSectionThresholdValue(threshold, minValue, maxValue) {
24554
+ if (threshold.value === "" || isNaN(Number(threshold.value))) {
24555
+ return undefined;
24556
+ }
24557
+ const numberValue = Number(threshold.value);
24558
+ const value = threshold.type === "number"
24559
+ ? numberValue
24560
+ : minValue + ((maxValue - minValue) * numberValue) / 100;
24561
+ return clip(value, minValue, maxValue);
24562
+ }
24092
24563
 
24093
24564
  class LineChart extends AbstractChart {
24094
24565
  dataSets;
@@ -24103,6 +24574,8 @@ class LineChart extends AbstractChart {
24103
24574
  cumulative;
24104
24575
  dataSetDesign;
24105
24576
  axesDesign;
24577
+ fillArea;
24578
+ showValues;
24106
24579
  constructor(definition, sheetId, getters) {
24107
24580
  super(definition, sheetId, getters);
24108
24581
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24116,6 +24589,8 @@ class LineChart extends AbstractChart {
24116
24589
  this.cumulative = definition.cumulative;
24117
24590
  this.dataSetDesign = definition.dataSets;
24118
24591
  this.axesDesign = definition.axesDesign;
24592
+ this.fillArea = definition.fillArea;
24593
+ this.showValues = definition.showValues;
24119
24594
  }
24120
24595
  static validateChartDefinition(validator, definition) {
24121
24596
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24137,6 +24612,8 @@ class LineChart extends AbstractChart {
24137
24612
  aggregated: context.aggregated ?? false,
24138
24613
  cumulative: context.cumulative ?? false,
24139
24614
  axesDesign: context.axesDesign,
24615
+ fillArea: context.fillArea,
24616
+ showValues: context.showValues,
24140
24617
  };
24141
24618
  }
24142
24619
  getDefinition() {
@@ -24165,6 +24642,8 @@ class LineChart extends AbstractChart {
24165
24642
  aggregated: this.aggregated,
24166
24643
  cumulative: this.cumulative,
24167
24644
  axesDesign: this.axesDesign,
24645
+ fillArea: this.fillArea,
24646
+ showValues: this.showValues,
24168
24647
  };
24169
24648
  }
24170
24649
  getContextCreation() {
@@ -24220,10 +24699,6 @@ class LineChart extends AbstractChart {
24220
24699
  return new LineChart(definition, sheetId, this.getters);
24221
24700
  }
24222
24701
  }
24223
- function createLineChartRuntime(chart, getters) {
24224
- const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
24225
- return { chartJsConfig, background };
24226
- }
24227
24702
 
24228
24703
  class PieChart extends AbstractChart {
24229
24704
  dataSets;
@@ -24234,6 +24709,7 @@ class PieChart extends AbstractChart {
24234
24709
  aggregated;
24235
24710
  dataSetsHaveTitle;
24236
24711
  isDoughnut;
24712
+ showValues;
24237
24713
  constructor(definition, sheetId, getters) {
24238
24714
  super(definition, sheetId, getters);
24239
24715
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24243,6 +24719,7 @@ class PieChart extends AbstractChart {
24243
24719
  this.aggregated = definition.aggregated;
24244
24720
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24245
24721
  this.isDoughnut = definition.isDoughnut;
24722
+ this.showValues = definition.showValues;
24246
24723
  }
24247
24724
  static transformDefinition(definition, executed) {
24248
24725
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24261,6 +24738,7 @@ class PieChart extends AbstractChart {
24261
24738
  labelRange: context.auxiliaryRange || undefined,
24262
24739
  aggregated: context.aggregated ?? false,
24263
24740
  isDoughnut: false,
24741
+ showValues: context.showValues,
24264
24742
  };
24265
24743
  }
24266
24744
  getDefinition() {
@@ -24292,6 +24770,7 @@ class PieChart extends AbstractChart {
24292
24770
  title: this.title,
24293
24771
  aggregated: this.aggregated,
24294
24772
  isDoughnut: this.isDoughnut,
24773
+ showValues: this.showValues,
24295
24774
  };
24296
24775
  }
24297
24776
  copyForSheetId(sheetId) {
@@ -24359,6 +24838,7 @@ function getPieConfiguration(chart, labels, localeFormat) {
24359
24838
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
24360
24839
  return xLabel ? `${xLabel}: ${yLabelStr} (${percentage}%)` : `${yLabelStr} (${percentage}%)`;
24361
24840
  };
24841
+ config.options.plugins.chartShowValuesPlugin = { showValues: chart.showValues };
24362
24842
  return config;
24363
24843
  }
24364
24844
  function getPieColors(colors, dataSetsValues) {
@@ -24444,6 +24924,7 @@ class PyramidChart extends AbstractChart {
24444
24924
  axesDesign;
24445
24925
  horizontal = true;
24446
24926
  stacked = true;
24927
+ showValues;
24447
24928
  constructor(definition, sheetId, getters) {
24448
24929
  super(definition, sheetId, getters);
24449
24930
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
@@ -24454,6 +24935,7 @@ class PyramidChart extends AbstractChart {
24454
24935
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24455
24936
  this.dataSetDesign = definition.dataSets;
24456
24937
  this.axesDesign = definition.axesDesign;
24938
+ this.showValues = definition.showValues;
24457
24939
  }
24458
24940
  static transformDefinition(definition, executed) {
24459
24941
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24474,6 +24956,7 @@ class PyramidChart extends AbstractChart {
24474
24956
  axesDesign: context.axesDesign,
24475
24957
  horizontal: true,
24476
24958
  stacked: true,
24959
+ showValues: context.showValues,
24477
24960
  };
24478
24961
  }
24479
24962
  getContextCreation() {
@@ -24527,6 +25010,7 @@ class PyramidChart extends AbstractChart {
24527
25010
  axesDesign: this.axesDesign,
24528
25011
  horizontal: true,
24529
25012
  stacked: true,
25013
+ showValues: this.showValues,
24530
25014
  };
24531
25015
  }
24532
25016
  getDefinitionForExcel() {
@@ -24561,6 +25045,8 @@ function createPyramidChartRuntime(chart, getters) {
24561
25045
  const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24562
25046
  return tooltipLabelCallback(tooltipItem);
24563
25047
  };
25048
+ const callback = config.options.plugins.chartShowValuesPlugin.callback;
25049
+ config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
24564
25050
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24565
25051
  }
24566
25052
 
@@ -24575,6 +25061,7 @@ class ScatterChart extends AbstractChart {
24575
25061
  dataSetsHaveTitle;
24576
25062
  dataSetDesign;
24577
25063
  axesDesign;
25064
+ showValues;
24578
25065
  constructor(definition, sheetId, getters) {
24579
25066
  super(definition, sheetId, getters);
24580
25067
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24586,6 +25073,7 @@ class ScatterChart extends AbstractChart {
24586
25073
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24587
25074
  this.dataSetDesign = definition.dataSets;
24588
25075
  this.axesDesign = definition.axesDesign;
25076
+ this.showValues = definition.showValues;
24589
25077
  }
24590
25078
  static validateChartDefinition(validator, definition) {
24591
25079
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24605,6 +25093,7 @@ class ScatterChart extends AbstractChart {
24605
25093
  labelRange: context.auxiliaryRange || undefined,
24606
25094
  aggregated: context.aggregated ?? false,
24607
25095
  axesDesign: context.axesDesign,
25096
+ showValues: context.showValues,
24608
25097
  };
24609
25098
  }
24610
25099
  getDefinition() {
@@ -24631,6 +25120,7 @@ class ScatterChart extends AbstractChart {
24631
25120
  labelsAsText: this.labelsAsText,
24632
25121
  aggregated: this.aggregated,
24633
25122
  axesDesign: this.axesDesign,
25123
+ showValues: this.showValues,
24634
25124
  };
24635
25125
  }
24636
25126
  getContextCreation() {
@@ -24731,6 +25221,7 @@ class WaterfallChart extends AbstractChart {
24731
25221
  subTotalValuesColor;
24732
25222
  dataSetDesign;
24733
25223
  axesDesign;
25224
+ showValues;
24734
25225
  constructor(definition, sheetId, getters) {
24735
25226
  super(definition, sheetId, getters);
24736
25227
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24748,6 +25239,7 @@ class WaterfallChart extends AbstractChart {
24748
25239
  this.firstValueAsSubtotal = definition.firstValueAsSubtotal;
24749
25240
  this.dataSetDesign = definition.dataSets;
24750
25241
  this.axesDesign = definition.axesDesign;
25242
+ this.showValues = definition.showValues;
24751
25243
  }
24752
25244
  static transformDefinition(definition, executed) {
24753
25245
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24770,6 +25262,7 @@ class WaterfallChart extends AbstractChart {
24770
25262
  showConnectorLines: context.showConnectorLines ?? true,
24771
25263
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
24772
25264
  axesDesign: context.axesDesign,
25265
+ showValues: context.showValues,
24773
25266
  };
24774
25267
  }
24775
25268
  getContextCreation() {
@@ -24828,6 +25321,7 @@ class WaterfallChart extends AbstractChart {
24828
25321
  subTotalValuesColor: this.subTotalValuesColor,
24829
25322
  firstValueAsSubtotal: this.firstValueAsSubtotal,
24830
25323
  axesDesign: this.axesDesign,
25324
+ showValues: this.showValues,
24831
25325
  };
24832
25326
  }
24833
25327
  getDefinitionForExcel() {
@@ -24925,6 +25419,10 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24925
25419
  },
24926
25420
  };
24927
25421
  config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
25422
+ config.options.plugins.chartShowValuesPlugin = {
25423
+ showValues: chart.showValues,
25424
+ background: chart.background,
25425
+ };
24928
25426
  return config;
24929
25427
  }
24930
25428
  function createWaterfallChartRuntime(chart, getters) {
@@ -25017,7 +25515,7 @@ chartRegistry.add("combo", {
25017
25515
  chartRegistry.add("line", {
25018
25516
  match: (type) => type === "line",
25019
25517
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
25020
- getChartRuntime: createLineChartRuntime,
25518
+ getChartRuntime: createLineOrScatterChartRuntime,
25021
25519
  validateChartDefinition: LineChart.validateChartDefinition,
25022
25520
  transformDefinition: LineChart.transformDefinition,
25023
25521
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
@@ -25091,27 +25589,46 @@ const chartCategories = {
25091
25589
  line: _t("Line"),
25092
25590
  column: _t("Column"),
25093
25591
  bar: _t("Bar"),
25592
+ area: _t("Area"),
25094
25593
  pie: _t("Pie"),
25095
25594
  misc: _t("Miscellaneous"),
25096
25595
  };
25097
25596
  const chartSubtypeRegistry = new Registry();
25098
25597
  chartSubtypeRegistry
25099
25598
  .add("line", {
25100
- matcher: (definition) => definition.type === "line" && !definition.stacked,
25599
+ matcher: (definition) => definition.type === "line" && !definition.stacked && !definition.fillArea,
25101
25600
  displayName: _t("Line"),
25102
25601
  chartType: "line",
25103
25602
  chartSubtype: "line",
25104
- subtypeDefinition: { stacked: false },
25603
+ subtypeDefinition: { stacked: false, fillArea: false },
25105
25604
  category: "line",
25106
25605
  preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25107
25606
  })
25108
25607
  .add("stacked_line", {
25109
- matcher: (definition) => definition.type === "line" && definition.stacked,
25608
+ matcher: (definition) => definition.type === "line" && !definition.fillArea && !!definition.stacked,
25110
25609
  displayName: _t("Stacked Line"),
25111
25610
  chartType: "line",
25112
25611
  chartSubtype: "stacked_line",
25113
- subtypeDefinition: { stacked: true },
25612
+ subtypeDefinition: { stacked: true, fillArea: false },
25114
25613
  category: "line",
25614
+ preview: "o-spreadsheet-ChartPreview.STACKED_LINE_CHART",
25615
+ })
25616
+ .add("area", {
25617
+ matcher: (definition) => definition.type === "line" && !definition.stacked && !!definition.fillArea,
25618
+ displayName: _t("Area"),
25619
+ chartType: "line",
25620
+ chartSubtype: "area",
25621
+ subtypeDefinition: { stacked: false, fillArea: true },
25622
+ category: "area",
25623
+ preview: "o-spreadsheet-ChartPreview.AREA_CHART",
25624
+ })
25625
+ .add("stacked_area", {
25626
+ matcher: (definition) => definition.type === "line" && definition.stacked && !!definition.fillArea,
25627
+ displayName: _t("Stacked Area"),
25628
+ chartType: "line",
25629
+ chartSubtype: "stacked_area",
25630
+ subtypeDefinition: { stacked: true, fillArea: true },
25631
+ category: "area",
25115
25632
  preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25116
25633
  })
25117
25634
  .add("scatter", {
@@ -25535,7 +26052,7 @@ class MenuItemRegistry extends Registry {
25535
26052
  * @param path Path of items to add this subitem
25536
26053
  * @param value Subitem to add
25537
26054
  */
25538
- addChild(key, path, value) {
26055
+ addChild(key, path, value, options = { force: false }) {
25539
26056
  if (typeof value !== "function" && value.id === undefined) {
25540
26057
  value.id = key;
25541
26058
  }
@@ -25557,6 +26074,19 @@ class MenuItemRegistry extends Registry {
25557
26074
  if (!node.children) {
25558
26075
  node.children = [];
25559
26076
  }
26077
+ const children = node.children;
26078
+ if (!children || typeof children === "function") {
26079
+ throw new Error(`${path} is either not a node or it's dynamically computed`);
26080
+ }
26081
+ if ("id" in value) {
26082
+ const valueIndex = children.findIndex((elt) => "id" in elt && elt.id === value.id);
26083
+ if (valueIndex > -1) {
26084
+ if (!options.force)
26085
+ throw new Error(`A child with the id "${value.id}" already exists.`);
26086
+ node.children.splice(valueIndex, 1, value);
26087
+ return this;
26088
+ }
26089
+ }
25560
26090
  node.children.push(value);
25561
26091
  return this;
25562
26092
  }
@@ -27849,6 +28379,23 @@ const CREATE_PIVOT = (env) => {
27849
28379
  env.openSidePanel("PivotSidePanel", { pivotId });
27850
28380
  }
27851
28381
  };
28382
+ const REINSERT_PIVOT_CHILDREN = (env) => env.model.getters.getPivotIds().map((pivotId, index) => ({
28383
+ id: `reinsert_pivot_${env.model.getters.getPivotFormulaId(pivotId)}`,
28384
+ name: env.model.getters.getPivotDisplayName(pivotId),
28385
+ sequence: index,
28386
+ execute: (env) => {
28387
+ const zone = env.model.getters.getSelectedZone();
28388
+ const table = env.model.getters.getPivot(pivotId).getTableStructure().export();
28389
+ env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
28390
+ pivotId,
28391
+ table,
28392
+ col: zone.left,
28393
+ row: zone.top,
28394
+ sheetId: env.model.getters.getActiveSheetId(),
28395
+ });
28396
+ env.model.dispatch("REFRESH_PIVOT", { id: pivotId });
28397
+ },
28398
+ }));
27852
28399
  //------------------------------------------------------------------------------
27853
28400
  // Image
27854
28401
  //------------------------------------------------------------------------------
@@ -28713,12 +29260,21 @@ const splitToColumns = {
28713
29260
  isEnabled: (env) => env.model.getters.isSingleColSelected(),
28714
29261
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
28715
29262
  };
29263
+ const reinsertPivotMenu = {
29264
+ id: "reinsert_pivot",
29265
+ name: _t("Re-insert pivot"),
29266
+ sequence: 1020,
29267
+ icon: "o-spreadsheet-Icon.INSERT_PIVOT",
29268
+ children: [REINSERT_PIVOT_CHILDREN],
29269
+ isVisible: (env) => env.model.getters.getPivotIds().length > 0,
29270
+ };
28716
29271
 
28717
29272
  var ACTION_DATA = /*#__PURE__*/Object.freeze({
28718
29273
  __proto__: null,
28719
29274
  createRemoveFilter: createRemoveFilter,
28720
29275
  createRemoveFilterTool: createRemoveFilterTool,
28721
29276
  dataCleanup: dataCleanup,
29277
+ reinsertPivotMenu: reinsertPivotMenu,
28722
29278
  removeDuplicates: removeDuplicates,
28723
29279
  sortAscending: sortAscending,
28724
29280
  sortDescending: sortDescending,
@@ -30199,7 +30755,8 @@ topbarMenuRegistry
30199
30755
  : undefined,
30200
30756
  };
30201
30757
  });
30202
- });
30758
+ })
30759
+ .addChild("reinsert_pivot", ["data"], reinsertPivotMenu);
30203
30760
 
30204
30761
  class OTRegistry extends Registry {
30205
30762
  /**
@@ -31891,6 +32448,7 @@ class ChartWithAxisDesignPanel extends Component {
31891
32448
  Section,
31892
32449
  AxisDesignEditor,
31893
32450
  RoundColorPicker,
32451
+ Checkbox,
31894
32452
  };
31895
32453
  static props = {
31896
32454
  figureId: String,
@@ -31983,6 +32541,12 @@ class ChartWithAxisDesignPanel extends Component {
31983
32541
  const dataSets = this.props.definition.dataSets;
31984
32542
  return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
31985
32543
  }
32544
+ get showValuesLabel() {
32545
+ return ChartTerms.ShowValues;
32546
+ }
32547
+ updateShowValues(showValues) {
32548
+ this.props.updateChart(this.props.figureId, { showValues });
32549
+ }
31986
32550
  }
31987
32551
 
31988
32552
  class GaugeChartConfigPanel extends Component {
@@ -32132,6 +32696,10 @@ class LineConfigPanel extends GenericChartConfigPanel {
32132
32696
  }
32133
32697
  return false;
32134
32698
  }
32699
+ get stackedLabel() {
32700
+ const definition = this.props.definition;
32701
+ return definition.fillArea ? this.chartTerms.StackedAreaChart : this.chartTerms.StackedBarChart;
32702
+ }
32135
32703
  getLabelRangeOptions() {
32136
32704
  const options = super.getLabelRangeOptions();
32137
32705
  if (this.canTreatLabelsAsText) {
@@ -32171,6 +32739,7 @@ class PieChartDesignPanel extends Component {
32171
32739
  static components = {
32172
32740
  GeneralDesignEditor,
32173
32741
  Section,
32742
+ Checkbox,
32174
32743
  };
32175
32744
  static props = {
32176
32745
  figureId: String,
@@ -32183,6 +32752,9 @@ class PieChartDesignPanel extends Component {
32183
32752
  legendPosition: ev.target.value,
32184
32753
  });
32185
32754
  }
32755
+ get showValuesLabel() {
32756
+ return ChartTerms.ShowValues;
32757
+ }
32186
32758
  }
32187
32759
 
32188
32760
  class ScatterConfigPanel extends GenericChartConfigPanel {
@@ -32377,6 +32949,12 @@ class WaterfallChartDesignPanel extends Component {
32377
32949
  verticalAxisPosition: ev.target.value,
32378
32950
  });
32379
32951
  }
32952
+ get showValuesLabel() {
32953
+ return ChartTerms.ShowValues;
32954
+ }
32955
+ updateShowValues(showValues) {
32956
+ this.props.updateChart(this.props.figureId, { showValues });
32957
+ }
32380
32958
  }
32381
32959
 
32382
32960
  const chartSidePanelComponentRegistry = new Registry();
@@ -32637,6 +33215,11 @@ class ChartPanel extends Component {
32637
33215
  }
32638
33216
  }
32639
33217
 
33218
+ /**
33219
+ * Registry to draw icons on cells
33220
+ */
33221
+ const iconsOnCellRegistry = new Registry();
33222
+
32640
33223
  css /* scss */ `
32641
33224
  .o-spreadsheet {
32642
33225
  .o-icon {
@@ -32672,49 +33255,47 @@ const FROWN = '<svg class="o-cf-icon frown" width="10" height="10" focusable="fa
32672
33255
  const GREEN_DOT = '<svg class="o-cf-icon green-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#6AA84F" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32673
33256
  const YELLOW_DOT = '<svg class="o-cf-icon yellow-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#F0AD4E" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32674
33257
  const RED_DOT = '<svg class="o-cf-icon red-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#E06666" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32675
- function loadIconImage(svg) {
33258
+ function getIconSrc(svg) {
32676
33259
  /** We have to add xmlns, as it's not added by owl in the canvas */
32677
33260
  svg = `<svg xmlns="http://www.w3.org/2000/svg" ${svg.slice(4)}`;
32678
- const image = new Image();
32679
- image.src = "data:image/svg+xml; charset=utf8, " + encodeURIComponent(svg);
32680
- return image;
33261
+ return "data:image/svg+xml; charset=utf8, " + encodeURIComponent(svg);
32681
33262
  }
32682
33263
  const ICONS = {
32683
33264
  arrowGood: {
32684
33265
  template: "ARROW_UP",
32685
- img: loadIconImage(ARROW_UP),
33266
+ img: getIconSrc(ARROW_UP),
32686
33267
  },
32687
33268
  arrowNeutral: {
32688
33269
  template: "ARROW_RIGHT",
32689
- img: loadIconImage(ARROW_RIGHT),
33270
+ img: getIconSrc(ARROW_RIGHT),
32690
33271
  },
32691
33272
  arrowBad: {
32692
33273
  template: "ARROW_DOWN",
32693
- img: loadIconImage(ARROW_DOWN),
33274
+ img: getIconSrc(ARROW_DOWN),
32694
33275
  },
32695
33276
  smileyGood: {
32696
33277
  template: "SMILE",
32697
- img: loadIconImage(SMILE),
33278
+ img: getIconSrc(SMILE),
32698
33279
  },
32699
33280
  smileyNeutral: {
32700
33281
  template: "MEH",
32701
- img: loadIconImage(MEH),
33282
+ img: getIconSrc(MEH),
32702
33283
  },
32703
33284
  smileyBad: {
32704
33285
  template: "FROWN",
32705
- img: loadIconImage(FROWN),
33286
+ img: getIconSrc(FROWN),
32706
33287
  },
32707
33288
  dotGood: {
32708
33289
  template: "GREEN_DOT",
32709
- img: loadIconImage(GREEN_DOT),
33290
+ img: getIconSrc(GREEN_DOT),
32710
33291
  },
32711
33292
  dotNeutral: {
32712
33293
  template: "YELLOW_DOT",
32713
- img: loadIconImage(YELLOW_DOT),
33294
+ img: getIconSrc(YELLOW_DOT),
32714
33295
  },
32715
33296
  dotBad: {
32716
33297
  template: "RED_DOT",
32717
- img: loadIconImage(RED_DOT),
33298
+ img: getIconSrc(RED_DOT),
32718
33299
  },
32719
33300
  };
32720
33301
  const ICON_SETS = {
@@ -32734,6 +33315,12 @@ const ICON_SETS = {
32734
33315
  bad: "dotBad",
32735
33316
  },
32736
33317
  };
33318
+ iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
33319
+ const icon = getters.getConditionalIcon(position);
33320
+ if (icon) {
33321
+ return ICONS[icon].img;
33322
+ }
33323
+ });
32737
33324
 
32738
33325
  css /* scss */ `
32739
33326
  .o-icon-picker {
@@ -33082,21 +33669,25 @@ function useHoveredElement(ref) {
33082
33669
 
33083
33670
  function useHighlightsOnHover(ref, highlightProvider) {
33084
33671
  const hoverState = useHoveredElement(ref);
33085
- const stores = useStoreProvider();
33086
33672
  useHighlights({
33087
33673
  get highlights() {
33088
33674
  return hoverState.hovered ? highlightProvider.highlights : [];
33089
33675
  },
33090
33676
  });
33091
- useEffect(() => {
33092
- stores.trigger("store-updated");
33093
- }, () => [hoverState.hovered]);
33094
33677
  }
33095
33678
  function useHighlights(highlightProvider) {
33679
+ const stores = useStoreProvider();
33096
33680
  const store = useLocalStore(HighlightStore);
33097
33681
  onMounted(() => {
33098
33682
  store.register(highlightProvider);
33099
33683
  });
33684
+ let currentHighlights = highlightProvider.highlights;
33685
+ useEffect((highlights) => {
33686
+ if (!deepEquals(highlights, currentHighlights)) {
33687
+ currentHighlights = highlights;
33688
+ stores.trigger("store-updated");
33689
+ }
33690
+ }, () => [highlightProvider.highlights]);
33100
33691
  }
33101
33692
 
33102
33693
  css /* scss */ `
@@ -34568,6 +35159,8 @@ class FindAndReplaceStore extends SpreadsheetStore {
34568
35159
  isSearchDirty = false;
34569
35160
  initialShowFormulaState;
34570
35161
  preserveSelectedMatchIndex = false;
35162
+ irreplaceableMatchCount = 0;
35163
+ notificationStore = this.get(NotificationStore);
34571
35164
  // fixme: why do we make selectedMatchIndex on top of a selected
34572
35165
  // property in the matches?
34573
35166
  selectedMatchIndex = null;
@@ -34577,7 +35170,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
34577
35170
  matchCase: false,
34578
35171
  exactMatch: false,
34579
35172
  searchFormulas: false,
34580
- searchScope: "allSheets",
35173
+ searchScope: "activeSheet",
34581
35174
  specificRange: undefined,
34582
35175
  };
34583
35176
  updateSearchContent = debounce(this._updateSearchContent.bind(this), 200);
@@ -34644,6 +35237,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
34644
35237
  for (const match of cmd.matches) {
34645
35238
  this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
34646
35239
  }
35240
+ if (this.irreplaceableMatchCount > 0) {
35241
+ this.showReplaceWarningMessage(cmd.matches.length, this.irreplaceableMatchCount);
35242
+ }
35243
+ this.irreplaceableMatchCount = 0;
35244
+ break;
34647
35245
  }
34648
35246
  }
34649
35247
  finalize() {
@@ -34825,12 +35423,36 @@ class FindAndReplaceStore extends SpreadsheetStore {
34825
35423
  searchOptions: this.searchOptions,
34826
35424
  });
34827
35425
  }
35426
+ /**
35427
+ * Show a warning message based on the number of matches replaced and irreplaceable.
35428
+ */
35429
+ showReplaceWarningMessage(totalMatches, irreplaceableMatches) {
35430
+ const replaceableMatches = totalMatches - irreplaceableMatches;
35431
+ if (replaceableMatches === 0) {
35432
+ this.notificationStore.notifyUser({
35433
+ type: "warning",
35434
+ sticky: false,
35435
+ text: _t("Match(es) cannot be replaced as they are part of a formula."),
35436
+ });
35437
+ }
35438
+ else {
35439
+ this.notificationStore.notifyUser({
35440
+ type: "warning",
35441
+ sticky: false,
35442
+ text: _t("%(replaceable_count)s match(es) replaced. %(irreplaceable_count)s match(es) cannot be replaced as they are part of a formula.", {
35443
+ replaceable_count: replaceableMatches,
35444
+ irreplaceable_count: irreplaceableMatches,
35445
+ }),
35446
+ });
35447
+ }
35448
+ }
34828
35449
  replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
34829
35450
  const cell = this.getters.getCell(selectedMatch);
34830
35451
  if (!cell?.content) {
34831
35452
  return;
34832
35453
  }
34833
35454
  if (cell?.isFormula && !searchOptions.searchFormulas) {
35455
+ this.irreplaceableMatchCount++;
34834
35456
  return;
34835
35457
  }
34836
35458
  const searchRegex = getSearchRegex(searchString, searchOptions);
@@ -35350,9 +35972,15 @@ class PivotLayoutConfigurator extends Component {
35350
35972
  addMeasureDimension(fieldName) {
35351
35973
  const { measures } = this.props.definition;
35352
35974
  this.props.onDimensionsUpdated({
35353
- measures: measures.concat([{ name: fieldName }]),
35975
+ measures: measures.concat([
35976
+ { name: fieldName, aggregator: this.getDefaultMeasureAggregator(fieldName) },
35977
+ ]),
35354
35978
  });
35355
35979
  }
35980
+ getDefaultMeasureAggregator(fieldName) {
35981
+ const field = this.props.unusedMeasureFields.find((f) => f.name === fieldName);
35982
+ return field?.aggregator ? field.aggregator : "count";
35983
+ }
35356
35984
  updateAggregator(updatedMeasure, aggregator) {
35357
35985
  const { measures } = this.props.definition;
35358
35986
  this.props.onDimensionsUpdated({
@@ -35507,9 +36135,11 @@ class PivotTitleSection extends Component {
35507
36135
  }
35508
36136
  duplicatePivot() {
35509
36137
  const newPivotId = this.env.model.uuidGenerator.uuidv4();
35510
- const result = this.env.model.dispatch("DUPLICATE_PIVOT", {
36138
+ const newSheetId = this.env.model.uuidGenerator.uuidv4();
36139
+ const result = this.env.model.dispatch("DUPLICATE_PIVOT_IN_NEW_SHEET", {
35511
36140
  pivotId: this.props.pivotId,
35512
36141
  newPivotId,
36142
+ newSheetId,
35513
36143
  });
35514
36144
  const text = result.isSuccessful ? _t("Pivot duplicated.") : _t("Pivot duplication failed");
35515
36145
  const type = result.isSuccessful ? "success" : "danger";
@@ -35574,7 +36204,7 @@ function createMeasure(fields, measure) {
35574
36204
  const field = name === "__count"
35575
36205
  ? { name: "__count", string: _t("Count"), type: "integer", aggregator: "sum" }
35576
36206
  : fields[name];
35577
- const aggregator = measure.aggregator || field?.aggregator;
36207
+ const aggregator = measure.aggregator;
35578
36208
  return {
35579
36209
  nameWithAggregator: name + (aggregator ? `:${aggregator}` : ""),
35580
36210
  /**
@@ -36044,7 +36674,7 @@ function createDate(dimension, value, locale) {
36044
36674
  if (typeof value === "number" || typeof value === "string") {
36045
36675
  const date = toJsDate(value, locale);
36046
36676
  switch (granularity) {
36047
- case "year_number":
36677
+ case "year":
36048
36678
  number = date.getFullYear();
36049
36679
  break;
36050
36680
  case "quarter_number":
@@ -36072,7 +36702,7 @@ function createDate(dimension, value, locale) {
36072
36702
  * This map is used to cache the different values of a pivot date value
36073
36703
  * 43_831 -> 01/01/2012
36074
36704
  * Example: {
36075
- * year_number: {
36705
+ * year: {
36076
36706
  * set: { 43_831 },
36077
36707
  * values: { '43_831': 2012 }
36078
36708
  * },
@@ -36099,7 +36729,7 @@ function createDate(dimension, value, locale) {
36099
36729
  * }
36100
36730
  */
36101
36731
  const MAP_VALUE_DIMENSION_DATE = {
36102
- year_number: {
36732
+ year: {
36103
36733
  set: new Set(),
36104
36734
  values: {},
36105
36735
  },
@@ -36310,7 +36940,7 @@ class SpreadsheetPivot {
36310
36940
  .map((value) => value[measure])
36311
36941
  .filter((cell) => cell && cell.type !== CellValueType.empty)
36312
36942
  .filter(isDefined);
36313
- const aggregator = this.getMeasure(measure).aggregator || "count";
36943
+ const aggregator = this.getMeasure(measure).aggregator;
36314
36944
  const operator = AGGREGATORS_FN[aggregator];
36315
36945
  if (!operator) {
36316
36946
  throw new Error(`Aggregator ${aggregator} does not exist`);
@@ -36484,7 +37114,7 @@ pivotRegistry.add("SPREADSHEET", {
36484
37114
  externalData: false,
36485
37115
  onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
36486
37116
  granularities: [
36487
- "year_number",
37117
+ "year",
36488
37118
  "quarter_number",
36489
37119
  "month_number",
36490
37120
  "iso_week_number",
@@ -36894,7 +37524,13 @@ class SettingsPanel extends Component {
36894
37524
  }
36895
37525
  async loadLocales() {
36896
37526
  this.loadedLocales = (await this.env.loadLocales())
36897
- .filter(isValidLocale)
37527
+ .filter((locale) => {
37528
+ const isValid = isValidLocale(locale);
37529
+ if (!isValid) {
37530
+ console.warn(`Invalid locale: ${locale["code"]} ${locale}`);
37531
+ }
37532
+ return isValid;
37533
+ })
36898
37534
  .sort((a, b) => a.name.localeCompare(b.name));
36899
37535
  }
36900
37536
  get numberFormatPreview() {
@@ -37953,6 +38589,9 @@ class TopBarComponentRegistry extends Registry {
37953
38589
  const component = { ...value, id: this.uuidGenerator.uuidv4() };
37954
38590
  return super.add(name, component);
37955
38591
  }
38592
+ getAllOrdered() {
38593
+ return this.getAll().sort((a, b) => a.sequence - b.sequence);
38594
+ }
37956
38595
  }
37957
38596
  const topbarComponentRegistry = new TopBarComponentRegistry();
37958
38597
 
@@ -40841,21 +41480,22 @@ class GridRenderer {
40841
41480
  isError: (cell.type === CellValueType.error && !!cell.message) ||
40842
41481
  this.getters.isDataValidationInvalid(position),
40843
41482
  };
40844
- if (cell.type === CellValueType.empty || this.getters.isCellValidCheckbox(position)) {
40845
- return box;
40846
- }
40847
- /** Icon CF */
40848
- const cfIcon = this.getters.getConditionalIcon(position);
41483
+ /** Icon */
41484
+ const iconSrc = this.getters.getCellIconSrc(position);
40849
41485
  const fontSizePX = computeTextFontSizeInPixels(box.style);
40850
- const iconBoxWidth = cfIcon ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
40851
- if (cfIcon) {
41486
+ const iconBoxWidth = iconSrc ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
41487
+ if (iconSrc) {
41488
+ const imageHtmlElement = loadIconImage(iconSrc);
40852
41489
  box.image = {
40853
41490
  type: "icon",
40854
41491
  size: fontSizePX,
40855
41492
  clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },
40856
- image: ICONS[cfIcon].img,
41493
+ image: imageHtmlElement,
40857
41494
  };
40858
41495
  }
41496
+ if (cell.type === CellValueType.empty || this.getters.isCellValidCheckbox(position)) {
41497
+ return box;
41498
+ }
40859
41499
  /** Filter Header or data validation icon */
40860
41500
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
40861
41501
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
@@ -40874,7 +41514,7 @@ class GridRenderer {
40874
41514
  };
40875
41515
  /** ClipRect */
40876
41516
  const isOverflowing = contentWidth > width || fontSizePX > height;
40877
- if (cfIcon || box.hasIcon) {
41517
+ if (iconSrc || box.hasIcon) {
40878
41518
  box.clipRect = {
40879
41519
  x: box.x + iconBoxWidth,
40880
41520
  y: box.y,
@@ -40985,6 +41625,11 @@ class GridRenderer {
40985
41625
  return boxes;
40986
41626
  }
40987
41627
  }
41628
+ const loadIconImage = memoize(function loadIconImage(src) {
41629
+ const image = new Image();
41630
+ image.src = src;
41631
+ return image;
41632
+ });
40988
41633
 
40989
41634
  function useGridDrawing(refName, model, canvasSize) {
40990
41635
  const canvasRef = useRef(refName);
@@ -42057,6 +42702,7 @@ class Grid extends Component {
42057
42702
  return;
42058
42703
  }
42059
42704
  if (clipboardData.types.indexOf(ClipboardMIMEType.PlainText) > -1) {
42705
+ ev.preventDefault();
42060
42706
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
42061
42707
  const target = this.env.model.getters.getSelectedZones();
42062
42708
  const clipboardString = this.env.model.getters.getClipboardTextContent();
@@ -42323,6 +42969,8 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42323
42969
  "BITOR",
42324
42970
  "BITRSHIFT",
42325
42971
  "BITXOR",
42972
+ "BYCOL",
42973
+ "BYROW",
42326
42974
  "CEILING.MATH",
42327
42975
  "CEILING.PRECISE",
42328
42976
  "CHISQ.DIST",
@@ -42330,6 +42978,8 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42330
42978
  "CHISQ.INV",
42331
42979
  "CHISQ.INV.RT",
42332
42980
  "CHISQ.TEST",
42981
+ "CHOOSECOLS",
42982
+ "CHOOSEROWS",
42333
42983
  "COMBINA",
42334
42984
  "CONCAT",
42335
42985
  "CONFIDENCE.NORM",
@@ -42342,14 +42992,17 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42342
42992
  "CSCH",
42343
42993
  "DAYS",
42344
42994
  "DECIMAL",
42995
+ "DROP",
42345
42996
  "ERF.PRECISE",
42346
42997
  "ERFC.PRECISE",
42998
+ "EXPAND",
42347
42999
  "EXPON.DIST",
42348
43000
  "F.DIST",
42349
43001
  "F.DIST.RT",
42350
43002
  "F.INV",
42351
43003
  "F.INV.RT",
42352
43004
  "F.TEST",
43005
+ "FIELDVALUE",
42353
43006
  "FILTERXML",
42354
43007
  "FLOOR.MATH",
42355
43008
  "FLOOR.PRECISE",
@@ -42364,6 +43017,7 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42364
43017
  "GAMMA.INV",
42365
43018
  "GAMMALN.PRECISE",
42366
43019
  "GAUSS",
43020
+ "HSTACK",
42367
43021
  "HYPGEOM.DIST",
42368
43022
  "IFNA",
42369
43023
  "IFS",
@@ -42376,9 +43030,14 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42376
43030
  "IMSINH",
42377
43031
  "IMTAN",
42378
43032
  "ISFORMULA",
43033
+ "ISOMITTED",
42379
43034
  "ISOWEEKNUM",
43035
+ "LAMBDA",
43036
+ "LET",
42380
43037
  "LOGNORM.DIST",
42381
43038
  "LOGNORM.INV",
43039
+ "MAKEARRAY",
43040
+ "MAP",
42382
43041
  "MAXIFS",
42383
43042
  "MINIFS",
42384
43043
  "MODE.MULT",
@@ -42398,17 +43057,26 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42398
43057
  "PERMUTATIONA",
42399
43058
  "PHI",
42400
43059
  "POISSON.DIST",
43060
+ "PQSOURCE",
43061
+ "PYTHON_STR",
43062
+ "PYTHON_TYPE",
43063
+ "PYTHON_TYPENAME",
42401
43064
  "QUARTILE.EXC",
42402
43065
  "QUARTILE.INC",
42403
43066
  "QUERYSTRING",
43067
+ "RANDARRAY",
42404
43068
  "RANK.AVG",
42405
43069
  "RANK.EQ",
43070
+ "REDUCE",
42406
43071
  "RRI",
43072
+ "SCAN",
42407
43073
  "SEC",
42408
43074
  "SECH",
43075
+ "SEQUENCE",
42409
43076
  "SHEET",
42410
43077
  "SHEETS",
42411
43078
  "SKEW.P",
43079
+ "SORTBY",
42412
43080
  "STDEV.P",
42413
43081
  "STDEV.S",
42414
43082
  "SWITCH",
@@ -42418,13 +43086,24 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42418
43086
  "T.INV",
42419
43087
  "T.INV.2T",
42420
43088
  "T.TEST",
43089
+ "TAKE",
43090
+ "TEXTAFTER",
43091
+ "TEXTBEFORE",
42421
43092
  "TEXTJOIN",
43093
+ "TEXTSPLIT",
43094
+ "TOCOL",
43095
+ "TOROW",
42422
43096
  "UNICHAR",
42423
43097
  "UNICODE",
43098
+ "UNIQUE",
42424
43099
  "VAR.P",
42425
43100
  "VAR.S",
43101
+ "VSTACK",
42426
43102
  "WEBSERVICE",
42427
43103
  "WEIBULL.DIST",
43104
+ "WRAPCOLS",
43105
+ "WRAPROWS",
43106
+ "XLOOKUP",
42428
43107
  "XOR",
42429
43108
  "Z.TEST",
42430
43109
  ];
@@ -45608,7 +46287,7 @@ function getRelationFile(file, xmls) {
45608
46287
  return relsFile;
45609
46288
  }
45610
46289
 
45611
- const EXCEL_IMPORT_VERSION = 16;
46290
+ const EXCEL_IMPORT_VERSION = 17;
45612
46291
  class XlsxReader {
45613
46292
  warningManager;
45614
46293
  xmls;
@@ -45740,7 +46419,7 @@ function normalizeV9(formula) {
45740
46419
  * a breaking change is made in the way the state is handled, and an upgrade
45741
46420
  * function should be defined
45742
46421
  */
45743
- const CURRENT_VERSION = 16;
46422
+ const CURRENT_VERSION = 17;
45744
46423
  const INITIAL_SHEET_ID = "Sheet1";
45745
46424
  /**
45746
46425
  * This function tries to load anything that could look like a valid
@@ -46687,6 +47366,21 @@ class BordersPlugin extends CorePlugin {
46687
47366
  return [];
46688
47367
  return Object.keys(sheetBorders).map((index) => parseInt(index, 10));
46689
47368
  }
47369
+ /**
47370
+ * Get all the rows which contains at least a border
47371
+ */
47372
+ getRowsWithBorders(sheetId) {
47373
+ const sheetBorders = this.borders[sheetId]?.filter(isDefined);
47374
+ if (!sheetBorders)
47375
+ return [];
47376
+ const rowsWithBorders = new Set();
47377
+ for (const rowBorders of sheetBorders) {
47378
+ for (const rowBorder in rowBorders) {
47379
+ rowsWithBorders.add(parseInt(rowBorder, 10));
47380
+ }
47381
+ }
47382
+ return Array.from(rowsWithBorders);
47383
+ }
46690
47384
  /**
46691
47385
  * Get the range of all the rows in the sheet
46692
47386
  */
@@ -46736,7 +47430,7 @@ class BordersPlugin extends CorePlugin {
46736
47430
  destructive: false,
46737
47431
  });
46738
47432
  }
46739
- this.getRowsRange(sheetId)
47433
+ this.getRowsWithBorders(sheetId)
46740
47434
  .filter((row) => row >= start)
46741
47435
  .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
46742
47436
  .forEach((row) => {
@@ -49062,10 +49756,12 @@ class MergePlugin extends CorePlugin {
49062
49756
  if (!sheetMap)
49063
49757
  return [];
49064
49758
  const mergeIds = new Set();
49065
- for (const { col, row } of positions(zone)) {
49066
- const mergeId = sheetMap[col]?.[row];
49067
- if (mergeId) {
49068
- mergeIds.add(mergeId);
49759
+ for (let col = zone.left; col <= zone.right; col++) {
49760
+ for (let row = zone.top; row <= zone.bottom; row++) {
49761
+ const mergeId = sheetMap[col]?.[row];
49762
+ if (mergeId) {
49763
+ mergeIds.add(mergeId);
49764
+ }
49069
49765
  }
49070
49766
  }
49071
49767
  return Array.from(mergeIds)
@@ -52933,7 +53629,13 @@ class FormulaDependencyGraph {
52933
53629
  const queue = Array.from(ranges).reverse();
52934
53630
  while (queue.length > 0) {
52935
53631
  const range = queue.pop();
52936
- visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
53632
+ const zone = range.zone;
53633
+ const sheetId = range.sheetId;
53634
+ for (let col = zone.left; col <= zone.right; col++) {
53635
+ for (let row = zone.top; row <= zone.bottom; row++) {
53636
+ visited.add({ sheetId, col, row });
53637
+ }
53638
+ }
52937
53639
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
52938
53640
  const nextInQueue = {};
52939
53641
  for (const position of impactedPositions) {
@@ -52949,7 +53651,16 @@ class FormulaDependencyGraph {
52949
53651
  queue.push(...zones.map((zone) => ({ sheetId, zone })));
52950
53652
  }
52951
53653
  }
52952
- visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
53654
+ // remove initial ranges
53655
+ for (const range of ranges) {
53656
+ const zone = range.zone;
53657
+ const sheetId = range.sheetId;
53658
+ for (let col = zone.left; col <= zone.right; col++) {
53659
+ for (let row = zone.top; row <= zone.bottom; row++) {
53660
+ visited.delete({ sheetId, col, row });
53661
+ }
53662
+ }
53663
+ }
52953
53664
  return visited;
52954
53665
  }
52955
53666
  }
@@ -53080,7 +53791,7 @@ class PositionSet {
53080
53791
  return this.sheets[position.sheetId].getValue(position) === 1;
53081
53792
  }
53082
53793
  clear() {
53083
- const insertions = this.insertions;
53794
+ const insertions = [...this];
53084
53795
  this.insertions = [];
53085
53796
  for (const sheetId in this.sheets) {
53086
53797
  this.sheets[sheetId].clear();
@@ -53424,6 +54135,7 @@ class Evaluator {
53424
54135
  }
53425
54136
  finally {
53426
54137
  this.cellsBeingComputed.delete(cellId);
54138
+ this.nextPositionsToUpdate.delete(position);
53427
54139
  }
53428
54140
  }
53429
54141
  computeAndSave(position) {
@@ -53450,8 +54162,33 @@ class Evaluator {
53450
54162
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
53451
54163
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
53452
54164
  this.spreadValues(formulaPosition, formulaReturn));
54165
+ this.invalidatePositionsDependingOnSpread(formulaPosition, nbColumns, nbRows);
53453
54166
  return createEvaluatedCell(nullValueToZeroValue(formulaReturn[0][0]), this.getters.getLocale(), cellData);
53454
54167
  }
54168
+ invalidatePositionsDependingOnSpread(arrayFormulaPosition, nbColumns, nbRows) {
54169
+ // the result matrix is split in 2 zones to exclude the array formula position
54170
+ const top = arrayFormulaPosition.row;
54171
+ const left = arrayFormulaPosition.col;
54172
+ const bottom = top + nbRows - 1;
54173
+ const leftColumnZone = {
54174
+ top: top + 1,
54175
+ bottom,
54176
+ left,
54177
+ right: left,
54178
+ };
54179
+ const rightPartZone = {
54180
+ top,
54181
+ bottom,
54182
+ left: left + 1,
54183
+ right: left + nbColumns - 1,
54184
+ };
54185
+ const sheetId = arrayFormulaPosition.sheetId;
54186
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn([
54187
+ { sheetId, zone: rightPartZone },
54188
+ { sheetId, zone: leftColumnZone },
54189
+ ]);
54190
+ this.nextPositionsToUpdate.addMany(invalidatedPositions);
54191
+ }
53455
54192
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
53456
54193
  const numberOfCols = this.getters.getNumberCols(sheetId);
53457
54194
  const numberOfRows = this.getters.getNumberRows(sheetId);
@@ -53471,9 +54208,9 @@ class Evaluator {
53471
54208
  assertNoMergedCellsInSpreadZone({ sheetId, col, row }, matrixResult) {
53472
54209
  const mergedCells = this.getters.getMergesInZone(sheetId, {
53473
54210
  top: row,
53474
- bottom: row + matrixResult.length,
54211
+ bottom: row + matrixResult[0].length - 1,
53475
54212
  left: col,
53476
- right: col + matrixResult[0].length,
54213
+ right: col + matrixResult.length - 1,
53477
54214
  });
53478
54215
  if (mergedCells.length === 0) {
53479
54216
  return;
@@ -53482,14 +54219,15 @@ class Evaluator {
53482
54219
  }
53483
54220
  updateSpreadRelation({ sheetId, col, row, }) {
53484
54221
  const arrayFormulaPosition = { sheetId, col, row };
53485
- return (i, j) => {
54222
+ const updateSpreadRelation = (i, j) => {
53486
54223
  const position = { sheetId, col: i + col, row: j + row };
53487
54224
  this.spreadingRelations.addRelation({ resultPosition: position, arrayFormulaPosition });
53488
54225
  };
54226
+ return updateSpreadRelation;
53489
54227
  }
53490
54228
  checkCollision(formulaPosition) {
53491
54229
  const { sheetId, col, row } = formulaPosition;
53492
- return (i, j) => {
54230
+ const checkCollision = (i, j) => {
53493
54231
  const position = { sheetId: sheetId, col: i + col, row: j + row };
53494
54232
  const rawCell = this.getters.getCell(position);
53495
54233
  if (rawCell?.content ||
@@ -53499,17 +54237,16 @@ class Evaluator {
53499
54237
  }
53500
54238
  this.blockedArrayFormulas.delete(formulaPosition);
53501
54239
  };
54240
+ return checkCollision;
53502
54241
  }
53503
54242
  spreadValues({ sheetId, col, row }, matrixResult) {
53504
- return (i, j) => {
54243
+ const spreadValues = (i, j) => {
53505
54244
  const position = { sheetId, col: i + col, row: j + row };
53506
54245
  const cell = this.getters.getCell(position);
53507
54246
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
53508
54247
  this.evaluatedCells.set(position, evaluatedCell);
53509
- // check if formula dependencies present in the spread zone
53510
- // if so, they need to be recomputed
53511
- this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([position]));
53512
54248
  };
54249
+ return spreadValues;
53513
54250
  }
53514
54251
  invalidateSpreading(position) {
53515
54252
  if (!this.spreadingRelations.isArrayFormula(position)) {
@@ -53564,12 +54301,12 @@ function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
53564
54301
  * rather than appearing empty. This indicates that the
53565
54302
  * cell is the result of a non-empty content.
53566
54303
  */
53567
- function nullValueToZeroValue(fPayload) {
53568
- if (fPayload.value === null || fPayload.value === undefined) {
53569
- // 'fPayload.value === undefined' is supposed to never happen, it's a safety net for javascript use
53570
- return { ...fPayload, value: 0 };
54304
+ function nullValueToZeroValue(functionResult) {
54305
+ if (functionResult.value === null || functionResult.value === undefined) {
54306
+ // 'functionResult.value === undefined' is supposed to never happen, it's a safety net for javascript use
54307
+ return { ...functionResult, value: 0 };
53571
54308
  }
53572
- return fPayload;
54309
+ return functionResult;
53573
54310
  }
53574
54311
  function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
53575
54312
  compilationParams.evalContext.__originCellXC = lazy(() => {
@@ -57137,6 +57874,12 @@ class InsertPivotPlugin extends UIPlugin {
57137
57874
  case "INSERT_NEW_PIVOT":
57138
57875
  this.insertNewPivot(cmd.pivotId, cmd.newSheetId);
57139
57876
  break;
57877
+ case "DUPLICATE_PIVOT_IN_NEW_SHEET":
57878
+ this.duplicatePivotInNewSheet(cmd.pivotId, cmd.newPivotId, cmd.newSheetId);
57879
+ break;
57880
+ case "INSERT_PIVOT_WITH_TABLE":
57881
+ this.insertPivotWithTable(cmd.sheetId, cmd.col, cmd.row, cmd.pivotId, cmd.table);
57882
+ break;
57140
57883
  }
57141
57884
  }
57142
57885
  insertNewPivot(pivotId, sheetId) {
@@ -57162,7 +57905,7 @@ class InsertPivotPlugin extends UIPlugin {
57162
57905
  const formulaId = this.getters.getPivotFormulaId(pivotId);
57163
57906
  this.dispatch("CREATE_SHEET", {
57164
57907
  sheetId,
57165
- name: _t("Pivot #%s", formulaId),
57908
+ name: _t("Pivot #%(formulaId)s", { formulaId }),
57166
57909
  position,
57167
57910
  });
57168
57911
  this.dispatch("ACTIVATE_SHEET", {
@@ -57176,6 +57919,92 @@ class InsertPivotPlugin extends UIPlugin {
57176
57919
  content: `=PIVOT(${formulaId})`,
57177
57920
  });
57178
57921
  }
57922
+ duplicatePivotInNewSheet(pivotId, newPivotId, newSheetId) {
57923
+ this.dispatch("DUPLICATE_PIVOT", {
57924
+ pivotId,
57925
+ newPivotId,
57926
+ });
57927
+ const activeSheetId = this.getters.getActiveSheetId();
57928
+ const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
57929
+ const formulaId = this.getters.getPivotFormulaId(newPivotId);
57930
+ const newPivotName = this.getters.getPivotName(newPivotId);
57931
+ this.dispatch("CREATE_SHEET", {
57932
+ sheetId: newSheetId,
57933
+ name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
57934
+ newPivotName,
57935
+ formulaId,
57936
+ })),
57937
+ position,
57938
+ });
57939
+ this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
57940
+ this.dispatch("UPDATE_CELL", {
57941
+ sheetId: newSheetId,
57942
+ col: 0,
57943
+ row: 0,
57944
+ content: `=PIVOT(${formulaId})`,
57945
+ });
57946
+ }
57947
+ getPivotDuplicateSheetName(pivotName) {
57948
+ let i = 1;
57949
+ const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
57950
+ let name = pivotName;
57951
+ while (names.includes(name)) {
57952
+ name = `${pivotName} (${i})`;
57953
+ i++;
57954
+ }
57955
+ return name;
57956
+ }
57957
+ insertPivotWithTable(sheetId, col, row, pivotId, table) {
57958
+ const { cols, rows, measures, fieldsType } = table;
57959
+ const pivotTable = new SpreadsheetPivotTable(cols, rows, measures, fieldsType || {});
57960
+ this.resizeSheet(sheetId, col, row, pivotTable);
57961
+ const pivotFormulaId = this.getters.getPivotFormulaId(pivotId);
57962
+ this.dispatch("UPDATE_CELL", {
57963
+ sheetId,
57964
+ col,
57965
+ row,
57966
+ content: `=PIVOT("${pivotFormulaId}")`,
57967
+ });
57968
+ const zone = {
57969
+ left: col,
57970
+ right: col,
57971
+ top: row,
57972
+ bottom: row,
57973
+ };
57974
+ const numberOfHeaders = pivotTable.columns.length - 1;
57975
+ this.dispatch("CREATE_TABLE", {
57976
+ tableType: "dynamic",
57977
+ sheetId,
57978
+ ranges: [this.getters.getRangeDataFromZone(sheetId, zone)],
57979
+ config: { ...PIVOT_TABLE_CONFIG, numberOfHeaders },
57980
+ });
57981
+ }
57982
+ resizeSheet(sheetId, col, row, table) {
57983
+ const colLimit = table.getNumberOfDataColumns() + 1; // +1 for the Top-Left
57984
+ const numberCols = this.getters.getNumberCols(sheetId);
57985
+ const deltaCol = numberCols - col;
57986
+ if (deltaCol < colLimit) {
57987
+ this.dispatch("ADD_COLUMNS_ROWS", {
57988
+ dimension: "COL",
57989
+ base: numberCols - 1,
57990
+ sheetId: sheetId,
57991
+ quantity: colLimit - deltaCol,
57992
+ position: "after",
57993
+ });
57994
+ }
57995
+ const rowLimit = table.columns.length + table.rows.length;
57996
+ const numberRows = this.getters.getNumberRows(sheetId);
57997
+ const deltaRow = numberRows - row;
57998
+ if (deltaRow < rowLimit) {
57999
+ this.dispatch("ADD_COLUMNS_ROWS", {
58000
+ dimension: "ROW",
58001
+ base: numberRows - 1,
58002
+ sheetId: sheetId,
58003
+ quantity: rowLimit - deltaRow,
58004
+ position: "after",
58005
+ });
58006
+ }
58007
+ }
57179
58008
  }
57180
58009
 
57181
58010
  class SortPlugin extends UIPlugin {
@@ -57356,6 +58185,7 @@ class SheetUIPlugin extends UIPlugin {
57356
58185
  static getters = [
57357
58186
  "doesCellHaveGridIcon",
57358
58187
  "getCellWidth",
58188
+ "getCellIconSrc",
57359
58189
  "getTextWidth",
57360
58190
  "getCellText",
57361
58191
  "getCellMultiLineText",
@@ -57406,7 +58236,7 @@ class SheetUIPlugin extends UIPlugin {
57406
58236
  const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
57407
58237
  contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
57408
58238
  }
57409
- const icon = this.getters.getConditionalIcon(position);
58239
+ const icon = this.getters.getCellIconSrc(position);
57410
58240
  if (icon) {
57411
58241
  contentWidth += computeIconWidth(style);
57412
58242
  }
@@ -57423,6 +58253,16 @@ class SheetUIPlugin extends UIPlugin {
57423
58253
  }
57424
58254
  return contentWidth;
57425
58255
  }
58256
+ getCellIconSrc(position) {
58257
+ const callbacks = iconsOnCellRegistry.getAll();
58258
+ for (const callback of callbacks) {
58259
+ const imageSrc = callback(this.getters, position);
58260
+ if (imageSrc) {
58261
+ return imageSrc;
58262
+ }
58263
+ }
58264
+ return undefined;
58265
+ }
57426
58266
  getTextWidth(text, style) {
57427
58267
  return computeTextWidth(this.ctx, text, style);
57428
58268
  }
@@ -61198,7 +62038,7 @@ class BottomBarSheet extends Component {
61198
62038
  this.DOMFocusableElementStore.focus();
61199
62039
  }
61200
62040
  }
61201
- onClickSheetName(ev) {
62041
+ onMouseEventSheetName(ev) {
61202
62042
  if (this.state.isEditing)
61203
62043
  ev.stopPropagation();
61204
62044
  }
@@ -62926,7 +63766,7 @@ class TopBar extends Component {
62926
63766
  }
62927
63767
  get topbarComponents() {
62928
63768
  return topbarComponentRegistry
62929
- .getAll()
63769
+ .getAllOrdered()
62930
63770
  .filter((item) => !item.isVisible || item.isVisible(this.env));
62931
63771
  }
62932
63772
  onExternalClick(ev) {
@@ -67339,6 +68179,7 @@ const registries = {
67339
68179
  linkMenuRegistry,
67340
68180
  functionRegistry,
67341
68181
  featurePluginRegistry,
68182
+ iconsOnCellRegistry,
67342
68183
  statefulUIPluginRegistry,
67343
68184
  coreViewsPluginRegistry,
67344
68185
  corePluginRegistry,
@@ -67513,6 +68354,6 @@ const constants = {
67513
68354
  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 };
67514
68355
 
67515
68356
 
67516
- __info__.version = "17.4.0-alpha.9";
67517
- __info__.date = "2024-06-26T11:09:20.284Z";
67518
- __info__.hash = "526be20";
68357
+ __info__.version = "17.4.1";
68358
+ __info__.date = "2024-07-18T14:25:54.421Z";
68359
+ __info__.hash = "fd7deaa";