@odoo/o-spreadsheet 19.1.0-alpha.12 → 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-12T14:16:26.552Z
7
- * @hash 6fefc9c
6
+ * @date 2025-12-02T05:34:07.213Z
7
+ * @hash 04cf666
8
8
  */
9
9
 
10
10
  (function (exports) {
@@ -637,6 +637,7 @@
637
637
  fontSize: 10,
638
638
  fillColor: "",
639
639
  textColor: "",
640
+ rotation: 0,
640
641
  };
641
642
  const DEFAULT_VERTICAL_ALIGN = DEFAULT_STYLE.verticalAlign;
642
643
  // Fonts
@@ -682,7 +683,7 @@
682
683
  };
683
684
  const PIVOT_INDENT = 15;
684
685
  const PIVOT_COLLAPSE_ICON_SIZE = 12;
685
- const PIVOT_MAX_NUMBER_OF_CELLS = 1e5;
686
+ const PIVOT_MAX_NUMBER_OF_CELLS = 5e5;
686
687
 
687
688
  //------------------------------------------------------------------------------
688
689
  // Miscellaneous
@@ -3108,8 +3109,56 @@
3108
3109
  return isMatrix(arg) && !isSingleElementMatrix(arg);
3109
3110
  }
3110
3111
 
3112
+ function stackHorizontally(ranges, options) {
3113
+ const matrices = ranges.map(toMatrix);
3114
+ const nbRowsArr = matrices.map((m) => m?.[0]?.length ?? 0);
3115
+ const nbRows = Math.max(...nbRowsArr);
3116
+ if (options?.requireSameRowCount) {
3117
+ const firstLength = nbRowsArr[0];
3118
+ if (nbRowsArr.some((len) => len !== firstLength)) {
3119
+ return new EvaluationError(_t("All ranges in [[FUNCTION_NAME]] must have the same number of columns (got %s).", nbRowsArr.join(", ")));
3120
+ }
3121
+ }
3122
+ const result = [];
3123
+ for (const matrix of matrices) {
3124
+ for (let col = 0; col < matrix.length; col++) {
3125
+ // Fill with nulls if needed
3126
+ const array = Array(nbRows).fill({ value: null });
3127
+ for (let row = 0; row < matrix[col].length; row++) {
3128
+ array[row] = matrix[col][row];
3129
+ }
3130
+ result.push(array);
3131
+ }
3132
+ }
3133
+ return result;
3134
+ }
3135
+ function stackVertically(ranges, options) {
3136
+ const matrices = ranges.map(toMatrix);
3137
+ const nbColsArr = matrices.map((m) => m?.length ?? 0);
3138
+ const nbCols = Math.max(...nbColsArr);
3139
+ if (options?.requireSameColCount) {
3140
+ const firstLength = nbColsArr[0];
3141
+ if (nbColsArr.some((len) => len !== firstLength)) {
3142
+ return new EvaluationError(_t("All ranges in [[FUNCTION_NAME]] must have the same number of columns (got %s).", nbColsArr.join(", ")));
3143
+ }
3144
+ }
3145
+ const nbRows = matrices.reduce((acc, m) => acc + (m?.[0]?.length ?? 0), 0);
3146
+ const result = generateMatrix(nbCols, nbRows, () => ({
3147
+ value: null,
3148
+ }));
3149
+ let currentRow = 0;
3150
+ for (const matrix of matrices) {
3151
+ for (let col = 0; col < matrix.length; col++) {
3152
+ for (let row = 0; row < matrix[col].length; row++) {
3153
+ result[col][currentRow + row] = matrix[col][row];
3154
+ }
3155
+ }
3156
+ currentRow += matrix[0]?.length ?? 0;
3157
+ }
3158
+ return result;
3159
+ }
3111
3160
  // -----------------------------------------------------------------------------
3112
- // ARRAY_CONSTRAIN
3161
+ // ARRAY.CONSTRAIN
3113
3162
  // -----------------------------------------------------------------------------
3114
3163
  const ARRAY_CONSTRAIN = {
3115
3164
  description: _t("Returns a result array constrained to a specific width and height."),
@@ -3135,6 +3184,30 @@
3135
3184
  isExported: false,
3136
3185
  };
3137
3186
  // -----------------------------------------------------------------------------
3187
+ // ARRAY.LITERAL
3188
+ // -----------------------------------------------------------------------------
3189
+ const ARRAY_LITERAL = {
3190
+ description: _t("Appends ranges vertically and in sequence to return a larger array. All ranges must have the same number of columns."),
3191
+ args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3192
+ compute: function (...ranges) {
3193
+ return stackVertically(ranges, { requireSameColCount: true });
3194
+ },
3195
+ isExported: false,
3196
+ hidden: true,
3197
+ };
3198
+ // -----------------------------------------------------------------------------
3199
+ // ARRAY.ROW
3200
+ // -----------------------------------------------------------------------------
3201
+ const ARRAY_ROW = {
3202
+ description: _t("Appends ranges horizontally and in sequence to return a larger array. All ranges must have the same number of rows."),
3203
+ args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3204
+ compute: function (...ranges) {
3205
+ return stackHorizontally(ranges, { requireSameRowCount: true });
3206
+ },
3207
+ isExported: false,
3208
+ hidden: true,
3209
+ };
3210
+ // -----------------------------------------------------------------------------
3138
3211
  // CHOOSECOLS
3139
3212
  // -----------------------------------------------------------------------------
3140
3213
  const CHOOSECOLS = {
@@ -3280,20 +3353,7 @@
3280
3353
  description: _t("Appends ranges horizontally and in sequence to return a larger array."),
3281
3354
  args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3282
3355
  compute: function (...ranges) {
3283
- const nbRows = Math.max(...ranges.map((r) => r?.[0]?.length ?? 0));
3284
- const result = [];
3285
- for (const range of ranges) {
3286
- const _range = toMatrix(range);
3287
- for (let col = 0; col < _range.length; col++) {
3288
- //TODO: fill with #N/A for unavailable values instead of zeroes
3289
- const array = Array(nbRows).fill({ value: null });
3290
- for (let row = 0; row < _range[col].length; row++) {
3291
- array[row] = _range[col][row];
3292
- }
3293
- result.push(array);
3294
- }
3295
- }
3296
- return result;
3356
+ return stackHorizontally(ranges);
3297
3357
  },
3298
3358
  isExported: true,
3299
3359
  };
@@ -3552,22 +3612,7 @@
3552
3612
  description: _t("Appends ranges vertically and in sequence to return a larger array."),
3553
3613
  args: [arg("range (any, range<any>, repeating)", _t("The range to be appended."))],
