@odoo/o-spreadsheet 16.3.1 → 16.4.0-alpha.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.
@@ -1113,6 +1113,21 @@ function isConsecutive(iterable) {
1113
1113
  }
1114
1114
  }
1115
1115
  return true;
1116
+ }
1117
+ class JetSet extends Set {
1118
+ add(...iterable) {
1119
+ for (const element of iterable) {
1120
+ super.add(element);
1121
+ }
1122
+ return this;
1123
+ }
1124
+ delete(...iterable) {
1125
+ let deleted = false;
1126
+ for (const element of iterable) {
1127
+ deleted ||= super.delete(element);
1128
+ }
1129
+ return deleted;
1130
+ }
1116
1131
  }
1117
1132
 
1118
1133
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -2883,6 +2898,14 @@ function positions(zone) {
2883
2898
  }
2884
2899
  return positions;
2885
2900
  }
2901
+ function forEachPositionsInZone(zone, callback) {
2902
+ const { left, right, top, bottom } = zone;
2903
+ for (let col = left; col <= right; col++) {
2904
+ for (let row = top; row <= bottom; row++) {
2905
+ callback(col, row);
2906
+ }
2907
+ }
2908
+ }
2886
2909
  /**
2887
2910
  * This function returns a zone with coordinates modified according to the change
2888
2911
  * applied to the zone. It may be possible to change the zone by resizing or moving
@@ -4183,6 +4206,11 @@ const invalidateEvaluationCommands = new Set([
4183
4206
  "INSERT_CELL",
4184
4207
  "UNDO",
4185
4208
  "REDO",
4209
+ "ADD_MERGE",
4210
+ ]);
4211
+ const invalidateDependenciesCommands = new Set([
4212
+ ...invalidateEvaluationCommands,
4213
+ "MOVE_RANGES",
4186
4214
  ]);
4187
4215
  const invalidateCFEvaluationCommands = new Set([
4188
4216
  ...invalidateEvaluationCommands,
@@ -4400,6 +4428,9 @@ var CommandResult;
4400
4428
  })(CommandResult || (CommandResult = {}));
4401
4429
 
4402
4430
  const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
4431
+ function isMatrix(x) {
4432
+ return Array.isArray(x) && Array.isArray(x[0]);
4433
+ }
4403
4434
  var DIRECTION;
4404
4435
  (function (DIRECTION) {
4405
4436
  DIRECTION["UP"] = "up";
@@ -10365,7 +10396,7 @@ const PRODUCT = {
10365
10396
  ],
10366
10397
  returns: ["NUMBER"],
10367
10398
  computeFormat: (factor1) => {
10368
- return Array.isArray(factor1) ? factor1[0][0]?.format : factor1?.format;
10399
+ return Array.isArray(factor1?.format) ? factor1.format[0][0] : factor1?.format;
10369
10400
  },
10370
10401
  compute: function (...factors) {
10371
10402
  let count = 0;
@@ -10591,7 +10622,7 @@ const SUM = {
10591
10622
  ],
10592
10623
  returns: ["NUMBER"],
10593
10624
  computeFormat: (value1) => {
10594
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
10625
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
10595
10626
  },
10596
10627
  compute: function (...values) {
10597
10628
  return reduceNumbers(values, (acc, a) => acc + a, 0);
@@ -10863,7 +10894,7 @@ const AVERAGE = {
10863
10894
  ],
10864
10895
  returns: ["NUMBER"],
10865
10896
  computeFormat: (value1) => {
10866
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
10897
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
10867
10898
  },
10868
10899
  compute: function (...values) {
10869
10900
  let count = 0;
@@ -10891,7 +10922,7 @@ const AVERAGE_WEIGHTED = {
10891
10922
  ],
10892
10923
  returns: ["NUMBER"],
10893
10924
  computeFormat: (values) => {
10894
- return Array.isArray(values) ? values[0][0]?.format : values?.format;
10925
+ return Array.isArray(values?.format) ? values.format[0][0] : values?.format;
10895
10926
  },
10896
10927
  compute: function (...values) {
10897
10928
  let sum = 0;
@@ -10949,7 +10980,7 @@ const AVERAGEA = {
10949
10980
  ],
10950
10981
  returns: ["NUMBER"],
10951
10982
  computeFormat: (value1) => {
10952
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
10983
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
10953
10984
  },
10954
10985
  compute: function (...values) {
10955
10986
  let count = 0;
@@ -11122,7 +11153,7 @@ const LARGE = {
11122
11153
  ],
11123
11154
  returns: ["NUMBER"],
11124
11155
  computeFormat: (data) => {
11125
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11156
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11126
11157
  },
11127
11158
  compute: function (data, n) {
11128
11159
  const _n = Math.trunc(toNumber(n));
@@ -11158,7 +11189,7 @@ const MAX = {
11158
11189
  ],
11159
11190
  returns: ["NUMBER"],
11160
11191
  computeFormat: (value1) => {
11161
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
11192
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
11162
11193
  },
11163
11194
  compute: function (...values) {
11164
11195
  const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity);
@@ -11177,7 +11208,7 @@ const MAXA = {
11177
11208
  ],
11178
11209
  returns: ["NUMBER"],
11179
11210
  computeFormat: (value1) => {
11180
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
11211
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
11181
11212
  },
11182
11213
  compute: function (...values) {
11183
11214
  const maxa = reduceNumbersTextAs0(values, (acc, a) => {
@@ -11223,7 +11254,7 @@ const MEDIAN = {
11223
11254
  ],
11224
11255
  returns: ["NUMBER"],
11225
11256
  computeFormat: (value1) => {
11226
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
11257
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
11227
11258
  },
11228
11259
  compute: function (...values) {
11229
11260
  let data = [];
@@ -11245,7 +11276,7 @@ const MIN = {
11245
11276
  ],
11246
11277
  returns: ["NUMBER"],
11247
11278
  computeFormat: (value1) => {
11248
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
11279
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
11249
11280
  },
11250
11281
  compute: function (...values) {
11251
11282
  const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity);
@@ -11264,7 +11295,7 @@ const MINA = {
11264
11295
  ],
11265
11296
  returns: ["NUMBER"],
11266
11297
  computeFormat: (value1) => {
11267
- return Array.isArray(value1) ? value1[0][0]?.format : value1?.format;
11298
+ return Array.isArray(value1?.format) ? value1.format[0][0] : value1?.format;
11268
11299
  },
11269
11300
  compute: function (...values) {
11270
11301
  const mina = reduceNumbersTextAs0(values, (acc, a) => {
@@ -11310,7 +11341,7 @@ const PERCENTILE = {
11310
11341
  ],
11311
11342
  returns: ["NUMBER"],
11312
11343
  computeFormat: (data) => {
11313
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11344
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11314
11345
  },
11315
11346
  compute: function (data, percentile) {
11316
11347
  return PERCENTILE_INC.compute(data, percentile);
@@ -11328,7 +11359,7 @@ const PERCENTILE_EXC = {
11328
11359
  ],
11329
11360
  returns: ["NUMBER"],
11330
11361
  computeFormat: (data) => {
11331
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11362
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11332
11363
  },
11333
11364
  compute: function (data, percentile) {
11334
11365
  return centile([data], percentile, false);
@@ -11346,7 +11377,7 @@ const PERCENTILE_INC = {
11346
11377
  ],
11347
11378
  returns: ["NUMBER"],
11348
11379
  computeFormat: (data) => {
11349
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11380
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11350
11381
  },
11351
11382
  compute: function (data, percentile) {
11352
11383
  return centile([data], percentile, true);
@@ -11364,7 +11395,7 @@ const QUARTILE = {
11364
11395
  ],
11365
11396
  returns: ["NUMBER"],
11366
11397
  computeFormat: (data) => {
11367
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11398
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11368
11399
  },
11369
11400
  compute: function (data, quartileNumber) {
11370
11401
  return QUARTILE_INC.compute(data, quartileNumber);
@@ -11382,7 +11413,7 @@ const QUARTILE_EXC = {
11382
11413
  ],
11383
11414
  returns: ["NUMBER"],
11384
11415
  computeFormat: (data) => {
11385
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11416
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11386
11417
  },
11387
11418
  compute: function (data, quartileNumber) {
11388
11419
  const _quartileNumber = Math.trunc(toNumber(quartileNumber));
@@ -11401,7 +11432,7 @@ const QUARTILE_INC = {
11401
11432
  ],
11402
11433
  returns: ["NUMBER"],
11403
11434
  computeFormat: (data) => {
11404
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11435
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11405
11436
  },
11406
11437
  compute: function (data, quartileNumber) {
11407
11438
  const _quartileNumber = Math.trunc(toNumber(quartileNumber));
@@ -11420,7 +11451,7 @@ const SMALL = {
11420
11451
  ],
11421
11452
  returns: ["NUMBER"],
11422
11453
  computeFormat: (data) => {
11423
- return Array.isArray(data) ? data[0][0]?.format : data?.format;
11454
+ return Array.isArray(data?.format) ? data.format[0][0] : data?.format;
11424
11455
  },
11425
11456
  compute: function (data, n) {
11426
11457
  const _n = Math.trunc(toNumber(n));
@@ -16207,17 +16238,11 @@ function extractArgValuesFromArgs(args) {
16207
16238
  return undefined;
16208
16239
  }
16209
16240
  if (typeof arg === "function") {
16210
- return () => _extractArgValuesFromArgs(arg());
16241
+ return () => arg()?.value;
16211
16242
  }
16212
- return _extractArgValuesFromArgs(arg);
16243
+ return arg.value;
16213
16244
  });
16214
16245
  }
16215
- function _extractArgValuesFromArgs(arg) {
16216
- if (Array.isArray(arg)) {
16217
- return arg.map((col) => col.map((simpleArg) => simpleArg?.value));
16218
- }
16219
- return arg?.value;
16220
- }
16221
16246
  const functionRegistry = new FunctionRegistry();
16222
16247
  for (let category of categories) {
16223
16248
  const fns = category.functions;
@@ -20757,7 +20782,7 @@ FunctionDescriptionProvider.props = {
20757
20782
  argToFocus: Number,
20758
20783
  };
20759
20784
 
20760
- const functions$2 = functionRegistry.content;
20785
+ const functions$3 = functionRegistry.content;
20761
20786
  const ASSISTANT_WIDTH = 300;
20762
20787
  const selectionIndicatorClass = "selector-flag";
20763
20788
  const selectionIndicatorColor = "#a9a9a9";
@@ -21259,7 +21284,7 @@ class Composer extends Component {
21259
21284
  // initialize Formula Assistant
21260
21285
  const tokenContext = tokenAtCursor.functionContext;
21261
21286
  const parentFunction = tokenContext.parent.toUpperCase();
21262
- const description = functions$2[parentFunction];
21287
+ const description = functions$3[parentFunction];
21263
21288
  const argPosition = tokenContext.argPosition;
21264
21289
  this.functionDescriptionState.functionName = parentFunction;
21265
21290
  this.functionDescriptionState.functionDescription = description;
@@ -22130,6 +22155,21 @@ FiguresContainer.props = {
22130
22155
  onFigureDeleted: Function,
22131
22156
  };
22132
22157
 
22158
+ /**
22159
+ * Manages an event listener on a ref. Useful for hooks that want to manage
22160
+ * event listeners, especially more than one. Prefer using t-on directly in
22161
+ * components. If your hook only needs a single event listener, consider simply
22162
+ * returning it from the hook and letting the user attach it with t-on.
22163
+ *
22164
+ * Adapted from Odoo Community - See https://github.com/odoo/odoo/blob/saas-16.2/addons/web/static/src/core/utils/hooks.js
22165
+ */
22166
+ function useRefListener(ref, ...listener) {
22167
+ useEffect((el) => {
22168
+ el?.addEventListener(...listener);
22169
+ return () => el?.removeEventListener(...listener);
22170
+ }, () => [ref.el]);
22171
+ }
22172
+
22133
22173
  /**
22134
22174
  * Repeatedly calls a callback function with a time delay between calls.
22135
22175
  */
