@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.
@@ -705,18 +705,18 @@ function isConsecutive(iterable) {
705
705
  return true;
706
706
  }
707
707
  class JetSet extends Set {
708
- add(...iterable) {
708
+ addMany(iterable) {
709
709
  for (const element of iterable) {
710
710
  super.add(element);
711
711
  }
712
712
  return this;
713
713
  }
714
- delete(...iterable) {
715
- let deleted = false;
714
+ deleteMany(iterable) {
715
+ let wasDeleted = false;
716
716
  for (const element of iterable) {
717
- deleted ||= super.delete(element);
717
+ wasDeleted ||= super.delete(element);
718
718
  }
719
- return deleted;
719
+ return wasDeleted;
720
720
  }
721
721
  }
722
722
  /**
@@ -1842,6 +1842,9 @@ function parseFormat(formatString) {
1842
1842
  function formatValue(value, { format, locale }) {
1843
1843
  switch (typeof value) {
1844
1844
  case "string":
1845
+ if (value.includes('\\"')) {
1846
+ return value.replace(/\\"/g, '"');
1847
+ }
1845
1848
  return value;
1846
1849
  case "boolean":
1847
1850
  return value ? "TRUE" : "FALSE";
@@ -15239,6 +15242,9 @@ function isValidLocale(locale) {
15239
15242
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
15240
15243
  return false;
15241
15244
  }
15245
+ if (locale.thousandsSeparator === locale.decimalSeparator) {
15246
+ return false;
15247
+ }
15242
15248
  try {
15243
15249
  formatValue(1, { locale, format: "#,##0.00" });
15244
15250
  formatValue(1, { locale, format: locale.dateFormat });
@@ -15329,7 +15335,7 @@ function canonicalizeNumberLiteral(content, locale) {
15329
15335
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
15330
15336
  return content;
15331
15337
  }
15332
- return content.replace(locale.decimalSeparator, ".");
15338
+ return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
15333
15339
  }
15334
15340
  /**
15335
15341
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -18631,6 +18637,12 @@ const INSERT_LINK = (env) => {
18631
18637
  let { col, row } = env.model.getters.getActivePosition();
18632
18638
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
18633
18639
  };
18640
+ const INSERT_LINK_NAME = (env) => {
18641
+ const sheetId = env.model.getters.getActiveSheetId();
18642
+ const { col, row } = env.model.getters.getActivePosition();
18643
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
18644
+ return cell && cell.link ? _lt("Edit link") : _lt("Insert link");
18645
+ };
18634
18646
  //------------------------------------------------------------------------------
18635
18647
  // Filters action
18636
18648
  //------------------------------------------------------------------------------
@@ -19171,7 +19183,7 @@ cellMenuRegistry
19171
19183
  })
19172
19184
  .add("insert_link", {
19173
19185
  ...insertLink,
19174
- name: _lt("Insert link"),
19186
+ name: INSERT_LINK_NAME,
19175
19187
  sequence: 150,
19176
19188
  separator: true,
19177
19189
  });
@@ -24082,6 +24094,7 @@ class Composer extends owl.Component {
24082
24094
  static components = { TextValueProvider, FunctionDescriptionProvider };
24083
24095
  static defaultProps = {
24084
24096
  inputStyle: "",
24097
+ isDefaultFocus: false,
24085
24098
  };
24086
24099
  composerRef = owl.useRef("o_composer");
24087
24100
  contentHelper = new ContentEditableHelper(this.composerRef.el);
@@ -24134,7 +24147,6 @@ class Composer extends owl.Component {
24134
24147
  F2: () => console.warn("Not implemented"),
24135
24148
  F4: this.processF4Key,
24136
24149
  Tab: (ev) => this.processTabKey(ev),
24137
- " ": (ev) => this.processSpaceKey(ev),
24138
24150
  };
24139
24151
  keyCodeMapping = {
24140
24152
  NumpadDecimal: this.processNumpadDecimal,
@@ -24142,11 +24154,11 @@ class Composer extends owl.Component {
24142
24154
  setup() {
24143
24155
  owl.onMounted(() => {
24144
24156
  const el = this.composerRef.el;
24157
+ if (this.props.isDefaultFocus) {
24158
+ this.env.focusableElement.setFocusableElement(el);
24159
+ }
24145
24160
  this.contentHelper.updateEl(el);
24146
24161
  });
24147
- owl.onWillUnmount(() => {
24148
- this.props.onComposerUnmounted?.();
24149
- });
24150
24162
  owl.useEffect(() => {
24151
24163
  this.processContent();
24152
24164
  });
@@ -24155,7 +24167,8 @@ class Composer extends owl.Component {
24155
24167
  // Handlers
24156
24168
  // ---------------------------------------------------------------------------
24157
24169
  processArrowKeys(ev) {
24158
- if (this.env.model.getters.isSelectingForComposer()) {
24170
+ if (this.env.model.getters.isSelectingForComposer() ||
24171
+ this.env.model.getters.getEditionMode() === "inactive") {
24159
24172
  this.functionDescriptionState.showDescription = false;
24160
24173
  // Prevent the default content editable behavior which moves the cursor
24161
24174
  ev.preventDefault();
@@ -24194,25 +24207,19 @@ class Composer extends owl.Component {
24194
24207
  processTabKey(ev) {
24195
24208
  ev.preventDefault();
24196
24209
  ev.stopPropagation();
24197
- if (this.autoCompleteState.showProvider) {
24198
- const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
24199
- if (autoCompleteValue) {
24200
- this.autoComplete(autoCompleteValue);
24201
- return;
24210
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
24211
+ if (this.autoCompleteState.showProvider) {
24212
+ const autoCompleteValue = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]?.text;
24213
+ if (autoCompleteValue) {
24214
+ this.autoComplete(autoCompleteValue);
24215
+ return;
24216
+ }
24202
24217
  }
24218
+ this.env.model.dispatch("STOP_EDITION");
24203
24219
  }
24204
24220
  const direction = ev.shiftKey ? "left" : "right";
24205
- this.env.model.dispatch("STOP_EDITION");
24206
24221
  this.env.model.selection.moveAnchorCell(direction, 1);
24207
24222
  }
24208
- processSpaceKey(ev) {
24209
- if (ev.ctrlKey) {
24210
- ev.preventDefault();
24211
- ev.stopPropagation();
24212
- this.showAutocomplete("");
24213
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
24214
- }
24215
- }
24216
24223
  processEnterKey(ev) {
24217
24224
  ev.preventDefault();
24218
24225
  ev.stopPropagation();
@@ -24264,6 +24271,9 @@ class Composer extends owl.Component {
24264
24271
  this.compositionActive = false;
24265
24272
  }
24266
24273
  onKeydown(ev) {
24274
+ if (this.env.model.getters.getEditionMode() === "inactive") {
24275
+ return;
24276
+ }
24267
24277
  let handler = this.keyMapping[ev.key] || this.keyCodeMapping[ev.code];
24268
24278
  if (handler) {
24269
24279
  handler.call(this, ev);
@@ -24272,14 +24282,33 @@ class Composer extends owl.Component {
24272
24282
  ev.stopPropagation();
24273
24283
  }
24274
24284
  }
24285
+ onPaste(ev) {
24286
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
24287
+ ev.stopPropagation();
24288
+ }
24289
+ }
24275
24290
  /*
24276
24291
  * Triggered automatically by the content-editable between the keydown and key up
24277
24292
  * */
24278
24293
  onInput(ev) {
24279
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
24294
+ if (!this.shouldProcessInputEvents) {
24280
24295
  return;
24281
24296
  }
24282
- let content = this.contentHelper.getText();
24297
+ if (ev.inputType === "insertFromPaste" &&
24298
+ this.env.model.getters.getEditionMode() === "inactive") {
24299
+ return;
24300
+ }
24301
+ ev.stopPropagation();
24302
+ let content;
24303
+ if (this.env.model.getters.getEditionMode() === "inactive") {
24304
+ content = ev.data || "";
24305
+ }
24306
+ else {
24307
+ content = this.contentHelper.getText();
24308
+ }
24309
+ if (this.props.focus === "inactive") {
24310
+ return this.props.onComposerCellFocused?.(content);
24311
+ }
24283
24312
  let selection = this.contentHelper.getCurrentSelection();
24284
24313
  if (ev.inputType === "insertParagraph") {
24285
24314
  const start = Math.min(selection.start, selection.end);
@@ -24357,9 +24386,8 @@ class Composer extends owl.Component {
24357
24386
  }
24358
24387
  const newSelection = this.contentHelper.getCurrentSelection();
24359
24388
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
24360
- if (this.props.focus === "inactive") {
24361
- this.props.onComposerContentFocused(newSelection);
24362
- }
24389
+ this.props.onComposerContentFocused();
24390
+ if (this.props.focus === "inactive") ;
24363
24391
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
24364
24392
  this.processTokenAtCursor();
24365
24393
  }
@@ -24526,7 +24554,7 @@ class Composer extends owl.Component {
24526
24554
  const highlights = this.env.model.getters.getHighlights();
24527
24555
  const refSheet = sheetName
24528
24556
  ? this.env.model.getters.getSheetIdByName(sheetName)
24529
- : this.env.model.getters.getCurrentEditedCell().sheetId;
24557
+ : this.env.model.getters.getCurrentEditedCell()?.sheetId;
24530
24558
  const highlight = highlights.find((highlight) => {
24531
24559
  if (highlight.sheetId !== refSheet)
24532
24560
  return false;
@@ -24606,12 +24634,13 @@ class Composer extends owl.Component {
24606
24634
  }
24607
24635
  }
24608
24636
  Composer.props = {
24609
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24610
- onComposerContentFocused: Function,
24611
24637
  inputStyle: { type: String, optional: true },
24612
24638
  rect: { type: Object, optional: true },
24613
24639
  delimitation: { type: Object, optional: true },
24614
- onComposerUnmounted: { type: Function, optional: true },
24640
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24641
+ onComposerCellFocused: { type: Function, optional: true },
24642
+ onComposerContentFocused: Function,
24643
+ isDefaultFocus: { type: Boolean, optional: true },
24615
24644
  };
24616
24645
 
24617
24646
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -24645,51 +24674,26 @@ css /* scss */ `
24645
24674
  class GridComposer extends owl.Component {
24646
24675
  static template = "o-spreadsheet-GridComposer";
24647
24676
  static components = { Composer };
24648
- gridComposerRef;
24649
- zone;
24650
- rect;
24677
+ // TODORAR see if we can keep it undefined
24678
+ rect = this.defaultRect;
24679
+ isEditing = false;
24651
24680
  isCellReferenceVisible;
24652
- composerState;
24681
+ get defaultRect() {
24682
+ return { x: 0, y: 0, width: 0, height: 0 };
24683
+ }
24653
24684
  setup() {
24654
- this.gridComposerRef = owl.useRef("gridComposer");
24655
- this.composerState = owl.useState({
24656
- rect: undefined,
24657
- delimitation: undefined,
24658
- });
24659
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
24660
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
24661
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
24662
- this.isCellReferenceVisible = false;
24663
- owl.onMounted(() => {
24664
- const el = this.gridComposerRef.el;
24665
- this.composerState.rect = {
24666
- x: this.rect.x,
24667
- y: this.rect.y,
24668
- width: el.clientWidth,
24669
- height: el.clientHeight,
24670
- };
24671
- this.composerState.delimitation = {
24672
- width: this.props.gridDims.width,
24673
- height: this.props.gridDims.height,
24674
- };
24675
- });
24676
24685
  owl.onWillUpdateProps(() => {
24677
- if (this.isCellReferenceVisible) {
24678
- return;
24679
- }
24680
- const sheetId = this.env.model.getters.getActiveSheetId();
24681
- const zone = this.env.model.getters.getSelectedZone();
24682
- const rect = this.env.model.getters.getVisibleRect(zone);
24683
- if (!deepEquals(rect, this.rect) ||
24684
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
24685
- this.isCellReferenceVisible = true;
24686
- }
24686
+ this.updateComponentPosition();
24687
+ this.updateCellReferenceVisibility();
24687
24688
  });
24688
24689
  }
24689
24690
  get shouldDisplayCellReference() {
24690
24691
  return this.isCellReferenceVisible;
24691
24692
  }
24692
24693
  get cellReference() {
24694
+ if (!this.env.model.getters.getCurrentEditedCell()) {
24695
+ return "";
24696
+ }
24693
24697
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
24694
24698
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
24695
24699
  return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -24701,7 +24705,27 @@ class GridComposer extends owl.Component {
24701
24705
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
24702
24706
  });
24703
24707
  }
24708
+ get composerProps() {
24709
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
24710
+ return {
24711
+ rect: { ...this.rect },
24712
+ delimitation: {
24713
+ width,
24714
+ height,
24715
+ },
24716
+ focus: this.props.focus,
24717
+ isDefaultFocus: true,
24718
+ onComposerContentFocused: this.props.onComposerContentFocused,
24719
+ onComposerCellFocused: this.props.onComposerCellFocused,
24720
+ };
24721
+ }
24704
24722
  get containerStyle() {
24723
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
24724
+ return `
24725
+ position: absolute;
24726
+ z-index: -1000;
24727
+ `;
24728
+ }
24705
24729
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
24706
24730
  const cell = this.env.model.getters.getActiveCell();
24707
24731
  const position = this.env.model.getters.getActivePosition();
@@ -24721,6 +24745,8 @@ class GridComposer extends owl.Component {
24721
24745
  if (!isFormula) {
24722
24746
  textAlign = style.align || cell.defaultAlign;
24723
24747
  }
24748
+ const maxHeight = this.props.gridDims.height - this.rect.y;
24749
+ const maxWidth = this.props.gridDims.width - this.rect.x;
24724
24750
  /**
24725
24751
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
24726
24752
  * including all the paddings/margins of the composer
@@ -24732,6 +24758,8 @@ class GridComposer extends owl.Component {
24732
24758
  top: `${top}px`,
24733
24759
  "min-width": `${width + 1}px`,
24734
24760
  "min-height": `${height + 1}px`,
24761
+ "max-width": `${maxWidth}px`,
24762
+ "max-height": `${maxHeight}px`,
24735
24763
  background,
24736
24764
  color,
24737
24765
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -24741,20 +24769,38 @@ class GridComposer extends owl.Component {
24741
24769
  "text-align": textAlign,
24742
24770
  });
24743
24771
  }
24744
- get composerStyle() {
24745
- const maxHeight = this.props.gridDims.height - this.rect.y;
24746
- const maxWidth = this.props.gridDims.width - this.rect.x;
24747
- return cssPropertiesToCss({
24748
- "max-width": `${maxWidth}px`,
24749
- "max-height": `${maxHeight}px`,
24750
- });
24772
+ updateComponentPosition() {
24773
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
24774
+ if (this.isEditing !== isEditing) {
24775
+ this.isEditing = isEditing;
24776
+ if (!isEditing) {
24777
+ this.rect = this.defaultRect;
24778
+ this.env.focusableElement.focus();
24779
+ return;
24780
+ }
24781
+ const position = this.env.model.getters.getActivePosition();
24782
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
24783
+ this.rect = this.env.model.getters.getVisibleRect(zone);
24784
+ }
24785
+ }
24786
+ updateCellReferenceVisibility() {
24787
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
24788
+ return;
24789
+ }
24790
+ const sheetId = this.env.model.getters.getActiveSheetId();
24791
+ const zone = this.env.model.getters.getSelectedZone();
24792
+ const rect = this.env.model.getters.getVisibleRect(zone);
24793
+ if (!deepEquals(rect, this.rect) ||
24794
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
24795
+ this.isCellReferenceVisible = true;
24796
+ }
24751
24797
  }
24752
24798
  }
24753
24799
  GridComposer.props = {
24754
24800
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
24755
- onComposerUnmounted: Function,
24756
24801
  onComposerContentFocused: Function,
24757
24802
  gridDims: Object,
24803
+ onComposerCellFocused: Function,
24758
24804
  };
24759
24805
 
24760
24806
  const CSS$1 = css /* scss */ `
@@ -25635,7 +25681,6 @@ class GridOverlay extends owl.Component {
25635
25681
  this.props.onCellDoubleClicked(col, row);
25636
25682
  }
25637
25683
  onContextMenu(ev) {
25638
- ev.preventDefault();
25639
25684
  const [col, row] = this.getCartesianCoordinates(ev);
25640
25685
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
25641
25686
  }
@@ -26728,7 +26773,6 @@ class Grid extends owl.Component {
26728
26773
  HEADER_WIDTH = HEADER_WIDTH;
26729
26774
  menuState;
26730
26775
  gridRef;
26731
- hiddenInput;
26732
26776
  onMouseWheel;
26733
26777
  canvasPosition;
26734
26778
  hoveredCell;
@@ -26739,17 +26783,16 @@ class Grid extends owl.Component {
26739
26783
  menuItems: [],
26740
26784
  });
26741
26785
  this.gridRef = owl.useRef("grid");
26742
- this.hiddenInput = owl.useRef("hiddenInput");
26743
26786
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
26744
26787
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
26745
26788
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
26746
26789
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
26747
26790
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
26748
26791
  owl.useExternalListener(document.body, "paste", this.paste);
26749
- owl.onMounted(() => this.focus());
26750
- this.props.exposeFocus(() => this.focus());
26792
+ owl.onMounted(() => this.focusDefaultElement());
26793
+ this.props.exposeFocus(() => this.focusDefaultElement());
26751
26794
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
26752
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
26795
+ owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
26753
26796
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
26754
26797
  this.moveCanvas(deltaX, deltaY);
26755
26798
  this.hoveredCell.col = undefined;
@@ -26772,7 +26815,7 @@ class Grid extends owl.Component {
26772
26815
  if (this.env.model.getters.hasOpenedPopover()) {
26773
26816
  this.closeOpenedPopover();
26774
26817
  }
26775
- this.focus();
26818
+ this.focusDefaultElement();
26776
26819
  }
26777
26820
  // this map will handle most of the actions that should happen on key down. The arrow keys are managed in the key
26778
26821
  // down itself
@@ -26945,10 +26988,10 @@ class Grid extends owl.Component {
26945
26988
  PAGEUP: () => this.env.model.dispatch("SHIFT_VIEWPORT_UP"),
26946
26989
  "CTRL+K": () => INSERT_LINK(this.env),
26947
26990
  };
26948
- focus() {
26991
+ focusDefaultElement() {
26949
26992
  if (!this.env.model.getters.getSelectedFigureId() &&
26950
26993
  this.env.model.getters.getEditionMode() === "inactive") {
26951
- this.hiddenInput.el?.focus();
26994
+ this.env.focusableElement.focus();
26952
26995
  }
26953
26996
  }
26954
26997
  get gridEl() {
@@ -27092,19 +27135,6 @@ class Grid extends owl.Component {
27092
27135
  return;
27093
27136
  }
27094
27137
  }
27095
- onInput(ev) {
27096
- // the user meant to paste in the sheet, not open the composer with the pasted content
27097
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
27098
- return;
27099
- }
27100
- if (ev.data) {
27101
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
27102
- // character
27103
- ev.preventDefault();
27104
- ev.stopPropagation();
27105
- this.props.onGridComposerCellFocused(ev.data);
27106
- }
27107
- }
27108
27138
  // ---------------------------------------------------------------------------
27109
27139
  // Context Menu
27110
27140
  // ---------------------------------------------------------------------------
@@ -27217,7 +27247,7 @@ class Grid extends owl.Component {
27217
27247
  }
27218
27248
  closeMenu() {
27219
27249
  this.menuState.isOpen = false;
27220
- this.focus();
27250
+ this.focusDefaultElement();
27221
27251
  }
27222
27252
  }
27223
27253
  Grid.props = {
@@ -30877,6 +30907,31 @@ const MIGRATIONS = [
30877
30907
  return data;
30878
30908
  },
30879
30909
  },
30910
+ {
30911
+ description: "Fix datafilter duplication",
30912
+ from: 12,
30913
+ to: 12.5,
30914
+ applyMigration(data) {
30915
+ for (let sheet of data.sheets || []) {
30916
+ let knownDataFilterZones = [];
30917
+ for (let filterTable of sheet.filterTables || []) {
30918
+ const zone = toZone(filterTable.range);
30919
+ // See commit message for the details
30920
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
30921
+ if (intersectZoneIndex !== -1) {
30922
+ knownDataFilterZones[intersectZoneIndex] = zone;
30923
+ }
30924
+ else {
30925
+ knownDataFilterZones.push(zone);
30926
+ }
30927
+ }
30928
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
30929
+ range: zoneToXc(zone),
30930
+ }));
30931
+ }
30932
+ return data;
30933
+ },
30934
+ },
30880
30935
  {
30881
30936
  description: "Change Border description structure",
30882
30937
  from: 12,
@@ -31796,9 +31851,9 @@ class CellPlugin extends CorePlugin {
31796
31851
  allowDispatch(cmd) {
31797
31852
  switch (cmd.type) {
31798
31853
  case "UPDATE_CELL":
31799
- return this.checkCellOutOfSheet(cmd);
31854
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
31800
31855
  case "CLEAR_CELL":
31801
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
31856
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
31802
31857
  default:
31803
31858
  return 0 /* CommandResult.Success */;
31804
31859
  }
@@ -31930,10 +31985,6 @@ class CellPlugin extends CorePlugin {
31930
31985
  format: cell.format ? getItemId(cell.format, formats) : undefined,
31931
31986
  content: cell.content || undefined,
31932
31987
  };
31933
- // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
31934
- if (cell.isFormula && cell.dependencies.length) {
31935
- cells[xc].content = this.buildFormulaContent(_sheet.id, cell, cell.dependencies, true);
31936
- }
31937
31988
  }
31938
31989
  _sheet.cells = cells;
31939
31990
  }
@@ -32096,10 +32147,7 @@ class CellPlugin extends CorePlugin {
32096
32147
  else {
32097
32148
  style = before ? before.style : undefined;
32098
32149
  }
32099
- const locale = this.getters.getLocale();
32100
- let format = ("format" in after ? after.format : before && before.format) ||
32101
- detectDateFormat(afterContent, locale) ||
32102
- detectNumberFormat(afterContent);
32150
+ const format = "format" in after ? after.format : before && before.format;
32103
32151
  /* Read the following IF as:
32104
32152
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
32105
32153
  * - the command says the new content is empty and has no border/format/style
@@ -32140,12 +32188,11 @@ class CellPlugin extends CorePlugin {
32140
32188
  }
32141
32189
  createLiteralCell(id, content, format, style) {
32142
32190
  const locale = this.getters.getLocale();
32143
- content = parseLiteral(content, locale).toString();
32144
32191
  return {
32145
32192
  id,
32146
- content,
32193
+ content: parseLiteral(content, locale).toString(),
32147
32194
  style,
32148
- format,
32195
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
32149
32196
  isFormula: false,
32150
32197
  };
32151
32198
  }
@@ -32206,6 +32253,18 @@ class CellPlugin extends CorePlugin {
32206
32253
  }
32207
32254
  return 0 /* CommandResult.Success */;
32208
32255
  }
32256
+ checkUselessUpdateCell(cmd) {
32257
+ const cell = this.getters.getCell(cmd);
32258
+ const hasContent = "content" in cmd || "formula" in cmd;
32259
+ const hasStyle = "style" in cmd;
32260
+ const hasFormat = "format" in cmd;
32261
+ if ((!hasContent || cell?.content === cmd.content) &&
32262
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
32263
+ (!hasFormat || cell?.format === cmd.format)) {
32264
+ return 98 /* CommandResult.NoChanges */;
32265
+ }
32266
+ return 0 /* CommandResult.Success */;
32267
+ }
32209
32268
  }
32210
32269
  class FormulaCellWithDependencies {
32211
32270
  id;
@@ -36346,7 +36405,15 @@ class SpreadsheetRTree {
36346
36405
  if (!this.rTrees[sheetId]) {
36347
36406
  return;
36348
36407
  }
36349
- this.rTrees[sheetId].remove(item, deepEquals);
36408
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
36409
+ }
36410
+ rtreeItemComparer(left, right) {
36411
+ return (left.data == right.data &&
36412
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
36413
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
36414
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
36415
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
36416
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
36350
36417
  }
36351
36418
  }
36352
36419
  /**
@@ -36424,7 +36491,7 @@ class FormulaDependencyGraph {
36424
36491
  const queue = Array.from(ranges).reverse();
36425
36492
  while (queue.length > 0) {
36426
36493
  const range = queue.pop();
36427
- visited.add(...this.encoder.encodeBoundingBox(range));
36494
+ visited.addMany(this.encoder.encodeBoundingBox(range));
36428
36495
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
36429
36496
  for (const positionId of impactedPositionIds) {
36430
36497
  if (!visited.has(positionId)) {
@@ -36432,7 +36499,7 @@ class FormulaDependencyGraph {
36432
36499
  }
36433
36500
  }
36434
36501
  }
36435
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36502
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36436
36503
  return visited;
36437
36504
  }
36438
36505
  }
@@ -36563,9 +36630,9 @@ class Evaluator {
36563
36630
  const cells = positions.map((p) => this.encoder.encode(p));
36564
36631
  const cellsToCompute = new JetSet(cells);
36565
36632
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
36566
- cellsToCompute.add(...this.getCellsDependingOn(cells));
36567
- cellsToCompute.add(...arrayFormulasPositionIds);
36568
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
36633
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
36634
+ cellsToCompute.addMany(arrayFormulasPositionIds);
36635
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
36569
36636
  this.evaluate(cellsToCompute);
36570
36637
  }
36571
36638
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -36579,7 +36646,7 @@ class Evaluator {
36579
36646
  }
36580
36647
  if (!content) {
36581
36648
  // The previous content could have blocked some array formulas
36582
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36649
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36583
36650
  }
36584
36651
  }
36585
36652
  return impactedPositionIds;
@@ -36627,7 +36694,7 @@ class Evaluator {
36627
36694
  }
36628
36695
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
36629
36696
  const cells = new JetSet(arrayFormulas);
36630
- cells.add(...this.getCellsDependingOn(arrayFormulas));
36697
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
36631
36698
  return cells;
36632
36699
  }
36633
36700
  nextPositionsToUpdate = new JetSet();
@@ -36778,7 +36845,7 @@ class Evaluator {
36778
36845
  this.setEvaluatedCell(positionId, evaluatedCell);
36779
36846
  // check if formula dependencies present in the spread zone
36780
36847
  // if so, they need to be recomputed
36781
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
36848
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
36782
36849
  };
36783
36850
  }
36784
36851
  invalidateSpreading(positionId) {
@@ -36793,8 +36860,8 @@ class Evaluator {
36793
36860
  continue;
36794
36861
  }
36795
36862
  this.evaluatedCells.delete(child);
36796
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
36797
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
36863
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
36864
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
36798
36865
  }
36799
36866
  this.spreadingRelations.removeNode(positionId);
36800
36867
  }
@@ -37154,7 +37221,13 @@ class EvaluationPlugin extends UIPlugin {
37154
37221
  const format = newFormat
37155
37222
  ? getItemId(newFormat, data.formats)
37156
37223
  : exportedCellData.format;
37157
- const content = !isExported ? newContent : exportedCellData.content;
37224
+ let content;
37225
+ if (formulaCell instanceof FormulaCellWithDependencies) {
37226
+ content = this.getters.buildFormulaContent(exportedSheetData.id, formulaCell, formulaCell.dependencies, true);
37227
+ }
37228
+ else {
37229
+ content = !isExported ? newContent : exportedCellData.content;
37230
+ }
37158
37231
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
37159
37232
  }
37160
37233
  }
@@ -43673,7 +43746,7 @@ class EditionPlugin extends UIPlugin {
43673
43746
  this.replaceText(ref, this.selectionInitialStart, this.selectionEnd);
43674
43747
  }
43675
43748
  getZoneReference(zone) {
43676
- const inputSheetId = this.getters.getCurrentEditedCell().sheetId;
43749
+ const inputSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43677
43750
  const sheetId = this.getters.getActiveSheetId();
43678
43751
  const range = this.getters.getRangeFromZone(sheetId, zone);
43679
43752
  return this.getters.getSelectionRangeString(range, inputSheetId);
@@ -43687,7 +43760,7 @@ class EditionPlugin extends UIPlugin {
43687
43760
  _fixedParts.pop();
43688
43761
  }
43689
43762
  const newRange = range.clone({ parts: _fixedParts });
43690
- return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
43763
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId());
43691
43764
  }
43692
43765
  /**
43693
43766
  * Replace the current selection by a new text.
@@ -43720,7 +43793,7 @@ class EditionPlugin extends UIPlugin {
43720
43793
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43721
43794
  return;
43722
43795
  }
43723
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43796
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43724
43797
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
43725
43798
  const colorsToKeep = {};
43726
43799
  for (const xc of XCs) {
@@ -43749,7 +43822,7 @@ class EditionPlugin extends UIPlugin {
43749
43822
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43750
43823
  return [];
43751
43824
  }
43752
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43825
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43753
43826
  const rangeColor = (rangeString) => {
43754
43827
  const colorIndex = this.colorIndexByRange[rangeString];
43755
43828
  return colors$1[colorIndex % colors$1.length];
@@ -43767,7 +43840,7 @@ class EditionPlugin extends UIPlugin {
43767
43840
  * Return ranges currently referenced in the composer
43768
43841
  */
43769
43842
  getReferencedRanges() {
43770
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43843
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43771
43844
  const referenceRanges = this.currentTokens
43772
43845
  .filter((token) => token.type === "REFERENCE")
43773
43846
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -45731,6 +45804,16 @@ class ImageProvider {
45731
45804
  }
45732
45805
  }
45733
45806
 
45807
+ class FocusableElement {
45808
+ focusableElement = undefined;
45809
+ setFocusableElement(element) {
45810
+ this.focusableElement = element;
45811
+ }
45812
+ focus() {
45813
+ this.focusableElement?.focus();
45814
+ }
45815
+ }
45816
+
45734
45817
  const RIPPLE_KEY_FRAMES = [
45735
45818
  { transform: "scale(0)" },
45736
45819
  { transform: "scale(0.8)", offset: 0.33 },
@@ -47773,6 +47856,7 @@ class Spreadsheet extends owl.Component {
47773
47856
  _t: Spreadsheet._t,
47774
47857
  clipboard: this.env.clipboard || instantiateClipboard(),
47775
47858
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
47859
+ focusableElement: new FocusableElement(),
47776
47860
  });
47777
47861
  owl.useExternalListener(window, "resize", () => this.render(true));
47778
47862
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -47879,17 +47963,18 @@ class Spreadsheet extends owl.Component {
47879
47963
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47880
47964
  this.composer.topBarFocus = "contentFocus";
47881
47965
  this.composer.gridFocusMode = "inactive";
47882
- this.setComposerContent({ selection } || {});
47966
+ this.setComposerContent({ selection });
47883
47967
  }
47884
- onGridComposerContentFocused() {
47968
+ onGridComposerContentFocused(selection) {
47885
47969
  if (this.model.getters.isReadonly()) {
47886
47970
  return;
47887
47971
  }
47888
47972
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47889
47973
  this.composer.topBarFocus = "inactive";
47890
47974
  this.composer.gridFocusMode = "contentFocus";
47891
- this.setComposerContent({});
47975
+ this.setComposerContent({ selection });
47892
47976
  }
47977
+ // TODO: either both are defined or none of them. change those args to an object
47893
47978
  onGridComposerCellFocused(content, selection) {
47894
47979
  if (this.model.getters.isReadonly()) {
47895
47980
  return;
@@ -49301,11 +49386,10 @@ class SelectionStreamProcessorImpl {
49301
49386
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
49302
49387
  */
49303
49388
  getEndOfCluster(startPosition, dim, dir) {
49304
- const sheet = this.getters.getActiveSheet();
49305
49389
  let currentPosition = startPosition;
49306
49390
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
49307
49391
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
49308
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
49392
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
49309
49393
  ? "endOfCluster"
49310
49394
  : "nextCluster";
49311
49395
  while (true) {
@@ -49315,7 +49399,7 @@ class SelectionStreamProcessorImpl {
49315
49399
  currentPosition.row === nextCellPosition.row) {
49316
49400
  break;
49317
49401
  }
49318
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
49402
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
49319
49403
  if (mode === "endOfCluster" && isNextCellEmpty) {
49320
49404
  break;
49321
49405
  }
@@ -49329,10 +49413,20 @@ class SelectionStreamProcessorImpl {
49329
49413
  return dim === "cols" ? currentPosition.col : currentPosition.row;
49330
49414
  }
49331
49415
  /**
49332
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
49333
- * check if the merge containing the cell is empty.
49416
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
49417
+ * the check applies to the main cell of the merge.
49418
+ */
49419
+ isCellEmpty({ col, row }) {
49420
+ const sheetId = this.getters.getActiveSheetId();
49421
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
49422
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
49423
+ }
49424
+ /**
49425
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
49426
+ * the check applies to the main cell of the merge.
49334
49427
  */
49335
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
49428
+ isEvaluatedCellEmpty({ col, row }) {
49429
+ const sheetId = this.getters.getActiveSheetId();
49336
49430
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
49337
49431
  const cell = this.getters.getEvaluatedCell(position);
49338
49432
  return cell.type === CellValueType.empty;
@@ -51613,6 +51707,6 @@ exports.setTranslationMethod = setTranslationMethod;
51613
51707
  exports.tokenize = tokenize;
51614
51708
 
51615
51709
 
51616
- __info__.version = '16.4.21';
51617
- __info__.date = '2024-02-02T13:45:48.276Z';
51618
- __info__.hash = '73662b1';
51710
+ __info__.version = '16.4.23';
51711
+ __info__.date = '2024-02-16T14:23:29.521Z';
51712
+ __info__.hash = 'cb5db1a';