3554
3614
  compute: function (...ranges) {
3555
- const nbColumns = Math.max(...ranges.map((range) => toMatrix(range).length));
3556
- const nbRows = ranges.reduce((acc, range) => acc + toMatrix(range)[0].length, 0);
3557
- const result = Array(nbColumns)
3558
- .fill([])
3559
- .map(() => Array(nbRows).fill({ value: 0 })); // TODO fill with #N/A
3560
- let currentRow = 0;
3561
- for (const range of ranges) {
3562
- const _array = toMatrix(range);
3563
- for (let col = 0; col < _array.length; col++) {
3564
- for (let row = 0; row < _array[col].length; row++) {
3565
- result[col][currentRow + row] = _array[col][row];
3566
- }
3567
- }
3568
- currentRow += _array[0].length;
3569
- }
3570
- return result;
3615
+ return stackVertically(ranges);
3571
3616
  },
3572
3617
  isExported: true,
3573
3618
  };
@@ -3627,6 +3672,8 @@
3627
3672
  var array = /*#__PURE__*/Object.freeze({
3628
3673
  __proto__: null,
3629
3674
  ARRAY_CONSTRAIN: ARRAY_CONSTRAIN,
3675
+ ARRAY_LITERAL: ARRAY_LITERAL,
3676
+ ARRAY_ROW: ARRAY_ROW,
3630
3677
  CHOOSECOLS: CHOOSECOLS,
3631
3678
  CHOOSEROWS: CHOOSEROWS,
3632
3679
  EXPAND: EXPAND,
@@ -3648,6 +3695,30 @@
3648
3695
  WRAPROWS: WRAPROWS
3649
3696
  });
3650
3697
 
3698
+ const DEFAULT_LOCALES = [
3699
+ {
3700
+ name: "English (US)",
3701
+ code: "en_US",
3702
+ thousandsSeparator: ",",
3703
+ decimalSeparator: ".",
3704
+ weekStart: 7, // Sunday
3705
+ dateFormat: "m/d/yyyy",
3706
+ timeFormat: "hh:mm:ss a",
3707
+ formulaArgSeparator: ",",
3708
+ },
3709
+ {
3710
+ name: "French",
3711
+ code: "fr_FR",
3712
+ thousandsSeparator: " ",
3713
+ decimalSeparator: ",",
3714
+ weekStart: 1, // Monday
3715
+ dateFormat: "dd/mm/yyyy",
3716
+ timeFormat: "hh:mm:ss",
3717
+ formulaArgSeparator: ";",
3718
+ },
3719
+ ];
3720
+ const DEFAULT_LOCALE = DEFAULT_LOCALES[0];
3721
+
3651
3722
  function tokenizeFormat(str) {
3652
3723
  const chars = new TokenizingChars(str);
3653
3724
  const result = [];
@@ -3932,7 +4003,7 @@
3932
4003
  }
