@odoo/o-spreadsheet 16.4.21 → 16.4.24

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
  });
@@ -20659,7 +20671,7 @@ class SelectionInput extends owl.Component {
20659
20671
  : this.props.ranges()
20660
20672
  ? this.props.ranges().map((xc, id) => ({
20661
20673
  xc,
20662
- id,
20674
+ id: id + 1,
20663
20675
  isFocused: false,
20664
20676
  }))
20665
20677
  : [];
@@ -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 = {
@@ -30878,8 +30908,16 @@ const MIGRATIONS = [
30878
30908
  },
30879
30909
  },
30880
30910
  {
30881
- description: "Change Border description structure",
30911
+ description: "Fix datafilter duplication",
30882
30912
  from: 12,
30913
+ to: 12.5,
30914
+ applyMigration(data) {
30915
+ return fixOverlappingFilters(data);
30916
+ },
30917
+ },
30918
+ {
30919
+ description: "Change Border description structure",
30920
+ from: 12.5,
30883
30921
  to: 13,
30884
30922
  applyMigration(data) {
30885
30923
  for (const borderId in data.borders) {
@@ -30910,6 +30948,14 @@ const MIGRATIONS = [
30910
30948
  return data;
30911
30949
  },
30912
30950
  },
30951
+ {
30952
+ description: "Fix datafilter duplication (post saas-16.4)",
30953
+ from: 14,
30954
+ to: 14.5,
30955
+ applyMigration(data) {
30956
+ return fixOverlappingFilters(data);
30957
+ },
30958
+ },
30913
30959
  ];
30914
30960
  /**
30915
30961
  * This function is used to repair faulty data independently of the migration.
@@ -31067,6 +31113,26 @@ function fixChartDefinitions(data, initialMessages) {
31067
31113
  }
31068
31114
  return messages;
31069
31115
  }
31116
+ function fixOverlappingFilters(data) {
31117
+ for (let sheet of data.sheets || []) {
31118
+ let knownDataFilterZones = [];
31119
+ for (let filterTable of sheet.filterTables || []) {
31120
+ const zone = toZone(filterTable.range);
31121
+ // See commit message of https://github.com/odoo/o-spreadsheet/pull/3632 of more details
31122
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
31123
+ if (intersectZoneIndex !== -1) {
31124
+ knownDataFilterZones[intersectZoneIndex] = zone;
31125
+ }
31126
+ else {
31127
+ knownDataFilterZones.push(zone);
31128
+ }
31129
+ }
31130
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
31131
+ range: zoneToXc(zone),
31132
+ }));
31133
+ }
31134
+ return data;
31135
+ }
31070
31136
  // -----------------------------------------------------------------------------
31071
31137
  // Helpers
31072
31138
  // -----------------------------------------------------------------------------
@@ -31796,9 +31862,9 @@ class CellPlugin extends CorePlugin {
31796
31862
  allowDispatch(cmd) {
31797
31863
  switch (cmd.type) {
31798
31864
  case "UPDATE_CELL":
31799
- return this.checkCellOutOfSheet(cmd);
31865
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
31800
31866
  case "CLEAR_CELL":
31801
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
31867
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
31802
31868
  default:
31803
31869
  return 0 /* CommandResult.Success */;
31804
31870
  }
@@ -31930,10 +31996,6 @@ class CellPlugin extends CorePlugin {
31930
31996
  format: cell.format ? getItemId(cell.format, formats) : undefined,
31931
31997
  content: cell.content || undefined,
31932
31998
  };
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
31999
  }
31938
32000
  _sheet.cells = cells;
31939
32001
  }
@@ -32096,10 +32158,7 @@ class CellPlugin extends CorePlugin {
32096
32158
  else {
32097
32159
  style = before ? before.style : undefined;
32098
32160
  }
32099
- const locale = this.getters.getLocale();
32100
- let format = ("format" in after ? after.format : before && before.format) ||
32101
- detectDateFormat(afterContent, locale) ||
32102
- detectNumberFormat(afterContent);
32161
+ const format = "format" in after ? after.format : before && before.format;
32103
32162
  /* Read the following IF as:
32104
32163
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
32105
32164
  * - the command says the new content is empty and has no border/format/style
@@ -32140,12 +32199,11 @@ class CellPlugin extends CorePlugin {
32140
32199
  }
32141
32200
  createLiteralCell(id, content, format, style) {
32142
32201
  const locale = this.getters.getLocale();
32143
- content = parseLiteral(content, locale).toString();
32144
32202
  return {
32145
32203
  id,
32146
- content,
32204
+ content: parseLiteral(content, locale).toString(),
32147
32205
  style,
32148
- format,
32206
+ format: format || detectDateFormat(content, locale) || detectNumberFormat(content),
32149
32207
  isFormula: false,
32150
32208
  };
32151
32209
  }
@@ -32206,6 +32264,18 @@ class CellPlugin extends CorePlugin {
32206
32264
  }
32207
32265
  return 0 /* CommandResult.Success */;
32208
32266
  }
