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