@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.
@@ -1691,6 +1691,9 @@ function parseFormat(formatString) {
1691
1691
  function formatValue(value, format) {
1692
1692
  switch (typeof value) {
1693
1693
  case "string":
1694
+ if (value.includes('\\"')) {
1695
+ return value.replace(/\\"/g, '"');
1696
+ }
1694
1697
  return value;
1695
1698
  case "boolean":
1696
1699
  return value ? "TRUE" : "FALSE";
@@ -6271,14 +6274,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6271
6274
  : undefined));
6272
6275
  }
6273
6276
  }
6274
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6275
- // A single cell. If it's only the title, the dataset is not added.
6276
- if (!dataSetsHaveTitle) {
6277
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6278
- }
6279
- }
6280
6277
  else {
6281
- /* 1 row or 1 column */
6278
+ /* 1 cell, 1 row or 1 column */
6282
6279
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6283
6280
  ? {
6284
6281
  top: zone.top,
@@ -6328,8 +6325,10 @@ function toExcelDataset(getters, ds) {
6328
6325
  }
6329
6326
  const dataRange = ds.dataRange.clone({ zone: dataZone });
6330
6327
  return {
6331
- label: ds.labelCell ? getters.getRangeString(ds.labelCell, "forceSheetReference") : undefined,
6332
- range: getters.getRangeString(dataRange, "forceSheetReference"),
6328
+ label: ds.labelCell
6329
+ ? getters.getRangeString(ds.labelCell, "forceSheetReference", true)
6330
+ : undefined,
6331
+ range: getters.getRangeString(dataRange, "forceSheetReference", true),
6333
6332
  };
6334
6333
  }
6335
6334
  /**
@@ -6854,6 +6853,9 @@ class BarChart extends AbstractChart {
6854
6853
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
6855
6854
  return {
6856
6855
  ...this.getDefinition(),
6856
+ labelRange: this.labelRange
6857
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
6858
+ : undefined,
6857
6859
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6858
6860
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6859
6861
  dataSets,
@@ -7418,6 +7420,9 @@ class LineChart extends AbstractChart {
7418
7420
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7419
7421
  return {
7420
7422
  ...this.getDefinition(),
7423
+ labelRange: this.labelRange
7424
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
7425
+ : undefined,
7421
7426
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7422
7427
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7423
7428
  dataSets,
@@ -7691,6 +7696,9 @@ class PieChart extends AbstractChart {
7691
7696
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7692
7697
  return {
7693
7698
  ...this.getDefinition(),
7699
+ labelRange: this.labelRange
7700
+ ? this.getters.getRangeString(this.labelRange, this.sheetId, true)
7701
+ : undefined,
7694
7702
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7695
7703
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7696
7704
  verticalAxisPosition: "left",
@@ -9134,6 +9142,12 @@ const INSERT_LINK = (env) => {
9134
9142
  let { col, row } = env.model.getters.getActivePosition();
9135
9143
  env.model.dispatch("OPEN_CELL_POPOVER", { col, row, popoverType: "LinkEditor" });
9136
9144
  };
9145
+ const INSERT_LINK_NAME = (env) => {
9146
+ const sheetId = env.model.getters.getActiveSheetId();
9147
+ const { col, row } = env.model.getters.getActivePosition();
9148
+ const cell = env.model.getters.getEvaluatedCell({ sheetId, col, row });
9149
+ return cell && cell.link ? _lt("Edit link") : _lt("Insert link");
9150
+ };
9137
9151
  //------------------------------------------------------------------------------
9138
9152
  // Filters action
9139
9153
  //------------------------------------------------------------------------------
@@ -9271,7 +9285,7 @@ cellMenuRegistry
9271
9285
  action: DELETE_CELL_SHIFT_LEFT,
9272
9286
  })
9273
9287
  .add("insert_link", {
9274
- name: _lt("Insert link"),
9288
+ name: INSERT_LINK_NAME,
9275
9289
  separator: true,
9276
9290
  sequence: 150,
9277
9291
  action: INSERT_LINK,
@@ -19957,14 +19971,13 @@ class Composer extends Component {
19957
19971
  setup() {
19958
19972
  onMounted(() => {
19959
19973
  const el = this.composerRef.el;
19974
+ if (this.props.isDefaultFocus) {
19975
+ this.env.focusableElement.setFocusableElement(el);
19976
+ }
19960
19977
  this.contentHelper.updateEl(el);
19961
19978
  this.processContent();
19962
19979
  this.contentHelper.scrollSelectionIntoView();
19963
19980
  });
19964
- onWillUnmount(() => {
19965
- var _a, _b;
19966
- (_b = (_a = this.props).onComposerUnmounted) === null || _b === void 0 ? void 0 : _b.call(_a);
19967
- });
19968
19981
  onPatched(() => {
19969
19982
  if (!this.isKeyStillDown) {
19970
19983
  this.processContent();
@@ -19975,7 +19988,8 @@ class Composer extends Component {
19975
19988
  // Handlers
19976
19989
  // ---------------------------------------------------------------------------
19977
19990
  processArrowKeys(ev) {
19978
- if (this.env.model.getters.isSelectingForComposer()) {
19991
+ if (this.env.model.getters.isSelectingForComposer() ||
19992
+ this.env.model.getters.getEditionMode() === "inactive") {
19979
19993
  this.functionDescriptionState.showDescription = false;
19980
19994
  // Prevent the default content editable behavior which moves the cursor
19981
19995
  ev.preventDefault();
@@ -20016,21 +20030,23 @@ class Composer extends Component {
20016
20030
  var _a;
20017
20031
  ev.preventDefault();
20018
20032
  ev.stopPropagation();
20019
- if (this.autoCompleteState.showProvider) {
20020
- const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
20021
- if (autoCompleteValue) {
20022
- this.autoComplete(autoCompleteValue);
20023
- return;
20033
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
20034
+ if (this.autoCompleteState.showProvider) {
20035
+ const autoCompleteValue = (_a = this.autoCompleteState.values[this.autoCompleteState.selectedIndex]) === null || _a === void 0 ? void 0 : _a.text;
20036
+ if (autoCompleteValue) {
20037
+ this.autoComplete(autoCompleteValue);
20038
+ return;
20039
+ }
20024
20040
  }
20025
- }
20026
- else {
20027
- // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
20028
- // we can't let the model think that it is for a ref selection.
20029
- // todo: check if this can be removed someday
20030
- this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20041
+ else {
20042
+ // when completing with tab, if there is no value to complete, the active cell will be moved to the right.
20043
+ // we can't let the model think that it is for a ref selection.
20044
+ // todo: check if this can be removed someday
20045
+ this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20046
+ }
20047
+ this.env.model.dispatch("STOP_EDITION");
20031
20048
  }
20032
20049
  const direction = ev.shiftKey ? "left" : "right";
20033
- this.env.model.dispatch("STOP_EDITION");
20034
20050
  this.env.model.selection.moveAnchorCell(direction, 1);
20035
20051
  }
20036
20052
  processEnterKey(ev) {
@@ -20075,6 +20091,9 @@ class Composer extends Component {
20075
20091
  this.compositionActive = false;
20076
20092
  }
20077
20093
  onKeydown(ev) {
20094
+ if (this.env.model.getters.getEditionMode() === "inactive") {
20095
+ return;
20096
+ }
20078
20097
  let handler = this.keyMapping[ev.key];
20079
20098
  if (handler) {
20080
20099
  handler.call(this, ev);
@@ -20085,22 +20104,45 @@ class Composer extends Component {
20085
20104
  }
20086
20105
  }
20087
20106
  updateCursorIfNeeded() {
20088
- if (!this.env.model.getters.isSelectingForComposer()) {
20107
+ const moveCursor = !this.env.model.getters.isSelectingForComposer() &&
20108
+ !(this.env.model.getters.getEditionMode() === "inactive");
20109
+ if (moveCursor) {
20089
20110
  const { start, end } = this.contentHelper.getCurrentSelection();
20090
20111
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", { start, end });
20091
20112
  this.isKeyStillDown = true;
20092
20113
  }
20093
20114
  }
20115
+ onPaste(ev) {
20116
+ if (this.env.model.getters.getEditionMode() !== "inactive") {
20117
+ ev.stopPropagation();
20118
+ }
20119
+ }
20094
20120
  /*
20095
20121
  * Triggered automatically by the content-editable between the keydown and key up
20096
20122
  * */
20097
- onInput() {
20098
- if (this.props.focus === "inactive" || !this.shouldProcessInputEvents) {
20123
+ onInput(ev) {
20124
+ var _a, _b;
20125
+ if (!this.shouldProcessInputEvents) {
20126
+ return;
20127
+ }
20128
+ if (ev.inputType === "insertFromPaste" &&
20129
+ this.env.model.getters.getEditionMode() === "inactive") {
20099
20130
  return;
20100
20131
  }
20132
+ ev.stopPropagation();
20133
+ let content;
20134
+ if (this.env.model.getters.getEditionMode() === "inactive") {
20135
+ content = ev.data || "";
20136
+ }
20137
+ else {
20138
+ content = this.contentHelper.getText();
20139
+ }
20140
+ if (this.props.focus === "inactive") {
20141
+ return (_b = (_a = this.props).onComposerCellFocused) === null || _b === void 0 ? void 0 : _b.call(_a, content);
20142
+ }
20101
20143
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20102
20144
  this.env.model.dispatch("SET_CURRENT_CONTENT", {
20103
- content: this.contentHelper.getText(),
20145
+ content,
20104
20146
  selection: this.contentHelper.getCurrentSelection(),
20105
20147
  });
20106
20148
  }
@@ -20171,9 +20213,8 @@ class Composer extends Component {
20171
20213
  }
20172
20214
  const newSelection = this.contentHelper.getCurrentSelection();
20173
20215
  this.env.model.dispatch("STOP_COMPOSER_RANGE_SELECTION");
20174
- if (this.props.focus === "inactive") {
20175
- this.props.onComposerContentFocused(newSelection);
20176
- }
20216
+ this.props.onComposerContentFocused();
20217
+ if (this.props.focus === "inactive") ;
20177
20218
  this.env.model.dispatch("CHANGE_COMPOSER_CURSOR_SELECTION", newSelection);
20178
20219
  this.processTokenAtCursor();
20179
20220
  }
@@ -20397,14 +20438,16 @@ Composer.template = "o-spreadsheet-Composer";
20397
20438
  Composer.components = { TextValueProvider, FunctionDescriptionProvider };
20398
20439
  Composer.defaultProps = {
20399
20440
  inputStyle: "",
20441
+ isDefaultFocus: false,
20400
20442
  };
20401
20443
  Composer.props = {
20402
- focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20403
- onComposerContentFocused: Function,
20404
20444
  inputStyle: { type: String, optional: true },
20405
20445
  rect: { type: Object, optional: true },
20406
20446
  delimitation: { type: Object, optional: true },
20407
- onComposerUnmounted: { type: Function, optional: true },
20447
+ focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20448
+ onComposerCellFocused: { type: Function, optional: true },
20449
+ onComposerContentFocused: Function,
20450
+ isDefaultFocus: { type: Boolean, optional: true },
20408
20451
  };
20409
20452
 
20410
20453
  const COMPOSER_BORDER_WIDTH = 3 * 0.4 * window.devicePixelRatio || 1;
@@ -20436,46 +20479,28 @@ css /* scss */ `
20436
20479
  * It also applies the style of the cell to the composer input.
20437
20480
  */
20438
20481
  class GridComposer extends Component {
20482
+ constructor() {
20483
+ super(...arguments);
20484
+ // TODORAR see if we can keep it undefined
20485
+ this.rect = this.defaultRect;
20486
+ this.isEditing = false;
20487
+ }
20488
+ get defaultRect() {
20489
+ return { x: 0, y: 0, width: 0, height: 0 };
20490
+ }
20439
20491
  setup() {
20440
- this.gridComposerRef = useRef("gridComposer");
20441
- this.composerState = useState({
20442
- rect: undefined,
20443
- delimitation: undefined,
20444
- });
20445
- const { sheetId, col, row } = this.env.model.getters.getActivePosition();
20446
- this.zone = this.env.model.getters.expandZone(sheetId, positionToZone({ col, row }));
20447
- this.rect = this.env.model.getters.getVisibleRect(this.zone);
20448
- this.isCellReferenceVisible = false;
20449
- onMounted(() => {
20450
- const el = this.gridComposerRef.el;
20451
- this.composerState.rect = {
20452
- x: this.rect.x,
20453
- y: this.rect.y,
20454
- width: el.clientWidth,
20455
- height: el.clientHeight,
20456
- };
20457
- this.composerState.delimitation = {
20458
- width: el.parentElement.clientWidth,
20459
- height: el.parentElement.clientHeight,
20460
- };
20461
- });
20462
20492
  onWillUpdateProps(() => {
20463
- if (this.isCellReferenceVisible) {
20464
- return;
20465
- }
20466
- const sheetId = this.env.model.getters.getActiveSheetId();
20467
- const zone = this.env.model.getters.getSelectedZone();
20468
- const rect = this.env.model.getters.getVisibleRect(zone);
20469
- if (!deepEquals(rect, this.rect) ||
20470
- sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
20471
- this.isCellReferenceVisible = true;
20472
- }
20493
+ this.updateComponentPosition();
20494
+ this.updateCellReferenceVisibility();
20473
20495
  });
20474
20496
  }
20475
20497
  get shouldDisplayCellReference() {
20476
20498
  return this.isCellReferenceVisible;
20477
20499
  }
20478
20500
  get cellReference() {
20501
+ if (!this.env.model.getters.getCurrentEditedCell()) {
20502
+ return "";
20503
+ }
20479
20504
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20480
20505
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20481
20506
  return `${prefixSheet ? getComposerSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
@@ -20487,7 +20512,27 @@ class GridComposer extends Component {
20487
20512
  top: `${top - GRID_CELL_REFERENCE_TOP_OFFSET}px`,
20488
20513
  });
20489
20514
  }
20515
+ get composerProps() {
20516
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
20517
+ return {
20518
+ rect: { ...this.rect },
20519
+ delimitation: {
20520
+ width,
20521
+ height,
20522
+ },
20523
+ focus: this.props.focus,
20524
+ isDefaultFocus: true,
20525
+ onComposerContentFocused: this.props.onComposerContentFocused,
20526
+ onComposerCellFocused: this.props.onComposerCellFocused,
20527
+ };
20528
+ }
20490
20529
  get containerStyle() {
20530
+ if (this.env.model.getters.getEditionMode() === "inactive" || !this.rect) {
20531
+ return `
20532
+ position: absolute;
20533
+ z-index: -1000;
20534
+ `;
20535
+ }
20491
20536
  const isFormula = this.env.model.getters.getCurrentContent().startsWith("=");
20492
20537
  const cell = this.env.model.getters.getActiveCell();
20493
20538
  const position = this.env.model.getters.getActivePosition();
@@ -20507,6 +20552,8 @@ class GridComposer extends Component {
20507
20552
  if (!isFormula) {
20508
20553
  textAlign = style.align || cell.defaultAlign;
20509
20554
  }
20555
+ const maxHeight = this.props.gridDims.height - this.rect.y;
20556
+ const maxWidth = this.props.gridDims.width - this.rect.x;
20510
20557
  /**
20511
20558
  * min-size is on the container, not the composer element, because we want to have the same size as the cell by default,
20512
20559
  * including all the paddings/margins of the composer
@@ -20518,6 +20565,8 @@ class GridComposer extends Component {
20518
20565
  top: `${top}px`,
20519
20566
  "min-width": `${width + 1}px`,
20520
20567
  "min-height": `${height + 1}px`,
20568
+ "max-width": `${maxWidth}px`,
20569
+ "max-height": `${maxHeight}px`,
20521
20570
  background,
20522
20571
  color,
20523
20572
  "font-size": `${fontSizeInPixels(fontSize)}px`,
@@ -20527,22 +20576,40 @@ class GridComposer extends Component {
20527
20576
  "text-align": textAlign,
20528
20577
  });
20529
20578
  }
20530
- get composerStyle() {
20531
- const maxHeight = this.props.gridDims.height - this.rect.y;
20532
- const maxWidth = this.props.gridDims.width - this.rect.x;
20533
- return cssPropertiesToCss({
20534
- "max-width": `${maxWidth}px`,
20535
- "max-height": `${maxHeight}px`,
20536
- });
20579
+ updateComponentPosition() {
20580
+ const isEditing = this.env.model.getters.getEditionMode() !== "inactive";
20581
+ if (this.isEditing !== isEditing) {
20582
+ this.isEditing = isEditing;
20583
+ if (!isEditing) {
20584
+ this.rect = this.defaultRect;
20585
+ this.env.focusableElement.focus();
20586
+ return;
20587
+ }
20588
+ const position = this.env.model.getters.getActivePosition();
20589
+ const zone = this.env.model.getters.expandZone(position.sheetId, positionToZone(position));
20590
+ this.rect = this.env.model.getters.getVisibleRect(zone);
20591
+ }
20592
+ }
20593
+ updateCellReferenceVisibility() {
20594
+ if (this.isCellReferenceVisible || this.env.model.getters.getEditionMode() === "inactive") {
20595
+ return;
20596
+ }
20597
+ const sheetId = this.env.model.getters.getActiveSheetId();
20598
+ const zone = this.env.model.getters.getSelectedZone();
20599
+ const rect = this.env.model.getters.getVisibleRect(zone);
20600
+ if (!deepEquals(rect, this.rect) ||
20601
+ sheetId !== this.env.model.getters.getCurrentEditedCell().sheetId) {
20602
+ this.isCellReferenceVisible = true;
20603
+ }
20537
20604
  }
20538
20605
  }
20539
20606
  GridComposer.template = "o-spreadsheet-GridComposer";
20540
20607
  GridComposer.components = { Composer };
20541
20608
  GridComposer.props = {
20542
20609
  focus: { validate: (value) => ["inactive", "cellFocus", "contentFocus"].includes(value) },
20543
- onComposerUnmounted: Function,
20544
20610
  onComposerContentFocused: Function,
20545
20611
  gridDims: Object,
20612
+ onComposerCellFocused: Function,
20546
20613
  };
20547
20614
 
20548
20615
  const CSS$1 = css /* scss */ `
@@ -21133,7 +21200,6 @@ class GridOverlay extends Component {
21133
21200
  this.props.onCellDoubleClicked(col, row);
21134
21201
  }
21135
21202
  onContextMenu(ev) {
21136
- ev.preventDefault();
21137
21203
  const [col, row] = this.getCartesianCoordinates(ev);
21138
21204
  this.props.onCellRightClicked(col, row, { x: ev.clientX, y: ev.clientY });
21139
21205
  }
@@ -22360,17 +22426,16 @@ class Grid extends Component {
22360
22426
  menuItems: [],
22361
22427
  });
22362
22428
  this.gridRef = useRef("grid");
22363
- this.hiddenInput = useRef("hiddenInput");
22364
22429
  this.canvasPosition = useAbsoluteBoundingRect(this.gridRef);
22365
22430
  this.hoveredCell = useState({ col: undefined, row: undefined });
22366
22431
  useChildSubEnv({ getPopoverContainerRect: () => this.getGridRect() });
22367
22432
  useExternalListener(document.body, "cut", this.copy.bind(this, true));
22368
22433
  useExternalListener(document.body, "copy", this.copy.bind(this, false));
22369
22434
  useExternalListener(document.body, "paste", this.paste);
22370
- onMounted(() => this.focus());
22371
- this.props.exposeFocus(() => this.focus());
22435
+ onMounted(() => this.focusDefaultElement());
22436
+ this.props.exposeFocus(() => this.focusDefaultElement());
22372
22437
  useGridDrawing("canvas", this.env.model, () => this.env.model.getters.getSheetViewDimensionWithHeaders());
22373
- useEffect(() => this.focus(), () => [this.env.model.getters.getActiveSheetId()]);
22438
+ useEffect(() => this.focusDefaultElement(), () => [this.env.model.getters.getActiveSheetId()]);
22374
22439
  this.onMouseWheel = useWheelHandler((deltaX, deltaY) => {
22375
22440
  this.moveCanvas(deltaX, deltaY);
22376
22441
  this.hoveredCell.col = undefined;
@@ -22393,13 +22458,12 @@ class Grid extends Component {
22393
22458
  if (this.env.model.getters.hasOpenedPopover()) {
22394
22459
  this.closeOpenedPopover();
22395
22460
  }
22396
- this.focus();
22461
+ this.focusDefaultElement();
22397
22462
  }
22398
- focus() {
22399
- var _a;
22463
+ focusDefaultElement() {
22400
22464
  if (!this.env.model.getters.getSelectedFigureId() &&
22401
22465
  this.env.model.getters.getEditionMode() === "inactive") {
22402
- (_a = this.hiddenInput.el) === null || _a === void 0 ? void 0 : _a.focus();
22466
+ this.env.focusableElement.focus();
22403
22467
  }
22404
22468
  }
22405
22469
  get gridEl() {
@@ -22544,19 +22608,6 @@ class Grid extends Component {
22544
22608
  return;
22545
22609
  }
22546
22610
  }
22547
- onInput(ev) {
22548
- // the user meant to paste in the sheet, not open the composer with the pasted content
22549
- if (!ev.isComposing && ev.inputType === "insertFromPaste") {
22550
- return;
22551
- }
22552
- if (ev.data) {
22553
- // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22554
- // character
22555
- ev.preventDefault();
22556
- ev.stopPropagation();
22557
- this.props.onGridComposerCellFocused(ev.data);
22558
- }
22559
- }
22560
22611
  // ---------------------------------------------------------------------------
22561
22612
  // Context Menu
22562
22613
  // ---------------------------------------------------------------------------
@@ -22653,7 +22704,7 @@ class Grid extends Component {
22653
22704
  }
22654
22705
  closeMenu() {
22655
22706
  this.menuState.isOpen = false;
22656
- this.focus();
22707
+ this.focusDefaultElement();
22657
22708
  }
22658
22709
  }
22659
22710
  Grid.template = "o-spreadsheet-Grid";
@@ -27005,7 +27056,7 @@ function normalizeV9(formula) {
27005
27056
  * a breaking change is made in the way the state is handled, and an upgrade
27006
27057
  * function should be defined
27007
27058
  */
27008
- const CURRENT_VERSION = 12;
27059
+ const CURRENT_VERSION = 12.5;
27009
27060
  const INITIAL_SHEET_ID = "Sheet1";
27010
27061
  /**
27011
27062
  * This function tries to load anything that could look like a valid
@@ -27271,6 +27322,31 @@ const MIGRATIONS = [
27271
27322
  return data;
27272
27323
  },
27273
27324
  },
27325
+ {
27326
+ description: "Fix datafilter duplication",
27327
+ from: 12,
27328
+ to: 12.5,
27329
+ applyMigration(data) {
27330
+ for (let sheet of data.sheets || []) {
27331
+ let knownDataFilterZones = [];
27332
+ for (let filterTable of sheet.filterTables || []) {
27333
+ const zone = toZone(filterTable.range);
27334
+ // See commit message for the details
27335
+ const intersectZoneIndex = knownDataFilterZones.findIndex((knownZone) => overlap(knownZone, zone));
27336
+ if (intersectZoneIndex !== -1) {
27337
+ knownDataFilterZones[intersectZoneIndex] = zone;
27338
+ }
27339
+ else {
27340
+ knownDataFilterZones.push(zone);
27341
+ }
27342
+ }
27343
+ sheet.filterTables = knownDataFilterZones.map((zone) => ({
27344
+ range: zoneToXc(zone),
27345
+ }));
27346
+ }
27347
+ return data;
27348
+ },
27349
+ },
27274
27350
  ];
27275
27351
  /**
27276
27352
  * This function is used to repair faulty data independently of the migration.
@@ -28123,9 +28199,9 @@ class CellPlugin extends CorePlugin {
28123
28199
  allowDispatch(cmd) {
28124
28200
  switch (cmd.type) {
28125
28201
  case "UPDATE_CELL":
28126
- return this.checkCellOutOfSheet(cmd);
28202
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessUpdateCell);
28127
28203
  case "CLEAR_CELL":
28128
- return this.checkValidations(cmd, this.chainValidations(this.checkCellOutOfSheet, this.checkUselessClearCell));
28204
+ return this.checkValidations(cmd, this.checkCellOutOfSheet, this.checkUselessClearCell);
28129
28205
  default:
28130
28206
  return 0 /* CommandResult.Success */;
28131
28207
  }
@@ -28291,13 +28367,13 @@ class CellPlugin extends CorePlugin {
28291
28367
  /*
28292
28368
  * Reconstructs the original formula string based on a normalized form and its dependencies
28293
28369
  */
28294
- buildFormulaContent(sheetId, cell, dependencies) {
28370
+ buildFormulaContent(sheetId, cell, dependencies, useFixedZone = false) {
28295
28371
  const ranges = dependencies || cell.dependencies;
28296
28372
  let rangeIndex = 0;
28297
28373
  return concat(cell.compiledFormula.tokens.map((token) => {
28298
28374
  if (token.type === "REFERENCE") {
28299
28375
  const range = ranges[rangeIndex++];
28300
- return this.getters.getRangeString(range, sheetId);
28376
+ return this.getters.getRangeString(range, sheetId, useFixedZone);
28301
28377
  }
28302
28378
  return token.value;
28303
28379
  }));
@@ -28420,7 +28496,7 @@ class CellPlugin extends CorePlugin {
28420
28496
  else {
28421
28497
  style = before ? before.style : undefined;
28422
28498
  }
28423
- let format = ("format" in after ? after.format : before && before.format) || detectFormat(afterContent);
28499
+ const format = "format" in after ? after.format : before && before.format;
28424
28500
  /* Read the following IF as:
28425
28501
  * we need to remove the cell if it is completely empty, but we can know if it completely empty if:
28426
28502
  * - the command says the new content is empty and has no border/format/style
@@ -28464,7 +28540,7 @@ class CellPlugin extends CorePlugin {
28464
28540
  id,
28465
28541
  content,
28466
28542
  style,
28467
- format,
28543
+ format: format || detectFormat(content),
28468
28544
  isFormula: false,
28469
28545
  };
28470
28546
  }
@@ -28541,6 +28617,18 @@ class CellPlugin extends CorePlugin {
28541
28617
  }
28542
28618
  return 0 /* CommandResult.Success */;
28543
28619
  }
28620
+ checkUselessUpdateCell(cmd) {
28621
+ const cell = this.getters.getCell(cmd);
28622
+ const hasContent = "content" in cmd || "formula" in cmd;
28623
+ const hasStyle = "style" in cmd;
28624
+ const hasFormat = "format" in cmd;
28625
+ if ((!hasContent || (cell === null || cell === void 0 ? void 0 : cell.content) === cmd.content) &&
28626
+ (!hasStyle || deepEquals(cell === null || cell === void 0 ? void 0 : cell.style, cmd.style)) &&
28627
+ (!hasFormat || (cell === null || cell === void 0 ? void 0 : cell.format) === cmd.format)) {
28628
+ return 89 /* CommandResult.NoChanges */;
28629
+ }
28630
+ return 0 /* CommandResult.Success */;
28631
+ }
28544
28632
  }
28545
28633
  CellPlugin.getters = [
28546
28634
  "zoneToXC",
@@ -28867,7 +28955,13 @@ class ConditionalFormatPlugin extends CorePlugin {
28867
28955
  }
28868
28956
  }
28869
28957
  exportForExcel(data) {
28870
- this.export(data);
28958
+ if (data.sheets) {
28959
+ for (let sheet of data.sheets) {
28960
+ if (this.cfRules[sheet.id]) {
28961
+ sheet.conditionalFormats = this.cfRules[sheet.id].map((rule) => this.mapToConditionalFormat(sheet.id, rule, { useFixedReference: true }));
28962
+ }
28963
+ }
28964
+ }
28871
28965
  }
28872
28966
  // ---------------------------------------------------------------------------
28873
28967
  // Getters
@@ -28917,11 +29011,11 @@ class ConditionalFormatPlugin extends CorePlugin {
28917
29011
  // ---------------------------------------------------------------------------
28918
29012
  // Private
28919
29013
  // ---------------------------------------------------------------------------
28920
- mapToConditionalFormat(sheetId, cf) {
29014
+ mapToConditionalFormat(sheetId, cf, options = { useFixedReference: false }) {
28921
29015
  return {
28922
29016
  ...cf,
28923
29017
  ranges: cf.ranges.map((range) => {
28924
- return this.getters.getRangeString(range, sheetId);
29018
+ return this.getters.getRangeString(range, sheetId, options.useFixedReference);
28925
29019
  }),
28926
29020
  };
28927
29021
  }
@@ -30850,8 +30944,9 @@ class RangeAdapter {
30850
30944
  *
30851
30945
  * @param range the range (received from getRangeFromXC or getRangeFromZone)
30852
30946
  * @param forSheetId the id of the sheet where the range string is supposed to be used.
30947
+ * @param [useFixedZone=false] if true, the range will be returned with fixed row and column
30853
30948
  */
30854
- getRangeString(range, forSheetId) {
30949
+ getRangeString(range, forSheetId, useFixedZone = false) {
30855
30950
  if (!range) {
30856
30951
  return INCORRECT_RANGE_STRING;
30857
30952
  }
@@ -30878,7 +30973,7 @@ class RangeAdapter {
30878
30973
  if (prefixSheet && !sheetName) {
30879
30974
  return INCORRECT_RANGE_STRING;
30880
30975
  }
30881
- let rangeString = this.getRangePartString(rangeImpl, 0);
30976
+ let rangeString = this.getRangePartString(rangeImpl, 0, useFixedZone);
30882
30977
  if (rangeImpl.parts && rangeImpl.parts.length === 2) {
30883
30978
  // this if converts A2:A2 into A2 except if any part of the original range had fixed row or column (with $)
30884
30979
  if (rangeImpl.zone.top !== rangeImpl.zone.bottom ||
@@ -30888,7 +30983,7 @@ class RangeAdapter {
30888
30983
  rangeImpl.parts[1].rowFixed ||
30889
30984
  rangeImpl.parts[1].colFixed) {
30890
30985
  rangeString += ":";
30891
- rangeString += this.getRangePartString(rangeImpl, 1);
30986
+ rangeString += this.getRangePartString(rangeImpl, 1, useFixedZone);
30892
30987
  }
30893
30988
  }
30894
30989
  return `${prefixSheet ? sheetName + "!" : ""}${rangeString}`;
@@ -30918,14 +31013,14 @@ class RangeAdapter {
30918
31013
  /**
30919
31014
  * Get a Xc string that represent a part of a range
30920
31015
  */
30921
- getRangePartString(range, part) {
31016
+ getRangePartString(range, part, useFixedZone = false) {
30922
31017
  var _a, _b;
30923
31018
  const colFixed = range.parts && ((_a = range.parts[part]) === null || _a === void 0 ? void 0 : _a.colFixed) ? "$" : "";
30924
31019
  const col = part === 0 ? numberToLetters(range.zone.left) : numberToLetters(range.zone.right);
30925
31020
  const rowFixed = range.parts && ((_b = range.parts[part]) === null || _b === void 0 ? void 0 : _b.rowFixed) ? "$" : "";
30926
31021
  const row = part === 0 ? String(range.zone.top + 1) : String(range.zone.bottom + 1);
30927
31022
  let str = "";
30928
- if (range.isFullCol) {
31023
+ if (range.isFullCol && !useFixedZone) {
30929
31024
  if (part === 0 && range.unboundedZone.hasHeader) {
30930
31025
  str = colFixed + col + rowFixed + row;
30931
31026
  }
@@ -30933,7 +31028,7 @@ class RangeAdapter {
30933
31028
  str = colFixed + col;
30934
31029
  }
30935
31030
  }
30936
- else if (range.isFullRow) {
31031
+ else if (range.isFullRow && !useFixedZone) {
30937
31032
  if (part === 0 && range.unboundedZone.hasHeader) {
30938
31033
  str = colFixed + col + rowFixed + row;
30939
31034
  }
@@ -32322,6 +32417,10 @@ class EvaluationPlugin extends UIPlugin {
32322
32417
  if (cell.format !== evaluatedCell.format) {
32323
32418
  exportedCellData.computedFormat = evaluatedCell.format;
32324
32419
  }
32420
+ // if there is a formula but no dependencies (maybe because the cell is in error), no need to recompute the formula text
32421
+ if (cell.isFormula && cell.dependencies.length) {
32422
+ exportedCellData.content = this.getters.buildFormulaContent(sheet.id, cell, cell.dependencies, true);
32423
+ }
32325
32424
  }
32326
32425
  }
32327
32426
  }
@@ -34184,8 +34283,7 @@ class AutofillPlugin extends UIPlugin {
34184
34283
  let row = zone.bottom;
34185
34284
  if (col > 0) {
34186
34285
  let leftPosition = { sheetId, col: col - 1, row };
34187
- while (this.getters.getEvaluatedCell(leftPosition).type !== CellValueType.empty ||
34188
- ((_a = this.getters.getCell(leftPosition)) === null || _a === void 0 ? void 0 : _a.content)) {
34286
+ while ((_a = this.getters.getCell(leftPosition)) === null || _a === void 0 ? void 0 : _a.content) {
34189
34287
  row += 1;
34190
34288
  leftPosition = { sheetId, col: col - 1, row };
34191
34289
  }
@@ -34194,8 +34292,7 @@ class AutofillPlugin extends UIPlugin {
34194
34292
  col = zone.right;
34195
34293
  if (col <= this.getters.getNumberCols(sheetId)) {
34196
34294
  let rightPosition = { sheetId, col: col + 1, row };
34197
- while (this.getters.getEvaluatedCell(rightPosition).type !== CellValueType.empty ||
34198
- ((_b = this.getters.getCell(rightPosition)) === null || _b === void 0 ? void 0 : _b.content)) {
34295
+ while ((_b = this.getters.getCell(rightPosition)) === null || _b === void 0 ? void 0 : _b.content) {
34199
34296
  row += 1;
34200
34297
  rightPosition = { sheetId, col: col + 1, row };
34201
34298
  }
@@ -39913,6 +40010,19 @@ class ImageProvider {
39913
40010
  }
39914
40011
  }
39915
40012
 
40013
+ class FocusableElement {
40014
+ constructor() {
40015
+ this.focusableElement = undefined;
40016
+ }
40017
+ setFocusableElement(element) {
40018
+ this.focusableElement = element;
40019
+ }
40020
+ focus() {
40021
+ var _a;
40022
+ (_a = this.focusableElement) === null || _a === void 0 ? void 0 : _a.focus();
40023
+ }
40024
+ }
40025
+
39916
40026
  const RIPPLE_KEY_FRAMES = [
39917
40027
  { transform: "scale(0)" },
39918
40028
  { transform: "scale(0.8)", offset: 0.33 },
@@ -41888,6 +41998,7 @@ class Spreadsheet extends Component {
41888
41998
  _t: Spreadsheet._t,
41889
41999
  clipboard: this.env.clipboard || instantiateClipboard(),
41890
42000
  startCellEdition: (content) => this.onGridComposerCellFocused(content),
42001
+ focusableElement: new FocusableElement(),
41891
42002
  });
41892
42003
  useExternalListener(window, "resize", () => this.render(true));
41893
42004
  useExternalListener(window, "beforeunload", this.unbindModelEvents.bind(this));
@@ -41986,17 +42097,18 @@ class Spreadsheet extends Component {
41986
42097
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
41987
42098
  this.composer.topBarFocus = "contentFocus";
41988
42099
  this.composer.gridFocusMode = "inactive";
41989
- this.setComposerContent({ selection } || {});
42100
+ this.setComposerContent({ selection });
41990
42101
  }
41991
- onGridComposerContentFocused() {
42102
+ onGridComposerContentFocused(selection) {
41992
42103
  if (this.model.getters.isReadonly()) {
41993
42104
  return;
41994
42105
  }
41995
42106
  this.model.dispatch("UNFOCUS_SELECTION_INPUT");
41996
42107
  this.composer.topBarFocus = "inactive";
41997
42108
  this.composer.gridFocusMode = "contentFocus";
41998
- this.setComposerContent({});
42109
+ this.setComposerContent({ selection });
41999
42110
  }
42111
+ // TODO: either both are defined or none of them. change those args to an object
42000
42112
  onGridComposerCellFocused(content, selection) {
42001
42113
  if (this.model.getters.isReadonly()) {
42002
42114
  return;
@@ -43458,11 +43570,10 @@ class SelectionStreamProcessorImpl {
43458
43570
  * next cluster if the given cell is outside a cluster or at the border of a cluster in the given direction.
43459
43571
  */
43460
43572
  getEndOfCluster(startPosition, dim, dir) {
43461
- const sheet = this.getters.getActiveSheet();
43462
43573
  let currentPosition = startPosition;
43463
43574
  // If both the current cell and the next cell are not empty, we want to go to the end of the cluster
43464
43575
  const nextCellPosition = this.getNextCellPosition(startPosition, dim, dir);
43465
- let mode = !this.isCellEmpty(currentPosition, sheet.id) && !this.isCellEmpty(nextCellPosition, sheet.id)
43576
+ let mode = !this.isEvaluatedCellEmpty(currentPosition) && !this.isEvaluatedCellEmpty(nextCellPosition)
43466
43577
  ? "endOfCluster"
43467
43578
  : "nextCluster";
43468
43579
  while (true) {
@@ -43472,7 +43583,7 @@ class SelectionStreamProcessorImpl {
43472
43583
  currentPosition.row === nextCellPosition.row) {
43473
43584
  break;
43474
43585
  }
43475
- const isNextCellEmpty = this.isCellEmpty(nextCellPosition, sheet.id);
43586
+ const isNextCellEmpty = this.isEvaluatedCellEmpty(nextCellPosition);
43476
43587
  if (mode === "endOfCluster" && isNextCellEmpty) {
43477
43588
  break;
43478
43589
  }
@@ -43486,14 +43597,25 @@ class SelectionStreamProcessorImpl {
43486
43597
  return dim === "cols" ? currentPosition.col : currentPosition.row;
43487
43598
  }
43488
43599
  /**
43489
- * Check if a cell is empty or undefined in the model. If the cell is part of a merge,
43490
- * check if the merge containing the cell is empty.
43600
+ * Check if a cell evaluated value is empty. If the cell is part of a merge,
43601
+ * the check applies to the main cell of the merge.
43491
43602
  */
43492
- isCellEmpty({ col, row }, sheetId = this.getters.getActiveSheetId()) {
43603
+ isEvaluatedCellEmpty({ col, row }) {
43604
+ const sheetId = this.getters.getActiveSheetId();
43493
43605
  const position = this.getters.getMainCellPosition({ sheetId, col, row });
43494
43606
  const cell = this.getters.getEvaluatedCell(position);
43495
43607
  return cell.type === CellValueType.empty;
43496
43608
  }
43609
+ /**
43610
+ * Checks if a cell is empty (i.e. does not have a content). If the cell is part of a merge,
43611
+ * the check applies to the main cell of the merge.
43612
+ */
43613
+ isCellEmpty({ col, row }) {
43614
+ var _a;
43615
+ const sheetId = this.getters.getActiveSheetId();
43616
+ const position = this.getters.getMainCellPosition({ sheetId, col, row });
43617
+ return !((_a = this.getters.getCell(position)) === null || _a === void 0 ? void 0 : _a.content);
43618
+ }
43497
43619
  /** Computes the next cell position in the given direction by crossing through merges and skipping hidden cells.
43498
43620
  *
43499
43621
  * This has the same behaviour as getNextAvailablePosition() for certain arguments, but use this method instead
@@ -43972,12 +44094,7 @@ function addFormula(cell) {
43972
44094
  attrs.push(["t", "str"]);
43973
44095
  cycle = escapeXml /*xml*/ `<v>${cell.value}</v>`;
43974
44096
  }
43975
- node = escapeXml /*xml*/ `
43976
- <f>
43977
- ${XlsxFormula}
43978
- </f>
43979
- ${cycle}
43980
- `;
44097
+ node = escapeXml /*xml*/ `<f> ${XlsxFormula} </f> ${cycle}`;
43981
44098
  return { attrs, node };
43982
44099
  }
43983
44100
  else {
@@ -44773,11 +44890,10 @@ function addRows(construct, data, sheet) {
44773
44890
  ({ attrs: additionalAttrs, node: cellNode } = addContent(cell.content, construct.sharedStrings, isTableHeader));
44774
44891
  }
44775
44892
  attributes.push(...additionalAttrs);
44776
- cellNodes.push(escapeXml /*xml*/ `
44777
- <c ${formatAttributes(attributes)}>
44778
- ${cellNode}
44779
- </c>
44780
- `);
44893
+ // prettier-ignore
44894
+ cellNodes.push(escapeXml /*xml*/ `<c ${formatAttributes(attributes)}>
44895
+ ${cellNode}
44896
+ </c>`);
44781
44897
  }
44782
44898
  }
44783
44899
  if (cellNodes.length || row.size !== DEFAULT_CELL_HEIGHT || row.isHidden) {
@@ -45643,6 +45759,6 @@ function addFunction(functionName, functionDescription) {
45643
45759
  export { AbstractChart, CommandResult, CorePlugin, DATETIME_FORMAT, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setTranslationMethod, tokenize };
45644
45760
 
45645
45761
 
45646
- __info__.version = '16.2.29';
45647
- __info__.date = '2024-01-29T13:49:36.240Z';
45648
- __info__.hash = '27b2ec1';
45762
+ __info__.version = '16.2.32';
45763
+ __info__.date = '2024-02-16T14:17:18.471Z';
45764
+ __info__.hash = 'd5e57e6';