@odoo/o-spreadsheet 17.2.7 → 17.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,12 +3,12 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.2.7
7
- * @date 2024-05-15T09:20:44.429Z
8
- * @hash 57e89fa
6
+ * @version 17.2.9
7
+ * @date 2024-06-03T14:56:21.684Z
8
+ * @hash 086af6d
9
9
  */
10
10
 
11
- import { reactive, useEnv, useSubEnv, useState, onWillUnmount, markRaw, toRaw, Component, useRef, onMounted, useEffect, onPatched, useComponent, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv } from '@odoo/owl';
11
+ import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, useState, onPatched, onWillPatch, onWillUpdateProps, useExternalListener, onWillStart, xml, useChildSubEnv, markRaw } from '@odoo/owl';
12
12
 
13
13
  const CANVAS_SHIFT = 0.5;
14
14
  // Colors
@@ -502,6 +502,26 @@ function debounce(func, wait, immediate) {
502
502
  };
503
503
  return debounced;
504
504
  }
505
+ /**
506
+ * Creates a batched version of a callback so that all calls to it in the same
507
+ * microtick will only call the original callback once.
508
+ *
509
+ * @param callback the callback to batch
510
+ * @returns a batched version of the original callback
511
+ *
512
+ * Copied from odoo/owl repo.
513
+ */
514
+ function batched(callback) {
515
+ let scheduled = false;
516
+ return async (...args) => {
517
+ if (!scheduled) {
518
+ scheduled = true;
519
+ await Promise.resolve();
520
+ scheduled = false;
521
+ callback(...args);
522
+ }
523
+ };
524
+ }
505
525
  /*
506
526
  * Concatenate an array of strings.
507
527
  */
@@ -576,8 +596,9 @@ function deepEquals(o1, o2, ignoreFunctions) {
576
596
  return false;
577
597
  }
578
598
  else {
579
- if (ignoreFunctions && typeOfO1Key === "function")
580
- return true;
599
+ if (ignoreFunctions && typeOfO1Key === "function") {
600
+ continue;
601
+ }
581
602
  if (o1[key] !== o2[key])
582
603
  return false;
583
604
  }
@@ -1828,7 +1849,7 @@ class LazyTranslatedString extends String {
1828
1849
  }
1829
1850
  valueOf() {
1830
1851
  const str = super.valueOf();
1831
- return _loaded() ? sprintf(_translate(str), ...this.values) : str;
1852
+ return _loaded() ? sprintf(_translate(str), ...this.values) : sprintf(str, ...this.values);
1832
1853
  }