@@ -22192,20 +22232,10 @@ function useCellHovered(env, gridRef, callback) {
22192
22232
  setPosition(undefined, undefined);
22193
22233
  }
22194
22234
  }
22195
- onMounted(() => {
22196
- const grid = gridRef.el;
22197
- grid.addEventListener("mousemove", updateMousePosition);
22198
- grid.addEventListener("mouseleave", pause);
22199
- grid.addEventListener("mouseenter", resume);
22200
- grid.addEventListener("mousedown", recompute);
22201
- });
22202
- onWillUnmount(() => {
22203
- const grid = gridRef.el;
22204
- grid.removeEventListener("mousemove", updateMousePosition);
22205
- grid.removeEventListener("mouseleave", pause);
22206
- grid.removeEventListener("mouseenter", resume);
22207
- grid.removeEventListener("mousedown", recompute);
22208
- });
22235
+ useRefListener(gridRef, "mousemove", updateMousePosition);
22236
+ useRefListener(gridRef, "mouseleave", pause);
22237
+ useRefListener(gridRef, "mouseenter", resume);
22238
+ useRefListener(gridRef, "mousedown", recompute);
22209
22239
  function setPosition(col, row) {
22210
22240
  if (col !== hoveredPosition.col || row !== hoveredPosition.row) {
22211
22241
  hoveredPosition.col = col;
@@ -22244,16 +22274,9 @@ function useTouchMove(gridRef, handler, canMoveUp) {
22244
22274
  x = currentX;
22245
22275
  y = currentY;
22246
22276
  }
22247
- onMounted(() => {
22248
- gridRef.el.addEventListener("touchstart", onTouchStart);
22249
- gridRef.el.addEventListener("touchend", onTouchEnd);
22250
- gridRef.el.addEventListener("touchmove", onTouchMove);
22251
- });
22252
- onWillUnmount(() => {
22253
- gridRef.el.removeEventListener("touchstart", onTouchStart);
22254
- gridRef.el.removeEventListener("touchend", onTouchEnd);
22255
- gridRef.el.removeEventListener("touchmove", onTouchMove);
22256
- });
22277
+ useRefListener(gridRef, "touchstart", onTouchStart);
22278
+ useRefListener(gridRef, "touchend", onTouchEnd);
22279
+ useRefListener(gridRef, "touchmove", onTouchMove);
22257
22280
  }
22258
22281
  class GridOverlay extends Component {
22259
22282
  static template = "o-spreadsheet-GridOverlay";
@@ -25104,7 +25127,7 @@ function indentCode(code) {
25104
25127
  * is useful for the composer, which needs to be able to work with incomplete
25105
25128
  * formulas.
25106
25129
  */
25107
- const functions$1 = functionRegistry.content;
25130
+ const functions$2 = functionRegistry.content;
25108
25131
  const POSTFIX_UNARY_OPERATORS = ["%"];
25109
25132
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
25110
25133
  function tokenize(str) {
@@ -25227,7 +25250,7 @@ function tokenizeSymbol(chars) {
25227
25250
  }
25228
25251
  if (result.length) {
25229
25252
  const value = result;
25230
- const isFunction = value.toUpperCase() in functions$1;
25253
+ const isFunction = value.toUpperCase() in functions$2;
25231
25254
  if (isFunction) {
25232
25255
  return { type: "FUNCTION", value };
25233
25256
  }
@@ -25697,7 +25720,7 @@ function rangeTokenize(formula) {
25697
25720
  return result;
25698
25721
  }
25699
25722
 
25700
- const functions = functionRegistry.content;
25723
+ const functions$1 = functionRegistry.content;
25701
25724
  const OPERATOR_MAP = {
25702
25725
  "=": "EQ",
25703
25726
  "+": "ADD",
@@ -25760,56 +25783,32 @@ function compile(formula) {
25760
25783
  * between a cell value and a non cell value.
25761
25784
  */
25762
25785
  function compileFunctionArgs(ast) {
25763
- const functionDefinition = functions[ast.value.toUpperCase()];
25764
- const currentFunctionArguments = ast.args;
25765
- // check if arguments are supplied in the correct quantities
25766
- const nbrArg = currentFunctionArguments.length;
25767
- if (nbrArg < functionDefinition.minArgRequired) {
25768
- throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected %s minimum, but got %s instead.", ast.value.toUpperCase(), functionDefinition.minArgRequired.toString(), nbrArg.toString()));
25769
- }
25770
- if (nbrArg > functionDefinition.maxArgPossible) {
25771
- throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected %s maximum, but got %s instead.", ast.value.toUpperCase(), functionDefinition.maxArgPossible.toString(), nbrArg.toString()));
25772
- }
25773
- const repeatingArg = functionDefinition.nbrArgRepeating;
25774
- if (repeatingArg > 1) {
25775
- const argBeforeRepeat = functionDefinition.args.length - repeatingArg;
25776
- const nbrRepeatingArg = nbrArg - argBeforeRepeat;
25777
- if (nbrRepeatingArg % repeatingArg !== 0) {
25778
- throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected all arguments after position %s to be supplied by groups of %s arguments", ast.value.toUpperCase(), argBeforeRepeat.toString(), repeatingArg.toString()));
25779
- }
25780
- }
25781
- let compiledArgs = [];
25782
- for (let i = 0; i < nbrArg; i++) {
25783
- const argPosition = functionDefinition.getArgToFocus(i + 1) - 1;
25784
- if (0 <= argPosition && argPosition < functionDefinition.args.length) {
25785
- const currentArg = currentFunctionArguments[i];
25786
- const argDefinition = functionDefinition.args[argPosition];
25787
- const argTypes = argDefinition.type || [];
25788
- // detect when an argument need to be evaluated as a meta argument
25789
- const isMeta = argTypes.includes("META");
25790
- // detect when an argument need to be evaluated as a lazy argument
25791
- const isLazy = argDefinition.lazy;
25792
- const hasRange = argTypes.some((t) => t === "RANGE" ||
25793
- t === "RANGE<BOOLEAN>" ||
25794
- t === "RANGE<DATE>" ||
25795
- t === "RANGE<NUMBER>" ||
25796
- t === "RANGE<STRING>");
25797
- const isRangeOnly = argTypes.every((t) => t === "RANGE" ||
25798
- t === "RANGE<BOOLEAN>" ||
25799
- t === "RANGE<DATE>" ||
25800
- t === "RANGE<NUMBER>" ||
25801
- t === "RANGE<STRING>");
25802
- if (isRangeOnly) {
25803
- if (currentArg.type !== "REFERENCE") {
25804
- throw new BadExpressionError(_lt("Function %s expects the parameter %s to be reference to a cell or range, not a %s.", ast.value.toUpperCase(), (i + 1).toString(), currentArg.type.toLowerCase()));
25805
- }
25786
+ const { args } = ast;
25787
+ const functionName = ast.value.toUpperCase();
25788
+ const functionDefinition = functions$1[functionName];
25789
+ assertEnoughArgs(ast);
25790
+ const compiledArgs = [];
25791
+ for (let i = 0; i < args.length; i++) {
25792
+ const argToFocus = functionDefinition.getArgToFocus(i + 1) - 1;
25793
+ const argDefinition = functionDefinition.args[argToFocus];
25794
+ const currentArg = args[i];
25795
+ const argTypes = argDefinition.type || [];
25796
+ // detect when an argument need to be evaluated as a meta argument
25797
+ const isMeta = argTypes.includes("META");
25798
+ // detect when an argument need to be evaluated as a lazy argument
25799
+ const isLazy = argDefinition.lazy;
25800
+ const hasRange = argTypes.some((t) => isRangeType(t));
25801
+ const isRangeOnly = argTypes.every((t) => isRangeType(t));
25802
+ if (isRangeOnly) {
25803
+ if (!isRangeInput(currentArg)) {
25804
+ throw new BadExpressionError(_lt("Function %s expects the parameter %s to be reference to a cell or range, not a %s.", functionName, (i + 1).toString(), currentArg.type.toLowerCase()));
25806
25805
  }
25807
- const compiledAST = compileAST(currentArg, isMeta, hasRange, {
25808
- functionName: ast.value.toUpperCase(),
25809
- paramIndex: i + 1,
25810
- });
25811
- compiledArgs.push(isLazy ? compiledAST.wrapInClosure() : compiledAST);
25812
25806
  }
25807
+ const compiledAST = compileAST(currentArg, isMeta, hasRange, {
25808
+ functionName,
25809
+ paramIndex: i + 1,
25810
+ });
25811
+ compiledArgs.push(isLazy ? compiledAST.wrapInClosure() : compiledAST);
25813
25812
  }
25814
25813
  return compiledArgs;
25815
25814
  }
@@ -25953,6 +25952,41 @@ function formulaArguments(tokens) {
25953
25952
  dependencies,
25954
25953
  constantValues,
25955
25954
  };
25955
+ }
25956
+ /**
25957
+ * Check if arguments are supplied in the correct quantities
25958
+ */
25959
+ function assertEnoughArgs(ast) {
25960
+ const nbrArg = ast.args.length;
25961
+ const functionName = ast.value.toUpperCase();
25962
+ const functionDefinition = functions$1[functionName];
25963
+ if (nbrArg < functionDefinition.minArgRequired) {
25964
+ throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected %s minimum, but got %s instead.", functionName, functionDefinition.minArgRequired.toString(), nbrArg.toString()));
25965
+ }
25966
+ if (nbrArg > functionDefinition.maxArgPossible) {
25967
+ throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected %s maximum, but got %s instead.", functionName, functionDefinition.maxArgPossible.toString(), nbrArg.toString()));
25968
+ }
25969
+ const repeatableArgs = functionDefinition.nbrArgRepeating;
25970
+ if (repeatableArgs > 1) {
25971
+ const unrepeatableArgs = functionDefinition.args.length - repeatableArgs;
25972
+ const repeatingArgs = nbrArg - unrepeatableArgs;
25973
+ if (repeatingArgs % repeatableArgs !== 0) {
25974
+ throw new BadExpressionError(_lt("Invalid number of arguments for the %s function. Expected all arguments after position %s to be supplied by groups of %s arguments", functionName, unrepeatableArgs.toString(), repeatableArgs.toString()));
25975
+ }
25976
+ }
25977
+ }
25978
+ function isRangeType(type) {
25979
+ return type.startsWith("RANGE");
25980
+ }
25981
+ function isRangeInput(arg) {
25982
+ if (arg.type === "REFERENCE") {
25983
+ return true;
25984
+ }
25985
+ if (arg.type === "FUNCALL") {
25986
+ const fnDef = functions$1[arg.value.toUpperCase()];
25987
+ return fnDef && isRangeType(fnDef.returns[0]);
25988
+ }
25989
+ return false;
25956
25990
  }
25957
25991
 
25958
25992
  /**
@@ -26069,16 +26103,6 @@ function convertOperator(operator) {
26069
26103
  // -------------------------------------
26070
26104
  // WORKSHEET HELPERS
26071
26105
  // -------------------------------------
26072
- function getCellType(value) {
26073
- switch (typeof value) {
26074
- case "boolean":
26075
- return "b";
26076
- case "string":
26077
- return "str";
26078
- case "number":
26079
- return "n";
26080
- }
26081
- }
26082
26106
  function convertHeightToExcel(height) {
26083
26107
  return Math.round(HEIGHT_FACTOR * height * 100) / 100;
26084
26108
  }
@@ -29765,23 +29789,7 @@ class CellPlugin extends CorePlugin {
29765
29789
  */
29766
29790
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
29767
29791
  const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
29768
- const buildFormulaContent = this.buildFormulaContent.bind(this);
29769
- // Only for formulas with dependencies because
29770
- // **the closure is expensive memory-wise**
29771
- return {
29772
- id,
29773
- get content() {
29774
- return buildFormulaContent(sheetId, {
29775
- dependencies: this.dependencies,
29776
- compiledFormula: this.compiledFormula,
29777
- });
29778
- },
29779
- style,
29780
- format,
29781
- isFormula: true,
29782
- compiledFormula,
29783
- dependencies,
29784
- };
29792
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.buildFormulaContent.bind(this));
29785
29793
  }
29786
29794
  createErrorFormula(id, content, format, style, error) {
29787
29795
  return {
@@ -29807,6 +29815,31 @@ class CellPlugin extends CorePlugin {
29807
29815
  const sheetZone = this.getters.getSheetZone(sheetId);
29808
29816
  return isInside(col, row, sheetZone) ? 0 /* CommandResult.Success */ : 18 /* CommandResult.TargetOutOfSheet */;
29809
29817
  }
29818
+ }
29819
+ class FormulaCellWithDependencies {
29820
+ id;
29821
+ compiledFormula;
29822
+ format;
29823
+ style;
29824
+ dependencies;
29825
+ sheetId;
29826
+ buildFormulaContent;
29827
+ isFormula = true;
29828
+ constructor(id, compiledFormula, format, style, dependencies, sheetId, buildFormulaContent) {
29829
+ this.id = id;
29830
+ this.compiledFormula = compiledFormula;
29831
+ this.format = format;
29832
+ this.style = style;
29833
+ this.dependencies = dependencies;
29834
+ this.sheetId = sheetId;
29835
+ this.buildFormulaContent = buildFormulaContent;
29836
+ }
29837
+ get content() {
29838
+ return this.buildFormulaContent(this.sheetId, {
29839
+ dependencies: this.dependencies,
29840
+ compiledFormula: this.compiledFormula,
29841
+ });
29842
+ }
29810
29843
  }
29811
29844
 
29812
29845
  class ChartPlugin extends CorePlugin {
@@ -33098,6 +33131,931 @@ class UIPlugin extends BasePlugin {
33098
33131
  drawGrid(ctx, layer) { }
33099
33132
  }
33100
33133
 
33134
+ const functionMap = functionRegistry.mapping;
33135
+ /**
33136
+ * Return all functions necessary to properly evaluate a formula:
33137
+ * - a refFn function to read any reference, cell or range of a normalized formula
33138
+ * - a range function to convert any reference to a proper value array
33139
+ * - an evaluation context
33140
+ */
33141
+ function buildCompilationParameters(context, getters, computeCell) {
33142
+ const builder = new CompilationParametersBuilder(context, getters, computeCell);
33143
+ return builder.getParameters();
33144
+ }
33145
+ class CompilationParametersBuilder {
33146
+ getters;
33147
+ computeCell;
33148
+ evalContext;
33149
+ constructor(context, getters, computeCell) {
33150
+ this.getters = getters;
33151
+ this.computeCell = computeCell;
33152
+ this.evalContext = Object.assign(Object.create(functionMap), context, {
33153
+ getters: this.getters,
33154
+ });
33155
+ }
33156
+ getParameters() {
33157
+ return [this.refFn.bind(this), this.range.bind(this), this.evalContext];
33158
+ }
33159
+ /**
33160
+ * Returns the value of the cell(s) used in reference
33161
+ *
33162
+ * @param range the references used
33163
+ * @param isMeta if a reference is supposed to be used in a `meta` parameter as described in the
33164
+ * function for which this parameter is used, we just return the string of the parameter.
33165
+ * The `compute` of the formula's function must process it completely
33166
+ */
33167
+ refFn(range, isMeta, functionName, paramNumber) {
33168
+ if (isMeta) {
33169
+ // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
33170
+ return { value: zoneToXc(range.zone) };
33171
+ }
33172
+ if (!isZoneValid(range.zone)) {
33173
+ throw new InvalidReferenceError();
33174
+ }
33175
+ // if the formula definition could have accepted a range, we would pass through the _range function and not here
33176
+ if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {
33177
+ throw new Error(paramNumber
33178
+ ? _lt("Function %s expects the parameter %s to be a single value or a single cell reference, not a range.", functionName.toString(), paramNumber.toString())
33179
+ : _lt("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
33180
+ }
33181
+ if (range.invalidSheetName) {
33182
+ throw new Error(_lt("Invalid sheet name: %s", range.invalidSheetName));
33183
+ }
33184
+ return this.readCell(range);
33185
+ }
33186
+ readCell(range) {
33187
+ if (!this.getters.tryGetSheet(range.sheetId)) {
33188
+ throw new Error(_lt("Invalid sheet name"));
33189
+ }
33190
+ const position = { sheetId: range.sheetId, col: range.zone.left, row: range.zone.top };
33191
+ const evaluatedCell = this.getEvaluatedCellIfNotEmpty(position);
33192
+ if (evaluatedCell === undefined) {
33193
+ return { value: null, format: this.getters.getCell(position)?.format };
33194
+ }
33195
+ return evaluatedCell;
33196
+ }
33197
+ getEvaluatedCellIfNotEmpty(position) {
33198
+ const evaluatedCell = this.getEvaluatedCell(position);
33199
+ if (evaluatedCell.type === CellValueType.empty) {
33200
+ const cell = this.getters.getCell(position);
33201
+ if (!cell || cell.content === "") {
33202
+ return undefined;
33203
+ }
33204
+ }
33205
+ return evaluatedCell;
33206
+ }
33207
+ getEvaluatedCell(position) {
33208
+ const evaluatedCell = this.computeCell(position);
33209
+ if (evaluatedCell.type === CellValueType.error) {
33210
+ throw evaluatedCell.error;
33211
+ }
33212
+ return evaluatedCell;
33213
+ }
33214
+ /**
33215
+ * Return the values of the cell(s) used in reference, but always in the format of a range even
33216
+ * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
33217
+ * range<number> etc.
33218
+ *
33219
+ * Note that each col is possibly sparse: it only contain the values of cells
33220
+ * that are actually present in the grid.
33221
+ */
33222
+ range({ sheetId, zone }) {
33223
+ if (!isZoneValid(zone)) {
33224
+ throw new InvalidReferenceError();
33225
+ }
33226
+ // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
33227
+ // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
33228
+ const sheetZone = this.getters.getSheetZone(sheetId);
33229
+ const _zone = intersection(zone, sheetZone);
33230
+ if (!_zone) {
33231
+ return { value: [[]], format: [[]] };
33232
+ }
33233
+ const height = _zone.bottom - _zone.top + 1;
33234
+ const width = _zone.right - _zone.left + 1;
33235
+ const value = Array.from({ length: width }, () => Array.from({ length: height }));
33236
+ const format = Array.from({ length: width }, () => Array.from({ length: height }));
33237
+ // Performance issue: nested loop is faster than a map here
33238
+ for (let col = _zone.left; col <= _zone.right; col++) {
33239
+ for (let row = _zone.top; row <= _zone.bottom; row++) {
33240
+ const evaluatedCell = this.getEvaluatedCellIfNotEmpty({ sheetId: sheetId, col, row });
33241
+ if (evaluatedCell) {
33242
+ const colIndex = col - _zone.left;
33243
+ const rowIndex = row - _zone.top;
33244
+ value[colIndex][rowIndex] = evaluatedCell.value;
33245
+ if (evaluatedCell.format !== undefined) {
33246
+ format[colIndex][rowIndex] = evaluatedCell.format;
33247
+ }
33248
+ }
33249
+ }
33250
+ }
33251
+ return { value, format };
33252
+ }
33253
+ }
33254
+
33255
+ /**
33256
+ * This class is an implementation of a dependency Graph.
33257
+ * The graph is used to evaluate the cells in the correct
33258
+ * order, and should be updated each time a cell's content is modified
33259
+ *
33260
+ */
33261
+ class FormulaDependencyGraph {
33262
+ /**
33263
+ * Internal structure:
33264
+ * - key: a cell position (encoded as an integer)
33265
+ * - value: a set of cell positions that depends on the key
33266
+ *
33267
+ * Given
33268
+ * - A1:"= B1 + SQRT(B2)"
33269
+ * - C1:"= B1";
33270
+ * - C2:"= C1"
33271
+ *
33272
+ * we will have something like:
33273
+ * - B1 ---> (A1, C1) meaning A1 and C1 depends on B1
33274
+ * - B2 ---> (A1) meaning A1 depends on B2
33275
+ * - C1 ---> (C2) meaning C2 depends on C1
33276
+ */
33277
+ inverseDependencies = new Map();
33278
+ dependencies = new Map();
33279
+ removeAllDependencies(formulaPositionId) {
33280
+ const dependencies = this.dependencies.get(formulaPositionId);
33281
+ if (!dependencies) {
33282
+ return;
33283
+ }
33284
+ for (const dependency of dependencies) {
33285
+ this.inverseDependencies.get(dependency)?.delete(formulaPositionId);
33286
+ }
33287
+ this.dependencies.delete(formulaPositionId);
33288
+ }
33289
+ addDependencies(formulaPositionId, dependencies) {
33290
+ for (const dependency of dependencies) {
33291
+ const inverseDependencies = this.inverseDependencies.get(dependency);
33292
+ if (inverseDependencies) {
33293
+ inverseDependencies.add(formulaPositionId);
33294
+ }
33295
+ else {
33296
+ this.inverseDependencies.set(dependency, new Set([formulaPositionId]));
33297
+ }
33298
+ }
33299
+ const existingDependencies = this.dependencies.get(formulaPositionId);
33300
+ if (existingDependencies) {
33301
+ existingDependencies.push(...dependencies);
33302
+ }
33303
+ else {
33304
+ this.dependencies.set(formulaPositionId, dependencies);
33305
+ }
33306
+ }
33307
+ /**
33308
+ * Return the cell and all cells that depend on it,
33309
+ * in the correct order they should be evaluated.
33310
+ * This is called a topological ordering (excluding cycles)
33311
+ */
33312
+ getCellsDependingOn(positionIds) {
33313
+ const visited = new JetSet();
33314
+ const queue = Array.from(positionIds);
33315
+ while (queue.length > 0) {
33316
+ const node = queue.shift();
33317
+ visited.add(node);
33318
+ const adjacentNodes = this.inverseDependencies.get(node) || new Set();
33319
+ for (const adjacentNode of adjacentNodes) {
33320
+ if (!visited.has(adjacentNode)) {
33321
+ queue.push(adjacentNode);
33322
+ }
33323
+ }
33324
+ }
33325
+ visited.delete(...positionIds);
33326
+ return visited;
33327
+ }
33328
+ }
33329
+
33330
+ /**
33331
+ * Contains, for each cell, the array
33332
+ * formulas that could potentially spread on it
33333
+ * This is essentially a two way mapping between array formulas
33334
+ * and their results.
33335
+ *
33336
+ * As we don't allow two array formulas to spread on the same cell, this structure
33337
+ * is used to force the reevaluation of the potential spreaders of a cell when the
33338
+ * content of this cell is modified. This structure should be updated each time
33339
+ * an array formula is evaluated and try to spread on another cell.
33340
+ *
33341
+ */
33342
+ class SpreadingRelation {
33343
+ /**
33344
+ * Internal structure:
33345
+ * For something like
33346
+ * - A2:'=SPLIT("KAYAK", "A")'
33347
+ * - B1:'=TRANSPOSE(SPLIT("COYOTE", "O"))'
33348
+ *
33349
+ * Resulting in:
33350
+ * ```
33351
+ * -----------------
33352
+ * | | A | B | C |
33353
+ * |---+---+---+---|
33354
+ * | 1 | | C | |
33355
+ * | 2 | K | Y | K |
33356
+ * | 3 | | T | |
33357
+ * | 4 | | E | |
33358
+ * -----------------
33359
+ * ```
33360
+ * We will have `resultsToArrayFormulas` looking like:
33361
+ * - (B2) --> (A2, B1) meaning B2 can be the result of A2 OR B1
33362
+ * - (C2) --> (A2) meaning C2 is the result of A2
33363
+ * - (B3) --> (B1) meaning B3 is the result of B1
33364
+ * - (B4) --> (B1) meaning B4 is the result of B1
33365
+ *
33366
+ * We will have `arrayFormulasToResults` looking like:
33367
+ * - (A2) --> (B2, C2) meaning A2 spreads on B2 and C2
33368
+ * - (B1) --> (B2, B3, B4) meaning B1 spreads on B2, B3 and B4
33369
+ *
33370
+ */
33371
+ resultsToArrayFormulas = new Map();
33372
+ arrayFormulasToResults = new Map();
33373
+ getFormulaPositionsSpreadingOn(resultPositionId) {
33374
+ return this.resultsToArrayFormulas.get(resultPositionId) || EMPTY_ARRAY;
33375
+ }
33376
+ getArrayResultPositionIds(formulasPositionId) {
33377
+ return this.arrayFormulasToResults.get(formulasPositionId) || EMPTY_ARRAY;
33378
+ }
33379
+ /**
33380
+ * Remove a node, also remove it from other nodes adjacency list
33381
+ */
33382
+ removeNode(positionId) {
33383
+ this.resultsToArrayFormulas.delete(positionId);
33384
+ this.arrayFormulasToResults.delete(positionId);
33385
+ }
33386
+ /**
33387
+ * Create a spreading relation between two cells
33388
+ */
33389
+ addRelation({ arrayFormulaPositionId, resultPositionId, }) {
33390
+ if (!this.resultsToArrayFormulas.has(resultPositionId)) {
33391
+ this.resultsToArrayFormulas.set(resultPositionId, new Set());
33392
+ }
33393
+ this.resultsToArrayFormulas.get(resultPositionId)?.add(arrayFormulaPositionId);
33394
+ if (!this.arrayFormulasToResults.has(arrayFormulaPositionId)) {
33395
+ this.arrayFormulasToResults.set(arrayFormulaPositionId, new Set());
33396
+ }
33397
+ this.arrayFormulasToResults.get(arrayFormulaPositionId)?.add(resultPositionId);
33398
+ }
33399
+ hasArrayFormulaResult(positionId) {
33400
+ return this.resultsToArrayFormulas.has(positionId);
33401
+ }
33402
+ isArrayFormula(positionId) {
33403
+ return this.arrayFormulasToResults.has(positionId);
33404
+ }
33405
+ }
33406
+ const EMPTY_ARRAY = [];
33407
+
33408
+ const MAX_ITERATION = 30;
33409
+ class Evaluator {
33410
+ getters;
33411
+ compilationParams;
33412
+ positionEncoder = new PositionBitsEncoder();
33413
+ evaluatedCells = new Map();
33414
+ formulaDependencies = lazy(new FormulaDependencyGraph());
33415
+ blockedArrayFormulas = new Set();
33416
+ spreadingRelations = new SpreadingRelation();
33417
+ constructor(context, getters) {
33418
+ this.getters = getters;
33419
+ this.compilationParams = buildCompilationParameters(context, getters, (position) => this.computeCell(this.encodePosition(position)));
33420
+ }
33421
+ getEvaluatedCell(position) {
33422
+ return this.evaluatedCells.get(this.encodePosition(position)) || createEvaluatedCell("");
33423
+ }
33424
+ getArrayFormulaSpreadingOn(position) {
33425
+ const positionId = this.encodePosition(position);
33426
+ const formulaPosition = this.getArrayFormulaSpreadingOnId(positionId);
33427
+ return formulaPosition !== undefined ? this.decodePosition(formulaPosition) : undefined;
33428
+ }
33429
+ getEvaluatedPositions() {
33430
+ return [...this.evaluatedCells.keys()].map(this.decodePosition.bind(this));
33431
+ }
33432
+ getArrayFormulaSpreadingOnId(positionId) {
33433
+ if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
33434
+ return undefined;
33435
+ }
33436
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
33437
+ return Array.from(arrayFormulas).find((positionId) => !this.blockedArrayFormulas.has(positionId));
33438
+ }
33439
+ updateDependencies(position) {
33440
+ const positionId = this.encodePosition(position);
33441
+ this.formulaDependencies().removeAllDependencies(positionId);
33442
+ const dependencies = this.getDirectDependencies(positionId);
33443
+ this.formulaDependencies().addDependencies(positionId, dependencies);
33444
+ }
33445
+ evaluateCells(positions) {
33446
+ const cells = positions.map(this.encodePosition.bind(this));
33447
+ const cellsToCompute = new JetSet(cells);
33448
+ const arrayFormulas = this.getArrayFormulasImpactedByChangesOf(cells);
33449
+ cellsToCompute.add(...this.getCellsDependingOn(cells));
33450
+ cellsToCompute.add(...arrayFormulas);
33451
+ cellsToCompute.add(...this.getCellsDependingOn(arrayFormulas));
33452
+ this.evaluate(cellsToCompute);
33453
+ }
33454
+ getArrayFormulasImpactedByChangesOf(positionIds) {
33455
+ const impactedPositionIds = new JetSet();
33456
+ for (const positionId of positionIds) {
33457
+ const content = this.getCell(positionId)?.content;
33458
+ const arrayFormulaPositionId = this.getArrayFormulaSpreadingOnId(positionId);
33459
+ if (arrayFormulaPositionId !== undefined) {
33460
+ // take into account new collisions.
33461
+ impactedPositionIds.add(arrayFormulaPositionId);
33462
+ }
33463
+ if (!content) {
33464
+ // The previous content could have blocked some array formulas
33465
+ impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
33466
+ }
33467
+ }
33468
+ return impactedPositionIds;
33469
+ }
33470
+ buildDependencyGraph() {
33471
+ this.blockedArrayFormulas = new Set();
33472
+ this.spreadingRelations = new SpreadingRelation();
33473
+ this.formulaDependencies = lazy(() => {
33474
+ const dependencyGraph = new FormulaDependencyGraph();
33475
+ for (const positionId of this.getAllCells()) {
33476
+ const dependencies = this.getDirectDependencies(positionId);
33477
+ dependencyGraph.addDependencies(positionId, dependencies);
33478
+ }
33479
+ return dependencyGraph;
33480
+ });
33481
+ }
33482
+ evaluateAllCells() {
33483
+ this.evaluatedCells = new Map();
33484
+ this.evaluate(this.getAllCells());
33485
+ }
33486
+ getAllCells() {
33487
+ const positionIds = new JetSet();
33488
+ for (const sheetId of this.getters.getSheetIds()) {
33489
+ const cellIds = this.getters.getCells(sheetId);
33490
+ for (const cellId in cellIds) {
33491
+ positionIds.add(this.encodePosition(this.getters.getCellPosition(cellId)));
33492
+ }
33493
+ }
33494
+ return positionIds;
33495
+ }
33496
+ getArrayFormulasBlockedByOrSpreadingOn(positionId) {
33497
+ if (!this.spreadingRelations.hasArrayFormulaResult(positionId)) {
33498
+ return [];
33499
+ }
33500
+ const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
33501
+ const cells = new JetSet(arrayFormulas);
33502
+ cells.add(...this.getCellsDependingOn(arrayFormulas));
33503
+ return cells;
33504
+ }
33505
+ nextPositionsToUpdate = new JetSet();
33506
+ cellsBeingComputed = new Set();
33507
+ evaluate(cells) {
33508
+ this.cellsBeingComputed = new Set();
33509
+ this.nextPositionsToUpdate = cells;
33510
+ let currentIteration = 0;
33511
+ while (this.nextPositionsToUpdate.size && currentIteration++ < MAX_ITERATION) {
33512
+ const positionIds = Array.from(this.nextPositionsToUpdate);
33513
+ this.nextPositionsToUpdate.clear();
33514
+ for (let i = 0; i < positionIds.length; ++i) {
33515
+ const cell = positionIds[i];
33516
+ this.evaluatedCells.delete(cell);
33517
+ }
33518
+ for (let i = 0; i < positionIds.length; ++i) {
33519
+ const cell = positionIds[i];
33520
+ this.setEvaluatedCell(cell, this.computeCell(cell));
33521
+ }
33522
+ }
33523
+ }
33524
+ setEvaluatedCell(positionId, evaluatedCell) {
33525
+ if (this.nextPositionsToUpdate.has(positionId)) {
33526
+ this.nextPositionsToUpdate.delete(positionId);
33527
+ }
33528
+ this.evaluatedCells.set(positionId, evaluatedCell);
33529
+ }
33530
+ computeCell(positionId) {
33531
+ const evaluation = this.evaluatedCells.get(positionId);
33532
+ if (evaluation) {
33533
+ return evaluation; // already computed
33534
+ }
33535
+ if (!this.blockedArrayFormulas.has(positionId)) {
33536
+ this.invalidateSpreading(positionId);
33537
+ }
33538
+ const cell = this.getCell(positionId);
33539
+ if (cell === undefined) {
33540
+ return createEvaluatedCell("");
33541
+ }
33542
+ const cellId = cell.id;
33543
+ try {
33544
+ if (this.cellsBeingComputed.has(cellId)) {
33545
+ throw new CircularDependencyError();
33546
+ }
33547
+ this.cellsBeingComputed.add(cellId);
33548
+ return cell.isFormula
33549
+ ? this.computeFormulaCell(cell)
33550
+ : evaluateLiteral(cell.content, cell.format);
33551
+ }
33552
+ catch (e) {
33553
+ return this.handleError(e, cell);
33554
+ }
33555
+ finally {
33556
+ this.cellsBeingComputed.delete(cellId);
33557
+ }
33558
+ }
33559
+ handleError(e, cell) {
33560
+ if (!(e instanceof Error)) {
33561
+ e = new Error(e);
33562
+ }
33563
+ const msg = e?.errorType || CellErrorType.GenericError;
33564
+ // apply function name
33565
+ const __lastFnCalled = this.compilationParams[2].__lastFnCalled || "";
33566
+ const error = new EvaluationError(msg, e.message.replace("[[FUNCTION_NAME]]", __lastFnCalled), e.logLevel !== undefined ? e.logLevel : CellErrorLevel.error);
33567
+ return errorCell(cell.content, error);
33568
+ }
33569
+ computeFormulaCell(cellData) {
33570
+ const cellId = cellData.id;
33571
+ this.compilationParams[2].__originCellXC = () => {
33572
+ // compute the value lazily for performance reasons
33573
+ const position = this.compilationParams[2].getters.getCellPosition(cellId);
33574
+ return toXC(position.col, position.row);
33575
+ };
33576
+ const formulaReturn = cellData.compiledFormula.execute(cellData.dependencies, ...this.compilationParams);
33577
+ assertFormulaReturnHasConsistentDimensions(formulaReturn);
33578
+ const { value: computedValue, format: computedFormat } = formulaReturn;
33579
+ if (!isMatrix(computedValue)) {
33580
+ return createEvaluatedCell(computedValue, cellData.format || computedFormat);
33581
+ }
33582
+ const formulaPosition = this.getters.getCellPosition(cellId);
33583
+ this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, computedValue);
33584
+ forEachSpreadPositionInMatrix(computedValue, this.updateSpreadRelation(formulaPosition));
33585
+ forEachSpreadPositionInMatrix(computedValue, this.checkCollision(formulaPosition));
33586
+ forEachSpreadPositionInMatrix(computedValue,
33587
+ // due the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
33588
+ this.spreadValues(formulaPosition, formulaReturn));
33589
+ const formatFromPosition = formatFromPositionAccess(computedFormat);
33590
+ return createEvaluatedCell(computedValue[0][0], cellData.format || formatFromPosition(0, 0));
33591
+ }
33592
+ assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
33593
+ const numberOfCols = this.getters.getNumberCols(sheetId);
33594
+ const numberOfRows = this.getters.getNumberRows(sheetId);
33595
+ const enoughCols = col + matrixResult.length <= numberOfCols;
33596
+ const enoughRows = row + matrixResult[0].length <= numberOfRows;
33597
+ if (enoughCols && enoughRows) {
33598
+ return;
33599
+ }
33600
+ if (enoughCols) {
33601
+ throw new Error(_lt("Result couldn't be automatically expanded. Please insert more rows."));
33602
+ }
33603
+ if (enoughRows) {
33604
+ throw new Error(_lt("Result couldn't be automatically expanded. Please insert more columns."));
33605
+ }
33606
+ throw new Error(_lt("Result couldn't be automatically expanded. Please insert more columns and rows."));
33607
+ }
33608
+ updateSpreadRelation({ sheetId, col, row, }) {
33609
+ const arrayFormulaPositionId = this.encodePosition({ sheetId, col, row });
33610
+ return (i, j) => {
33611
+ const position = { sheetId, col: i + col, row: j + row };
33612
+ const resultPositionId = this.encodePosition(position);
33613
+ this.spreadingRelations.addRelation({ resultPositionId, arrayFormulaPositionId });
33614
+ };
33615
+ }
33616
+ checkCollision({ sheetId, col, row }) {
33617
+ const formulaPositionId = this.encodePosition({ sheetId, col, row });
33618
+ return (i, j) => {
33619
+ const position = { sheetId: sheetId, col: i + col, row: j + row };
33620
+ const rawCell = this.getters.getCell(position);
33621
+ if (rawCell?.content ||
33622
+ this.getters.getEvaluatedCell(position).type !== CellValueType.empty) {
33623
+ this.blockedArrayFormulas.add(formulaPositionId);
33624
+ throw new Error(_lt("Array result was not expanded because it would overwrite data in %s.", toXC(position.col, position.row)));
33625
+ }
33626
+ this.blockedArrayFormulas.delete(formulaPositionId);
33627
+ };
33628
+ }
33629
+ spreadValues({ sheetId, col, row }, matrixResult) {
33630
+ const formatFromPosition = formatFromPositionAccess(matrixResult.format);
33631
+ return (i, j) => {
33632
+ const position = { sheetId, col: i + col, row: j + row };
33633
+ const cell = this.getters.getCell(position);
33634
+ const format = cell?.format;
33635
+ const evaluatedCell = createEvaluatedCell(matrixResult.value[i][j], format || formatFromPosition(i, j));
33636
+ const positionId = this.encodePosition(position);
33637
+ this.setEvaluatedCell(positionId, evaluatedCell);
33638
+ // check if formula dependencies present in the spread zone
33639
+ // if so, they need to be recomputed
33640
+ this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
33641
+ };
33642
+ }
33643
+ invalidateSpreading(positionId) {
33644
+ if (!this.spreadingRelations.isArrayFormula(positionId)) {
33645
+ return;
33646
+ }
33647
+ for (const child of this.spreadingRelations.getArrayResultPositionIds(positionId)) {
33648
+ const content = this.getCell(child)?.content;
33649
+ if (content) {
33650
+ // there's no point at re-evaluating overlapping array formulas,
33651
+ // there's still a collision
33652
+ continue;
33653
+ }
33654
+ this.evaluatedCells.delete(child);
33655
+ this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
33656
+ this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
33657
+ }
33658
+ this.spreadingRelations.removeNode(positionId);
33659
+ }
33660
+ // ----------------------------------------------------------
33661
+ // COMMON FUNCTIONALITY
33662
+ // ----------------------------------------------------------
33663
+ getDirectDependencies(positionId) {
33664
+ const cell = this.getCell(positionId);
33665
+ if (!cell?.isFormula) {
33666
+ return [];
33667
+ }
33668
+ const dependencies = [];
33669
+ for (const range of cell.dependencies) {
33670
+ if (range.invalidSheetName || range.invalidXc) {
33671
+ continue;
33672
+ }
33673
+ const sheetId = range.sheetId;
33674
+ forEachPositionsInZone(range.zone, (col, row) => {
33675
+ dependencies.push(this.encodePosition({ sheetId, col, row }));
33676
+ });
33677
+ }
33678
+ return dependencies;
33679
+ }
33680
+ getCellsDependingOn(positionIds) {
33681
+ return this.formulaDependencies().getCellsDependingOn(positionIds);
33682
+ }
33683
+ getCell(positionId) {
33684
+ return this.getters.getCell(this.decodePosition(positionId));
33685
+ }
33686
+ encodePosition(position) {
33687
+ return this.positionEncoder.encode(position);
33688
+ }
33689
+ decodePosition(positionId) {
33690
+ return this.positionEncoder.decode(positionId);
33691
+ }
33692
+ }
33693
+ function forEachSpreadPositionInMatrix(matrix, callback) {
33694
+ for (let i = 0; i < matrix.length; ++i) {
33695
+ for (let j = 0; j < matrix[i].length; ++j) {
33696
+ if (i === 0 && j === 0) {
33697
+ continue;
33698
+ }
33699
+ callback(i, j);
33700
+ }
33701
+ }
33702
+ }
33703
+ function formatFromPositionAccess(format) {
33704
+ return isMatrix(format) ? (i, j) => format[i][j] : () => format;
33705
+ }
33706
+ function assertFormulaReturnHasConsistentDimensions(formulaReturn) {
33707
+ const { value: computedValue, format: computedFormat } = formulaReturn;
33708
+ if (!isMatrix(computedValue)) {
33709
+ if (isMatrix(computedFormat)) {
33710
+ throw new Error("A format matrix should never be associated with a scalar value");
33711
+ }
33712
+ return;
33713
+ }
33714
+ if (isMatrix(computedFormat)) {
33715
+ const sameDimensions = computedValue.length === computedFormat.length &&
33716
+ computedValue[0].length === computedFormat[0].length;
33717
+ if (!sameDimensions) {
33718
+ throw new Error("Formats and values should have the same dimensions!");
33719
+ }
33720
+ }
33721
+ }
33722
+ /**
33723
+ * Encode (and decode) cell positions { sheetId, col, row }
33724
+ * to a single integer.
33725
+ *
33726
+ * `col` and `row` values are encoded on 21 bits each (max 2^21 = 2_097_152),
33727
+ * An incremental integer id is assigned to each different sheet id, starting at 0.
33728
+ *
33729
+ * e.g.
33730
+ * Given { col: 10, row: 4, sheetId: "abcde" }
33731
+ * we have:
33732
+ * - row "4" encoded on 21 bits: 000000000000000000100
33733
+ * - col "10" encoded on 21 bits: 000000000000000001010
33734
+ * - sheetId: let's say it's the 4th sheetId met, encoded to: 11
33735
+ *
33736
+ * The final encoded value is found by concatenating the 3 bit sequences:
33737
+ *
33738
+ * sheetId: 11
33739
+ * col: 000000000000000001010
33740
+ * row: 000000000000000000100
33741
+ * => 11000000000000000001010000000000000000000100
33742
+ *
33743
+ * this binary sequence is the integer 13194160504836
33744
+ */
33745
+ class PositionBitsEncoder {
33746
+ sheetMapping = {};
33747
+ inverseSheetMapping = new Map();
33748
+ constructor() {
33749
+ try {
33750
+ // @ts-ignore
33751
+ o_spreadsheet.__DEBUG__ = o_spreadsheet.__DEBUG__ || {};
33752
+ // @ts-ignore
33753
+ o_spreadsheet.__DEBUG__.decodePosition = this.decode.bind(this);
33754
+ // @ts-ignore
33755
+ o_spreadsheet.__DEBUG__.encodePosition = this.encode.bind(this);
33756
+ }
33757
+ catch (error) { }
33758
+ }
33759
+ /**
33760
+ * Encode a cell position to a single integer.
33761
+ */
33762
+ encode({ sheetId, col, row }) {
33763
+ return (this.encodeSheet(sheetId) << 42n) | (BigInt(col) << 21n) | BigInt(row);
33764
+ }
33765
+ decode(id) {
33766
+ // keep only the last 21 bits by AND-ing the bit sequence with 21 ones
33767
+ const row = Number(id & 2097151n);
33768
+ const col = Number((id >> 21n) & 2097151n);
33769
+ const sheetId = this.decodeSheet(id >> 42n);
33770
+ return { sheetId, col, row };
33771
+ }
33772
+ encodeSheet(sheetId) {
33773
+ const sheetKey = this.sheetMapping[sheetId];
33774
+ if (sheetKey === undefined) {
33775
+ const newSheetKey = BigInt(Object.keys(this.sheetMapping).length);
33776
+ this.sheetMapping[sheetId] = newSheetKey;
33777
+ this.inverseSheetMapping.set(newSheetKey, sheetId);
33778
+ return newSheetKey;
33779
+ }
33780
+ return sheetKey;
33781
+ }
33782
+ decodeSheet(sheetKey) {
33783
+ const sheetId = this.inverseSheetMapping.get(sheetKey);
33784
+ if (sheetId === undefined) {
33785
+ throw new Error("Sheet id not found");
33786
+ }
33787
+ return sheetId;
33788
+ }
33789
+ }
33790
+
33791
+ const functions = functionRegistry.content;
33792
+ //#region
33793
+ // ---------------------------------------------------------------------------
33794
+ // INTRODUCTION
33795
+ // ---------------------------------------------------------------------------
33796
+ // The evaluation plugin is in charge of computing the values of the cells.
33797
+ // This is a fairly complex task for several reasons:
33798
+ // Reason n°1: Cells can contain formulas that must be interpreted to know
33799
+ // the final value of the cell. And these formulas can depend on other cells.
33800
+ // ex A1:"=SUM(B1:B2)" we have to evaluate B1:B2 first to be able to evaluate A1.
33801
+ // We say here that we have a 'formula dependency' between A1 and B1:B2.
33802
+ // Reason n°2: A cell can assign value to other cells that haven't content.
33803
+ // This concerns cells containing a formula that returns an array of values.
33804
+ // ex A1:"=SPLIT('Odoo','d')" Evaluating A1 must assign the value "O" to A1 and
33805
+ // "oo" to B1. We say here that we have a 'spread relation' between A1 and B1.
33806
+ // B1 have a spread value from A1.
33807
+ // Note that a cell can contain a formula which depends on other cells which
33808
+ // themselves can:
33809
+ // - contain formulas which depends on other cells (and so on).
33810
+ // - contain a spread value from other formulas which depends on other cells
33811
+ // (and so on).
33812
+ // I - How to build the evaluation ?
33813
+ // If we had only formulas dependencies to treat, the evaluation would be
33814
+ // simple: the formulas dependencies are directly deduced from the content
33815
+ // of the formulas. With the dependencies we are able to calculate which
33816
+ // formula must be evaluated before another.
33817
+ // Cycles
33818
+ // ------
33819
+ // We can also easily detect if the cells are included in reference cycles
33820
+ // and return an error in this case. ex: A1:"=B1" B1:"=C1" C1:"=A1"
33821
+ // The "#CYCLE" error must be returned for
33822
+ // all three cells.
33823
+ // But there's more! There are formulas referring to other cells but never
33824
+ // use them. This is the case for example
33825
+ // with the "IF" formula. ex:
33826
+ // A1:"=IF(D1,A2,B1)"
33827
+ // A2:"=A1"
33828
+ // In this case it is obvious that we have a cyclic dependency. But in practice
33829
+ // this will only exist if D1 is true.
33830
+ // For this reason, we believe that the evaluation should be **partly recursive**:
33831
+ // The function computing a formula cell starts by marking them as 'being evaluated'
33832
+ // and then call itself on the dependencies of the concerned cell. This allows
33833
+ // to evaluate the dependencies before the cell itself and to detect
33834
+ // if the cell that is being evaluated isn't part of a cycle.
33835
+ // II - The spread relation anticipation problem
33836
+ // The biggest difficulty to solve with the evaluation lies in the fact that
33837
+ // we cannot anticipate the spread relations: cells impacted by the result array
33838
+ // of a formula are only determined after the array formula has been
33839
+ // evaluated. In the case where the impacted cells are used in other formulas,
33840
+ // this will require to re-evaluation other formulas (and so on...). ex:
33841
+ // A1:"=B2"
33842
+ // A2:"=SPLIT('Odoo','d')"
33843
+ // in the example above, A2 spreads on B2, but we will know it only after
33844
+ // the evaluation of A2. To be able to evaluate A1 correctly, we must therefore
33845
+ // reevaluate A1 after the evaluation of A2.
33846
+ // We could evaluate which formula spreads first. Except that the array formulas
33847
+ // can themselves depend on the spreads of other formulas. ex:
33848
+ // A1:"=SPLIT(B3,'d')"
33849
+ // A2:="odoo odoo"
33850
+ // A3:"=SPLIT(A2,' ')"
33851
+ // In the example above, A3 must be evaluated before A1 because A1 needs B3 which
33852
+ // can be modified by A3.
33853
+ // Therefore, there would be a spatial evaluation order to be respected between
33854
+ // the array formulas. We could imagine that, when an array formula depends
33855
+ // on a cell, then we evaluate the first formula that spreads located in the upper
33856
+ // left corner of this cell.
33857
+ // Although this possibility has been explored, it remains complicated to spatially
33858
+ // predict which formula should be evaluated before another, especially when
33859
+ // the array formulas are located in different sheets or when the array formulas
33860
+ // depends on the spreads of each other. ex:
33861
+ //
33862
+ // A1:"=ARRAY_FORMULA_ALPHA(B2)"
33863
+ // A2:"=ARRAY_FORMULA_BETA(B1)"
33864
+ // In the example above, ARRAY_FORMULA_ALPHA and ARRAY_FORMULA_BETA are some
33865
+ // formulas that could spread depending on the value of B2 and B1. This could be a
33866
+ // cyclic dependency that we cannot anticipate.
33867
+ // And as with the "IF" formula, array formulas may not use their dependency.
33868
+ // It then becomes very difficult to manage...
33869
+ // Also, if we have a cycle, that doesn't mean it's bad. The cycle can converge to
33870
+ // a stable state at the scale of the sheets. Functionally, we don't want to forbid
33871
+ // convergent cycles. It is an interesting feature but which requires to re-evaluate
33872
+ // the cycle as many times as convergence is not reached.
33873
+ // Thus, in order to respect the relations between the cells (formula dependencies and
33874
+ // spread relations), the evaluation of the cells must:
33875
+ // - respect a precise order (cells values used by another must be evaluated first) : As
33876
+ // we cannot anticipate which dependencies are really used by the formulas, we must
33877
+ // evaluate the cells in a recursive way;
33878
+ // - be done as many times as necessary to ensure that all the cells have been correctly
33879
+ // evaluated in the correct order (in case of, for example, spreading relation cycles).
33880
+ // The chosen solution is to reevaluate the formulas impacted by spreads as many times
33881
+ // as necessary in several iterations, where evaluated cells can trigger the evaluation
33882
+ // of other cells depending on it, at the next iteration.
33883
+ //#endregion
33884
+ class EvaluationPlugin extends UIPlugin {
33885
+ static getters = [
33886
+ "evaluateFormula",
33887
+ "getRangeFormattedValues",
33888
+ "getRangeValues",
33889
+ "getRangeFormats",
33890
+ "getEvaluatedCell",
33891
+ "getEvaluatedCells",
33892
+ "getEvaluatedCellsInZone",
33893
+ ];
33894
+ shouldRebuildDependenciesGraph = true;
33895
+ evaluator;
33896
+ compilationParams;
33897
+ positionsToUpdate = [];
33898
+ constructor(config) {
33899
+ super(config);
33900
+ this.evaluator = new Evaluator(config.custom, this.getters);
33901
+ this.compilationParams = buildCompilationParameters(config.custom, this.getters, (position) => this.evaluator.getEvaluatedCell(position));
33902
+ }
33903
+ // ---------------------------------------------------------------------------
33904
+ // Command Handling
33905
+ // ---------------------------------------------------------------------------
33906
+ beforeHandle(cmd) {
33907
+ if (invalidateDependenciesCommands.has(cmd.type)) {
33908
+ this.shouldRebuildDependenciesGraph = true;
33909
+ }
33910
+ }
33911
+ handle(cmd) {
33912
+ switch (cmd.type) {
33913
+ case "UPDATE_CELL":
33914
+ if (!("content" in cmd || "format" in cmd) || this.shouldRebuildDependenciesGraph) {
33915
+ return;
33916
+ }
33917
+ this.positionsToUpdate.push(cmd);
33918
+ if ("content" in cmd) {
33919
+ this.evaluator.updateDependencies(cmd);
33920
+ }
33921
+ break;
33922
+ case "EVALUATE_CELLS":
33923
+ this.evaluator.evaluateAllCells();
33924
+ break;
33925
+ }
33926
+ }
33927
+ finalize() {
33928
+ if (this.shouldRebuildDependenciesGraph) {
33929
+ this.evaluator.buildDependencyGraph();
33930
+ this.evaluator.evaluateAllCells();
33931
+ this.shouldRebuildDependenciesGraph = false;
33932
+ }
33933
+ else if (this.positionsToUpdate.length) {
33934
+ this.evaluator.evaluateCells(this.positionsToUpdate);
33935
+ }
33936
+ this.positionsToUpdate = [];
33937
+ }
33938
+ // ---------------------------------------------------------------------------
33939
+ // Getters
33940
+ // ---------------------------------------------------------------------------
33941
+ evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33942
+ const compiledFormula = compile(formulaString);
33943
+ const ranges = [];
33944
+ for (let xc of compiledFormula.dependencies) {
33945
+ ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33946
+ }
33947
+ return compiledFormula.execute(ranges, ...this.compilationParams).value;
33948
+ }
33949
+ /**
33950
+ * Return the value of each cell in the range as they are displayed in the grid.
33951
+ */
33952
+ getRangeFormattedValues(range) {
33953
+ const sheet = this.getters.tryGetSheet(range.sheetId);
33954
+ if (sheet === undefined)
33955
+ return [];
33956
+ return this.getters
33957
+ .getEvaluatedCellsInZone(sheet.id, range.zone)
33958
+ .map((cell) => cell.formattedValue);
33959
+ }
33960
+ /**
33961
+ * Return the value of each cell in the range.
33962
+ */
33963
+ getRangeValues(range) {
33964
+ const sheet = this.getters.tryGetSheet(range.sheetId);
33965
+ if (sheet === undefined)
33966
+ return [];
33967
+ return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.value);
33968
+ }
33969
+ /**
33970
+ * Return the format of each cell in the range.
33971
+ */
33972
+ getRangeFormats(range) {
33973
+ const sheet = this.getters.tryGetSheet(range.sheetId);
33974
+ if (sheet === undefined)
33975
+ return [];
33976
+ return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.format);
33977
+ }
33978
+ getEvaluatedCell(position) {
33979
+ return this.evaluator.getEvaluatedCell(position);
33980
+ }
33981
+ getEvaluatedCells(sheetId) {
33982
+ const rawCells = this.getters.getCells(sheetId) || {};
33983
+ const record = {};
33984
+ for (let cellId of Object.keys(rawCells)) {
33985
+ const position = this.getters.getCellPosition(cellId);
33986
+ record[cellId] = this.getEvaluatedCell(position);
33987
+ }
33988
+ return record;
33989
+ }
33990
+ getEvaluatedCellsInZone(sheetId, zone) {
33991
+ return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
33992
+ }
33993
+ // ---------------------------------------------------------------------------
33994
+ // Export
33995
+ // ---------------------------------------------------------------------------
33996
+ exportForExcel(data) {
33997
+ for (const position of this.evaluator.getEvaluatedPositions()) {
33998
+ const evaluatedCell = this.evaluator.getEvaluatedCell(position);
33999
+ const xc = toXC(position.col, position.row);
34000
+ const value = evaluatedCell.value;
34001
+ let isFormula = false;
34002
+ let newContent = undefined;
34003
+ let newFormat = undefined;
34004
+ let isExported = true;
34005
+ const formulaCell = this.getCorrespondingFormulaCell(position);
34006
+ if (formulaCell) {
34007
+ isExported = formulaCell.compiledFormula.tokens
34008
+ .filter((tk) => tk.type === "FUNCTION")
34009
+ .every((tk) => functions[tk.value.toUpperCase()].isExported);
34010
+ isFormula = isExported;
34011
+ if (!isExported) {
34012
+ newContent = value.toString();
34013
+ newFormat = evaluatedCell.format;
34014
+ }
34015
+ }
34016
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
34017
+ const exportedCellData = exportedSheetData.cells[xc] || {};
34018
+ const format = newFormat
34019
+ ? getItemId(newFormat, data.formats)
34020
+ : exportedCellData.format;
34021
+ const content = !isExported ? newContent : exportedCellData.content;
34022
+ exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
34023
+ }
34024
+ }
34025
+ /**
34026
+ * Returns the corresponding formula cell of a given cell
34027
+ * It could be the formula present in the cell itself or the
34028
+ * formula of the array formula that spreads to the cell
34029
+ */
34030
+ getCorrespondingFormulaCell(position) {
34031
+ const cell = this.getters.getCell(position);
34032
+ if (cell && cell.content) {
34033
+ if (cell.isFormula && !isBadExpression(cell.content)) {
34034
+ return cell;
34035
+ }
34036
+ return undefined;
34037
+ }
34038
+ const spreadingFormulaPosition = this.evaluator.getArrayFormulaSpreadingOn(position);
34039
+ if (spreadingFormulaPosition === undefined) {
34040
+ return undefined;
34041
+ }
34042
+ const spreadingFormulaCell = this.getters.getCell(spreadingFormulaPosition);
34043
+ if (spreadingFormulaCell?.isFormula) {
34044
+ return spreadingFormulaCell;
34045
+ }
34046
+ return undefined;
34047
+ }
34048
+ }
34049
+ function isBadExpression(formula) {
34050
+ try {
34051
+ compile(formula);
34052
+ return false;
34053
+ }
34054
+ catch (error) {
34055
+ return true;
34056
+ }
34057
+ }
34058
+
33101
34059
  /**
33102
34060
  * https://tomekdev.com/posts/sorting-colors-in-js
33103
34061
  */
@@ -33248,329 +34206,6 @@ class CustomColorsPlugin extends UIPlugin {
33248
34206
  }
33249
34207
  }
33250
34208
 
33251
- const functionMap = functionRegistry.mapping;
33252
- class EvaluationPlugin extends UIPlugin {
33253
- static getters = [
33254
- "evaluateFormula",
33255
- "getRangeFormattedValues",
33256
- "getRangeValues",
33257
- "getRangeFormats",
33258
- "getEvaluatedCell",
33259
- "getEvaluatedCells",
33260
- "getColEvaluatedCells",
33261
- "getEvaluatedCellsInZone",
33262
- ];
33263
- isUpToDate = false;
33264
- evaluatedCells = {};
33265
- evalContext;
33266
- lazyEvaluation;
33267
- constructor(config) {
33268
- super(config);
33269
- this.evalContext = config.custom;
33270
- this.lazyEvaluation = config.lazyEvaluation;
33271
- }
33272
- // ---------------------------------------------------------------------------
33273
- // Command Handling
33274
- // ---------------------------------------------------------------------------
33275
- handle(cmd) {
33276
- if (invalidateEvaluationCommands.has(cmd.type)) {
33277
- this.isUpToDate = false;
33278
- }
33279
- switch (cmd.type) {
33280
- case "UPDATE_CELL":
33281
- if ("content" in cmd || "format" in cmd) {
33282
- this.isUpToDate = false;
33283
- }
33284
- break;
33285
- case "EVALUATE_CELLS":
33286
- this.evaluate();
33287
- break;
33288
- }
33289
- }
33290
- finalize() {
33291
- if (!this.isUpToDate) {
33292
- this.evaluate();
33293
- this.isUpToDate = true;
33294
- }
33295
- }
33296
- // ---------------------------------------------------------------------------
33297
- // Getters
33298
- // ---------------------------------------------------------------------------
33299
- evaluateFormula(formulaString, sheetId = this.getters.getActiveSheetId()) {
33300
- const compiledFormula = compile(formulaString);
33301
- const params = this.getCompilationParameters((cell) => this.getEvaluatedCell(this.getters.getCellPosition(cell.id)));
33302
- const ranges = [];
33303
- for (let xc of compiledFormula.dependencies) {
33304
- ranges.push(this.getters.getRangeFromSheetXC(sheetId, xc));
33305
- }
33306
- return compiledFormula.execute(ranges, ...params).value;
33307
- }
33308
- /**
33309
- * Return the value of each cell in the range as they are displayed in the grid.
33310
- */
33311
- getRangeFormattedValues(range) {
33312
- const sheet = this.getters.tryGetSheet(range.sheetId);
33313
- if (sheet === undefined)
33314
- return [];
33315
- return this.getters
33316
- .getEvaluatedCellsInZone(sheet.id, range.zone)
33317
- .map((cell) => cell.formattedValue);
33318
- }
33319
- /**
33320
- * Return the value of each cell in the range.
33321
- */
33322
- getRangeValues(range) {
33323
- const sheet = this.getters.tryGetSheet(range.sheetId);
33324
- if (sheet === undefined)
33325
- return [];
33326
- return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.value);
33327
- }
33328
- /**
33329
- * Return the format of each cell in the range.
33330
- */
33331
- getRangeFormats(range) {
33332
- const sheet = this.getters.tryGetSheet(range.sheetId);
33333
- if (sheet === undefined)
33334
- return [];
33335
- return this.getters.getEvaluatedCellsInZone(sheet.id, range.zone).map((cell) => cell.format);
33336
- }
33337
- getEvaluatedCell({ sheetId, col, row }) {
33338
- const cell = this.getters.getCell({ sheetId, col, row });
33339
- if (cell === undefined) {
33340
- return createEvaluatedCell("");
33341
- }
33342
- // the cell might have been created by a command in the current
33343
- // dispatch but the evaluation is not done yet.
33344
- return this.evaluatedCells[sheetId]?.[col]?.[row]?.() || createEvaluatedCell("");
33345
- }
33346
- getEvaluatedCells(sheetId) {
33347
- const rawCells = this.getters.getCells(sheetId) || {};
33348
- const record = {};
33349
- for (let cellId of Object.keys(rawCells)) {
33350
- const position = this.getters.getCellPosition(cellId);
33351
- record[cellId] = this.getEvaluatedCell(position);
33352
- }
33353
- return record;
33354
- }
33355
- /**
33356
- * Returns all the evaluated cells of a col
33357
- */
33358
- getColEvaluatedCells(sheetId, col) {
33359
- return Object.values(this.evaluatedCells[sheetId]?.[col] || [])
33360
- .filter(isDefined$1)
33361
- .map((lazyCell) => lazyCell());
33362
- }
33363
- getEvaluatedCellsInZone(sheetId, zone) {
33364
- return positions(zone).map(({ col, row }) => this.getters.getEvaluatedCell({ sheetId, col, row }));
33365
- }
33366
- // ---------------------------------------------------------------------------
33367
- // Evaluator
33368
- // ---------------------------------------------------------------------------
33369
- setEvaluatedCell(cellId, evaluatedCell) {
33370
- const { col, row, sheetId } = this.getters.getCellPosition(cellId);
33371
- if (!this.evaluatedCells[sheetId]) {
33372
- this.evaluatedCells[sheetId] = {};
33373
- }
33374
- if (!this.evaluatedCells[sheetId][col]) {
33375
- this.evaluatedCells[sheetId][col] = {};
33376
- }
33377
- this.evaluatedCells[sheetId][col][row] = evaluatedCell;
33378
- if (!this.lazyEvaluation) {
33379
- this.evaluatedCells[sheetId][col][row]();
33380
- }
33381
- }
33382
- *getAllCells() {
33383
- // use a generator function to avoid re-building a new object
33384
- for (const sheetId of this.getters.getSheetIds()) {
33385
- const cells = this.getters.getCells(sheetId);
33386
- for (const cellId in cells) {
33387
- yield cells[cellId];
33388
- }
33389
- }
33390
- }
33391
- evaluate() {
33392
- this.evaluatedCells = {};
33393
- const cellsBeingComputed = new Set();
33394
- const computeCell = (cell) => {
33395
- const cellId = cell.id;
33396
- const { col, row, sheetId } = this.getters.getCellPosition(cellId);
33397
- const lazyEvaluation = this.evaluatedCells[sheetId]?.[col]?.[row];
33398
- if (lazyEvaluation) {
33399
- return lazyEvaluation; // already computed
33400
- }
33401
- return lazy(() => {
33402
- try {
33403
- switch (cell.isFormula) {
33404
- case true:
33405
- return computeFormulaCell(cell);
33406
- case false:
33407
- return evaluateLiteral(cell.content, cell.format);
33408
- }
33409
- }
33410
- catch (e) {
33411
- return handleError(e, cell);
33412
- }
33413
- });
33414
- };
33415
- const handleError = (e, cell) => {
33416
- if (!(e instanceof Error)) {
33417
- e = new Error(e);
33418
- }
33419
- const msg = e?.errorType || CellErrorType.GenericError;
33420
- // apply function name
33421
- const __lastFnCalled = compilationParameters[2].__lastFnCalled || "";
33422
- const error = new EvaluationError(msg, e.message.replace("[[FUNCTION_NAME]]", __lastFnCalled), e.logLevel !== undefined ? e.logLevel : CellErrorLevel.error);
33423
- return errorCell(cell.content, error);
33424
- };
33425
- const computeFormulaCell = (cellData) => {
33426
- const cellId = cellData.id;
33427
- if (cellsBeingComputed.has(cellId)) {
33428
- throw new CircularDependencyError();
33429
- }
33430
- compilationParameters[2].__originCellXC = () => {
33431
- // compute the value lazily for performance reasons
33432
- const position = compilationParameters[2].getters.getCellPosition(cellId);
33433
- return toXC(position.col, position.row);
33434
- };
33435
- cellsBeingComputed.add(cellId);
33436
- const computedCell = cellData.compiledFormula.execute(cellData.dependencies, ...compilationParameters);
33437
- cellsBeingComputed.delete(cellId);
33438
- if (Array.isArray(computedCell.value)) {
33439
- // if a value returns an array (like =A1:A3)
33440
- throw new Error(_lt("This formula depends on invalid values"));
33441
- }
33442
- return createEvaluatedCell(computedCell.value, cellData.format || computedCell.format);
33443
- };
33444
- const compilationParameters = this.getCompilationParameters((cell) => computeCell(cell)());
33445
- for (const cell of this.getAllCells()) {
33446
- this.setEvaluatedCell(cell.id, computeCell(cell));
33447
- }
33448
- }
33449
- /**
33450
- * Return all functions necessary to properly evaluate a formula:
33451
- * - a refFn function to read any reference, cell or range of a normalized formula
33452
- * - a range function to convert any reference to a proper value array
33453
- * - an evaluation context
33454
- */
33455
- getCompilationParameters(computeCell) {
33456
- const evalContext = Object.assign(Object.create(functionMap), this.evalContext, {
33457
- getters: this.getters,
33458
- });
33459
- const getters = this.getters;
33460
- function readCell(range) {
33461
- let cell;
33462
- if (!getters.tryGetSheet(range.sheetId)) {
33463
- throw new Error(_lt("Invalid sheet name"));
33464
- }
33465
- cell = getters.getCell({ sheetId: range.sheetId, col: range.zone.left, row: range.zone.top });
33466
- if (!cell || cell.content === "") {
33467
- // magic "empty" value
33468
- // Returning {value: null} instead of undefined will ensure that we don't
33469
- // fall back on the default value of the argument provided to the formula's compute function
33470
- return { value: null, format: cell?.format };
33471
- }
33472
- return getEvaluatedCell(cell);
33473
- }
33474
- const getEvaluatedCell = (cell) => {
33475
- const evaluatedCell = computeCell(cell);
33476
- if (evaluatedCell.type === CellValueType.error) {
33477
- throw evaluatedCell.error;
33478
- }
33479
- return evaluatedCell;
33480
- };
33481
- /**
33482
- * Return the values of the cell(s) used in reference, but always in the format of a range even
33483
- * if a single cell is referenced. It is a list of col values. This is useful for the formulas that describe parameters as
33484
- * range<number> etc.
33485
- *
33486
- * Note that each col is possibly sparse: it only contain the values of cells
33487
- * that are actually present in the grid.
33488
- */
33489
- function range(range) {
33490
- const sheetId = range.sheetId;
33491
- if (!isZoneValid(range.zone)) {
33492
- throw new InvalidReferenceError();
33493
- }
33494
- // Performance issue: Avoid fetching data on positions that are out of the spreadsheet
33495
- // e.g. A1:ZZZ9999 in a sheet with 10 cols and 10 rows should ignore everything past J10 and return a 10x10 array
33496
- const sheetZone = getters.getSheetZone(sheetId);
33497
- const result = [];
33498
- const zone = intersection(range.zone, sheetZone);
33499
- if (!zone) {
33500
- result.push([]);
33501
- return result;
33502
- }
33503
- // Performance issue: nested loop is faster than a map here
33504
- for (let col = zone.left; col <= zone.right; col++) {
33505
- const rowValues = [];
33506
- for (let row = zone.top; row <= zone.bottom; row++) {
33507
- const cell = evalContext.getters.getCell({ sheetId: range.sheetId, col, row });
33508
- rowValues.push(cell ? getEvaluatedCell(cell) : undefined);
33509
- }
33510
- result.push(rowValues);
33511
- }
33512
- return result;
33513
- }
33514
- /**
33515
- * Returns the value of the cell(s) used in reference
33516
- *
33517
- * @param range the references used
33518
- * @param isMeta if a reference is supposed to be used in a `meta` parameter as described in the
33519
- * function for which this parameter is used, we just return the string of the parameter.
33520
- * The `compute` of the formula's function must process it completely
33521
- */
33522
- function refFn(range, isMeta, functionName, paramNumber) {
33523
- if (isMeta) {
33524
- // Use zoneToXc of zone instead of getRangeString to avoid sending unbounded ranges
33525
- return { value: zoneToXc(range.zone) };
33526
- }
33527
- if (!isZoneValid(range.zone)) {
33528
- throw new InvalidReferenceError();
33529
- }
33530
- // if the formula definition could have accepted a range, we would pass through the _range function and not here
33531
- if (range.zone.bottom !== range.zone.top || range.zone.left !== range.zone.right) {
33532
- throw new Error(paramNumber
33533
- ? _lt("Function %s expects the parameter %s to be a single value or a single cell reference, not a range.", functionName.toString(), paramNumber.toString())
33534
- : _lt("Function %s expects its parameters to be single values or single cell references, not ranges.", functionName.toString()));
33535
- }
33536
- if (range.invalidSheetName) {
33537
- throw new Error(_lt("Invalid sheet name: %s", range.invalidSheetName));
33538
- }
33539
- return readCell(range);
33540
- }
33541
- return [refFn, range, evalContext];
33542
- }
33543
- // ---------------------------------------------------------------------------
33544
- // Export
33545
- // ---------------------------------------------------------------------------
33546
- exportForExcel(data) {
33547
- for (let sheet of data.sheets) {
33548
- for (const xc in sheet.cells) {
33549
- const position = { sheetId: sheet.id, ...toCartesian(xc) };
33550
- const cell = this.getters.getCell(position);
33551
- if (cell) {
33552
- const exportedCellData = sheet.cells[xc];
33553
- const evaluatedCell = this.getEvaluatedCell(position);
33554
- exportedCellData.value = evaluatedCell.value;
33555
- exportedCellData.isFormula = cell.isFormula && !this.isBadExpression(cell.content);
33556
- if (cell.format !== evaluatedCell.format) {
33557
- exportedCellData.computedFormat = evaluatedCell.format;
33558
- }
33559
- }
33560
- }
33561
- }
33562
- }
33563
- isBadExpression(formula) {
33564
- try {
33565
- compile(formula);
33566
- return false;
33567
- }
33568
- catch (error) {
33569
- return true;
33570
- }
33571
- }
33572
- }
33573
-
33574
34209
  class EvaluationChartPlugin extends UIPlugin {
33575
34210
  static getters = ["getChartRuntime", "getBackgroundOfSingleCellChart"];
33576
34211
  charts = {};
@@ -45789,44 +46424,30 @@ function numberRef(reference) {
45789
46424
 
45790
46425
  function addFormula(cell) {
45791
46426
  const formula = cell.content;
45792
- const functions = functionRegistry.content;
45793
- const tokens = tokenize(formula);
45794
46427
  const attrs = [];
45795
46428
  let node = escapeXml ``;
45796
- const isExported = tokens
45797
- .filter((tk) => tk.type === "FUNCTION")
45798
- .every((tk) => functions[tk.value.toUpperCase()].isExported);
45799
- if (isExported) {
45800
- let cycle = escapeXml ``;
45801
- const XlsxFormula = adaptFormulaToExcel(formula);
45802
- // hack for cycles : if we don't set a value (be it 0 or #VALUE!), it will appear as invisible on excel,
45803
- // Making it very hard for the client to find where the recursion is.
45804
- if (cell.value === CellErrorType.CircularDependency) {
45805
- attrs.push(["t", "str"]);
45806
- cycle = escapeXml /*xml*/ `<v>${cell.value}</v>`;
45807
- }
45808
- node = escapeXml /*xml*/ `
46429
+ let cycle = escapeXml ``;
46430
+ const XlsxFormula = adaptFormulaToExcel(formula);
46431
+ // hack for cycles : if we don't set a value (be it 0 or #VALUE!), it will appear as invisible on excel,
46432
+ // Making it very hard for the client to find where the recursion is.
46433
+ if (cell.value === CellErrorType.CircularDependency) {
46434
+ attrs.push(["t", "str"]);
46435
+ cycle = escapeXml /*xml*/ `<v>${cell.value}</v>`;
46436
+ }
46437
+ node = escapeXml /*xml*/ `
45809
46438
  <f>
45810
46439
  ${XlsxFormula}
45811
46440
  </f>
45812
46441
  ${cycle}
45813
46442
  `;
45814
- return { attrs, node };
45815
- }
45816
- else {
45817
- // Shouldn't we always output the value then ?
45818
- const value = cell.value;
45819
- const type = getCellType(value);
45820
- attrs.push(["t", type]);
45821
- node = escapeXml /*xml*/ `<v>${value}</v>`;
45822
- return { attrs, node };
45823
- }
46443
+ return { attrs, node };
45824
46444
  }
45825
46445
  function addContent(content, sharedStrings, forceString = false) {
45826
46446
  let value = content;
45827
46447
  const attrs = [];
45828
- if (!forceString && ["TRUE", "FALSE"].includes(value.trim())) {
45829
- value = value === "TRUE" ? "1" : "0";
46448
+ const clearValue = value.trim().toUpperCase();
46449
+ if (!forceString && ["TRUE", "FALSE"].includes(clearValue)) {
46450
+ value = clearValue === "TRUE" ? "1" : "0";
45830
46451
  attrs.push(["t", "b"]);
45831
46452
  }
45832
46453
  else if (forceString || !isNumber(value)) {
@@ -47217,7 +47838,6 @@ class Model extends EventBus {
47217
47838
  moveClient: () => { },
47218
47839
  snapshotRequested: false,
47219
47840
  notifyUI: (payload) => this.trigger("notify-ui", payload),
47220
- lazyEvaluation: "lazyEvaluation" in config ? config.lazyEvaluation : true,
47221
47841
  };
47222
47842
  }
47223
47843
  setupCorePluginConfig() {
@@ -47240,7 +47860,6 @@ class Model extends EventBus {
47240
47860
  moveClient: this.session.move.bind(this.session),
47241
47861
  custom: this.config.custom,
47242
47862
  uiActions: this.config,
47243
- lazyEvaluation: this.config.lazyEvaluation,
47244
47863
  session: this.session,
47245
47864
  };
47246
47865
  }
@@ -47551,6 +48170,6 @@ function addFunction(functionName, functionDescription) {
47551
48170
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
47552
48171
 
47553
48172
 
47554
- __info__.version = '16.3.1';
47555
- __info__.date = '2023-05-25T13:00:27.320Z';
47556
- __info__.hash = 'b3cfd2c';
48173
+ __info__.version = '16.4.0-alpha.1';
48174
+ __info__.date = '2023-05-25T13:00:14.133Z';
48175
+ __info__.hash = '564c094';