@odoo/o-spreadsheet 16.2.29 → 16.2.32

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.
@@ -1695,6 +1695,9 @@ function parseFormat(formatString) {
1695
1695
  function formatValue(value, format) {
1696
1696
  switch (typeof value) {
1697
1697
  case "string":
1698
+ if (value.includes('\\"')) {
1699
+ return value.replace(/\\"/g, '"');
1700
+ }
1698
1701
  return value;
1699
1702
  case "boolean":
1700
1703
  return value ? "TRUE" : "FALSE";
@@ -6275,14 +6278,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6275
6278
  : undefined));
6276
6279
  }
6277
6280
  }
6278
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6279
- // A single cell. If it's only the title, the dataset is not added.
6280
- if (!dataSetsHaveTitle) {
6281
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6282
- }
6283
- }
6284
6281
  else {
6285
- /* 1 row or 1 column */
6282
+ /* 1 cell, 1 row or 1 column */
6286
6283
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6287
6284
  ? {
6288
6285
  top: zone.top,
@@ -6332,8 +6329,10 @@ function toExcelDataset(getters, ds) {
6332
6329
  }
6333
6330
  const dataRange = ds.dataRange.clone({ zone: dataZone });
6334
6331
  return {
6335
- label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined,
6336
- range: getters.getRangeString(dataRange, "forceSheetReference"),
6332
+ label: ds.labelCell
6333
+ ? getters.getRangeString(ds.labelCell, "forceSheetReference", true)
6334
+ : undefined,
6335
+ range: getters.getRangeString(dataRange, "forceSheetReference", true),
6337
6336
  };
6338
6337
  }
6339
6338
  /**
@@ -6858,6 +6857,9 @@ class BarChart extends AbstractChart {
6858
6857
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
6859
6858
  return {
6860
6859
  ...this.getDefinition(),
6860
+ labelRange: this.labelRange
6861
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
6862
+ : undefined,
6861
6863
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6862
6864
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6863
6865
  dataSets,
@@ -7422,6 +7424,9 @@ class LineChart extends AbstractChart {
7422
7424
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7423
7425
  return {
7424
7426
  ...this.getDefinition(),
7427
+ labelRange: this.labelRange
7428
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
7429
+ : undefined,
7425
7430
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7426
7431
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7427
7432
  dataSets,
@@ -7695,6 +7700,9 @@ class PieChart extends AbstractChart {
7695
7700
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7696
7701
  return {
7697
7702
  ...this.getDefinition(),
7703
+ labelRange: this.labelRange
7704
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
7705
+ : undefined,
7698
7706
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7699
7707
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7700
7708
  verticalAxisPosition: "left",
@@ -9138,6 +9146,12 @@ const INSERT_LINK = (env) => {
9138
9146
  let { col, row } = env.model.getters.getActivePosition();
9139
9147
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
9140
9148
  };
9149
+ const INSERT_LINK_NAME = (env) => {
9150
+ const sheetId = env.model.getters.getActiveSheetId();
9151
+ const { col, row } = env.model.getters.getActivePosition();
9152
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
9153
+ return cell && cell.link ? _lt("Edit link") : _lt("Insert link");
9154
+ };
9141
9155
  //------------------------------------------------------------------------------
9142
9156
  // Filters action
9143
9157
  //------------------------------------------------------------------------------
@@ -9275,7 +9289,7 @@ cellMenuRegistry
9275
9289
  action: DELETE_CELL_SHIFT_LEFT,
9276
9290
  })
9277
9291
  .add("insert_link", {
9278
- name: _lt("Insert link"),
9292
+ name: INSERT_LINK_NAME,
9279
9293
  separator: true,
9280
9294
  sequence: 150,
9281
9295
  action: INSERT_LINK,
@@ -19961,14 +19975,13 @@ class Composer extends owl.Component {
19961
19975
  setup() {
19962
19976
  owl.onMounted(() => {
19963
19977
  const el = this.composerRef.el;
19978
+ if (this.props.isDefaultFocus) {
19979
+ this.env.focusableElement.setFocusableElement(el);
19980
+ }
19964
19981
  this.contentHelper.updateEl(el);
19965
19982
  this.processContent();
19966
19983
  this.contentHelper.scrollSelectionIntoView();
19967
19984
  });
19968
- owl.onWillUnmount(() => {
19969
- var _a, _b;
19970
- (_b = (_a = this.props).onComposerUnmounted) === null || _b === void 0 ? void 0 : _b.call(_a);
19971
- });
19972
19985
  owl.onPatched(() => {
19973
19986
  if (!this.isKeyStillDown) {
19974
19987
  this.processContent();
@@ -19979,7 +19992,8 @@ class Composer extends owl.Component {
19979
19992
  // Handlers
19980
19993
  // ---------------------------------------------------------------------------
19981
19994
  processArrowKeys(ev) {
19982
- if (this.env.model.getters.isSelectingForComposer()) {
19995
+ if (this.env.model.getters.isSelectingForComposer() ||
19996
+ this.env.model.getters.getEditionMode() === "inactive") {
19983
19997
  this.functionDescriptionState.showDescription = false;
19984
19998
  // Prevent the default content editable behavior which moves the cursor
19985
19999
  ev.preventDefault();
@@ -20020,21 +20034,23 @@ class Composer extends owl.Component {
20020
20034
  var _a;
20021
20035
  ev.preventDefault();
20022
20036
  ev.stopPropagation();
20023
- if (this.autoCompleteState.showProvider) {
20024
- const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
20025
- if (autoCompleteValue) {
20026
- this.autoComplete(autoCompleteValue);
20027
- return;
20037
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
20038
+ if (this.autoCompleteState.showProvider) {
20039
+ const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
20040
+ if (autoCompleteValue) {
20041
+ this.autoComplete(autoCompleteValue);
20042
+ return;
20043
+ }
20028
20044
  }
20029
- }
20030
- else {
20031
- // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
20032
- // we can't let the model think that it is for a ref selection.
20033
- // todo: check if this can be removed someday
20034
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20045
+ else {
20046
+ // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
20047
+ // we can't let the model think that it is for a ref selection.
20048
+ // todo: check if this can be removed someday
20049
+ this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20050
+ }
20051
+ this.env.model.dispatch("STOP_EDITION");
20035
20052
  }
20036
20053
  const direction = ev.shiftKey ? "left" : "right";
20037
- this.env.model.dispatch("STOP_EDITION");
20038
20054
  this.env.model.selection.moveAnchorCell(direction, 1);
20039
20055
  }
20040
20056
  processEnterKey(ev) {
@@ -20079,6 +20095,9 @@ class Composer extends owl.Component {
20079
20095
  this.compositionActive = false;
20080
20096
  }
20081
20097
  onKeydown(ev) {
20098
+ if (this.env.model.getters.getEditionMode() === "inactive") {
20099
+ return;
20100
+ }
20082
20101
  let handler = this.keyMapping[ev.key];
20083
20102
  if (handler) {
20084
20103
  handler.call(this, ev);
@@ -20089,22 +20108,45 @@ class Composer extends owl.Component {
20089
20108
  }
20090
20109
  }
20091
20110
  updateCursorIfNeeded() {
20092
- if (!this.env.model.getters.isSelectingForComposer()) {
20111
+ const moveCursor = !this.env.model.getters.isSelectingForComposer() &&
20112
+ !(this.env.model.getters.getEditionMode() === "inactive");
20113
+ if (moveCursor) {
20093
20114
  const { start, end } = this.contentHelper.getCurrentSelection();
20094
20115
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
20095
20116
  this.isKeyStillDown = true;
20096
20117
  }
20097
20118
  }
20119
+ onPaste(ev) {
20120
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
20121
+ ev.stopPropagation();
20122
+ }
20123
+ }
20098
20124
  /*
20099
20125
  * Triggered automatically by the content-editable between the keydown and key up
20100
20126
  * */
20101
- onInput() {
20102
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
20127
+ onInput(ev) {
20128
+ var _a, _b;
20129
+ if (!this.shouldProcessInputEvents) {
20130
+ return;
20131
+ }
20132
+ if (ev.inputType === "insertFromPaste" &&
20133
+ this.env.model.getters.getEditionMode() === "inactive") {
20103
20134
  return;
20104
20135
  }
20136
+ ev.stopPropagation();
20137
+ let content;
20138
+ if (this.env.model.getters.getEditionMode() === "inactive") {
20139
+ content = ev.data || "";
20140
+ }
20141
+ else {
20142
+ content = this.contentHelper.getText();
20143
+ }
20144
+ if (this.props.focus === "inactive") {
20145
+ return (_b = (_a = this.props).onComposerCellFocused) === null || _b === void 0 ? void 0 : _b.call(_a, content);
20146
+ }
20105
20147
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20106
20148
  this.env.model.dispatch("SET_CURRENT_CONTENT", {
20107
- content: this.contentHelper.getText(),
20149
+ content,
20108
20150
  selection: this.contentHelper.getCurrentSelection(),
20109
20151
  });
20110
20152
  }
@@ -20175,9 +20217,8 @@ class Composer extends owl.Component {
20175
20217
  }
20176
20218
  const newSelection = this.contentHelper.getCurrentSelection();
20177
20219
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20178
- if (this.props.focus === "inactive") {
20179
- this.props.onComposerContentFocused(newSelection);
20180
- }
20220
+ this.props.onComposerContentFocused();
20221
+ if (this.props.focus === "inactive") ;
20181
20222
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
20182
20223
  this.processTokenAtCursor();
20183
20224
  }
@@ -20401,14 +20442,16 @@ Composer.template = "o-spreadsheet-Composer";
20401
20442
  Composer.components = { TextValueProvider, FunctionDescriptionProvider };
20402
20443
  Composer.defaultProps = {
20403
20444
  inputStyle: "",
20445
+ isDefaultFocus: false,
20404
20446
  };
20405
20447
  Composer.props = {
20406
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20407
- onComposerContentFocused: Function,
20408
20448
  inputStyle: { type: String, optional: true },
20409
20449
  rect: { type: Object, optional: true },
20410
20450
  delimitation: { type: Object, optional: true },
20411
- onComposerUnmounted: { type: Function, optional: true },
20451
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20452
+ onComposerCellFocused: { type: Function, optional: true },
20453
+ onComposerContentFocused: Function,
20454
+ isDefaultFocus: { type: Boolean, optional: true },
20412
20455
  };
20413
20456
 
20414
20457
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -20440,46 +20483,28 @@ css /* scss */ `
20440
20483
  * It also applies the style of the cell to the composer input.
20441
20484
  */
20442
20485
  class GridComposer extends owl.Component {
20486
+ constructor() {
20487
+ super(...arguments);
20488
+ // TODORAR see if we can keep it undefined
20489
+ this.rect = this.defaultRect;
20490
+ this.isEditing = false;
20491
+ }
20492
+ get defaultRect() {
20493
+ return { x: 0, y: 0, width: 0, height: 0 };
20494
+ }
20443
20495
  setup() {
20444
- this.gridComposerRef = owl.useRef("gridComposer");
20445
- this.composerState = owl.useState({
20446
- rect: undefined,
20447
- delimitation: undefined,
20448
- });
20449
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
20450
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
20451
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
20452
- this.isCellReferenceVisible = false;
20453
- owl.onMounted(() => {
20454
- const el = this.gridComposerRef.el;
20455
- this.composerState.rect = {
20456
- x: this.rect.x,
20457
- y: this.rect.y,
20458
- width: el.clientWidth,
20459
- height: el.clientHeight,
20460
- };
20461
- this.composerState.delimitation = {
20462
- width: el.parentElement.clientWidth,
20463
- height: el.parentElement.clientHeight,
20464
- };
20465
- });
20466
20496
  owl.onWillUpdateProps(() => {
20467
- if (this.isCellReferenceVisible) {
20468
- return;
20469
- }
20470
- const sheetId = this.env.model.getters.getActiveSheetId();
20471
- const zone = this.env.model.getters.getSelectedZone();
20472
- const rect = this.env.model.getters.getVisibleRect(zone);
20473
- if (!deepEquals(rect, this.rect) ||
20474
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
20475
- this.isCellReferenceVisible = true;
20476
- }
20497
+ this.updateComponentPosition();
20498
+ this.updateCellReferenceVisibility();
20477
20499
  });
20478
20500
  }
20479
20501
  get shouldDisplayCellReference() {
20480
20502
  return this.isCellReferenceVisible;
20481
20503
  }
20482
20504
  get cellReference() {
20505
+ if (!this.env.model.getters.getCurrentEditedCell()) {
20506
+ return "";
20507
+ }
20483
20508
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20484
20509
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20485
20510
  return `${prefixSheet ? getComposerSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -20491,7 +20516,27 @@ class GridComposer extends owl.Component {
20491
20516
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
20492
20517
  });
20493
20518
  }
