@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
  import { Component, useRef, onMounted, useEffect, onWillPatch, useState, onWillUpdateProps, onPatched, useComponent, useExternalListener, onWillUnmount, onWillStart, xml, useChildSubEnv, useSubEnv, markRaw } from '@odoo/owl';
@@ -550,8 +550,8 @@ function isObjectEmptyRecursive(argument) {
550
550
  * If the given item does not exist in the dictionary, it creates one with a new id.
551
551
  */
552
552
  function getItemId(item, itemsDic) {
553
- for (let [key, value] of Object.entries(itemsDic)) {
554
- if (deepEquals(value, item)) {
553
+ for (const key in itemsDic) {
554
+ if (deepEquals(itemsDic[key], item)) {
555
555
  return parseInt(key, 10);
556
556
  }
557
557
  }
@@ -650,11 +650,13 @@ function deepEquals(o1, o2) {
650
650
  if (typeof o1 !== "object")
651
651
  return o1 === o2;
652
652
  // Objects can have different keys if the values are undefined
653
- const keys = new Set();
654
- Object.keys(o1).forEach((key) => keys.add(key));
655
- Object.keys(o2).forEach((key) => keys.add(key));
656
- for (let key of keys) {
657
- if (typeof o1[key] !== typeof o1[key])
653
+ for (const key in o2) {
654
+ if (!(key in o1) && o2[key] !== undefined) {
655
+ return false;
656
+ }
657
+ }
658
+ for (const key in o1) {
659
+ if (typeof o1[key] !== typeof o2[key])
658
660
  return false;
659
661
  if (typeof o1[key] === "object") {
660
662
  if (!deepEquals(o1[key], o2[key]))
@@ -724,18 +726,18 @@ function isConsecutive(iterable) {
724
726
  return true;
725
727
  }
726
728
  class JetSet extends Set {
727
- add(...iterable) {
729
+ addMany(iterable) {
728
730
  for (const element of iterable) {
729
731
  super.add(element);
730
732
  }
731
733
  return this;
732
734
  }
733
- delete(...iterable) {
734
- let deleted = false;
735
+ deleteMany(iterable) {
736
+ let wasDeleted = false;
735
737
  for (const element of iterable) {
736
- deleted ||= super.delete(element);
738
+ wasDeleted ||= super.delete(element);
737
739
  }
738
- return deleted;
740
+ return wasDeleted;
739
741
  }
740
742
  }
741
743
  /**
@@ -1994,7 +1996,7 @@ class DispatchResult {
1994
1996
  * Static helper which returns a successful DispatchResult
1995
1997
  */
1996
1998
  static get Success() {
1997
- return new DispatchResult();
1999
+ return SUCCESS;
1998
2000
  }
1999
2001
  get isSuccessful() {
2000
2002
  return this.reasons.length === 0;
@@ -2007,6 +2009,7 @@ class DispatchResult {
2007
2009
  return this.reasons.includes(reason);
2008
2010
  }
2009
2011
  }
2012
+ const SUCCESS = new DispatchResult();
2010
2013
  var CommandResult;
2011
2014
  (function (CommandResult) {
2012
2015
  CommandResult["Success"] = "Success";
@@ -2888,6 +2891,9 @@ function parseFormat(formatString) {
2888
2891
  function formatValue(value, { format, locale }) {
2889
2892
  switch (typeof value) {
2890
2893
  case "string":
2894
+ if (value.includes('\\"')) {
2895
+ return value.replace(/\\"/g, '"');
2896
+ }
2891
2897
  return value;
2892
2898
  case "boolean":
2893
2899
  return value ? "TRUE" : "FALSE";
@@ -6276,11 +6282,14 @@ function cellTextStyleToCss(style) {
6276
6282
  * Transform CSS properties into a CSS string.
6277
6283
  */
6278
6284
  function cssPropertiesToCss(attributes) {
6279
- const str = Object.entries(attributes)
6280
- .filter(([attName, attValue]) => attValue !== undefined)
6281
- .map(([attName, attValue]) => `${attName}:${attValue};`)
6282
- .join(" ");
6283
- return str;
6285
+ let styleStr = "";
6286
+ for (const attName in attributes) {
6287
+ if (!attributes[attName]) {
6288
+ continue;
6289
+ }
6290
+ styleStr += `${attName}:${attributes[attName]}; `;
6291
+ }
6292
+ return styleStr;
6284
6293
  }
6285
6294
  function getElementMargins(el) {
6286
6295
  const style = window.getComputedStyle(el);
@@ -7379,7 +7388,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7379
7388
  while (!chars.isOver()) {
7380
7389
  let token = tokenizeSpace(chars) ||
7381
7390
  tokenizeArgsSeparator(chars, locale) ||
7382
- tokenizeMisc(chars) ||
7391
+ tokenizeParenthesis(chars) ||
7383
7392
  tokenizeOperator(chars) ||
7384
7393
  tokenizeString(chars) ||
7385
7394
  tokenizeDebugger(chars) ||
@@ -7400,15 +7409,14 @@ function tokenizeDebugger(chars) {
7400
7409
  }
7401
7410
  return null;
7402
7411
  }
7403
- const misc$1 = {
7404
- "(": "LEFT_PAREN",
7405
- ")": "RIGHT_PAREN",
7412
+ const parenthesis = {
7413
+ "(": { type: "LEFT_PAREN", value: "(" },
7414
+ ")": { type: "RIGHT_PAREN", value: ")" },
7406
7415
  };
7407
- function tokenizeMisc(chars) {
7408
- if (chars.current in misc$1) {
7416
+ function tokenizeParenthesis(chars) {
7417
+ if (chars.current === "(" || chars.current === ")") {
7409
7418
  const value = chars.shift();
7410
- const type = misc$1[value];
7411
- return { type, value };
7419
+ return parenthesis[value];
7412
7420
  }
7413
7421
  return null;
7414
7422
  }
@@ -7429,7 +7437,12 @@ function tokenizeOperator(chars) {
7429
7437
  }
7430
7438
  return null;
7431
7439
  }
7440
+ const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789");
7432
7441
  function tokenizeNumber(chars, locale) {
7442
+ if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) &&
7443
+ chars.current !== locale.decimalSeparator) {
7444
+ return null;
7445
+ }
7433
7446
  const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
7434
7447
  if (match) {
7435
7448
  chars.advanceBy(match[0].length);
@@ -7454,7 +7467,7 @@ function tokenizeString(chars) {
7454
7467
  }
7455
7468
  return null;
7456
7469
  }
7457
- const separatorRegexp = /\w|\.|!|\$/;
7470
+ const separatorRegexp = /^[\w\.!\$]+/;
7458
7471
  /**
7459
7472
  * A "Symbol" is just basically any word-like element that can appear in a
7460
7473
  * formula, which is not a string. So:
@@ -7494,8 +7507,11 @@ function tokenizeSymbol(chars) {
7494
7507
  };
7495
7508
  }
7496
7509
  }
7497
- while (chars.current && separatorRegexp.test(chars.current)) {
7498
- result += chars.shift();
7510
+ const match = chars.remaining().match(separatorRegexp);
7511
+ if (match) {
7512
+ const value = match[0];
7513
+ result += value;
7514
+ chars.advanceBy(value.length);
7499
7515
  }
7500
7516
  if (result.length) {
7501
7517
  const value = result;
@@ -7557,7 +7573,10 @@ class TokenizingChars {
7557
7573
  return this.text.substring(this.currentIndex);
7558
7574
  }
7559
7575
  currentStartsWith(str) {
7560
- for (let j = 0; j < str.length; j++) {
7576
+ if (this.current !== str[0]) {
7577
+ return false;
7578
+ }
7579
+ for (let j = 1; j < str.length; j++) {
7561
7580
  if (this.text[this.currentIndex + j] !== str[j]) {
7562
7581
  return false;
7563
7582
  }
@@ -7584,6 +7603,9 @@ function isValidLocale(locale) {
7584
7603
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7585
7604
  return false;
7586
7605
  }
7606
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7607
+ return false;
7608
+ }
7587
7609
  try {
7588
7610
  formatValue(1, { locale, format: "#,##0.00" });
7589
7611
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7674,7 +7696,7 @@ function canonicalizeNumberLiteral(content, locale) {
7674
7696
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7675
7697
  return content;
7676
7698
  }
7677
- return content.replace(locale.decimalSeparator, ".");
7699
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7678
7700
  }
7679
7701
  /**
7680
7702
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -10746,6 +10768,12 @@ const INSERT_LINK = (env) => {
10746
10768
  let { col, row } = env.model.getters.getActivePosition();
10747
10769
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10748
10770
  };
10771
+ const INSERT_LINK_NAME = (env) => {
10772
+ const sheetId = env.model.getters.getActiveSheetId();
10773
+ const { col, row } = env.model.getters.getActivePosition();
10774
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10775
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10776
+ };
10749
10777
  //------------------------------------------------------------------------------
10750
10778
  // Filters action
10751
10779
  //------------------------------------------------------------------------------
@@ -12898,6 +12926,18 @@ const TRUNC = {
12898
12926
  },
12899
12927
  isExported: true,
12900
12928
  };
12929
+ // -----------------------------------------------------------------------------
12930
+ // INT
12931
+ // -----------------------------------------------------------------------------
12932
+ const INT = {
12933
+ description: _t("Rounds a number down to the nearest integer that is less than or equal to it."),
12934
+ args: [arg("value (number)", _t("The number to round down to the nearest integer."))],
12935
+ returns: ["NUMBER"],
12936
+ compute: function (value) {
12937
+ return Math.floor(toNumber(value, this.locale));
12938
+ },
12939
+ isExported: true,
12940
+ };
12901
12941
 
12902
12942
  var math = /*#__PURE__*/Object.freeze({
12903
12943
  __proto__: null,
@@ -12931,6 +12971,7 @@ var math = /*#__PURE__*/Object.freeze({
12931
12971
  FLOOR: FLOOR,
12932
12972
  FLOOR_MATH: FLOOR_MATH,
12933
12973
  FLOOR_PRECISE: FLOOR_PRECISE,
12974
+ INT: INT,
12934
12975
  ISEVEN: ISEVEN,
12935
12976
  ISODD: ISODD,
12936
12977
  ISO_CEILING: ISO_CEILING,
@@ -19777,7 +19818,7 @@ cellMenuRegistry
19777
19818
  })
19778
19819
  .add("insert_link", {
19779
19820
  ...insertLink,
19780
- name: _t("Insert link"),
19821
+ name: INSERT_LINK_NAME,
19781
19822
  sequence: 150,
19782
19823
  separator: true,
19783
19824
  });
@@ -21369,7 +21410,7 @@ class SelectionInput extends Component {
21369
21410
  ? existingSelectionRanges
21370
21411
  : this.props.ranges().map((xc, id) => ({
21371
21412
  xc,
21372
- id,
21413
+ id: id + 1,
21373
21414
  isFocused: false,
21374
21415
  }));
21375
21416
  return ranges.map((range) => ({
@@ -26030,6 +26071,17 @@ css /* scss */ `
26030
26071
  `;
26031
26072
  class TextValueProvider extends Component {
26032
26073
  static template = "o-spreadsheet-TextValueProvider";
26074
+ autoCompleteListRef = useRef("autoCompleteList");
26075
+ setup() {
26076
+ useEffect(() => {
26077
+ const selectedIndex = this.props.selectedIndex;
26078
+ if (selectedIndex === undefined) {
26079
+ return;
26080
+ }
26081
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26082
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26083
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26084
+ }
26033
26085
  }
26034
26086
  TextValueProvider.props = {
26035
26087
  values: Array,
@@ -26481,6 +26533,7 @@ css /* scss */ `
26481
26533
  position: absolute;
26482
26534
  margin: 1px 4px;
26483
26535
  pointer-events: none;
26536
+ overflow: auto;
26484
26537
 
26485
26538
  .o-semi-bold {
26486
26539
  /** FIXME: to remove in favor of Bootstrap
@@ -26496,6 +26549,7 @@ class Composer extends Component {
26496
26549
  static components = { TextValueProvider, FunctionDescriptionProvider };
26497
26550
  static defaultProps = {
26498
26551
  inputStyle: "",
26552
+ isDefaultFocus: false,
26499
26553
  };
26500
26554
  composerRef = useRef("o_composer");
26501
26555
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -26526,7 +26580,10 @@ class Composer extends Component {
26526
26580
  if (this.props.delimitation && this.props.rect) {
26527
26581
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
26528
26582
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
26583
+ assistantStyle["max-height"] = `${remainingHeight}px`;
26529
26584
  if (cellY > remainingHeight) {
26585
+ const availableSpaceAbove = cellY;
26586
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
26530
26587
  // render top
26531
26588
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
26532
26589
  assistantStyle.top = `-3px`;
@@ -26552,7 +26609,6 @@ class Composer extends Component {
26552
26609
  F2: () => console.warn("Not implemented"),
26553
26610
  F4: this.processF4Key,
26554
26611
  Tab: (ev) => this.processTabKey(ev),
26555
- " ": (ev) => this.processSpaceKey(ev),
26556
26612
  };
26557
26613
  keyCodeMapping = {
26558
26614
  NumpadDecimal: this.processNumpadDecimal,
@@ -26560,12 +26616,12 @@ class Composer extends Component {
26560
26616
  setup() {
26561
26617
  onMounted(() => {
26562
26618
  const el = this.composerRef.el;
26619
+ if (this.props.isDefaultFocus) {
26620
+ this.env.focusableElement.setFocusableElement(el);
26621
+ }
26563
26622
  this.contentHelper.updateEl(el);
26564
26623
  this.processTokenAtCursor();
26565
26624
  });
26566
- onWillUnmount(() => {
26567
- this.props.onComposerUnmounted?.();
26568
- });
26569
26625
  useEffect(() => {
26570
26626
  this.processContent();
26571
26627
  });
@@ -26574,7 +26630,8 @@ class Composer extends Component {
26574
26630
  // Handlers
26575
26631
  // ---------------------------------------------------------------------------
26576
26632
  processArrowKeys(ev) {
26577
- if (this.env.model.getters.isSelectingForComposer()) {
26633
+ if (this.env.model.getters.isSelectingForComposer() ||
26634
+ this.env.model.getters.getEditionMode() === "inactive") {
26578
26635
  this.functionDescriptionState.showDescription = false;
26579
26636
  // Prevent the default content editable behavior which moves the cursor
26580
26637
  ev.preventDefault();
@@ -26617,26 +26674,20 @@ class Composer extends Component {
26617
26674
  processTabKey(ev) {
26618
26675
  ev.preventDefault();
26619
26676
  ev.stopPropagation();
26620
- const state = this.autoCompleteState;
26621
- if (state.showProvider && state.selectedIndex !== undefined) {
26622
- const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26623
- if (autoCompleteValue) {
26624
- this.autoComplete(autoCompleteValue);
26625
- return;
26677
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26678
+ const state = this.autoCompleteState;
26679
+ if (state.showProvider && state.selectedIndex !== undefined) {
26680
+ const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26681
+ if (autoCompleteValue) {
26682
+ this.autoComplete(autoCompleteValue);
26683
+ return;
26684
+ }
26626
26685
  }
26686
+ interactiveStopEdition(this.env);
26627
26687
  }
26628
26688
  const direction = ev.shiftKey ? "left" : "right";
26629
- interactiveStopEdition(this.env);
26630
26689
  this.env.model.selection.moveAnchorCell(direction, 1);
26631
26690
  }
26632
- processSpaceKey(ev) {
26633
- if (ev.ctrlKey) {
26634
- ev.preventDefault();
26635
- ev.stopPropagation();
26636
- this.showFunctionAutocomplete("");
26637
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26638
- }
26639
- }
26640
26691
  processEnterKey(ev) {
26641
26692
  ev.preventDefault();
26642
26693
  ev.stopPropagation();
@@ -26689,6 +26740,9 @@ class Composer extends Component {
26689
26740
  this.compositionActive = false;
26690
26741
  }
26691
26742
  onKeydown(ev) {
26743
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26744
+ return;
26745
+ }
26692
26746
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
26693
26747
  if (handler) {
26694
26748
  handler.call(this, ev);
@@ -26697,14 +26751,33 @@ class Composer extends Component {
26697
26751
  ev.stopPropagation();
26698
26752
  }
26699
26753
  }
26754
+ onPaste(ev) {
26755
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26756
+ ev.stopPropagation();
26757
+ }
26758
+ }
26700
26759
  /*
26701
26760
  * Triggered automatically by the content-editable between the keydown and key up
26702
26761
  * */
26703
26762
  onInput(ev) {
26704
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
26763
+ if (!this.shouldProcessInputEvents) {
26764
+ return;
26765
+ }
26766
+ if (ev.inputType === "insertFromPaste" &&
26767
+ this.env.model.getters.getEditionMode() === "inactive") {
26705
26768
  return;
26706
26769
  }
26707
- let content = this.contentHelper.getText();
26770
+ ev.stopPropagation();
26771
+ let content;
26772
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26773
+ content = ev.data || "";
26774
+ }
26775
+ else {
26776
+ content = this.contentHelper.getText();
26777
+ }
26778
+ if (this.props.focus === "inactive") {
26779
+ return this.props.onComposerCellFocused?.(content);
26780
+ }
26708
26781
  let selection = this.contentHelper.getCurrentSelection();
26709
26782
  if (ev.inputType === "insertParagraph") {
26710
26783
  const start = Math.min(selection.start, selection.end);
@@ -26786,9 +26859,8 @@ class Composer extends Component {
26786
26859
  }
26787
26860
  const newSelection = this.contentHelper.getCurrentSelection();
26788
26861
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26789
- if (this.props.focus === "inactive") {
26790
- this.props.onComposerContentFocused(newSelection);
26791
- }
26862
+ this.props.onComposerContentFocused();
26863
+ if (this.props.focus === "inactive") ;
26792
26864
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
26793
26865
  this.processTokenAtCursor();
26794
26866
  }
@@ -26955,7 +27027,7 @@ class Composer extends Component {
26955
27027
  const highlights = this.env.model.getters.getHighlights();
26956
27028
  const refSheet = sheetName
26957
27029
  ? this.env.model.getters.getSheetIdByName(sheetName)
26958
- : this.env.model.getters.getCurrentEditedCell().sheetId;
27030
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
26959
27031
  const highlight = highlights.find((highlight) => {
26960
27032
  if (highlight.sheetId !== refSheet)
26961
27033
  return false;
@@ -27048,12 +27120,13 @@ class Composer extends Component {
27048
27120
  }
27049
27121
  }
27050
27122
  Composer.props = {
27051
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27052
- onComposerContentFocused: Function,
27053
27123
  inputStyle: { type: String, optional: true },
27054
27124
  rect: { type: Object, optional: true },
27055
27125
  delimitation: { type: Object, optional: true },
27056
- onComposerUnmounted: { type: Function, optional: true },
27126
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27127
+ onComposerCellFocused: { type: Function, optional: true },
27128
+ onComposerContentFocused: Function,
27129
+ isDefaultFocus: { type: Boolean, optional: true },
27057
27130
  };
27058
27131
 
27059
27132
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -27087,51 +27160,25 @@ css /* scss */ `
27087
27160
  class GridComposer extends Component {
27088
27161
  static template = "o-spreadsheet-GridComposer";
27089
27162
  static components = { Composer };
27090
- gridComposerRef;
27091
- zone;
27092
- rect;
27163
+ rect = this.defaultRect;
27164
+ isEditing = false;
27093
27165
  isCellReferenceVisible;
27094
- composerState;
27166
+ get defaultRect() {
27167
+ return { x: 0, y: 0, width: 0, height: 0 };
27168
+ }
27095
27169
  setup() {
27096
- this.gridComposerRef = useRef("gridComposer");
27097
- this.composerState = useState({
27098
- rect: undefined,
27099
- delimitation: undefined,
27100
- });
27101
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
27102
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
27103
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
27104
- this.isCellReferenceVisible = false;
27105
- onMounted(() => {
27106
- const el = this.gridComposerRef.el;
27107
- this.composerState.rect = {
27108
- x: this.rect.x,
27109
- y: this.rect.y,
27110
- width: el.clientWidth,
27111
- height: el.clientHeight,
27112
- };
27113
- this.composerState.delimitation = {
27114
- width: this.props.gridDims.width,
27115
- height: this.props.gridDims.height,
27116
- };
27117
- });
27118
27170
  onWillUpdateProps(() => {
27119
- if (this.isCellReferenceVisible) {
27120
- return;
27121
- }
27122
- const sheetId = this.env.model.getters.getActiveSheetId();
27123
- const zone = this.env.model.getters.getSelectedZone();
27124
- const rect = this.env.model.getters.getVisibleRect(zone);
27125
- if (!deepEquals(rect, this.rect) ||
27126
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27127
- this.isCellReferenceVisible = true;
27128
- }
27171
+ this.updateComponentPosition();
27172
+ this.updateCellReferenceVisibility();
27129
27173
  });
27130
27174
  }
27131
27175
  get shouldDisplayCellReference() {
27132
27176
  return this.isCellReferenceVisible;
27133
27177
  }
27134
27178
  get cellReference() {
27179
+ if (!this.env.model.getters.getCurrentEditedCell()) {
27180
+ return "";
27181
+ }
27135
27182
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27136
27183
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27137
27184
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -27143,7 +27190,27 @@ class GridComposer extends Component {
27143
27190
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
27144
27191
  });
27145
27192
  }
27193
+ get composerProps() {
27194
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27195
+ return {
27196
+ rect: { ...this.rect },
27197
+ delimitation: {
27198
+ width,
27199
+ height,
27200
+ },
27201
+ focus: this.props.focus,
27202
+ isDefaultFocus: true,
27203
+ onComposerContentFocused: this.props.onComposerContentFocused,
27204
+ onComposerCellFocused: this.props.onComposerCellFocused,
27205
+ };
27206
+ }
27146
27207
  get containerStyle() {
27208
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27209
+ return `
27210
+ position: absolute;
27211
+ z-index: -1000;
27212
+ `;
27213
+ }
27147
27214
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27148
27215
  const cell = this.env.model.getters.getActiveCell();
27149
27216
  const position = this.env.model.getters.getActivePosition();
@@ -27163,6 +27230,8 @@ class GridComposer extends Component {
27163
27230
  if (!isFormula) {
27164
27231
  textAlign = style.align || cell.defaultAlign;
27165
27232
  }
27233
+ const maxHeight = this.props.gridDims.height - this.rect.y;
27234
+ const maxWidth = this.props.gridDims.width - this.rect.x;
27166
27235
  /**
27167
27236
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
27168
27237
  * including all the paddings/margins of the composer
@@ -27174,6 +27243,8 @@ class GridComposer extends Component {
27174
27243
  top: `${top}px`,
27175
27244
  "min-width": `${width + 1}px`,
27176
27245
  "min-height": `${height + 1}px`,
27246
+ "max-width": `${maxWidth}px`,
27247
+ "max-height": `${maxHeight}px`,
27177
27248
  background,
27178
27249
  color,
27179
27250
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -27183,20 +27254,38 @@ class GridComposer extends Component {
27183
27254
  "text-align": textAlign,
27184
27255
  });
27185
27256
  }
27186
- get composerStyle() {
27187
- const maxHeight = this.props.gridDims.height - this.rect.y;
27188
- const maxWidth = this.props.gridDims.width - this.rect.x;
27189
- return cssPropertiesToCss({
27190
- "max-width": `${maxWidth}px`,
27191
- "max-height": `${maxHeight}px`,
27192
- });
27257
+ updateComponentPosition() {
27258
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
27259
+ if (this.isEditing !== isEditing) {
27260
+ this.isEditing = isEditing;
27261
+ if (!isEditing) {
27262
+ this.rect = this.defaultRect;
27263
+ this.env.focusableElement.focus();
27264
+ return;
27265
+ }
27266
+ const position = this.env.model.getters.getActivePosition();
27267
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
27268
+ this.rect = this.env.model.getters.getVisibleRect(zone);
27269
+ }
27270
+ }
27271
+ updateCellReferenceVisibility() {
27272
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27273
+ return;
27274
+ }
27275
+ const sheetId = this.env.model.getters.getActiveSheetId();
27276
+ const zone = this.env.model.getters.getSelectedZone();
27277
+ const rect = this.env.model.getters.getVisibleRect(zone);
27278
+ if (!deepEquals(rect, this.rect) ||
27279
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27280
+ this.isCellReferenceVisible = true;
27281
+ }
27193
27282
  }
27194
27283
  }
27195
27284
  GridComposer.props = {
27196
27285
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27197
- onComposerUnmounted: Function,
27198
27286
  onComposerContentFocused: Function,
27199
27287
  gridDims: Object,
27288
+ onComposerCellFocused: Function,
27200
27289
  };
27201
27290
 
27202
27291
  const CSS$1 = css /* scss */ `
@@ -28287,7 +28376,6 @@ class GridOverlay extends Component {
28287
28376
  this.props.onCellDoubleClicked(col, row);
28288
28377
  }
28289
28378
  onContextMenu(ev) {
28290
- ev.preventDefault();
28291
28379
  const [col, row] = this.getCartesianCoordinates(ev);
28292
28380
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
28293
28381
  }
@@ -29372,7 +29460,6 @@ class Grid extends Component {
29372
29460
  HEADER_WIDTH = HEADER_WIDTH;
29373
29461
  menuState;
29374
29462
  gridRef;
29375
- hiddenInput;
29376
29463
  onMouseWheel;
29377
29464
  canvasPosition;
29378
29465
  hoveredCell;
@@ -29383,17 +29470,16 @@ class Grid extends Component {
29383
29470
  menuItems: [],
29384
29471
  });
29385
29472
  this.gridRef = useRef("grid");
29386
- this.hiddenInput = useRef("hiddenInput");
29387
29473
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
29388
29474
  this.hoveredCell = useState({ col: undefined, row: undefined });
29389
29475
  useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
29390
29476
  useExternalListener(document.body, "cut", this.copy.bind(this, true));
29391
29477
  useExternalListener(document.body, "copy", this.copy.bind(this, false));
29392
29478
  useExternalListener(document.body, "paste", this.paste);
29393
- onMounted(() => this.focus());
29394
- this.props.exposeFocus(() => this.focus());
29479
+ onMounted(() => this.focusDefaultElement());
29480
+ this.props.exposeFocus(() => this.focusDefaultElement());
29395
29481
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29396
- useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29482
+ useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
29397
29483
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29398
29484
  this.moveCanvas(deltaX, deltaY);
29399
29485
  this.hoveredCell.col = undefined;
@@ -29416,7 +29502,7 @@ class Grid extends Component {
29416
29502
  if (this.env.model.getters.hasOpenedPopover()) {
29417
29503
  this.closeOpenedPopover();
29418
29504
  }
29419
- this.focus();
29505
+ this.focusDefaultElement();
29420
29506
  }
29421
29507
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
29422
29508
  // down itself
@@ -29595,10 +29681,10 @@ class Grid extends Component {
29595
29681
  "ALT+SHIFT+ARROWUP": () => this.processHeaderGroupingKey("up"),
29596
29682
  "ALT+SHIFT+ARROWDOWN": () => this.processHeaderGroupingKey("down"),
29597
29683
  };
29598
- focus() {
29684
+ focusDefaultElement() {
29599
29685
  if (!this.env.model.getters.getSelectedFigureId() &&
29600
29686
  this.env.model.getters.getEditionMode() === "inactive") {
29601
- this.hiddenInput.el?.focus();
29687
+ this.env.focusableElement.focus();
29602
29688
  }
29603
29689
  }
29604
29690
  get gridEl() {
@@ -29743,19 +29829,6 @@ class Grid extends Component {
29743
29829
  return;
29744
29830
  }
29745
29831
  }
29746
- onInput(ev) {
29747
- // the user meant to paste in the sheet, not open the composer with the pasted content
29748
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
29749
- return;
29750
- }
29751
- if (ev.data) {
29752
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
29753
- // character
29754
- ev.preventDefault();
29755
- ev.stopPropagation();
29756
- this.props.onGridComposerCellFocused(ev.data);
29757
- }
29758
- }
29759
29832
  // ---------------------------------------------------------------------------
29760
29833
  // Context Menu
29761
29834
  // ---------------------------------------------------------------------------
@@ -29870,7 +29943,7 @@ class Grid extends Component {
29870
29943
  }
29871
29944
  closeMenu() {
29872
29945
  this.menuState.isOpen = false;
29873
- this.focus();
29946
+ this.focusDefaultElement();
29874
29947
  }
29875
29948
  processHeaderGroupingKey(direction) {
29876
29949
  if (this.env.model.getters.getSelectedZones().length !== 1) {
@@ -33766,8 +33839,16 @@ const MIGRATIONS = [
33766
33839
  },
33767
33840
  },
33768
33841
  {
33769
- description: "Change Border description structure",
33842
+ description: "Fix datafilter duplication",
33770
33843
  from: 12,
33844
+ to: 12.5,
33845
+ applyMigration(data) {
33846
+ return fixOverlappingFilters(data);
33847
+ },
33848
+ },
33849
+ {
33850
+ description: "Change Border description structure",
33851
+ from: 12.5,
33771
33852
  to: 13,
33772
33853
  applyMigration(data) {
33773
33854
  for (const borderId in data.borders) {
@@ -33798,6 +33879,14 @@ const MIGRATIONS = [
33798
33879
  return data;
33799
33880
  },
33800
33881
  },
33882
+ {
33883
+ description: "Fix datafilter duplication (post saas-16.4)",
33884
+ from: 14,
33885
+ to: 14.5,
33886
+ applyMigration(data) {
33887
+ return fixOverlappingFilters(data);
33888
+ },
33889
+ },
33801
33890
  ];
33802
33891
  /**
33803
33892
  * This function is used to repair faulty data independently of the migration.
@@ -33955,6 +34044,26 @@ function fixChartDefinitions(data, initialMessages) {
33955
34044
  }
33956
34045
  return messages;
33957
34046
  }
34047
+ function fixOverlappingFilters(data) {
34048
+ for (let sheet of data.sheets || []) {
34049
+ let knownDataFilterZones = [];
34050
+ for (let filterTable of sheet.filterTables || []) {
34051
+ const zone = toZone(filterTable.range);
34052
+ // See commit message of https://github.com/odoo/o-spreadsheet/pull/3632 of more details
34053
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
34054
+ if (intersectZoneIndex !== -1) {
34055
+ knownDataFilterZones[intersectZoneIndex] = zone;
34056
+ }
34057
+ else {
34058
+ knownDataFilterZones.push(zone);
34059
+ }
34060
+ }
34061
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
34062
+ range: zoneToXc(zone),
34063
+ }));
34064
+ }
34065
+ return data;
34066
+ }
33958
34067
  // -----------------------------------------------------------------------------
33959
34068
  // Helpers
33960
34069
  // -----------------------------------------------------------------------------
@@ -34604,22 +34713,7 @@ class BordersPlugin extends CorePlugin {
34604
34713
  }
34605
34714
  }
34606
34715
  export(data) {
34607
- // Borders
34608
- let borderId = 0;
34609
34716
  const borders = {};
34610
- /**
34611
- * Get the id of the given border. If the border does not exist, it creates
34612
- * one.
34613
- */
34614
- function getBorderId(border) {
34615
- for (let [key, value] of Object.entries(borders)) {
34616
- if (deepEquals(value, border)) {
34617
- return parseInt(key, 10);
34618
- }
34619
- }
34620
- borders[++borderId] = border;
34621
- return borderId;
34622
- }
34623
34717
  for (let sheet of data.sheets) {
34624
34718
  for (let col = 0; col < sheet.colNumber; col++) {
34625
34719
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -34627,7 +34721,7 @@ class BordersPlugin extends CorePlugin {
34627
34721
  if (border) {
34628
34722
  const xc = toXC(col, row);
34629
34723
  const cell = sheet.cells[xc];
34630
- const borderId = getBorderId(border);
34724
+ const borderId = getItemId(border, borders);
34631
34725
  if (cell) {
34632
34726
  cell.border = borderId;
34633
34727
  }
@@ -35432,9 +35526,9 @@ class CellPlugin extends CorePlugin {
35432
35526
  allowDispatch(cmd) {
35433
35527
  switch (cmd.type) {
35434
35528
  case "UPDATE_CELL":
35435
- return this.checkCellOutOfSheet(cmd);
35529
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35436
35530
  case "CLEAR_CELL":
35437
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35531
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35438
35532
  default:
35439
35533
  return "Success" /* CommandResult.Success */;
35440
35534
  }
@@ -35567,9 +35661,6 @@ class CellPlugin extends CorePlugin {
35567
35661
  format: cell.format ? getItemId(cell.format, formats) : undefined,
35568
35662
  content: cell.content || undefined,
35569
35663
  };
35570
- if (cell instanceof FormulaCellWithDependencies) {
35571
- cells[xc].content = cell.contentWithFixedReferences || undefined;
35572
- }
35573
35664
  }
35574
35665
  _sheet.cells = cells;
35575
35666
  }
@@ -35587,8 +35678,8 @@ class CellPlugin extends CorePlugin {
35587
35678
  }
35588
35679
  removeDefaultStyleValues(style) {
35589
35680
  const cleanedStyle = { ...style };
35590
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
35591
- if (cleanedStyle[property] === defaultValue) {
35681
+ for (const property in DEFAULT_STYLE) {
35682
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
35592
35683
  delete cleanedStyle[property];
35593
35684
  }
35594
35685
  }
@@ -35745,10 +35836,7 @@ class CellPlugin extends CorePlugin {
35745
35836
  else {
35746
35837
  style = before ? before.style : undefined;
35747
35838
  }
35748
- const locale = this.getters.getLocale();
35749
- let format = ("format" in after ? after.format : before && before.format) ||
35750
- detectDateFormat(afterContent, locale) ||
35751
- detectNumberFormat(afterContent);
35839
+ const format = "format" in after ? after.format : before && before.format;
35752
35840
  /* Read the following IF as:
35753
35841
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
35754
35842
  * - the command says the new content is empty and has no border/format/style
@@ -35789,12 +35877,11 @@ class CellPlugin extends CorePlugin {
35789
35877
  }
35790
35878
  createLiteralCell(id, content, format, style) {
35791
35879
  const locale = this.getters.getLocale();
35792
- content = parseLiteral(content, locale).toString();
35793
35880
  return {
35794
35881
  id,
35795
- content,
35882
+ content: parseLiteral(content, locale).toString(),
35796
35883
  style,
35797
- format,
35884
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
35798
35885
  isFormula: false,
35799
35886
  };
35800
35887
  }
@@ -35820,8 +35907,11 @@ class CellPlugin extends CorePlugin {
35820
35907
  * being a computed property to rebuild the dependencies XC.
35821
35908
  */
35822
35909
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
35823
- const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
35824
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
35910
+ const dependencies = [];
35911
+ for (const xc of compiledFormula.dependencies) {
35912
+ dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc));
35913
+ }
35914
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString);
35825
35915
  }
35826
35916
  createErrorFormula(id, content, format, style, error) {
35827
35917
  return {
@@ -35856,6 +35946,18 @@ class CellPlugin extends CorePlugin {
35856
35946
  }
35857
35947
  return "Success" /* CommandResult.Success */;
35858
35948
  }
35949
+ checkUselessUpdateCell(cmd) {
35950
+ const cell = this.getters.getCell(cmd);
35951
+ const hasContent = "content" in cmd || "formula" in cmd;
35952
+ const hasStyle = "style" in cmd;
35953
+ const hasFormat = "format" in cmd;
35954
+ if ((!hasContent || cell?.content === cmd.content) &&
35955
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
35956
+ (!hasFormat || cell?.format === cmd.format)) {
35957
+ return "NoChanges" /* CommandResult.NoChanges */;
35958
+ }
35959
+ return "Success" /* CommandResult.Success */;
35960
+ }
35859
35961
  }
35860
35962
  class FormulaCellWithDependencies {
35861
35963
  id;
@@ -35875,7 +35977,7 @@ class FormulaCellWithDependencies {
35875
35977
  const tokens = compiledFormula.tokens.map((token) => {
35876
35978
  if (token.type === "REFERENCE") {
35877
35979
  const index = rangeIndex++;
35878
- return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
35980
+ return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString);
35879
35981
  }
35880
35982
  return token;
35881
35983
  });
@@ -35902,13 +36004,20 @@ class FormulaCellWithDependencies {
35902
36004
  }
35903
36005
  }
35904
36006
  class RangeReferenceToken {
35905
- getValue;
36007
+ ranges;
36008
+ rangeIndex;
36009
+ sheetId;
36010
+ getRangeString;
35906
36011
  type = "REFERENCE";
35907
- constructor(getValue) {
35908
- this.getValue = getValue;
36012
+ constructor(ranges, rangeIndex, sheetId, getRangeString) {
36013
+ this.ranges = ranges;
36014
+ this.rangeIndex = rangeIndex;
36015
+ this.sheetId = sheetId;
36016
+ this.getRangeString = getRangeString;
35909
36017
  }
35910
36018
  get value() {
35911
- return this.getValue();
36019
+ const range = this.ranges[this.rangeIndex];
36020
+ return this.getRangeString(range, this.sheetId);
35912
36021
  }
35913
36022
  }
35914
36023
 
@@ -40644,7 +40753,15 @@ class SpreadsheetRTree {
40644
40753
  if (!this.rTrees[sheetId]) {
40645
40754
  return;
40646
40755
  }
40647
- this.rTrees[sheetId].remove(item, deepEquals);
40756
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
40757
+ }
40758
+ rtreeItemComparer(left, right) {
40759
+ return (left.data == right.data &&
40760
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
40761
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
40762
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
40763
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
40764
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
40648
40765
  }
40649
40766
  }
40650
40767
  /**
@@ -40722,7 +40839,7 @@ class FormulaDependencyGraph {
40722
40839
  const queue = Array.from(ranges).reverse();
40723
40840
  while (queue.length > 0) {
40724
40841
  const range = queue.pop();
40725
- visited.add(...this.encoder.encodeBoundingBox(range));
40842
+ visited.addMany(this.encoder.encodeBoundingBox(range));
40726
40843
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40727
40844
  for (const positionId of impactedPositionIds) {
40728
40845
  if (!visited.has(positionId)) {
@@ -40730,7 +40847,7 @@ class FormulaDependencyGraph {
40730
40847
  }
40731
40848
  }
40732
40849
  }
40733
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40850
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40734
40851
  return visited;
40735
40852
  }
40736
40853
  }
@@ -40868,9 +40985,9 @@ class Evaluator {
40868
40985
  const cells = positions.map((p) => this.encoder.encode(p));
40869
40986
  const cellsToCompute = new JetSet(cells);
40870
40987
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40871
- cellsToCompute.add(...this.getCellsDependingOn(cells));
40872
- cellsToCompute.add(...arrayFormulasPositionIds);
40873
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40988
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
40989
+ cellsToCompute.addMany(arrayFormulasPositionIds);
40990
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
40874
40991
  this.evaluate(cellsToCompute);
40875
40992
  }
40876
40993
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40884,7 +41001,7 @@ class Evaluator {
40884
41001
  }
40885
41002
  if (!content) {
40886
41003
  // The previous content could have blocked some array formulas
40887
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
41004
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40888
41005
  }
40889
41006
  }
40890
41007
  return impactedPositionIds;
@@ -40933,7 +41050,7 @@ class Evaluator {
40933
41050
  }
40934
41051
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
40935
41052
  const cells = new JetSet(arrayFormulas);
40936
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41053
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
40937
41054
  return cells;
40938
41055
  }
40939
41056
  nextPositionsToUpdate = new JetSet();
@@ -41082,7 +41199,7 @@ class Evaluator {
41082
41199
  this.setEvaluatedCell(positionId, evaluatedCell);
41083
41200
  // check if formula dependencies present in the spread zone
41084
41201
  // if so, they need to be recomputed
41085
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41202
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41086
41203
  };
41087
41204
  }
41088
41205
  invalidateSpreading(positionId) {
@@ -41097,8 +41214,8 @@ class Evaluator {
41097
41214
  continue;
41098
41215
  }
41099
41216
  this.evaluatedCells.delete(child);
41100
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41101
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41217
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41218
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41102
41219
  }
41103
41220
  this.spreadingRelations.removeNode(positionId);
41104
41221
  }
@@ -41447,7 +41564,13 @@ class EvaluationPlugin extends UIPlugin {
41447
41564
  const format = newFormat
41448
41565
  ? getItemId(newFormat, data.formats)
41449
41566
  : exportedCellData.format;
41450
- const content = !isExported ? newContent : exportedCellData.content;
41567
+ let content;
41568
+ if (formulaCell instanceof FormulaCellWithDependencies) {
41569
+ content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41570
+ }
41571
+ else {
41572
+ content = !isExported ? newContent : exportedCellData.content;
41573
+ }
41451
41574
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
41452
41575
  }
41453
41576
  }
@@ -46331,19 +46454,17 @@ class SelectionInputsManagerPlugin extends UIPlugin {
46331
46454
  break;
46332
46455
  case "FOCUS_RANGE":
46333
46456
  case "CHANGE_RANGE":
46334
- if (cmd.id !== this.focusedInputId) {
46335
- const input = this.inputs[cmd.id];
46336
- const range = input.ranges.find((range) => range.id === cmd.rangeId);
46337
- if (this.isRangeValid(range?.xc || "A1")) {
46338
- const sheetId = this.getters.getActiveSheetId();
46339
- const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
46340
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
46341
- handleEvent: input.handleEvent.bind(input),
46342
- release: () => (this.focusedInputId = null),
46343
- });
46344
- }
46345
- this.focusedInputId = cmd.id;
46457
+ const input = this.inputs[cmd.id];
46458
+ const range = input.ranges.find((range) => range.id === cmd.rangeId);
46459
+ if (range) {
46460
+ const sheetId = this.getters.getActiveSheetId();
46461
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
46462
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, {
46463
+ handleEvent: input.handleEvent.bind(input),
46464
+ release: () => (this.focusedInputId = null),
46465
+ });
46346
46466
  }
46467
+ this.focusedInputId = cmd.id;
46347
46468
  break;
46348
46469
  }
46349
46470
  this.currentInput?.handle(cmd);
@@ -50763,6 +50884,16 @@ class ImageProvider {
50763
50884
  }
50764
50885
  }
50765
50886
 
50887
+ class FocusableElement {
50888
+ focusableElement = undefined;
50889
+ setFocusableElement(element) {
50890
+ this.focusableElement = element;
50891
+ }
50892
+ focus() {
50893
+ this.focusableElement?.focus();
50894
+ }
50895
+ }
50896
+
50766
50897
  const RIPPLE_KEY_FRAMES = [
50767
50898
  { transform: "scale(0)" },
50768
50899
  { transform: "scale(0.8)", offset: 0.33 },
@@ -52919,6 +53050,7 @@ class Spreadsheet extends Component {
52919
53050
  toggleSidePanel: this.toggleSidePanel.bind(this),
52920
53051
  clipboard: this.env.clipboard || instantiateClipboard(),
52921
53052
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
53053
+ focusableElement: new FocusableElement(),
52922
53054
  });
52923
53055
  useExternalListener(window, "resize", () => this.render(true));
52924
53056
  useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -53025,16 +53157,17 @@ class Spreadsheet extends Component {
53025
53157
  }
53026
53158
  this.composer.topBarFocus = "contentFocus";
53027
53159
  this.composer.gridFocusMode = "inactive";
53028
- this.setComposerContent({ selection } || {});
53160
+ this.setComposerContent({ selection });
53029
53161
  }
53030
- onGridComposerContentFocused() {
53162
+ onGridComposerContentFocused(selection) {
53031
53163
  if (this.model.getters.isReadonly()) {
53032
53164
  return;
53033
53165
  }
53034
53166
  this.composer.topBarFocus = "inactive";
53035
53167
  this.composer.gridFocusMode = "contentFocus";
53036
- this.setComposerContent({});
53168
+ this.setComposerContent({ selection });
53037
53169
  }
53170
+ // TODO: either both are defined or none of them. change those args to an object
53038
53171
  onGridComposerCellFocused(content, selection) {
53039
53172
  if (this.model.getters.isReadonly()) {
53040
53173
  return;
@@ -54460,11 +54593,10 @@ class SelectionStreamProcessorImpl {
54460
54593
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
54461
54594
  */
54462
54595
  getEndOfCluster(startPosition, dim, dir) {
54463
- const sheet = this.getters.getActiveSheet();
54464
54596
  let currentPosition = startPosition;
54465
54597
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54466
54598
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54467
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
54599
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
54468
54600
  ? "endOfCluster"
54469
54601
  : "nextCluster";
54470
54602
  while (true) {
@@ -54474,7 +54606,7 @@ class SelectionStreamProcessorImpl {
54474
54606
  currentPosition.row === nextCellPosition.row) {
54475
54607
  break;
54476
54608
  }
54477
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
54609
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
54478
54610
  if (mode === "endOfCluster" && isNextCellEmpty) {
54479
54611
  break;
54480
54612
  }
@@ -54488,10 +54620,20 @@ class SelectionStreamProcessorImpl {
54488
54620
  return dim === "cols" ? currentPosition.col : currentPosition.row;
54489
54621
  }
54490
54622
  /**
54491
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
54492
- * check if the merge containing the cell is empty.
54623
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
54624
+ * the check applies to the main cell of the merge.
54493
54625
  */
54494
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
54626
+ isCellEmpty({ col, row }) {
54627
+ const sheetId = this.getters.getActiveSheetId();
54628
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
54629
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
54630
+ }
54631
+ /**
54632
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
54633
+ * the check applies to the main cell of the merge.
54634
+ */
54635
+ isEvaluatedCellEmpty({ col, row }) {
54636
+ const sheetId = this.getters.getActiveSheetId();
54495
54637
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
54496
54638
  const cell = this.getters.getEvaluatedCell(position);
54497
54639
  return cell.type === CellValueType.empty;
@@ -56762,6 +56904,6 @@ const constants = {
56762
56904
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
56763
56905
 
56764
56906
 
56765
- __info__.version = "17.0.10";
56766
- __info__.date = "2024-02-02T13:48:28.727Z";
56767
- __info__.hash = "be06aa9";
56907
+ __info__.version = "17.0.13";
56908
+ __info__.date = "2024-02-28T12:28:44.914Z";
56909
+ __info__.hash = "ed67be7";