@odoo/o-spreadsheet 19.1.0-alpha.13 → 19.1.0-alpha.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@
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
5
  * @version 19.1.0-alpha.3
6
- * @date 2025-11-24T07:52:34.809Z
7
- * @hash e232982
6
+ * @date 2025-12-02T05:34:07.213Z
7
+ * @hash 04cf666
8
8
  */
9
9
 
10
10
  class FunctionCodeBuilder {
@@ -3106,8 +3106,56 @@ function isMultipleElementMatrix(arg) {
3106
3106
  return isMatrix(arg) && !isSingleElementMatrix(arg);
3107
3107
  }
3108
3108
 
3109
+ function stackHorizontally(ranges, options) {
3110
+ const matrices = ranges.map(toMatrix);
3111
+ const nbRowsArr = matrices.map((m) => m?.[0]?.length ?? 0);
3112
+ const nbRows = Math.max(...nbRowsArr);
3113
+ if (options?.requireSameRowCount) {
3114
+ const firstLength = nbRowsArr[0];
3115
+ if (nbRowsArr.some((len) => len !== firstLength)) {
3116
+ return new EvaluationError(_t("All ranges in [[FUNCTION_NAME]] must have the same number of columns (got %s).", nbRowsArr.join(", ")));
3117
+ }
3118
+ }
3119
+ const result = [];
3120
+ for (const matrix of matrices) {
3121
+ for (let col = 0; col < matrix.length; col++) {
3122
+ // Fill with nulls if needed
3123
+ const array = Array(nbRows).fill({ value: null });
3124
+ for (let row = 0; row < matrix[col].length; row++) {
3125
+ array[row] = matrix[col][row];
3126
+ }
3127
+ result.push(array);
3128
+ }
3129
+ }
3130
+ return result;
3131
+ }
3132
+ function stackVertically(ranges, options) {
3133
+ const matrices = ranges.map(toMatrix);
3134
+ const nbColsArr = matrices.map((m) => m?.length ?? 0);
3135
+ const nbCols = Math.max(...nbColsArr);
3136
+ if (options?.requireSameColCount) {
3137
+ const firstLength = nbColsArr[0];
3138
+ if (nbColsArr.some((len) => len !== firstLength)) {
3139
+ return new EvaluationError(_t("All ranges in [[FUNCTION_NAME]] must have the same number of columns (got %s).", nbColsArr.join(", ")));
3140
+ }
3141
+ }
3142
+ const nbRows = matrices.reduce((acc, m) => acc + (m?.[0]?.length ?? 0), 0);
3143
+ const result = generateMatrix(nbCols, nbRows, () => ({
3144
+ value: null,
3145
+ }));
3146
+ let currentRow = 0;
3147
+ for (const matrix of matrices) {
3148
+ for (let col = 0; col < matrix.length; col++) {
3149
+ for (let row = 0; row < matrix[col].length; row++) {
3150
+ result[col][currentRow + row] = matrix[col][row];
3151
+ }
3152
+ }
3153
+ currentRow += matrix[0]?.length ?? 0;
3154
+ }
3155
+ return result;
3156
+ }
3109
3157
  // -----------------------------------------------------------------------------
3110
- // ARRAY_CONSTRAIN
3158
+ // ARRAY.CONSTRAIN
3111
3159
  // -----------------------------------------------------------------------------
3112
3160
  const ARRAY_CONSTRAIN = {
3113
3161
  description: _t("Returns a result array constrained to a specific width and height."),
@@ -3133,6 +3181,30 @@ const ARRAY_CONSTRAIN = {
3133
3181
  isExported: false,
3134
3182
  };
3135
3183
  // -----------------------------------------------------------------------------
3184
+ // ARRAY.LITERAL
3185
+ // -----------------------------------------------------------------------------
3186
+ const ARRAY_LITERAL = {
3187
+ description: _t("Appends ranges vertically and in sequence to return a larger array. All ranges must have the same number of columns."),
3188
+ args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3189
+ compute: function (...ranges) {
3190
+ return stackVertically(ranges, { requireSameColCount: true });
3191
+ },
3192
+ isExported: false,
3193
+ hidden: true,
3194
+ };
3195
+ // -----------------------------------------------------------------------------
3196
+ // ARRAY.ROW
3197
+ // -----------------------------------------------------------------------------
3198
+ const ARRAY_ROW = {
3199
+ description: _t("Appends ranges horizontally and in sequence to return a larger array. All ranges must have the same number of rows."),
3200
+ args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3201
+ compute: function (...ranges) {
3202
+ return stackHorizontally(ranges, { requireSameRowCount: true });
3203
+ },
3204
+ isExported: false,
3205
+ hidden: true,
3206
+ };
3207
+ // -----------------------------------------------------------------------------
3136
3208
  // CHOOSECOLS
3137
3209
  // -----------------------------------------------------------------------------
3138
3210
  const CHOOSECOLS = {
@@ -3278,20 +3350,7 @@ const HSTACK = {
3278
3350
  description: _t("Appends ranges horizontally and in sequence to return a larger array."),
3279
3351
  args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3280
3352
  compute: function (...ranges) {
3281
- const nbRows = Math.max(...ranges.map((r) => r?.[0]?.length ?? 0));
3282
- const result = [];
3283
- for (const range of ranges) {
3284
- const _range = toMatrix(range);
3285
- for (let col = 0; col < _range.length; col++) {
3286
- //TODO: fill with #N/A for unavailable values instead of zeroes
3287
- const array = Array(nbRows).fill({ value: null });
3288
- for (let row = 0; row < _range[col].length; row++) {
3289
- array[row] = _range[col][row];
3290
- }
3291
- result.push(array);
3292
- }
3293
- }
3294
- return result;
3353
+ return stackHorizontally(ranges);
3295
3354
  },
3296
3355
  isExported: true,
3297
3356
  };
@@ -3550,22 +3609,7 @@ const VSTACK = {
3550
3609
  description: _t("Appends ranges vertically and in sequence to return a larger array."),
3551
3610
  args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3552
3611
  compute: function (...ranges) {
3553
- const nbColumns = Math.max(...ranges.map((range) => toMatrix(range).length));
3554
- const nbRows = ranges.reduce((acc, range) => acc + toMatrix(range)[0].length, 0);
3555
- const result = Array(nbColumns)
3556
- .fill([])
3557
- .map(() => Array(nbRows).fill({ value: 0 })); // TODO fill with #N/A
3558
- let currentRow = 0;
3559
- for (const range of ranges) {
3560
- const _array = toMatrix(range);
3561
- for (let col = 0; col < _array.length; col++) {
3562
- for (let row = 0; row < _array[col].length; row++) {
3563
- result[col][currentRow + row] = _array[col][row];
3564
- }
3565
- }
3566
- currentRow += _array[0].length;
3567
- }
3568
- return result;
3612
+ return stackVertically(ranges);
3569
3613
  },
3570
3614
  isExported: true,
3571
3615
  };
@@ -3625,6 +3669,8 @@ const WRAPROWS = {
3625
3669
  var array = /*#__PURE__*/Object.freeze({
3626
3670
  __proto__: null,
3627
3671
  ARRAY_CONSTRAIN: ARRAY_CONSTRAIN,
3672
+ ARRAY_LITERAL: ARRAY_LITERAL,
3673
+ ARRAY_ROW: ARRAY_ROW,
3628
3674
  CHOOSECOLS: CHOOSECOLS,
3629
3675
  CHOOSEROWS: CHOOSEROWS,
3630
3676
  EXPAND: EXPAND,
@@ -6080,6 +6126,47 @@ function splitIfAdjacent(zone, zoneToRemove) {
6080
6126
  return newZones;
6081
6127
  }
6082
6128
  }
6129
+ /**
6130
+ * Splits zone z2 by removing the overlapping zone z1 (fully inside z2).
6131
+ * Returns the remaining parts of z2 that don't overlap with z1.
6132
+ *
6133
+ * Diagram:
6134
+ * ┌──────────── z2 ─────────────┐
6135
+ * │ 1 │
6136
+ * │--------─────────────--------│
6137
+ * │ 2 | z1 | 3 │
6138
+ * │--------─────────────--------│
6139
+ * │ 4 │
6140
+ * └─────────────────────────────┘
6141
+ *
6142
+ * Input:
6143
+ * z1 = { top: 2, bottom: 3, left: 2, right: 3 }
6144
+ * z2 = { top: 1, bottom: 4, left: 1, right: 4 }
6145
+ *
6146
+ * Output:
6147
+ * [
6148
+ * { top: 4, bottom: 4, left: 1, right: 4 }, // bottom
6149
+ * { top: 2, bottom: 3, left: 4, right: 4 }, // right
6150
+ * { top: 2, bottom: 3, left: 1, right: 1 }, // left
6151
+ * { top: 1, bottom: 1, left: 1, right: 4 } // top
6152
+ * ]
6153
+ */
6154
+ function splitZone(z1, z2) {
6155
+ const zones = [];
6156
+ if (z1.bottom < z2.bottom) {
6157
+ zones.push({ ...z2, top: z1.bottom + 1 });
6158
+ }
6159
+ if (z1.right < z2.right) {
6160
+ zones.push({ ...z2, left: z1.right + 1, top: z1.top, bottom: z1.bottom });
6161
+ }
6162
+ if (z1.left > z2.left) {
6163
+ zones.push({ ...z2, right: z1.left - 1, top: z1.top, bottom: z1.bottom });
6164
+ }
6165
+ if (z1.top > z2.top) {
6166
+ zones.push({ ...z2, bottom: z1.top - 1 });
6167
+ }
6168
+ return zones;
6169
+ }
6083
6170
 
6084
6171
  function isSheetDependent(cmd) {
6085
6172
  return "sheetId" in cmd;
@@ -6110,7 +6197,6 @@ const invalidateEvaluationCommands = new Set([
6110
6197
  "REDO",
6111
6198
  "ADD_MERGE",
6112
6199
  "REMOVE_MERGE",
6113
- "DUPLICATE_SHEET",
6114
6200
  "UPDATE_LOCALE",
6115
6201
  "ADD_PIVOT",
6116
6202
  "UPDATE_PIVOT",
@@ -9600,7 +9686,9 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
9600
9686
  while (!chars.isOver()) {
9601
9687
  let token = tokenizeNewLine(chars) ||
9602
9688
  tokenizeSpace(chars) ||
9689
+ tokenizeArrayRowSeparator(chars, locale) ||
9603
9690
  tokenizeArgsSeparator(chars, locale) ||
9691
+ tokenizeBraces(chars) ||
9604
9692
  tokenizeParenthesis(chars) ||
9605
9693
  tokenizeOperator(chars) ||
9606
9694
  tokenizeString(chars) ||
@@ -9633,6 +9721,17 @@ function tokenizeParenthesis(chars) {
9633
9721
  }
9634
9722
  return null;
9635
9723
  }
9724
+ const braces = {
9725
+ "{": { type: "LEFT_BRACE", value: "{" },
9726
+ "}": { type: "RIGHT_BRACE", value: "}" },
9727
+ };
9728
+ function tokenizeBraces(chars) {
9729
+ if (chars.current === "{" || chars.current === "}") {
9730
+ const value = chars.shift();
9731
+ return braces[value];
9732
+ }
9733
+ return null;
9734
+ }
9636
9735
  function tokenizeArgsSeparator(chars, locale) {
9637
9736
  if (chars.current === locale.formulaArgSeparator) {
9638
9737
  const value = chars.shift();
@@ -9641,6 +9740,20 @@ function tokenizeArgsSeparator(chars, locale) {
9641
9740
  }
9642
9741
  return null;
9643
9742
  }
9743
+ function tokenizeArrayRowSeparator(chars, locale) {
9744
+ // The array row separator is used in array literals to separate rows.
9745
+ // It is not explicitly defined in locales, but depends on the formulaArgSeparator.
9746
+ // Example: {1,2,3;4,5,6} — here, ';' separates rows and ',' separates columns.
9747
+ const rowSeparator = locale.formulaArgSeparator === ";" ? "\\" : ";";
9748
+ if (!rowSeparator) {
9749
+ return null;
9750
+ }
9751
+ if (chars.current === rowSeparator) {
9752
+ chars.shift();
9753
+ return { type: "ARRAY_ROW_SEPARATOR", value: rowSeparator };
9754
+ }
9755
+ return null;
9756
+ }
9644
9757
  function tokenizeOperator(chars) {
9645
9758
  for (const op of OPERATORS) {
9646
9759
  if (chars.currentStartsWith(op)) {
@@ -9861,6 +9974,9 @@ function _localizeFormula(formula, fromLocale, toLocale) {
9861
9974
  else if (token.type === "ARG_SEPARATOR") {
9862
9975
  localizedFormula += toLocale.formulaArgSeparator;
9863
9976
  }
9977
+ else if (token.type === "ARRAY_ROW_SEPARATOR") {
9978
+ localizedFormula += toLocale.formulaArgSeparator === ";" ? "\\" : ";";
9979
+ }
9864
9980
  else {
9865
9981
  localizedFormula += token.value;
9866
9982
  }
@@ -11024,7 +11140,6 @@ function sortMatrix(matrix, locale, ...criteria) {
11024
11140
  // -----------------------------------------------------------------------------
11025
11141
  const FILTER = {
11026
11142
  description: _t("Returns a filtered version of the source range, returning only rows or columns that meet the specified conditions."),
11027
- // TODO modify args description when vectorization on formulas is available
11028
11143
  args: [
11029
11144
  arg("range (any, range<any>)", _t("The data to be filtered.")),
11030
11145
  arg("condition (boolean, range<boolean>, repeating)", _t("Column or row containing true or false values corresponding to the range.")),
@@ -17224,6 +17339,8 @@ function parseOperand(tokens) {
17224
17339
  tokenStartIndex: current.tokenIndex,
17225
17340
  tokenEndIndex: rightParen.tokenIndex,
17226
17341
  };
17342
+ case "LEFT_BRACE":
17343
+ return parseArrayLiteral(tokens, current);
17227
17344
  case "OPERATOR":
17228
17345
  const operator = current.value;
17229
17346
  if (UNARY_OPERATORS_PREFIX.includes(operator)) {
@@ -17277,6 +17394,34 @@ function consumeOrThrow(tokens, type, message) {
17277
17394
  }
17278
17395
  return token;
17279
17396
  }
17397
+ function parseArrayLiteral(tokens, leftBrace) {
17398
+ const rows = [];
17399
+ let currentRow = [parseExpression(tokens)]; // there must be at least one element
17400
+ while (tokens.current?.type !== "RIGHT_BRACE") {
17401
+ const nextToken = tokens.shift();
17402
+ if (!nextToken) {
17403
+ throw new BadExpressionError(_t("Missing closing brace"));
17404
+ }
17405
+ else if (nextToken.type === "ARG_SEPARATOR") {
17406
+ currentRow.push(parseExpression(tokens));
17407
+ }
17408
+ else if (nextToken.type === "ARRAY_ROW_SEPARATOR") {
17409
+ rows.push(currentRow);
17410
+ currentRow = [parseExpression(tokens)];
17411
+ }
17412
+ else {
17413
+ throw new BadExpressionError(_t("Unexpected token: %s", nextToken.value));
17414
+ }
17415
+ }
17416
+ const rightBrace = consumeOrThrow(tokens, "RIGHT_BRACE", _t("Missing closing brace"));
17417
+ rows.push(currentRow);
17418
+ return {
17419
+ type: "ARRAY",
17420
+ value: rows,
17421
+ tokenStartIndex: leftBrace.tokenIndex,
17422
+ tokenEndIndex: rightBrace.tokenIndex,
17423
+ };
17424
+ }
17280
17425
  function parseExpression(tokens, parent_priority = 0) {
17281
17426
  if (tokens.length === 0) {
17282
17427
  throw new BadExpressionError();
@@ -17367,6 +17512,13 @@ function* astIterator(ast) {
17367
17512
  yield* astIterator(arg);
17368
17513
  }
17369
17514
  break;
17515
+ case "ARRAY":
17516
+ for (const row of ast.value) {
17517
+ for (const cell of row) {
17518
+ yield* astIterator(cell);
17519
+ }
17520
+ }
17521
+ break;
17370
17522
  case "UNARY_OPERATION":
17371
17523
  yield* astIterator(ast.operand);
17372
17524
  break;
@@ -17384,6 +17536,11 @@ function mapAst(ast, fn) {
17384
17536
  ...ast,
17385
17537
  args: ast.args.map((child) => mapAst(child, fn)),
17386
17538
  };
17539
+ case "ARRAY":
17540
+ return {
17541
+ ...ast,
17542
+ value: ast.value.map((row) => row.map((cell) => mapAst(cell, fn))),
17543
+ };
17387
17544
  case "UNARY_OPERATION":
17388
17545
  return {
17389
17546
  ...ast,
@@ -17542,6 +17699,23 @@ function compileTokensOrThrow(tokens) {
17542
17699
  code.append(...args);
17543
17700
  const fnName = ast.value.toUpperCase();
17544
17701
  return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
17702
+ case "ARRAY": {
17703
+ // a literal array is compiled into function calls
17704
+ const arrayFunctionCall = {
17705
+ type: "FUNCALL",
17706
+ value: "ARRAY.LITERAL",
17707
+ args: ast.value.map((row) => ({
17708
+ type: "FUNCALL",
17709
+ value: "ARRAY.ROW",
17710
+ args: row,
17711
+ tokenStartIndex: 0,
17712
+ tokenEndIndex: 0,
17713
+ })),
17714
+ tokenStartIndex: 0,
17715
+ tokenEndIndex: 0,
17716
+ };
17717
+ return compileAST(arrayFunctionCall);
17718
+ }
17545
17719
  case "UNARY_OPERATION": {
17546
17720
  const fnName = UNARY_OPERATOR_MAP[ast.value];
17547
17721
  const operand = compileAST(ast.operand, false, false).assignResultToVariable();
@@ -25884,6 +26058,17 @@ class XlsxReader {
25884
26058
  }
25885
26059
  }
25886
26060
 
26061
+ function schemeToColorScale(scheme) {
26062
+ const colors = COLORSCHEMES[scheme];
26063
+ return colors === undefined
26064
+ ? undefined
26065
+ : {
26066
+ minColor: colors[0],
26067
+ midColor: colors.length === 3 ? colors[1] : undefined,
26068
+ maxColor: colors[colors.length - 1],
26069
+ };
26070
+ }
26071
+
25887
26072
  /**
25888
26073
  * parses a formula (as a string) into the same formula,
25889
26074
  * but with the references to other cells extracted
@@ -26528,13 +26713,22 @@ migrationStepRegistry
26528
26713
  return data;
26529
26714
  },
26530
26715
  })
26531
- .add("19.2", {
26716
+ .add("19.1.0", {
26532
26717
  migrate(data) {
26533
26718
  for (const sheet of data.sheets || []) {
26534
26719
  for (const figure of sheet.figures || []) {
26535
26720
  if (figure.tag === "chart" && figure.data.type === "geo") {
26536
26721
  if ("colorScale" in figure.data && typeof figure.data.colorScale === "string") {
26537
- figure.data.colorScale = COLORSCHEMES[figure.data.colorScale];
26722
+ figure.data.colorScale = schemeToColorScale(figure.data.colorScale);
26723
+ }
26724
+ }
26725
+ if (figure.tag === "carousel") {
26726
+ for (const definition of Object.values(figure.data.chartDefinitions) || []) {
26727
+ if (definition.type === "geo") {
26728
+ if ("colorScale" in definition && typeof definition.colorScale === "string") {
26729
+ definition.colorScale = schemeToColorScale(definition.colorScale);
26730
+ }
26731
+ }
26538
26732
  }
26539
26733
  }
26540
26734
  }
@@ -26797,18 +26991,22 @@ function dropCommands(initialMessages, commandType) {
26797
26991
  return messages;
26798
26992
  }
26799
26993
  function fixChartDefinitions(data, initialMessages) {
26994
+ /**
26995
+ * Revisions created after version 18.5.1 contain the full chart definition in the command
26996
+ * if the data was alreay updated to 18.5.1, then those older revision cannot (by definition) be reaplied
26997
+ * and should not be replayed.
26998
+ * FIXME: every command should be versionned when upgraded to allow finer tuning.
26999
+ */
27000
+ if (!data.version || compareVersions(String(data.version), "18.5.1") >= 0) {
27001
+ return initialMessages;
27002
+ }
26800
27003
  const messages = [];
26801
27004
  const map = {};
26802
27005
  for (const sheet of data.sheets || []) {
26803
27006
  sheet.figures?.forEach((figure) => {
26804
27007
  if (figure.tag === "chart") {
26805
27008
  // chart definition
26806
- if (data.version && compareVersions(String(data.version), "18.5.1") <= 0) {
26807
- map[figure.data.chartId] = figure.data;
26808
- }
26809
- else {
26810
- map[figure.id] = figure.data;
26811
- }
27009
+ map[figure.id] = figure.data;
26812
27010
  }
26813
27011
  });
26814
27012
  }
@@ -29848,7 +30046,8 @@ class FigurePlugin extends CorePlugin {
29848
30046
  }
29849
30047
  break;
29850
30048
  case "DUPLICATE_SHEET": {
29851
- for (const figureId in this.figures[cmd.sheetId]) {
30049
+ for (const figure of this.getFigures(cmd.sheetId)) {
30050
+ const figureId = figure.id;
29852
30051
  const fig = this.figures[cmd.sheetId]?.[figureId];
29853
30052
  if (!fig) {
29854
30053
  continue;
@@ -32181,10 +32380,17 @@ class PivotCorePlugin extends CorePlugin {
32181
32380
  if (!pivot) {
32182
32381
  continue;
32183
32382
  }
32184
- for (const measure of pivot.definition.measures) {
32383
+ const def = deepCopy(pivot.definition);
32384
+ for (const measure of def.measures) {
32185
32385
  if (measure.computedBy?.formula === formulaString) {
32186
- const measureIndex = pivot.definition.measures.indexOf(measure);
32187
- this.history.update("pivots", pivotId, "definition", "measures", measureIndex, "computedBy", { formula: newFormulaString, sheetId });
32386
+ const measureIndex = def.measures.indexOf(measure);
32387
+ if (measureIndex !== -1) {
32388
+ def.measures[measureIndex].computedBy = {
32389
+ formula: newFormulaString,
32390
+ sheetId,
32391
+ };
32392
+ }
32393
+ this.dispatch("UPDATE_PIVOT", { pivotId, pivot: def });
32188
32394
  }
32189
32395
  }
32190
32396
  }
@@ -33216,6 +33422,9 @@ class SpreadsheetPivotCorePlugin extends CorePlugin {
33216
33422
  const { sheetId, zone } = definition.dataSet;
33217
33423
  const range = this.getters.getRangeFromZone(sheetId, zone);
33218
33424
  const adaptedRange = adaptPivotRange(range, applyChange);
33425
+ if (adaptedRange === range) {
33426
+ return;
33427
+ }
33219
33428
  const dataSet = adaptedRange && {
33220
33429
  sheetId: adaptedRange.sheetId,
33221
33430
  zone: adaptedRange.zone,
@@ -38947,6 +39156,10 @@ function astToFormula(ast) {
38947
39156
  ? `(${astToFormula(ast.operand)})`
38948
39157
  : astToFormula(ast.operand);
38949
39158
  return ast.value + rightOperand;
39159
+ case "ARRAY":
39160
+ return ("{" +
39161
+ ast.value.map((row) => row.map((cell) => astToFormula(cell)).join(",")).join(";") +
39162
+ "}");
38950
39163
  case "BIN_OPERATION":
38951
39164
  const leftOperation = leftOperandNeedsParenthesis(ast)
38952
39165
  ? `(${astToFormula(ast.left)})`
@@ -40511,7 +40724,6 @@ const dateGranularities = [
40511
40724
  pivotRegistry.add("SPREADSHEET", {
40512
40725
  ui: SpreadsheetPivot,
40513
40726
  definition: SpreadsheetPivotRuntimeDefinition,
40514
- externalData: false,
40515
40727
  dateGranularities: [...dateGranularities],
40516
40728
  datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
40517
40729
  isMeasureCandidate: (field) => field.type !== "boolean",
@@ -40553,9 +40765,7 @@ class PivotUIPlugin extends CoreViewPlugin {
40553
40765
  handle(cmd) {
40554
40766
  if (invalidateEvaluationCommands.has(cmd.type)) {
40555
40767
  for (const pivotId of this.getters.getPivotIds()) {
40556
- if (!pivotRegistry.get(this.getters.getPivotCoreDefinition(pivotId).type).externalData) {
40557
- this.setupPivot(pivotId, { recreate: true });
40558
- }
40768
+ this.setupPivot(pivotId, { recreate: true });
40559
40769
  }
40560
40770
  }
40561
40771
  switch (cmd.type) {
@@ -40769,7 +40979,7 @@ class PivotUIPlugin extends CoreViewPlugin {
40769
40979
  pivot.init({ reload: true });
40770
40980
  }
40771
40981
  setupPivot(pivotId, { recreate } = { recreate: false }) {
40772
- const definition = this.getters.getPivotCoreDefinition(pivotId);
40982
+ const definition = deepCopy(this.getters.getPivotCoreDefinition(pivotId));
40773
40983
  if (!(pivotId in this.pivots)) {
40774
40984
  const Pivot = withPivotPresentationLayer(pivotRegistry.get(definition.type).ui);
40775
40985
  this.pivots[pivotId] = new Pivot(this.custom, { definition, getters: this.getters });
@@ -43840,6 +44050,15 @@ class CarouselUIPlugin extends UIPlugin {
43840
44050
  return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
43841
44051
  }
43842
44052
  return "Success" /* CommandResult.Success */;
44053
+ case "DUPLICATE_CAROUSEL_CHART":
44054
+ if (!this.getters.doesCarouselExist(cmd.carouselId) ||
44055
+ !this.getters
44056
+ .getCarousel(cmd.carouselId)
44057
+ .items.some((item) => item.type === "chart" && item.chartId === cmd.chartId) ||
44058
+ this.getters.getChart(cmd.duplicatedChartId)) {
44059
+ return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
44060
+ }
44061
+ return "Success" /* CommandResult.Success */;
43843
44062
  case "ADD_NEW_CHART_TO_CAROUSEL":
43844
44063
  if (!this.getters.doesCarouselExist(cmd.figureId)) {
43845
44064
  return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
@@ -43864,6 +44083,9 @@ class CarouselUIPlugin extends UIPlugin {
43864
44083
  case "ADD_FIGURE_CHART_TO_CAROUSEL":
43865
44084
  this.addFigureChartToCarousel(cmd.carouselFigureId, cmd.chartFigureId, cmd.sheetId);
43866
44085
  break;
44086
+ case "DUPLICATE_CAROUSEL_CHART":
44087
+ this.duplicateCarouselChart(cmd);
44088
+ break;
43867
44089
  case "UPDATE_CAROUSEL_ACTIVE_ITEM":
43868
44090
  this.carouselStates[cmd.figureId] = this.getCarouselItemId(cmd.item);
43869
44091
  break;
@@ -44002,6 +44224,29 @@ class CarouselUIPlugin extends UIPlugin {
44002
44224
  });
44003
44225
  this.dispatch("DELETE_FIGURE", { sheetId, figureId: chartFigureId });
44004
44226
  }
44227
+ duplicateCarouselChart({ carouselId, chartId, sheetId, duplicatedChartId, }) {
44228
+ const chart = this.getters.getChart(chartId);
44229
+ if (!chart) {
44230
+ return;
44231
+ }
44232
+ const carousel = this.getters.getCarousel(carouselId);
44233
+ const duplicatedItemIndex = carousel.items.findIndex((item) => item.type === "chart" && item.chartId === chartId);
44234
+ if (duplicatedItemIndex === -1) {
44235
+ return;
44236
+ }
44237
+ this.dispatch("CREATE_CHART", {
44238
+ chartId: duplicatedChartId,
44239
+ figureId: carouselId,
44240
+ sheetId,
44241
+ definition: chart.getDefinition(),
44242
+ });
44243
+ const carouselItems = insertItemsAtIndex(carousel.items, [{ type: "chart", chartId: duplicatedChartId }], duplicatedItemIndex + 1);
44244
+ this.dispatch("UPDATE_CAROUSEL", {
44245
+ sheetId,
44246
+ figureId: carouselId,
44247
+ definition: { ...carousel, items: carouselItems },
44248
+ });
44249
+ }
44005
44250
  getCarouselItemId(item) {
44006
44251
  return item.type === "chart" ? item.chartId : "carouselDataView";
44007
44252
  }
@@ -45217,25 +45462,43 @@ class GridSelectionPlugin extends UIPlugin {
45217
45462
  return "Success" /* CommandResult.Success */;
45218
45463
  }
45219
45464
  handleEvent(event) {
45220
- const anchor = event.anchor;
45221
- let zones = [];
45465
+ let anchor = event.anchor;
45466
+ let zones = [...this.gridSelection.zones];
45222
45467
  this.isUnbounded = event.options?.unbounded || false;
45223
45468
  switch (event.mode) {
45224
45469
  case "overrideSelection":
45225
45470
  zones = [anchor.zone];
45226
45471
  break;
45227
45472
  case "updateAnchor":
45228
- zones = [...this.gridSelection.zones];
45229
45473
  const index = zones.findIndex((z) => isEqual(z, event.previousAnchor.zone));
45230
45474
  if (index >= 0) {
45231
45475
  zones[index] = anchor.zone;
45232
45476
  }
45233
45477
  break;
45234
45478
  case "newAnchor":
45235
- zones = [...this.gridSelection.zones, anchor.zone];
45479
+ zones.push(anchor.zone);
45480
+ break;
45481
+ case "commitSelection":
45482
+ const zoneToSplit = zones.find((zone) => isZoneInside(anchor.zone, zone) && !isEqual(anchor.zone, zone));
45483
+ const removeFullAnchor = zones.filter((zone) => isEqual(anchor.zone, zone)).length > 1;
45484
+ if (removeFullAnchor && zones.length > 2) {
45485
+ zones = zones.filter((zone) => !isEqual(zone, anchor.zone));
45486
+ }
45487
+ else if (zoneToSplit) {
45488
+ const splittedZones = splitZone(anchor.zone, zoneToSplit);
45489
+ zones = zones
45490
+ .filter((z) => !isEqual(z, anchor.zone) && !isEqual(z, zoneToSplit))
45491
+ .concat(splittedZones);
45492
+ }
45493
+ zones = uniqueZones(zones);
45494
+ const lastZone = zones[zones.length - 1];
45495
+ anchor = {
45496
+ cell: { col: lastZone.left, row: lastZone.top },
45497
+ zone: lastZone,
45498
+ };
45236
45499
  break;
45237
45500
  }
45238
- this.setSelectionMixin(event.anchor, zones);
45501
+ this.setSelectionMixin(anchor, zones);
45239
45502
  /** Any change to the selection has to be reflected in the selection processor. */
45240
45503
  this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));
45241
45504
  const { col, row } = this.gridSelection.anchor.cell;
@@ -45515,7 +45778,7 @@ class GridSelectionPlugin extends UIPlugin {
45515
45778
  setSelectionMixin(anchor, zones) {
45516
45779
  const { anchor: clippedAnchor, zones: clippedZones } = this.clipSelection(this.getters.getActiveSheetId(), { anchor, zones });
45517
45780
  this.gridSelection.anchor = clippedAnchor;
45518
- this.gridSelection.zones = uniqueZones(clippedZones);
45781
+ this.gridSelection.zones = clippedZones;
45519
45782
  }
45520
45783
  /**
45521
45784
  * Change the anchor of the selection active cell to an absolute col and row index.
@@ -47487,6 +47750,9 @@ class SelectionStreamProcessorImpl {
47487
47750
  bottom: Math.max(anchorRow, row),
47488
47751
  };
47489
47752
  const expandedZone = this.getters.expandZone(sheetId, zone);
47753
+ if (isEqual(this.anchor.zone, expandedZone)) {
47754
+ return new DispatchResult("NoChanges" /* CommandResult.NoChanges */);
47755
+ }
47490
47756
  const anchor = { zone: expandedZone, cell: { col: anchorCol, row: anchorRow } };
47491
47757
  return this.processEvent({
47492
47758
  mode: "updateAnchor",
@@ -47507,6 +47773,22 @@ class SelectionStreamProcessorImpl {
47507
47773
  mode: "newAnchor",
47508
47774
  });
47509
47775
  }
47776
+ /**
47777
+ * Triggered on mouse up to finalize the selection.
47778
+ * - If the current anchor zone already exists in the selection, it will be removed.
47779
+ * - If the anchor zone overlaps with existing zones, it will be split into
47780
+ * multiple non-overlapping parts.
47781
+ */
47782
+ commitSelection() {
47783
+ return this.processEvent({
47784
+ options: {
47785
+ scrollIntoView: false,
47786
+ unbounded: true,
47787
+ },
47788
+ anchor: this.anchor,
47789
+ mode: "commitSelection",
47790
+ });
47791
+ }
47510
47792
  /**
47511
47793
  * Increase or decrease the size of the current anchor zone.
47512
47794
  * The anchor cell remains where it is. It's the opposite side
@@ -50913,5 +51195,5 @@ export { BadExpressionError, BasePlugin, CellErrorType, CircularDependencyError,
50913
51195
 
50914
51196
 
50915
51197
  __info__.version = "19.1.0-alpha.3";
50916
- __info__.date = "2025-11-24T07:52:34.809Z";
50917
- __info__.hash = "e232982";
51198
+ __info__.date = "2025-12-02T05:34:07.213Z";
51199
+ __info__.hash = "04cf666";