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