32267
+ checkUselessUpdateCell(cmd) {
32268
+ const cell = this.getters.getCell(cmd);
32269
+ const hasContent = "content" in cmd || "formula" in cmd;
32270
+ const hasStyle = "style" in cmd;
32271
+ const hasFormat = "format" in cmd;
32272
+ if ((!hasContent || cell?.content === cmd.content) &&
32273
+ (!hasStyle || deepEquals(cell?.style, cmd.style)) &&
32274
+ (!hasFormat || cell?.format === cmd.format)) {
32275
+ return 98 /* CommandResult.NoChanges */;
32276
+ }
32277
+ return 0 /* CommandResult.Success */;
32278
+ }
32209
32279
  }
32210
32280
  class FormulaCellWithDependencies {
32211
32281
  id;
@@ -36346,7 +36416,15 @@ class SpreadsheetRTree {
36346
36416
  if (!this.rTrees[sheetId]) {
36347
36417
  return;
36348
36418
  }
36349
- this.rTrees[sheetId].remove(item, deepEquals);
36419
+ this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
36420
+ }
36421
+ rtreeItemComparer(left, right) {
36422
+ return (left.data == right.data &&
36423
+ left.boundingBox.sheetId === right.boundingBox.sheetId &&
36424
+ left.boundingBox?.zone.left === right.boundingBox.zone.left &&
36425
+ left.boundingBox?.zone.top === right.boundingBox.zone.top &&
36426
+ left.boundingBox?.zone.right === right.boundingBox.zone.right &&
36427
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
36350
36428
  }
36351
36429
  }
36352
36430
  /**
@@ -36424,7 +36502,7 @@ class FormulaDependencyGraph {
36424
36502
  const queue = Array.from(ranges).reverse();
36425
36503
  while (queue.length > 0) {
36426
36504
  const range = queue.pop();
36427
- visited.add(...this.encoder.encodeBoundingBox(range));
36505
+ visited.addMany(this.encoder.encodeBoundingBox(range));
36428
36506
  const impactedPositionIds = this.rTree.search(range).map((dep) => dep.data);
36429
36507
  for (const positionId of impactedPositionIds) {
36430
36508
  if (!visited.has(positionId)) {
@@ -36432,7 +36510,7 @@ class FormulaDependencyGraph {
36432
36510
  }
36433
36511
  }
36434
36512
  }
36435
- visited.delete(...ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36513
+ visited.deleteMany(ranges.flatMap((r) => this.encoder.encodeBoundingBox(r)));
36436
36514
  return visited;
36437
36515
  }
36438
36516
  }
@@ -36563,9 +36641,9 @@ class Evaluator {
36563
36641
  const cells = positions.map((p) => this.encoder.encode(p));
36564
36642
  const cellsToCompute = new JetSet(cells);
36565
36643
  const arrayFormulasPositionIds = this.getArrayFormulasImpactedByChangesOf(cells);
36566
- cellsToCompute.add(...this.getCellsDependingOn(cells));
36567
- cellsToCompute.add(...arrayFormulasPositionIds);
36568
- cellsToCompute.add(...this.getCellsDependingOn(arrayFormulasPositionIds));
36644
+ cellsToCompute.addMany(this.getCellsDependingOn(cells));
36645
+ cellsToCompute.addMany(arrayFormulasPositionIds);
36646
+ cellsToCompute.addMany(this.getCellsDependingOn(arrayFormulasPositionIds));
36569
36647
  this.evaluate(cellsToCompute);
36570
36648
  }
36571
36649
  getArrayFormulasImpactedByChangesOf(positionIds) {
@@ -36579,7 +36657,7 @@ class Evaluator {
36579
36657
  }
36580
36658
  if (!content) {
36581
36659
  // The previous content could have blocked some array formulas
36582
- impactedPositionIds.add(...this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36660
+ impactedPositionIds.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(positionId));
36583
36661
  }
36584
36662
  }
36585
36663
  return impactedPositionIds;
@@ -36627,7 +36705,7 @@ class Evaluator {
36627
36705
  }
36628
36706
  const arrayFormulas = this.spreadingRelations.getFormulaPositionsSpreadingOn(positionId);
36629
36707
  const cells = new JetSet(arrayFormulas);
36630
- cells.add(...this.getCellsDependingOn(arrayFormulas));
36708
+ cells.addMany(this.getCellsDependingOn(arrayFormulas));
36631
36709
  return cells;
36632
36710
  }
36633
36711
  nextPositionsToUpdate = new JetSet();
@@ -36778,7 +36856,7 @@ class Evaluator {
36778
36856
  this.setEvaluatedCell(positionId, evaluatedCell);
36779
36857
  // check if formula dependencies present in the spread zone
36780
36858
  // if so, they need to be recomputed
36781
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([positionId]));
36859
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([positionId]));
36782
36860
  };
36783
36861
  }
36784
36862
  invalidateSpreading(positionId) {
@@ -36793,8 +36871,8 @@ class Evaluator {
36793
36871
  continue;
36794
36872
  }
36795
36873
  this.evaluatedCells.delete(child);
36796
- this.nextPositionsToUpdate.add(...this.getCellsDependingOn([child]));
36797
- this.nextPositionsToUpdate.add(...this.getArrayFormulasBlockedByOrSpreadingOn(child));
36874
+ this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([child]));
36875
+ this.nextPositionsToUpdate.addMany(this.getArrayFormulasBlockedByOrSpreadingOn(child));
36798
36876
  }
36799
36877
  this.spreadingRelations.removeNode(positionId);
36800
36878
  }
@@ -37154,7 +37232,13 @@ class EvaluationPlugin extends UIPlugin {
37154
37232
  const format = newFormat
37155
37233
  ? getItemId(newFormat, data.formats)
37156
37234
  : exportedCellData.format;
37157
- const content = !isExported ? newContent : exportedCellData.content;
37235
+ let content;
37236
+ if (formulaCell instanceof FormulaCellWithDependencies) {
37237
+ content = this.getters.buildFormulaContent(exportedSheetData.id, formulaCell, formulaCell.dependencies, true);
37238
+ }
37239
+ else {
37240
+ content = !isExported ? newContent : exportedCellData.content;
37241
+ }
37158
37242
  exportedSheetData.cells[xc] = { ...exportedCellData, value, isFormula, content, format };
37159
37243
  }
37160
37244
  }
@@ -40977,14 +41061,12 @@ class SelectionInputsManagerPlugin extends UIPlugin {
40977
41061
  break;
40978
41062
  case "FOCUS_RANGE":
40979
41063
  case "CHANGE_RANGE":
40980
- if (cmd.id !== this.focusedInputId) {
40981
- const input = this.inputs[cmd.id];
40982
- const range = input.ranges.find((range) => range.id === cmd.rangeId);
40983
- if (this.isRangeValid(range?.xc || "A1")) {
40984
- const sheetId = this.getters.getActiveSheetId();
40985
- const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
40986
- this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
40987
- }
41064
+ const input = this.inputs[cmd.id];
41065
+ const range = input.ranges.find((range) => range.id === cmd.rangeId);
41066
+ if (range) {
41067
+ const sheetId = this.getters.getActiveSheetId();
41068
+ const zone = this.getters.getRangeFromSheetXC(sheetId, range?.xc || "A1").zone;
41069
+ this.selection.capture(input, { cell: { col: zone.left, row: zone.top }, zone }, { handleEvent: input.handleEvent.bind(input) });
40988
41070
  this.focusedInputId = cmd.id;
40989
41071
  }
40990
41072
  break;
@@ -43673,7 +43755,7 @@ class EditionPlugin extends UIPlugin {
43673
43755
  this.replaceText(ref, this.selectionInitialStart, this.selectionEnd);
43674
43756
  }
43675
43757
  getZoneReference(zone) {
43676
- const inputSheetId = this.getters.getCurrentEditedCell().sheetId;
43758
+ const inputSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43677
43759
  const sheetId = this.getters.getActiveSheetId();
43678
43760
  const range = this.getters.getRangeFromZone(sheetId, zone);
43679
43761
  return this.getters.getSelectionRangeString(range, inputSheetId);
@@ -43687,7 +43769,7 @@ class EditionPlugin extends UIPlugin {
43687
43769
  _fixedParts.pop();
43688
43770
  }
43689
43771
  const newRange = range.clone({ parts: _fixedParts });
43690
- return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
43772
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId());
43691
43773
  }
43692
43774
  /**
43693
43775
  * Replace the current selection by a new text.
@@ -43720,7 +43802,7 @@ class EditionPlugin extends UIPlugin {
43720
43802
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43721
43803
  return;
43722
43804
  }
43723
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43805
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43724
43806
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
43725
43807
  const colorsToKeep = {};
43726
43808
  for (const xc of XCs) {
@@ -43749,7 +43831,7 @@ class EditionPlugin extends UIPlugin {
43749
43831
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
43750
43832
  return [];
43751
43833
  }
43752
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43834
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43753
43835
  const rangeColor = (rangeString) => {
43754
43836
  const colorIndex = this.colorIndexByRange[rangeString];
43755
43837
  return colors$1[colorIndex % colors$1.length];
@@ -43767,7 +43849,7 @@ class EditionPlugin extends UIPlugin {
43767
43849
  * Return ranges currently referenced in the composer
43768
43850
  */
43769
43851
  getReferencedRanges() {
43770
- const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
43852
+ const editionSheetId = this.getters.getCurrentEditedCell()?.sheetId || this.getters.getActiveSheetId();
43771
43853
  const referenceRanges = this.currentTokens
43772
43854
  .filter((token) => token.type === "REFERENCE")
43773
43855
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -45731,6 +45813,16 @@ class ImageProvider {
45731
45813
  }
45732
45814
  }
45733
45815
 
45816
+ class FocusableElement {
45817
+ focusableElement = undefined;
45818
+ setFocusableElement(element) {
45819
+ this.focusableElement = element;
45820
+ }
45821
+ focus() {
45822
+ this.focusableElement?.focus();
45823
+ }
45824
+ }
45825
+
45734
45826
  const RIPPLE_KEY_FRAMES = [
45735
45827
  { transform: "scale(0)" },
45736
45828
  { transform: "scale(0.8)", offset: 0.33 },
@@ -47773,6 +47865,7 @@ class Spreadsheet extends owl.Component {
47773
47865
  _t: Spreadsheet._t,
47774
47866
  clipboard: this.env.clipboard || instantiateClipboard(),
47775
47867
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
47868
+ focusableElement: new FocusableElement(),
47776
47869
  });
47777
47870
  owl.useExternalListener(window, "resize", () => this.render(true));
47778
47871
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -47879,17 +47972,18 @@ class Spreadsheet extends owl.Component {
47879
47972
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47880
47973
  this.composer.topBarFocus = "contentFocus";
47881
47974
  this.composer.gridFocusMode = "inactive";
47882
- this.setComposerContent({ selection } || {});
47975
+ this.setComposerContent({ selection });
47883
47976
  }
47884
- onGridComposerContentFocused() {
47977
+ onGridComposerContentFocused(selection) {
47885
47978
  if (this.model.getters.isReadonly()) {
47886
47979
  return;
47887
47980
  }
47888
47981
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
47889
47982
  this.composer.topBarFocus = "inactive";
47890
47983
  this.composer.gridFocusMode = "contentFocus";
47891
- this.setComposerContent({});
47984
+ this.setComposerContent({ selection });
47892
47985
  }
47986
+ // TODO: either both are defined or none of them. change those args to an object
47893
47987
  onGridComposerCellFocused(content, selection) {
47894
47988
  if (this.model.getters.isReadonly()) {
47895
47989
  return;
@@ -49301,11 +49395,10 @@ class SelectionStreamProcessorImpl {
49301
49395
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
49302
49396
  */
49303
49397
  getEndOfCluster(startPosition, dim, dir) {
49304
- const sheet = this.getters.getActiveSheet();
49305
49398
  let currentPosition = startPosition;
49306
49399
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
49307
49400
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
49308
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
49401
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
49309
49402
  ? "endOfCluster"
49310
49403
  : "nextCluster";
49311
49404
  while (true) {
@@ -49315,7 +49408,7 @@ class SelectionStreamProcessorImpl {
49315
49408
  currentPosition.row === nextCellPosition.row) {
49316
49409
  break;
49317
49410
  }
49318
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
49411
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
49319
49412
  if (mode === "endOfCluster" && isNextCellEmpty) {
49320
49413
  break;
49321
49414
  }
@@ -49329,10 +49422,20 @@ class SelectionStreamProcessorImpl {
49329
49422
  return dim === "cols" ? currentPosition.col : currentPosition.row;
49330
49423
  }
49331
49424
  /**
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.
49425
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
49426
+ * the check applies to the main cell of the merge.
49427
+ */
49428
+ isCellEmpty({ col, row }) {
49429
+ const sheetId = this.getters.getActiveSheetId();
49430
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
49431
+ return !(this.getters.getCorrespondingFormulaCell(position) || this.getters.getCell(position)?.content);
49432
+ }
49433
+ /**
49434
+ * Checks if a cell evaluated value is empty. If the cell is part of a merge,
49435
+ * the check applies to the main cell of the merge.
49334
49436
  */
49335
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
49437
+ isEvaluatedCellEmpty({ col, row }) {
49438
+ const sheetId = this.getters.getActiveSheetId();
49336
49439
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
49337
49440
  const cell = this.getters.getEvaluatedCell(position);
49338
49441
  return cell.type === CellValueType.empty;
@@ -51613,6 +51716,6 @@ exports.setTranslationMethod = setTranslationMethod;
51613
51716
  exports.tokenize = tokenize;
51614
51717
 
51615
51718
 
51616
- __info__.version = '16.4.21';
51617
- __info__.date = '2024-02-02T13:45:48.276Z';
51618
- __info__.hash = '73662b1';
51719
+ __info__.version = '16.4.24';
51720
+ __info__.date = '2024-02-28T12:27:32.683Z';
51721
+ __info__.hash = '90c74d1';