@odoo/o-spreadsheet 16.4.21 → 16.4.23

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.
@@ -701,18 +701,18 @@ function isConsecutive(iterable) {
701
701
  return true;
702
702
  }
703
703
  class JetSet extends Set {
704
- add(...iterable) {
704
+ addMany(iterable) {
705
705
  for (const element of iterable) {
706
706
  super.add(element);
707
707
  }
708
708
  return this;
709
709
  }
710
- delete(...iterable) {
711
- let deleted = false;
710
+ deleteMany(iterable) {
711
+ let wasDeleted = false;
712
712
  for (const element of iterable) {
713
- deleted ||= super.delete(element);
713
+ wasDeleted ||= super.delete(element);
714
714
  }
715
- return deleted;
715
+ return wasDeleted;
716
716
  }
717
717
  }
718
718
  /**
@@ -1838,6 +1838,9 @@ function parseFormat(formatString) {
1838
1838
  function formatValue(value, { format, locale }) {
1839
1839
  switch (typeof value) {
1840
1840
  case "string":
1841
+ if (value.includes('\\"')) {
1842
+ return value.replace(/\\"/g, '"');
1843
+ }
1841
1844
  return value;
1842
1845
  case "boolean":
1843
1846
  return value ? "TRUE" : "FALSE";
@@ -15235,6 +15238,9 @@ function isValidLocale(locale) {
15235
15238
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
15236
15239
  return false;
15237
15240
  }
15241
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
15242
+ return false;
15243
+ }
15238
15244
  try {
15239
15245
  formatValue(1, { locale, format: "#,##0.00" });
15240
15246
  formatValue(1, { locale, format: locale.dateFormat });
@@ -15325,7 +15331,7 @@ function canonicalizeNumberLiteral(content, locale) {
15325
15331
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
15326
15332
  return content;
15327
15333
  }
15328
- return content.replace(locale.decimalSeparator, ".");
15334
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
15329
15335
  }
15330
15336
  /**
15331
15337
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -18627,6 +18633,12 @@ const INSERT_LINK = (env) => {
18627
18633
  let { col, row } = env.model.getters.getActivePosition();
18628
18634
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
18629
18635
  };
18636
+ const INSERT_LINK_NAME = (env) => {
18637
+ const sheetId = env.model.getters.getActiveSheetId();
18638
+ const { col, row } = env.model.getters.getActivePosition();
18639
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
18640
+ return cell && cell.link ? _lt("Edit link") : _lt("Insert link");
18641
+ };
18630
18642
  //------------------------------------------------------------------------------
18631
18643
  // Filters action
18632
18644
  //------------------------------------------------------------------------------
@@ -19167,7 +19179,7 @@ cellMenuRegistry
19167
19179
  })
19168
19180
  .add("insert_link", {
19169
19181
  ...insertLink,
19170
- name: _lt("Insert link"),
19182
+ name: INSERT_LINK_NAME,
19171
19183
  sequence: 150,
19172
19184
  separator: true,
19173
19185
  });
@@ -24078,6 +24090,7 @@ class Composer extends Component {
24078
24090
  static components = { TextValueProvider, FunctionDescriptionProvider };
24079
24091
  static defaultProps = {
24080
24092
  inputStyle: "",
24093
+ isDefaultFocus: false,
24081
24094
  };
24082
24095
  composerRef = useRef("o_composer");
24083
24096
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -24130,7 +24143,6 @@ class Composer extends Component {
24130
24143
  F2: () => console.warn("Not implemented"),
24131
24144
  F4: this.processF4Key,
24132
24145
  Tab: (ev) => this.processTabKey(ev),
24133
- " ": (ev) => this.processSpaceKey(ev),
24134
24146
  };
24135
24147
  keyCodeMapping = {
24136
24148
  NumpadDecimal: this.processNumpadDecimal,
@@ -24138,11 +24150,11 @@ class Composer extends Component {
24138
24150
  setup() {
24139
24151
  onMounted(() => {
24140
24152
  const el = this.composerRef.el;
24153
+ if (this.props.isDefaultFocus) {
24154
+ this.env.focusableElement.setFocusableElement(el);
24155
+ }
24141
24156
  this.contentHelper.updateEl(el);
24142
24157
  });
24143
- onWillUnmount(() => {
24144
- this.props.onComposerUnmounted?.();
24145
- });
24146
24158
  useEffect(() => {
24147
24159
  this.processContent();
24148
24160
  });
@@ -24151,7 +24163,8 @@ class Composer extends Component {
24151
24163
  // Handlers
24152
24164
  // ---------------------------------------------------------------------------
24153
24165
  processArrowKeys(ev) {
24154
- if (this.env.model.getters.isSelectingForComposer()) {
24166
+ if (this.env.model.getters.isSelectingForComposer() ||
24167
+ this.env.model.getters.getEditionMode() === "inactive") {
24155
24168
  this.functionDescriptionState.showDescription = false;
24156
24169
  // Prevent the default content editable behavior which moves the cursor
24157
24170
  ev.preventDefault();
@@ -24190,25 +24203,19 @@ class Composer extends Component {
24190
24203
  processTabKey(ev) {
24191
24204
  ev.preventDefault();
24192
24205
  ev.stopPropagation();
24193
- if (this.autoCompleteState.showProvider) {
24194
- const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
24195
- if (autoCompleteValue) {
24196
- this.autoComplete(autoCompleteValue);
24197
- return;
24206
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
24207
+ if (this.autoCompleteState.showProvider) {
24208
+ const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
24209
+ if (autoCompleteValue) {
24210
+ this.autoComplete(autoCompleteValue);
24211
+ return;
24212
+ }
24198
24213
  }
24214
+ this.env.model.dispatch("STOP_EDITION");
24199
24215
  }
24200
24216
  const direction = ev.shiftKey ? "left" : "right";
24201
- this.env.model.dispatch("STOP_EDITION");
24202
24217
  this.env.model.selection.moveAnchorCell(direction, 1);
24203
24218
  }
24204
- processSpaceKey(ev) {
24205
- if (ev.ctrlKey) {
24206
- ev.preventDefault();
24207
- ev.stopPropagation();
24208
- this.showAutocomplete("");
24209
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
24210
- }
24211
- }
24212
24219
  processEnterKey(ev) {
24213
24220
  ev.preventDefault();
24214
24221
  ev.stopPropagation();
@@ -24260,6 +24267,9 @@ class Composer extends Component {
24260
24267
  this.compositionActive = false;
24261
24268
  }
24262
24269
  onKeydown(ev) {
24270
+ if (this.env.model.getters.getEditionMode() === "inactive") {
24271
+ return;
24272
+ }
24263
24273
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
24264
24274
  if (handler) {
24265
24275
  handler.call(this, ev);
@@ -24268,14 +24278,33 @@ class Composer extends Component {
24268
24278
  ev.stopPropagation();
24269
24279
  }
24270
24280
  }
24281
+ onPaste(ev) {
24282
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
24283
+ ev.stopPropagation();
24284
+ }
24285
+ }
24271
24286
  /*
24272
24287
  * Triggered automatically by the content-editable between the keydown and key up
24273
24288
  * */
24274
24289
  onInput(ev) {
24275
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
24290
+ if (!this.shouldProcessInputEvents) {
24276
24291
  return;
24277
24292
  }
24278
- let content = this.contentHelper.getText();
24293
+ if (ev.inputType === "insertFromPaste" &&
24294
+ this.env.model.getters.getEditionMode() === "inactive") {
24295
+ return;
24296
+ }
24297
+ ev.stopPropagation();
24298
+ let content;
24299
+ if (this.env.model.getters.getEditionMode() === "inactive") {
24300
+ content = ev.data || "";
24301
+ }
24302
+ else {
24303
+ content = this.contentHelper.getText();
24304
+ }
24305
+ if (this.props.focus === "inactive") {
24306
+ return this.props.onComposerCellFocused?.(content);
24307
+ }
24279
24308
  let selection = this.contentHelper.getCurrentSelection();
24280
24309
  if (ev.inputType === "insertParagraph") {
24281
24310
  const start = Math.min(selection.start, selection.end);
@@ -24353,9 +24382,8 @@ class Composer extends Component {
24353
24382
  }
24354
24383
  const newSelection = this.contentHelper.getCurrentSelection();
24355
24384
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
24356
- if (this.props.focus === "inactive") {
24357
- this.props.onComposerContentFocused(newSelection);
24358
- }
24385
+ this.props.onComposerContentFocused();
24386
+ if (this.props.focus === "inactive") ;
24359
24387
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
24360
24388
  this.processTokenAtCursor();
24361
24389
  }
@@ -24522,7 +24550,7 @@ class Composer extends Component {
24522
24550
  const highlights = this.env.model.getters.getHighlights();
24523
24551
  const refSheet = sheetName
24524
24552
  ? this.env.model.getters.getSheetIdByName(sheetName)
24525
- : this.env.model.getters.getCurrentEditedCell().sheetId;
24553
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
24526
24554
  const highlight = highlights.find((highlight) => {
24527
24555
  if (highlight.sheetId !== refSheet)
24528
24556
  return false;
@@ -24602,12 +24630,13 @@ class Composer extends Component {
24602
24630
  }
24603
24631
  }
24604
24632
  Composer.props = {
24605
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24606
- onComposerContentFocused: Function,
24607
24633
  inputStyle: { type: String, optional: true },
24608
24634
  rect: { type: Object, optional: true },
24609
24635
  delimitation: { type: Object, optional: true },
24610
- onComposerUnmounted: { type: Function, optional: true },
24636
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24637
+ onComposerCellFocused: { type: Function, optional: true },
24638
+ onComposerContentFocused: Function,
24639
+ isDefaultFocus: { type: Boolean, optional: true },
24611
24640
  };
24612
24641
 
24613
24642
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -24641,51 +24670,26 @@ css /* scss */ `
24641
24670
  class GridComposer extends Component {
24642
24671
  static template = "o-spreadsheet-GridComposer";
24643
24672
  static components = { Composer };
24644
- gridComposerRef;
24645
- zone;
24646
- rect;
24673
+ // TODORAR see if we can keep it undefined
24674
+ rect = this.defaultRect;
24675
+ isEditing = false;
24647
24676
  isCellReferenceVisible;
24648
- composerState;
24677
+ get defaultRect() {
24678
+ return { x: 0, y: 0, width: 0, height: 0 };
24679
+ }
24649
24680
  setup() {
24650
- this.gridComposerRef = useRef("gridComposer");
24651
- this.composerState = useState({
24652
- rect: undefined,
24653
- delimitation: undefined,
24654
- });
24655
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
24656
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
24657
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
24658
- this.isCellReferenceVisible = false;
24659
- onMounted(() => {
24660
- const el = this.gridComposerRef.el;
24661
- this.composerState.rect = {
24662
- x: this.rect.x,
24663
- y: this.rect.y,
24664
- width: el.clientWidth,
24665
- height: el.clientHeight,
24666
- };
24667
- this.composerState.delimitation = {
24668
- width: this.props.gridDims.width,
24669
- height: this.props.gridDims.height,
24670
- };
24671
- });
24672
24681
  onWillUpdateProps(() => {
24673
- if (this.isCellReferenceVisible) {
24674
- return;
24675
- }
24676
- const sheetId = this.env.model.getters.getActiveSheetId();
24677
- const zone = this.env.model.getters.getSelectedZone();
24678
- const rect = this.env.model.getters.getVisibleRect(zone);
24679
- if (!deepEquals(rect, this.rect) ||
24680
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
24681
- this.isCellReferenceVisible = true;
24682
- }
24682
+ this.updateComponentPosition();
24683
+ this.updateCellReferenceVisibility();
24683
24684
  });
24684
24685
  }
24685
24686
  get shouldDisplayCellReference() {
24686
24687
  return this.isCellReferenceVisible;
24687
24688
  }
24688
24689
  get cellReference() {
24690
+ if (!this.env.model.getters.getCurrentEditedCell()) {
24691
+ return "";
24692
+ }
24689
24693
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
24690
24694
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
24691
24695
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -24697,7 +24701,27 @@ class GridComposer extends Component {
24697
24701
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
24698
24702
  });
24699
24703
  }
24704
+ get composerProps() {
24705
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
24706
+ return {
24707
+ rect: { ...this.rect },
24708
+ delimitation: {
24709
+ width,
24710
+ height,
24711
+ },
24712
+ focus: this.props.focus,
24713
+ isDefaultFocus: true,
24714
+ onComposerContentFocused: this.props.onComposerContentFocused,
24715
+ onComposerCellFocused: this.props.onComposerCellFocused,
24716
+ };
24717
+ }
24700
24718
  get containerStyle() {
24719
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
24720
+ return `
24721
+ position: absolute;
24722
+ z-index: -1000;
24723
+ `;
24724
+ }
24701
24725
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
24702
24726
  const cell = this.env.model.getters.getActiveCell();
24703
24727
  const position = this.env.model.getters.getActivePosition();
@@ -24717,6 +24741,8 @@ class GridComposer extends Component {
24717
24741
  if (!isFormula) {
24718
24742
  textAlign = style.align || cell.defaultAlign;
24719
24743
  }
24744
+ const maxHeight = this.props.gridDims.height - this.rect.y;
24745
+ const maxWidth = this.props.gridDims.width - this.rect.x;
24720
24746
  /**
24721
24747
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
24722
24748
  * including all the paddings/margins of the composer
@@ -24728,6 +24754,8 @@ class GridComposer extends Component {
24728
24754
  top: `${top}px`,
24729
24755
  "min-width": `${width + 1}px`,
24730
24756
  "min-height": `${height + 1}px`,
24757
+ "max-width": `${maxWidth}px`,
24758
+ "max-height": `${maxHeight}px`,
24731
24759
  background,
24732
24760
  color,
24733
24761
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -24737,20 +24765,38 @@ class GridComposer extends Component {
24737
24765
  "text-align": textAlign,
24738
24766
  });
24739
24767
  }
24740
- get composerStyle() {
24741
- const maxHeight = this.props.gridDims.height - this.rect.y;
24742
- const maxWidth = this.props.gridDims.width - this.rect.x;
24743
- return cssPropertiesToCss({
24744
- "max-width": `${maxWidth}px`,
24745
- "max-height": `${maxHeight}px`,
24746
- });
24768
+ updateComponentPosition() {
24769
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
24770
+ if (this.isEditing !== isEditing) {
24771
+ this.isEditing = isEditing;
24772
+ if (!isEditing) {
24773
+ this.rect = this.defaultRect;
24774
+ this.env.focusableElement.focus();
24775
+ return;
24776
+ }
24777
+ const position = this.env.model.getters.getActivePosition();
24778
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
24779
+ this.rect = this.env.model.getters.getVisibleRect(zone);
24780
+ }
24781
+ }
24782
+ updateCellReferenceVisibility() {
24783
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
24784
+ return;
24785
+ }
24786
+ const sheetId = this.env.model.getters.getActiveSheetId();
24787
+ const zone = this.env.model.getters.getSelectedZone();
24788
+ const rect = this.env.model.getters.getVisibleRect(zone);
24789
+ if (!deepEquals(rect, this.rect) ||
24790
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
24791
+ this.isCellReferenceVisible = true;
24792
+ }
24747
24793
  }
24748
24794
  }
24749
24795
  GridComposer.props = {
24750
24796
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24751
- onComposerUnmounted: Function,
24752
24797
  onComposerContentFocused: Function,
24753
24798
  gridDims: Object,
24799
+ onComposerCellFocused: Function,
24754
24800
  };
24755
24801
 
24756
24802
  const CSS$1 = css /* scss */ `
@@ -25631,7 +25677,6 @@ class GridOverlay extends Component {
25631
25677
  this.props.onCellDoubleClicked(col, row);
25632
25678
  }
25633
25679
  onContextMenu(ev) {
25634
- ev.preventDefault();
25635
25680
  const [col, row] = this.getCartesianCoordinates(ev);
25636
25681
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
25637
25682
  }
@@ -26724,7 +26769,6 @@ class Grid extends Component {
26724
26769
  HEADER_WIDTH = HEADER_WIDTH;
26725
26770
  menuState;
26726
26771
  gridRef;
26727
- hiddenInput;
26728
26772
  onMouseWheel;
26729
26773
  canvasPosition;
26730
26774
  hoveredCell;
@@ -26735,17 +26779,16 @@ class Grid extends Component {
26735
26779
  menuItems: [],
26736
26780
  });
26737
26781
  this.gridRef = useRef("grid");
26738
- this.hiddenInput = useRef("hiddenInput");
26739
26782
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
26740
26783
  this.hoveredCell = useState({ col: undefined, row: undefined });
26741
26784
  useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
26742
26785
  useExternalListener(document.body, "cut", this.copy.bind(this, true));
26743
26786
  useExternalListener(document.body, "copy", this.copy.bind(this, false));
26744
26787
  useExternalListener(document.body, "paste", this.paste);
26745
- onMounted(() => this.focus());
26746
- this.props.exposeFocus(() => this.focus());
26788
+ onMounted(() => this.focusDefaultElement());
26789
+ this.props.exposeFocus(() => this.focusDefaultElement());
26747
26790
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
26748
- useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
26791
+ useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
26749
26792
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
26750
26793
  this.moveCanvas(deltaX, deltaY);
26751
26794
  this.hoveredCell.col = undefined;
@@ -26768,7 +26811,7 @@ class Grid extends Component {
26768
26811
  if (this.env.model.getters.hasOpenedPopover()) {
26769
26812
  this.closeOpenedPopover();
26770
26813
  }
26771
- this.focus();
26814
+ this.focusDefaultElement();
26772
26815
  }
26773
26816
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
26774
26817
  // down itself
@@ -26941,10 +26984,10 @@ class Grid extends Component {
26941
26984
  PAGEUP: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
26942
26985
  "CTRL+K": () => INSERT_LINK(this.env),
26943
26986
  };
26944
- focus() {
26987
+ focusDefaultElement() {
26945
26988
  if (!this.env.model.getters.getSelectedFigureId() &&
26946
26989
  this.env.model.getters.getEditionMode() === "inactive") {
26947
- this.hiddenInput.el?.focus();
26990
+ this.env.focusableElement.focus();
26948
26991
  }
26949
26992
  }
26950
26993
  get gridEl() {
@@ -27088,19 +27131,6 @@ class Grid extends Component {
27088
27131
  return;
27089
27132
  }
27090
27133
  }
27091
- onInput(ev) {
27092
- // the user meant to paste in the sheet, not open the composer with the pasted content
27093
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
27094
- return;
27095
- }
27096
- if (ev.data) {
27097
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
27098
- // character
27099
- ev.preventDefault();
27100
- ev.stopPropagation();
27101
- this.props.onGridComposerCellFocused(ev.data);
27102
- }
27103
- }
27104
27134
  // ---------------------------------------------------------------------------
27105
27135
  // Context Menu
27106
27136
  // ---------------------------------------------------------------------------
@@ -27213,7 +27243,7 @@ class Grid extends Component {
27213
27243
  }
27214
27244
  closeMenu() {
27215
27245
  this.menuState.isOpen = false;
27216
- this.focus();
27246
+ this.focusDefaultElement();
27217
27247
  }
27218
27248
  }
27219
27249
  Grid.props = {
@@ -30873,6 +30903,31 @@ const MIGRATIONS = [
30873
30903
  return data;
30874
30904
  },
30875
30905
  },
30906
+ {
30907
+ description: "Fix datafilter duplication",
30908
+ from: 12,
30909
+ to: 12.5,
30910
+ applyMigration(data) {
30911
+ for (let sheet of data.sheets || []) {
30912
+ let knownDataFilterZones = [];
30913
+ for (let filterTable of sheet.filterTables || []) {
30914
+ const zone = toZone(filterTable.range);
30915
+ // See commit message for the details
30916
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
30917
+ if (intersectZoneIndex !== -1) {
30918
+ knownDataFilterZones[intersectZoneIndex] = zone;
30919
+ }
30920
+ else {
30921
+ knownDataFilterZones.push(zone);
30922
+ }
30923
+ }
30924
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
30925
+ range: zoneToXc(zone),
30926
+ }));
30927
+ }
30928
+ return data;
30929
+ },
30930
+ },
30876
30931
  {
30877
30932
  description: "Change Border description structure",
30878
30933
  from: 12,
@@ -31792,9 +31847,9 @@ class CellPlugin extends CorePlugin {
31792
31847
  allowDispatch(cmd) {
31793
31848
  switch (cmd.type) {
31794
31849
  case "UPDATE_CELL":
31795
- return this.checkCellOutOfSheet(cmd);
31850
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
31796
31851
  case "CLEAR_CELL":
31797
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
31852
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
31798
31853
  default:
31799
31854
  return 0 /* CommandResult.Success */;
31800
31855
  }
@@ -31926,10 +31981,6 @@ class CellPlugin extends CorePlugin {
31926
31981
  format: cell.format ? getItemId(cell.format, formats) : undefined,
31927
31982
  content: cell.content || undefined,
31928
31983
  };
31929
- // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
31930
- if (cell.isFormula && cell.dependencies.length) {
31931
- cells[xc].content = this.buildFormulaContent(_sheet.id, cell, cell.dependencies, true);
31932
- }
31933
31984
  }
31934
31985
  _sheet.cells = cells;
31935
31986
  }
@@ -32092,10 +32143,7 @@ class CellPlugin extends CorePlugin {
32092
32143
  else {
32093
32144
  style = before ? before.style : undefined;
32094
32145
  }
32095
- const locale = this.getters.getLocale();
32096
- let format = ("format" in after ? after.format : before && before.format) ||
32097
- detectDateFormat(afterContent, locale) ||
32098
- detectNumberFormat(afterContent);
32146
+ const format = "format" in after ? after.format : before && before.format;
32099
32147
  /* Read the following IF as:
32100
32148
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
32101
32149
  * - the command says the new content is empty and has no border/format/style
@@ -32136,12 +32184,11 @@ class CellPlugin extends CorePlugin {
32136
32184
  }
32137
32185
  createLiteralCell(id, content, format, style) {
32138
32186
  const locale = this.getters.getLocale();
32139
- content = parseLiteral(content, locale).toString();
32140
32187
  return {
32141
32188
  id,
32142
- content,
32189
+ content: parseLiteral(content, locale).toString(),
32143
32190
  style,
32144
- format,
32191
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
32145
32192
  isFormula: false,
32146
32193
  };
32147
32194
  }
@@ -32202,6 +32249,18 @@ class CellPlugin extends CorePlugin {
32202
32249
  }
32203
32250
  return 0 /* CommandResult.Success */;
32204
32251
  }
32252
+ checkUselessUpdateCell(cmd) {
32253
+ const cell = this.getters.getCell(cmd);
32254
+ const hasContent = "content" in cmd || "formula" in cmd;
32255
+ const hasStyle = "style" in cmd;
32256
+ const hasFormat = "format" in cmd;
32257
+ if ((!hasContent || cell?.content === cmd.content) &&
32258
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
32259
+ (!hasFormat || cell?.format === cmd.format)) {
32260
+ return 98 /* CommandResult.NoChanges */;
32261
+ }
32262
+ return 0 /* CommandResult.Success */;
32263
+ }
32205
32264
  }
32206
32265
  class FormulaCellWithDependencies {
32207
32266
  id;
@@ -36342,7 +36401,15 @@ class SpreadsheetRTree {
36342
36401
  if (!this.rTrees[sheetId]) {
36343
36402
  return;
36344
36403
  }
36345
- this.rTrees[sheetId].remove(item, deepEquals);
36404
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
36405
+ }
36406
+ rtreeItemComparer(left, right) {
36407
+ return (left.data == right.data &&
36408
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
36409
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
36410
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
36411
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
36412
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
36346
36413
  }
36347
36414
  }
36348
36415
  /**
@@ -36420,7 +36487,7 @@ class FormulaDependencyGraph {
36420
36487
  const queue = Array.from(ranges).reverse();
36421
36488
  while (queue.length > 0) {
36422
36489
  const range = queue.pop();
36423
- visited.add(...this.encoder.encodeBoundingBox(range));
36490
+ visited.addMany(this.encoder.encodeBoundingBox(range));
36424
36491
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
36425
36492
  for (const positionId of impactedPositionIds) {
36426
36493
  if (!visited.has(positionId)) {
@@ -36428,7 +36495,7 @@ class FormulaDependencyGraph {
36428
36495
  }
36429
36496
  }
36430
36497
  }
36431
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36498
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36432
36499
  return visited;
36433
36500
  }
36434
36501
  }
@@ -36559,9 +36626,9 @@ class Evaluator {
36559
36626
  const cells = positions.map((p) => this.encoder.encode(p));
36560
36627
  const cellsToCompute = new JetSet(cells);
36561
36628
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
36562
- cellsToCompute.add(...this.getCellsDependingOn(cells));
36563
- cellsToCompute.add(...arrayFormulasPositionIds);
36564
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
36629
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
36630
+ cellsToCompute.addMany(arrayFormulasPositionIds);
36631
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
36565
36632
  this.evaluate(cellsToCompute);
36566
36633
  }
36567
36634
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -36575,7 +36642,7 @@ class Evaluator {
36575
36642
  }
36576
36643
  if (!content) {
36577
36644
  // The previous content could have blocked some array formulas
36578
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36645
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36579
36646
  }
36580
36647
  }
36581
36648
  return impactedPositionIds;
@@ -36623,7 +36690,7 @@ class Evaluator {
36623
36690
  }
36624
36691
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
36625
36692
  const cells = new JetSet(arrayFormulas);
36626
- cells.add(...this.getCellsDependingOn(arrayFormulas));
36693
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
36627
36694
  return cells;
36628
36695
  }
36629
36696
  nextPositionsToUpdate = new JetSet();
@@ -36774,7 +36841,7 @@ class Evaluator {
36774
36841
  this.setEvaluatedCell(positionId, evaluatedCell);
36775
36842
  // check if formula dependencies present in the spread zone
36776
36843
  // if so, they need to be recomputed
36777
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
36844
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
36778
36845
  };
36779
36846
  }
36780
36847
  invalidateSpreading(positionId) {
@@ -36789,8 +36856,8 @@ class Evaluator {
36789
36856
  continue;
36790
36857
  }
36791
36858
  this.evaluatedCells.delete(child);
36792
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
36793
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
36859
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
36860
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
36794
36861
  }
36795
36862
  this.spreadingRelations.removeNode(positionId);
36796
36863
  }
@@ -37150,7 +37217,13 @@ class EvaluationPlugin extends UIPlugin {
37150
37217
  const format = newFormat
37151
37218
  ? getItemId(newFormat, data.formats)
37152
37219
  : exportedCellData.format;
37153
- const content = !isExported ? newContent : exportedCellData.content;
37220
+ let content;
37221
+ if (formulaCell instanceof FormulaCellWithDependencies) {
37222
+ content = this.getters.buildFormulaContent(exportedSheetData.id, formulaCell, formulaCell.dependencies, true);
37223
+ }
37224
+ else {
37225
+ content = !isExported ? newContent : exportedCellData.content;
37226
+ }
37154
37227
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
37155
37228
  }
37156
37229
  }
@@ -43669,7 +43742,7 @@ class EditionPlugin extends UIPlugin {
43669
43742
  this.replaceText(ref, this.selectionInitialStart, this.selectionEnd);
43670
43743
  }
43671
43744
  getZoneReference(zone) {
43672
- const inputSheetId = this.getters.getCurrentEditedCell().sheetId;
43745
+ const inputSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43673
43746
  const sheetId = this.getters.getActiveSheetId();
43674
43747
  const range = this.getters.getRangeFromZone(sheetId, zone);
43675
43748
  return this.getters.getSelectionRangeString(range, inputSheetId);
@@ -43683,7 +43756,7 @@ class EditionPlugin extends UIPlugin {
43683
43756
  _fixedParts.pop();
43684
43757
  }
43685
43758
  const newRange = range.clone({ parts: _fixedParts });
43686
- return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
43759
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId());
43687
43760
  }
43688
43761
  /**
43689
43762
  * Replace the current selection by a new text.
@@ -43716,7 +43789,7 @@ class EditionPlugin extends UIPlugin {
43716
43789
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43717
43790
  return;
43718
43791
  }
43719
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43792
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43720
43793
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
43721
43794
  const colorsToKeep = {};
43722
43795
  for (const xc of XCs) {
@@ -43745,7 +43818,7 @@ class EditionPlugin extends UIPlugin {
43745
43818
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43746
43819
  return [];
43747
43820
  }
43748
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43821
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43749
43822
  const rangeColor = (rangeString) => {
43750
43823
  const colorIndex = this.colorIndexByRange[rangeString];
43751
43824
  return colors$1[colorIndex % colors$1.length];
@@ -43763,7 +43836,7 @@ class EditionPlugin extends UIPlugin {
43763
43836
  * Return ranges currently referenced in the composer
43764
43837
  */
43765
43838
  getReferencedRanges() {
43766
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43839
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43767
43840
  const referenceRanges = this.currentTokens
43768
43841
  .filter((token) => token.type === "REFERENCE")
43769
43842
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -45727,6 +45800,16 @@ class ImageProvider {
45727
45800
  }
45728
45801
  }
45729
45802
 
45803
+ class FocusableElement {
45804
+ focusableElement = undefined;
45805
+ setFocusableElement(element) {
45806
+ this.focusableElement = element;
45807
+ }
45808
+ focus() {
45809
+ this.focusableElement?.focus();
45810
+ }
45811
+ }
45812
+
45730
45813
  const RIPPLE_KEY_FRAMES = [
45731
45814
  { transform: "scale(0)" },
45732
45815
  { transform: "scale(0.8)", offset: 0.33 },
@@ -47769,6 +47852,7 @@ class Spreadsheet extends Component {
47769
47852
  _t: Spreadsheet._t,
47770
47853
  clipboard: this.env.clipboard || instantiateClipboard(),
47771
47854
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
47855
+ focusableElement: new FocusableElement(),
47772
47856
  });
47773
47857
  useExternalListener(window, "resize", () => this.render(true));
47774
47858
  useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -47875,17 +47959,18 @@ class Spreadsheet extends Component {
47875
47959
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47876
47960
  this.composer.topBarFocus = "contentFocus";
47877
47961
  this.composer.gridFocusMode = "inactive";
47878
- this.setComposerContent({ selection } || {});
47962
+ this.setComposerContent({ selection });
47879
47963
  }
47880
- onGridComposerContentFocused() {
47964
+ onGridComposerContentFocused(selection) {
47881
47965
  if (this.model.getters.isReadonly()) {
47882
47966
  return;
47883
47967
  }
47884
47968
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47885
47969
  this.composer.topBarFocus = "inactive";
47886
47970
  this.composer.gridFocusMode = "contentFocus";
47887
- this.setComposerContent({});
47971
+ this.setComposerContent({ selection });
47888
47972
  }
47973
+ // TODO: either both are defined or none of them. change those args to an object
47889
47974
  onGridComposerCellFocused(content, selection) {
47890
47975
  if (this.model.getters.isReadonly()) {
47891
47976
  return;
@@ -49297,11 +49382,10 @@ class SelectionStreamProcessorImpl {
49297
49382
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
49298
49383
  */
49299
49384
  getEndOfCluster(startPosition, dim, dir) {
49300
- const sheet = this.getters.getActiveSheet();
49301
49385
  let currentPosition = startPosition;
49302
49386
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
49303
49387
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
49304
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
49388
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
49305
49389
  ? "endOfCluster"
49306
49390
  : "nextCluster";
49307
49391
  while (true) {
@@ -49311,7 +49395,7 @@ class SelectionStreamProcessorImpl {
49311
49395
  currentPosition.row === nextCellPosition.row) {
49312
49396
  break;
49313
49397
  }
49314
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
49398
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
49315
49399
  if (mode === "endOfCluster" && isNextCellEmpty) {
49316
49400
  break;
49317
49401
  }
@@ -49325,10 +49409,20 @@ class SelectionStreamProcessorImpl {
49325
49409
  return dim === "cols" ? currentPosition.col : currentPosition.row;
49326
49410
  }
49327
49411
  /**
49328
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
49329
- * check if the merge containing the cell is empty.
49412
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
49413
+ * the check applies to the main cell of the merge.
49414
+ */
49415
+ isCellEmpty({ col, row }) {
49416
+ const sheetId = this.getters.getActiveSheetId();
49417
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
49418
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
49419
+ }
49420
+ /**
49421
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
49422
+ * the check applies to the main cell of the merge.
49330
49423
  */
49331
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
49424
+ isEvaluatedCellEmpty({ col, row }) {
49425
+ const sheetId = this.getters.getActiveSheetId();
49332
49426
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
49333
49427
  const cell = this.getters.getEvaluatedCell(position);
49334
49428
  return cell.type === CellValueType.empty;
@@ -51577,6 +51671,6 @@ const constants = {
51577
51671
  export { AbstractChart, CommandResult, CorePlugin, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateCFEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, tokenize };
51578
51672
 
51579
51673
 
51580
- __info__.version = '16.4.21';
51581
- __info__.date = '2024-02-02T13:45:48.276Z';
51582
- __info__.hash = '73662b1';
51674
+ __info__.version = '16.4.23';
51675
+ __info__.date = '2024-02-16T14:23:29.521Z';
51676
+ __info__.hash = 'cb5db1a';