@odoo/o-spreadsheet 17.0.10 → 17.0.12

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.12
6
+ * @date 2024-02-16T14:24:02.984Z
7
+ * @hash 8fa2bab
8
8
  */
9
9
 
10
10
  'use strict';
@@ -552,8 +552,8 @@ function isObjectEmptyRecursive(argument) {
552
552
  * If the given item does not exist in the dictionary, it creates one with a new id.
553
553
  */
554
554
  function getItemId(item, itemsDic) {
555
- for (let [key, value] of Object.entries(itemsDic)) {
556
- if (deepEquals(value, item)) {
555
+ for (const key in itemsDic) {
556
+ if (deepEquals(itemsDic[key], item)) {
557
557
  return parseInt(key, 10);
558
558
  }
559
559
  }
@@ -652,11 +652,13 @@ function deepEquals(o1, o2) {
652
652
  if (typeof o1 !== "object")
653
653
  return o1 === o2;
654
654
  // Objects can have different keys if the values are undefined
655
- const keys = new Set();
656
- Object.keys(o1).forEach((key) => keys.add(key));
657
- Object.keys(o2).forEach((key) => keys.add(key));
658
- for (let key of keys) {
659
- if (typeof o1[key] !== typeof o1[key])
655
+ for (const key in o2) {
656
+ if (!(key in o1) && o2[key] !== undefined) {
657
+ return false;
658
+ }
659
+ }
660
+ for (const key in o1) {
661
+ if (typeof o1[key] !== typeof o2[key])
660
662
  return false;
661
663
  if (typeof o1[key] === "object") {
662
664
  if (!deepEquals(o1[key], o2[key]))
@@ -726,18 +728,18 @@ function isConsecutive(iterable) {
726
728
  return true;
727
729
  }
728
730
  class JetSet extends Set {
729
- add(...iterable) {
731
+ addMany(iterable) {
730
732
  for (const element of iterable) {
731
733
  super.add(element);
732
734
  }
733
735
  return this;
734
736
  }
735
- delete(...iterable) {
736
- let deleted = false;
737
+ deleteMany(iterable) {
738
+ let wasDeleted = false;
737
739
  for (const element of iterable) {
738
- deleted ||= super.delete(element);
740
+ wasDeleted ||= super.delete(element);
739
741
  }
740
- return deleted;
742
+ return wasDeleted;
741
743
  }
742
744
  }
743
745
  /**
@@ -1996,7 +1998,7 @@ class DispatchResult {
1996
1998
  * Static helper which returns a successful DispatchResult
1997
1999
  */
1998
2000
  static get Success() {
1999
- return new DispatchResult();
2001
+ return SUCCESS;
2000
2002
  }
2001
2003
  get isSuccessful() {
2002
2004
  return this.reasons.length === 0;
@@ -2009,6 +2011,7 @@ class DispatchResult {
2009
2011
  return this.reasons.includes(reason);
2010
2012
  }
2011
2013
  }
2014
+ const SUCCESS = new DispatchResult();
2012
2015
  exports.CommandResult = void 0;
2013
2016
  (function (CommandResult) {
2014
2017
  CommandResult["Success"] = "Success";
@@ -2890,6 +2893,9 @@ function parseFormat(formatString) {
2890
2893
  function formatValue(value, { format, locale }) {
2891
2894
  switch (typeof value) {
2892
2895
  case "string":
2896
+ if (value.includes('\\"')) {
2897
+ return value.replace(/\\"/g, '"');
2898
+ }
2893
2899
  return value;
2894
2900
  case "boolean":
2895
2901
  return value ? "TRUE" : "FALSE";
@@ -7381,7 +7387,7 @@ function tokenize(str, locale = DEFAULT_LOCALE) {
7381
7387
  while (!chars.isOver()) {
7382
7388
  let token = tokenizeSpace(chars) ||
7383
7389
  tokenizeArgsSeparator(chars, locale) ||
7384
- tokenizeMisc(chars) ||
7390
+ tokenizeParenthesis(chars) ||
7385
7391
  tokenizeOperator(chars) ||
7386
7392
  tokenizeString(chars) ||
7387
7393
  tokenizeDebugger(chars) ||
@@ -7402,15 +7408,14 @@ function tokenizeDebugger(chars) {
7402
7408
  }
7403
7409
  return null;
7404
7410
  }
7405
- const misc$1 = {
7406
- "(": "LEFT_PAREN",
7407
- ")": "RIGHT_PAREN",
7411
+ const parenthesis = {
7412
+ "(": { type: "LEFT_PAREN", value: "(" },
7413
+ ")": { type: "RIGHT_PAREN", value: ")" },
7408
7414
  };
7409
- function tokenizeMisc(chars) {
7410
- if (chars.current in misc$1) {
7415
+ function tokenizeParenthesis(chars) {
7416
+ if (chars.current === "(" || chars.current === ")") {
7411
7417
  const value = chars.shift();
7412
- const type = misc$1[value];
7413
- return { type, value };
7418
+ return parenthesis[value];
7414
7419
  }
7415
7420
  return null;
7416
7421
  }
@@ -7431,7 +7436,12 @@ function tokenizeOperator(chars) {
7431
7436
  }
7432
7437
  return null;
7433
7438
  }
7439
+ const FIRST_POSSIBLE_NUMBER_CHARS = new Set("0123456789");
7434
7440
  function tokenizeNumber(chars, locale) {
7441
+ if (!FIRST_POSSIBLE_NUMBER_CHARS.has(chars.current) &&
7442
+ chars.current !== locale.decimalSeparator) {
7443
+ return null;
7444
+ }
7435
7445
  const match = chars.remaining().match(getFormulaNumberRegex(locale.decimalSeparator));
7436
7446
  if (match) {
7437
7447
  chars.advanceBy(match[0].length);
@@ -7456,7 +7466,7 @@ function tokenizeString(chars) {
7456
7466
  }
7457
7467
  return null;
7458
7468
  }
7459
- const separatorRegexp = /\w|\.|!|\$/;
7469
+ const separatorRegexp = /^[\w\.!\$]+/;
7460
7470
  /**
7461
7471
  * A "Symbol" is just basically any word-like element that can appear in a
7462
7472
  * formula, which is not a string. So:
@@ -7496,8 +7506,11 @@ function tokenizeSymbol(chars) {
7496
7506
  };
7497
7507
  }
7498
7508
  }
7499
- while (chars.current && separatorRegexp.test(chars.current)) {
7500
- result += chars.shift();
7509
+ const match = chars.remaining().match(separatorRegexp);
7510
+ if (match) {
7511
+ const value = match[0];
7512
+ result += value;
7513
+ chars.advanceBy(value.length);
7501
7514
  }
7502
7515
  if (result.length) {
7503
7516
  const value = result;
@@ -7559,7 +7572,10 @@ class TokenizingChars {
7559
7572
  return this.text.substring(this.currentIndex);
7560
7573
  }
7561
7574
  currentStartsWith(str) {
7562
- for (let j = 0; j < str.length; j++) {
7575
+ if (this.current !== str[0]) {
7576
+ return false;
7577
+ }
7578
+ for (let j = 1; j < str.length; j++) {
7563
7579
  if (this.text[this.currentIndex + j] !== str[j]) {
7564
7580
  return false;
7565
7581
  }
@@ -7586,6 +7602,9 @@ function isValidLocale(locale) {
7586
7602
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
7587
7603
  return false;
7588
7604
  }
7605
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
7606
+ return false;
7607
+ }
7589
7608
  try {
7590
7609
  formatValue(1, { locale, format: "#,##0.00" });
7591
7610
  formatValue(1, { locale, format: locale.dateFormat });
@@ -7676,7 +7695,7 @@ function canonicalizeNumberLiteral(content, locale) {
7676
7695
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
7677
7696
  return content;
7678
7697
  }
7679
- return content.replace(locale.decimalSeparator, ".");
7698
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
7680
7699
  }
7681
7700
  /**
7682
7701
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -10748,6 +10767,12 @@ const INSERT_LINK = (env) => {
10748
10767
  let { col, row } = env.model.getters.getActivePosition();
10749
10768
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
10750
10769
  };
10770
+ const INSERT_LINK_NAME = (env) => {
10771
+ const sheetId = env.model.getters.getActiveSheetId();
10772
+ const { col, row } = env.model.getters.getActivePosition();
10773
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
10774
+ return cell && cell.link ? _t("Edit link") : _t("Insert link");
10775
+ };
10751
10776
  //------------------------------------------------------------------------------
10752
10777
  // Filters action
10753
10778
  //------------------------------------------------------------------------------
@@ -19779,7 +19804,7 @@ cellMenuRegistry
19779
19804
  })
19780
19805
  .add("insert_link", {
19781
19806
  ...insertLink,
19782
- name: _t("Insert link"),
19807
+ name: INSERT_LINK_NAME,
19783
19808
  sequence: 150,
19784
19809
  separator: true,
19785
19810
  });
@@ -26032,6 +26057,17 @@ css /* scss */ `
26032
26057
  `;
26033
26058
  class TextValueProvider extends owl.Component {
26034
26059
  static template = "o-spreadsheet-TextValueProvider";
26060
+ autoCompleteListRef = owl.useRef("autoCompleteList");
26061
+ setup() {
26062
+ owl.useEffect(() => {
26063
+ const selectedIndex = this.props.selectedIndex;
26064
+ if (selectedIndex === undefined) {
26065
+ return;
26066
+ }
26067
+ const selectedElement = this.autoCompleteListRef.el?.children[selectedIndex];
26068
+ selectedElement?.scrollIntoView?.({ block: "nearest" });
26069
+ }, () => [this.props.selectedIndex, this.autoCompleteListRef.el]);
26070
+ }
26035
26071
  }
26036
26072
  TextValueProvider.props = {
26037
26073
  values: Array,
@@ -26483,6 +26519,7 @@ css /* scss */ `
26483
26519
  position: absolute;
26484
26520
  margin: 1px 4px;
26485
26521
  pointer-events: none;
26522
+ overflow: auto;
26486
26523
 
26487
26524
  .o-semi-bold {
26488
26525
  /** FIXME: to remove in favor of Bootstrap
@@ -26498,6 +26535,7 @@ class Composer extends owl.Component {
26498
26535
  static components = { TextValueProvider, FunctionDescriptionProvider };
26499
26536
  static defaultProps = {
26500
26537
  inputStyle: "",
26538
+ isDefaultFocus: false,
26501
26539
  };
26502
26540
  composerRef = owl.useRef("o_composer");
26503
26541
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -26528,7 +26566,10 @@ class Composer extends owl.Component {
26528
26566
  if (this.props.delimitation && this.props.rect) {
26529
26567
  const { x: cellX, y: cellY, height: cellHeight } = this.props.rect;
26530
26568
  const remainingHeight = this.props.delimitation.height - (cellY + cellHeight);
26569
+ assistantStyle["max-height"] = `${remainingHeight}px`;
26531
26570
  if (cellY > remainingHeight) {
26571
+ const availableSpaceAbove = cellY;
26572
+ assistantStyle["max-height"] = `${availableSpaceAbove}px`;
26532
26573
  // render top
26533
26574
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
26534
26575
  assistantStyle.top = `-3px`;
@@ -26554,7 +26595,6 @@ class Composer extends owl.Component {
26554
26595
  F2: () => console.warn("Not implemented"),
26555
26596
  F4: this.processF4Key,
26556
26597
  Tab: (ev) => this.processTabKey(ev),
26557
- " ": (ev) => this.processSpaceKey(ev),
26558
26598
  };
26559
26599
  keyCodeMapping = {
26560
26600
  NumpadDecimal: this.processNumpadDecimal,
@@ -26562,12 +26602,12 @@ class Composer extends owl.Component {
26562
26602
  setup() {
26563
26603
  owl.onMounted(() => {
26564
26604
  const el = this.composerRef.el;
26605
+ if (this.props.isDefaultFocus) {
26606
+ this.env.focusableElement.setFocusableElement(el);
26607
+ }
26565
26608
  this.contentHelper.updateEl(el);
26566
26609
  this.processTokenAtCursor();
26567
26610
  });
26568
- owl.onWillUnmount(() => {
26569
- this.props.onComposerUnmounted?.();
26570
- });
26571
26611
  owl.useEffect(() => {
26572
26612
  this.processContent();
26573
26613
  });
@@ -26576,7 +26616,8 @@ class Composer extends owl.Component {
26576
26616
  // Handlers
26577
26617
  // ---------------------------------------------------------------------------
26578
26618
  processArrowKeys(ev) {
26579
- if (this.env.model.getters.isSelectingForComposer()) {
26619
+ if (this.env.model.getters.isSelectingForComposer() ||
26620
+ this.env.model.getters.getEditionMode() === "inactive") {
26580
26621
  this.functionDescriptionState.showDescription = false;
26581
26622
  // Prevent the default content editable behavior which moves the cursor
26582
26623
  ev.preventDefault();
@@ -26619,26 +26660,20 @@ class Composer extends owl.Component {
26619
26660
  processTabKey(ev) {
26620
26661
  ev.preventDefault();
26621
26662
  ev.stopPropagation();
26622
- const state = this.autoCompleteState;
26623
- if (state.showProvider && state.selectedIndex !== undefined) {
26624
- const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26625
- if (autoCompleteValue) {
26626
- this.autoComplete(autoCompleteValue);
26627
- return;
26663
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26664
+ const state = this.autoCompleteState;
26665
+ if (state.showProvider && state.selectedIndex !== undefined) {
26666
+ const autoCompleteValue = this.autoCompleteState.values[state.selectedIndex]?.text;
26667
+ if (autoCompleteValue) {
26668
+ this.autoComplete(autoCompleteValue);
26669
+ return;
26670
+ }
26628
26671
  }
26672
+ interactiveStopEdition(this.env);
26629
26673
  }
26630
26674
  const direction = ev.shiftKey ? "left" : "right";
26631
- interactiveStopEdition(this.env);
26632
26675
  this.env.model.selection.moveAnchorCell(direction, 1);
26633
26676
  }
26634
- processSpaceKey(ev) {
26635
- if (ev.ctrlKey) {
26636
- ev.preventDefault();
26637
- ev.stopPropagation();
26638
- this.showFunctionAutocomplete("");
26639
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26640
- }
26641
- }
26642
26677
  processEnterKey(ev) {
26643
26678
  ev.preventDefault();
26644
26679
  ev.stopPropagation();
@@ -26691,6 +26726,9 @@ class Composer extends owl.Component {
26691
26726
  this.compositionActive = false;
26692
26727
  }
26693
26728
  onKeydown(ev) {
26729
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26730
+ return;
26731
+ }
26694
26732
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
26695
26733
  if (handler) {
26696
26734
  handler.call(this, ev);
@@ -26699,14 +26737,33 @@ class Composer extends owl.Component {
26699
26737
  ev.stopPropagation();
26700
26738
  }
26701
26739
  }
26740
+ onPaste(ev) {
26741
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
26742
+ ev.stopPropagation();
26743
+ }
26744
+ }
26702
26745
  /*
26703
26746
  * Triggered automatically by the content-editable between the keydown and key up
26704
26747
  * */
26705
26748
  onInput(ev) {
26706
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
26749
+ if (!this.shouldProcessInputEvents) {
26707
26750
  return;
26708
26751
  }
26709
- let content = this.contentHelper.getText();
26752
+ if (ev.inputType === "insertFromPaste" &&
26753
+ this.env.model.getters.getEditionMode() === "inactive") {
26754
+ return;
26755
+ }
26756
+ ev.stopPropagation();
26757
+ let content;
26758
+ if (this.env.model.getters.getEditionMode() === "inactive") {
26759
+ content = ev.data || "";
26760
+ }
26761
+ else {
26762
+ content = this.contentHelper.getText();
26763
+ }
26764
+ if (this.props.focus === "inactive") {
26765
+ return this.props.onComposerCellFocused?.(content);
26766
+ }
26710
26767
  let selection = this.contentHelper.getCurrentSelection();
26711
26768
  if (ev.inputType === "insertParagraph") {
26712
26769
  const start = Math.min(selection.start, selection.end);
@@ -26788,9 +26845,8 @@ class Composer extends owl.Component {
26788
26845
  }
26789
26846
  const newSelection = this.contentHelper.getCurrentSelection();
26790
26847
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
26791
- if (this.props.focus === "inactive") {
26792
- this.props.onComposerContentFocused(newSelection);
26793
- }
26848
+ this.props.onComposerContentFocused();
26849
+ if (this.props.focus === "inactive") ;
26794
26850
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
26795
26851
  this.processTokenAtCursor();
26796
26852
  }
@@ -26957,7 +27013,7 @@ class Composer extends owl.Component {
26957
27013
  const highlights = this.env.model.getters.getHighlights();
26958
27014
  const refSheet = sheetName
26959
27015
  ? this.env.model.getters.getSheetIdByName(sheetName)
26960
- : this.env.model.getters.getCurrentEditedCell().sheetId;
27016
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
26961
27017
  const highlight = highlights.find((highlight) => {
26962
27018
  if (highlight.sheetId !== refSheet)
26963
27019
  return false;
@@ -27050,12 +27106,13 @@ class Composer extends owl.Component {
27050
27106
  }
27051
27107
  }
27052
27108
  Composer.props = {
27053
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27054
- onComposerContentFocused: Function,
27055
27109
  inputStyle: { type: String, optional: true },
27056
27110
  rect: { type: Object, optional: true },
27057
27111
  delimitation: { type: Object, optional: true },
27058
- onComposerUnmounted: { type: Function, optional: true },
27112
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27113
+ onComposerCellFocused: { type: Function, optional: true },
27114
+ onComposerContentFocused: Function,
27115
+ isDefaultFocus: { type: Boolean, optional: true },
27059
27116
  };
27060
27117
 
27061
27118
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -27089,51 +27146,25 @@ css /* scss */ `
27089
27146
  class GridComposer extends owl.Component {
27090
27147
  static template = "o-spreadsheet-GridComposer";
27091
27148
  static components = { Composer };
27092
- gridComposerRef;
27093
- zone;
27094
- rect;
27149
+ rect = this.defaultRect;
27150
+ isEditing = false;
27095
27151
  isCellReferenceVisible;
27096
- composerState;
27152
+ get defaultRect() {
27153
+ return { x: 0, y: 0, width: 0, height: 0 };
27154
+ }
27097
27155
  setup() {
27098
- this.gridComposerRef = owl.useRef("gridComposer");
27099
- this.composerState = owl.useState({
27100
- rect: undefined,
27101
- delimitation: undefined,
27102
- });
27103
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
27104
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
27105
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
27106
- this.isCellReferenceVisible = false;
27107
- owl.onMounted(() => {
27108
- const el = this.gridComposerRef.el;
27109
- this.composerState.rect = {
27110
- x: this.rect.x,
27111
- y: this.rect.y,
27112
- width: el.clientWidth,
27113
- height: el.clientHeight,
27114
- };
27115
- this.composerState.delimitation = {
27116
- width: this.props.gridDims.width,
27117
- height: this.props.gridDims.height,
27118
- };
27119
- });
27120
27156
  owl.onWillUpdateProps(() => {
27121
- if (this.isCellReferenceVisible) {
27122
- return;
27123
- }
27124
- const sheetId = this.env.model.getters.getActiveSheetId();
27125
- const zone = this.env.model.getters.getSelectedZone();
27126
- const rect = this.env.model.getters.getVisibleRect(zone);
27127
- if (!deepEquals(rect, this.rect) ||
27128
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27129
- this.isCellReferenceVisible = true;
27130
- }
27157
+ this.updateComponentPosition();
27158
+ this.updateCellReferenceVisibility();
27131
27159
  });
27132
27160
  }
27133
27161
  get shouldDisplayCellReference() {
27134
27162
  return this.isCellReferenceVisible;
27135
27163
  }
27136
27164
  get cellReference() {
27165
+ if (!this.env.model.getters.getCurrentEditedCell()) {
27166
+ return "";
27167
+ }
27137
27168
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
27138
27169
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
27139
27170
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -27145,7 +27176,27 @@ class GridComposer extends owl.Component {
27145
27176
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
27146
27177
  });
27147
27178
  }
27179
+ get composerProps() {
27180
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
27181
+ return {
27182
+ rect: { ...this.rect },
27183
+ delimitation: {
27184
+ width,
27185
+ height,
27186
+ },
27187
+ focus: this.props.focus,
27188
+ isDefaultFocus: true,
27189
+ onComposerContentFocused: this.props.onComposerContentFocused,
27190
+ onComposerCellFocused: this.props.onComposerCellFocused,
27191
+ };
27192
+ }
27148
27193
  get containerStyle() {
27194
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
27195
+ return `
27196
+ position: absolute;
27197
+ z-index: -1000;
27198
+ `;
27199
+ }
27149
27200
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
27150
27201
  const cell = this.env.model.getters.getActiveCell();
27151
27202
  const position = this.env.model.getters.getActivePosition();
@@ -27165,6 +27216,8 @@ class GridComposer extends owl.Component {
27165
27216
  if (!isFormula) {
27166
27217
  textAlign = style.align || cell.defaultAlign;
27167
27218
  }
27219
+ const maxHeight = this.props.gridDims.height - this.rect.y;
27220
+ const maxWidth = this.props.gridDims.width - this.rect.x;
27168
27221
  /**
27169
27222
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
27170
27223
  * including all the paddings/margins of the composer
@@ -27176,6 +27229,8 @@ class GridComposer extends owl.Component {
27176
27229
  top: `${top}px`,
27177
27230
  "min-width": `${width + 1}px`,
27178
27231
  "min-height": `${height + 1}px`,
27232
+ "max-width": `${maxWidth}px`,
27233
+ "max-height": `${maxHeight}px`,
27179
27234
  background,
27180
27235
  color,
27181
27236
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -27185,20 +27240,38 @@ class GridComposer extends owl.Component {
27185
27240
  "text-align": textAlign,
27186
27241
  });
27187
27242
  }
27188
- get composerStyle() {
27189
- const maxHeight = this.props.gridDims.height - this.rect.y;
27190
- const maxWidth = this.props.gridDims.width - this.rect.x;
27191
- return cssPropertiesToCss({
27192
- "max-width": `${maxWidth}px`,
27193
- "max-height": `${maxHeight}px`,
27194
- });
27243
+ updateComponentPosition() {
27244
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
27245
+ if (this.isEditing !== isEditing) {
27246
+ this.isEditing = isEditing;
27247
+ if (!isEditing) {
27248
+ this.rect = this.defaultRect;
27249
+ this.env.focusableElement.focus();
27250
+ return;
27251
+ }
27252
+ const position = this.env.model.getters.getActivePosition();
27253
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
27254
+ this.rect = this.env.model.getters.getVisibleRect(zone);
27255
+ }
27256
+ }
27257
+ updateCellReferenceVisibility() {
27258
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
27259
+ return;
27260
+ }
27261
+ const sheetId = this.env.model.getters.getActiveSheetId();
27262
+ const zone = this.env.model.getters.getSelectedZone();
27263
+ const rect = this.env.model.getters.getVisibleRect(zone);
27264
+ if (!deepEquals(rect, this.rect) ||
27265
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
27266
+ this.isCellReferenceVisible = true;
27267
+ }
27195
27268
  }
27196
27269
  }
27197
27270
  GridComposer.props = {
27198
27271
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
27199
- onComposerUnmounted: Function,
27200
27272
  onComposerContentFocused: Function,
27201
27273
  gridDims: Object,
27274
+ onComposerCellFocused: Function,
27202
27275
  };
27203
27276
 
27204
27277
  const CSS$1 = css /* scss */ `
@@ -28289,7 +28362,6 @@ class GridOverlay extends owl.Component {
28289
28362
  this.props.onCellDoubleClicked(col, row);
28290
28363
  }
28291
28364
  onContextMenu(ev) {
28292
- ev.preventDefault();
28293
28365
  const [col, row] = this.getCartesianCoordinates(ev);
28294
28366
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
28295
28367
  }
@@ -29374,7 +29446,6 @@ class Grid extends owl.Component {
29374
29446
  HEADER_WIDTH = HEADER_WIDTH;
29375
29447
  menuState;
29376
29448
  gridRef;
29377
- hiddenInput;
29378
29449
  onMouseWheel;
29379
29450
  canvasPosition;
29380
29451
  hoveredCell;
@@ -29385,17 +29456,16 @@ class Grid extends owl.Component {
29385
29456
  menuItems: [],
29386
29457
  });
29387
29458
  this.gridRef = owl.useRef("grid");
29388
- this.hiddenInput = owl.useRef("hiddenInput");
29389
29459
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
29390
29460
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
29391
29461
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
29392
29462
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
29393
29463
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
29394
29464
  owl.useExternalListener(document.body, "paste", this.paste);
29395
- owl.onMounted(() => this.focus());
29396
- this.props.exposeFocus(() => this.focus());
29465
+ owl.onMounted(() => this.focusDefaultElement());
29466
+ this.props.exposeFocus(() => this.focusDefaultElement());
29397
29467
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
29398
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
29468
+ owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
29399
29469
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
29400
29470
  this.moveCanvas(deltaX, deltaY);
29401
29471
  this.hoveredCell.col = undefined;
@@ -29418,7 +29488,7 @@ class Grid extends owl.Component {
29418
29488
  if (this.env.model.getters.hasOpenedPopover()) {
29419
29489
  this.closeOpenedPopover();
29420
29490
  }
29421
- this.focus();
29491
+ this.focusDefaultElement();
29422
29492
  }
29423
29493
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
29424
29494
  // down itself
@@ -29597,10 +29667,10 @@ class Grid extends owl.Component {
29597
29667
  "ALT+SHIFT+ARROWUP": () => this.processHeaderGroupingKey("up"),
29598
29668
  "ALT+SHIFT+ARROWDOWN": () => this.processHeaderGroupingKey("down"),
29599
29669
  };
29600
- focus() {
29670
+ focusDefaultElement() {
29601
29671
  if (!this.env.model.getters.getSelectedFigureId() &&
29602
29672
  this.env.model.getters.getEditionMode() === "inactive") {
29603
- this.hiddenInput.el?.focus();
29673
+ this.env.focusableElement.focus();
29604
29674
  }
29605
29675
  }
29606
29676
  get gridEl() {
@@ -29745,19 +29815,6 @@ class Grid extends owl.Component {
29745
29815
  return;
29746
29816
  }
29747
29817
  }
29748
- onInput(ev) {
29749
- // the user meant to paste in the sheet, not open the composer with the pasted content
29750
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
29751
- return;
29752
- }
29753
- if (ev.data) {
29754
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
29755
- // character
29756
- ev.preventDefault();
29757
- ev.stopPropagation();
29758
- this.props.onGridComposerCellFocused(ev.data);
29759
- }
29760
- }
29761
29818
  // ---------------------------------------------------------------------------
29762
29819
  // Context Menu
29763
29820
  // ---------------------------------------------------------------------------
@@ -29872,7 +29929,7 @@ class Grid extends owl.Component {
29872
29929
  }
29873
29930
  closeMenu() {
29874
29931
  this.menuState.isOpen = false;
29875
- this.focus();
29932
+ this.focusDefaultElement();
29876
29933
  }
29877
29934
  processHeaderGroupingKey(direction) {
29878
29935
  if (this.env.model.getters.getSelectedZones().length !== 1) {
@@ -33767,6 +33824,31 @@ const MIGRATIONS = [
33767
33824
  return data;
33768
33825
  },
33769
33826
  },
33827
+ {
33828
+ description: "Fix datafilter duplication",
33829
+ from: 12,
33830
+ to: 12.5,
33831
+ applyMigration(data) {
33832
+ for (let sheet of data.sheets || []) {
33833
+ let knownDataFilterZones = [];
33834
+ for (let filterTable of sheet.filterTables || []) {
33835
+ const zone = toZone(filterTable.range);
33836
+ // See commit message for the details
33837
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
33838
+ if (intersectZoneIndex !== -1) {
33839
+ knownDataFilterZones[intersectZoneIndex] = zone;
33840
+ }
33841
+ else {
33842
+ knownDataFilterZones.push(zone);
33843
+ }
33844
+ }
33845
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
33846
+ range: zoneToXc(zone),
33847
+ }));
33848
+ }
33849
+ return data;
33850
+ },
33851
+ },
33770
33852
  {
33771
33853
  description: "Change Border description structure",
33772
33854
  from: 12,
@@ -34606,22 +34688,7 @@ class BordersPlugin extends CorePlugin {
34606
34688
  }
34607
34689
  }
34608
34690
  export(data) {
34609
- // Borders
34610
- let borderId = 0;
34611
34691
  const borders = {};
34612
- /**
34613
- * Get the id of the given border. If the border does not exist, it creates
34614
- * one.
34615
- */
34616
- function getBorderId(border) {
34617
- for (let [key, value] of Object.entries(borders)) {
34618
- if (deepEquals(value, border)) {
34619
- return parseInt(key, 10);
34620
- }
34621
- }
34622
- borders[++borderId] = border;
34623
- return borderId;
34624
- }
34625
34692
  for (let sheet of data.sheets) {
34626
34693
  for (let col = 0; col < sheet.colNumber; col++) {
34627
34694
  for (let row = 0; row < sheet.rowNumber; row++) {
@@ -34629,7 +34696,7 @@ class BordersPlugin extends CorePlugin {
34629
34696
  if (border) {
34630
34697
  const xc = toXC(col, row);
34631
34698
  const cell = sheet.cells[xc];
34632
- const borderId = getBorderId(border);
34699
+ const borderId = getItemId(border, borders);
34633
34700
  if (cell) {
34634
34701
  cell.border = borderId;
34635
34702
  }
@@ -35434,9 +35501,9 @@ class CellPlugin extends CorePlugin {
35434
35501
  allowDispatch(cmd) {
35435
35502
  switch (cmd.type) {
35436
35503
  case "UPDATE_CELL":
35437
- return this.checkCellOutOfSheet(cmd);
35504
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
35438
35505
  case "CLEAR_CELL":
35439
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
35506
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
35440
35507
  default:
35441
35508
  return "Success" /* CommandResult.Success */;
35442
35509
  }
@@ -35569,9 +35636,6 @@ class CellPlugin extends CorePlugin {
35569
35636
  format: cell.format ? getItemId(cell.format, formats) : undefined,
35570
35637
  content: cell.content || undefined,
35571
35638
  };
35572
- if (cell instanceof FormulaCellWithDependencies) {
35573
- cells[xc].content = cell.contentWithFixedReferences || undefined;
35574
- }
35575
35639
  }
35576
35640
  _sheet.cells = cells;
35577
35641
  }
@@ -35589,8 +35653,8 @@ class CellPlugin extends CorePlugin {
35589
35653
  }
35590
35654
  removeDefaultStyleValues(style) {
35591
35655
  const cleanedStyle = { ...style };
35592
- for (const [property, defaultValue] of Object.entries(DEFAULT_STYLE)) {
35593
- if (cleanedStyle[property] === defaultValue) {
35656
+ for (const property in DEFAULT_STYLE) {
35657
+ if (cleanedStyle[property] === DEFAULT_STYLE[property]) {
35594
35658
  delete cleanedStyle[property];
35595
35659
  }
35596
35660
  }
@@ -35747,10 +35811,7 @@ class CellPlugin extends CorePlugin {
35747
35811
  else {
35748
35812
  style = before ? before.style : undefined;
35749
35813
  }
35750
- const locale = this.getters.getLocale();
35751
- let format = ("format" in after ? after.format : before && before.format) ||
35752
- detectDateFormat(afterContent, locale) ||
35753
- detectNumberFormat(afterContent);
35814
+ const format = "format" in after ? after.format : before && before.format;
35754
35815
  /* Read the following IF as:
35755
35816
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
35756
35817
  * - the command says the new content is empty and has no border/format/style
@@ -35791,12 +35852,11 @@ class CellPlugin extends CorePlugin {
35791
35852
  }
35792
35853
  createLiteralCell(id, content, format, style) {
35793
35854
  const locale = this.getters.getLocale();
35794
- content = parseLiteral(content, locale).toString();
35795
35855
  return {
35796
35856
  id,
35797
- content,
35857
+ content: parseLiteral(content, locale).toString(),
35798
35858
  style,
35799
- format,
35859
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
35800
35860
  isFormula: false,
35801
35861
  };
35802
35862
  }
@@ -35822,8 +35882,11 @@ class CellPlugin extends CorePlugin {
35822
35882
  * being a computed property to rebuild the dependencies XC.
35823
35883
  */
35824
35884
  createFormulaCellWithDependencies(id, compiledFormula, format, style, sheetId) {
35825
- const dependencies = compiledFormula.dependencies.map((xc) => this.getters.getRangeFromSheetXC(sheetId, xc));
35826
- return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString.bind(this));
35885
+ const dependencies = [];
35886
+ for (const xc of compiledFormula.dependencies) {
35887
+ dependencies.push(this.getters.getRangeFromSheetXC(sheetId, xc));
35888
+ }
35889
+ return new FormulaCellWithDependencies(id, compiledFormula, format, style, dependencies, sheetId, this.getters.getRangeString);
35827
35890
  }
35828
35891
  createErrorFormula(id, content, format, style, error) {
35829
35892
  return {
@@ -35858,6 +35921,18 @@ class CellPlugin extends CorePlugin {
35858
35921
  }
35859
35922
  return "Success" /* CommandResult.Success */;
35860
35923
  }
35924
+ checkUselessUpdateCell(cmd) {
35925
+ const cell = this.getters.getCell(cmd);
35926
+ const hasContent = "content" in cmd || "formula" in cmd;
35927
+ const hasStyle = "style" in cmd;
35928
+ const hasFormat = "format" in cmd;
35929
+ if ((!hasContent || cell?.content === cmd.content) &&
35930
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
35931
+ (!hasFormat || cell?.format === cmd.format)) {
35932
+ return "NoChanges" /* CommandResult.NoChanges */;
35933
+ }
35934
+ return "Success" /* CommandResult.Success */;
35935
+ }
35861
35936
  }
35862
35937
  class FormulaCellWithDependencies {
35863
35938
  id;
@@ -35877,7 +35952,7 @@ class FormulaCellWithDependencies {
35877
35952
  const tokens = compiledFormula.tokens.map((token) => {
35878
35953
  if (token.type === "REFERENCE") {
35879
35954
  const index = rangeIndex++;
35880
- return new RangeReferenceToken(() => this.getRangeString(dependencies[index], this.sheetId));
35955
+ return new RangeReferenceToken(dependencies, index, this.sheetId, this.getRangeString);
35881
35956
  }
35882
35957
  return token;
35883
35958
  });
@@ -35904,13 +35979,20 @@ class FormulaCellWithDependencies {
35904
35979
  }
35905
35980
  }
35906
35981
  class RangeReferenceToken {
35907
- getValue;
35982
+ ranges;
35983
+ rangeIndex;
35984
+ sheetId;
35985
+ getRangeString;
35908
35986
  type = "REFERENCE";
35909
- constructor(getValue) {
35910
- this.getValue = getValue;
35987
+ constructor(ranges, rangeIndex, sheetId, getRangeString) {
35988
+ this.ranges = ranges;
35989
+ this.rangeIndex = rangeIndex;
35990
+ this.sheetId = sheetId;
35991
+ this.getRangeString = getRangeString;
35911
35992
  }
35912
35993
  get value() {
35913
- return this.getValue();
35994
+ const range = this.ranges[this.rangeIndex];
35995
+ return this.getRangeString(range, this.sheetId);
35914
35996
  }
35915
35997
  }
35916
35998
 
@@ -40646,7 +40728,15 @@ class SpreadsheetRTree {
40646
40728
  if (!this.rTrees[sheetId]) {
40647
40729
  return;
40648
40730
  }
40649
- this.rTrees[sheetId].remove(item, deepEquals);
40731
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
40732
+ }
40733
+ rtreeItemComparer(left, right) {
40734
+ return (left.data == right.data &&
40735
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
40736
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
40737
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
40738
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
40739
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
40650
40740
  }
40651
40741
  }
40652
40742
  /**
@@ -40724,7 +40814,7 @@ class FormulaDependencyGraph {
40724
40814
  const queue = Array.from(ranges).reverse();
40725
40815
  while (queue.length > 0) {
40726
40816
  const range = queue.pop();
40727
- visited.add(...this.encoder.encodeBoundingBox(range));
40817
+ visited.addMany(this.encoder.encodeBoundingBox(range));
40728
40818
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
40729
40819
  for (const positionId of impactedPositionIds) {
40730
40820
  if (!visited.has(positionId)) {
@@ -40732,7 +40822,7 @@ class FormulaDependencyGraph {
40732
40822
  }
40733
40823
  }
40734
40824
  }
40735
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40825
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
40736
40826
  return visited;
40737
40827
  }
40738
40828
  }
@@ -40870,9 +40960,9 @@ class Evaluator {
40870
40960
  const cells = positions.map((p) => this.encoder.encode(p));
40871
40961
  const cellsToCompute = new JetSet(cells);
40872
40962
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
40873
- cellsToCompute.add(...this.getCellsDependingOn(cells));
40874
- cellsToCompute.add(...arrayFormulasPositionIds);
40875
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
40963
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
40964
+ cellsToCompute.addMany(arrayFormulasPositionIds);
40965
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
40876
40966
  this.evaluate(cellsToCompute);
40877
40967
  }
40878
40968
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -40886,7 +40976,7 @@ class Evaluator {
40886
40976
  }
40887
40977
  if (!content) {
40888
40978
  // The previous content could have blocked some array formulas
40889
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40979
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
40890
40980
  }
40891
40981
  }
40892
40982
  return impactedPositionIds;
@@ -40935,7 +41025,7 @@ class Evaluator {
40935
41025
  }
40936
41026
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
40937
41027
  const cells = new JetSet(arrayFormulas);
40938
- cells.add(...this.getCellsDependingOn(arrayFormulas));
41028
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
40939
41029
  return cells;
40940
41030
  }
40941
41031
  nextPositionsToUpdate = new JetSet();
@@ -41084,7 +41174,7 @@ class Evaluator {
41084
41174
  this.setEvaluatedCell(positionId, evaluatedCell);
41085
41175
  // check if formula dependencies present in the spread zone
41086
41176
  // if so, they need to be recomputed
41087
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
41177
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
41088
41178
  };
41089
41179
  }
41090
41180
  invalidateSpreading(positionId) {
@@ -41099,8 +41189,8 @@ class Evaluator {
41099
41189
  continue;
41100
41190
  }
41101
41191
  this.evaluatedCells.delete(child);
41102
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
41103
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
41192
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
41193
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
41104
41194
  }
41105
41195
  this.spreadingRelations.removeNode(positionId);
41106
41196
  }
@@ -41449,7 +41539,13 @@ class EvaluationPlugin extends UIPlugin {
41449
41539
  const format = newFormat
41450
41540
  ? getItemId(newFormat, data.formats)
41451
41541
  : exportedCellData.format;
41452
- const content = !isExported ? newContent : exportedCellData.content;
41542
+ let content;
41543
+ if (formulaCell instanceof FormulaCellWithDependencies) {
41544
+ content = this.getters.getFormulaCellContent(exportedSheetData.id, formulaCell.compiledFormula, formulaCell.compiledFormula.dependencies, true);
41545
+ }
41546
+ else {
41547
+ content = !isExported ? newContent : exportedCellData.content;
41548
+ }
41453
41549
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
41454
41550
  }
41455
41551
  }
@@ -50765,6 +50861,16 @@ class ImageProvider {
50765
50861
  }
50766
50862
  }
50767
50863
 
50864
+ class FocusableElement {
50865
+ focusableElement = undefined;
50866
+ setFocusableElement(element) {
50867
+ this.focusableElement = element;
50868
+ }
50869
+ focus() {
50870
+ this.focusableElement?.focus();
50871
+ }
50872
+ }
50873
+
50768
50874
  const RIPPLE_KEY_FRAMES = [
50769
50875
  { transform: "scale(0)" },
50770
50876
  { transform: "scale(0.8)", offset: 0.33 },
@@ -52921,6 +53027,7 @@ class Spreadsheet extends owl.Component {
52921
53027
  toggleSidePanel: this.toggleSidePanel.bind(this),
52922
53028
  clipboard: this.env.clipboard || instantiateClipboard(),
52923
53029
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
53030
+ focusableElement: new FocusableElement(),
52924
53031
  });
52925
53032
  owl.useExternalListener(window, "resize", () => this.render(true));
52926
53033
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -53027,16 +53134,17 @@ class Spreadsheet extends owl.Component {
53027
53134
  }
53028
53135
  this.composer.topBarFocus = "contentFocus";
53029
53136
  this.composer.gridFocusMode = "inactive";
53030
- this.setComposerContent({ selection } || {});
53137
+ this.setComposerContent({ selection });
53031
53138
  }
53032
- onGridComposerContentFocused() {
53139
+ onGridComposerContentFocused(selection) {
53033
53140
  if (this.model.getters.isReadonly()) {
53034
53141
  return;
53035
53142
  }
53036
53143
  this.composer.topBarFocus = "inactive";
53037
53144
  this.composer.gridFocusMode = "contentFocus";
53038
- this.setComposerContent({});
53145
+ this.setComposerContent({ selection });
53039
53146
  }
53147
+ // TODO: either both are defined or none of them. change those args to an object
53040
53148
  onGridComposerCellFocused(content, selection) {
53041
53149
  if (this.model.getters.isReadonly()) {
53042
53150
  return;
@@ -54462,11 +54570,10 @@ class SelectionStreamProcessorImpl {
54462
54570
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
54463
54571
  */
54464
54572
  getEndOfCluster(startPosition, dim, dir) {
54465
- const sheet = this.getters.getActiveSheet();
54466
54573
  let currentPosition = startPosition;
54467
54574
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
54468
54575
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
54469
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
54576
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
54470
54577
  ? "endOfCluster"
54471
54578
  : "nextCluster";
54472
54579
  while (true) {
@@ -54476,7 +54583,7 @@ class SelectionStreamProcessorImpl {
54476
54583
  currentPosition.row === nextCellPosition.row) {
54477
54584
  break;
54478
54585
  }
54479
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
54586
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
54480
54587
  if (mode === "endOfCluster" && isNextCellEmpty) {
54481
54588
  break;
54482
54589
  }
@@ -54490,10 +54597,20 @@ class SelectionStreamProcessorImpl {
54490
54597
  return dim === "cols" ? currentPosition.col : currentPosition.row;
54491
54598
  }
54492
54599
  /**
54493
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
54494
- * check if the merge containing the cell is empty.
54600
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
54601
+ * the check applies to the main cell of the merge.
54602
+ */
54603
+ isCellEmpty({ col, row }) {
54604
+ const sheetId = this.getters.getActiveSheetId();
54605
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
54606
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
54607
+ }
54608
+ /**
54609
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
54610
+ * the check applies to the main cell of the merge.
54495
54611
  */
54496
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
54612
+ isEvaluatedCellEmpty({ col, row }) {
54613
+ const sheetId = this.getters.getActiveSheetId();
54497
54614
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
54498
54615
  const cell = this.getters.getEvaluatedCell(position);
54499
54616
  return cell.type === CellValueType.empty;
@@ -56799,6 +56916,6 @@ exports.setTranslationMethod = setTranslationMethod;
56799
56916
  exports.tokenize = tokenize;
56800
56917
 
56801
56918
 
56802
- __info__.version = "17.0.10";
56803
- __info__.date = "2024-02-02T13:48:28.727Z";
56804
- __info__.hash = "be06aa9";
56919
+ __info__.version = "17.0.12";
56920
+ __info__.date = "2024-02-16T14:24:02.984Z";
56921
+ __info__.hash = "8fa2bab";