20519
+ get composerProps() {
20520
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
20521
+ return {
20522
+ rect: { ...this.rect },
20523
+ delimitation: {
20524
+ width,
20525
+ height,
20526
+ },
20527
+ focus: this.props.focus,
20528
+ isDefaultFocus: true,
20529
+ onComposerContentFocused: this.props.onComposerContentFocused,
20530
+ onComposerCellFocused: this.props.onComposerCellFocused,
20531
+ };
20532
+ }
20494
20533
  get containerStyle() {
20534
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
20535
+ return `
20536
+ position: absolute;
20537
+ z-index: -1000;
20538
+ `;
20539
+ }
20495
20540
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
20496
20541
  const cell = this.env.model.getters.getActiveCell();
20497
20542
  const position = this.env.model.getters.getActivePosition();
@@ -20511,6 +20556,8 @@ class GridComposer extends owl.Component {
20511
20556
  if (!isFormula) {
20512
20557
  textAlign = style.align || cell.defaultAlign;
20513
20558
  }
20559
+ const maxHeight = this.props.gridDims.height - this.rect.y;
20560
+ const maxWidth = this.props.gridDims.width - this.rect.x;
20514
20561
  /**
20515
20562
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
20516
20563
  * including all the paddings/margins of the composer
@@ -20522,6 +20569,8 @@ class GridComposer extends owl.Component {
20522
20569
  top: `${top}px`,
20523
20570
  "min-width": `${width + 1}px`,
20524
20571
  "min-height": `${height + 1}px`,
20572
+ "max-width": `${maxWidth}px`,
20573
+ "max-height": `${maxHeight}px`,
20525
20574
  background,
20526
20575
  color,
20527
20576
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -20531,22 +20580,40 @@ class GridComposer extends owl.Component {
20531
20580
  "text-align": textAlign,
20532
20581
  });
20533
20582
  }
20534
- get composerStyle() {
20535
- const maxHeight = this.props.gridDims.height - this.rect.y;
20536
- const maxWidth = this.props.gridDims.width - this.rect.x;
20537
- return cssPropertiesToCss({
20538
- "max-width": `${maxWidth}px`,
20539
- "max-height": `${maxHeight}px`,
20540
- });
20583
+ updateComponentPosition() {
20584
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
20585
+ if (this.isEditing !== isEditing) {
20586
+ this.isEditing = isEditing;
20587
+ if (!isEditing) {
20588
+ this.rect = this.defaultRect;
20589
+ this.env.focusableElement.focus();
20590
+ return;
20591
+ }
20592
+ const position = this.env.model.getters.getActivePosition();
20593
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
20594
+ this.rect = this.env.model.getters.getVisibleRect(zone);
20595
+ }
20596
+ }
20597
+ updateCellReferenceVisibility() {
20598
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
20599
+ return;
20600
+ }
20601
+ const sheetId = this.env.model.getters.getActiveSheetId();
20602
+ const zone = this.env.model.getters.getSelectedZone();
20603
+ const rect = this.env.model.getters.getVisibleRect(zone);
20604
+ if (!deepEquals(rect, this.rect) ||
20605
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
20606
+ this.isCellReferenceVisible = true;
20607
+ }
20541
20608
  }
20542
20609
  }
20543
20610
  GridComposer.template = "o-spreadsheet-GridComposer";
20544
20611
  GridComposer.components = { Composer };
20545
20612
  GridComposer.props = {
20546
20613
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20547
- onComposerUnmounted: Function,
20548
20614
  onComposerContentFocused: Function,
20549
20615
  gridDims: Object,
20616
+ onComposerCellFocused: Function,
20550
20617
  };
20551
20618
 
20552
20619
  const CSS$1 = css /* scss */ `
@@ -21137,7 +21204,6 @@ class GridOverlay extends owl.Component {
21137
21204
  this.props.onCellDoubleClicked(col, row);
21138
21205
  }
21139
21206
  onContextMenu(ev) {
21140
- ev.preventDefault();
21141
21207
  const [col, row] = this.getCartesianCoordinates(ev);
21142
21208
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
21143
21209
  }
@@ -22364,17 +22430,16 @@ class Grid extends owl.Component {
22364
22430
  menuItems: [],
22365
22431
  });
22366
22432
  this.gridRef = owl.useRef("grid");
22367
- this.hiddenInput = owl.useRef("hiddenInput");
22368
22433
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
22369
22434
  this.hoveredCell = owl.useState({ col: undefined, row: undefined });
22370
22435
  owl.useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
22371
22436
  owl.useExternalListener(document.body, "cut", this.copy.bind(this, true));
22372
22437
  owl.useExternalListener(document.body, "copy", this.copy.bind(this, false));
22373
22438
  owl.useExternalListener(document.body, "paste", this.paste);
22374
- owl.onMounted(() => this.focus());
22375
- this.props.exposeFocus(() => this.focus());
22439
+ owl.onMounted(() => this.focusDefaultElement());
22440
+ this.props.exposeFocus(() => this.focusDefaultElement());
22376
22441
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
22377
- owl.useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
22442
+ owl.useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
22378
22443
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
22379
22444
  this.moveCanvas(deltaX, deltaY);
22380
22445
  this.hoveredCell.col = undefined;
@@ -22397,13 +22462,12 @@ class Grid extends owl.Component {
22397
22462
  if (this.env.model.getters.hasOpenedPopover()) {
22398
22463
  this.closeOpenedPopover();
22399
22464
  }
22400
- this.focus();
22465
+ this.focusDefaultElement();
22401
22466
  }
22402
- focus() {
22403
- var _a;
22467
+ focusDefaultElement() {
22404
22468
  if (!this.env.model.getters.getSelectedFigureId() &&
22405
22469
  this.env.model.getters.getEditionMode() === "inactive") {
22406
- (_a = this.hiddenInput.el) === null || _a === void 0 ? void 0 : _a.focus();
22470
+ this.env.focusableElement.focus();
22407
22471
  }
22408
22472
  }
22409
22473
  get gridEl() {
@@ -22548,19 +22612,6 @@ class Grid extends owl.Component {
22548
22612
  return;
22549
22613
  }
22550
22614
  }
22551
- onInput(ev) {
22552
- // the user meant to paste in the sheet, not open the composer with the pasted content
22553
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
22554
- return;
22555
- }
22556
- if (ev.data) {
22557
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22558
- // character
22559
- ev.preventDefault();
22560
- ev.stopPropagation();
22561
- this.props.onGridComposerCellFocused(ev.data);
22562
- }
22563
- }
22564
22615
  // ---------------------------------------------------------------------------
22565
22616
  // Context Menu
22566
22617
  // ---------------------------------------------------------------------------
@@ -22657,7 +22708,7 @@ class Grid extends owl.Component {
22657
22708
  }
22658
22709
  closeMenu() {
22659
22710
  this.menuState.isOpen = false;
22660
- this.focus();
22711
+ this.focusDefaultElement();
22661
22712
  }
22662
22713
  }
22663
22714
  Grid.template = "o-spreadsheet-Grid";
@@ -27009,7 +27060,7 @@ function normalizeV9(formula) {
27009
27060
  * a breaking change is made in the way the state is handled, and an upgrade
27010
27061
  * function should be defined
27011
27062
  */
27012
- const CURRENT_VERSION = 12;
27063
+ const CURRENT_VERSION = 12.5;
27013
27064
  const INITIAL_SHEET_ID = "Sheet1";
27014
27065
  /**
27015
27066
  * This function tries to load anything that could look like a valid
@@ -27275,6 +27326,31 @@ const MIGRATIONS = [
27275
27326
  return data;
27276
27327
  },
27277
27328
  },
27329
+ {
27330
+ description: "Fix datafilter duplication",
27331
+ from: 12,
27332
+ to: 12.5,
27333
+ applyMigration(data) {
27334
+ for (let sheet of data.sheets || []) {
27335
+ let knownDataFilterZones = [];
27336
+ for (let filterTable of sheet.filterTables || []) {
27337
+ const zone = toZone(filterTable.range);
27338
+ // See commit message for the details
27339
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
27340
+ if (intersectZoneIndex !== -1) {
27341
+ knownDataFilterZones[intersectZoneIndex] = zone;
27342
+ }
27343
+ else {
27344
+ knownDataFilterZones.push(zone);
27345
+ }
27346
+ }
27347
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
27348
+ range: zoneToXc(zone),
27349
+ }));
27350
+ }
27351
+ return data;
27352
+ },
27353
+ },
27278
27354
  ];
27279
27355
  /**
27280
27356
  * This function is used to repair faulty data independently of the migration.
@@ -28127,9 +28203,9 @@ class CellPlugin extends CorePlugin {
28127
28203
  allowDispatch(cmd) {
28128
28204
  switch (cmd.type) {
28129
28205
  case "UPDATE_CELL":
28130
- return this.checkCellOutOfSheet(cmd);
28206
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
28131
28207
  case "CLEAR_CELL":
28132
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
28208
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
28133
28209
  default:
28134
28210
  return 0 /* CommandResult.Success */;
28135
28211
  }
@@ -28295,13 +28371,13 @@ class CellPlugin extends CorePlugin {
28295
28371
  /*
28296
28372
  * Reconstructs the original formula string based on a normalized form and its dependencies
28297
28373
  */
28298
- buildFormulaContent(sheetId, cell, dependencies) {
28374
+ buildFormulaContent(sheetId, cell, dependencies, useFixedZone = false) {
28299
28375
  const ranges = dependencies || cell.dependencies;
28300
28376
  let rangeIndex = 0;
28301
28377
  return concat(cell.compiledFormula.tokens.map((token) => {
28302
28378
  if (token.type === "REFERENCE") {
28303
28379
  const range = ranges[rangeIndex++];
28304
- return this.getters.getRangeString(range, sheetId);
28380
+ return this.getters.getRangeString(range, sheetId, useFixedZone);
28305
28381
  }
28306
28382
  return token.value;
28307
28383
  }));
@@ -28424,7 +28500,7 @@ class CellPlugin extends CorePlugin {
28424
28500
  else {
28425
28501
  style = before ? before.style : undefined;
28426
28502
  }
28427
- let format = ("format" in after ? after.format : before && before.format) || detectFormat(afterContent);
28503
+ const format = "format" in after ? after.format : before && before.format;
28428
28504
  /* Read the following IF as:
28429
28505
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
28430
28506
  * - the command says the new content is empty and has no border/format/style
@@ -28468,7 +28544,7 @@ class CellPlugin extends CorePlugin {
28468
28544
  id,
28469
28545
  content,
28470
28546
  style,
28471
- format,
28547
+ format: format || detectFormat(content),
28472
28548
  isFormula: false,
28473
28549
  };
28474
28550
  }
@@ -28545,6 +28621,18 @@ class CellPlugin extends CorePlugin {
28545
28621
  }
28546
28622
  return 0 /* CommandResult.Success */;
28547
28623
  }
28624
+ checkUselessUpdateCell(cmd) {
28625
+ const cell = this.getters.getCell(cmd);
28626
+ const hasContent = "content" in cmd || "formula" in cmd;
28627
+ const hasStyle = "style" in cmd;
28628
+ const hasFormat = "format" in cmd;
28629
+ if ((!hasContent || (cell === null || cell === void 0 ? void 0 : cell.content) === cmd.content) &&
28630
+ (!hasStyle || deepEquals(cell === null || cell === void 0 ? void 0 : cell.style, cmd.style)) &&
28631
+ (!hasFormat || (cell === null || cell === void 0 ? void 0 : cell.format) === cmd.format)) {
28632
+ return 89 /* CommandResult.NoChanges */;
28633
+ }
28634
+ return 0 /* CommandResult.Success */;
28635
+ }
28548
28636
  }
28549
28637
  CellPlugin.getters = [
28550
28638
  "zoneToXC",
@@ -28871,7 +28959,13 @@ class ConditionalFormatPlugin extends CorePlugin {
28871
28959
  }
28872
28960
  }
28873
28961
  exportForExcel(data) {
28874
- this.export(data);
28962
+ if (data.sheets) {
28963
+ for (let sheet of data.sheets) {
28964
+ if (this.cfRules[sheet.id]) {
28965
+ sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useFixedReference: true }));
28966
+ }
28967
+ }
28968
+ }
28875
28969
  }
28876
28970
  // ---------------------------------------------------------------------------
28877
28971
  // Getters
@@ -28921,11 +29015,11 @@ class ConditionalFormatPlugin extends CorePlugin {
28921
29015
  // ---------------------------------------------------------------------------
28922
29016
  // Private
28923
29017
  // ---------------------------------------------------------------------------
28924
- mapToConditionalFormat(sheetId, cf) {
29018
+ mapToConditionalFormat(sheetId, cf, options = { useFixedReference: false }) {
28925
29019
  return {
28926
29020
  ...cf,
28927
29021
  ranges: cf.ranges.map((range) => {
28928
- return this.getters.getRangeString(range, sheetId);
29022
+ return this.getters.getRangeString(range, sheetId, options.useFixedReference);
28929
29023
  }),
28930
29024
  };
28931
29025
  }
@@ -30854,8 +30948,9 @@ class RangeAdapter {
30854
30948
  *
30855
30949
  * @param range the range (received from getRangeFromXC or getRangeFromZone)
30856
30950
  * @param forSheetId the id of the sheet where the range string is supposed to be used.
30951
+ * @param [useFixedZone=false] if true, the range will be returned with fixed row and column
30857
30952
  */
30858
- getRangeString(range, forSheetId) {
30953
+ getRangeString(range, forSheetId, useFixedZone = false) {
30859
30954
  if (!range) {
30860
30955
  return INCORRECT_RANGE_STRING;
30861
30956
  }
@@ -30882,7 +30977,7 @@ class RangeAdapter {
30882
30977
  if (prefixSheet && !sheetName) {
30883
30978
  return INCORRECT_RANGE_STRING;
30884
30979
  }
30885
- let rangeString = this.getRangePartString(rangeImpl, 0);
30980
+ let rangeString = this.getRangePartString(rangeImpl, 0, useFixedZone);
30886
30981
  if (rangeImpl.parts && rangeImpl.parts.length === 2) {
30887
30982
  // this if converts A2:A2 into A2 except if any part of the original range had fixed row or column (with $)
30888
30983
  if (rangeImpl.zone.top !== rangeImpl.zone.bottom ||
@@ -30892,7 +30987,7 @@ class RangeAdapter {
30892
30987
  rangeImpl.parts[1].rowFixed ||
30893
30988
  rangeImpl.parts[1].colFixed) {
30894
30989
  rangeString += ":";
30895
- rangeString += this.getRangePartString(rangeImpl, 1);
30990
+ rangeString += this.getRangePartString(rangeImpl, 1, useFixedZone);
30896
30991
  }
30897
30992
  }
30898
30993
  return `${prefixSheet ? sheetName + "!" : ""}${rangeString}`;
@@ -30922,14 +31017,14 @@ class RangeAdapter {
30922
31017
  /**
30923
31018
  * Get a Xc string that represent a part of a range
30924
31019
  */
30925
- getRangePartString(range, part) {
31020
+ getRangePartString(range, part, useFixedZone = false) {
30926
31021
  var _a, _b;
30927
31022
  const colFixed = range.parts && ((_a = range.parts[part]) === null || _a === void 0 ? void 0 : _a.colFixed) ? "$" : "";
30928
31023
  const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);
30929
31024
  const rowFixed = range.parts && ((_b = range.parts[part]) === null || _b === void 0 ? void 0 : _b.rowFixed) ? "$" : "";
30930
31025
  const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);
30931
31026
  let str = "";
30932
- if (range.isFullCol) {
31027
+ if (range.isFullCol && !useFixedZone) {
30933
31028
  if (part === 0 && range.unboundedZone.hasHeader) {
30934
31029
  str = colFixed + col + rowFixed + row;
30935
31030
  }
@@ -30937,7 +31032,7 @@ class RangeAdapter {
30937
31032
  str = colFixed + col;
30938
31033
  }
30939
31034
  }
30940
- else if (range.isFullRow) {
31035
+ else if (range.isFullRow && !useFixedZone) {
30941
31036
  if (part === 0 && range.unboundedZone.hasHeader) {
30942
31037
  str = colFixed + col + rowFixed + row;
30943
31038
  }
@@ -32326,6 +32421,10 @@ class EvaluationPlugin extends UIPlugin {
32326
32421
  if (cell.format !== evaluatedCell.format) {
32327
32422
  exportedCellData.computedFormat = evaluatedCell.format;
32328
32423
  }
32424
+ // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
32425
+ if (cell.isFormula && cell.dependencies.length) {
32426
+ exportedCellData.content = this.getters.buildFormulaContent(sheet.id, cell, cell.dependencies, true);
32427
+ }
32329
32428
  }
32330
32429
  }
32331
32430
  }
@@ -34188,8 +34287,7 @@ class AutofillPlugin extends UIPlugin {
34188
34287
  let row = zone.bottom;
34189
34288
  if (col > 0) {
34190
34289
  let leftPosition = { sheetId, col: col - 1, row };
34191
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
34192
- ((_a = this.getters.getCell(leftPosition)) === null || _a === void 0 ? void 0 : _a.content)) {
34290
+ while ((_a = this.getters.getCell(leftPosition)) === null || _a === void 0 ? void 0 : _a.content) {
34193
34291
  row += 1;
34194
34292
  leftPosition = { sheetId, col: col - 1, row };
34195
34293
  }
@@ -34198,8 +34296,7 @@ class AutofillPlugin extends UIPlugin {
34198
34296
  col = zone.right;
34199
34297
  if (col <= this.getters.getNumberCols(sheetId)) {
34200
34298
  let rightPosition = { sheetId, col: col + 1, row };
34201
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
34202
- ((_b = this.getters.getCell(rightPosition)) === null || _b === void 0 ? void 0 : _b.content)) {
34299
+ while ((_b = this.getters.getCell(rightPosition)) === null || _b === void 0 ? void 0 : _b.content) {
34203
34300
  row += 1;
34204
34301
  rightPosition = { sheetId, col: col + 1, row };
34205
34302
  }
@@ -39917,6 +40014,19 @@ class ImageProvider {
39917
40014
  }
39918
40015
  }
39919
40016
 
40017
+ class FocusableElement {
40018
+ constructor() {
40019
+ this.focusableElement = undefined;
40020
+ }
40021
+ setFocusableElement(element) {
40022
+ this.focusableElement = element;
40023
+ }
40024
+ focus() {
40025
+ var _a;
40026
+ (_a = this.focusableElement) === null || _a === void 0 ? void 0 : _a.focus();
40027
+ }
40028
+ }
40029
+
39920
40030
  const RIPPLE_KEY_FRAMES = [
39921
40031
  { transform: "scale(0)" },
39922
40032
  { transform: "scale(0.8)", offset: 0.33 },
@@ -41892,6 +42002,7 @@ class Spreadsheet extends owl.Component {
41892
42002
  _t: Spreadsheet._t,
41893
42003
  clipboard: this.env.clipboard || instantiateClipboard(),
41894
42004
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
42005
+ focusableElement: new FocusableElement(),
41895
42006
  });
41896
42007
  owl.useExternalListener(window, "resize", () => this.render(true));
41897
42008
  owl.useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -41990,17 +42101,18 @@ class Spreadsheet extends owl.Component {
41990
42101
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
41991
42102
  this.composer.topBarFocus = "contentFocus";
41992
42103
  this.composer.gridFocusMode = "inactive";
41993
- this.setComposerContent({ selection } || {});
42104
+ this.setComposerContent({ selection });
41994
42105
  }
41995
- onGridComposerContentFocused() {
42106
+ onGridComposerContentFocused(selection) {
41996
42107
  if (this.model.getters.isReadonly()) {
41997
42108
  return;
41998
42109
  }
41999
42110
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
42000
42111
  this.composer.topBarFocus = "inactive";
42001
42112
  this.composer.gridFocusMode = "contentFocus";
42002
- this.setComposerContent({});
42113
+ this.setComposerContent({ selection });
42003
42114
  }
42115
+ // TODO: either both are defined or none of them. change those args to an object
42004
42116
  onGridComposerCellFocused(content, selection) {
42005
42117
  if (this.model.getters.isReadonly()) {
42006
42118
  return;
@@ -43462,11 +43574,10 @@ class SelectionStreamProcessorImpl {
43462
43574
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
43463
43575
  */
43464
43576
  getEndOfCluster(startPosition, dim, dir) {
43465
- const sheet = this.getters.getActiveSheet();
43466
43577
  let currentPosition = startPosition;
43467
43578
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
43468
43579
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
43469
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
43580
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
43470
43581
  ? "endOfCluster"
43471
43582
  : "nextCluster";
43472
43583
  while (true) {
@@ -43476,7 +43587,7 @@ class SelectionStreamProcessorImpl {
43476
43587
  currentPosition.row === nextCellPosition.row) {
43477
43588
  break;
43478
43589
  }
43479
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
43590
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
43480
43591
  if (mode === "endOfCluster" && isNextCellEmpty) {
43481
43592
  break;
43482
43593
  }
@@ -43490,14 +43601,25 @@ class SelectionStreamProcessorImpl {
43490
43601
  return dim === "cols" ? currentPosition.col : currentPosition.row;
43491
43602
  }
43492
43603
  /**
43493
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
43494
- * check if the merge containing the cell is empty.
43604
+ * Check if a cell evaluated value is empty. If the cell is part of a merge,
43605
+ * the check applies to the main cell of the merge.
43495
43606
  */
43496
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
43607
+ isEvaluatedCellEmpty({ col, row }) {
43608
+ const sheetId = this.getters.getActiveSheetId();
43497
43609
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
43498
43610
  const cell = this.getters.getEvaluatedCell(position);
43499
43611
  return cell.type === CellValueType.empty;
43500
43612
  }
43613
+ /**
43614
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
43615
+ * the check applies to the main cell of the merge.
43616
+ */
43617
+ isCellEmpty({ col, row }) {
43618
+ var _a;
43619
+ const sheetId = this.getters.getActiveSheetId();
43620
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
43621
+ return !((_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.content);
43622
+ }
43501
43623
  /** Computes the next cell position in the given direction by crossing through merges and skipping hidden cells.
43502
43624
  *
43503
43625
  * This has the same behaviour as getNextAvailablePosition() for certain arguments, but use this method instead
@@ -43976,12 +44098,7 @@ function addFormula(cell) {
43976
44098
  attrs.push(["t", "str"]);
43977
44099
  cycle = escapeXml /*xml*/ `<v>${cell.value}</v>`;
43978
44100
  }
43979
- node = escapeXml /*xml*/ `
43980
- <f>
43981
- ${XlsxFormula}
43982
- </f>
43983
- ${cycle}
43984
- `;
44101
+ node = escapeXml /*xml*/ `<f> ${XlsxFormula} </f> ${cycle}`;
43985
44102
  return { attrs, node };
43986
44103
  }
43987
44104
  else {
@@ -44777,11 +44894,10 @@ function addRows(construct, data, sheet) {
44777
44894
  ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader));
44778
44895
  }
44779
44896
  attributes.push(...additionalAttrs);
44780
- cellNodes.push(escapeXml /*xml*/ `
44781
- <c ${formatAttributes(attributes)}>
44782
- ${cellNode}
44783
- </c>
44784
- `);
44897
+ // prettier-ignore
44898
+ cellNodes.push(escapeXml /*xml*/ `<c ${formatAttributes(attributes)}>
44899
+ ${cellNode}
44900
+ </c>`);
44785
44901
  }
44786
44902
  }
44787
44903
  if (cellNodes.length || row.size !== DEFAULT_CELL_HEIGHT || row.isHidden) {
@@ -45675,6 +45791,6 @@ exports.setTranslationMethod = setTranslationMethod;
45675
45791
  exports.tokenize = tokenize;
45676
45792
 
45677
45793
 
45678
- __info__.version = '16.2.29';
45679
- __info__.date = '2024-01-29T13:49:36.240Z';
45680
- __info__.hash = '27b2ec1';
45794
+ __info__.version = '16.2.32';
45795
+ __info__.date = '2024-02-16T14:17:18.471Z';
45796
+ __info__.hash = 'd5e57e6';