@odoo/o-spreadsheet 17.1.3 → 17.1.5

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.1.3
6
- * @date 2024-02-02T13:46:54.332Z
7
- * @hash 798f979
5
+ * @version 17.1.5
6
+ * @date 2024-02-16T14:23:54.651Z
7
+ * @hash 29d8ad6
8
8
  */
9
9
 
10
10
  'use strict';
@@ -453,8 +453,8 @@ function isObjectEmptyRecursive(argument) {
453
453
  * If the given item does not exist in the dictionary, it creates one with a new id.
454
454
  */
455
455
  function getItemId(item, itemsDic) {
456
- for (let [key, value] of Object.entries(itemsDic)) {
457
- if (deepEquals(value, item)) {
456
+ for (const key in itemsDic) {
457
+ if (deepEquals(itemsDic[key], item)) {
458
458
  return parseInt(key, 10);
459
459
  }
460
460
  }
@@ -553,11 +553,13 @@ function deepEquals(o1, o2) {
553
553
  if (typeof o1 !== "object")
554
554
  return o1 === o2;
555
555
  // Objects can have different keys if the values are undefined
556
- const keys = new Set();
557
- Object.keys(o1).forEach((key) => keys.add(key));
558
- Object.keys(o2).forEach((key) => keys.add(key));
559
- for (let key of keys) {
560
- if (typeof o1[key] !== typeof o1[key])
556
+ for (const key in o2) {
557
+ if (!(key in o1) && o2[key] !== undefined) {
558
+ return false;
559
+ }
560
+ }
561
+ for (const key in o1) {
562
+ if (typeof o1[key] !== typeof o2[key])
561
563
  return false;
562
564
  if (typeof o1[key] === "object") {
563
565
  if (!deepEquals(o1[key], o2[key]))
@@ -627,18 +629,18 @@ function isConsecutive(iterable) {
627
629
  return true;
628
630
  }
629
631
  class JetSet extends Set {
630
- add(...iterable) {
632
+ addMany(iterable) {
631
633
  for (const element of iterable) {
632
634
  super.add(element);
633
635
  }
634
636
  return this;
635
637
  }
636
- delete(...iterable) {
637
- let deleted = false;
638
+ deleteMany(iterable) {
639
+ let wasDeleted = false;
638
640
  for (const element of iterable) {
639
- deleted ||= super.delete(element);
641
+ wasDeleted ||= super.delete(element);
640
642
  }
641
- return deleted;
643
+ return wasDeleted;
642
644
  }
643
645
  }
644
646
  /**
@@ -1936,7 +1938,7 @@ class DispatchResult {
1936
1938
  * Static helper which returns a successful DispatchResult
1937
1939
  */
1938
1940
  static get Success() {
1939
- return new DispatchResult();
1941
+ return SUCCESS;
1940
1942
  }
1941
1943
  get isSuccessful() {
1942
1944
  return this.reasons.length === 0;
@@ -1949,6 +1951,7 @@ class DispatchResult {
1949
1951
  return this.reasons.includes(reason);
1950
1952
  }
1951
1953
  }
1954
+ const SUCCESS = new DispatchResult();
1952
1955
  exports.CommandResult = void 0;
1953
1956
  (function (CommandResult) {
1954
1957
  CommandResult["Success"] = "Success";
@@ -2940,6 +2943,9 @@ function formatValue(value, { format, locale }) {
2940
2943
  }
2941
2944
  switch (typeof value) {
2942
2945
  case "string":
2946
+ if (value.includes('\\"')) {
2947
+ return value.replace(/\\"/g, '"');
2948
+ }
2943
2949
  return value;
2944
2950
  case "boolean":
2945
2951
  return value ? "TRUE" : "FALSE";
@@ -4993,14 +4999,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
4993
4999
  : undefined));
4994
5000
  }
4995
5001
  }
4996
- else if (zone.left === zone.right && zone.top === zone.bottom) {
4997
- // A single cell. If it's only the title, the dataset is not added.
4998
- if (!dataSetsHaveTitle) {
4999
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
5000
- }
5001
- }
5002
5002
  else {
5003
- /* 1 row or 1 column */
5003
+ /* 1 cell, 1 row or 1 column */
5004
5004
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
5005
5005
  ? {
5006
5006
  top: zone.top,
@@ -7443,7 +7443,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7443
7443
  while (!chars.isOver()) {
7444
7444
  let token = tokenizeSpace(chars) ||
7445
7445
  tokenizeArgsSeparator(chars, locale) ||
7446
- tokenizeMisc(chars) ||
7446
+ tokenizeParenthesis(chars) ||
7447
7447
  tokenizeOperator(chars) ||
7448
7448
  tokenizeString(chars) ||
7449
7449
  tokenizeDebugger(chars) ||
@@ -7464,15 +7464,14 @@ function tokenizeDebugger(chars) {
7464
7464
  }
7465
7465
  return null;
7466
7466
  }
7467
- const misc$1 = {
7468
- "(": "LEFT_PAREN",
7469
- ")": "RIGHT_PAREN",
7467
+ const parenthesis = {
7468
+ "(": { type: "LEFT_PAREN", value: "(" },
7469
+ ")": { type: "RIGHT_PAREN", value: ")" },
7470
7470
  };
7471
- function tokenizeMisc(chars) {
7472
- if (chars.current in misc$1) {
7471
+ function tokenizeParenthesis(chars) {
7472
+ if (chars.current === "(" || chars.current === ")") {
7473
7473
  const value = chars.shift();
7474
- const type = misc$1[value];
7475
- return { type, value };
7474
+ return parenthesis[value];
7476
7475
  }
7477
7476
  return null;
7478
7477
  }
@@ -7493,7 +7492,12 @@ function tokenizeOperator(chars) {
7493
7492
  }
7494
7493
  return null;
7495
7494
  }
7495
+ const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789");
7496
7496
  function tokenizeNumber(chars, locale) {
7497
+ if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) &&
7498
+ chars.current !== locale.decimalSeparator) {
7499
+ return null;
7500
+ }
7497
7501
  const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
7498
7502
  if (match) {
7499
7503
  chars.advanceBy(match[0].length);
@@ -7518,7 +7522,7 @@ function tokenizeString(chars) {
7518
7522
  }
7519
7523
  return null;
7520
7524
  }
7521
- const separatorRegexp = /\w|\.|!|\$/;
7525
+ const separatorRegexp = /^[\w\.!\$]+/;
7522
7526
  /**
7523
7527
  * A "Symbol" is just basically any word-like element that can appear in a
7524
7528
  * formula, which is not a string. So:
@@ -7558,8 +7562,11 @@ function tokenizeSymbol(chars) {
7558
7562
  };
7559
7563
  }
7560
7564
  }
7561
- while (chars.current && separatorRegexp.test(chars.current)) {
7562
- result += chars.shift();
7565
+ const match = chars.remaining().match(separatorRegexp);
7566
+ if (match) {
7567
+ const value = match[0];
7568
+ result += value;
7569
+ chars.advanceBy(value.length);
7563
7570
  }
7564
7571
  if (result.length) {
7565
7572
  const value = result;
@@ -7621,7 +7628,10 @@ class TokenizingChars {
7621
7628
  return this.text.substring(this.currentIndex);
7622
7629
  }
7623
7630
  currentStartsWith(str) {
7624
- for (let j = 0; j < str.length; j++) {
7631
+ if (this.current !== str[0]) {
7632
+ return false;
7633
+ }
7634
+ for (let j = 1; j < str.length; j++) {
7625
7635
  if (this.text[this.currentIndex + j] !== str[j]) {
7626
7636
  return false;
7627
7637
  }
@@ -7648,6 +7658,9 @@ function isValidLocale(locale) {
7648
7658
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7649
7659
  return false;
7650
7660
  }
7661
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7662
+ return false;
7663
+ }
7651
7664
  try {
7652
7665
  formatValue(1, { locale, format: "#,##0.00" });
7653
7666
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7738,7 +7751,7 @@ function canonicalizeNumberLiteral(content, locale) {
7738
7751
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7739
7752
  return content;
7740
7753
  }
7741
- return content.replace(locale.decimalSeparator, ".");
7754
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7742
7755
  }
7743
7756
  /**
7744
7757
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -8653,7 +8666,7 @@ class BarChart extends AbstractChart {
8653
8666
  return undefined;
8654
8667
  const dataSets = this.dataSets
8655
8668
  .map((ds) => toExcelDataset(this.getters, ds))
8656
- .filter((ds) => ds.range !== ""); // && range !== CellErrorType.InvalidReference ? show incorrect #ref ?
8669
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
8657
8670
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
8658
8671
  return {
8659
8672
  ...this.getDefinition(),
@@ -9231,7 +9244,7 @@ class LineChart extends AbstractChart {
9231
9244
  return undefined;
9232
9245
  const dataSets = this.dataSets
9233
9246
  .map((ds) => toExcelDataset(this.getters, ds))
9234
- .filter((ds) => ds.range !== ""); // && range !== CellErrorType.InvalidReference ? show incorrect #ref ?
9247
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
9235
9248
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
9236
9249
  return {
9237
9250
  ...this.getDefinition(),
@@ -9537,7 +9550,7 @@ class PieChart extends AbstractChart {
9537
9550
  return undefined;
9538
9551
  const dataSets = this.dataSets
9539
9552
  .map((ds) => toExcelDataset(this.getters, ds))
9540
- .filter((ds) => ds.range !== ""); // && range !== CellErrorType.InvalidReference ? show incorrect #ref ?
9553
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
9541
9554
  const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
9542
9555
  return {
9543
9556
  ...this.getDefinition(),
@@ -10833,6 +10846,12 @@ const INSERT_LINK = (env) => {
10833
10846
  let { col, row } = env.model.getters.getActivePosition();
10834
10847
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10835
10848
  };
10849
+ const INSERT_LINK_NAME = (env) => {
10850
+ const sheetId = env.model.getters.getActiveSheetId();
10851
+ const { col, row } = env.model.getters.getActivePosition();
10852
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10853
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10854
+ };
10836
10855
  //------------------------------------------------------------------------------
10837
10856
  // Filters action
10838
10857
  //------------------------------------------------------------------------------
@@ -18684,6 +18703,9 @@ const HLOOKUP = {
18684
18703
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18685
18704
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18686
18705
  assert(() => 1 <= _index && _index <= range[0].length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18706
+ if (searchKey && isEvaluationError(searchKey.value)) {
18707
+ return searchKey;
18708
+ }
18687
18709
  const getValueFromRange = (range, index) => range[index][0].value;
18688
18710
  const _isSorted = toBoolean(isSorted.value);
18689
18711
  const colIndex = _isSorted
@@ -18851,6 +18873,9 @@ const VLOOKUP = {
18851
18873
  compute: function (searchKey, range, index, isSorted = { value: DEFAULT_IS_SORTED }) {
18852
18874
  const _index = Math.trunc(toNumber(index?.value, this.locale));
18853
18875
  assert(() => 1 <= _index && _index <= range.length, _t("[[FUNCTION_NAME]] evaluates to an out of bounds range."));
18876
+ if (searchKey && isEvaluationError(searchKey.value)) {
18877
+ return searchKey;
18878
+ }
18854
18879
  const getValueFromRange = (range, index) => range[0][index].value;
18855
18880
  const _isSorted = toBoolean(isSorted.value);
18856
18881
  const rowIndex = _isSorted
@@ -18890,6 +18915,9 @@ const XLOOKUP = {
18890
18915
  assert(() => lookupDirection === "col"
18891
18916
  ? returnRange[0].length === lookupRange[0].length
18892
18917
  : returnRange.length === lookupRange.length, _t("return_range should have the same dimensions as lookup_range."));
18918
+ if (searchKey && isEvaluationError(searchKey.value)) {
18919
+ return [[searchKey]];
18920
+ }
18893
18921
  const getElement = lookupDirection === "col"
18894
18922
  ? (range, index) => range[0][index].value
18895
18923
  : (range, index) => range[index][0].value;
@@ -20068,7 +20096,7 @@ cellMenuRegistry
20068
20096
  })
20069
20097
  .add("insert_link", {
20070
20098
  ...insertLink,
20071
- name: _t("Insert link"),
20099
+ name: INSERT_LINK_NAME,
20072
20100
  sequence: 150,
20073
20101
  separator: true,
20074
20102
  });
@@ -26511,6 +26539,17 @@ class TextValueProvider extends owl.Component {
26511
26539
  onValueSelected: Function,
26512
26540
  onValueHovered: Function,
26513
26541
  };
26542
+ autoCompleteListRef = owl.useRef("autoCompleteList");
26543
+ setup() {
26544
+ owl.useEffect(() => {
26545
+ const selectedIndex = this.props.selectedIndex;
26546
+ if (selectedIndex === undefined) {
26547
+ return;
26548
+ }
26549
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26550
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26551
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26552
+ }
26514
26553
  }
26515
26554
 
26516
26555
  class ContentEditableHelper {
@@ -26955,6 +26994,7 @@ css /* scss */ `
26955
26994
  position: absolute;
26956
26995
  margin: 1px 4px;
26957
26996
  pointer-events: none;
26997
+ overflow: auto;
26958
26998
 
26959
26999
  .o-semi-bold {
26960
27000
  /** FIXME: to remove in favor of Bootstrap
@@ -26971,15 +27011,17 @@ class Composer extends owl.Component {
26971
27011
  focus: {
26972
27012
  validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value),
26973
27013
  },
26974
- onComposerContentFocused: Function,
26975
27014
  inputStyle: { type: String, optional: true },
26976
27015
  rect: { type: Object, optional: true },
26977
27016
  delimitation: { type: Object, optional: true },
26978
- onComposerUnmounted: { type: Function, optional: true },
27017
+ onComposerCellFocused: { type: Function, optional: true },
27018
+ onComposerContentFocused: Function,
27019
+ isDefaultFocus: { type: Boolean, optional: true },
26979
27020
  };
26980
27021
  static components = { TextValueProvider, FunctionDescriptionProvider };
26981
27022
  static defaultProps = {
26982
27023
  inputStyle: "",
27024
+ isDefaultFocus: false,
26983
27025
  };
26984
27026
  composerRef = owl.useRef("o_composer");
26985
27027
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -27010,7 +27052,10 @@ class Composer extends owl.Component {
27010
27052
  if (this.props.delimitation && this.props.rect) {
27011
27053
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
27012
27054
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
27055
+ assistantStyle["max-height"] = `${remainingHeight}px`;
27013
27056
  if (cellY > remainingHeight) {
27057
+ const availableSpaceAbove = cellY;
27058
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
27014
27059
  // render top
27015
27060
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
27016
27061
  assistantStyle.top = `-3px`;
@@ -27036,7 +27081,6 @@ class Composer extends owl.Component {
27036
27081
  F4: this.processF4Key,
27037
27082
  Tab: (ev) => this.processTabKey(ev, "right"),
27038
27083
  "Shift+Tab": (ev) => this.processTabKey(ev, "left"),
27039
- "Ctrl+ ": this.processSpaceKey,
27040
27084
  };
27041
27085
  keyCodeMapping = {
27042
27086
  NumpadDecimal: this.processNumpadDecimal,
@@ -27044,12 +27088,12 @@ class Composer extends owl.Component {
27044
27088
  setup() {
27045
27089
  owl.onMounted(() => {
27046
27090
  const el = this.composerRef.el;
27091
+ if (this.props.isDefaultFocus) {
27092
+ this.env.focusableElement.setFocusableElement(el);
27093
+ }
27047
27094
  this.contentHelper.updateEl(el);
27048
27095
  this.processTokenAtCursor();
27049
27096
  });
27050
- owl.onWillUnmount(() => {
27051
- this.props.onComposerUnmounted?.();
27052
- });
27053
27097
  owl.useEffect(() => {
27054
27098
  this.processContent();
27055
27099
  });
@@ -27058,7 +27102,8 @@ class Composer extends owl.Component {
27058
27102
  // Handlers
27059
27103
  // ---------------------------------------------------------------------------
27060
27104
  processArrowKeys(ev) {
27061
- if (this.env.model.getters.isSelectingForComposer()) {
27105
+ if (this.env.model.getters.isSelectingForComposer() ||
27106
+ this.env.model.getters.getEditionMode() === "inactive") {
27062
27107
  this.functionDescriptionState.showDescription = false;
27063
27108
  // Prevent the default content editable behavior which moves the cursor
27064
27109
  ev.preventDefault();
@@ -27101,23 +27146,19 @@ class Composer extends owl.Component {
27101
27146
  processTabKey(ev, direction) {
27102
27147
  ev.preventDefault();
27103
27148
  ev.stopPropagation();
27104
- const state = this.autoCompleteState;
27105
- if (state.showProvider && state.selectedIndex !== undefined) {
27106
- const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
27107
- if (autoCompleteValue) {
27108
- this.autoComplete(autoCompleteValue);
27109
- return;
27149
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
27150
+ const state = this.autoCompleteState;
27151
+ if (state.showProvider && state.selectedIndex !== undefined) {
27152
+ const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
27153
+ if (autoCompleteValue) {
27154
+ this.autoComplete(autoCompleteValue);
27155
+ return;
27156
+ }
27110
27157
  }
27158
+ interactiveStopEdition(this.env);
27111
27159
  }
27112
- interactiveStopEdition(this.env);
27113
27160
  this.env.model.selection.moveAnchorCell(direction, 1);
27114
27161
  }
27115
- processSpaceKey(ev) {
27116
- ev.preventDefault();
27117
- ev.stopPropagation();
27118
- this.showFunctionAutocomplete("");
27119
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
27120
- }
27121
27162
  processEnterKey(ev, direction) {
27122
27163
  ev.preventDefault();
27123
27164
  ev.stopPropagation();
@@ -27179,6 +27220,9 @@ class Composer extends owl.Component {
27179
27220
  this.compositionActive = false;
27180
27221
  }
27181
27222
  onKeydown(ev) {
27223
+ if (this.env.model.getters.getEditionMode() === "inactive") {
27224
+ return;
27225
+ }
27182
27226
  if (ev.key.startsWith("Arrow")) {
27183
27227
  this.processArrowKeys(ev);
27184
27228
  return;
@@ -27192,14 +27236,33 @@ class Composer extends owl.Component {
27192
27236
  ev.stopPropagation();
27193
27237
  }
27194
27238
  }
27239
+ onPaste(ev) {
27240
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
27241
+ ev.stopPropagation();
27242
+ }
27243
+ }
27195
27244
  /*
27196
27245
  * Triggered automatically by the content-editable between the keydown and key up
27197
27246
  * */
27198
27247
  onInput(ev) {
27199
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
27248
+ if (!this.shouldProcessInputEvents) {
27200
27249
  return;
27201
27250
  }
27202
- let content = this.contentHelper.getText();
27251
+ if (ev.inputType === "insertFromPaste" &&
27252
+ this.env.model.getters.getEditionMode() === "inactive") {
27253
+ return;
27254
+ }
27255
+ ev.stopPropagation();
27256
+ let content;
27257
+ if (this.env.model.getters.getEditionMode() === "inactive") {
27258
+ content = ev.data || "";
27259
+ }
27260
+ else {
27261
+ content = this.contentHelper.getText();
27262
+ }
27263
+ if (this.props.focus === "inactive") {
27264
+ return this.props.onComposerCellFocused?.(content);
27265
+ }
27203
27266
  let selection = this.contentHelper.getCurrentSelection();
27204
27267
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
27205
27268
  this.env.model.dispatch("SET_CURRENT_CONTENT", {
@@ -27275,9 +27338,8 @@ class Composer extends owl.Component {
27275
27338
  }
27276
27339
  const newSelection = this.contentHelper.getCurrentSelection();
27277
27340
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
27278
- if (this.props.focus === "inactive") {
27279
- this.props.onComposerContentFocused(newSelection);
27280
- }
27341
+ this.props.onComposerContentFocused();
27342
+ if (this.props.focus === "inactive") ;
27281
27343
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
27282
27344
  this.processTokenAtCursor();
27283
27345
  }
@@ -27444,7 +27506,7 @@ class Composer extends owl.Component {
27444
27506
  const highlights = this.env.model.getters.getHighlights();
27445
27507
  const refSheet = sheetName
27446
27508
  ? this.env.model.getters.getSheetIdByName(sheetName)
27447
- : this.env.model.getters.getCurrentEditedCell().sheetId;
27509
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
27448
27510
  const highlight = highlights.find((highlight) => {
27449
27511
  if (highlight.sheetId !== refSheet)
27450
27512
  return false;
@@ -27571,56 +27633,30 @@ class GridComposer extends owl.Component {
27571
27633
  focus: {
27572
27634
  validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value),
27573
27635
  },
27574
- onComposerUnmounted: Function,
27636
+ onComposerCellFocused: Function,
27575
27637
  onComposerContentFocused: Function,
27576
27638
  gridDims: Object,
27577
27639
  };
27578
27640
  static components = { Composer };
27579
- gridComposerRef;
27580
- zone;
27581
- rect;
27641
+ rect = this.defaultRect;
27642
+ isEditing = false;
27582
27643
  isCellReferenceVisible;
27583
- composerState;
27644
+ get defaultRect() {
27645
+ return { x: 0, y: 0, width: 0, height: 0 };
27646
+ }
27584
27647
  setup() {
27585
- this.gridComposerRef = owl.useRef("gridComposer");
27586
- this.composerState = owl.useState({
27587
- rect: undefined,
27588
- delimitation: undefined,
27589
- });
27590
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
27591
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
27592
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
27593
- this.isCellReferenceVisible = false;
27594
- owl.onMounted(() => {
27595
- const el = this.gridComposerRef.el;
27596
- this.composerState.rect = {
27597
- x: this.rect.x,
27598
- y: this.rect.y,
27599
- width: el.clientWidth,
27600
- height: el.clientHeight,
27601
- };
27602
- this.composerState.delimitation = {
27603
- width: this.props.gridDims.width,
27604
- height: this.props.gridDims.height,
27605
- };
27606
- });
27607
27648
  owl.onWillUpdateProps(() => {
27608
- if (this.isCellReferenceVisible) {
27609
- return;
27610
- }
27611
- const sheetId = this.env.model.getters.getActiveSheetId();
27612
- const zone = this.env.model.getters.getSelectedZone();
27613
- const rect = this.env.model.getters.getVisibleRect(zone);
27614
- if (!deepEquals(rect, this.rect) ||
27615
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27616
- this.isCellReferenceVisible = true;
27617
- }
27649
+ this.updateComponentPosition();
27650
+ this.updateCellReferenceVisibility();
27618
27651
  });
27619
27652
  }
27620
27653
  get shouldDisplayCellReference() {
27621
27654
  return this.isCellReferenceVisible;
27622
27655
  }
27623
27656
  get cellReference() {
27657
+ if (!this.env.model.getters.getCurrentEditedCell()) {
27658
+ return "";
27659
+ }
27624
27660
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27625
27661
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27626
27662
  return getFullReference(prefixSheet ? this.env.model.getters.getSheetName(sheetId) : undefined, toXC(col, row));
@@ -27632,7 +27668,27 @@ class GridComposer extends owl.Component {
27632
27668
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
27633
27669
  });
27634
27670
  }
27671
+ get composerProps() {
27672
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27673
+ return {
27674
+ rect: { ...this.rect },
27675
+ delimitation: {
27676
+ width,
27677
+ height,
27678
+ },
27679
+ focus: this.props.focus,
27680
+ isDefaultFocus: true,
27681
+ onComposerContentFocused: this.props.onComposerContentFocused,
27682
+ onComposerCellFocused: this.props.onComposerCellFocused,
27683
+ };
27684
+ }
27635
27685
  get containerStyle() {
27686
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27687
+ return `
27688
+ position: absolute;
27689
+ z-index: -1000;
27690
+ `;
27691
+ }
27636
27692
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27637
27693
  const cell = this.env.model.getters.getActiveCell();
27638
27694
  const position = this.env.model.getters.getActivePosition();
@@ -27652,6 +27708,8 @@ class GridComposer extends owl.Component {
27652
27708
  if (!isFormula) {
27653
27709
  textAlign = style.align || cell.defaultAlign;
27654
27710
  }
27711
+ const maxHeight = this.props.gridDims.height - this.rect.y;
27712
+ const maxWidth = this.props.gridDims.width - this.rect.x;
27655
27713
  /**
27656
27714
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
27657
27715
  * including all the paddings/margins of the composer
@@ -27663,6 +27721,8 @@ class GridComposer extends owl.Component {
27663
27721
  top: `${top}px`,
27664
27722
  "min-width": `${width + 1}px`,
27665
27723
  "min-height": `${height + 1}px`,
27724
+ "max-width": `${maxWidth}px`,
27725
+ "max-height": `${maxHeight}px`,
27666
27726
  background,
27667
27727
  color,
27668
27728
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -27672,13 +27732,31 @@ class GridComposer extends owl.Component {
27672
27732
  "text-align": textAlign,
27673
27733
  });
27674
27734
  }
27675
- get composerStyle() {
27676
- const maxHeight = this.props.gridDims.height - this.rect.y;
27677
- const maxWidth = this.props.gridDims.width - this.rect.x;
27678
- return cssPropertiesToCss({
27679
- "max-width": `${maxWidth}px`,
27680
- "max-height": `${maxHeight}px`,
27681
- });
27735
+ updateComponentPosition() {
27736
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
27737
+ if (this.isEditing !== isEditing) {
27738
+ this.isEditing = isEditing;
27739
+ if (!isEditing) {
27740
+ this.rect = this.defaultRect;
27741
+ this.env.focusableElement.focus();
27742
+ return;
27743
+ }
27744
+ const position = this.env.model.getters.getActivePosition();
27745
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
27746
+ this.rect = this.env.model.getters.getVisibleRect(zone);
27747
+ }
27748
+ }
27749
+ updateCellReferenceVisibility() {
27750
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27751
+ return;
27752
+ }
27753
+ const sheetId = this.env.model.getters.getActiveSheetId();
27754
+ const zone = this.env.model.getters.getSelectedZone();
27755
+ const rect = this.env.model.getters.getVisibleRect(zone);
27756
+ if (!deepEquals(rect, this.rect) ||
27757
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27758
+ this.isCellReferenceVisible = true;
27759
+ }
27682
27760
  }
27683
27761
  }
27684
27762
 
@@ -28778,7 +28856,6 @@ class GridOverlay extends owl.Component {
28778
28856
  this.props.onCellDoubleClicked(col, row);
28779
28857
  }
28780
28858
  onContextMenu(ev) {
28781
- ev.preventDefault();
28782
28859
  const [col, row] = this.getCartesianCoordinates(ev);
28783
28860
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
28784
28861
  }
@@ -29843,7 +29920,6 @@ class Grid extends owl.Component {
29843
29920
  HEADER_WIDTH = HEADER_WIDTH;
29844
29921
  menuState;
29845
29922
  gridRef;
29846
- hiddenInput;
29847
29923
  onMouseWheel;
29848
29924
  canvasPosition;
29849
29925
  hoveredCell;
@@ -29854,15 +29930,14 @@ class Grid extends owl.Component {
29854
29930
  menuItems: [],
29855
29931
  });
29856
29932
  this.gridRef = owl.useRef("grid");
29857
- this.hiddenInput = owl.useRef("hiddenInput");
29858
29933
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
29859
29934
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
29860
29935
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
29861
29936
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
29862
29937
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
29863
29938
  owl.useExternalListener(document.body, "paste", this.paste);
29864
- owl.onMounted(() => this.focus());
29865
- this.props.exposeFocus(() => this.focus());
29939
+ owl.onMounted(() => this.focusDefaultElement());
29940
+ this.props.exposeFocus(() => this.focusDefaultElement());
29866
29941
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29867
29942
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29868
29943
  this.moveCanvas(deltaX, deltaY);
@@ -29886,7 +29961,7 @@ class Grid extends owl.Component {
29886
29961
  if (this.env.model.getters.hasOpenedPopover()) {
29887
29962
  this.closeOpenedPopover();
29888
29963
  }
29889
- this.focus();
29964
+ this.focusDefaultElement();
29890
29965
  }
29891
29966
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
29892
29967
  // down itself
@@ -30065,10 +30140,10 @@ class Grid extends owl.Component {
30065
30140
  "Alt+Shift+ArrowUp": () => this.processHeaderGroupingKey("up"),
30066
30141
  "Alt+Shift+ArrowDown": () => this.processHeaderGroupingKey("down"),
30067
30142
  };
30068
- focus() {
30143
+ focusDefaultElement() {
30069
30144
  if (!this.env.model.getters.getSelectedFigureId() &&
30070
30145
  this.env.model.getters.getEditionMode() === "inactive") {
30071
- this.hiddenInput.el?.focus();
30146
+ this.env.focusableElement.focus();
30072
30147
  }
30073
30148
  }
30074
30149
  get gridEl() {
@@ -30204,19 +30279,6 @@ class Grid extends owl.Component {
30204
30279
  return;
30205
30280
  }
30206
30281
  }
30207
- onInput(ev) {
30208
- // the user meant to paste in the sheet, not open the composer with the pasted content
30209
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
30210
- return;
30211
- }
30212
- if (ev.data) {
30213
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
30214
- // character
30215
- ev.preventDefault();
30216
- ev.stopPropagation();
30217
- this.props.onGridComposerCellFocused(ev.data);
30218
- }
30219
- }
30220
30282
  // ---------------------------------------------------------------------------
30221
30283
  // Context Menu
30222
30284
  // ---------------------------------------------------------------------------
@@ -30331,7 +30393,7 @@ class Grid extends owl.Component {
30331
30393
  }
30332
30394
  closeMenu() {
30333
30395
  this.menuState.isOpen = false;
30334
- this.focus();
30396
+ this.focusDefaultElement();
30335
30397
  }
30336
30398
  processHeaderGroupingKey(direction) {
30337
30399
  if (this.env.model.getters.getSelectedZones().length !== 1) {
@@ -34248,6 +34310,31 @@ const MIGRATIONS = [
34248
34310
  return data;
34249
34311
  },
34250
34312
  },
34313
+ {
34314
+ description: "Fix datafilter duplication",
34315
+ from: 12,
34316
+ to: 12.5,
34317
+ applyMigration(data) {
34318
+ for (let sheet of data.sheets || []) {
34319
+ let knownDataFilterZones = [];
34320
+ for (let filterTable of sheet.filterTables || []) {
34321
+ const zone = toZone(filterTable.range);
34322
+ // See commit message for the details
34323
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
34324
+ if (intersectZoneIndex !== -1) {
34325
+ knownDataFilterZones[intersectZoneIndex] = zone;
34326
+ }
34327
+ else {
34328
+ knownDataFilterZones.push(zone);
34329
+ }
34330
+ }
34331
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
34332
+ range: zoneToXc(zone),
34333
+ }));
34334
+ }
34335
+ return data;
34336
+ },
34337
+ },
34251
34338
  {
34252
34339
  description: "Change Border description structure",
34253
34340
  from: 12,
@@ -35087,22 +35174,7 @@ class BordersPlugin extends CorePlugin {
35087
35174
  }
35088
35175
  }
35089
35176
  export(data) {
35090
- // Borders
35091
- let borderId = 0;
35092
35177
  const borders = {};
35093
- /**
35094
- * Get the id of the given border. If the border does not exist, it creates
35095
- * one.
35096
- */
35097
- function getBorderId(border) {
35098
- for (let [key, value] of Object.entries(borders)) {
35099
- if (deepEquals(value, border)) {
35100
- return parseInt(key, 10);
35101
- }
35102
- }
35103
- borders[++borderId] = border;
35104
- return borderId;
35105
- }
35106
35178
  for (let sheet of data.sheets) {
35107
35179
  for (let col = 0; col < sheet.colNumber; col++) {
35108
35180
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -35110,7 +35182,7 @@ class BordersPlugin extends CorePlugin {
35110
35182
  if (border) {
35111
35183
  const xc = toXC(col, row);
35112
35184
  const cell = sheet.cells[xc];
35113
- const borderId = getBorderId(border);
35185
+ const borderId = getItemId(border, borders);
35114
35186
  if (cell) {
35115
35187
  cell.border = borderId;
35116
35188
  }
@@ -35899,9 +35971,9 @@ class CellPlugin extends CorePlugin {
35899
35971
  allowDispatch(cmd) {
35900
35972
  switch (cmd.type) {
35901
35973
  case "UPDATE_CELL":
35902
- return this.checkCellOutOfSheet(cmd);
35974
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35903
35975
  case "CLEAR_CELL":
35904
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35976
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35905
35977
  default:
35906
35978
  return "Success" /* CommandResult.Success */;
35907
35979
  }
@@ -36034,9 +36106,6 @@ class CellPlugin extends CorePlugin {
36034
36106
  format: cell.format ? getItemId(cell.format, formats) : undefined,
36035
36107
  content: cell.content || undefined,
36036
36108
  };
36037
- if (cell instanceof FormulaCellWithDependencies) {
36038
- cells[xc].content = cell.contentWithFixedReferences || undefined;
36039
- }
36040
36109
  }
36041
36110
  _sheet.cells = cells;
36042
36111
  }
@@ -36054,8 +36123,8 @@ class CellPlugin extends CorePlugin {
36054
36123
  }
36055
36124
  removeDefaultStyleValues(style) {
36056
36125
  const cleanedStyle = { ...style };
36057
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
36058
- if (cleanedStyle[property] === defaultValue) {
36126
+ for (const property in DEFAULT_STYLE) {
36127
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
36059
36128
  delete cleanedStyle[property];
36060
36129
  }
36061
36130
  }
@@ -36211,10 +36280,7 @@ class CellPlugin extends CorePlugin {
36211
36280
  else {
36212
36281
  style = before ? before.style : undefined;
36213
36282
  }
36214
- const locale = this.getters.getLocale();
36215
- let format = ("format" in after ? after.format : before && before.format) ||
36216
- detectDateFormat(afterContent, locale) ||
36217
- detectNumberFormat(afterContent);
36283
+ const format = "format" in after ? after.format : before && before.format;
36218
36284
  /* Read the following IF as:
36219
36285
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
36220
36286
  * - the command says the new content is empty and has no border/format/style
@@ -36255,6 +36321,7 @@ class CellPlugin extends CorePlugin {
36255
36321
  }
36256
36322
  createLiteralCell(id, content, format, style) {
36257
36323
  const locale = this.getters.getLocale();
36324
+ format = format || detectDateFormat(content, locale) || detectNumberFormat(content);
36258
36325
  if (format !== PLAIN_TEXT_FORMAT && !isEvaluationError(content)) {
36259
36326
  content = toString(parseLiteral(content, locale));
36260
36327
  }
@@ -36288,8 +36355,11 @@ class CellPlugin extends CorePlugin {
36288
36355
  * being a computed property to rebuild the dependencies XC.
36289
36356
  */
36290
36357
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
36291
- const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
36292
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
36358
+ const dependencies = [];
36359
+ for (const xc of compiledFormula.dependencies) {
36360
+ dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc));
36361
+ }
36362
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString);
36293
36363
  }
36294
36364
  createErrorFormula(id, content, format, style, error) {
36295
36365
  return {
@@ -36324,6 +36394,18 @@ class CellPlugin extends CorePlugin {
36324
36394
  }
36325
36395
  return "Success" /* CommandResult.Success */;
36326
36396
  }
36397
+ checkUselessUpdateCell(cmd) {
36398
+ const cell = this.getters.getCell(cmd);
36399
+ const hasContent = "content" in cmd || "formula" in cmd;
36400
+ const hasStyle = "style" in cmd;
36401
+ const hasFormat = "format" in cmd;
36402
+ if ((!hasContent || cell?.content === cmd.content) &&
36403
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
36404
+ (!hasFormat || cell?.format === cmd.format)) {
36405
+ return "NoChanges" /* CommandResult.NoChanges */;
36406
+ }
36407
+ return "Success" /* CommandResult.Success */;
36408
+ }
36327
36409
  }
36328
36410
  class FormulaCellWithDependencies {
36329
36411
  id;
@@ -36343,7 +36425,7 @@ class FormulaCellWithDependencies {
36343
36425
  const tokens = compiledFormula.tokens.map((token) => {
36344
36426
  if (token.type === "REFERENCE") {
36345
36427
  const index = rangeIndex++;
36346
- return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
36428
+ return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString);
36347
36429
  }
36348
36430
  return token;
36349
36431
  });
@@ -36370,13 +36452,20 @@ class FormulaCellWithDependencies {
36370
36452
  }
36371
36453
  }
36372
36454
  class RangeReferenceToken {
36373
- getValue;
36455
+ ranges;
36456
+ rangeIndex;
36457
+ sheetId;
36458
+ getRangeString;
36374
36459
  type = "REFERENCE";
36375
- constructor(getValue) {
36376
- this.getValue = getValue;
36460
+ constructor(ranges, rangeIndex, sheetId, getRangeString) {
36461
+ this.ranges = ranges;
36462
+ this.rangeIndex = rangeIndex;
36463
+ this.sheetId = sheetId;
36464
+ this.getRangeString = getRangeString;
36377
36465
  }
36378
36466
  get value() {
36379
- return this.getValue();
36467
+ const range = this.ranges[this.rangeIndex];
36468
+ return this.getRangeString(range, this.sheetId);
36380
36469
  }
36381
36470
  }
36382
36471
 
@@ -41101,7 +41190,15 @@ class SpreadsheetRTree {
41101
41190
  if (!this.rTrees[sheetId]) {
41102
41191
  return;
41103
41192
  }
41104
- this.rTrees[sheetId].remove(item, deepEquals);
41193
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
41194
+ }
41195
+ rtreeItemComparer(left, right) {
41196
+ return (left.data == right.data &&
41197
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
41198
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
41199
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
41200
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
41201
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
41105
41202
  }
41106
41203
  }
41107
41204
  /**
@@ -41179,7 +41276,7 @@ class FormulaDependencyGraph {
41179
41276
  const queue = Array.from(ranges).reverse();
41180
41277
  while (queue.length > 0) {
41181
41278
  const range = queue.pop();
41182
- visited.add(...this.encoder.encodeBoundingBox(range));
41279
+ visited.addMany(this.encoder.encodeBoundingBox(range));
41183
41280
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
41184
41281
  for (const positionId of impactedPositionIds) {
41185
41282
  if (!visited.has(positionId)) {
@@ -41187,7 +41284,7 @@ class FormulaDependencyGraph {
41187
41284
  }
41188
41285
  }
41189
41286
  }
41190
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41287
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
41191
41288
  return visited;
41192
41289
  }
41193
41290
  }
@@ -41327,9 +41424,9 @@ class Evaluator {
41327
41424
  const cells = positions.map((p) => this.encoder.encode(p));
41328
41425
  const cellsToCompute = new JetSet(cells);
41329
41426
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
41330
- cellsToCompute.add(...this.getCellsDependingOn(cells));
41331
- cellsToCompute.add(...arrayFormulasPositionIds);
41332
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
41427
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
41428
+ cellsToCompute.addMany(arrayFormulasPositionIds);
41429
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
41333
41430
  this.evaluate(cellsToCompute);
41334
41431
  }
41335
41432
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -41343,7 +41440,7 @@ class Evaluator {
41343
41440
  }
41344
41441
  if (!content) {
41345
41442
  // The previous content could have blocked some array formulas
41346
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41443
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41347
41444
  }
41348
41445
  }
41349
41446
  return impactedPositionIds;
@@ -41395,7 +41492,7 @@ class Evaluator {
41395
41492
  }
41396
41493
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
41397
41494
  const cells = new JetSet(arrayFormulas);
41398
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41495
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
41399
41496
  return cells;
41400
41497
  }
41401
41498
  nextPositionsToUpdate = new JetSet();
@@ -41533,7 +41630,7 @@ class Evaluator {
41533
41630
  this.setEvaluatedCell(positionId, evaluatedCell);
41534
41631
  // check if formula dependencies present in the spread zone
41535
41632
  // if so, they need to be recomputed
41536
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41633
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41537
41634
  };
41538
41635
  }
41539
41636
  invalidateSpreading(positionId) {
@@ -41548,8 +41645,8 @@ class Evaluator {
41548
41645
  continue;
41549
41646
  }
41550
41647
  this.evaluatedCells.delete(child);
41551
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41552
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41648
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41649
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41553
41650
  }
41554
41651
  this.spreadingRelations.removeNode(positionId);
41555
41652
  }
@@ -41911,7 +42008,13 @@ class EvaluationPlugin extends UIPlugin {
41911
42008
  const format = newFormat
41912
42009
  ? getItemId(newFormat, data.formats)
41913
42010
  : exportedCellData.format;
41914
- const content = !isExported ? newContent : exportedCellData.content;
42011
+ let content;
42012
+ if (formulaCell instanceof FormulaCellWithDependencies) {
42013
+ content = formulaCell.contentWithFixedReferences;
42014
+ }
42015
+ else {
42016
+ content = !isExported ? newContent : exportedCellData.content;
42017
+ }
41915
42018
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
41916
42019
  }
41917
42020
  }
@@ -45412,7 +45515,7 @@ class FindAndReplacePlugin extends UIPlugin {
45412
45515
  }
45413
45516
  finalize() {
45414
45517
  if (this.isSearchDirty) {
45415
- this.refreshSearch();
45518
+ this.refreshSearch(false);
45416
45519
  this.isSearchDirty = false;
45417
45520
  }
45418
45521
  }
@@ -45456,10 +45559,10 @@ class FindAndReplacePlugin extends UIPlugin {
45456
45559
  /**
45457
45560
  * refresh the matches according to the current search options
45458
45561
  */
45459
- refreshSearch() {
45562
+ refreshSearch(jumpToMatchSheet = true) {
45460
45563
  this.selectedMatchIndex = null;
45461
45564
  this.findMatches();
45462
- this.selectNextCell(Direction.current);
45565
+ this.selectNextCell(Direction.current, jumpToMatchSheet);
45463
45566
  }
45464
45567
  /**
45465
45568
  * Updates the regex based on the current searchOptions and
@@ -45541,7 +45644,7 @@ class FindAndReplacePlugin extends UIPlugin {
45541
45644
  * It is also used to keep coherence between the selected searchMatch
45542
45645
  * and selectedMatchIndex.
45543
45646
  */
45544
- selectNextCell(indexChange) {
45647
+ selectNextCell(indexChange, jumpToMatchSheet = true) {
45545
45648
  const matches = this.searchMatches;
45546
45649
  if (!matches.length) {
45547
45650
  this.selectedMatchIndex = null;
@@ -45567,7 +45670,7 @@ class FindAndReplacePlugin extends UIPlugin {
45567
45670
  this.selectedMatchIndex = nextIndex;
45568
45671
  const selectedMatch = matches[nextIndex];
45569
45672
  // Switch to the sheet where the match is located
45570
- if (this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45673
+ if (jumpToMatchSheet && this.getters.getActiveSheetId() !== selectedMatch.sheetId) {
45571
45674
  this.dispatch("ACTIVATE_SHEET", {
45572
45675
  sheetIdFrom: this.getters.getActiveSheetId(),
45573
45676
  sheetIdTo: selectedMatch.sheetId,
@@ -47139,7 +47242,7 @@ class SheetUIPlugin extends UIPlugin {
47139
47242
  "getCellText",
47140
47243
  "getCellMultiLineText",
47141
47244
  "getContiguousZone",
47142
- "isCellEmpty",
47245
+ "isEvaluatedCellEmpty",
47143
47246
  ];
47144
47247
  ctx = document.createElement("canvas").getContext("2d");
47145
47248
  // ---------------------------------------------------------------------------
@@ -47267,14 +47370,23 @@ class SheetUIPlugin extends UIPlugin {
47267
47370
  return zone;
47268
47371
  }
47269
47372
  /**
47270
- * Check if a cell is empty. If the cell is part of a merge,
47271
- * check if the merge containing the cell is empty.
47373
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
47374
+ * the check applies to the main cell of the merge.
47272
47375
  */
47273
- isCellEmpty(position) {
47376
+ isEvaluatedCellEmpty(position) {
47274
47377
  const mainPosition = this.getters.getMainCellPosition(position);
47275
47378
  const cell = this.getters.getEvaluatedCell(mainPosition);
47276
47379
  return cell.type === CellValueType.empty;
47277
47380
  }
47381
+ /**
47382
+ * Checks if a cell is empty (i.e. does not have a content or a formula does not spread over it).
47383
+ * If the cell is part of a merge, the check applies to the main cell of the merge.
47384
+ */
47385
+ isCellEmpty(position) {
47386
+ const mainPosition = this.getters.getMainCellPosition(position);
47387
+ return !(this.getters.getCorrespondingFormulaCell(mainPosition) ||
47388
+ this.getters.getCell(mainPosition)?.content);
47389
+ }
47278
47390
  getColMaxWidth(sheetId, index) {
47279
47391
  const cellsPositions = positions(this.getters.getColsZone(sheetId, index, index));
47280
47392
  const sizes = cellsPositions.map((position) => this.getCellWidth({ sheetId, ...position }));
@@ -51095,6 +51207,16 @@ class ImageProvider {
51095
51207
  }
51096
51208
  }
51097
51209
 
51210
+ class FocusableElement {
51211
+ focusableElement = undefined;
51212
+ setFocusableElement(element) {
51213
+ this.focusableElement = element;
51214
+ }
51215
+ focus() {
51216
+ this.focusableElement?.focus();
51217
+ }
51218
+ }
51219
+
51098
51220
  const RIPPLE_KEY_FRAMES = [
51099
51221
  { transform: "scale(0)" },
51100
51222
  { transform: "scale(0.8)", offset: 0.33 },
@@ -53249,6 +53371,7 @@ class Spreadsheet extends owl.Component {
53249
53371
  toggleSidePanel: this.toggleSidePanel.bind(this),
53250
53372
  clipboard: this.env.clipboard || instantiateClipboard(),
53251
53373
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
53374
+ focusableElement: new FocusableElement(),
53252
53375
  });
53253
53376
  owl.useEffect(() => {
53254
53377
  /**
@@ -53367,16 +53490,17 @@ class Spreadsheet extends owl.Component {
53367
53490
  }
53368
53491
  this.composer.topBarFocus = "contentFocus";
53369
53492
  this.composer.gridFocusMode = "inactive";
53370
- this.setComposerContent({ selection } || {});
53493
+ this.setComposerContent({ selection });
53371
53494
  }
53372
- onGridComposerContentFocused() {
53495
+ onGridComposerContentFocused(selection) {
53373
53496
  if (this.model.getters.isReadonly()) {
53374
53497
  return;
53375
53498
  }
53376
53499
  this.composer.topBarFocus = "inactive";
53377
53500
  this.composer.gridFocusMode = "contentFocus";
53378
- this.setComposerContent({});
53501
+ this.setComposerContent({ selection });
53379
53502
  }
53503
+ // TODO: either both are defined or none of them. change those args to an object
53380
53504
  onGridComposerCellFocused(content, selection) {
53381
53505
  if (this.model.getters.isReadonly()) {
53382
53506
  return;
@@ -54804,8 +54928,8 @@ class SelectionStreamProcessorImpl {
54804
54928
  let currentPosition = startPosition;
54805
54929
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54806
54930
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54807
- let mode = !this.getters.isCellEmpty({ ...currentPosition, sheetId }) &&
54808
- !this.getters.isCellEmpty({ ...nextCellPosition, sheetId })
54931
+ let mode = !this.getters.isEvaluatedCellEmpty({ ...currentPosition, sheetId }) &&
54932
+ !this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId })
54809
54933
  ? "endOfCluster"
54810
54934
  : "nextCluster";
54811
54935
  while (true) {
@@ -54815,7 +54939,7 @@ class SelectionStreamProcessorImpl {
54815
54939
  currentPosition.row === nextCellPosition.row) {
54816
54940
  break;
54817
54941
  }
54818
- const isNextCellEmpty = this.getters.isCellEmpty({ ...nextCellPosition, sheetId });
54942
+ const isNextCellEmpty = this.getters.isEvaluatedCellEmpty({ ...nextCellPosition, sheetId });
54819
54943
  if (mode === "endOfCluster" && isNextCellEmpty) {
54820
54944
  break;
54821
54945
  }
@@ -57100,6 +57224,6 @@ exports.setTranslationMethod = setTranslationMethod;
57100
57224
  exports.tokenize = tokenize;
57101
57225
 
57102
57226
 
57103
- __info__.version = "17.1.3";
57104
- __info__.date = "2024-02-02T13:46:54.332Z";
57105
- __info__.hash = "798f979";
57227
+ __info__.version = "17.1.5";
57228
+ __info__.date = "2024-02-16T14:23:54.651Z";
57229
+ __info__.hash = "29d8ad6";