@odoo/o-spreadsheet 17.0.10 → 17.0.13

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.0.10
6
- * @date 2024-02-02T13:48:28.727Z
7
- * @hash be06aa9
5
+ * @version 17.0.13
6
+ * @date 2024-02-28T12:28:44.914Z
7
+ * @hash ed67be7
8
8
  */
9
9
 
10
10
  'use strict';
@@ -552,8 +552,8 @@ function isObjectEmptyRecursive(argument) {
552
552
  * If the given item does not exist in the dictionary, it creates one with a new id.
553
553
  */
554
554
  function getItemId(item, itemsDic) {
555
- for (let [key, value] of Object.entries(itemsDic)) {
556
- if (deepEquals(value, item)) {
555
+ for (const key in itemsDic) {
556
+ if (deepEquals(itemsDic[key], item)) {
557
557
  return parseInt(key, 10);
558
558
  }
559
559
  }
@@ -652,11 +652,13 @@ function deepEquals(o1, o2) {
652
652
  if (typeof o1 !== "object")
653
653
  return o1 === o2;
654
654
  // Objects can have different keys if the values are undefined
655
- const keys = new Set();
656
- Object.keys(o1).forEach((key) => keys.add(key));
657
- Object.keys(o2).forEach((key) => keys.add(key));
658
- for (let key of keys) {
659
- if (typeof o1[key] !== typeof o1[key])
655
+ for (const key in o2) {
656
+ if (!(key in o1) && o2[key] !== undefined) {
657
+ return false;
658
+ }
659
+ }
660
+ for (const key in o1) {
661
+ if (typeof o1[key] !== typeof o2[key])
660
662
  return false;
661
663
  if (typeof o1[key] === "object") {
662
664
  if (!deepEquals(o1[key], o2[key]))
@@ -726,18 +728,18 @@ function isConsecutive(iterable) {
726
728
  return true;
727
729
  }
728
730
  class JetSet extends Set {
729
- add(...iterable) {
731
+ addMany(iterable) {
730
732
  for (const element of iterable) {
731
733
  super.add(element);
732
734
  }
733
735
  return this;
734
736
  }
735
- delete(...iterable) {
736
- let deleted = false;
737
+ deleteMany(iterable) {
738
+ let wasDeleted = false;
737
739
  for (const element of iterable) {
738
- deleted ||= super.delete(element);
740
+ wasDeleted ||= super.delete(element);
739
741
  }
740
- return deleted;
742
+ return wasDeleted;
741
743
  }
742
744
  }
743
745
  /**
@@ -1996,7 +1998,7 @@ class DispatchResult {
1996
1998
  * Static helper which returns a successful DispatchResult
1997
1999
  */
1998
2000
  static get Success() {
1999
- return new DispatchResult();
2001
+ return SUCCESS;
2000
2002
  }
2001
2003
  get isSuccessful() {
2002
2004
  return this.reasons.length === 0;
@@ -2009,6 +2011,7 @@ class DispatchResult {
2009
2011
  return this.reasons.includes(reason);
2010
2012
  }
2011
2013
  }
2014
+ const SUCCESS = new DispatchResult();
2012
2015
  exports.CommandResult = void 0;
2013
2016
  (function (CommandResult) {
2014
2017
  CommandResult["Success"] = "Success";
@@ -2890,6 +2893,9 @@ function parseFormat(formatString) {
2890
2893
  function formatValue(value, { format, locale }) {
2891
2894
  switch (typeof value) {
2892
2895
  case "string":
2896
+ if (value.includes('\\"')) {
2897
+ return value.replace(/\\"/g, '"');
2898
+ }
2893
2899
  return value;
2894
2900
  case "boolean":
2895
2901
  return value ? "TRUE" : "FALSE";
@@ -6278,11 +6284,14 @@ function cellTextStyleToCss(style) {
6278
6284
  * Transform CSS properties into a CSS string.
6279
6285
  */
6280
6286
  function cssPropertiesToCss(attributes) {
6281
- const str = Object.entries(attributes)
6282
- .filter(([attName, attValue]) => attValue !== undefined)
6283
- .map(([attName, attValue]) => `${attName}:${attValue};`)
6284
- .join(" ");
6285
- return str;
6287
+ let styleStr = "";
6288
+ for (const attName in attributes) {
6289
+ if (!attributes[attName]) {
6290
+ continue;
6291
+ }
6292
+ styleStr += `${attName}:${attributes[attName]}; `;
6293
+ }
6294
+ return styleStr;
6286
6295
  }
6287
6296
  function getElementMargins(el) {
6288
6297
  const style = window.getComputedStyle(el);
@@ -7381,7 +7390,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7381
7390
  while (!chars.isOver()) {
7382
7391
  let token = tokenizeSpace(chars) ||
7383
7392
  tokenizeArgsSeparator(chars, locale) ||
7384
- tokenizeMisc(chars) ||
7393
+ tokenizeParenthesis(chars) ||
7385
7394
  tokenizeOperator(chars) ||
7386
7395
  tokenizeString(chars) ||
7387
7396
  tokenizeDebugger(chars) ||
@@ -7402,15 +7411,14 @@ function tokenizeDebugger(chars) {
7402
7411
  }
7403
7412
  return null;
7404
7413
  }
7405
- const misc$1 = {
7406
- "(": "LEFT_PAREN",
7407
- ")": "RIGHT_PAREN",
7414
+ const parenthesis = {
7415
+ "(": { type: "LEFT_PAREN", value: "(" },
7416
+ ")": { type: "RIGHT_PAREN", value: ")" },
7408
7417
  };
7409
- function tokenizeMisc(chars) {
7410
- if (chars.current in misc$1) {
7418
+ function tokenizeParenthesis(chars) {
7419
+ if (chars.current === "(" || chars.current === ")") {
7411
7420
  const value = chars.shift();
7412
- const type = misc$1[value];
7413
- return { type, value };
7421
+ return parenthesis[value];
7414
7422
  }
7415
7423
  return null;
7416
7424
  }
@@ -7431,7 +7439,12 @@ function tokenizeOperator(chars) {
7431
7439
  }
7432
7440
  return null;
7433
7441
  }
7442
+ const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789");
7434
7443
  function tokenizeNumber(chars, locale) {
7444
+ if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) &&
7445
+ chars.current !== locale.decimalSeparator) {
7446
+ return null;
7447
+ }
7435
7448
  const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
7436
7449
  if (match) {
7437
7450
  chars.advanceBy(match[0].length);
@@ -7456,7 +7469,7 @@ function tokenizeString(chars) {
7456
7469
  }
7457
7470
  return null;
7458
7471
  }
7459
- const separatorRegexp = /\w|\.|!|\$/;
7472
+ const separatorRegexp = /^[\w\.!\$]+/;
7460
7473
  /**
7461
7474
  * A "Symbol" is just basically any word-like element that can appear in a
7462
7475
  * formula, which is not a string. So:
@@ -7496,8 +7509,11 @@ function tokenizeSymbol(chars) {
7496
7509
  };
7497
7510
  }
7498
7511
  }
7499
- while (chars.current && separatorRegexp.test(chars.current)) {
7500
- result += chars.shift();
7512
+ const match = chars.remaining().match(separatorRegexp);
7513
+ if (match) {
7514
+ const value = match[0];
7515
+ result += value;
7516
+ chars.advanceBy(value.length);
7501
7517
  }
7502
7518
  if (result.length) {
7503
7519
  const value = result;
@@ -7559,7 +7575,10 @@ class TokenizingChars {
7559
7575
  return this.text.substring(this.currentIndex);
7560
7576
  }
7561
7577
  currentStartsWith(str) {
7562
- for (let j = 0; j < str.length; j++) {
7578
+ if (this.current !== str[0]) {
7579
+ return false;
7580
+ }
7581
+ for (let j = 1; j < str.length; j++) {
7563
7582
  if (this.text[this.currentIndex + j] !== str[j]) {
7564
7583
  return false;
7565
7584
  }
@@ -7586,6 +7605,9 @@ function isValidLocale(locale) {
7586
7605
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7587
7606
  return false;
7588
7607
  }
7608
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7609
+ return false;
7610
+ }
7589
7611
  try {
7590
7612
  formatValue(1, { locale, format: "#,##0.00" });
7591
7613
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7676,7 +7698,7 @@ function canonicalizeNumberLiteral(content, locale) {
7676
7698
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7677
7699
  return content;
7678
7700
  }
7679
- return content.replace(locale.decimalSeparator, ".");
7701
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7680
7702
  }
7681
7703
  /**
7682
7704
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -10748,6 +10770,12 @@ const INSERT_LINK = (env) => {
10748
10770
  let { col, row } = env.model.getters.getActivePosition();
10749
10771
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10750
10772
  };
10773
+ const INSERT_LINK_NAME = (env) => {
10774
+ const sheetId = env.model.getters.getActiveSheetId();
10775
+ const { col, row } = env.model.getters.getActivePosition();
10776
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10777
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10778
+ };
10751
10779
  //------------------------------------------------------------------------------
10752
10780
  // Filters action
10753
10781
  //------------------------------------------------------------------------------
@@ -12900,6 +12928,18 @@ const TRUNC = {
12900
12928
  },
12901
12929
  isExported: true,
12902
12930
  };
12931
+ // -----------------------------------------------------------------------------
12932
+ // INT
12933
+ // -----------------------------------------------------------------------------
12934
+ const INT = {
12935
+ description: _t("Rounds a number down to the nearest integer that is less than or equal to it."),
12936
+ args: [arg("value (number)", _t("The number to round down to the nearest integer."))],
12937
+ returns: ["NUMBER"],
12938
+ compute: function (value) {
12939
+ return Math.floor(toNumber(value, this.locale));
12940
+ },
12941
+ isExported: true,
12942
+ };
12903
12943
 
12904
12944
  var math = /*#__PURE__*/Object.freeze({
12905
12945
  __proto__: null,
@@ -12933,6 +12973,7 @@ var math = /*#__PURE__*/Object.freeze({
12933
12973
  FLOOR: FLOOR,
12934
12974
  FLOOR_MATH: FLOOR_MATH,
12935
12975
  FLOOR_PRECISE: FLOOR_PRECISE,
12976
+ INT: INT,
12936
12977
  ISEVEN: ISEVEN,
12937
12978
  ISODD: ISODD,
12938
12979
  ISO_CEILING: ISO_CEILING,
@@ -19779,7 +19820,7 @@ cellMenuRegistry
19779
19820
  })
19780
19821
  .add("insert_link", {
19781
19822
  ...insertLink,
19782
- name: _t("Insert link"),
19823
+ name: INSERT_LINK_NAME,
19783
19824
  sequence: 150,
19784
19825
  separator: true,
19785
19826
  });
@@ -21371,7 +21412,7 @@ class SelectionInput extends owl.Component {
21371
21412
  ? existingSelectionRanges
21372
21413
  : this.props.ranges().map((xc, id) => ({
21373
21414
  xc,
21374
- id,
21415
+ id: id + 1,
21375
21416
  isFocused: false,
21376
21417
  }));
21377
21418
  return ranges.map((range) => ({
@@ -26032,6 +26073,17 @@ css /* scss */ `
26032
26073
  `;
26033
26074
  class TextValueProvider extends owl.Component {
26034
26075
  static template = "o-spreadsheet-TextValueProvider";
26076
+ autoCompleteListRef = owl.useRef("autoCompleteList");
26077
+ setup() {
26078
+ owl.useEffect(() => {
26079
+ const selectedIndex = this.props.selectedIndex;
26080
+ if (selectedIndex === undefined) {
26081
+ return;
26082
+ }
26083
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26084
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26085
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26086
+ }
26035
26087
  }
26036
26088
  TextValueProvider.props = {
26037
26089
  values: Array,
@@ -26483,6 +26535,7 @@ css /* scss */ `
26483
26535
  position: absolute;
26484
26536
  margin: 1px 4px;
26485
26537
  pointer-events: none;
26538
+ overflow: auto;
26486
26539
 
26487
26540
  .o-semi-bold {
26488
26541
  /** FIXME: to remove in favor of Bootstrap
@@ -26498,6 +26551,7 @@ class Composer extends owl.Component {
26498
26551
  static components = { TextValueProvider, FunctionDescriptionProvider };
26499
26552
  static defaultProps = {
26500
26553
  inputStyle: "",
26554
+ isDefaultFocus: false,
26501
26555
  };
26502
26556
  composerRef = owl.useRef("o_composer");
26503
26557
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -26528,7 +26582,10 @@ class Composer extends owl.Component {
26528
26582
  if (this.props.delimitation && this.props.rect) {
26529
26583
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
26530
26584
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
26585
+ assistantStyle["max-height"] = `${remainingHeight}px`;
26531
26586
  if (cellY > remainingHeight) {
26587
+ const availableSpaceAbove = cellY;
26588
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
26532
26589
  // render top
26533
26590
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
26534
26591
  assistantStyle.top = `-3px`;
@@ -26554,7 +26611,6 @@ class Composer extends owl.Component {
26554
26611
  F2: () => console.warn("Not implemented"),
26555
26612
  F4: this.processF4Key,
26556
26613
  Tab: (ev) => this.processTabKey(ev),
26557
- " ": (ev) => this.processSpaceKey(ev),
26558
26614
  };
26559
26615
  keyCodeMapping = {
26560
26616
  NumpadDecimal: this.processNumpadDecimal,
@@ -26562,12 +26618,12 @@ class Composer extends owl.Component {
26562
26618
  setup() {
26563
26619
  owl.onMounted(() => {
26564
26620
  const el = this.composerRef.el;
26621
+ if (this.props.isDefaultFocus) {
26622
+ this.env.focusableElement.setFocusableElement(el);
26623
+ }
26565
26624
  this.contentHelper.updateEl(el);
26566
26625
  this.processTokenAtCursor();
26567
26626
  });
26568
- owl.onWillUnmount(() => {
26569
- this.props.onComposerUnmounted?.();
26570
- });
26571
26627
  owl.useEffect(() => {
26572
26628
  this.processContent();
26573
26629
  });
@@ -26576,7 +26632,8 @@ class Composer extends owl.Component {
26576
26632
  // Handlers
26577
26633
  // ---------------------------------------------------------------------------
26578
26634
  processArrowKeys(ev) {
26579
- if (this.env.model.getters.isSelectingForComposer()) {
26635
+ if (this.env.model.getters.isSelectingForComposer() ||
26636
+ this.env.model.getters.getEditionMode() === "inactive") {
26580
26637
  this.functionDescriptionState.showDescription = false;
26581
26638
  // Prevent the default content editable behavior which moves the cursor
26582
26639
  ev.preventDefault();
@@ -26619,26 +26676,20 @@ class Composer extends owl.Component {
26619
26676
  processTabKey(ev) {
26620
26677
  ev.preventDefault();
26621
26678
  ev.stopPropagation();
26622
- const state = this.autoCompleteState;
26623
- if (state.showProvider && state.selectedIndex !== undefined) {
26624
- const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26625
- if (autoCompleteValue) {
26626
- this.autoComplete(autoCompleteValue);
26627
- return;
26679
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26680
+ const state = this.autoCompleteState;
26681
+ if (state.showProvider && state.selectedIndex !== undefined) {
26682
+ const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26683
+ if (autoCompleteValue) {
26684
+ this.autoComplete(autoCompleteValue);
26685
+ return;
26686
+ }
26628
26687
  }
26688
+ interactiveStopEdition(this.env);
26629
26689
  }
26630
26690
  const direction = ev.shiftKey ? "left" : "right";
26631
- interactiveStopEdition(this.env);
26632
26691
  this.env.model.selection.moveAnchorCell(direction, 1);
26633
26692
  }
26634
- processSpaceKey(ev) {
26635
- if (ev.ctrlKey) {
26636
- ev.preventDefault();
26637
- ev.stopPropagation();
26638
- this.showFunctionAutocomplete("");
26639
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26640
- }
26641
- }
26642
26693
  processEnterKey(ev) {
26643
26694
  ev.preventDefault();
26644
26695
  ev.stopPropagation();
@@ -26691,6 +26742,9 @@ class Composer extends owl.Component {
26691
26742
  this.compositionActive = false;
26692
26743
  }
26693
26744
  onKeydown(ev) {
26745
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26746
+ return;
26747
+ }
26694
26748
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
26695
26749
  if (handler) {
26696
26750
  handler.call(this, ev);
@@ -26699,14 +26753,33 @@ class Composer extends owl.Component {
26699
26753
  ev.stopPropagation();
26700
26754
  }
26701
26755
  }
26756
+ onPaste(ev) {
26757
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26758
+ ev.stopPropagation();
26759
+ }
26760
+ }
26702
26761
  /*
26703
26762
  * Triggered automatically by the content-editable between the keydown and key up
26704
26763
  * */
26705
26764
  onInput(ev) {
26706
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
26765
+ if (!this.shouldProcessInputEvents) {
26766
+ return;
26767
+ }
26768
+ if (ev.inputType === "insertFromPaste" &&
26769
+ this.env.model.getters.getEditionMode() === "inactive") {
26707
26770
  return;
26708
26771
  }
26709
- let content = this.contentHelper.getText();
26772
+ ev.stopPropagation();
26773
+ let content;
26774
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26775
+ content = ev.data || "";
26776
+ }
26777
+ else {
26778
+ content = this.contentHelper.getText();
26779
+ }
26780
+ if (this.props.focus === "inactive") {
26781
+ return this.props.onComposerCellFocused?.(content);
26782
+ }
26710
26783
  let selection = this.contentHelper.getCurrentSelection();
26711
26784
  if (ev.inputType === "insertParagraph") {
26712
26785
  const start = Math.min(selection.start, selection.end);
@@ -26788,9 +26861,8 @@ class Composer extends owl.Component {
26788
26861
  }
26789
26862
  const newSelection = this.contentHelper.getCurrentSelection();
26790
26863
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26791
- if (this.props.focus === "inactive") {
26792
- this.props.onComposerContentFocused(newSelection);
26793
- }
26864
+ this.props.onComposerContentFocused();
26865
+ if (this.props.focus === "inactive") ;
26794
26866
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
26795
26867
  this.processTokenAtCursor();
26796
26868
  }
@@ -26957,7 +27029,7 @@ class Composer extends owl.Component {
26957
27029
  const highlights = this.env.model.getters.getHighlights();
26958
27030
  const refSheet = sheetName
26959
27031
  ? this.env.model.getters.getSheetIdByName(sheetName)
26960
- : this.env.model.getters.getCurrentEditedCell().sheetId;
27032
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
26961
27033
  const highlight = highlights.find((highlight) => {
26962
27034
  if (highlight.sheetId !== refSheet)
26963
27035
  return false;
@@ -27050,12 +27122,13 @@ class Composer extends owl.Component {
27050
27122
  }
27051
27123
  }
27052
27124
  Composer.props = {
27053
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27054
- onComposerContentFocused: Function,
27055
27125
  inputStyle: { type: String, optional: true },
27056
27126
  rect: { type: Object, optional: true },
27057
27127
  delimitation: { type: Object, optional: true },
27058
- onComposerUnmounted: { type: Function, optional: true },
27128
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27129
+ onComposerCellFocused: { type: Function, optional: true },
27130
+ onComposerContentFocused: Function,
27131
+ isDefaultFocus: { type: Boolean, optional: true },
27059
27132
  };
27060
27133
 
27061
27134
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -27089,51 +27162,25 @@ css /* scss */ `
27089
27162
  class GridComposer extends owl.Component {
27090
27163
  static template = "o-spreadsheet-GridComposer";
27091
27164
  static components = { Composer };
27092
- gridComposerRef;
27093
- zone;
27094
- rect;
27165
+ rect = this.defaultRect;
27166
+ isEditing = false;
27095
27167
  isCellReferenceVisible;
27096
- composerState;
27168
+ get defaultRect() {
27169
+ return { x: 0, y: 0, width: 0, height: 0 };
27170
+ }
27097
27171
  setup() {
27098
- this.gridComposerRef = owl.useRef("gridComposer");
27099
- this.composerState = owl.useState({
27100
- rect: undefined,
27101
- delimitation: undefined,
27102
- });
27103
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
27104
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
27105
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
27106
- this.isCellReferenceVisible = false;
27107
- owl.onMounted(() => {
27108
- const el = this.gridComposerRef.el;
27109
- this.composerState.rect = {
27110
- x: this.rect.x,
27111
- y: this.rect.y,
27112
- width: el.clientWidth,
27113
- height: el.clientHeight,
27114
- };
27115
- this.composerState.delimitation = {
27116
- width: this.props.gridDims.width,
27117
- height: this.props.gridDims.height,
27118
- };
27119
- });
27120
27172
  owl.onWillUpdateProps(() => {
27121
- if (this.isCellReferenceVisible) {
27122
- return;
27123
- }
27124
- const sheetId = this.env.model.getters.getActiveSheetId();
27125
- const zone = this.env.model.getters.getSelectedZone();
27126
- const rect = this.env.model.getters.getVisibleRect(zone);
27127
- if (!deepEquals(rect, this.rect) ||
27128
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27129
- this.isCellReferenceVisible = true;
27130
- }
27173
+ this.updateComponentPosition();
27174
+ this.updateCellReferenceVisibility();
27131
27175
  });
27132
27176
  }
27133
27177
  get shouldDisplayCellReference() {
27134
27178
  return this.isCellReferenceVisible;
27135
27179
  }
27136
27180
  get cellReference() {
27181
+ if (!this.env.model.getters.getCurrentEditedCell()) {
27182
+ return "";
27183
+ }
27137
27184
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27138
27185
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27139
27186
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -27145,7 +27192,27 @@ class GridComposer extends owl.Component {
27145
27192
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
27146
27193
  });
27147
27194
  }
27195
+ get composerProps() {
27196
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27197
+ return {
27198
+ rect: { ...this.rect },
27199
+ delimitation: {
27200
+ width,
27201
+ height,
27202
+ },
27203
+ focus: this.props.focus,
27204
+ isDefaultFocus: true,
27205
+ onComposerContentFocused: this.props.onComposerContentFocused,
27206
+ onComposerCellFocused: this.props.onComposerCellFocused,
27207
+ };
27208
+ }
27148
27209
  get containerStyle() {
27210
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27211
+ return `
27212
+ position: absolute;
27213
+ z-index: -1000;
27214
+ `;
27215
+ }
27149
27216
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27150
27217
  const cell = this.env.model.getters.getActiveCell();
27151
27218
  const position = this.env.model.getters.getActivePosition();
@@ -27165,6 +27232,8 @@ class GridComposer extends owl.Component {
27165
27232
  if (!isFormula) {
27166
27233
  textAlign = style.align || cell.defaultAlign;
27167
27234
  }
27235
+ const maxHeight = this.props.gridDims.height - this.rect.y;
27236
+ const maxWidth = this.props.gridDims.width - this.rect.x;
27168
27237
  /**
27169
27238
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
27170
27239
  * including all the paddings/margins of the composer
@@ -27176,6 +27245,8 @@ class GridComposer extends owl.Component {
27176
27245
  top: `${top}px`,
27177
27246
  "min-width": `${width + 1}px`,
27178
27247
  "min-height": `${height + 1}px`,
27248
+ "max-width": `${maxWidth}px`,
27249
+ "max-height": `${maxHeight}px`,
27179
27250
  background,
27180
27251
  color,
27181
27252
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -27185,20 +27256,38 @@ class GridComposer extends owl.Component {
27185
27256
  "text-align": textAlign,
27186
27257
  });
27187
27258
  }
27188
- get composerStyle() {
27189
- const maxHeight = this.props.gridDims.height - this.rect.y;
27190
- const maxWidth = this.props.gridDims.width - this.rect.x;
27191
- return cssPropertiesToCss({
27192
- "max-width": `${maxWidth}px`,
27193
- "max-height": `${maxHeight}px`,
27194
- });
27259
+ updateComponentPosition() {
27260
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
27261
+ if (this.isEditing !== isEditing) {
27262
+ this.isEditing = isEditing;
27263
+ if (!isEditing) {
27264
+ this.rect = this.defaultRect;
27265
+ this.env.focusableElement.focus();
27266
+ return;
27267
+ }
27268
+ const position = this.env.model.getters.getActivePosition();
27269
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
27270
+ this.rect = this.env.model.getters.getVisibleRect(zone);
27271
+ }
27272
+ }
27273
+ updateCellReferenceVisibility() {
27274
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27275
+ return;
27276
+ }
27277
+ const sheetId = this.env.model.getters.getActiveSheetId();
27278
+ const zone = this.env.model.getters.getSelectedZone();
27279
+ const rect = this.env.model.getters.getVisibleRect(zone);
27280
+ if (!deepEquals(rect, this.rect) ||
27281
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27282
+ this.isCellReferenceVisible = true;
27283
+ }
27195
27284
  }
27196
27285
  }
27197
27286
  GridComposer.props = {
27198
27287
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27199
- onComposerUnmounted: Function,
27200
27288
  onComposerContentFocused: Function,
27201
27289
  gridDims: Object,
27290
+ onComposerCellFocused: Function,
27202
27291
  };
27203
27292
 
27204
27293
  const CSS$1 = css /* scss */ `
@@ -28289,7 +28378,6 @@ class GridOverlay extends owl.Component {
28289
28378
  this.props.onCellDoubleClicked(col, row);
28290
28379
  }
28291
28380
  onContextMenu(ev) {
28292
- ev.preventDefault();
28293
28381
  const [col, row] = this.getCartesianCoordinates(ev);
28294
28382
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
28295
28383
  }
@@ -29374,7 +29462,6 @@ class Grid extends owl.Component {
29374
29462
  HEADER_WIDTH = HEADER_WIDTH;
29375
29463
  menuState;
29376
29464
  gridRef;
29377
- hiddenInput;
29378
29465
  onMouseWheel;
29379
29466
  canvasPosition;
29380
29467
  hoveredCell;
@@ -29385,17 +29472,16 @@ class Grid extends owl.Component {
29385
29472
  menuItems: [],
29386
29473
  });
29387
29474
  this.gridRef = owl.useRef("grid");
29388
- this.hiddenInput = owl.useRef("hiddenInput");
29389
29475
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
29390
29476
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
29391
29477
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
29392
29478
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
29393
29479
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
29394
29480
  owl.useExternalListener(document.body, "paste", this.paste);
29395
- owl.onMounted(() => this.focus());
29396
- this.props.exposeFocus(() => this.focus());
29481
+ owl.onMounted(() => this.focusDefaultElement());
29482
+ this.props.exposeFocus(() => this.focusDefaultElement());
29397
29483
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29398
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29484
+ owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
29399
29485
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29400
29486
  this.moveCanvas(deltaX, deltaY);
29401
29487
  this.hoveredCell.col = undefined;
@@ -29418,7 +29504,7 @@ class Grid extends owl.Component {
29418
29504
  if (this.env.model.getters.hasOpenedPopover()) {
29419
29505
  this.closeOpenedPopover();
29420
29506
  }
29421
- this.focus();
29507
+ this.focusDefaultElement();
29422
29508
  }
29423
29509
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
29424
29510
  // down itself
@@ -29597,10 +29683,10 @@ class Grid extends owl.Component {
29597
29683
  "ALT+SHIFT+ARROWUP": () => this.processHeaderGroupingKey("up"),
29598
29684
  "ALT+SHIFT+ARROWDOWN": () => this.processHeaderGroupingKey("down"),
29599
29685
  };
29600
- focus() {
29686
+ focusDefaultElement() {
29601
29687
  if (!this.env.model.getters.getSelectedFigureId() &&
29602
29688
  this.env.model.getters.getEditionMode() === "inactive") {
29603
- this.hiddenInput.el?.focus();
29689
+ this.env.focusableElement.focus();
29604
29690
  }
29605
29691
  }
29606
29692
  get gridEl() {
@@ -29745,19 +29831,6 @@ class Grid extends owl.Component {
29745
29831
  return;
29746
29832
  }
29747
29833
  }
29748
- onInput(ev) {
29749
- // the user meant to paste in the sheet, not open the composer with the pasted content
29750
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
29751
- return;
29752
- }
29753
- if (ev.data) {
29754
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
29755
- // character
29756
- ev.preventDefault();
29757
- ev.stopPropagation();
29758
- this.props.onGridComposerCellFocused(ev.data);
29759
- }
29760
- }
29761
29834
  // ---------------------------------------------------------------------------
29762
29835
  // Context Menu
29763
29836
  // ---------------------------------------------------------------------------
@@ -29872,7 +29945,7 @@ class Grid extends owl.Component {
29872
29945
  }
29873
29946
  closeMenu() {
29874
29947
  this.menuState.isOpen = false;
29875
- this.focus();
29948
+ this.focusDefaultElement();
29876
29949
  }
29877
29950
  processHeaderGroupingKey(direction) {
29878
29951
  if (this.env.model.getters.getSelectedZones().length !== 1) {
@@ -33768,8 +33841,16 @@ const MIGRATIONS = [
33768
33841
  },
33769
33842
  },
33770
33843
  {
33771
- description: "Change Border description structure",
33844
+ description: "Fix datafilter duplication",
33772
33845
  from: 12,
33846
+ to: 12.5,
33847
+ applyMigration(data) {
33848
+ return fixOverlappingFilters(data);
33849
+ },
33850
+ },
33851
+ {
33852
+ description: "Change Border description structure",
33853
+ from: 12.5,
33773
33854
  to: 13,
33774
33855
  applyMigration(data) {
33775
33856
  for (const borderId in data.borders) {
@@ -33800,6 +33881,14 @@ const MIGRATIONS = [
33800
33881
  return data;
33801
33882
  },
33802
33883
  },
33884
+ {
33885
+ description: "Fix datafilter duplication (post saas-16.4)",
33886
+ from: 14,
33887
+ to: 14.5,
33888
+ applyMigration(data) {
33889
+ return fixOverlappingFilters(data);
33890
+ },
33891
+ },
33803
33892
  ];
33804
33893
  /**
33805
33894
  * This function is used to repair faulty data independently of the migration.
@@ -33957,6 +34046,26 @@ function fixChartDefinitions(data, initialMessages) {
33957
34046
  }
33958
34047
  return messages;
33959
34048
  }
34049
+ function fixOverlappingFilters(data) {
34050
+ for (let sheet of data.sheets || []) {
34051
+ let knownDataFilterZones = [];
34052
+ for (let filterTable of sheet.filterTables || []) {
34053
+ const zone = toZone(filterTable.range);
34054
+ // See commit message of https://github.com/odoo/o-spreadsheet/pull/3632 of more details
34055
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
34056
+ if (intersectZoneIndex !== -1) {
34057
+ knownDataFilterZones[intersectZoneIndex] = zone;
34058
+ }
34059
+ else {
34060
+ knownDataFilterZones.push(zone);
34061
+ }
34062
+ }
34063
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
34064
+ range: zoneToXc(zone),
34065
+ }));
34066
+ }
34067
+ return data;
34068
+ }
33960
34069
  // -----------------------------------------------------------------------------
33961
34070
  // Helpers
33962
34071
  // -----------------------------------------------------------------------------
@@ -34606,22 +34715,7 @@ class BordersPlugin extends CorePlugin {
34606
34715
  }
34607
34716
  }
34608
34717
  export(data) {
34609
- // Borders
34610
- let borderId = 0;
34611
34718
  const borders = {};
34612
- /**
34613
- * Get the id of the given border. If the border does not exist, it creates
34614
- * one.
34615
- */
34616
- function getBorderId(border) {
34617
- for (let [key, value] of Object.entries(borders)) {
34618
- if (deepEquals(value, border)) {
34619
- return parseInt(key, 10);
34620
- }
34621
- }
34622
- borders[++borderId] = border;
34623
- return borderId;
34624
- }
34625
34719
  for (let sheet of data.sheets) {
34626
34720
  for (let col = 0; col < sheet.colNumber; col++) {
34627
34721
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -34629,7 +34723,7 @@ class BordersPlugin extends CorePlugin {
34629
34723
  if (border) {
34630
34724
  const xc = toXC(col, row);
34631
34725
  const cell = sheet.cells[xc];
34632
- const borderId = getBorderId(border);
34726
+ const borderId = getItemId(border, borders);
34633
34727
  if (cell) {
34634
34728
  cell.border = borderId;
34635
34729
  }
@@ -35434,9 +35528,9 @@ class CellPlugin extends CorePlugin {
35434
35528
  allowDispatch(cmd) {
35435
35529
  switch (cmd.type) {
35436
35530
  case "UPDATE_CELL":
35437
- return this.checkCellOutOfSheet(cmd);
35531
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35438
35532
  case "CLEAR_CELL":
35439
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35533
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35440
35534
  default:
35441
35535
  return "Success" /* CommandResult.Success */;
35442
35536
  }
@@ -35569,9 +35663,6 @@ class CellPlugin extends CorePlugin {
35569
35663
  format: cell.format ? getItemId(cell.format, formats) : undefined,
35570
35664
  content: cell.content || undefined,
35571
35665
  };
35572
- if (cell instanceof FormulaCellWithDependencies) {
35573
- cells[xc].content = cell.contentWithFixedReferences || undefined;
35574
- }
35575
35666
  }
35576
35667
  _sheet.cells = cells;
35577
35668
  }
@@ -35589,8 +35680,8 @@ class CellPlugin extends CorePlugin {
35589
35680
  }
35590
35681
  removeDefaultStyleValues(style) {
35591
35682
  const cleanedStyle = { ...style };
35592
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
35593
- if (cleanedStyle[property] === defaultValue) {
35683
+ for (const property in DEFAULT_STYLE) {
35684
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
35594
35685
  delete cleanedStyle[property];
35595
35686
  }
35596
35687
  }
@@ -35747,10 +35838,7 @@ class CellPlugin extends CorePlugin {
35747
35838
  else {
35748
35839
  style = before ? before.style : undefined;
35749
35840
  }
35750
- const locale = this.getters.getLocale();
35751
- let format = ("format" in after ? after.format : before && before.format) ||
35752
- detectDateFormat(afterContent, locale) ||
35753
- detectNumberFormat(afterContent);
35841
+ const format = "format" in after ? after.format : before && before.format;
35754
35842
  /* Read the following IF as:
35755
35843
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
35756
35844
  * - the command says the new content is empty and has no border/format/style
@@ -35791,12 +35879,11 @@ class CellPlugin extends CorePlugin {
35791
35879
  }
35792
35880
  createLiteralCell(id, content, format, style) {
35793
35881
  const locale = this.getters.getLocale();
35794
- content = parseLiteral(content, locale).toString();
35795
35882
  return {
35796
35883
  id,
35797
- content,
35884
+ content: parseLiteral(content, locale).toString(),
35798
35885
  style,
35799
- format,
35886
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
35800
35887
  isFormula: false,
35801
35888
  };
35802
35889
  }
@@ -35822,8 +35909,11 @@ class CellPlugin extends CorePlugin {
35822
35909
  * being a computed property to rebuild the dependencies XC.
35823
35910
  */
35824
35911
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
35825
- const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
35826
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
35912
+ const dependencies = [];
35913
+ for (const xc of compiledFormula.dependencies) {
35914
+ dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc));
35915
+ }
35916
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString);
35827
35917
  }
35828
35918
  createErrorFormula(id, content, format, style, error) {
35829
35919
  return {
@@ -35858,6 +35948,18 @@ class CellPlugin extends CorePlugin {
35858
35948
  }
35859
35949
  return "Success" /* CommandResult.Success */;
35860
35950
  }
35951
+ checkUselessUpdateCell(cmd) {
35952
+ const cell = this.getters.getCell(cmd);
35953
+ const hasContent = "content" in cmd || "formula" in cmd;
35954
+ const hasStyle = "style" in cmd;
35955
+ const hasFormat = "format" in cmd;
35956
+ if ((!hasContent || cell?.content === cmd.content) &&
35957
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
35958
+ (!hasFormat || cell?.format === cmd.format)) {
35959
+ return "NoChanges" /* CommandResult.NoChanges */;
35960
+ }
35961
+ return "Success" /* CommandResult.Success */;
35962
+ }
35861
35963
  }
35862
35964
  class FormulaCellWithDependencies {
35863
35965
  id;
@@ -35877,7 +35979,7 @@ class FormulaCellWithDependencies {
35877
35979
  const tokens = compiledFormula.tokens.map((token) => {
35878
35980
  if (token.type === "REFERENCE") {
35879
35981
  const index = rangeIndex++;
35880
- return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
35982
+ return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString);
35881
35983
  }
35882
35984
  return token;
35883
35985
  });
@@ -35904,13 +36006,20 @@ class FormulaCellWithDependencies {
35904
36006
  }
35905
36007
  }
35906
36008
  class RangeReferenceToken {
35907
- getValue;
36009
+ ranges;
36010
+ rangeIndex;
36011
+ sheetId;
36012
+ getRangeString;
35908
36013
  type = "REFERENCE";
35909
- constructor(getValue) {
35910
- this.getValue = getValue;
36014
+ constructor(ranges, rangeIndex, sheetId, getRangeString) {
36015
+ this.ranges = ranges;
36016
+ this.rangeIndex = rangeIndex;
36017
+ this.sheetId = sheetId;
36018
+ this.getRangeString = getRangeString;
35911
36019
  }
35912
36020
  get value() {
35913
- return this.getValue();
36021
+ const range = this.ranges[this.rangeIndex];
36022
+ return this.getRangeString(range, this.sheetId);
35914
36023
  }
35915
36024
  }
35916
36025
 
@@ -40646,7 +40755,15 @@ class SpreadsheetRTree {
40646
40755
  if (!this.rTrees[sheetId]) {
40647
40756
  return;
40648
40757
  }
40649
- this.rTrees[sheetId].remove(item, deepEquals);
40758
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
40759
+ }
40760
+ rtreeItemComparer(left, right) {
40761
+ return (left.data == right.data &&
40762
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
40763
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
40764
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
40765
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
40766
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
40650
40767
  }
40651
40768
  }
40652
40769
  /**
@@ -40724,7 +40841,7 @@ class FormulaDependencyGraph {
40724
40841
  const queue = Array.from(ranges).reverse();
40725
40842
  while (queue.length > 0) {
40726
40843
  const range = queue.pop();
40727
- visited.add(...this.encoder.encodeBoundingBox(range));
40844
+ visited.addMany(this.encoder.encodeBoundingBox(range));
40728
40845
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40729
40846
  for (const positionId of impactedPositionIds) {
40730
40847
  if (!visited.has(positionId)) {
@@ -40732,7 +40849,7 @@ class FormulaDependencyGraph {
40732
40849
  }
40733
40850
  }
40734
40851
  }
40735
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40852
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40736
40853
  return visited;
40737
40854
  }
40738
40855
  }
@@ -40870,9 +40987,9 @@ class Evaluator {
40870
40987
  const cells = positions.map((p) => this.encoder.encode(p));
40871
40988
  const cellsToCompute = new JetSet(cells);
40872
40989
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40873
- cellsToCompute.add(...this.getCellsDependingOn(cells));
40874
- cellsToCompute.add(...arrayFormulasPositionIds);
40875
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40990
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
40991
+ cellsToCompute.addMany(arrayFormulasPositionIds);
40992
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
40876
40993
  this.evaluate(cellsToCompute);
40877
40994
  }
40878
40995
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40886,7 +41003,7 @@ class Evaluator {
40886
41003
  }
40887
41004
  if (!content) {
40888
41005
  // The previous content could have blocked some array formulas
40889
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41006
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40890
41007
  }
40891
41008
  }
40892
41009
  return impactedPositionIds;
@@ -40935,7 +41052,7 @@ class Evaluator {
40935
41052
  }
40936
41053
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
40937
41054
  const cells = new JetSet(arrayFormulas);
40938
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41055
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
40939
41056
  return cells;
40940
41057
  }
40941
41058
  nextPositionsToUpdate = new JetSet();
@@ -41084,7 +41201,7 @@ class Evaluator {
41084
41201
  this.setEvaluatedCell(positionId, evaluatedCell);
41085
41202
  // check if formula dependencies present in the spread zone
41086
41203
  // if so, they need to be recomputed
41087
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41204
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41088
41205
  };
41089
41206
  }
41090
41207
  invalidateSpreading(positionId) {
@@ -41099,8 +41216,8 @@ class Evaluator {
41099
41216
  continue;
41100
41217
  }
41101
41218
  this.evaluatedCells.delete(child);
41102
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41103
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41219
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41220
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41104
41221
  }
41105
41222
  this.spreadingRelations.removeNode(positionId);
41106
41223
  }
@@ -41449,7 +41566,13 @@ class EvaluationPlugin extends UIPlugin {
41449
41566
  const format = newFormat
41450
41567
  ? getItemId(newFormat, data.formats)
41451
41568
  : exportedCellData.format;
41452
- const content = !isExported ? newContent : exportedCellData.content;
41569
+ let content;
41570
+ if (formulaCell instanceof FormulaCellWithDependencies) {
41571
+ content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41572
+ }
41573
+ else {
41574
+ content = !isExported ? newContent : exportedCellData.content;
41575
+ }
41453
41576
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
41454
41577
  }
41455
41578
  }
@@ -46333,19 +46456,17 @@ class SelectionInputsManagerPlugin extends UIPlugin {
46333
46456
  break;
46334
46457
  case "FOCUS_RANGE":
46335
46458
  case "CHANGE_RANGE":
46336
- if (cmd.id !== this.focusedInputId) {
46337
- const input = this.inputs[cmd.id];
46338
- const range = input.ranges.find((range) => range.id === cmd.rangeId);
46339
- if (this.isRangeValid(range?.xc || "A1")) {
46340
- const sheetId = this.getters.getActiveSheetId();
46341
- const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
46342
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
46343
- handleEvent: input.handleEvent.bind(input),
46344
- release: () => (this.focusedInputId = null),
46345
- });
46346
- }
46347
- this.focusedInputId = cmd.id;
46459
+ const input = this.inputs[cmd.id];
46460
+ const range = input.ranges.find((range) => range.id === cmd.rangeId);
46461
+ if (range) {
46462
+ const sheetId = this.getters.getActiveSheetId();
46463
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
46464
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
46465
+ handleEvent: input.handleEvent.bind(input),
46466
+ release: () => (this.focusedInputId = null),
46467
+ });
46348
46468
  }
46469
+ this.focusedInputId = cmd.id;
46349
46470
  break;
46350
46471
  }
46351
46472
  this.currentInput?.handle(cmd);
@@ -50765,6 +50886,16 @@ class ImageProvider {
50765
50886
  }
50766
50887
  }
50767
50888
 
50889
+ class FocusableElement {
50890
+ focusableElement = undefined;
50891
+ setFocusableElement(element) {
50892
+ this.focusableElement = element;
50893
+ }
50894
+ focus() {
50895
+ this.focusableElement?.focus();
50896
+ }
50897
+ }
50898
+
50768
50899
  const RIPPLE_KEY_FRAMES = [
50769
50900
  { transform: "scale(0)" },
50770
50901
  { transform: "scale(0.8)", offset: 0.33 },
@@ -52921,6 +53052,7 @@ class Spreadsheet extends owl.Component {
52921
53052
  toggleSidePanel: this.toggleSidePanel.bind(this),
52922
53053
  clipboard: this.env.clipboard || instantiateClipboard(),
52923
53054
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
53055
+ focusableElement: new FocusableElement(),
52924
53056
  });
52925
53057
  owl.useExternalListener(window, "resize", () => this.render(true));
52926
53058
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -53027,16 +53159,17 @@ class Spreadsheet extends owl.Component {
53027
53159
  }
53028
53160
  this.composer.topBarFocus = "contentFocus";
53029
53161
  this.composer.gridFocusMode = "inactive";
53030
- this.setComposerContent({ selection } || {});
53162
+ this.setComposerContent({ selection });
53031
53163
  }
53032
- onGridComposerContentFocused() {
53164
+ onGridComposerContentFocused(selection) {
53033
53165
  if (this.model.getters.isReadonly()) {
53034
53166
  return;
53035
53167
  }
53036
53168
  this.composer.topBarFocus = "inactive";
53037
53169
  this.composer.gridFocusMode = "contentFocus";
53038
- this.setComposerContent({});
53170
+ this.setComposerContent({ selection });
53039
53171
  }
53172
+ // TODO: either both are defined or none of them. change those args to an object
53040
53173
  onGridComposerCellFocused(content, selection) {
53041
53174
  if (this.model.getters.isReadonly()) {
53042
53175
  return;
@@ -54462,11 +54595,10 @@ class SelectionStreamProcessorImpl {
54462
54595
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
54463
54596
  */
54464
54597
  getEndOfCluster(startPosition, dim, dir) {
54465
- const sheet = this.getters.getActiveSheet();
54466
54598
  let currentPosition = startPosition;
54467
54599
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54468
54600
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54469
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
54601
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
54470
54602
  ? "endOfCluster"
54471
54603
  : "nextCluster";
54472
54604
  while (true) {
@@ -54476,7 +54608,7 @@ class SelectionStreamProcessorImpl {
54476
54608
  currentPosition.row === nextCellPosition.row) {
54477
54609
  break;
54478
54610
  }
54479
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
54611
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
54480
54612
  if (mode === "endOfCluster" && isNextCellEmpty) {
54481
54613
  break;
54482
54614
  }
@@ -54490,10 +54622,20 @@ class SelectionStreamProcessorImpl {
54490
54622
  return dim === "cols" ? currentPosition.col : currentPosition.row;
54491
54623
  }
54492
54624
  /**
54493
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
54494
- * check if the merge containing the cell is empty.
54625
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
54626
+ * the check applies to the main cell of the merge.
54495
54627
  */
54496
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
54628
+ isCellEmpty({ col, row }) {
54629
+ const sheetId = this.getters.getActiveSheetId();
54630
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
54631
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
54632
+ }
54633
+ /**
54634
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
54635
+ * the check applies to the main cell of the merge.
54636
+ */
54637
+ isEvaluatedCellEmpty({ col, row }) {
54638
+ const sheetId = this.getters.getActiveSheetId();
54497
54639
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
54498
54640
  const cell = this.getters.getEvaluatedCell(position);
54499
54641
  return cell.type === CellValueType.empty;
@@ -56799,6 +56941,6 @@ exports.setTranslationMethod = setTranslationMethod;
56799
56941
  exports.tokenize = tokenize;
56800
56942
 
56801
56943
 
56802
- __info__.version = "17.0.10";
56803
- __info__.date = "2024-02-02T13:48:28.727Z";
56804
- __info__.hash = "be06aa9";
56944
+ __info__.version = "17.0.13";
56945
+ __info__.date = "2024-02-28T12:28:44.914Z";
56946
+ __info__.hash = "ed67be7";