3933
4004
  function parseDateFormatTokens(tokens) {
3934
4005
  const internalFormat = tokens && areValidDateFormatTokens(tokens) ? { type: "date", tokens } : undefined;
3935
- if (!internalFormat) {
4006
+ if (!internalFormat || !tokens?.some((token) => token.type === "DATE_PART")) {
3936
4007
  return undefined;
3937
4008
  }
3938
4009
  if (internalFormat.tokens.length &&
@@ -4169,6 +4240,9 @@
4169
4240
  return prefix + repeatedChar.repeat(timesToRepeat) + padding + suffix;
4170
4241
  }
4171
4242
  function applyInternalNumberFormat(value, format, locale) {
4243
+ if (!format.integerPart.length && !format.decimalPart?.length) {
4244
+ return "";
4245
+ }
4172
4246
  if (value === Infinity) {
4173
4247
  return "∞" + (format.percentSymbols ? "%" : "");
4174
4248
  }
@@ -4775,7 +4849,7 @@
4775
4849
  function setXcToFixedReferenceType(xc, referenceType) {
4776
4850
  let sheetName;
4777
4851
  ({ sheetName, xc } = splitReference(xc));
4778
- sheetName = sheetName ? sheetName + "!" : "";
4852
+ sheetName = sheetName ? getCanonicalSymbolName(sheetName) + "!" : "";
4779
4853
  xc = xc.replace(/\$/g, "");
4780
4854
  const splitIndex = xc.indexOf(":");
4781
4855
  if (splitIndex >= 0) {
@@ -6055,6 +6129,47 @@
6055
6129
  return newZones;
6056
6130
  }
6057
6131
  }
6132
+ /**
6133
+ * Splits zone z2 by removing the overlapping zone z1 (fully inside z2).
6134
+ * Returns the remaining parts of z2 that don't overlap with z1.
6135
+ *
6136
+ * Diagram:
6137
+ * ┌──────────── z2 ─────────────┐
6138
+ * │ 1 │
6139
+ * │--------─────────────--------│
6140
+ * │ 2 | z1 | 3 │
6141
+ * │--------─────────────--------│
6142
+ * │ 4 │
6143
+ * └─────────────────────────────┘
6144
+ *
6145
+ * Input:
6146
+ * z1 = { top: 2, bottom: 3, left: 2, right: 3 }
6147
+ * z2 = { top: 1, bottom: 4, left: 1, right: 4 }
6148
+ *
6149
+ * Output:
6150
+ * [
6151
+ * { top: 4, bottom: 4, left: 1, right: 4 }, // bottom
6152
+ * { top: 2, bottom: 3, left: 4, right: 4 }, // right
6153
+ * { top: 2, bottom: 3, left: 1, right: 1 }, // left
6154
+ * { top: 1, bottom: 1, left: 1, right: 4 } // top
6155
+ * ]
6156
+ */
6157
+ function splitZone(z1, z2) {
6158
+ const zones = [];
6159
+ if (z1.bottom < z2.bottom) {
6160
+ zones.push({ ...z2, top: z1.bottom + 1 });
6161
+ }
6162
+ if (z1.right < z2.right) {
6163
+ zones.push({ ...z2, left: z1.right + 1, top: z1.top, bottom: z1.bottom });
6164
+ }
6165
+ if (z1.left > z2.left) {
6166
+ zones.push({ ...z2, right: z1.left - 1, top: z1.top, bottom: z1.bottom });
6167
+ }
6168
+ if (z1.top > z2.top) {
6169
+ zones.push({ ...z2, bottom: z1.top - 1 });
6170
+ }
6171
+ return zones;
6172
+ }
6058
6173
 
6059
6174
  function isSheetDependent(cmd) {
6060
6175
  return "sheetId" in cmd;
@@ -6085,7 +6200,6 @@
6085
6200
  "REDO",
6086
6201
  "ADD_MERGE",
6087
6202
  "REMOVE_MERGE",
6088
- "DUPLICATE_SHEET",
6089
6203
  "UPDATE_LOCALE",
6090
6204
  "ADD_PIVOT",
6091
6205
  "UPDATE_PIVOT",
@@ -7652,7 +7766,7 @@
7652
7766
  if (!acceptHiddenCells && this.getters.isRowHiddenByUser(sheetId, row))
7653
7767
  continue;
7654
7768
  for (let col = left; col <= right; col++) {
7655
- const cell = this.getters.getCell({ sheetId, col, row });
7769
+ const cell = this.getters.getCorrespondingFormulaCell({ sheetId, col, row });
7656
7770
  if (!cell || !isSubtotalCell(cell)) {
7657
7771
  evaluatedCellToKeep.push(this.getters.getEvaluatedCell({ sheetId, col, row }));
7658
7772
  }
@@ -9547,30 +9661,6 @@
9547
9661
  DVARP: DVARP
9548
9662
  });
9549
9663
 
9550
- const DEFAULT_LOCALES = [
9551
- {
9552
- name: "English (US)",
9553
- code: "en_US",
9554
- thousandsSeparator: ",",
9555
- decimalSeparator: ".",
9556
- weekStart: 7, // Sunday
9557
- dateFormat: "m/d/yyyy",
9558
- timeFormat: "hh:mm:ss a",
9559
- formulaArgSeparator: ",",
9560
- },
9561
- {
9562
- name: "French",
9563
- code: "fr_FR",
9564
- thousandsSeparator: " ",
9565
- decimalSeparator: ",",
9566
- weekStart: 1, // Monday
9567
- dateFormat: "dd/mm/yyyy",
9568
- timeFormat: "hh:mm:ss",
9569
- formulaArgSeparator: ";",
9570
- },
9571
- ];
9572
- const DEFAULT_LOCALE = DEFAULT_LOCALES[0];
9573
-
9574
9664
  /**
9575
9665
  * Tokenizer
9576
9666
  *
@@ -9599,7 +9689,9 @@
9599
9689
  while (!chars.isOver()) {
9600
9690
  let token = tokenizeNewLine(chars) ||
9601
9691
  tokenizeSpace(chars) ||
9692
+ tokenizeArrayRowSeparator(chars, locale) ||
9602
9693
  tokenizeArgsSeparator(chars, locale) ||
9694
+ tokenizeBraces(chars) ||
9603
9695
  tokenizeParenthesis(chars) ||
9604
9696
  tokenizeOperator(chars) ||
9605
9697
  tokenizeString(chars) ||
@@ -9632,6 +9724,17 @@
9632
9724
  }
9633
9725
  return null;
9634
9726
  }
9727
+ const braces = {
9728
+ "{": { type: "LEFT_BRACE", value: "{" },
9729
+ "}": { type: "RIGHT_BRACE", value: "}" },
9730
+ };
9731
+ function tokenizeBraces(chars) {
9732
+ if (chars.current === "{" || chars.current === "}") {
9733
+ const value = chars.shift();
9734
+ return braces[value];
9735
+ }
9736
+ return null;
9737
+ }
9635
9738
  function tokenizeArgsSeparator(chars, locale) {
9636
9739
  if (chars.current === locale.formulaArgSeparator) {
9637
9740
  const value = chars.shift();
@@ -9640,6 +9743,20 @@
9640
9743
  }
9641
9744
  return null;
9642
9745
  }
9746
+ function tokenizeArrayRowSeparator(chars, locale) {
9747
+ // The array row separator is used in array literals to separate rows.
9748
+ // It is not explicitly defined in locales, but depends on the formulaArgSeparator.
9749
+ // Example: {1,2,3;4,5,6} — here, ';' separates rows and ',' separates columns.
9750
+ const rowSeparator = locale.formulaArgSeparator === ";" ? "\\" : ";";
9751
+ if (!rowSeparator) {
9752
+ return null;
9753
+ }
9754
+ if (chars.current === rowSeparator) {
9755
+ chars.shift();
9756
+ return { type: "ARRAY_ROW_SEPARATOR", value: rowSeparator };
9757
+ }
9758
+ return null;
9759
+ }
9643
9760
  function tokenizeOperator(chars) {
9644
9761
  for (const op of OPERATORS) {
9645
9762
  if (chars.currentStartsWith(op)) {
@@ -9860,6 +9977,9 @@
9860
9977
  else if (token.type === "ARG_SEPARATOR") {
9861
9978
  localizedFormula += toLocale.formulaArgSeparator;
9862
9979
  }
9980
+ else if (token.type === "ARRAY_ROW_SEPARATOR") {
9981
+ localizedFormula += toLocale.formulaArgSeparator === ";" ? "\\" : ";";
9982
+ }
9863
9983
  else {
9864
9984
  localizedFormula += token.value;
9865
9985
  }
@@ -11023,7 +11143,6 @@
11023
11143
  // -----------------------------------------------------------------------------
11024
11144
  const FILTER = {
11025
11145
  description: _t("Returns a filtered version of the source range, returning only rows or columns that meet the specified conditions."),
11026
- // TODO modify args description when vectorization on formulas is available
11027
11146
  args: [
11028
11147
  arg("range (any, range<any>)", _t("The data to be filtered.")),
11029
11148
  arg("condition (boolean, range<boolean>, repeating)", _t("Column or row containing true or false values corresponding to the range.")),
@@ -13975,18 +14094,17 @@
13975
14094
  ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
13976
14095
  ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
13977
14096
  },
13978
- GeoChart: {
13979
- ColorScales: {
13980
- blues: _t("Blues"),
13981
- cividis: _t("Cividis"),
13982
- greens: _t("Greens"),
13983
- greys: _t("Greys"),
13984
- oranges: _t("Oranges"),
13985
- purples: _t("Purples"),
13986
- rainbow: _t("Rainbow"),
13987
- reds: _t("Reds"),
13988
- viridis: _t("Viridis"),
13989
- },
14097
+ ColorScales: {
14098
+ blues: _t("Blues"),
14099
+ cividis: _t("Cividis"),
14100
+ custom: _t("Custom"),
14101
+ greens: _t("Greens"),
14102
+ greys: _t("Greys"),
14103
+ oranges: _t("Oranges"),
14104
+ purples: _t("Purples"),
14105
+ rainbow: _t("Rainbow"),
14106
+ reds: _t("Reds"),
14107
+ viridis: _t("Viridis"),
13990
14108
  },
13991
14109
  });
13992
14110
  ({
@@ -17224,6 +17342,8 @@
17224
17342
  tokenStartIndex: current.tokenIndex,
17225
17343
  tokenEndIndex: rightParen.tokenIndex,
17226
17344
  };
17345
+ case "LEFT_BRACE":
17346
+ return parseArrayLiteral(tokens, current);
17227
17347
  case "OPERATOR":
17228
17348
  const operator = current.value;
17229
17349
  if (UNARY_OPERATORS_PREFIX.includes(operator)) {
@@ -17277,6 +17397,34 @@
17277
17397
  }
17278
17398
  return token;
17279
17399
  }
17400
+ function parseArrayLiteral(tokens, leftBrace) {
17401
+ const rows = [];
17402
+ let currentRow = [parseExpression(tokens)]; // there must be at least one element
17403
+ while (tokens.current?.type !== "RIGHT_BRACE") {
17404
+ const nextToken = tokens.shift();
17405
+ if (!nextToken) {
17406
+ throw new BadExpressionError(_t("Missing closing brace"));
17407
+ }
17408
+ else if (nextToken.type === "ARG_SEPARATOR") {
17409
+ currentRow.push(parseExpression(tokens));
17410
+ }
17411
+ else if (nextToken.type === "ARRAY_ROW_SEPARATOR") {
17412
+ rows.push(currentRow);
17413
+ currentRow = [parseExpression(tokens)];
17414
+ }
17415
+ else {
17416
+ throw new BadExpressionError(_t("Unexpected token: %s", nextToken.value));
17417
+ }
17418
+ }
17419
+ const rightBrace = consumeOrThrow(tokens, "RIGHT_BRACE", _t("Missing closing brace"));
17420
+ rows.push(currentRow);
17421
+ return {
17422
+ type: "ARRAY",
17423
+ value: rows,
17424
+ tokenStartIndex: leftBrace.tokenIndex,
17425
+ tokenEndIndex: rightBrace.tokenIndex,
17426
+ };
17427
+ }
17280
17428
  function parseExpression(tokens, parent_priority = 0) {
17281
17429
  if (tokens.length === 0) {
17282
17430
  throw new BadExpressionError();
@@ -17367,6 +17515,13 @@
17367
17515
  yield* astIterator(arg);
17368
17516
  }
17369
17517
  break;
17518
+ case "ARRAY":
17519
+ for (const row of ast.value) {
17520
+ for (const cell of row) {
17521
+ yield* astIterator(cell);
17522
+ }
17523
+ }
17524
+ break;
17370
17525
  case "UNARY_OPERATION":
17371
17526
  yield* astIterator(ast.operand);
17372
17527
  break;
@@ -17384,6 +17539,11 @@
17384
17539
  ...ast,
17385
17540
  args: ast.args.map((child) => mapAst(child, fn)),
17386
17541
  };
17542
+ case "ARRAY":
17543
+ return {
17544
+ ...ast,
17545
+ value: ast.value.map((row) => row.map((cell) => mapAst(cell, fn))),
17546
+ };
17387
17547
  case "UNARY_OPERATION":
17388
17548
  return {
17389
17549
  ...ast,
@@ -17542,6 +17702,23 @@
17542
17702
  code.append(...args);
17543
17703
  const fnName = ast.value.toUpperCase();
17544
17704
  return code.return(`ctx['${fnName}'](${args.map((arg) => arg.returnExpression)})`);
17705
+ case "ARRAY": {
17706
+ // a literal array is compiled into function calls
17707
+ const arrayFunctionCall = {
17708
+ type: "FUNCALL",
17709
+ value: "ARRAY.LITERAL",
17710
+ args: ast.value.map((row) => ({
17711
+ type: "FUNCALL",
17712
+ value: "ARRAY.ROW",
17713
+ args: row,
17714
+ tokenStartIndex: 0,
17715
+ tokenEndIndex: 0,
17716
+ })),
17717
+ tokenStartIndex: 0,
17718
+ tokenEndIndex: 0,
17719
+ };
17720
+ return compileAST(arrayFunctionCall);
17721
+ }
17545
17722
  case "UNARY_OPERATION": {
17546
17723
  const fnName = UNARY_OPERATOR_MAP[ast.value];
17547
17724
  const operand = compileAST(ast.operand, false, false).assignResultToVariable();
@@ -18821,6 +18998,17 @@
18821
18998
  return this.colors[id];
18822
18999
  }
18823
19000
  }
19001
+ const COLORSCHEMES = {
19002
+ greys: ["#ffffff", "#808080", "#000000"],
19003
+ blues: ["#f7fbff", "#6aaed6", "#08306b"],
19004
+ reds: ["#fff5f0", "#fb694a", "#67000d"],
19005
+ greens: ["#f7fcf5", "#73c476", "#00441b"],
19006
+ oranges: ["#fff5eb", "#fd8c3b", "#7f2704"],
19007
+ purples: ["#fcfbfd", "#9e9ac8", "#3f007d"],
19008
+ viridis: ["#440154", "#21918c", "#fde725"],
19009
+ cividis: ["#00224e", "#7d7c78", "#fee838"],
19010
+ rainbow: ["#B41DB4", "#FFFF00", "#00FFFF"],
19011
+ };
18824
19012
  /**
18825
19013
  * Returns a function that maps a value to a color using a color scale defined by the given
18826
19014
  * color/threshold values pairs.
@@ -25873,6 +26061,17 @@
25873
26061
  }
25874
26062
  }
25875
26063
 
26064
+ function schemeToColorScale(scheme) {
26065
+ const colors = COLORSCHEMES[scheme];
26066
+ return colors === undefined
26067
+ ? undefined
26068
+ : {
26069
+ minColor: colors[0],
26070
+ midColor: colors.length === 3 ? colors[1] : undefined,
26071
+ maxColor: colors[colors.length - 1],
26072
+ };
26073
+ }
26074
+
25876
26075
  /**
25877
26076
  * parses a formula (as a string) into the same formula,
25878
26077
  * but with the references to other cells extracted
@@ -26516,6 +26715,29 @@
26516
26715
  }
26517
26716
  return data;
26518
26717
  },
26718
+ })
26719
+ .add("19.1.0", {
26720
+ migrate(data) {
26721
+ for (const sheet of data.sheets || []) {
26722
+ for (const figure of sheet.figures || []) {
26723
+ if (figure.tag === "chart" && figure.data.type === "geo") {
26724
+ if ("colorScale" in figure.data && typeof figure.data.colorScale === "string") {
26725
+ figure.data.colorScale = schemeToColorScale(figure.data.colorScale);
26726
+ }
26727
+ }
26728
+ if (figure.tag === "carousel") {
26729
+ for (const definition of Object.values(figure.data.chartDefinitions) || []) {
26730
+ if (definition.type === "geo") {
26731
+ if ("colorScale" in definition && typeof definition.colorScale === "string") {
26732
+ definition.colorScale = schemeToColorScale(definition.colorScale);
26733
+ }
26734
+ }
26735
+ }
26736
+ }
26737
+ }
26738
+ }
26739
+ return data;
26740
+ },
26519
26741
  });
26520
26742
  function fixOverlappingFilters(data) {
26521
26743
  for (const sheet of data.sheets || []) {
@@ -26772,18 +26994,22 @@
26772
26994
  return messages;
26773
26995
  }
26774
26996
  function fixChartDefinitions(data, initialMessages) {
26997
+ /**
26998
+ * Revisions created after version 18.5.1 contain the full chart definition in the command
26999
+ * if the data was alreay updated to 18.5.1, then those older revision cannot (by definition) be reaplied
27000
+ * and should not be replayed.
27001
+ * FIXME: every command should be versionned when upgraded to allow finer tuning.
27002
+ */
27003
+ if (!data.version || compareVersions(String(data.version), "18.5.1") >= 0) {
27004
+ return initialMessages;
27005
+ }
26775
27006
  const messages = [];
26776
27007
  const map = {};
26777
27008
  for (const sheet of data.sheets || []) {
26778
27009
  sheet.figures?.forEach((figure) => {
26779
27010
  if (figure.tag === "chart") {
26780
27011
  // chart definition
26781
- if (data.version && compareVersions(String(data.version), "18.5.1") <= 0) {
26782
- map[figure.data.chartId] = figure.data;
26783
- }
26784
- else {
26785
- map[figure.id] = figure.data;
26786
- }
27012
+ map[figure.id] = figure.data;
26787
27013
  }
26788
27014
  });
26789
27015
  }
@@ -27316,22 +27542,34 @@
27316
27542
  addBorder(sheetId, zone, newBorder, force = false) {
27317
27543
  const borders = [];
27318
27544
  const plannedBorder = newBorder ? { zone, style: newBorder } : undefined;
27319
- const sideToClear = {
27320
- left: force || !!newBorder?.left,
27321
- right: force || !!newBorder?.right,
27322
- top: force || !!newBorder?.top,
27323
- bottom: force || !!newBorder?.bottom,
27545
+ // For each side, decide if we must clear the border on the *adjacent*
27546
+ // existing cell when we draw on the opposite side of the new zone.
27547
+ //
27548
+ // Example:
27549
+ // - newBorder.right is set → we draw border on the RIGHT side of `zone`
27550
+ // - the cell on the right may already have a LEFT border on that edge
27551
+ // In that case we clear that LEFT border, so only the new RIGHT border
27552
+ // remains on the shared edge.
27553
+ //
27554
+ // existingBorderSideToClear[side] = true means we should clear the border on that
27555
+ // side of the existing adjacent zone before adding the new border.
27556
+ const existingBorderSideToClear = {
27557
+ left: force || !!newBorder?.right,
27558
+ right: force || !!newBorder?.left,
27559
+ top: force || !!newBorder?.bottom,
27560
+ bottom: force || !!newBorder?.top,
27324
27561
  };
27325
27562
  let editingZone = [zone];
27326
27563
  for (const existingBorder of this.borders[sheetId] ?? []) {
27327
27564
  const inter = intersection(existingBorder.zone, zone);
27328
27565
  if (!inter) {
27329
- // Clear adjacent borders on which you write
27566
+ // Check if the existing border is adjacent to the new zone
27330
27567
  const adjacentEdge = adjacent(existingBorder.zone, zone);
27331
- if (adjacentEdge && sideToClear[adjacentEdge.position]) {
27568
+ if (adjacentEdge && existingBorderSideToClear[adjacentEdge.position]) {
27332
27569
  for (const newZone of splitIfAdjacent(existingBorder.zone, zone)) {
27333
27570
  const border = this.computeBorderFromZone(newZone, existingBorder);
27334
27571
  const adjacentEdge = adjacent(newZone, zone);
27572
+ // Clear the existing border on the side that touches the new zone
27335
27573
  switch (adjacentEdge?.position) {
27336
27574
  case "left":
27337
27575
  border.style.left = undefined;
@@ -29811,7 +30049,8 @@
29811
30049
  }
29812
30050
  break;
29813
30051
  case "DUPLICATE_SHEET": {
29814
- for (const figureId in this.figures[cmd.sheetId]) {
30052
+ for (const figure of this.getFigures(cmd.sheetId)) {
30053
+ const figureId = figure.id;
29815
30054
  const fig = this.figures[cmd.sheetId]?.[figureId];
29816
30055
  if (!fig) {
29817
30056
  continue;
@@ -32144,10 +32383,17 @@
32144
32383
  if (!pivot) {
32145
32384
  continue;
32146
32385
  }
32147
- for (const measure of pivot.definition.measures) {
32386
+ const def = deepCopy(pivot.definition);
32387
+ for (const measure of def.measures) {
32148
32388
  if (measure.computedBy?.formula === formulaString) {
32149
- const measureIndex = pivot.definition.measures.indexOf(measure);
32150
- this.history.update("pivots", pivotId, "definition", "measures", measureIndex, "computedBy", { formula: newFormulaString, sheetId });
32389
+ const measureIndex = def.measures.indexOf(measure);
32390
+ if (measureIndex !== -1) {
32391
+ def.measures[measureIndex].computedBy = {
32392
+ formula: newFormulaString,
32393
+ sheetId,
32394
+ };
32395
+ }
32396
+ this.dispatch("UPDATE_PIVOT", { pivotId, pivot: def });
32151
32397
  }
32152
32398
  }
32153
32399
  }
@@ -33179,6 +33425,9 @@
33179
33425
  const { sheetId, zone } = definition.dataSet;
33180
33426
  const range = this.getters.getRangeFromZone(sheetId, zone);
33181
33427
  const adaptedRange = adaptPivotRange(range, applyChange);
33428
+ if (adaptedRange === range) {
33429
+ return;
33430
+ }
33182
33431
  const dataSet = adaptedRange && {
33183
33432
  sheetId: adaptedRange.sheetId,
33184
33433
  zone: adaptedRange.zone,
@@ -33218,6 +33467,13 @@
33218
33467
  "getStyleColors",
33219
33468
  ];
33220
33469
  styles = {};
33470
+ allowDispatch(cmd) {
33471
+ switch (cmd.type) {
33472
+ case "SET_FORMATTING":
33473
+ return this.checkUselessSetFormatting(cmd);
33474
+ }
33475
+ return "Success" /* CommandResult.Success */;
33476
+ }
33221
33477
  handle(cmd) {
33222
33478
  switch (cmd.type) {
33223
33479
  case "ADD_MERGE":
@@ -33442,6 +33698,28 @@
33442
33698
  exportForExcel(data) {
33443
33699
  this.export(data);
33444
33700
  }
33701
+ checkUselessSetFormatting(cmd) {
33702
+ const { sheetId, target } = cmd;
33703
+ const hasStyle = "style" in cmd;
33704
+ const hasFormat = "format" in cmd;
33705
+ if (!hasStyle && !hasFormat) {
33706
+ return "NoChanges" /* CommandResult.NoChanges */;
33707
+ }
33708
+ for (const zone of recomputeZones(target)) {
33709
+ for (let col = zone.left; col <= zone.right; col++) {
33710
+ for (let row = zone.top; row <= zone.bottom; row++) {
33711
+ const position = { sheetId, col, row };
33712
+ const cell = this.getters.getCell(position);
33713
+ const style = this.getCellStyle(position);
33714
+ if ((hasStyle && !deepEquals(style, cmd.style)) ||
33715
+ (hasFormat && cell?.format !== cmd.format)) {
33716
+ return "Success" /* CommandResult.Success */;
33717
+ }
33718
+ }
33719
+ }
33720
+ }
33721
+ return "NoChanges" /* CommandResult.NoChanges */;
33722
+ }
33445
33723
  }
33446
33724
 
33447
33725
  class TableStylePlugin extends CorePlugin {
@@ -36469,48 +36747,64 @@
36469
36747
  }
36470
36748
  function getCellContentHeight(ctx, content, style, colSize) {
36471
36749
  const maxWidth = style?.wrapping === "wrap" ? colSize - 2 * MIN_CELL_TEXT_MARGIN : undefined;
36472
- const numberOfLines = splitTextToWidth(ctx, content, style, maxWidth).length;
36473
- const fontSize = computeTextFontSizeInPixels(style);
36474
- return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
36750
+ const lines = splitTextToWidth(ctx, content, style, maxWidth);
36751
+ return computeMultilineTextSize(ctx, lines, style).height + 2 * PADDING_AUTORESIZE_VERTICAL;
36475
36752
  }
36476
36753
  function getDefaultContextFont(fontSize, bold = false, italic = false) {
36477
36754
  const italicStr = italic ? "italic" : "";
36478
36755
  const weight = bold ? "bold" : "";
36479
36756
  return `${italicStr} ${weight} ${fontSize}px ${DEFAULT_FONT}`;
36480
36757
  }
36481
- const textWidthCache = {};
36482
- function computeTextWidth(context, text, style, fontUnit = "pt") {
36758
+ function computeMultilineTextSize(context, textLines, style = {}, fontUnit = "pt") {
36759
+ if (!textLines.length)
36760
+ return { width: 0, height: 0 };
36483
36761
  const font = computeTextFont(style, fontUnit);
36484
- return computeCachedTextWidth(context, text, font);
36485
- }
36486
- function computeCachedTextWidth(context, text, font) {
36487
- if (!textWidthCache[font]) {
36488
- textWidthCache[font] = {};
36762
+ const sizes = textLines.map((line) => computeCachedTextDimension(context, line, font));
36763
+ const height = computeTextLinesHeight(sizes[0].height, textLines.length);
36764
+ const width = Math.max(...sizes.map((size) => size.width));
36765
+ if (!style.rotation) {
36766
+ return { height, width };
36489
36767
  }
36490
- if (textWidthCache[font][text] === undefined) {
36491
- const oldFont = context.font;
36492
- context.font = font;
36493
- textWidthCache[font][text] = context.measureText(text).width;
36494
- context.font = oldFont;
36768
+ const cos = Math.abs(Math.cos(style.rotation));
36769
+ const sin = Math.abs(Math.sin(style.rotation));
36770
+ return { width: width * cos + height * sin, height: sin * width + cos * height };
36771
+ }
36772
+ function computeTextWidth(context, text, style = {}, fontUnit = "pt") {
36773
+ const font = computeTextFont(style, fontUnit);
36774
+ return computeCachedTextWidth(context, text, font, style.rotation);
36775
+ }
36776
+ function computeCachedTextWidth(context, text, font, rotation) {
36777
+ const size = computeCachedTextDimension(context, text, font);
36778
+ if (!rotation) {
36779
+ return size.width;
36495
36780
  }
36496
- return textWidthCache[font][text];
36781
+ const cos = Math.abs(Math.cos(rotation));
36782
+ const sin = Math.abs(Math.sin(rotation));
36783
+ return size.width * cos + size.height * sin;
36497
36784
  }
36498
36785
  const textDimensionsCache = {};
36499
36786
  function computeTextDimension(context, text, style, fontUnit = "pt") {
36500
36787
  const font = computeTextFont(style, fontUnit);
36501
- context.save();
36502
- context.font = font;
36503
- const dimensions = computeCachedTextDimension(context, text);
36504
- context.restore();
36505
- return dimensions;
36506
- }
36507
- function computeCachedTextDimension(context, text) {
36508
- const font = context.font;
36788
+ const size = computeCachedTextDimension(context, text, font);
36789
+ if (!style.rotation) {
36790
+ return size;
36791
+ }
36792
+ const cos = Math.abs(Math.cos(style.rotation));
36793
+ const sin = Math.abs(Math.sin(style.rotation));
36794
+ return {
36795
+ width: size.width * cos + size.height * sin,
36796
+ height: size.height * cos + size.width * sin,
36797
+ };
36798
+ }
36799
+ function computeCachedTextDimension(context, text, font) {
36509
36800
  if (!textDimensionsCache[font]) {
36510
36801
  textDimensionsCache[font] = {};
36511
36802
  }
36512
36803
  if (textDimensionsCache[font][text] === undefined) {
36804
+ context.save();
36805
+ context.font = font;
36513
36806
  const measure = context.measureText(text);
36807
+ context.restore();
36514
36808
  const width = measure.width;
36515
36809
  const height = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
36516
36810
  textDimensionsCache[font][text] = { width, height };
@@ -38707,7 +39001,8 @@
38707
39001
  }
38708
39002
  break;
38709
39003
  case "SET_FORMATTING":
38710
- if (cmd.style && ("fontSize" in cmd.style || "wrapping" in cmd.style)) {
39004
+ if (cmd.style &&
39005
+ ("fontSize" in cmd.style || "wrapping" in cmd.style || "rotation" in cmd.style)) {
38711
39006
  for (const zone of cmd.target) {
38712
39007
  // TODO FLDA use rangeSet
38713
39008
  this.updateRowSizeForZoneChange(cmd.sheetId, zone);
@@ -38864,6 +39159,10 @@
38864
39159
  ? `(${astToFormula(ast.operand)})`
38865
39160
  : astToFormula(ast.operand);
38866
39161
  return ast.value + rightOperand;
39162
+ case "ARRAY":
39163
+ return ("{" +
39164
+ ast.value.map((row) => row.map((cell) => astToFormula(cell)).join(",")).join(";") +
39165
+ "}");
38867
39166
  case "BIN_OPERATION":
38868
39167
  const leftOperation = leftOperandNeedsParenthesis(ast)
38869
39168
  ? `(${astToFormula(ast.left)})`
@@ -40428,7 +40727,6 @@
40428
40727
  pivotRegistry.add("SPREADSHEET", {
40429
40728
  ui: SpreadsheetPivot,
40430
40729
  definition: SpreadsheetPivotRuntimeDefinition,
40431
- externalData: false,
40432
40730
  dateGranularities: [...dateGranularities],
40433
40731
  datetimeGranularities: [...dateGranularities, "hour_number", "minute_number", "second_number"],
40434
40732
  isMeasureCandidate: (field) => field.type !== "boolean",
@@ -40470,9 +40768,7 @@
40470
40768
  handle(cmd) {
40471
40769
  if (invalidateEvaluationCommands.has(cmd.type)) {
40472
40770
  for (const pivotId of this.getters.getPivotIds()) {
40473
- if (!pivotRegistry.get(this.getters.getPivotCoreDefinition(pivotId).type).externalData) {
40474
- this.setupPivot(pivotId, { recreate: true });
40475
- }
40771
+ this.setupPivot(pivotId, { recreate: true });
40476
40772
  }
40477
40773
  }
40478
40774
  switch (cmd.type) {
@@ -40686,7 +40982,7 @@
40686
40982
  pivot.init({ reload: true });
40687
40983
  }
40688
40984
  setupPivot(pivotId, { recreate } = { recreate: false }) {
40689
- const definition = this.getters.getPivotCoreDefinition(pivotId);
40985
+ const definition = deepCopy(this.getters.getPivotCoreDefinition(pivotId));
40690
40986
  if (!(pivotId in this.pivots)) {
40691
40987
  const Pivot = withPivotPresentationLayer(pivotRegistry.get(definition.type).ui);
40692
40988
  this.pivots[pivotId] = new Pivot(this.custom, { definition, getters: this.getters });
@@ -43484,6 +43780,7 @@
43484
43780
  "getTextWidth",
43485
43781
  "getCellText",
43486
43782
  "getCellMultiLineText",
43783
+ "getMultilineTextSize",
43487
43784
  "getContiguousZone",
43488
43785
  "computeTextYCoordinate",
43489
43786
  ];
@@ -43534,7 +43831,7 @@
43534
43831
  const content = this.getters.getEvaluatedCell(position).formattedValue;
43535
43832
  if (content) {
43536
43833
  const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
43537
- contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
43834
+ contentWidth += computeMultilineTextSize(this.ctx, multiLineText, style).width;
43538
43835
  }
43539
43836
  for (const icon of this.getters.getCellIcons(position)) {
43540
43837
  contentWidth += icon.margin + icon.size;
@@ -43555,6 +43852,9 @@
43555
43852
  getTextWidth(text, style) {
43556
43853
  return computeTextWidth(this.ctx, text, style);
43557
43854
  }
43855
+ getMultilineTextSize(text, style) {
43856
+ return computeMultilineTextSize(this.ctx, text, style);
43857
+ }
43558
43858
  getCellText(position, args) {
43559
43859
  const cell = this.getters.getCell(position);
43560
43860
  const locale = this.getters.getLocale();
@@ -43753,6 +44053,15 @@
43753
44053
  return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
43754
44054
  }
43755
44055
  return "Success" /* CommandResult.Success */;
44056
+ case "DUPLICATE_CAROUSEL_CHART":
44057
+ if (!this.getters.doesCarouselExist(cmd.carouselId) ||
44058
+ !this.getters
44059
+ .getCarousel(cmd.carouselId)
44060
+ .items.some((item) => item.type === "chart" && item.chartId === cmd.chartId) ||
44061
+ this.getters.getChart(cmd.duplicatedChartId)) {
44062
+ return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
44063
+ }
44064
+ return "Success" /* CommandResult.Success */;
43756
44065
  case "ADD_NEW_CHART_TO_CAROUSEL":
43757
44066
  if (!this.getters.doesCarouselExist(cmd.figureId)) {
43758
44067
  return "InvalidFigureId" /* CommandResult.InvalidFigureId */;
@@ -43777,6 +44086,9 @@
43777
44086
  case "ADD_FIGURE_CHART_TO_CAROUSEL":
43778
44087
  this.addFigureChartToCarousel(cmd.carouselFigureId, cmd.chartFigureId, cmd.sheetId);
43779
44088
  break;
44089
+ case "DUPLICATE_CAROUSEL_CHART":
44090
+ this.duplicateCarouselChart(cmd);
44091
+ break;
43780
44092
  case "UPDATE_CAROUSEL_ACTIVE_ITEM":
43781
44093
  this.carouselStates[cmd.figureId] = this.getCarouselItemId(cmd.item);
43782
44094
  break;
@@ -43915,6 +44227,29 @@
43915
44227
  });
43916
44228
  this.dispatch("DELETE_FIGURE", { sheetId, figureId: chartFigureId });
43917
44229
  }
44230
+ duplicateCarouselChart({ carouselId, chartId, sheetId, duplicatedChartId, }) {
44231
+ const chart = this.getters.getChart(chartId);
44232
+ if (!chart) {
44233
+ return;
44234
+ }
44235
+ const carousel = this.getters.getCarousel(carouselId);
44236
+ const duplicatedItemIndex = carousel.items.findIndex((item) => item.type === "chart" && item.chartId === chartId);
44237
+ if (duplicatedItemIndex === -1) {
44238
+ return;
44239
+ }
44240
+ this.dispatch("CREATE_CHART", {
44241
+ chartId: duplicatedChartId,
44242
+ figureId: carouselId,
44243
+ sheetId,
44244
+ definition: chart.getDefinition(),
44245
+ });
44246
+ const carouselItems = insertItemsAtIndex(carousel.items, [{ type: "chart", chartId: duplicatedChartId }], duplicatedItemIndex + 1);
44247
+ this.dispatch("UPDATE_CAROUSEL", {
44248
+ sheetId,
44249
+ figureId: carouselId,
44250
+ definition: { ...carousel, items: carouselItems },
44251
+ });
44252
+ }
43918
44253
  getCarouselItemId(item) {
43919
44254
  return item.type === "chart" ? item.chartId : "carouselDataView";
43920
44255
  }
@@ -45130,25 +45465,43 @@
45130
45465
  return "Success" /* CommandResult.Success */;
45131
45466
  }
45132
45467
  handleEvent(event) {
45133
- const anchor = event.anchor;
45134
- let zones = [];
45468
+ let anchor = event.anchor;
45469
+ let zones = [...this.gridSelection.zones];
45135
45470
  this.isUnbounded = event.options?.unbounded || false;
45136
45471
  switch (event.mode) {
45137
45472
  case "overrideSelection":
45138
45473
  zones = [anchor.zone];
45139
45474
  break;
45140
45475
  case "updateAnchor":
45141
- zones = [...this.gridSelection.zones];
45142
45476
  const index = zones.findIndex((z) => isEqual(z, event.previousAnchor.zone));
45143
45477
  if (index >= 0) {
45144
45478
  zones[index] = anchor.zone;
45145
45479
  }
45146
45480
  break;
45147
45481
  case "newAnchor":
45148
- zones = [...this.gridSelection.zones, anchor.zone];
45482
+ zones.push(anchor.zone);
45483
+ break;
45484
+ case "commitSelection":
45485
+ const zoneToSplit = zones.find((zone) => isZoneInside(anchor.zone, zone) && !isEqual(anchor.zone, zone));
45486
+ const removeFullAnchor = zones.filter((zone) => isEqual(anchor.zone, zone)).length > 1;
45487
+ if (removeFullAnchor && zones.length > 2) {
45488
+ zones = zones.filter((zone) => !isEqual(zone, anchor.zone));
45489
+ }
45490
+ else if (zoneToSplit) {
45491
+ const splittedZones = splitZone(anchor.zone, zoneToSplit);
45492
+ zones = zones
45493
+ .filter((z) => !isEqual(z, anchor.zone) && !isEqual(z, zoneToSplit))
45494
+ .concat(splittedZones);
45495
+ }
45496
+ zones = uniqueZones(zones);
45497
+ const lastZone = zones[zones.length - 1];
45498
+ anchor = {
45499
+ cell: { col: lastZone.left, row: lastZone.top },
45500
+ zone: lastZone,
45501
+ };
45149
45502
  break;
45150
45503
  }
45151
- this.setSelectionMixin(event.anchor, zones);
45504
+ this.setSelectionMixin(anchor, zones);
45152
45505
  /** Any change to the selection has to be reflected in the selection processor. */
45153
45506
  this.selection.resetDefaultAnchor(this, deepCopy(this.gridSelection.anchor));
45154
45507
  const { col, row } = this.gridSelection.anchor.cell;
@@ -45428,7 +45781,7 @@
45428
45781
  setSelectionMixin(anchor, zones) {
45429
45782
  const { anchor: clippedAnchor, zones: clippedZones } = this.clipSelection(this.getters.getActiveSheetId(), { anchor, zones });
45430
45783
  this.gridSelection.anchor = clippedAnchor;
45431
- this.gridSelection.zones = uniqueZones(clippedZones);
45784
+ this.gridSelection.zones = clippedZones;
45432
45785
  }
45433
45786
  /**
45434
45787
  * Change the anchor of the selection active cell to an absolute col and row index.
@@ -46138,6 +46491,7 @@
46138
46491
  "getGridOffset",
46139
46492
  "getViewportZoomLevel",
46140
46493
  "getScrollBarWidth",
46494
+ "getMaximumSheetOffset",
46141
46495
  ];
46142
46496
  viewports = {};
46143
46497
  /**
@@ -47243,6 +47597,9 @@
47243
47597
  observe(owner, callbacks) {
47244
47598
  this.observers.set(owner, { owner, callbacks });
47245
47599
  }
47600
+ unobserve(owner) {
47601
+ this.observers.delete(owner);
47602
+ }
47246
47603
  /**
47247
47604
  * Capture the stream for yourself
47248
47605
  */
@@ -47335,6 +47692,9 @@
47335
47692
  observe(owner, callbacks) {
47336
47693
  this.stream.observe(owner, callbacks);
47337
47694
  }
47695
+ unobserve(owner) {
47696
+ this.stream.unobserve(owner);
47697
+ }
47338
47698
  release(owner) {
47339
47699
  if (this.stream.isListening(owner)) {
47340
47700
  this.stream.release(owner);
@@ -47393,6 +47753,9 @@
47393
47753
  bottom: Math.max(anchorRow, row),
47394
47754
  };
47395
47755
  const expandedZone = this.getters.expandZone(sheetId, zone);
47756
+ if (isEqual(this.anchor.zone, expandedZone)) {
47757
+ return new DispatchResult("NoChanges" /* CommandResult.NoChanges */);
47758
+ }
47396
47759
  const anchor = { zone: expandedZone, cell: { col: anchorCol, row: anchorRow } };
47397
47760
  return this.processEvent({
47398
47761
  mode: "updateAnchor",
@@ -47413,6 +47776,22 @@
47413
47776
  mode: "newAnchor",
47414
47777
  });
47415
47778
  }
47779
+ /**
47780
+ * Triggered on mouse up to finalize the selection.
47781
+ * - If the current anchor zone already exists in the selection, it will be removed.
47782
+ * - If the anchor zone overlaps with existing zones, it will be split into
47783
+ * multiple non-overlapping parts.
47784
+ */
47785
+ commitSelection() {
47786
+ return this.processEvent({
47787
+ options: {
47788
+ scrollIntoView: false,
47789
+ unbounded: true,
47790
+ },
47791
+ anchor: this.anchor,
47792
+ mode: "commitSelection",
47793
+ });
47794
+ }
47416
47795
  /**
47417
47796
  * Increase or decrease the size of the current anchor zone.
47418
47797
  * The anchor cell remains where it is. It's the opposite side
@@ -50927,8 +51306,8 @@
50927
51306
 
50928
51307
 
50929
51308
  __info__.version = "19.1.0-alpha.3";
50930
- __info__.date = "2025-11-12T14:16:26.552Z";
50931
- __info__.hash = "6fefc9c";
51309
+ __info__.date = "2025-12-02T05:34:07.213Z";
51310
+ __info__.hash = "04cf666";
50932
51311
 
50933
51312
 
50934
51313
  })(this.o_spreadsheet_engine = this.o_spreadsheet_engine || {});