1833
1854
  toString() {
1834
1855
  return this.valueOf();
@@ -2637,7 +2658,7 @@ function evaluatePredicate(value, criterion) {
2637
2658
  return false;
2638
2659
  }
2639
2660
  if (typeof operand === "number" && operator === "=") {
2640
- return toString(value) === toString(operand);
2661
+ return value.toString() === operand.toString();
2641
2662
  }
2642
2663
  if (operator === "<>" || operator === "=") {
2643
2664
  let result;
@@ -2697,14 +2718,13 @@ function visitMatchingRanges(args, cb, locale, isQuery = false) {
2697
2718
  if (countArg % 2 === 1) {
2698
2719
  throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects criteria_range and criterion to be in pairs."));
2699
2720
  }
2700
- const dimRow = args[0].length;
2701
- const dimCol = args[0][0].length;
2721
+ const firstArg = toMatrix(args[0]);
2722
+ const dimRow = firstArg.length;
2723
+ const dimCol = firstArg[0].length;
2702
2724
  let predicates = [];
2703
2725
  for (let i = 0; i < countArg - 1; i += 2) {
2704
- const criteriaRange = args[i];
2705
- if (!isMatrix(criteriaRange) ||
2706
- criteriaRange.length !== dimRow ||
2707
- criteriaRange[0].length !== dimCol) {
2726
+ const criteriaRange = toMatrix(args[i]);
2727
+ if (criteriaRange.length !== dimRow || criteriaRange[0].length !== dimCol) {
2708
2728
  throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects criteria_range to have the same dimension"));
2709
2729
  }
2710
2730
  const description = toString(args[i + 1]);
@@ -2718,7 +2738,7 @@ function visitMatchingRanges(args, cb, locale, isQuery = false) {
2718
2738
  for (let j = 0; j < dimCol; j++) {
2719
2739
  let validatedPredicates = true;
2720
2740
  for (let k = 0; k < countArg - 1; k += 2) {
2721
- const criteriaValue = args[k][i][j].value;
2741
+ const criteriaValue = toMatrix(args[k])[i][j].value;
2722
2742
  const criterion = predicates[k / 2];
2723
2743
  validatedPredicates = evaluatePredicate(criteriaValue ?? undefined, criterion);
2724
2744
  if (!validatedPredicates) {
@@ -4435,8 +4455,11 @@ function copyRangeWithNewSheetId(sheetIdFrom, sheetIdTo, range) {
4435
4455
  /**
4436
4456
  * Create a range from a xc. If the xc is empty, this function returns undefined.
4437
4457
  */
4438
- function createRange(getters, sheetId, range) {
4439
- return range ? getters.getRangeFromSheetXC(sheetId, range) : undefined;
4458
+ function createValidRange(getters, sheetId, xc) {
4459
+ if (!xc)
4460
+ return;
4461
+ const range = getters.getRangeFromSheetXC(sheetId, xc);
4462
+ return !(range.invalidSheetName || range.invalidXc) ? range : undefined;
4440
4463
  }
4441
4464
  /**
4442
4465
  * Spread multiple colrows zone to one row/col zone and add a many new input range as needed.
@@ -8012,10 +8035,66 @@ function getDateCriterionFormattedValues(criterion, getters) {
8012
8035
  });
8013
8036
  }
8014
8037
 
8038
+ /**
8039
+ * This is a generic event bus based on the Owl event bus.
8040
+ * This bus however ensures type safety across events and subscription callbacks.
8041
+ */
8042
+ class EventBus {
8043
+ subscriptions = {};
8044
+ /**
8045
+ * Add a listener for the 'eventType' events.
8046
+ *
8047
+ * Note that the 'owner' of this event can be anything, but will more likely
8048
+ * be a component or a class. The idea is that the callback will be called with
8049
+ * the proper owner bound.
8050
+ *
8051
+ * Also, the owner should be kind of unique. This will be used to remove the
8052
+ * listener.
8053
+ */
8054
+ on(type, owner, callback) {
8055
+ if (!callback) {
8056
+ throw new Error("Missing callback");
8057
+ }
8058
+ if (!this.subscriptions[type]) {
8059
+ this.subscriptions[type] = [];
8060
+ }
8061
+ this.subscriptions[type].push({
8062
+ owner,
8063
+ callback,
8064
+ });
8065
+ }
8066
+ /**
8067
+ * Emit an event of type 'eventType'. Any extra arguments will be passed to
8068
+ * the listeners callback.
8069
+ */
8070
+ trigger(type, payload) {
8071
+ const subs = this.subscriptions[type] || [];
8072
+ for (let i = 0, iLen = subs.length; i < iLen; i++) {
8073
+ const sub = subs[i];
8074
+ sub.callback.call(sub.owner, payload);
8075
+ }
8076
+ }
8077
+ /**
8078
+ * Remove a listener
8079
+ */
8080
+ off(eventType, owner) {
8081
+ const subs = this.subscriptions[eventType];
8082
+ if (subs) {
8083
+ this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);
8084
+ }
8085
+ }
8086
+ /**
8087
+ * Remove all subscriptions.
8088
+ */
8089
+ clear() {
8090
+ this.subscriptions = {};
8091
+ }
8092
+ }
8093
+
8015
8094
  /**
8016
8095
  * A type-safe dependency container
8017
8096
  */
8018
- class DependencyContainer {
8097
+ class DependencyContainer extends EventBus {
8019
8098
  dependencies = new Map();
8020
8099
  factory = new StoreFactory(this.get.bind(this));
8021
8100
  /**
@@ -8092,15 +8171,12 @@ stores.inject(MyMetaStore, storeInstance);
8092
8171
  }
8093
8172
  return MetaStore;
8094
8173
  }
8095
- class ReactiveStore {
8174
+ class DisposableStore {
8096
8175
  get;
8176
+ disposeCallbacks = [];
8097
8177
  constructor(get) {
8098
8178
  this.get = get;
8099
- return reactive(this);
8100
8179
  }
8101
- }
8102
- class DisposableStore extends ReactiveStore {
8103
- disposeCallbacks = [];
8104
8180
  onDispose(callback) {
8105
8181
  this.disposeCallbacks.push(callback);
8106
8182
  }
@@ -8120,7 +8196,10 @@ function useStoreProvider() {
8120
8196
  const container = new DependencyContainer();
8121
8197
  useSubEnv({
8122
8198
  __spreadsheet_stores__: container,
8123
- getStore: container.get.bind(container),
8199
+ getStore: (Store) => {
8200
+ const store = container.get(Store);
8201
+ return proxifyStoreMutation(store, () => container.trigger("store-updated"));
8202
+ },
8124
8203
  });
8125
8204
  return container;
8126
8205
  }
@@ -8130,14 +8209,57 @@ function useStoreProvider() {
8130
8209
  function useStore(Store) {
8131
8210
  const env = useEnv();
8132
8211
  const container = getDependencyContainer(env);
8133
- return useState(container.get(Store));
8212
+ const store = container.get(Store);
8213
+ return useStoreRenderProxy(container, store);
8134
8214
  }
8135
8215
  function useLocalStore(Store, ...args) {
8136
8216
  const env = useEnv();
8137
8217
  const container = getDependencyContainer(env);
8138
- const store = useState(container.instantiate(Store, ...args));
8218
+ const store = container.instantiate(Store, ...args);
8139
8219
  onWillUnmount(() => store.dispose());
8140
- return store;
8220
+ return useStoreRenderProxy(container, store);
8221
+ }
8222
+ /**
8223
+ * Trigger an event to re-render the app (deep render) when
8224
+ * a store is mutated by invoking one of its mutator methods.
8225
+ */
8226
+ function useStoreRenderProxy(container, store) {
8227
+ const component = useComponent();
8228
+ const proxy = proxifyStoreMutation(store, () => {
8229
+ if (status(component) === "mounted") {
8230
+ container.trigger("store-updated");
8231
+ }
8232
+ });
8233
+ return proxy;
8234
+ }
8235
+ /**
8236
+ * Creates a proxied version of a store object with mutation tracking.
8237
+ * Whenever a mutator method of the store is called, the provided callback function is invoked.
8238
+ */
8239
+ function proxifyStoreMutation(store, callback) {
8240
+ const proxy = new Proxy(store, {
8241
+ get(target, property, receiver) {
8242
+ const thisStore = target;
8243
+ // The third argument is `thisStore` (target) instead of `receiver`.
8244
+ // The goal is to always have the same `this` value in getter functions
8245
+ // (when `target[property]` is an accessor property).
8246
+ // `thisStore` is always the same object reference. `receiver` however is the
8247
+ // object on which the property is called, which is the Proxy object which is different for each component.
8248
+ const value = Reflect.get(target, property, thisStore);
8249
+ if (store.mutators.includes(property)) {
8250
+ const functionProxy = new Proxy(value, {
8251
+ // trap the function call
8252
+ apply(target, thisArg, argArray) {
8253
+ Reflect.apply(target, thisStore, argArray);
8254
+ callback();
8255
+ },
8256
+ });
8257
+ return functionProxy;
8258
+ }
8259
+ return value;
8260
+ },
8261
+ });
8262
+ return proxy;
8141
8263
  }
8142
8264
  function getDependencyContainer(env) {
8143
8265
  const container = env.__spreadsheet_stores__;
@@ -8149,7 +8271,8 @@ function getDependencyContainer(env) {
8149
8271
 
8150
8272
  const ModelStore = createAbstractStore("Model");
8151
8273
 
8152
- class RendererStore extends ReactiveStore {
8274
+ class RendererStore {
8275
+ mutators = ["register", "unRegister"];
8153
8276
  renderers = {};
8154
8277
  register(renderer) {
8155
8278
  if (!renderer.renderingLayers.length) {
@@ -8183,7 +8306,7 @@ class RendererStore extends ReactiveStore {
8183
8306
  class SpreadsheetStore extends DisposableStore {
8184
8307
  // cast the model store as Model to allow model.dispatch to return the DispatchResult
8185
8308
  model = this.get(ModelStore);
8186
- getters = markRaw(this.model.getters);
8309
+ getters = this.model.getters;
8187
8310
  renderer = this.get(RendererStore);
8188
8311
  constructor(get) {
8189
8312
  super(get);
@@ -8230,6 +8353,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8230
8353
  }
8231
8354
 
8232
8355
  class HighlightStore extends SpreadsheetStore {
8356
+ mutators = ["register", "unRegister"];
8233
8357
  providers = [];
8234
8358
  constructor(get) {
8235
8359
  super(get);
@@ -8260,7 +8384,7 @@ class HighlightStore extends SpreadsheetStore {
8260
8384
  this.providers.push(highlightProvider);
8261
8385
  }
8262
8386
  unRegister(highlightProvider) {
8263
- this.providers = this.providers.filter((h) => toRaw(h) !== toRaw(highlightProvider));
8387
+ this.providers = this.providers.filter((h) => h !== highlightProvider);
8264
8388
  }
8265
8389
  drawLayer(ctx, layer) {
8266
8390
  if (layer === "Highlights") {
@@ -8276,6 +8400,16 @@ const NotificationStore = createAbstractStore("Notifications");
8276
8400
 
8277
8401
  const CELL_DELETED_MESSAGE = _t("The cell you are trying to edit has been deleted.");
8278
8402
  class ComposerStore extends SpreadsheetStore {
8403
+ mutators = [
8404
+ "startEdition",
8405
+ "setCurrentContent",
8406
+ "stopEdition",
8407
+ "stopComposerRangeSelection",
8408
+ "cancelEdition",
8409
+ "cycleReferences",
8410
+ "changeComposerCursorSelection",
8411
+ "replaceComposerCursorSelection",
8412
+ ];
8279
8413
  col = 0;
8280
8414
  row = 0;
8281
8415
  editionMode = "inactive";
@@ -8290,9 +8424,9 @@ class ComposerStore extends SpreadsheetStore {
8290
8424
  highlightStore = this.get(HighlightStore);
8291
8425
  constructor(get) {
8292
8426
  super(get);
8293
- this.highlightStore.register(toRaw(this));
8427
+ this.highlightStore.register(this);
8294
8428
  this.onDispose(() => {
8295
- this.highlightStore.unRegister(toRaw(this));
8429
+ this.highlightStore.unRegister(this);
8296
8430
  });
8297
8431
  }
8298
8432
  canStopEdition() {
@@ -8419,7 +8553,7 @@ class ComposerStore extends SpreadsheetStore {
8419
8553
  if (this.isSelectingRange) {
8420
8554
  this.editionMode = "editing";
8421
8555
  }
8422
- this.model.selection.resetAnchor(toRaw(this), {
8556
+ this.model.selection.resetAnchor(this, {
8423
8557
  cell: { col: left, row: top },
8424
8558
  zone: cmd.zone,
8425
8559
  });
@@ -8437,7 +8571,7 @@ class ComposerStore extends SpreadsheetStore {
8437
8571
  row: activePosition.row,
8438
8572
  });
8439
8573
  const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));
8440
- this.model.selection.resetAnchor(toRaw(this), { cell: { col, row }, zone });
8574
+ this.model.selection.resetAnchor(this, { cell: { col, row }, zone });
8441
8575
  }
8442
8576
  break;
8443
8577
  case "DELETE_SHEET":
@@ -8536,7 +8670,7 @@ class ComposerStore extends SpreadsheetStore {
8536
8670
  startComposerRangeSelection() {
8537
8671
  if (this.sheetId === this.getters.getActiveSheetId()) {
8538
8672
  const zone = positionToZone({ col: this.col, row: this.row });
8539
- this.model.selection.resetAnchor(toRaw(this), {
8673
+ this.model.selection.resetAnchor(this, {
8540
8674
  cell: { col: this.col, row: this.row },
8541
8675
  zone,
8542
8676
  });
@@ -8565,7 +8699,7 @@ class ComposerStore extends SpreadsheetStore {
8565
8699
  this.setContent(str || this.initialContent, selection);
8566
8700
  this.colorIndexByRange = {};
8567
8701
  const zone = positionToZone({ col: this.col, row: this.row });
8568
- this.model.selection.capture(toRaw(this), { cell: { col: this.col, row: this.row }, zone }, {
8702
+ this.model.selection.capture(this, { cell: { col: this.col, row: this.row }, zone }, {
8569
8703
  handleEvent: this.handleEvent.bind(this),
8570
8704
  release: () => {
8571
8705
  this._stopEdition();
@@ -8673,7 +8807,7 @@ class ComposerStore extends SpreadsheetStore {
8673
8807
  return;
8674
8808
  }
8675
8809
  this.editionMode = "inactive";
8676
- this.model.selection.release(toRaw(this));
8810
+ this.model.selection.release(this);
8677
8811
  }
8678
8812
  /**
8679
8813
  * Reset the current content to the active cell content
@@ -8995,6 +9129,7 @@ class ComposerStore extends SpreadsheetStore {
8995
9129
  }
8996
9130
 
8997
9131
  class ComposerFocusStore extends SpreadsheetStore {
9132
+ mutators = ["focusTopBarComposer", "focusGridComposerContent", "focusGridComposerCell"];
8998
9133
  composerStore = this.get(ComposerStore);
8999
9134
  topBarFocus = "inactive";
9000
9135
  gridFocusMode = "inactive";
@@ -9511,8 +9646,8 @@ let ScorecardChart$1 = class ScorecardChart extends AbstractChart {
9511
9646
  type = "scorecard";
9512
9647
  constructor(definition, sheetId, getters) {
9513
9648
  super(definition, sheetId, getters);
9514
- this.keyValue = createRange(getters, sheetId, definition.keyValue);
9515
- this.baseline = createRange(getters, sheetId, definition.baseline);
9649
+ this.keyValue = createValidRange(getters, sheetId, definition.keyValue);
9650
+ this.baseline = createValidRange(getters, sheetId, definition.baseline);
9516
9651
  this.baselineMode = definition.baselineMode;
9517
9652
  this.baselineDescr = definition.baselineDescr;
9518
9653
  this.background = definition.background;
@@ -10082,6 +10217,9 @@ function makeArg(str, description) {
10082
10217
  if (types.some((t) => t.startsWith("RANGE"))) {
10083
10218
  result.acceptMatrix = true;
10084
10219
  }
10220
+ if (types.every((t) => t.startsWith("RANGE"))) {
10221
+ result.acceptMatrixOnly = true;
10222
+ }
10085
10223
  return result;
10086
10224
  }
10087
10225
  /**
@@ -10363,11 +10501,16 @@ const CHOOSECOLS = {
10363
10501
  compute: function (array, ...columns) {
10364
10502
  const _array = toMatrix(array);
10365
10503
  const _columns = flattenRowFirst(columns, (item) => toInteger(item?.value, this.locale));
10366
- assert(() => _columns.every((col) => col > 0 && col <= _array.length), _t("The columns arguments must be between 1 and %s (got %s).", _array.length.toString(), (_columns.find((col) => col <= 0 || col > _array.length) || 0).toString()));
10504
+ const argOutOfRange = _columns.filter((col) => col === 0 || _array.length < Math.abs(col));
10505
+ assert(() => argOutOfRange.length === 0, _t("The columns arguments must be between -%s and %s (got %s), excluding 0.", _array.length.toString(), _array.length.toString(), argOutOfRange.join(",")));
10367
10506
  const result = Array(_columns.length);
10368
10507
  for (let col = 0; col < _columns.length; col++) {
10369
- const colIndex = _columns[col] - 1; // -1 because columns arguments are 1-indexed
10370
- result[col] = _array[colIndex];
10508
+ if (_columns[col] > 0) {
10509
+ result[col] = _array[_columns[col] - 1]; // -1 because columns arguments are 1-indexed
10510
+ }
10511
+ else {
10512
+ result[col] = _array[_array.length + _columns[col]];
10513
+ }
10371
10514
  }
10372
10515
  return result;
10373
10516
  },
@@ -10388,8 +10531,14 @@ const CHOOSEROWS = {
10388
10531
  const _array = toMatrix(array);
10389
10532
  const _rows = flattenRowFirst(rows, (item) => toInteger(item?.value, this.locale));
10390
10533
  const _nbColumns = _array.length;
10391
- assert(() => _rows.every((row) => row > 0 && row <= _array[0].length), _t("The rows arguments must be between 1 and %s (got %s).", _array[0].length.toString(), (_rows.find((row) => row <= 0 || row > _array[0].length) || 0).toString()));
10392
- return generateMatrix(_nbColumns, _rows.length, (col, row) => _array[col][_rows[row] - 1]); // -1 because rows arguments are 1-indexed
10534
+ const argOutOfRange = _rows.filter((row) => row === 0 || _array[0].length < Math.abs(row));
10535
+ assert(() => argOutOfRange.length === 0, _t("The rows arguments must be between -%s and %s (got %s), excluding 0.", _array[0].length.toString(), _array[0].length.toString(), argOutOfRange.join(",")));
10536
+ return generateMatrix(_nbColumns, _rows.length, (col, row) => {
10537
+ if (_rows[row] > 0) {
10538
+ return _array[col][_rows[row] - 1]; // -1 because columns arguments are 1-indexed
10539
+ }
10540
+ return _array[col][_array[col].length + _rows[row]];
10541
+ });
10393
10542
  },
10394
10543
  isExported: true,
10395
10544
  };
@@ -11297,7 +11446,7 @@ const COUNTUNIQUEIFS = {
11297
11446
  compute: function (range, ...args) {
11298
11447
  let uniqueValues = new Set();
11299
11448
  visitMatchingRanges(args, (i, j) => {
11300
- const data = range[i][j];
11449
+ const data = range[i]?.[j];
11301
11450
  if (isDefined(data)) {
11302
11451
  uniqueValues.add(data.value);
11303
11452
  }
@@ -11927,7 +12076,7 @@ const SUMIF = {
11927
12076
  }
11928
12077
  let sum = 0;
11929
12078
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
11930
- const value = sumRange[i][j].value;
12079
+ const value = sumRange[i]?.[j]?.value;
11931
12080
  if (typeof value === "number") {
11932
12081
  sum += value;
11933
12082
  }
@@ -11952,7 +12101,7 @@ const SUMIFS = {
11952
12101
  compute: function (sumRange, ...criters) {
11953
12102
  let sum = 0;
11954
12103
  visitMatchingRanges(criters, (i, j) => {
11955
- const value = sumRange[i][j].value;
12104
+ const value = sumRange[i]?.[j]?.value;
11956
12105
  if (typeof value === "number") {
11957
12106
  sum += value;
11958
12107
  }
@@ -12499,7 +12648,7 @@ const AVERAGEIF = {
12499
12648
  let count = 0;
12500
12649
  let sum = 0;
12501
12650
  visitMatchingRanges([criteriaRange, criterion], (i, j) => {
12502
- const value = _averageRange[i][j].value;
12651
+ const value = _averageRange[i]?.[j]?.value;
12503
12652
  if (typeof value === "number") {
12504
12653
  count += 1;
12505
12654
  sum += value;
@@ -12528,7 +12677,7 @@ const AVERAGEIFS = {
12528
12677
  let count = 0;
12529
12678
  let sum = 0;
12530
12679
  visitMatchingRanges(args, (i, j) => {
12531
- const value = _averageRange[i][j].value;
12680
+ const value = _averageRange[i]?.[j]?.value;
12532
12681
  if (typeof value === "number") {
12533
12682
  count += 1;
12534
12683
  sum += value;
@@ -12833,7 +12982,7 @@ const MAXIFS = {
12833
12982
  compute: function (range, ...args) {
12834
12983
  let result = -Infinity;
12835
12984
  visitMatchingRanges(args, (i, j) => {
12836
- const value = range[i][j].value;
12985
+ const value = range[i]?.[j]?.value;
12837
12986
  if (typeof value === "number") {
12838
12987
  result = result < value ? value : result;
12839
12988
  }
@@ -12916,7 +13065,7 @@ const MINIFS = {
12916
13065
  compute: function (range, ...args) {
12917
13066
  let result = Infinity;
12918
13067
  visitMatchingRanges(args, (i, j) => {
12919
- const value = range[i][j].value;
13068
+ const value = range[i]?.[j]?.value;
12920
13069
  if (typeof value === "number") {
12921
13070
  result = result > value ? value : result;
12922
13071
  }
@@ -18668,6 +18817,9 @@ function addInputHandling(descr) {
18668
18817
  }
18669
18818
  args[i] = arg[0][0];
18670
18819
  }
18820
+ if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
18821
+ throw new BadExpressionError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be reference to a cell or range.", (i + 1).toString()));
18822
+ }
18671
18823
  }
18672
18824
  return descr.compute.apply(this, args);
18673
18825
  }
@@ -19670,7 +19822,7 @@ class BarChart extends AbstractChart {
19670
19822
  constructor(definition, sheetId, getters) {
19671
19823
  super(definition, sheetId, getters);
19672
19824
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
19673
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
19825
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
19674
19826
  this.background = definition.background;
19675
19827
  this.verticalAxisPosition = definition.verticalAxisPosition;
19676
19828
  this.legendPosition = definition.legendPosition;
@@ -19914,7 +20066,7 @@ class GaugeChart extends AbstractChart {
19914
20066
  type = "gauge";
19915
20067
  constructor(definition, sheetId, getters) {
19916
20068
  super(definition, sheetId, getters);
19917
- this.dataRange = createRange(this.getters, this.sheetId, definition.dataRange);
20069
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
19918
20070
  this.sectionRule = definition.sectionRule;
19919
20071
  this.background = definition.background;
19920
20072
  }
@@ -20435,7 +20587,7 @@ class LineChart extends AbstractChart {
20435
20587
  constructor(definition, sheetId, getters) {
20436
20588
  super(definition, sheetId, getters);
20437
20589
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
20438
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
20590
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
20439
20591
  this.background = definition.background;
20440
20592
  this.verticalAxisPosition = definition.verticalAxisPosition;
20441
20593
  this.legendPosition = definition.legendPosition;
@@ -20550,7 +20702,7 @@ class PieChart extends AbstractChart {
20550
20702
  constructor(definition, sheetId, getters) {
20551
20703
  super(definition, sheetId, getters);
20552
20704
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
20553
- this.labelRange = createRange(getters, sheetId, definition.labelRange);
20705
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
20554
20706
  this.background = definition.background;
20555
20707
  this.legendPosition = definition.legendPosition;
20556
20708
  this.aggregated = definition.aggregated;
@@ -20752,7 +20904,7 @@ class ScatterChart extends AbstractChart {
20752
20904
  constructor(definition, sheetId, getters) {
20753
20905
  super(definition, sheetId, getters);
20754
20906
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
20755
- this.labelRange = createRange(this.getters, sheetId, definition.labelRange);
20907
+ this.labelRange = createValidRange(this.getters, sheetId, definition.labelRange);
20756
20908
  this.background = definition.background;
20757
20909
  this.verticalAxisPosition = definition.verticalAxisPosition;
20758
20910
  this.legendPosition = definition.legendPosition;
@@ -20838,13 +20990,6 @@ function createScatterChartRuntime(chart, getters) {
20838
20990
  // have less options than the line chart (it only works with linear labels)
20839
20991
  chartJsConfig.type = "line";
20840
20992
  const configOptions = chartJsConfig.options;
20841
- configOptions.elements = {
20842
- point: {
20843
- radius: 3,
20844
- hoverRadius: 3,
20845
- hitRadius: 8,
20846
- },
20847
- };
20848
20993
  const locale = getters.getLocale();
20849
20994
  configOptions.plugins.tooltip.callbacks.title = () => "";
20850
20995
  configOptions.plugins.tooltip.callbacks.label = (tooltipItem) => {
@@ -21451,6 +21596,7 @@ function interactiveAddMerge(env, sheetId, target) {
21451
21596
  }
21452
21597
 
21453
21598
  class HoveredCellStore extends SpreadsheetStore {
21599
+ mutators = ["clear", "hover"];
21454
21600
  col;
21455
21601
  row;
21456
21602
  handle(cmd) {
@@ -21470,6 +21616,7 @@ class HoveredCellStore extends SpreadsheetStore {
21470
21616
  }
21471
21617
 
21472
21618
  class CellPopoverStore extends SpreadsheetStore {
21619
+ mutators = ["open", "close"];
21473
21620
  persistentPopover;
21474
21621
  hoveredCell = this.get(HoveredCellStore);
21475
21622
  handle(cmd) {
@@ -25961,12 +26108,13 @@ function updateSelectionWithArrowKeys(ev, selection) {
25961
26108
 
25962
26109
  // The name is misleading and can be confused with the DOM focus.
25963
26110
  class FocusStore {
26111
+ mutators = ["focus", "unfocus"];
25964
26112
  focusedElement = null;
25965
26113
  focus(element) {
25966
26114
  this.focusedElement = element;
25967
26115
  }
25968
26116
  unfocus(element) {
25969
- if (this.focusedElement && toRaw(this.focusedElement) === toRaw(element)) {
26117
+ if (this.focusedElement && this.focusedElement === element) {
25970
26118
  this.focusedElement = null;
25971
26119
  }
25972
26120
  }
@@ -25982,6 +26130,16 @@ class FocusStore {
25982
26130
  class SelectionInputStore extends SpreadsheetStore {
25983
26131
  initialRanges;
25984
26132
  inputHasSingleRange;
26133
+ mutators = [
26134
+ "resetWithRanges",
26135
+ "focusById",
26136
+ "unfocus",
26137
+ "addEmptyRange",
26138
+ "removeRange",
26139
+ "changeRange",
26140
+ "reset",
26141
+ "confirm",
26142
+ ];
25985
26143
  ranges = [];
25986
26144
  focusedRangeIndex = null;
25987
26145
  inputSheetId;
@@ -26041,7 +26199,7 @@ class SelectionInputStore extends SpreadsheetStore {
26041
26199
  row: 0,
26042
26200
  });
26043
26201
  const zone = this.getters.expandZone(cmd.sheetIdTo, positionToZone({ col, row }));
26044
- this.model.selection.resetAnchor(toRaw(this), { cell: { col, row }, zone });
26202
+ this.model.selection.resetAnchor(this, { cell: { col, row }, zone });
26045
26203
  }
26046
26204
  break;
26047
26205
  }
@@ -26060,7 +26218,7 @@ class SelectionInputStore extends SpreadsheetStore {
26060
26218
  if (focusIndex !== -1) {
26061
26219
  this.focus(focusIndex);
26062
26220
  const { left, top } = newZone;
26063
- this.model.selection.resetAnchor(toRaw(this), {
26221
+ this.model.selection.resetAnchor(this, {
26064
26222
  cell: { col: left, row: top },
26065
26223
  zone: newZone,
26066
26224
  });
@@ -26151,7 +26309,7 @@ class SelectionInputStore extends SpreadsheetStore {
26151
26309
  }
26152
26310
  get hasMainFocus() {
26153
26311
  const focusedElement = this.focusStore.focusedElement;
26154
- return !!focusedElement && toRaw(focusedElement) === toRaw(this);
26312
+ return !!focusedElement && focusedElement === this;
26155
26313
  }
26156
26314
  get highlights() {
26157
26315
  if (!this.hasMainFocus) {
@@ -26180,7 +26338,7 @@ class SelectionInputStore extends SpreadsheetStore {
26180
26338
  unfocus() {
26181
26339
  this.focusedRangeIndex = null;
26182
26340
  this.focusStore.unfocus(this);
26183
- this.model.selection.release(toRaw(this));
26341
+ this.model.selection.release(this);
26184
26342
  }
26185
26343
  captureSelection() {
26186
26344
  if (this.focusedRangeIndex === null) {
@@ -26189,7 +26347,7 @@ class SelectionInputStore extends SpreadsheetStore {
26189
26347
  const range = this.ranges[this.focusedRangeIndex];
26190
26348
  const sheetId = this.getters.getActiveSheetId();
26191
26349
  const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
26192
- this.model.selection.capture(toRaw(this), { cell: { col: zone.left, row: zone.top }, zone }, {
26350
+ this.model.selection.capture(this, { cell: { col: zone.left, row: zone.top }, zone }, {
26193
26351
  handleEvent: this.handleEvent.bind(this),
26194
26352
  release: this.unfocus.bind(this),
26195
26353
  });
@@ -26665,7 +26823,7 @@ class LineBarPieConfigPanel extends Component {
26665
26823
  }
26666
26824
  const getters = this.env.model.getters;
26667
26825
  const sheetId = getters.getActiveSheetId();
26668
- const labelRange = createRange(getters, sheetId, this.labelRange);
26826
+ const labelRange = createValidRange(getters, sheetId, this.labelRange);
26669
26827
  const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
26670
26828
  if (dataSets.length) {
26671
26829
  return dataSets[0].dataRange.zone.top + 1;
@@ -26694,15 +26852,23 @@ class BarConfigPanel extends LineBarPieConfigPanel {
26694
26852
  }
26695
26853
  }
26696
26854
 
26855
+ /**
26856
+ * Start listening to pointer events and apply the given callbacks.
26857
+ *
26858
+ * @returns A function to remove the listeners.
26859
+ */
26697
26860
  function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
26698
- const _onMouseUp = (ev) => {
26699
- onMouseUp(ev);
26861
+ const removeListeners = () => {
26700
26862
  window.removeEventListener("pointerdown", onMouseDown);
26701
26863
  window.removeEventListener("pointerup", _onMouseUp);
26702
26864
  window.removeEventListener("dragstart", _onDragStart);
26703
26865
  window.removeEventListener("pointermove", onMouseMove);
26704
26866
  window.removeEventListener("wheel", onMouseMove);
26705
26867
  };
26868
+ const _onMouseUp = (ev) => {
26869
+ onMouseUp(ev);
26870
+ removeListeners();
26871
+ };
26706
26872
  function _onDragStart(ev) {
26707
26873
  ev.preventDefault();
26708
26874
  }
@@ -26714,6 +26880,7 @@ function startDnd(onMouseMove, onMouseUp, onMouseDown = () => { }) {
26714
26880
  // preventDefault() is not allowed in passive event handler.
26715
26881
  // https://chromestatus.com/feature/6662647093133312
26716
26882
  window.addEventListener("wheel", onMouseMove, { passive: false });
26883
+ return removeListeners;
26717
26884
  }
26718
26885
  /**
26719
26886
  * Function to be used during a pointerdown event, this function allows to
@@ -27657,6 +27824,7 @@ chartSidePanelComponentRegistry
27657
27824
  });
27658
27825
 
27659
27826
  class MainChartPanelStore extends SpreadsheetStore {
27827
+ mutators = ["activatePanel"];
27660
27828
  panel = "configuration";
27661
27829
  activatePanel(panel) {
27662
27830
  this.panel = panel;
@@ -27907,6 +28075,7 @@ function useDragAndDropListItems() {
27907
28075
  state.itemsStyle = {};
27908
28076
  document.body.style.cursor = previousCursor;
27909
28077
  args.onCancel?.();
28078
+ cleanUp();
27910
28079
  };
27911
28080
  const onDragEnd = (itemId, indexAtEnd) => {
27912
28081
  state.draggedItemId = undefined;
@@ -27927,7 +28096,8 @@ function useDragAndDropListItems() {
27927
28096
  onDragEnd,
27928
28097
  onCancel: state.cancel,
27929
28098
  });
27930
- startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
28099
+ const stopListening = startDnd(dndHelper.onMouseMove.bind(dndHelper), dndHelper.onMouseUp.bind(dndHelper));
28100
+ cleanupFns.push(stopListening);
27931
28101
  const onScroll = dndHelper.onScroll.bind(dndHelper);
27932
28102
  args.containerEl.addEventListener("scroll", onScroll);
27933
28103
  cleanupFns.push(() => args.containerEl.removeEventListener("scroll", onScroll));
@@ -28004,7 +28174,7 @@ class DOMDndHelper {
28004
28174
  this.moveDraggedItemToPosition(this.currentMousePosition + this.scrollOffset);
28005
28175
  }
28006
28176
  onMouseMove(ev) {
28007
- if (ev.button !== -1) {
28177
+ if (ev.button > 1) {
28008
28178
  this.onCancel();
28009
28179
  return;
28010
28180
  }
@@ -28195,14 +28365,14 @@ function useHoveredElement(ref) {
28195
28365
 
28196
28366
  function useHighlightsOnHover(ref, highlightProvider) {
28197
28367
  const hoverState = useHoveredElement(ref);
28198
- const env = useEnv();
28368
+ const stores = useStoreProvider();
28199
28369
  useHighlights({
28200
28370
  get highlights() {
28201
28371
  return hoverState.hovered ? highlightProvider.highlights : [];
28202
28372
  },
28203
28373
  });
28204
28374
  useEffect(() => {
28205
- env.model.dispatch("RENDER_CANVAS");
28375
+ stores.trigger("store-updated");
28206
28376
  }, () => [hoverState.hovered]);
28207
28377
  }
28208
28378
  function useHighlights(highlightProvider) {
@@ -29658,6 +29828,14 @@ var Direction;
29658
29828
  Direction[Direction["next"] = 1] = "next";
29659
29829
  })(Direction || (Direction = {}));
29660
29830
  class FindAndReplaceStore extends SpreadsheetStore {
29831
+ mutators = [
29832
+ "updateSearchOptions",
29833
+ "updateSearchContent",
29834
+ "searchFormulas",
29835
+ "selectPreviousMatch",
29836
+ "selectNextMatch",
29837
+ "replace",
29838
+ ];
29661
29839
  allSheetsMatches = [];
29662
29840
  activeSheetMatches = [];
29663
29841
  specificRangeMatches = [];
@@ -29682,11 +29860,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
29682
29860
  this.initialShowFormulaState = this.model.getters.shouldShowFormulas();
29683
29861
  this.searchOptions.searchFormulas = this.initialShowFormulaState;
29684
29862
  const highlightStore = get(HighlightStore);
29685
- highlightStore.register(toRaw(this));
29863
+ highlightStore.register(this);
29686
29864
  this.onDispose(() => {
29687
29865
  this.model.dispatch("SET_FORMULA_VISIBILITY", { show: this.initialShowFormulaState });
29688
29866
  this.updateSearchContent.stopDebounce();
29689
- highlightStore.unRegister(toRaw(this));
29867
+ highlightStore.unRegister(this);
29690
29868
  });
29691
29869
  }
29692
29870
  get searchMatches() {
@@ -31166,14 +31344,14 @@ class FigureComponent extends Component {
31166
31344
  }
31167
31345
  onKeyDown(ev) {
31168
31346
  const figure = this.props.figure;
31169
- switch (ev.key) {
31347
+ const keyDownShortcut = keyboardEventToShortcutString(ev);
31348
+ switch (keyDownShortcut) {
31170
31349
  case "Delete":
31171
31350
  this.env.model.dispatch("DELETE_FIGURE", {
31172
31351
  sheetId: this.env.model.getters.getActiveSheetId(),
31173
31352
  id: figure.id,
31174
31353
  });
31175
31354
  this.props.onFigureDeleted();
31176
- ev.stopPropagation();
31177
31355
  ev.preventDefault();
31178
31356
  ev.stopPropagation();
31179
31357
  break;
@@ -31194,7 +31372,22 @@ class FigureComponent extends Component {
31194
31372
  x: figure.x + delta[0],
31195
31373
  y: figure.y + delta[1],
31196
31374
  });
31375
+ ev.preventDefault();
31376
+ ev.stopPropagation();
31377
+ break;
31378
+ case "Ctrl+A":
31379
+ // Maybe in the future we will implement a way to select all figures
31380
+ ev.preventDefault();
31197
31381
  ev.stopPropagation();
31382
+ break;
31383
+ case "Ctrl+Y":
31384
+ case "Ctrl+Z":
31385
+ if (keyDownShortcut === "Ctrl+Y") {
31386
+ this.env.model.dispatch("REQUEST_REDO");
31387
+ }
31388
+ else if (keyDownShortcut === "Ctrl+Z") {
31389
+ this.env.model.dispatch("REQUEST_UNDO");
31390
+ }
31198
31391
  ev.preventDefault();
31199
31392
  ev.stopPropagation();
31200
31393
  break;
@@ -31329,6 +31522,7 @@ unGroupHeadersMenuRegistry
31329
31522
  });
31330
31523
 
31331
31524
  class DOMFocusableElementStore {
31525
+ mutators = ["setFocusableElement", "focus"];
31332
31526
  focusableElement = undefined;
31333
31527
  setFocusableElement(element) {
31334
31528
  this.focusableElement = element;
@@ -32065,7 +32259,7 @@ class Composer extends Component {
32065
32259
  "Ctrl+Enter": this.processNewLineEvent,
32066
32260
  Escape: this.processEscapeKey,
32067
32261
  F2: () => console.warn("Not implemented"),
32068
- F4: this.processF4Key,
32262
+ F4: (ev) => this.processF4Key(ev),
32069
32263
  Tab: (ev) => this.processTabKey(ev, "right"),
32070
32264
  "Shift+Tab": (ev) => this.processTabKey(ev, "left"),
32071
32265
  };
@@ -32187,7 +32381,8 @@ class Composer extends Component {
32187
32381
  processEscapeKey() {
32188
32382
  this.composerStore.cancelEdition();
32189
32383
  }
32190
- processF4Key() {
32384
+ processF4Key(ev) {
32385
+ ev.stopPropagation();
32191
32386
  this.composerStore.cycleReferences();
32192
32387
  this.processContent();
32193
32388
  }
@@ -34390,13 +34585,6 @@ class GridRenderer {
34390
34585
  this.getters = get(ModelStore).getters;
34391
34586
  this.renderer = get(RendererStore);
34392
34587
  this.renderer.register(this);
34393
- /**
34394
- * Mark the instance as raw to avoid reactivity as this class is instanciated
34395
- * as a Store by `useGridDrawing` (which casts it as reactive).
34396
- *
34397
- * Calling `this.` on a reactive instance is significantly slower than on a raw object.
34398
- */
34399
- markRaw(this);
34400
34588
  }
34401
34589
  get renderingLayers() {
34402
34590
  return ["Background", "Headers"];
@@ -35026,7 +35214,7 @@ class GridRenderer {
35026
35214
  function useGridDrawing(refName, model, canvasSize) {
35027
35215
  const canvasRef = useRef(refName);
35028
35216
  useEffect(drawGrid);
35029
- const rendererManager = useStore(RendererStore);
35217
+ const rendererStore = useStore(RendererStore);
35030
35218
  useStore(GridRenderer);
35031
35219
  function drawGrid() {
35032
35220
  const canvas = canvasRef.el;
@@ -35054,7 +35242,11 @@ function useGridDrawing(refName, model, canvasSize) {
35054
35242
  ctx.scale(dpr, dpr);
35055
35243
  for (const layer of OrderedLayers()) {
35056
35244
  model.drawLayer(renderingContext, layer);
35057
- rendererManager.drawLayer(renderingContext, layer);
35245
+ // @ts-ignore 'drawLayer' is not declated as a mutator because:
35246
+ // it does not mutate anything. Most importantly it's used
35247
+ // during rendering. Invoking a mutator during rendering would
35248
+ // trigger another rendering, ultimately resulting in an infinite loop.
35249
+ rendererStore.drawLayer(renderingContext, layer);
35058
35250
  }
35059
35251
  }
35060
35252
  }
@@ -35456,6 +35648,7 @@ class VerticalScrollBar extends Component {
35456
35648
  }
35457
35649
 
35458
35650
  class SidePanelStore extends SpreadsheetStore {
35651
+ mutators = ["open", "toggle", "close"];
35459
35652
  initialPanelProps = {};
35460
35653
  componentTag = "";
35461
35654
  get isOpen() {
@@ -40982,12 +41175,6 @@ function compileTokens(tokens) {
40982
41175
  // detect when an argument need to be evaluated as a meta argument
40983
41176
  const isMeta = argTypes.includes("META");
40984
41177
  const hasRange = argTypes.some((t) => isRangeType(t));
40985
- const isRangeOnly = argTypes.every((t) => isRangeType(t));
40986
- if (isRangeOnly) {
40987
- if (!isRangeInput(currentArg)) {
40988
- throw new BadExpressionError(_t("Function %s expects the parameter %s to be reference to a cell or range, not a %s.", functionName, (i + 1).toString(), currentArg.type.toLowerCase()));
40989
- }
40990
- }
40991
41178
  compiledArgs.push(compileAST(currentArg, isMeta, hasRange, {
40992
41179
  functionName,
40993
41180
  paramIndex: i + 1,
@@ -41158,16 +41345,6 @@ function assertEnoughArgs(ast) {
41158
41345
  function isRangeType(type) {
41159
41346
  return type.startsWith("RANGE");
41160
41347
  }
41161
- function isRangeInput(arg) {
41162
- if (arg.type === "REFERENCE") {
41163
- return true;
41164
- }
41165
- if (arg.type === "FUNCALL") {
41166
- const fnDef = functions$1[arg.value.toUpperCase()];
41167
- return fnDef && isRangeType(fnDef.returns[0]);
41168
- }
41169
- return false;
41170
- }
41171
41348
 
41172
41349
  const functions = functionRegistry.content;
41173
41350
  function isExportableToExcel(tokens) {
@@ -43284,7 +43461,12 @@ class MergePlugin extends CorePlugin {
43284
43461
  * if they have at least a common cell
43285
43462
  */
43286
43463
  doesIntersectMerge(sheetId, zone) {
43287
- return positions(zone).some(({ col, row }) => this.getMerge({ sheetId, col, row }) !== undefined);
43464
+ for (const merge of this.getMerges(sheetId)) {
43465
+ if (overlap(zone, merge)) {
43466
+ return true;
43467
+ }
43468
+ }
43469
+ return false;
43288
43470
  }
43289
43471
  /**
43290
43472
  * Returns true if two columns have at least one merge in common
@@ -43900,6 +44082,9 @@ class RangeAdapter {
43900
44082
  if (range.invalidXc) {
43901
44083
  return range.invalidXc;
43902
44084
  }
44085
+ if (!this.getters.tryGetSheet(range.sheetId)) {
44086
+ return CellErrorType.InvalidReference;
44087
+ }
43903
44088
  if (range.zone.bottom - range.zone.top < 0 || range.zone.right - range.zone.left < 0) {
43904
44089
  return CellErrorType.InvalidReference;
43905
44090
  }
@@ -47284,7 +47469,6 @@ class PositionSet {
47284
47469
  *
47285
47470
  */
47286
47471
  class SpreadingRelation {
47287
- createEmptyPositionSet;
47288
47472
  /**
47289
47473
  * Internal structure:
47290
47474
  * For something like
@@ -47315,9 +47499,6 @@ class SpreadingRelation {
47315
47499
  */
47316
47500
  resultsToArrayFormulas = new PositionMap();
47317
47501
  arrayFormulasToResults = new PositionMap();
47318
- constructor(createEmptyPositionSet) {
47319
- this.createEmptyPositionSet = createEmptyPositionSet;
47320
- }
47321
47502
  getFormulaPositionsSpreadingOn(resultPosition) {
47322
47503
  return this.resultsToArrayFormulas.get(resultPosition) || EMPTY_ARRAY;
47323
47504
  }
@@ -47336,13 +47517,13 @@ class SpreadingRelation {
47336
47517
  */
47337
47518
  addRelation({ arrayFormulaPosition, resultPosition, }) {
47338
47519
  if (!this.resultsToArrayFormulas.has(resultPosition)) {
47339
- this.resultsToArrayFormulas.set(resultPosition, this.createEmptyPositionSet());
47520
+ this.resultsToArrayFormulas.set(resultPosition, []);
47340
47521
  }
47341
- this.resultsToArrayFormulas.get(resultPosition)?.add(arrayFormulaPosition);
47522
+ this.resultsToArrayFormulas.get(resultPosition)?.push(arrayFormulaPosition);
47342
47523
  if (!this.arrayFormulasToResults.has(arrayFormulaPosition)) {
47343
- this.arrayFormulasToResults.set(arrayFormulaPosition, this.createEmptyPositionSet());
47524
+ this.arrayFormulasToResults.set(arrayFormulaPosition, []);
47344
47525
  }
47345
- this.arrayFormulasToResults.get(arrayFormulaPosition)?.add(resultPosition);
47526
+ this.arrayFormulasToResults.get(arrayFormulaPosition)?.push(resultPosition);
47346
47527
  }
47347
47528
  hasArrayFormulaResult(position) {
47348
47529
  return this.resultsToArrayFormulas.has(position);
@@ -47363,7 +47544,7 @@ class Evaluator {
47363
47544
  evaluatedCells = new PositionMap();
47364
47545
  formulaDependencies = lazy(new FormulaDependencyGraph(this.createEmptyPositionSet.bind(this)));
47365
47546
  blockedArrayFormulas = new PositionSet({});
47366
- spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
47547
+ spreadingRelations = new SpreadingRelation();
47367
47548
  constructor(context, getters) {
47368
47549
  this.context = context;
47369
47550
  this.getters = getters;
@@ -47440,7 +47621,7 @@ class Evaluator {
47440
47621
  }
47441
47622
  buildDependencyGraph() {
47442
47623
  this.blockedArrayFormulas = this.createEmptyPositionSet();
47443
- this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
47624
+ this.spreadingRelations = new SpreadingRelation();
47444
47625
  this.formulaDependencies = lazy(() => {
47445
47626
  const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
47446
47627
  .filter((range) => !range.invalidSheetName && !range.invalidXc)
@@ -47929,16 +48110,22 @@ class EvaluationPlugin extends UIPlugin {
47929
48110
  let newContent = undefined;
47930
48111
  let newFormat = undefined;
47931
48112
  let isExported = true;
48113
+ const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
47932
48114
  const formulaCell = this.getCorrespondingFormulaCell(position);
47933
48115
  if (formulaCell) {
47934
48116
  isExported = isExportableToExcel(formulaCell.compiledFormula.tokens);
47935
48117
  isFormula = isExported;
47936
48118
  if (!isExported) {
47937
- newContent = (value ?? "").toString();
47938
- newFormat = evaluatedCell.format;
48119
+ // If the cell contains a non-exported formula and that is evaluates to
48120
+ // nothing* ,we don't export it.
48121
+ // * non-falsy value are relevant and so are 0 and FALSE, which only leaves
48122
+ // the empty string.
48123
+ if (value !== "") {
48124
+ newContent = (value ?? "").toString();
48125
+ newFormat = evaluatedCell.format;
48126
+ }
47939
48127
  }
47940
48128
  }
47941
- const exportedSheetData = data.sheets.find((sheet) => sheet.id === position.sheetId);
47942
48129
  const exportedCellData = exportedSheetData.cells[xc] || {};
47943
48130
  const format = newFormat
47944
48131
  ? getItemId(newFormat, data.formats)
@@ -49604,62 +49791,6 @@ class AutomaticSumPlugin extends UIPlugin {
49604
49791
  }
49605
49792
  }
49606
49793
 
49607
- /**
49608
- * This is a generic event bus based on the Owl event bus.
49609
- * This bus however ensures type safety across events and subscription callbacks.
49610
- */
49611
- class EventBus {
49612
- subscriptions = {};
49613
- /**
49614
- * Add a listener for the 'eventType' events.
49615
- *
49616
- * Note that the 'owner' of this event can be anything, but will more likely
49617
- * be a component or a class. The idea is that the callback will be called with
49618
- * the proper owner bound.
49619
- *
49620
- * Also, the owner should be kind of unique. This will be used to remove the
49621
- * listener.
49622
- */
49623
- on(type, owner, callback) {
49624
- if (!callback) {
49625
- throw new Error("Missing callback");
49626
- }
49627
- if (!this.subscriptions[type]) {
49628
- this.subscriptions[type] = [];
49629
- }
49630
- this.subscriptions[type].push({
49631
- owner,
49632
- callback,
49633
- });
49634
- }
49635
- /**
49636
- * Emit an event of type 'eventType'. Any extra arguments will be passed to
49637
- * the listeners callback.
49638
- */
49639
- trigger(type, payload) {
49640
- const subs = this.subscriptions[type] || [];
49641
- for (let i = 0, iLen = subs.length; i < iLen; i++) {
49642
- const sub = subs[i];
49643
- sub.callback.call(sub.owner, payload);
49644
- }
49645
- }
49646
- /**
49647
- * Remove a listener
49648
- */
49649
- off(eventType, owner) {
49650
- const subs = this.subscriptions[eventType];
49651
- if (subs) {
49652
- this.subscriptions[eventType] = subs.filter((s) => s.owner !== owner);
49653
- }
49654
- }
49655
- /**
49656
- * Remove all subscriptions.
49657
- */
49658
- clear() {
49659
- this.subscriptions = {};
49660
- }
49661
- }
49662
-
49663
49794
  /*
49664
49795
  * This file contains the specifics transformations
49665
49796
  */
@@ -51984,7 +52115,7 @@ class ClipboardPlugin extends UIPlugin {
51984
52115
  paintFormatStatus = "inactive";
51985
52116
  originSheetId;
51986
52117
  copiedData;
51987
- _isCutOperation;
52118
+ _isCutOperation = false;
51988
52119
  // ---------------------------------------------------------------------------
51989
52120
  // Command Handling
51990
52121
  // ---------------------------------------------------------------------------
@@ -51996,14 +52127,17 @@ class ClipboardPlugin extends UIPlugin {
51996
52127
  case "PASTE_FROM_OS_CLIPBOARD": {
51997
52128
  const copiedData = this.convertOSClipboardData(cmd.text);
51998
52129
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
51999
- return this.isPasteAllowed(cmd.target, copiedData, { pasteOption });
52130
+ return this.isPasteAllowed(cmd.target, copiedData, { pasteOption, isCutOperation: false });
52000
52131
  }
52001
52132
  case "PASTE": {
52002
52133
  if (!this.copiedData) {
52003
52134
  return "EmptyClipboard" /* CommandResult.EmptyClipboard */;
52004
52135
  }
52005
52136
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
52006
- return this.isPasteAllowed(cmd.target, this.copiedData, { pasteOption });
52137
+ return this.isPasteAllowed(cmd.target, this.copiedData, {
52138
+ pasteOption: pasteOption,
52139
+ isCutOperation: this._isCutOperation,
52140
+ });
52007
52141
  }
52008
52142
  case "COPY_PASTE_CELLS_ABOVE": {
52009
52143
  const zones = this.getters.getSelectedZones();
@@ -52021,13 +52155,13 @@ class ClipboardPlugin extends UIPlugin {
52021
52155
  }
52022
52156
  case "INSERT_CELL": {
52023
52157
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
52024
- const copiedData = this.copy("CUT", cut);
52025
- return this.isPasteAllowed(paste, copiedData, {});
52158
+ const copiedData = this.copy(cut);
52159
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
52026
52160
  }
52027
52161
  case "DELETE_CELL": {
52028
52162
  const { cut, paste } = this.getDeleteCellsTargets(cmd.zone, cmd.shiftDimension);
52029
- const copiedData = this.copy("CUT", cut);
52030
- return this.isPasteAllowed(paste, copiedData, {});
52163
+ const copiedData = this.copy(cut);
52164
+ return this.isPasteAllowed(paste, copiedData, { isCutOperation: true });
52031
52165
  }
52032
52166
  case "ACTIVATE_PAINT_FORMAT": {
52033
52167
  if (this.paintFormatStatus !== "inactive") {
@@ -52045,23 +52179,27 @@ class ClipboardPlugin extends UIPlugin {
52045
52179
  const zones = this.getters.getSelectedZones();
52046
52180
  this.status = "visible";
52047
52181
  this.originSheetId = this.getters.getActiveSheetId();
52048
- this.copiedData = this.copy(cmd.type, zones);
52182
+ this.copiedData = this.copy(zones);
52183
+ this._isCutOperation = cmd.type === "CUT";
52049
52184
  break;
52050
52185
  case "PASTE_FROM_OS_CLIPBOARD": {
52186
+ this._isCutOperation = false;
52051
52187
  this.copiedData = this.convertOSClipboardData(cmd.text);
52052
52188
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
52053
- this.paste(cmd.target, {
52189
+ this.paste(cmd.target, this.copiedData, {
52054
52190
  pasteOption,
52055
52191
  selectTarget: true,
52192
+ isCutOperation: false,
52056
52193
  });
52057
52194
  this.status = "invisible";
52058
52195
  break;
52059
52196
  }
52060
52197
  case "PASTE": {
52061
52198
  const pasteOption = cmd.pasteOption || (this.paintFormatStatus !== "inactive" ? "onlyFormat" : undefined);
52062
- this.paste(cmd.target, {
52199
+ this.paste(cmd.target, this.copiedData, {
52063
52200
  pasteOption,
52064
52201
  selectTarget: true,
52202
+ isCutOperation: this._isCutOperation,
52065
52203
  });
52066
52204
  if (this.paintFormatStatus === "oneOff") {
52067
52205
  this.paintFormatStatus = "inactive";
@@ -52082,9 +52220,9 @@ class ClipboardPlugin extends UIPlugin {
52082
52220
  top: multipleRowsInSelection ? zone.top : zone.top - 1,
52083
52221
  };
52084
52222
  this.originSheetId = this.getters.getActiveSheetId();
52085
- this.copiedData = this.copy("COPY", [copyTarget]);
52086
- this.paste([zone], {
52087
- pasteOption: undefined,
52223
+ const copiedData = this.copy([copyTarget]);
52224
+ this.paste([zone], copiedData, {
52225
+ isCutOperation: false,
52088
52226
  selectTarget: true,
52089
52227
  });
52090
52228
  }
@@ -52099,9 +52237,9 @@ class ClipboardPlugin extends UIPlugin {
52099
52237
  left: multipleColsInSelection ? zone.left : zone.left - 1,
52100
52238
  };
52101
52239
  this.originSheetId = this.getters.getActiveSheetId();
52102
- this.copiedData = this.copy("COPY", [copyTarget]);
52103
- this.paste([zone], {
52104
- pasteOption: undefined,
52240
+ const copiedData = this.copy([copyTarget]);
52241
+ this.paste([zone], copiedData, {
52242
+ isCutOperation: false,
52105
52243
  selectTarget: true,
52106
52244
  });
52107
52245
  }
@@ -52117,20 +52255,20 @@ class ClipboardPlugin extends UIPlugin {
52117
52255
  }
52118
52256
  break;
52119
52257
  }
52120
- this.copiedData = this.copy("CUT", cut);
52121
- this.paste(paste, {});
52258
+ const copiedData = this.copy(cut);
52259
+ this.paste(paste, copiedData, { isCutOperation: true });
52122
52260
  break;
52123
52261
  }
52124
52262
  case "INSERT_CELL": {
52125
52263
  const { cut, paste } = this.getInsertCellsTargets(cmd.zone, cmd.shiftDimension);
52126
- this.copiedData = this.copy("CUT", cut);
52127
- this.paste(paste, {});
52264
+ const copiedData = this.copy(cut);
52265
+ this.paste(paste, copiedData, { isCutOperation: true });
52128
52266
  break;
52129
52267
  }
52130
52268
  case "ADD_COLUMNS_ROWS": {
52131
52269
  this.status = "invisible";
52132
52270
  // If we add a col/row inside or before the cut area, we invalidate the clipboard
52133
- if (this._isCutOperation !== true) {
52271
+ if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
52134
52272
  return;
52135
52273
  }
52136
52274
  const isClipboardDirty = this.isColRowDirtyingClipboard(cmd.position === "before" ? cmd.base : cmd.base + 1, cmd.dimension);
@@ -52142,7 +52280,7 @@ class ClipboardPlugin extends UIPlugin {
52142
52280
  case "REMOVE_COLUMNS_ROWS": {
52143
52281
  this.status = "invisible";
52144
52282
  // If we remove a col/row inside or before the cut area, we invalidate the clipboard
52145
- if (this._isCutOperation !== true) {
52283
+ if (this._isCutOperation !== true || cmd.sheetId !== this.copiedData?.sheetId) {
52146
52284
  return;
52147
52285
  }
52148
52286
  for (let el of cmd.elements) {
@@ -52156,7 +52294,8 @@ class ClipboardPlugin extends UIPlugin {
52156
52294
  break;
52157
52295
  }
52158
52296
  case "REPEAT_PASTE": {
52159
- this.paste(cmd.target, {
52297
+ this.paste(cmd.target, this.copiedData, {
52298
+ isCutOperation: false,
52160
52299
  pasteOption: cmd.pasteOption,
52161
52300
  selectTarget: true,
52162
52301
  });
@@ -52164,7 +52303,7 @@ class ClipboardPlugin extends UIPlugin {
52164
52303
  }
52165
52304
  case "ACTIVATE_PAINT_FORMAT": {
52166
52305
  const zones = this.getters.getSelectedZones();
52167
- this.copiedData = this.copy("COPY", zones);
52306
+ this.copiedData = this.copy(zones);
52168
52307
  this.status = "visible";
52169
52308
  if (cmd.persistent) {
52170
52309
  this.paintFormatStatus = "persistent";
@@ -52195,7 +52334,6 @@ class ClipboardPlugin extends UIPlugin {
52195
52334
  }
52196
52335
  }
52197
52336
  convertOSClipboardData(clipboardData) {
52198
- this._isCutOperation = false;
52199
52337
  const handlers = clipboardHandlersRegistries.figureHandlers
52200
52338
  .getAll()
52201
52339
  .map((handler) => new handler(this.getters, this.dispatch));
@@ -52233,7 +52371,6 @@ class ClipboardPlugin extends UIPlugin {
52233
52371
  for (const handler of this.selectClipboardHandlers(copiedData)) {
52234
52372
  const result = handler.isPasteAllowed(this.getters.getActiveSheetId(), target, copiedData, {
52235
52373
  ...options,
52236
- isCutOperation: this.isCutOperation(),
52237
52374
  });
52238
52375
  if (result !== "Success" /* CommandResult.Success */) {
52239
52376
  return result;
@@ -52256,9 +52393,8 @@ class ClipboardPlugin extends UIPlugin {
52256
52393
  }
52257
52394
  return false;
52258
52395
  }
52259
- copy(operation, zones) {
52396
+ copy(zones) {
52260
52397
  let copiedData = {};
52261
- this._isCutOperation = operation === "CUT";
52262
52398
  const clipboardData = this.getClipboardData(zones);
52263
52399
  for (const handler of this.selectClipboardHandlers(clipboardData)) {
52264
52400
  const data = handler.copy(clipboardData);
@@ -52266,8 +52402,8 @@ class ClipboardPlugin extends UIPlugin {
52266
52402
  }
52267
52403
  return copiedData;
52268
52404
  }
52269
- paste(zones, options) {
52270
- if (!this.copiedData) {
52405
+ paste(zones, copiedData, options) {
52406
+ if (!copiedData) {
52271
52407
  return;
52272
52408
  }
52273
52409
  let zone = undefined;
@@ -52275,12 +52411,9 @@ class ClipboardPlugin extends UIPlugin {
52275
52411
  let target = {
52276
52412
  zones,
52277
52413
  };
52278
- const handlers = this.selectClipboardHandlers(this.copiedData);
52414
+ const handlers = this.selectClipboardHandlers(copiedData);
52279
52415
  for (const handler of handlers) {
52280
- const currentTarget = handler.getPasteTarget(zones, this.copiedData, {
52281
- ...options,
52282
- isCutOperation: this.isCutOperation(),
52283
- });
52416
+ const currentTarget = handler.getPasteTarget(zones, copiedData, options);
52284
52417
  if (currentTarget.figureId) {
52285
52418
  target.figureId = currentTarget.figureId;
52286
52419
  }
@@ -52296,7 +52429,7 @@ class ClipboardPlugin extends UIPlugin {
52296
52429
  if (zone !== undefined) {
52297
52430
  this.addMissingDimensions(this.getters.getActiveSheetId(), zone.right - zone.left + 1, zone.bottom - zone.top + 1, zone.left, zone.top);
52298
52431
  }
52299
- handlers.forEach((handler) => handler.paste(target, this.copiedData, { ...options, isCutOperation: this.isCutOperation() }));
52432
+ handlers.forEach((handler) => handler.paste(target, copiedData, options));
52300
52433
  if (!options?.selectTarget) {
52301
52434
  return;
52302
52435
  }
@@ -54766,6 +54899,7 @@ class BottomBarSheet extends Component {
54766
54899
  sheetDivRef = useRef("sheetDiv");
54767
54900
  sheetNameRef = useRef("sheetNameSpan");
54768
54901
  editionState = "initializing";
54902
+ DOMFocusableElementStore;
54769
54903
  setup() {
54770
54904
  onMounted(() => {
54771
54905
  if (this.isSheetActive) {
@@ -54778,6 +54912,7 @@ class BottomBarSheet extends Component {
54778
54912
  this.focusInputAndSelectContent();
54779
54913
  }
54780
54914
  });
54915
+ this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
54781
54916
  }
54782
54917
  focusInputAndSelectContent() {
54783
54918
  if (!this.state.isEditing || !this.sheetNameRef.el)
@@ -54819,9 +54954,11 @@ class BottomBarSheet extends Component {
54819
54954
  if (ev.key === "Enter") {
54820
54955
  ev.preventDefault();
54821
54956
  this.stopEdition();
54957
+ this.DOMFocusableElementStore.focus();
54822
54958
  }
54823
54959
  if (ev.key === "Escape") {
54824
54960
  this.cancelEdition();
54961
+ this.DOMFocusableElementStore.focus();
54825
54962
  }
54826
54963
  }
54827
54964
  onClickSheetName(ev) {
@@ -56777,16 +56914,25 @@ class Spreadsheet extends Component {
56777
56914
  }
56778
56915
  }, () => [this.env.model.getters.getActiveSheetId()]);
56779
56916
  useExternalListener(window, "resize", () => this.render(true));
56917
+ // For some reason, the wheel event is not properly registered inside templates
56918
+ // in Chromium-based browsers based on chromium 125
56919
+ // This hack ensures the event declared in the template is properly registered/working
56920
+ useExternalListener(document.body, "wheel", () => { });
56780
56921
  this.bindModelEvents();
56781
56922
  onWillUpdateProps((nextProps) => {
56782
56923
  if (nextProps.model !== this.props.model) {
56783
56924
  throw new Error("Changing the props model is not supported at the moment.");
56784
56925
  }
56785
56926
  });
56927
+ const render = batched(this.render.bind(this, true));
56786
56928
  onMounted(() => {
56787
56929
  this.checkViewportSize();
56930
+ stores.on("store-updated", this, render);
56931
+ });
56932
+ onWillUnmount(() => {
56933
+ this.unbindModelEvents();
56934
+ stores.off("store-updated", this);
56788
56935
  });
56789
- onWillUnmount(() => this.unbindModelEvents());
56790
56936
  onPatched(() => {
56791
56937
  this.checkViewportSize();
56792
56938
  });
@@ -59535,7 +59681,11 @@ function addRows(construct, data, sheet) {
59535
59681
  let cellNode = escapeXml ``;
59536
59682
  // Either formula or static value inside the cell
59537
59683
  if (cell.isFormula) {
59538
- ({ attrs: additionalAttrs, node: cellNode } = addFormula(cell));
59684
+ const res = addFormula(cell);
59685
+ if (!res) {
59686
+ continue;
59687
+ }
59688
+ ({ attrs: additionalAttrs, node: cellNode } = res);
59539
59689
  }
59540
59690
  else if (cell.content && isMarkdownLink(cell.content)) {
59541
59691
  const { label } = parseMarkdownLink(cell.content);
@@ -60574,6 +60724,6 @@ const constants = {
60574
60724
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
60575
60725
 
60576
60726
 
60577
- __info__.version = "17.2.7";
60578
- __info__.date = "2024-05-15T09:20:44.429Z";
60579
- __info__.hash = "57e89fa";
60727
+ __info__.version = "17.2.9";
60728
+ __info__.date = "2024-06-03T14:56:21.684Z";
60729
+ __info__.hash = "086af6d";