@odoo/o-spreadsheet 16.3.24 → 16.3.26

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.
@@ -1742,6 +1742,9 @@ function parseFormat(formatString) {
1742
1742
  function formatValue(value, format) {
1743
1743
  switch (typeof value) {
1744
1744
  case "string":
1745
+ if (value.includes('\\"')) {
1746
+ return value.replace(/\\"/g, '"');
1747
+ }
1745
1748
  return value;
1746
1749
  case "boolean":
1747
1750
  return value ? "TRUE" : "FALSE";
@@ -9429,6 +9432,12 @@ const INSERT_LINK = (env) => {
9429
9432
  let { col, row } = env.model.getters.getActivePosition();
9430
9433
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
9431
9434
  };
9435
+ const INSERT_LINK_NAME = (env) => {
9436
+ const sheetId = env.model.getters.getActiveSheetId();
9437
+ const { col, row } = env.model.getters.getActivePosition();
9438
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
9439
+ return cell && cell.link ? _lt("Edit link") : _lt("Insert link");
9440
+ };
9432
9441
  //------------------------------------------------------------------------------
9433
9442
  // Filters action
9434
9443
  //------------------------------------------------------------------------------
@@ -16653,7 +16662,7 @@ cellMenuRegistry
16653
16662
  })
16654
16663
  .add("insert_link", {
16655
16664
  ...insertLink,
16656
- name: _lt("Insert link"),
16665
+ name: INSERT_LINK_NAME,
16657
16666
  sequence: 150,
16658
16667
  separator: true,
16659
16668
  });
@@ -20980,6 +20989,7 @@ class Composer extends Component {
20980
20989
  static components = { TextValueProvider, FunctionDescriptionProvider };
20981
20990
  static defaultProps = {
20982
20991
  inputStyle: "",
20992
+ isDefaultFocus: false,
20983
20993
  };
20984
20994
  composerRef = useRef("o_composer");
20985
20995
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -21037,13 +21047,13 @@ class Composer extends Component {
21037
21047
  setup() {
21038
21048
  onMounted(() => {
21039
21049
  const el = this.composerRef.el;
21050
+ if (this.props.isDefaultFocus) {
21051
+ this.env.focusableElement.setFocusableElement(el);
21052
+ }
21040
21053
  this.contentHelper.updateEl(el);
21041
21054
  this.processContent();
21042
21055
  this.contentHelper.scrollSelectionIntoView();
21043
21056
  });
21044
- onWillUnmount(() => {
21045
- this.props.onComposerUnmounted?.();
21046
- });
21047
21057
  onPatched(() => {
21048
21058
  if (!this.isKeyStillDown) {
21049
21059
  this.processContent();
@@ -21054,7 +21064,8 @@ class Composer extends Component {
21054
21064
  // Handlers
21055
21065
  // ---------------------------------------------------------------------------
21056
21066
  processArrowKeys(ev) {
21057
- if (this.env.model.getters.isSelectingForComposer()) {
21067
+ if (this.env.model.getters.isSelectingForComposer() ||
21068
+ this.env.model.getters.getEditionMode() === "inactive") {
21058
21069
  this.functionDescriptionState.showDescription = false;
21059
21070
  // Prevent the default content editable behavior which moves the cursor
21060
21071
  ev.preventDefault();
@@ -21094,21 +21105,23 @@ class Composer extends Component {
21094
21105
  processTabKey(ev) {
21095
21106
  ev.preventDefault();
21096
21107
  ev.stopPropagation();
21097
- if (this.autoCompleteState.showProvider) {
21098
- const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
21099
- if (autoCompleteValue) {
21100
- this.autoComplete(autoCompleteValue);
21101
- return;
21108
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
21109
+ if (this.autoCompleteState.showProvider) {
21110
+ const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
21111
+ if (autoCompleteValue) {
21112
+ this.autoComplete(autoCompleteValue);
21113
+ return;
21114
+ }
21102
21115
  }
21103
- }
21104
- else {
21105
- // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
21106
- // we can't let the model think that it is for a ref selection.
21107
- // todo: check if this can be removed someday
21108
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21116
+ else {
21117
+ // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
21118
+ // we can't let the model think that it is for a ref selection.
21119
+ // todo: check if this can be removed someday
21120
+ this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21121
+ }
21122
+ this.env.model.dispatch("STOP_EDITION");
21109
21123
  }
21110
21124
  const direction = ev.shiftKey ? "left" : "right";
21111
- this.env.model.dispatch("STOP_EDITION");
21112
21125
  this.env.model.selection.moveAnchorCell(direction, 1);
21113
21126
  }
21114
21127
  processEnterKey(ev) {
@@ -21152,6 +21165,9 @@ class Composer extends Component {
21152
21165
  this.compositionActive = false;
21153
21166
  }
21154
21167
  onKeydown(ev) {
21168
+ if (this.env.model.getters.getEditionMode() === "inactive") {
21169
+ return;
21170
+ }
21155
21171
  let handler = this.keyMapping[ev.key];
21156
21172
  if (handler) {
21157
21173
  handler.call(this, ev);
@@ -21162,22 +21178,44 @@ class Composer extends Component {
21162
21178
  }
21163
21179
  }
21164
21180
  updateCursorIfNeeded() {
21165
- if (!this.env.model.getters.isSelectingForComposer()) {
21181
+ const moveCursor = !this.env.model.getters.isSelectingForComposer() &&
21182
+ !(this.env.model.getters.getEditionMode() === "inactive");
21183
+ if (moveCursor) {
21166
21184
  const { start, end } = this.contentHelper.getCurrentSelection();
21167
21185
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
21168
21186
  this.isKeyStillDown = true;
21169
21187
  }
21170
21188
  }
21189
+ onPaste(ev) {
21190
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
21191
+ ev.stopPropagation();
21192
+ }
21193
+ }
21171
21194
  /*
21172
21195
  * Triggered automatically by the content-editable between the keydown and key up
21173
21196
  * */
21174
- onInput() {
21175
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
21197
+ onInput(ev) {
21198
+ if (!this.shouldProcessInputEvents) {
21176
21199
  return;
21177
21200
  }
21201
+ if (ev.inputType === "insertFromPaste" &&
21202
+ this.env.model.getters.getEditionMode() === "inactive") {
21203
+ return;
21204
+ }
21205
+ ev.stopPropagation();
21206
+ let content;
21207
+ if (this.env.model.getters.getEditionMode() === "inactive") {
21208
+ content = ev.data || "";
21209
+ }
21210
+ else {
21211
+ content = this.contentHelper.getText();
21212
+ }
21213
+ if (this.props.focus === "inactive") {
21214
+ return this.props.onComposerCellFocused?.(content);
21215
+ }
21178
21216
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21179
21217
  this.env.model.dispatch("SET_CURRENT_CONTENT", {
21180
- content: this.contentHelper.getText(),
21218
+ content,
21181
21219
  selection: this.contentHelper.getCurrentSelection(),
21182
21220
  });
21183
21221
  }
@@ -21248,9 +21286,8 @@ class Composer extends Component {
21248
21286
  }
21249
21287
  const newSelection = this.contentHelper.getCurrentSelection();
21250
21288
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
21251
- if (this.props.focus === "inactive") {
21252
- this.props.onComposerContentFocused(newSelection);
21253
- }
21289
+ this.props.onComposerContentFocused();
21290
+ if (this.props.focus === "inactive") ;
21254
21291
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
21255
21292
  this.processTokenAtCursor();
21256
21293
  }
@@ -21394,7 +21431,7 @@ class Composer extends Component {
21394
21431
  const highlights = this.env.model.getters.getHighlights();
21395
21432
  const refSheet = sheetName
21396
21433
  ? this.env.model.getters.getSheetIdByName(sheetName)
21397
- : this.env.model.getters.getCurrentEditedCell().sheetId;
21434
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
21398
21435
  const highlight = highlights.find((highlight) => {
21399
21436
  if (highlight.sheetId !== refSheet)
21400
21437
  return false;
@@ -21471,12 +21508,13 @@ class Composer extends Component {
21471
21508
  }
21472
21509
  }
21473
21510
  Composer.props = {
21474
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
21475
- onComposerContentFocused: Function,
21476
21511
  inputStyle: { type: String, optional: true },
21477
21512
  rect: { type: Object, optional: true },
21478
21513
  delimitation: { type: Object, optional: true },
21479
- onComposerUnmounted: { type: Function, optional: true },
21514
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
21515
+ onComposerCellFocused: { type: Function, optional: true },
21516
+ onComposerContentFocused: Function,
21517
+ isDefaultFocus: { type: Boolean, optional: true },
21480
21518
  };
21481
21519
 
21482
21520
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -21510,51 +21548,26 @@ css /* scss */ `
21510
21548
  class GridComposer extends Component {
21511
21549
  static template = "o-spreadsheet-GridComposer";
21512
21550
  static components = { Composer };
21513
- gridComposerRef;
21514
- zone;
21515
- rect;
21551
+ // TODORAR see if we can keep it undefined
21552
+ rect = this.defaultRect;
21553
+ isEditing = false;
21516
21554
  isCellReferenceVisible;
21517
- composerState;
21555
+ get defaultRect() {
21556
+ return { x: 0, y: 0, width: 0, height: 0 };
21557
+ }
21518
21558
  setup() {
21519
- this.gridComposerRef = useRef("gridComposer");
21520
- this.composerState = useState({
21521
- rect: undefined,
21522
- delimitation: undefined,
21523
- });
21524
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
21525
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
21526
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
21527
- this.isCellReferenceVisible = false;
21528
- onMounted(() => {
21529
- const el = this.gridComposerRef.el;
21530
- this.composerState.rect = {
21531
- x: this.rect.x,
21532
- y: this.rect.y,
21533
- width: el.clientWidth,
21534
- height: el.clientHeight,
21535
- };
21536
- this.composerState.delimitation = {
21537
- width: el.parentElement.clientWidth,
21538
- height: el.parentElement.clientHeight,
21539
- };
21540
- });
21541
21559
  onWillUpdateProps(() => {
21542
- if (this.isCellReferenceVisible) {
21543
- return;
21544
- }
21545
- const sheetId = this.env.model.getters.getActiveSheetId();
21546
- const zone = this.env.model.getters.getSelectedZone();
21547
- const rect = this.env.model.getters.getVisibleRect(zone);
21548
- if (!deepEquals(rect, this.rect) ||
21549
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
21550
- this.isCellReferenceVisible = true;
21551
- }
21560
+ this.updateComponentPosition();
21561
+ this.updateCellReferenceVisibility();
21552
21562
  });
21553
21563
  }
21554
21564
  get shouldDisplayCellReference() {
21555
21565
  return this.isCellReferenceVisible;
21556
21566
  }
21557
21567
  get cellReference() {
21568
+ if (!this.env.model.getters.getCurrentEditedCell()) {
21569
+ return "";
21570
+ }
21558
21571
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
21559
21572
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
21560
21573
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -21566,7 +21579,27 @@ class GridComposer extends Component {
21566
21579
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
21567
21580
  });
21568
21581
  }
21582
+ get composerProps() {
21583
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
21584
+ return {
21585
+ rect: { ...this.rect },
21586
+ delimitation: {
21587
+ width,
21588
+ height,
21589
+ },
21590
+ focus: this.props.focus,
21591
+ isDefaultFocus: true,
21592
+ onComposerContentFocused: this.props.onComposerContentFocused,
21593
+ onComposerCellFocused: this.props.onComposerCellFocused,
21594
+ };
21595
+ }
21569
21596
  get containerStyle() {
21597
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
21598
+ return `
21599
+ position: absolute;
21600
+ z-index: -1000;
21601
+ `;
21602
+ }
21570
21603
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
21571
21604
  const cell = this.env.model.getters.getActiveCell();
21572
21605
  const position = this.env.model.getters.getActivePosition();
@@ -21586,6 +21619,8 @@ class GridComposer extends Component {
21586
21619
  if (!isFormula) {
21587
21620
  textAlign = style.align || cell.defaultAlign;
21588
21621
  }
21622
+ const maxHeight = this.props.gridDims.height - this.rect.y;
21623
+ const maxWidth = this.props.gridDims.width - this.rect.x;
21589
21624
  /**
21590
21625
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
21591
21626
  * including all the paddings/margins of the composer
@@ -21597,6 +21632,8 @@ class GridComposer extends Component {
21597
21632
  top: `${top}px`,
21598
21633
  "min-width": `${width + 1}px`,
21599
21634
  "min-height": `${height + 1}px`,
21635
+ "max-width": `${maxWidth}px`,
21636
+ "max-height": `${maxHeight}px`,
21600
21637
  background,
21601
21638
  color,
21602
21639
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -21606,20 +21643,38 @@ class GridComposer extends Component {
21606
21643
  "text-align": textAlign,
21607
21644
  });
21608
21645
  }
21609
- get composerStyle() {
21610
- const maxHeight = this.props.gridDims.height - this.rect.y;
21611
- const maxWidth = this.props.gridDims.width - this.rect.x;
21612
- return cssPropertiesToCss({
21613
- "max-width": `${maxWidth}px`,
21614
- "max-height": `${maxHeight}px`,
21615
- });
21646
+ updateComponentPosition() {
21647
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
21648
+ if (this.isEditing !== isEditing) {
21649
+ this.isEditing = isEditing;
21650
+ if (!isEditing) {
21651
+ this.rect = this.defaultRect;
21652
+ this.env.focusableElement.focus();
21653
+ return;
21654
+ }
21655
+ const position = this.env.model.getters.getActivePosition();
21656
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
21657
+ this.rect = this.env.model.getters.getVisibleRect(zone);
21658
+ }
21659
+ }
21660
+ updateCellReferenceVisibility() {
21661
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
21662
+ return;
21663
+ }
21664
+ const sheetId = this.env.model.getters.getActiveSheetId();
21665
+ const zone = this.env.model.getters.getSelectedZone();
21666
+ const rect = this.env.model.getters.getVisibleRect(zone);
21667
+ if (!deepEquals(rect, this.rect) ||
21668
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
21669
+ this.isCellReferenceVisible = true;
21670
+ }
21616
21671
  }
21617
21672
  }
21618
21673
  GridComposer.props = {
21619
21674
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
21620
- onComposerUnmounted: Function,
21621
21675
  onComposerContentFocused: Function,
21622
21676
  gridDims: Object,
21677
+ onComposerCellFocused: Function,
21623
21678
  };
21624
21679
 
21625
21680
  const CSS$1 = css /* scss */ `
@@ -22488,7 +22543,6 @@ class GridOverlay extends Component {
22488
22543
  this.props.onCellDoubleClicked(col, row);
22489
22544
  }
22490
22545
  onContextMenu(ev) {
22491
- ev.preventDefault();
22492
22546
  const [col, row] = this.getCartesianCoordinates(ev);
22493
22547
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
22494
22548
  }
@@ -23582,7 +23636,6 @@ class Grid extends Component {
23582
23636
  HEADER_WIDTH = HEADER_WIDTH;
23583
23637
  menuState;
23584
23638
  gridRef;
23585
- hiddenInput;
23586
23639
  onMouseWheel;
23587
23640
  canvasPosition;
23588
23641
  hoveredCell;
@@ -23593,17 +23646,16 @@ class Grid extends Component {
23593
23646
  menuItems: [],
23594
23647
  });
23595
23648
  this.gridRef = useRef("grid");
23596
- this.hiddenInput = useRef("hiddenInput");
23597
23649
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
23598
23650
  this.hoveredCell = useState({ col: undefined, row: undefined });
23599
23651
  useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
23600
23652
  useExternalListener(document.body, "cut", this.copy.bind(this, true));
23601
23653
  useExternalListener(document.body, "copy", this.copy.bind(this, false));
23602
23654
  useExternalListener(document.body, "paste", this.paste);
23603
- onMounted(() => this.focus());
23604
- this.props.exposeFocus(() => this.focus());
23655
+ onMounted(() => this.focusDefaultElement());
23656
+ this.props.exposeFocus(() => this.focusDefaultElement());
23605
23657
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
23606
- useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
23658
+ useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
23607
23659
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
23608
23660
  this.moveCanvas(deltaX, deltaY);
23609
23661
  this.hoveredCell.col = undefined;
@@ -23626,7 +23678,7 @@ class Grid extends Component {
23626
23678
  if (this.env.model.getters.hasOpenedPopover()) {
23627
23679
  this.closeOpenedPopover();
23628
23680
  }
23629
- this.focus();
23681
+ this.focusDefaultElement();
23630
23682
  }
23631
23683
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
23632
23684
  // down itself
@@ -23796,10 +23848,10 @@ class Grid extends Component {
23796
23848
  PAGEUP: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
23797
23849
  "CTRL+K": () => INSERT_LINK(this.env),
23798
23850
  };
23799
- focus() {
23851
+ focusDefaultElement() {
23800
23852
  if (!this.env.model.getters.getSelectedFigureId() &&
23801
23853
  this.env.model.getters.getEditionMode() === "inactive") {
23802
- this.hiddenInput.el?.focus();
23854
+ this.env.focusableElement.focus();
23803
23855
  }
23804
23856
  }
23805
23857
  get gridEl() {
@@ -23943,19 +23995,6 @@ class Grid extends Component {
23943
23995
  return;
23944
23996
  }
23945
23997
  }
23946
- onInput(ev) {
23947
- // the user meant to paste in the sheet, not open the composer with the pasted content
23948
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
23949
- return;
23950
- }
23951
- if (ev.data) {
23952
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
23953
- // character
23954
- ev.preventDefault();
23955
- ev.stopPropagation();
23956
- this.props.onGridComposerCellFocused(ev.data);
23957
- }
23958
- }
23959
23998
  // ---------------------------------------------------------------------------
23960
23999
  // Context Menu
23961
24000
  // ---------------------------------------------------------------------------
@@ -24065,7 +24104,7 @@ class Grid extends Component {
24065
24104
  }
24066
24105
  closeMenu() {
24067
24106
  this.menuState.isOpen = false;
24068
- this.focus();
24107
+ this.focusDefaultElement();
24069
24108
  }
24070
24109
  }
24071
24110
  Grid.props = {
@@ -28725,6 +28764,31 @@ const MIGRATIONS = [
28725
28764
  return data;
28726
28765
  },
28727
28766
  },
28767
+ {
28768
+ description: "Fix datafilter duplication",
28769
+ from: 12,
28770
+ to: 12.5,
28771
+ applyMigration(data) {
28772
+ for (let sheet of data.sheets || []) {
28773
+ let knownDataFilterZones = [];
28774
+ for (let filterTable of sheet.filterTables || []) {
28775
+ const zone = toZone(filterTable.range);
28776
+ // See commit message for the details
28777
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
28778
+ if (intersectZoneIndex !== -1) {
28779
+ knownDataFilterZones[intersectZoneIndex] = zone;
28780
+ }
28781
+ else {
28782
+ knownDataFilterZones.push(zone);
28783
+ }
28784
+ }
28785
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
28786
+ range: zoneToXc(zone),
28787
+ }));
28788
+ }
28789
+ return data;
28790
+ },
28791
+ },
28728
28792
  {
28729
28793
  description: "Change Border description structure",
28730
28794
  from: 12,
@@ -29626,9 +29690,9 @@ class CellPlugin extends CorePlugin {
29626
29690
  allowDispatch(cmd) {
29627
29691
  switch (cmd.type) {
29628
29692
  case "UPDATE_CELL":
29629
- return this.checkCellOutOfSheet(cmd);
29693
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
29630
29694
  case "CLEAR_CELL":
29631
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
29695
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
29632
29696
  default:
29633
29697
  return 0 /* CommandResult.Success */;
29634
29698
  }
@@ -29760,10 +29824,6 @@ class CellPlugin extends CorePlugin {
29760
29824
  format: cell.format ? getItemId(cell.format, formats) : undefined,
29761
29825
  content: cell.content || undefined,
29762
29826
  };
29763
- // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
29764
- if (cell.isFormula && cell.dependencies.length) {
29765
- cells[xc].content = this.buildFormulaContent(_sheet.id, cell, cell.dependencies, true);
29766
- }
29767
29827
  }
29768
29828
  _sheet.cells = cells;
29769
29829
  }
@@ -29926,7 +29986,7 @@ class CellPlugin extends CorePlugin {
29926
29986
  else {
29927
29987
  style = before ? before.style : undefined;
29928
29988
  }
29929
- let format = ("format" in after ? after.format : before && before.format) || detectFormat(afterContent);
29989
+ const format = "format" in after ? after.format : before && before.format;
29930
29990
  /* Read the following IF as:
29931
29991
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
29932
29992
  * - the command says the new content is empty and has no border/format/style
@@ -29970,7 +30030,7 @@ class CellPlugin extends CorePlugin {
29970
30030
  id,
29971
30031
  content,
29972
30032
  style,
29973
- format,
30033
+ format: format || detectFormat(content),
29974
30034
  isFormula: false,
29975
30035
  };
29976
30036
  }
@@ -30047,6 +30107,18 @@ class CellPlugin extends CorePlugin {
30047
30107
  }
30048
30108
  return 0 /* CommandResult.Success */;
30049
30109
  }
30110
+ checkUselessUpdateCell(cmd) {
30111
+ const cell = this.getters.getCell(cmd);
30112
+ const hasContent = "content" in cmd || "formula" in cmd;
30113
+ const hasStyle = "style" in cmd;
30114
+ const hasFormat = "format" in cmd;
30115
+ if ((!hasContent || cell?.content === cmd.content) &&
30116
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
30117
+ (!hasFormat || cell?.format === cmd.format)) {
30118
+ return 94 /* CommandResult.NoChanges */;
30119
+ }
30120
+ return 0 /* CommandResult.Success */;
30121
+ }
30050
30122
  }
30051
30123
 
30052
30124
  class ChartPlugin extends CorePlugin {
@@ -33838,6 +33910,10 @@ class EvaluationPlugin extends UIPlugin {
33838
33910
  if (cell.format !== evaluatedCell.format) {
33839
33911
  exportedCellData.computedFormat = evaluatedCell.format;
33840
33912
  }
33913
+ // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
33914
+ if (cell.isFormula && cell.dependencies.length) {
33915
+ exportedCellData.content = this.getters.buildFormulaContent(sheet.id, cell, cell.dependencies, true);
33916
+ }
33841
33917
  }
33842
33918
  }
33843
33919
  }
@@ -35706,8 +35782,7 @@ class AutofillPlugin extends UIPlugin {
35706
35782
  let row = zone.bottom;
35707
35783
  if (col > 0) {
35708
35784
  let leftPosition = { sheetId, col: col - 1, row };
35709
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
35710
- this.getters.getCell(leftPosition)?.content) {
35785
+ while (this.getters.getCell(leftPosition)?.content) {
35711
35786
  row += 1;
35712
35787
  leftPosition = { sheetId, col: col - 1, row };
35713
35788
  }
@@ -35716,8 +35791,7 @@ class AutofillPlugin extends UIPlugin {
35716
35791
  col = zone.right;
35717
35792
  if (col <= this.getters.getNumberCols(sheetId)) {
35718
35793
  let rightPosition = { sheetId, col: col + 1, row };
35719
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
35720
- this.getters.getCell(rightPosition)?.content) {
35794
+ while (this.getters.getCell(rightPosition)?.content) {
35721
35795
  row += 1;
35722
35796
  rightPosition = { sheetId, col: col + 1, row };
35723
35797
  }
@@ -41182,7 +41256,7 @@ class EditionPlugin extends UIPlugin {
41182
41256
  getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
41183
41257
  const sheetId = this.getters.getActiveSheetId();
41184
41258
  let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
41185
- if (this.getters.getCurrentEditedCell().sheetId !== this.getters.getActiveSheetId()) {
41259
+ if (this.getters.getCurrentEditedCell()?.sheetId !== this.getters.getActiveSheetId()) {
41186
41260
  const sheetName = getCanonicalSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
41187
41261
  selectedXc = `${sheetName}!${selectedXc}`;
41188
41262
  }
@@ -41197,7 +41271,7 @@ class EditionPlugin extends UIPlugin {
41197
41271
  _fixedParts.pop();
41198
41272
  }
41199
41273
  const newRange = range.clone({ parts: _fixedParts });
41200
- return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
41274
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId());
41201
41275
  }
41202
41276
  /**
41203
41277
  * Replace the current selection by a new text.
@@ -41230,7 +41304,7 @@ class EditionPlugin extends UIPlugin {
41230
41304
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
41231
41305
  return;
41232
41306
  }
41233
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
41307
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
41234
41308
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
41235
41309
  const colorsToKeep = {};
41236
41310
  for (const xc of XCs) {
@@ -41259,7 +41333,7 @@ class EditionPlugin extends UIPlugin {
41259
41333
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
41260
41334
  return [];
41261
41335
  }
41262
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
41336
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
41263
41337
  const rangeColor = (rangeString) => {
41264
41338
  const colorIndex = this.colorIndexByRange[rangeString];
41265
41339
  return colors$1[colorIndex % colors$1.length];
@@ -41277,7 +41351,7 @@ class EditionPlugin extends UIPlugin {
41277
41351
  * Return ranges currently referenced in the composer
41278
41352
  */
41279
41353
  getReferencedRanges() {
41280
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
41354
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
41281
41355
  const referenceRanges = this.currentTokens
41282
41356
  .filter((token) => token.type === "REFERENCE")
41283
41357
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -42088,6 +42162,16 @@ class ImageProvider {
42088
42162
  }
42089
42163
  }
42090
42164
 
42165
+ class FocusableElement {
42166
+ focusableElement = undefined;
42167
+ setFocusableElement(element) {
42168
+ this.focusableElement = element;
42169
+ }
42170
+ focus() {
42171
+ this.focusableElement?.focus();
42172
+ }
42173
+ }
42174
+
42091
42175
  const RIPPLE_KEY_FRAMES = [
42092
42176
  { transform: "scale(0)" },
42093
42177
  { transform: "scale(0.8)", offset: 0.33 },
@@ -44116,6 +44200,7 @@ class Spreadsheet extends Component {
44116
44200
  _t: Spreadsheet._t,
44117
44201
  clipboard: this.env.clipboard || instantiateClipboard(),
44118
44202
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
44203
+ focusableElement: new FocusableElement(),
44119
44204
  });
44120
44205
  useExternalListener(window, "resize", () => this.render(true));
44121
44206
  useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -44222,17 +44307,18 @@ class Spreadsheet extends Component {
44222
44307
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
44223
44308
  this.composer.topBarFocus = "contentFocus";
44224
44309
  this.composer.gridFocusMode = "inactive";
44225
- this.setComposerContent({ selection } || {});
44310
+ this.setComposerContent({ selection });
44226
44311
  }
44227
- onGridComposerContentFocused() {
44312
+ onGridComposerContentFocused(selection) {
44228
44313
  if (this.model.getters.isReadonly()) {
44229
44314
  return;
44230
44315
  }
44231
44316
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
44232
44317
  this.composer.topBarFocus = "inactive";
44233
44318
  this.composer.gridFocusMode = "contentFocus";
44234
- this.setComposerContent({});
44319
+ this.setComposerContent({ selection });
44235
44320
  }
44321
+ // TODO: either both are defined or none of them. change those args to an object
44236
44322
  onGridComposerCellFocused(content, selection) {
44237
44323
  if (this.model.getters.isReadonly()) {
44238
44324
  return;
@@ -45630,11 +45716,10 @@ class SelectionStreamProcessorImpl {
45630
45716
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
45631
45717
  */
45632
45718
  getEndOfCluster(startPosition, dim, dir) {
45633
- const sheet = this.getters.getActiveSheet();
45634
45719
  let currentPosition = startPosition;
45635
45720
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
45636
45721
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
45637
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
45722
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
45638
45723
  ? "endOfCluster"
45639
45724
  : "nextCluster";
45640
45725
  while (true) {
@@ -45644,7 +45729,7 @@ class SelectionStreamProcessorImpl {
45644
45729
  currentPosition.row === nextCellPosition.row) {
45645
45730
  break;
45646
45731
  }
45647
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
45732
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
45648
45733
  if (mode === "endOfCluster" && isNextCellEmpty) {
45649
45734
  break;
45650
45735
  }
@@ -45658,14 +45743,24 @@ class SelectionStreamProcessorImpl {
45658
45743
  return dim === "cols" ? currentPosition.col : currentPosition.row;
45659
45744
  }
45660
45745
  /**
45661
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
45662
- * check if the merge containing the cell is empty.
45746
+ * Check if a cell evaluated value is empty. If the cell is part of a merge,
45747
+ * the check applies to the main cell of the merge.
45663
45748
  */
45664
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
45749
+ isEvaluatedCellEmpty({ col, row }) {
45750
+ const sheetId = this.getters.getActiveSheetId();
45665
45751
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
45666
45752
  const cell = this.getters.getEvaluatedCell(position);
45667
45753
  return cell.type === CellValueType.empty;
45668
45754
  }
45755
+ /**
45756
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
45757
+ * the check applies to the main cell of the merge.
45758
+ */
45759
+ isCellEmpty({ col, row }) {
45760
+ const sheetId = this.getters.getActiveSheetId();
45761
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
45762
+ return !this.getters.getCell(position)?.content;
45763
+ }
45669
45764
  /** Computes the next cell position in the given direction by crossing through merges and skipping hidden cells.
45670
45765
  *
45671
45766
  * This has the same behaviour as getNextAvailablePosition() for certain arguments, but use this method instead
@@ -47906,6 +48001,6 @@ function addFunction(functionName, functionDescription) {
47906
48001
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
47907
48002
 
47908
48003
 
47909
- __info__.version = '16.3.24';
47910
- __info__.date = '2024-02-02T13:45:48.440Z';
47911
- __info__.hash = '0bf5f45';
48004
+ __info__.version = '16.3.26';
48005
+ __info__.date = '2024-02-16T14:22:11.270Z';
48006
+ __info__.hash = 'ab9abb6';