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