@odoo/o-spreadsheet 16.3.0-alpha.7 → 16.3.0

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.
@@ -237,8 +237,8 @@
237
237
  const DEFAULT_FONT = "'Roboto', arial";
238
238
  const DEFAULT_VERTICAL_ALIGN = "bottom";
239
239
  // Borders
240
- const DEFAULT_BORDER_DESC = ["thin", "#000"];
241
- const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
240
+ const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
241
+ const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
242
242
  // Ranges
243
243
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
244
244
  // Max Number of history steps kept in memory
@@ -1102,6 +1102,18 @@
1102
1102
  text = text.replace(whiteSpaceRegexp, " ");
1103
1103
  text = text.replace(newLineRegex, NEWLINE);
1104
1104
  return text;
1105
+ }
1106
+ /**
1107
+ * Determine if the numbers are consecutive.
1108
+ */
1109
+ function isConsecutive(iterable) {
1110
+ const array = Array.from(iterable).sort((a, b) => a - b); // sort numerically rather than lexicographically
1111
+ for (let i = 1; i < array.length; i++) {
1112
+ if (array[i] - array[i - 1] !== 1) {
1113
+ return false;
1114
+ }
1115
+ }
1116
+ return true;
1105
1117
  }
1106
1118
 
1107
1119
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -4244,6 +4256,7 @@
4244
4256
  "SET_FORMATTING",
4245
4257
  "CLEAR_FORMATTING",
4246
4258
  "SET_BORDER",
4259
+ "SET_ZONE_BORDERS",
4247
4260
  /** CHART */
4248
4261
  "CREATE_CHART",
4249
4262
  "UPDATE_CHART",
@@ -4384,8 +4397,10 @@
4384
4397
  CommandResult[CommandResult["EmptySplitSeparator"] = 89] = "EmptySplitSeparator";
4385
4398
  CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
4386
4399
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4400
+ CommandResult[CommandResult["NoActiveSheet"] = 92] = "NoActiveSheet";
4387
4401
  })(exports.CommandResult || (exports.CommandResult = {}));
4388
4402
 
4403
+ const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
4389
4404
  var DIRECTION;
4390
4405
  (function (DIRECTION) {
4391
4406
  DIRECTION["UP"] = "up";
@@ -5479,12 +5494,6 @@
5479
5494
  cursor: pointer;
5480
5495
  user-select: none;
5481
5496
 
5482
- .o-menu-item-name {
5483
- overflow: hidden;
5484
- white-space: nowrap;
5485
- text-overflow: ellipsis;
5486
- }
5487
-
5488
5497
  &.o-menu-root {
5489
5498
  display: flex;
5490
5499
  justify-content: space-between;
@@ -5495,10 +5504,6 @@
5495
5504
  margin: 0px 8px 0px 0px;
5496
5505
  width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5497
5506
  line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5498
-
5499
- .o-icon {
5500
- color: ${ICONS_COLOR};
5501
- }
5502
5507
  }
5503
5508
  .o-menu-item-root {
5504
5509
  width: 10px;
@@ -5512,6 +5517,11 @@
5512
5517
  .o-menu-item-description {
5513
5518
  color: grey;
5514
5519
  }
5520
+ .o-menu-item-icon {
5521
+ .o-icon {
5522
+ color: ${ICONS_COLOR};
5523
+ }
5524
+ }
5515
5525
  }
5516
5526
  &.disabled {
5517
5527
  color: ${MENU_ITEM_DISABLED_COLOR};
@@ -6328,8 +6338,14 @@
6328
6338
  : undefined));
6329
6339
  }
6330
6340
  }
6341
+ else if (zone.left === zone.right && zone.top === zone.bottom) {
6342
+ // A single cell. If it's only the title, the dataset is not added.
6343
+ if (!dataSetsHaveTitle) {
6344
+ dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6345
+ }
6346
+ }
6331
6347
  else {
6332
- /* 1 cell, 1 row or 1 column */
6348
+ /* 1 row or 1 column */
6333
6349
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6334
6350
  ? {
6335
6351
  top: zone.top,
@@ -8940,7 +8956,7 @@
8940
8956
  handlePasteResult(env, result);
8941
8957
  };
8942
8958
  const MENU_INSERT_ROWS_NAME = (env) => {
8943
- const number = getColumnsNumber(env);
8959
+ const number = getRowsNumber(env);
8944
8960
  return number === 1 ? _lt("Insert row") : _lt("Insert %s rows", number.toString());
8945
8961
  };
8946
8962
  const MENU_INSERT_ROWS_BEFORE_NAME = (env) => {
@@ -15305,7 +15321,11 @@
15305
15321
  : getNormalizedValueFromRowRange;
15306
15322
  const rangeLength = verticalSearch ? nbRow : nbCol;
15307
15323
  const index = dichotomicSearch(searchArray, _searchKey, "nextSmaller", "asc", rangeLength, getElement);
15308
- assertAvailable(searchArray[0][index], searchKey);
15324
+ if (index === -1)
15325
+ assertAvailable(undefined, searchKey);
15326
+ verticalSearch
15327
+ ? assertAvailable(searchArray[0][index], searchKey)
15328
+ : assertAvailable(searchArray[index][nbRow - 1], searchKey);
15309
15329
  if (resultRange === undefined) {
15310
15330
  return (verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1]);
15311
15331
  }
@@ -16212,75 +16232,97 @@
16212
16232
 
16213
16233
  const insertRow = {
16214
16234
  name: MENU_INSERT_ROWS_NAME,
16215
- isVisible: IS_ONLY_ONE_RANGE,
16235
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16236
+ IS_ONLY_ONE_RANGE(env) &&
16237
+ env.model.getters.getActiveCols().size === 0,
16216
16238
  icon: "o-spreadsheet-Icon.INSERT_ROW",
16217
16239
  };
16218
16240
  const rowInsertRowBefore = {
16219
16241
  name: ROW_INSERT_ROWS_BEFORE_NAME,
16220
16242
  execute: INSERT_ROWS_BEFORE_ACTION,
16243
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16244
+ IS_ONLY_ONE_RANGE(env) &&
16245
+ env.model.getters.getActiveCols().size === 0,
16246
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16221
16247
  };
16222
16248
  const topBarInsertRowsBefore = {
16223
16249
  ...rowInsertRowBefore,
16224
16250
  name: MENU_INSERT_ROWS_BEFORE_NAME,
16225
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16226
16251
  };
16227
16252
  const cellInsertRowsBefore = {
16228
16253
  ...rowInsertRowBefore,
16229
16254
  name: CELL_INSERT_ROWS_BEFORE_NAME,
16230
16255
  isVisible: IS_ONLY_ONE_RANGE,
16231
- icon: "o-spreadsheet-Icon.INSERT_ROW",
16256
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16232
16257
  };
16233
16258
  const rowInsertRowsAfter = {
16234
16259
  execute: INSERT_ROWS_AFTER_ACTION,
16235
16260
  name: ROW_INSERT_ROWS_AFTER_NAME,
16261
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16262
+ IS_ONLY_ONE_RANGE(env) &&
16263
+ env.model.getters.getActiveCols().size === 0,
16264
+ icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER",
16236
16265
  };
16237
16266
  const topBarInsertRowsAfter = {
16238
16267
  ...rowInsertRowsAfter,
16239
16268
  name: MENU_INSERT_ROWS_AFTER_NAME,
16240
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16241
16269
  };
16242
16270
  const insertCol = {
16243
16271
  name: MENU_INSERT_COLUMNS_NAME,
16244
- isVisible: IS_ONLY_ONE_RANGE,
16272
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16273
+ IS_ONLY_ONE_RANGE(env) &&
16274
+ env.model.getters.getActiveRows().size === 0,
16245
16275
  icon: "o-spreadsheet-Icon.INSERT_COL",
16246
16276
  };
16247
16277
  const colInsertColsBefore = {
16248
16278
  name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,
16249
16279
  execute: INSERT_COLUMNS_BEFORE_ACTION,
16280
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16281
+ IS_ONLY_ONE_RANGE(env) &&
16282
+ env.model.getters.getActiveRows().size === 0,
16283
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16250
16284
  };
16251
16285
  const topBarInsertColsBefore = {
16252
16286
  ...colInsertColsBefore,
16253
16287
  name: MENU_INSERT_COLUMNS_BEFORE_NAME,
16254
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16255
16288
  };
16256
16289
  const cellInsertColsBefore = {
16257
16290
  ...colInsertColsBefore,
16258
16291
  name: CELL_INSERT_COLUMNS_BEFORE_NAME,
16259
16292
  isVisible: IS_ONLY_ONE_RANGE,
16260
- icon: "o-spreadsheet-Icon.INSERT_COL",
16293
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16261
16294
  };
16262
16295
  const colInsertColsAfter = {
16263
16296
  name: COLUMN_INSERT_COLUMNS_AFTER_NAME,
16264
16297
  execute: INSERT_COLUMNS_AFTER_ACTION,
16298
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16299
+ IS_ONLY_ONE_RANGE(env) &&
16300
+ env.model.getters.getActiveRows().size === 0,
16301
+ icon: "o-spreadsheet-Icon.INSERT_COL_AFTER",
16265
16302
  };
16266
16303
  const topBarInsertColsAfter = {
16267
16304
  ...colInsertColsAfter,
16268
16305
  name: MENU_INSERT_COLUMNS_AFTER_NAME,
16269
16306
  execute: INSERT_COLUMNS_AFTER_ACTION,
16270
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16271
16307
  };
16272
16308
  const insertCell = {
16273
16309
  name: _lt("Insert cells"),
16274
- isVisible: IS_ONLY_ONE_RANGE,
16310
+ isVisible: (env) => IS_ONLY_ONE_RANGE(env) &&
16311
+ env.model.getters.getActiveCols().size === 0 &&
16312
+ env.model.getters.getActiveRows().size === 0,
16275
16313
  icon: "o-spreadsheet-Icon.INSERT_CELL",
16276
16314
  };
16277
16315
  const insertCellShiftDown = {
16278
16316
  name: _lt("Insert cells and shift down"),
16279
16317
  execute: INSERT_CELL_SHIFT_DOWN,
16318
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16319
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_DOWN",
16280
16320
  };
16281
16321
  const insertCellShiftRight = {
16282
16322
  name: _lt("Insert cells and shift right"),
16283
16323
  execute: INSERT_CELL_SHIFT_RIGHT,
16324
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16325
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_RIGHT",
16284
16326
  };
16285
16327
  const insertChart = {
16286
16328
  name: _lt("Chart"),
@@ -16478,6 +16520,7 @@
16478
16520
  sequence: 1,
16479
16521
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
16480
16522
  isEnabled: (env) => env.model.getters.isSingleColSelected(),
16523
+ icon: "o-spreadsheet-Icon.SPLIT_TEXT",
16481
16524
  };
16482
16525
 
16483
16526
  const formatNumberAutomatic = {
@@ -16693,60 +16736,6 @@
16693
16736
  execute: FORMAT_CLEARFORMAT_ACTION,
16694
16737
  icon: "o-spreadsheet-Icon.CLEAR_FORMAT",
16695
16738
  };
16696
- const borders = {
16697
- name: _lt("Borders"),
16698
- icon: "o-spreadsheet-Icon.BORDERS",
16699
- };
16700
- const bordersAll = {
16701
- name: _lt("All borders"),
16702
- execute: (env) => setBorder(env, "all"),
16703
- icon: "o-spreadsheet-Icon.BORDERS",
16704
- };
16705
- const bordersInner = {
16706
- name: _lt("Inner borders"),
16707
- execute: (env) => setBorder(env, "hv"),
16708
- icon: "o-spreadsheet-Icon.BORDER_HV",
16709
- };
16710
- const bordersHorizontal = {
16711
- name: _lt("Horizontal borders"),
16712
- execute: (env) => setBorder(env, "h"),
16713
- icon: "o-spreadsheet-Icon.BORDER_H",
16714
- };
16715
- const bordersVertical = {
16716
- name: _lt("Vertical borders"),
16717
- execute: (env) => setBorder(env, "v"),
16718
- icon: "o-spreadsheet-Icon.BORDER_V",
16719
- };
16720
- const bordersExternal = {
16721
- name: _lt("External borders"),
16722
- execute: (env) => setBorder(env, "external"),
16723
- icon: "o-spreadsheet-Icon.BORDER_EXTERNAL",
16724
- };
16725
- const bordersLeft = {
16726
- name: _lt("Left borders"),
16727
- execute: (env) => setBorder(env, "left"),
16728
- icon: "o-spreadsheet-Icon.BORDER_LEFT",
16729
- };
16730
- const bordersTop = {
16731
- name: _lt("Top borders"),
16732
- execute: (env) => setBorder(env, "top"),
16733
- icon: "o-spreadsheet-Icon.BORDER_TOP",
16734
- };
16735
- const bordersRight = {
16736
- name: _lt("Right borders"),
16737
- execute: (env) => setBorder(env, "right"),
16738
- icon: "o-spreadsheet-Icon.BORDER_RIGHT",
16739
- };
16740
- const bordersBottom = {
16741
- name: _lt("Bottom borders"),
16742
- execute: (env) => setBorder(env, "bottom"),
16743
- icon: "o-spreadsheet-Icon.BORDER_BOTTOM",
16744
- };
16745
- const bordersClear = {
16746
- name: _lt("Clear borders"),
16747
- execute: (env) => setBorder(env, "clear"),
16748
- icon: "o-spreadsheet-Icon.BORDER_CLEAR",
16749
- };
16750
16739
  function fontSizeMenuBuilder() {
16751
16740
  return FONT_SIZES.map((fs) => {
16752
16741
  return {
@@ -16759,24 +16748,17 @@
16759
16748
  });
16760
16749
  }
16761
16750
  function isAutomaticFormatSelected(env) {
16762
- const activeCell = env.model.getters.getActiveCell();
16751
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16763
16752
  return !activeCell || !activeCell.format;
16764
16753
  }
16765
16754
  function isFormatSelected(env, format) {
16766
- const activeCell = env.model.getters.getActiveCell();
16767
- return activeCell && activeCell.format === format;
16755
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16756
+ return activeCell?.format === format;
16768
16757
  }
16769
16758
  function isFontSizeSelected(env, fontSize) {
16770
16759
  const currentFontSize = env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
16771
16760
  return currentFontSize === fontSize;
16772
16761
  }
16773
- function setBorder(env, command) {
16774
- env.model.dispatch("SET_FORMATTING", {
16775
- sheetId: env.model.getters.getActiveSheetId(),
16776
- target: env.model.getters.getSelectedZones(),
16777
- border: command,
16778
- });
16779
- }
16780
16762
  function getHorizontalAlign(env) {
16781
16763
  const style = env.model.getters.getCurrentStyle();
16782
16764
  if (style.align) {
@@ -16823,18 +16805,7 @@
16823
16805
  fillColor: fillColor,
16824
16806
  formatCF: formatCF,
16825
16807
  paintFormat: paintFormat,
16826
- clearFormat: clearFormat,
16827
- borders: borders,
16828
- bordersAll: bordersAll,
16829
- bordersInner: bordersInner,
16830
- bordersHorizontal: bordersHorizontal,
16831
- bordersVertical: bordersVertical,
16832
- bordersExternal: bordersExternal,
16833
- bordersLeft: bordersLeft,
16834
- bordersTop: bordersTop,
16835
- bordersRight: bordersRight,
16836
- bordersBottom: bordersBottom,
16837
- bordersClear: bordersClear
16808
+ clearFormat: clearFormat
16838
16809
  });
16839
16810
 
16840
16811
  function interactiveFreezeColumnsRows(env, dimension, base) {
@@ -16850,6 +16821,7 @@
16850
16821
  name: HIDE_COLUMNS_NAME,
16851
16822
  execute: HIDE_COLUMNS_ACTION,
16852
16823
  isVisible: NOT_ALL_VISIBLE_COLS_SELECTED,
16824
+ icon: "o-spreadsheet-Icon.HIDE_COL",
16853
16825
  };
16854
16826
  const unhideCols = {
16855
16827
  name: _lt("Unhide columns"),
@@ -16871,6 +16843,7 @@
16871
16843
  name: HIDE_ROWS_NAME,
16872
16844
  execute: HIDE_ROWS_ACTION,
16873
16845
  isVisible: NOT_ALL_VISIBLE_ROWS_SELECTED,
16846
+ icon: "o-spreadsheet-Icon.HIDE_ROW",
16874
16847
  };
16875
16848
  const unhideRows = {
16876
16849
  name: _lt("Unhide rows"),
@@ -17074,19 +17047,21 @@
17074
17047
  .add("delete_column", {
17075
17048
  ...deleteCols,
17076
17049
  sequence: 90,
17050
+ icon: "o-spreadsheet-Icon.DELETE",
17077
17051
  })
17078
17052
  .add("clear_column", {
17079
17053
  ...clearCols,
17080
17054
  sequence: 100,
17055
+ icon: "o-spreadsheet-Icon.CLEAR",
17081
17056
  })
17082
17057
  .add("hide_columns", {
17083
17058
  ...hideCols,
17084
- sequence: 85,
17059
+ sequence: 105,
17085
17060
  separator: true,
17086
17061
  })
17087
17062
  .add("unhide_columns", {
17088
17063
  ...unhideCols,
17089
- sequence: 86,
17064
+ sequence: 106,
17090
17065
  separator: true,
17091
17066
  })
17092
17067
  .add("conditional_formatting", {
@@ -17184,10 +17159,12 @@
17184
17159
  .add("delete_row", {
17185
17160
  ...deleteRows,
17186
17161
  sequence: 70,
17162
+ icon: "o-spreadsheet-Icon.DELETE",
17187
17163
  })
17188
17164
  .add("clear_row", {
17189
17165
  ...clearRows,
17190
17166
  sequence: 80,
17167
+ icon: "o-spreadsheet-Icon.CLEAR",
17191
17168
  })
17192
17169
  .add("hide_rows", {
17193
17170
  ...hideRows,
@@ -17570,7 +17547,7 @@
17570
17547
  })
17571
17548
  .addChild("sort_range", ["data"], {
17572
17549
  ...sortRange,
17573
- sequence: 20,
17550
+ sequence: 10,
17574
17551
  separator: true,
17575
17552
  })
17576
17553
  .addChild("sort_ascending", ["data", "sort_range"], {
@@ -17583,16 +17560,16 @@
17583
17560
  })
17584
17561
  .addChild("split_to_columns", ["data"], {
17585
17562
  ...splitToColumns,
17586
- sequence: 10,
17563
+ sequence: 20,
17587
17564
  separator: true,
17588
17565
  })
17589
17566
  .addChild("add_data_filter", ["data"], {
17590
17567
  ...addDataFilter,
17591
- sequence: 10,
17568
+ sequence: 30,
17592
17569
  })
17593
17570
  .addChild("remove_data_filter", ["data"], {
17594
17571
  ...removeDataFilter,
17595
- sequence: 10,
17572
+ sequence: 30,
17596
17573
  });
17597
17574
 
17598
17575
  class OTRegistry extends Registry {
@@ -17680,6 +17657,12 @@
17680
17657
  height: 25px;
17681
17658
  }
17682
17659
  }
17660
+ /** Make the character a bit bigger
17661
+ compared to its neighbor INPUT box */
17662
+ .o-remove-selection {
17663
+ font-weight: bold;
17664
+ font-size: calc(100% + 4px);
17665
+ }
17683
17666
  }
17684
17667
  `;
17685
17668
  /**
@@ -17775,6 +17758,9 @@
17775
17758
  this.confirm();
17776
17759
  }
17777
17760
  }
17761
+ extractRanges(value) {
17762
+ return this.props.hasSingleRange ? value.split(",")[0] : value;
17763
+ }
17778
17764
  focus(rangeId) {
17779
17765
  this.state.isMissing = false;
17780
17766
  this.state.mode = "select-range";
@@ -17793,10 +17779,11 @@
17793
17779
  }
17794
17780
  onInputChanged(rangeId, ev) {
17795
17781
  const target = ev.target;
17782
+ const value = this.extractRanges(target.value);
17796
17783
  this.env.model.dispatch("CHANGE_RANGE", {
17797
17784
  id: this.id,
17798
17785
  rangeId,
17799
- value: target.value,
17786
+ value,
17800
17787
  });
17801
17788
  this.triggerChange();
17802
17789
  }
@@ -18689,6 +18676,18 @@
18689
18676
  onCloseSidePanel: Function,
18690
18677
  };
18691
18678
 
18679
+ css /* scss */ `
18680
+ .o-spreadsheet {
18681
+ .o-icon {
18682
+ .small-text {
18683
+ font: bold 9px sans-serif;
18684
+ }
18685
+ .heavy-text {
18686
+ font: bold 16px sans-serif;
18687
+ }
18688
+ }
18689
+ }
18690
+ `;
18692
18691
  // -----------------------------------------------------------------------------
18693
18692
  // We need here the svg of the icons that we need to convert to images for the renderer
18694
18693
  // -----------------------------------------------------------------------------
@@ -20565,14 +20564,31 @@
20565
20564
  * */
20566
20565
  getCurrentSelection() {
20567
20566
  let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();
20568
- let startSizeBefore = this.findSizeBeforeElement(startElement);
20569
- let endSizeBefore = this.findSizeBeforeElement(endElement);
20567
+ let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
20568
+ let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
20570
20569
  return {
20571
- start: startSizeBefore + startSelectionOffset,
20572
- end: endSizeBefore + endSelectionOffset,
20570
+ start: startSizeBefore,
20571
+ end: endSizeBefore,
20573
20572
  };
20574
20573
  }
20575
- findSizeBeforeElement(nodeToFind) {
20574
+ /**
20575
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
20576
+ * The selected node is either a Text node or an Element node.
20577
+ *
20578
+ * case 1 -Text node:
20579
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
20580
+ * content length of all previous nodes.
20581
+ *
20582
+ * case 2 - Element node:
20583
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
20584
+ * all the bnodes prior to the selected node as well as the content of the child node before the offset.
20585
+ *
20586
+ * See the MDN documentation for more details.
20587
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
20588
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
20589
+ *
20590
+ */
20591
+ findSelectionIndex(nodeToFind, nodeOffset) {
20576
20592
  let usedCharacters = 0;
20577
20593
  let it = iterateChildren(this.el);
20578
20594
  let current = it.next();
@@ -20599,6 +20615,28 @@
20599
20615
  if (current.value !== nodeToFind) {
20600
20616
  throw new Error("Cannot find the node in the children of the element");
20601
20617
  }
20618
+ else {
20619
+ if (!current.value.hasChildNodes()) {
20620
+ usedCharacters += nodeOffset;
20621
+ }
20622
+ else {
20623
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
20624
+ usedCharacters += children.reduce((acc, child, index) => {
20625
+ if (child.textContent !== null) {
20626
+ // need to account for paragraph nodes that implicitely add a new line
20627
+ // except for the last paragraph
20628
+ let chars = child.textContent.length;
20629
+ if (child.nodeName === "P" && index !== children.length - 1) {
20630
+ chars++;
20631
+ }
20632
+ return acc + chars;
20633
+ }
20634
+ else {
20635
+ return acc;
20636
+ }
20637
+ }, 0);
20638
+ }
20639
+ }
20602
20640
  if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
20603
20641
  usedCharacters++;
20604
20642
  }
@@ -21399,13 +21437,13 @@
21399
21437
 
21400
21438
  const CSS$1 = css /* scss */ `
21401
21439
  .o-filter-icon {
21440
+ color: ${FILTERS_COLOR};
21402
21441
  position: absolute;
21403
21442
  display: flex;
21404
21443
  align-items: center;
21405
21444
  justify-content: center;
21406
21445
  width: ${FILTER_ICON_EDGE_LENGTH}px;
21407
21446
  height: ${FILTER_ICON_EDGE_LENGTH}px;
21408
- color: #4a4f59;
21409
21447
  }
21410
21448
  .o-filter-icon:hover {
21411
21449
  background: ${FILTERS_COLOR};
@@ -24136,17 +24174,17 @@
24136
24174
  const BORDER_STYLE_CONVERSION_MAP = {
24137
24175
  dashDot: "thin",
24138
24176
  dashDotDot: "thin",
24139
- dashed: "thin",
24140
- dotted: "thin",
24177
+ dashed: "dashed",
24178
+ dotted: "dotted",
24141
24179
  double: "thin",
24142
24180
  hair: "thin",
24143
- medium: "thin",
24181
+ medium: "medium",
24144
24182
  mediumDashDot: "thin",
24145
24183
  mediumDashDotDot: "thin",
24146
24184
  mediumDashed: "thin",
24147
24185
  none: undefined,
24148
24186
  slantDashDot: "thin",
24149
- thick: "thin",
24187
+ thick: "thick",
24150
24188
  thin: "thin",
24151
24189
  };
24152
24190
  /** Conversion map Horizontal Alignment in XLSX <=> Horizontal Alignment in o_spreadsheet*/
@@ -24645,7 +24683,7 @@
24645
24683
  return undefined;
24646
24684
  addBorderDescrWarnings(borderDescr, warningManager);
24647
24685
  const style = BORDER_STYLE_CONVERSION_MAP[borderDescr.style];
24648
- return style ? [style, convertColor(borderDescr.color)] : undefined;
24686
+ return style ? { style, color: convertColor(borderDescr.color) } : undefined;
24649
24687
  }
24650
24688
  function convertStyles(data, warningManager) {
24651
24689
  const stylesArray = data.styles.map((style) => {
@@ -26043,8 +26081,8 @@
26043
26081
  return undefined;
26044
26082
  }
26045
26083
  return {
26046
- style: descr[0],
26047
- color: { rgb: descr[1] },
26084
+ style: descr.style,
26085
+ color: { rgb: descr.color },
26048
26086
  };
26049
26087
  }
26050
26088
  function extractStyle(cell, data) {
@@ -26622,7 +26660,7 @@
26622
26660
  const TABLE_HIGHLIGHTED_CELL_STYLE = {
26623
26661
  bold: true,
26624
26662
  };
26625
- const TABLE_BORDER_STYLE = ["thin", "#000000FF"];
26663
+ const TABLE_BORDER_STYLE = { style: "thin", color: "#000000FF" };
26626
26664
  /**
26627
26665
  * Convert the imported XLSX tables.
26628
26666
  *
@@ -28191,7 +28229,7 @@
28191
28229
  * a breaking change is made in the way the state is handled, and an upgrade
28192
28230
  * function should be defined
28193
28231
  */
28194
- const CURRENT_VERSION = 12;
28232
+ const CURRENT_VERSION = 13;
28195
28233
  const INITIAL_SHEET_ID = "Sheet1";
28196
28234
  /**
28197
28235
  * This function tries to load anything that could look like a valid
@@ -28455,6 +28493,25 @@
28455
28493
  return data;
28456
28494
  },
28457
28495
  },
28496
+ {
28497
+ description: "Change Border description structure",
28498
+ from: 12,
28499
+ to: 13,
28500
+ applyMigration(data) {
28501
+ for (const borderId in data.borders) {
28502
+ const border = data.borders[borderId];
28503
+ for (const position in border) {
28504
+ if (Array.isArray(border[position])) {
28505
+ border[position] = {
28506
+ style: border[position][0],
28507
+ color: border[position][1],
28508
+ };
28509
+ }
28510
+ }
28511
+ }
28512
+ return data;
28513
+ },
28514
+ },
28458
28515
  ];
28459
28516
  /**
28460
28517
  * This function is used to repair faulty data independently of the migration.
@@ -28790,7 +28847,7 @@
28790
28847
  * - borders
28791
28848
  */
28792
28849
  class BordersPlugin extends CorePlugin {
28793
- static getters = ["getCellBorder"];
28850
+ static getters = ["getCellBorder", "getBordersColors"];
28794
28851
  borders = {};
28795
28852
  // ---------------------------------------------------------------------------
28796
28853
  // Command Handling
@@ -28821,10 +28878,15 @@
28821
28878
  case "SET_BORDER":
28822
28879
  this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
28823
28880
  break;
28824
- case "SET_FORMATTING":
28881
+ case "SET_ZONE_BORDERS":
28825
28882
  if (cmd.border) {
28826
28883
  const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
28827
- this.setBorders(cmd.sheetId, target, cmd.border);
28884
+ this.setBorders(cmd.sheetId, target, cmd.border.position, cmd.border.color === ""
28885
+ ? undefined
28886
+ : {
28887
+ style: cmd.border.style || DEFAULT_BORDER_DESC.style,
28888
+ color: cmd.border.color || DEFAULT_BORDER_DESC.color,
28889
+ });
28828
28890
  }
28829
28891
  break;
28830
28892
  case "CLEAR_FORMATTING":
@@ -28913,6 +28975,23 @@
28913
28975
  }
28914
28976
  return border;
28915
28977
  }
28978
+ getBordersColors(sheetId) {
28979
+ const colors = [];
28980
+ const sheetBorders = this.borders[sheetId];
28981
+ if (sheetBorders) {
28982
+ for (const borders of sheetBorders.filter(isDefined$1)) {
28983
+ for (const cellBorder of borders) {
28984
+ if (cellBorder?.horizontal) {
28985
+ colors.push(cellBorder.horizontal.color);
28986
+ }
28987
+ if (cellBorder?.vertical) {
28988
+ colors.push(cellBorder.vertical.color);
28989
+ }
28990
+ }
28991
+ }
28992
+ }
28993
+ return colors;
28994
+ }
28916
28995
  // ---------------------------------------------------------------------------
28917
28996
  // Private
28918
28997
  // ---------------------------------------------------------------------------
@@ -29124,43 +29203,43 @@
29124
29203
  * Set the borders of a zone by computing the borders to add from the given
29125
29204
  * command
29126
29205
  */
29127
- setBorders(sheetId, zones, command) {
29128
- if (command === "clear") {
29206
+ setBorders(sheetId, zones, position, border) {
29207
+ if (position === "clear") {
29129
29208
  return this.clearBorders(sheetId, zones);
29130
29209
  }
29131
29210
  for (let zone of zones) {
29132
- if (command === "h" || command === "hv" || command === "all") {
29211
+ if (position === "h" || position === "hv" || position === "all") {
29133
29212
  for (let row = zone.top + 1; row <= zone.bottom; row++) {
29134
29213
  for (let col = zone.left; col <= zone.right; col++) {
29135
- this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });
29214
+ this.addBorder(sheetId, col, row, { top: border });
29136
29215
  }
29137
29216
  }
29138
29217
  }
29139
- if (command === "v" || command === "hv" || command === "all") {
29218
+ if (position === "v" || position === "hv" || position === "all") {
29140
29219
  for (let row = zone.top; row <= zone.bottom; row++) {
29141
29220
  for (let col = zone.left + 1; col <= zone.right; col++) {
29142
- this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });
29221
+ this.addBorder(sheetId, col, row, { left: border });
29143
29222
  }
29144
29223
  }
29145
29224
  }
29146
- if (command === "left" || command === "all" || command === "external") {
29225
+ if (position === "left" || position === "all" || position === "external") {
29147
29226
  for (let row = zone.top; row <= zone.bottom; row++) {
29148
- this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });
29227
+ this.addBorder(sheetId, zone.left, row, { left: border });
29149
29228
  }
29150
29229
  }
29151
- if (command === "right" || command === "all" || command === "external") {
29230
+ if (position === "right" || position === "all" || position === "external") {
29152
29231
  for (let row = zone.top; row <= zone.bottom; row++) {
29153
- this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });
29232
+ this.addBorder(sheetId, zone.right + 1, row, { left: border });
29154
29233
  }
29155
29234
  }
29156
- if (command === "top" || command === "all" || command === "external") {
29235
+ if (position === "top" || position === "all" || position === "external") {
29157
29236
  for (let col = zone.left; col <= zone.right; col++) {
29158
- this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });
29237
+ this.addBorder(sheetId, col, zone.top, { top: border });
29159
29238
  }
29160
29239
  }
29161
- if (command === "bottom" || command === "all" || command === "external") {
29240
+ if (position === "bottom" || position === "all" || position === "external") {
29162
29241
  for (let col = zone.left; col <= zone.right; col++) {
29163
- this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });
29242
+ this.addBorder(sheetId, col, zone.bottom + 1, { top: border });
29164
29243
  }
29165
29244
  }
29166
29245
  }
@@ -29174,16 +29253,22 @@
29174
29253
  const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });
29175
29254
  this.clearBorders(sheetId, [zone]);
29176
29255
  if (bordersTopLeft?.top) {
29177
- this.setBorders(sheetId, [{ ...zone, bottom: top }], "top");
29256
+ this.setBorders(sheetId, [{ ...zone, bottom: top }], "top", bordersTopLeft.top);
29178
29257
  }
29179
29258
  if (bordersTopLeft?.left) {
29180
- this.setBorders(sheetId, [{ ...zone, right: left }], "left");
29259
+ this.setBorders(sheetId, [{ ...zone, right: left }], "left", bordersTopLeft.left);
29260
+ }
29261
+ if (bordersBottomRight?.bottom) {
29262
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersBottomRight.bottom);
29181
29263
  }
29182
- if (bordersBottomRight?.bottom || bordersTopLeft?.bottom) {
29183
- this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom");
29264
+ else if (bordersTopLeft?.bottom) {
29265
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersTopLeft.bottom);
29184
29266
  }
29185
- if (bordersBottomRight?.right || bordersTopLeft?.right) {
29186
- this.setBorders(sheetId, [{ ...zone, left: right }], "right");
29267
+ if (bordersBottomRight?.right) {
29268
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersBottomRight.right);
29269
+ }
29270
+ else if (bordersTopLeft?.right) {
29271
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29187
29272
  }
29188
29273
  }
29189
29274
  // ---------------------------------------------------------------------------
@@ -32119,6 +32204,8 @@
32119
32204
  "getSheetSize",
32120
32205
  "getSheetZone",
32121
32206
  "getPaneDivisions",
32207
+ "checkZonesExistInSheet",
32208
+ "getCommandZones",
32122
32209
  ];
32123
32210
  sheetIdsMapName = {};
32124
32211
  orderedSheetIds = [];
@@ -32128,7 +32215,7 @@
32128
32215
  // Command Handling
32129
32216
  // ---------------------------------------------------------------------------
32130
32217
  allowDispatch(cmd) {
32131
- const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZones)(cmd);
32218
+ const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZonesAreInSheet)(cmd);
32132
32219
  if (genericChecks !== 0 /* CommandResult.Success */) {
32133
32220
  return genericChecks;
32134
32221
  }
@@ -32468,6 +32555,37 @@
32468
32555
  .map(({ col, row }) => this.getCell({ sheetId, col, row }))
32469
32556
  .every((cell) => !cell || cell.content === "");
32470
32557
  }
32558
+ getCommandZones(cmd) {
32559
+ const zones = [];
32560
+ if ("zone" in cmd) {
32561
+ zones.push(cmd.zone);
32562
+ }
32563
+ if ("target" in cmd) {
32564
+ zones.push(...cmd.target);
32565
+ }
32566
+ if ("ranges" in cmd) {
32567
+ zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone));
32568
+ }
32569
+ if ("col" in cmd && "row" in cmd) {
32570
+ zones.push({ top: cmd.row, left: cmd.col, bottom: cmd.row, right: cmd.col });
32571
+ }
32572
+ return zones;
32573
+ }
32574
+ /**
32575
+ * Check if zones in the command are well formed and
32576
+ * not outside the sheet.
32577
+ */
32578
+ checkZonesExistInSheet(sheetId, zones) {
32579
+ if (!zones.every(isZoneValid))
32580
+ return 25 /* CommandResult.InvalidRange */;
32581
+ if (zones.length) {
32582
+ const sheetZone = this.getSheetZone(sheetId);
32583
+ return zones.every((zone) => isZoneInside(zone, sheetZone))
32584
+ ? 0 /* CommandResult.Success */
32585
+ : 18 /* CommandResult.TargetOutOfSheet */;
32586
+ }
32587
+ return 0 /* CommandResult.Success */;
32588
+ }
32471
32589
  updateCellPosition(cmd) {
32472
32590
  const { sheetId, cellId, col, row } = cmd;
32473
32591
  if (cellId) {
@@ -32916,9 +33034,6 @@
32916
33034
  }
32917
33035
  return { rowNumber, colNumber };
32918
33036
  }
32919
- // ----------------------------------------------------
32920
- // HIDE / SHOW
32921
- // ----------------------------------------------------
32922
33037
  /**
32923
33038
  * Check that any "sheetId" in the command matches an existing
32924
33039
  * sheet.
@@ -32936,27 +33051,10 @@
32936
33051
  * Check if zones in the command are well formed and
32937
33052
  * not outside the sheet.
32938
33053
  */
32939
- checkZones(cmd) {
32940
- const zones = [];
32941
- if ("zone" in cmd) {
32942
- zones.push(cmd.zone);
32943
- }
32944
- if ("target" in cmd && Array.isArray(cmd.target)) {
32945
- zones.push(...cmd.target);
32946
- }
32947
- if ("ranges" in cmd && Array.isArray(cmd.ranges)) {
32948
- zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone));
32949
- }
32950
- if (!zones.every(isZoneValid)) {
32951
- return 25 /* CommandResult.InvalidRange */;
32952
- }
32953
- else if (zones.length && "sheetId" in cmd) {
32954
- const sheetZone = this.getSheetZone(cmd.sheetId);
32955
- return zones.every((zone) => isZoneInside(zone, sheetZone))
32956
- ? 0 /* CommandResult.Success */
32957
- : 18 /* CommandResult.TargetOutOfSheet */;
32958
- }
32959
- return 0 /* CommandResult.Success */;
33054
+ checkZonesAreInSheet(cmd) {
33055
+ if (!("sheetId" in cmd))
33056
+ return 0 /* CommandResult.Success */;
33057
+ return this.checkZonesExistInSheet(cmd.sheetId, this.getCommandZones(cmd));
32960
33058
  }
32961
33059
  }
32962
33060
 
@@ -33036,6 +33134,9 @@
33036
33134
  case "UPDATE_CHART":
33037
33135
  case "CREATE_CHART":
33038
33136
  case "ADD_CONDITIONAL_FORMAT":
33137
+ case "SET_BORDER":
33138
+ case "SET_ZONE_BORDERS":
33139
+ case "SET_FORMATTING":
33039
33140
  this.shouldUpdateColors = true;
33040
33141
  }
33041
33142
  }
@@ -33050,8 +33151,7 @@
33050
33151
  getCustomColors() {
33051
33152
  let usedColors = [];
33052
33153
  for (const sheetId of this.getters.getSheetIds()) {
33053
- const cells = Object.values(this.getters.getCells(sheetId));
33054
- usedColors = usedColors.concat(this.getColorsFromCells(cells), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
33154
+ usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
33055
33155
  }
33056
33156
  return sortWithClusters([
33057
33157
  ...new Set(
@@ -33061,7 +33161,8 @@
33061
33161
  [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
33062
33162
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
33063
33163
  }
33064
- getColorsFromCells(cells) {
33164
+ getColorsFromCells(sheetId) {
33165
+ const cells = Object.values(this.getters.getCells(sheetId));
33065
33166
  const colors = new Set();
33066
33167
  for (const cell of cells) {
33067
33168
  if (cell.style?.textColor) {
@@ -33071,6 +33172,9 @@
33071
33172
  colors.add(cell.style.fillColor);
33072
33173
  }
33073
33174
  }
33175
+ for (const color of this.getters.getBordersColors(sheetId)) {
33176
+ colors.add(color);
33177
+ }
33074
33178
  return [...colors];
33075
33179
  }
33076
33180
  getFormattingColors(sheetId) {
@@ -33344,7 +33448,7 @@
33344
33448
  // magic "empty" value
33345
33449
  // Returning {value: null} instead of undefined will ensure that we don't
33346
33450
  // fall back on the default value of the argument provided to the formula's compute function
33347
- return { value: null };
33451
+ return { value: null, format: cell?.format };
33348
33452
  }
33349
33453
  return getEvaluatedCell(cell);
33350
33454
  }
@@ -33541,7 +33645,7 @@
33541
33645
  }
33542
33646
  break;
33543
33647
  case "PASTE_CONDITIONAL_FORMAT":
33544
- this.pasteCf(cmd.origin, cmd.target, cmd.operation);
33648
+ this.pasteCf(cmd.originPosition, cmd.targetPosition, cmd.operation);
33545
33649
  break;
33546
33650
  }
33547
33651
  }
@@ -36173,6 +36277,7 @@
36173
36277
 
36174
36278
  class Revision {
36175
36279
  rootCommand;
36280
+ timestamp;
36176
36281
  id;
36177
36282
  clientId;
36178
36283
  _commands = [];
@@ -36186,8 +36291,9 @@
36186
36291
  * - clientId: Client who initiated the action
36187
36292
  * - changes: List of changes applied on the state.
36188
36293
  */
36189
- constructor(id, clientId, commands, rootCommand, changes) {
36294
+ constructor(id, clientId, commands, rootCommand, changes, timestamp) {
36190
36295
  this.rootCommand = rootCommand;
36296
+ this.timestamp = timestamp;
36191
36297
  this.id = id;
36192
36298
  this.clientId = clientId;
36193
36299
  this._commands = [...commands];
@@ -36259,7 +36365,7 @@
36259
36365
  save(rootCommand, commands, changes) {
36260
36366
  if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())
36261
36367
  return;
36262
- const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes);
36368
+ const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
36263
36369
  this.revisions.append(revision.id, revision);
36264
36370
  this.trigger("new-local-state-update", { id: revision.id });
36265
36371
  this.sendUpdateMessage({
@@ -36310,13 +36416,9 @@
36310
36416
  }
36311
36417
  loadInitialMessages(messages) {
36312
36418
  this.isReplayingInitialRevisions = true;
36313
- this.on("unexpected-revision-id", this, ({ revisionId }) => {
36314
- throw new Error(`The spreadsheet could not be loaded. Revision ${revisionId} is corrupted.`);
36315
- });
36316
36419
  for (const message of messages) {
36317
36420
  this.onMessageReceived(message);
36318
36421
  }
36319
- this.off("unexpected-revision-id", this);
36320
36422
  this.isReplayingInitialRevisions = false;
36321
36423
  }
36322
36424
  /**
@@ -36402,6 +36504,10 @@
36402
36504
  onMessageReceived(message) {
36403
36505
  if (this.isAlreadyProcessed(message))
36404
36506
  return;
36507
+ if (this.isWrongServerRevisionId(message)) {
36508
+ this.trigger("unexpected-revision-id");
36509
+ return;
36510
+ }
36405
36511
  switch (message.type) {
36406
36512
  case "CLIENT_MOVED":
36407
36513
  this.onClientMoved(message);
@@ -36428,12 +36534,8 @@
36428
36534
  });
36429
36535
  break;
36430
36536
  case "REMOTE_REVISION":
36431
- if (message.serverRevisionId !== this.serverRevisionId) {
36432
- this.trigger("unexpected-revision-id", { revisionId: message.serverRevisionId });
36433
- return;
36434
- }
36435
- const { clientId, commands } = message;
36436
- const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE");
36537
+ const { clientId, commands, timestamp } = message;
36538
+ const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE", undefined, timestamp);
36437
36539
  if (revision.clientId !== this.clientId) {
36438
36540
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36439
36541
  const pendingCommands = this.pendingMessages
@@ -36446,7 +36548,7 @@
36446
36548
  }
36447
36549
  break;
36448
36550
  case "SNAPSHOT_CREATED": {
36449
- const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT");
36551
+ const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT", undefined, Date.now());
36450
36552
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36451
36553
  this.dropPendingHistoryMessages();
36452
36554
  this.trigger("snapshot");
@@ -36563,6 +36665,17 @@
36563
36665
  return false;
36564
36666
  }
36565
36667
  }
36668
+ isWrongServerRevisionId(message) {
36669
+ switch (message.type) {
36670
+ case "REMOTE_REVISION":
36671
+ case "REVISION_REDONE":
36672
+ case "REVISION_UNDONE":
36673
+ case "SNAPSHOT_CREATED":
36674
+ return message.serverRevisionId !== this.serverRevisionId;
36675
+ default:
36676
+ return false;
36677
+ }
36678
+ }
36566
36679
  dropPendingHistoryMessages() {
36567
36680
  this.waitingUndoRedoAck = false;
36568
36681
  this.pendingMessages = this.pendingMessages.filter(({ type }) => type !== "REVISION_REDONE" && type !== "REVISION_UNDONE");
@@ -37278,7 +37391,7 @@
37278
37391
  }
37279
37392
  }
37280
37393
  drawBorders(renderingContext) {
37281
- const { ctx, thinLineWidth } = renderingContext;
37394
+ const { ctx } = renderingContext;
37282
37395
  for (let box of this.boxes) {
37283
37396
  const border = box.border;
37284
37397
  if (border) {
@@ -37297,13 +37410,66 @@
37297
37410
  }
37298
37411
  }
37299
37412
  }
37300
- function drawBorder([style, color], x1, y1, x2, y2) {
37413
+ /**
37414
+ * Following https://usefulangle.com/post/17/html5-canvas-drawing-1px-crisp-straight-lines,
37415
+ * we need to make sure that a "single" pixel line is drawn on a "half" pixel coordinate,
37416
+ * while a "double" pixel line is drawn on a "full" pixel coordinate. As, in the rendering
37417
+ * process, we always had 0.5 before rendering line (to make sure it is drawn on a "half"
37418
+ * pixel), we need to correct this behavior for the "medium" and the "dotted" styles, as
37419
+ * they are drawing a two pixels width line.
37420
+ * We also adapt here the coordinates of the line to make sure corner are correctly drawn,
37421
+ * avoiding a "round corners" effect. This is done by subtracting 1 pixel to the origin of
37422
+ * each line and adding 1 pixel to the end of each line (depending on the direction of the
37423
+ * line).
37424
+ */
37425
+ function drawBorder({ style, color }, x1, y1, x2, y2) {
37301
37426
  ctx.strokeStyle = color;
37302
- ctx.lineWidth = (style === "thin" ? 2 : 3) * thinLineWidth;
37427
+ switch (style) {
37428
+ case "medium":
37429
+ ctx.lineWidth = 2;
37430
+ x1 += y1 === y2 ? -0.5 : 0.5;
37431
+ x2 += y1 === y2 ? 1.5 : 0.5;
37432
+ y1 += x1 === x2 ? -0.5 : 0.5;
37433
+ y2 += x1 === x2 ? 1.5 : 0.5;
37434
+ break;
37435
+ case "thick":
37436
+ ctx.lineWidth = 3;
37437
+ if (y1 === y2) {
37438
+ x1--;
37439
+ x2++;
37440
+ }
37441
+ if (x1 === x2) {
37442
+ y1--;
37443
+ y2++;
37444
+ }
37445
+ break;
37446
+ case "dashed":
37447
+ ctx.lineWidth = 1;
37448
+ ctx.setLineDash([1, 3]);
37449
+ break;
37450
+ case "dotted":
37451
+ ctx.lineWidth = 1;
37452
+ if (y1 === y2) {
37453
+ x1 += 0.5;
37454
+ x2 += 0.5;
37455
+ }
37456
+ if (x1 === x2) {
37457
+ y1 += 0.5;
37458
+ y2 += 0.5;
37459
+ }
37460
+ ctx.setLineDash([1, 1]);
37461
+ break;
37462
+ case "thin":
37463
+ default:
37464
+ ctx.lineWidth = 1;
37465
+ break;
37466
+ }
37303
37467
  ctx.beginPath();
37304
37468
  ctx.moveTo(x1, y1);
37305
37469
  ctx.lineTo(x2, y2);
37306
37470
  ctx.stroke();
37471
+ ctx.lineWidth = 1;
37472
+ ctx.setLineDash([]);
37307
37473
  }
37308
37474
  }
37309
37475
  drawTexts(renderingContext) {
@@ -37781,6 +37947,9 @@
37781
37947
  activeSheet;
37782
37948
  willAddNewRange = false;
37783
37949
  constructor(config, initialRanges, inputHasSingleRange) {
37950
+ if (inputHasSingleRange && initialRanges.length > 1) {
37951
+ throw new Error("Input with a single range cannot be instantiated with several range references.");
37952
+ }
37784
37953
  super(config);
37785
37954
  this.inputHasSingleRange = inputHasSingleRange;
37786
37955
  this.insertNewRange(0, initialRanges);
@@ -37800,6 +37969,11 @@
37800
37969
  return 30 /* CommandResult.MaximumRangesReached */;
37801
37970
  }
37802
37971
  break;
37972
+ case "CHANGE_RANGE":
37973
+ if (this.inputHasSingleRange && cmd.value.split(",").length > 1) {
37974
+ return 30 /* CommandResult.MaximumRangesReached */;
37975
+ }
37976
+ break;
37803
37977
  }
37804
37978
  return 0 /* CommandResult.Success */;
37805
37979
  }
@@ -38456,18 +38630,7 @@
38456
38630
  // Command Handling
38457
38631
  // ---------------------------------------------------------------------------
38458
38632
  allowDispatch(cmd) {
38459
- switch (cmd.type) {
38460
- case "AUTORESIZE_ROWS":
38461
- case "AUTORESIZE_COLUMNS":
38462
- try {
38463
- this.getters.getSheet(cmd.sheetId);
38464
- break;
38465
- }
38466
- catch (error) {
38467
- return 27 /* CommandResult.InvalidSheetId */;
38468
- }
38469
- }
38470
- return 0 /* CommandResult.Success */;
38633
+ return this.chainValidations(this.checkSheetExists, this.checkZonesAreInSheet)(cmd);
38471
38634
  }
38472
38635
  handle(cmd) {
38473
38636
  switch (cmd.type) {
@@ -38667,6 +38830,31 @@
38667
38830
  splitWord.push(wordPart);
38668
38831
  return splitWord;
38669
38832
  }
38833
+ /**
38834
+ * Check that any "sheetId" in the command matches an existing
38835
+ * sheet.
38836
+ */
38837
+ checkSheetExists(cmd) {
38838
+ if ("sheetId" in cmd && this.getters.tryGetSheet(cmd.sheetId) === undefined) {
38839
+ return 27 /* CommandResult.InvalidSheetId */;
38840
+ }
38841
+ return 0 /* CommandResult.Success */;
38842
+ }
38843
+ /**
38844
+ * Check if zones in the command are well formed and
38845
+ * not outside the sheet.
38846
+ */
38847
+ checkZonesAreInSheet(cmd) {
38848
+ const sheetId = "sheetId" in cmd ? cmd.sheetId : this.getters.tryGetActiveSheetId();
38849
+ const zones = this.getters.getCommandZones(cmd);
38850
+ if (!sheetId && zones.length > 0) {
38851
+ return 92 /* CommandResult.NoActiveSheet */;
38852
+ }
38853
+ if (sheetId && zones.length > 0) {
38854
+ return this.getters.checkZonesExistInSheet(sheetId, zones);
38855
+ }
38856
+ return 0 /* CommandResult.Success */;
38857
+ }
38670
38858
  }
38671
38859
 
38672
38860
  const genericRepeatsTransforms = [
@@ -39491,8 +39679,8 @@
39491
39679
  this.pasteCell(origin, position, this.operation, clipboardOptions);
39492
39680
  if (shouldPasteCF) {
39493
39681
  this.dispatch("PASTE_CONDITIONAL_FORMAT", {
39494
- origin: origin.position,
39495
- target: position,
39682
+ originPosition: origin.position,
39683
+ targetPosition: position,
39496
39684
  operation: this.operation,
39497
39685
  });
39498
39686
  }
@@ -39883,7 +40071,7 @@
39883
40071
  allowDispatch(cmd) {
39884
40072
  switch (cmd.type) {
39885
40073
  case "CUT":
39886
- const zones = cmd.target || this.getters.getSelectedZones();
40074
+ const zones = cmd.cutTarget || this.getters.getSelectedZones();
39887
40075
  const state = this.getClipboardState(zones, cmd.type);
39888
40076
  return state.isCutAllowed(zones);
39889
40077
  case "PASTE":
@@ -39913,7 +40101,7 @@
39913
40101
  switch (cmd.type) {
39914
40102
  case "COPY":
39915
40103
  case "CUT":
39916
- const zones = ("target" in cmd && cmd.target) || this.getters.getSelectedZones();
40104
+ const zones = ("cutTarget" in cmd && cmd.cutTarget) || this.getters.getSelectedZones();
39917
40105
  this.state = this.getClipboardState(zones, cmd.type);
39918
40106
  this.status = "visible";
39919
40107
  break;
@@ -40869,6 +41057,7 @@
40869
41057
  "isSelected",
40870
41058
  "isSingleColSelected",
40871
41059
  "getElementsFromSelection",
41060
+ "tryGetActiveSheetId",
40872
41061
  ];
40873
41062
  gridSelection = {
40874
41063
  anchor: {
@@ -41084,6 +41273,9 @@
41084
41273
  getActiveSheetId() {
41085
41274
  return this.activeSheet.id;
41086
41275
  }
41276
+ tryGetActiveSheetId() {
41277
+ return this.activeSheet?.id;
41278
+ }
41087
41279
  getActiveCell() {
41088
41280
  return this.getters.getEvaluatedCell(this.getActivePosition());
41089
41281
  }
@@ -41286,7 +41478,8 @@
41286
41478
  },
41287
41479
  zone: selectedZone,
41288
41480
  };
41289
- this.setSelectionMixin(anchor, [selectedZone]);
41481
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "left", [...cmd.elements]));
41482
+ this.setSelectionMixin(anchor, selections);
41290
41483
  }
41291
41484
  onRowsRemoved(cmd) {
41292
41485
  const { cell, zone } = this.gridSelection.anchor;
@@ -41300,13 +41493,18 @@
41300
41493
  },
41301
41494
  zone: selectedZone,
41302
41495
  };
41303
- this.setSelectionMixin(anchor, [selectedZone]);
41496
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "top", [...cmd.elements]));
41497
+ this.setSelectionMixin(anchor, selections);
41304
41498
  }
41305
41499
  onAddElements(cmd) {
41306
- const selection = this.gridSelection.anchor.zone;
41307
- const zone = updateSelectionOnInsertion(selection, cmd.dimension === "COL" ? "left" : "top", cmd.base, cmd.position, cmd.quantity);
41308
- const anchor = { cell: { col: zone.left, row: zone.top }, zone };
41309
- this.setSelectionMixin(anchor, [zone]);
41500
+ const start = cmd.dimension === "COL" ? "left" : "top";
41501
+ const anchorZone = updateSelectionOnInsertion(this.gridSelection.anchor.zone, start, cmd.base, cmd.position, cmd.quantity);
41502
+ const selection = this.gridSelection.zones.map((zone) => updateSelectionOnInsertion(zone, start, cmd.base, cmd.position, cmd.quantity));
41503
+ const anchor = {
41504
+ cell: { col: anchorZone.left, row: anchorZone.top },
41505
+ zone: anchorZone,
41506
+ };
41507
+ this.setSelectionMixin(anchor, selection);
41310
41508
  }
41311
41509
  onMoveElements(cmd) {
41312
41510
  const thickness = cmd.elements.length;
@@ -41324,7 +41522,7 @@
41324
41522
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
41325
41523
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
41326
41524
  this.dispatch("CUT", {
41327
- target: [
41525
+ cutTarget: [
41328
41526
  {
41329
41527
  left: isCol ? start + deltaCol : 0,
41330
41528
  right: isCol ? end + deltaCol : this.getters.getNumberCols(cmd.sheetId) - 1,
@@ -42552,7 +42750,6 @@
42552
42750
  color: dimgrey;
42553
42751
  }
42554
42752
  .o-sidePanelClose {
42555
- font-size: 1.5rem;
42556
42753
  padding: 5px 10px;
42557
42754
  cursor: pointer;
42558
42755
  &:hover {
@@ -42737,7 +42934,6 @@
42737
42934
  justify-content: center;
42738
42935
  align-items: center;
42739
42936
  margin: 2px;
42740
- padding: 0px 3px;
42741
42937
  border-radius: 2px;
42742
42938
  min-width: 20px;
42743
42939
  }
@@ -42794,7 +42990,228 @@
42794
42990
  onClick: { type: Function, optional: true },
42795
42991
  };
42796
42992
 
42993
+ /**
42994
+ * List the available borders positions and the corresponding icons.
42995
+ * The structure of this array is defined to match the order/lines we want
42996
+ * to display in the topbar's border tool.
42997
+ */
42998
+ const BORDER_POSITIONS = [
42999
+ [
43000
+ ["all", "o-spreadsheet-Icon.BORDERS"],
43001
+ ["hv", "o-spreadsheet-Icon.BORDER_HV"],
43002
+ ["h", "o-spreadsheet-Icon.BORDER_H"],
43003
+ ["v", "o-spreadsheet-Icon.BORDER_V"],
43004
+ ["external", "o-spreadsheet-Icon.BORDER_EXTERNAL"],
43005
+ ],
43006
+ [
43007
+ ["left", "o-spreadsheet-Icon.BORDER_LEFT"],
43008
+ ["top", "o-spreadsheet-Icon.BORDER_TOP"],
43009
+ ["right", "o-spreadsheet-Icon.BORDER_RIGHT"],
43010
+ ["bottom", "o-spreadsheet-Icon.BORDER_BOTTOM"],
43011
+ ["clear", "o-spreadsheet-Icon.BORDER_CLEAR"],
43012
+ ],
43013
+ ];
43014
+ // -----------------------------------------------------------------------------
43015
+ // Border Editor
43016
+ // -----------------------------------------------------------------------------
43017
+ css /* scss */ `
43018
+ .o-border-selector {
43019
+ padding: 4px;
43020
+ }
43021
+ .o-divider {
43022
+ border-right: 1px solid #e0e2e4;
43023
+ margin: 0 6px;
43024
+ }
43025
+ .o-border-selector-section {
43026
+ .o-dropdown-line {
43027
+ height: 30px;
43028
+ margin: 1px;
43029
+ .o-line-item {
43030
+ padding: 4px;
43031
+ width: 18px;
43032
+ height: 18px;
43033
+ }
43034
+ }
43035
+ .o-border-style-tool {
43036
+ padding: 0px 3px;
43037
+ margin: 2px;
43038
+ height: 25px;
43039
+ }
43040
+ }
43041
+ .o-style-preview {
43042
+ margin: 7px 5px 7px 5px;
43043
+ width: 60px;
43044
+ height: 5px;
43045
+ }
43046
+ .o-style-thin {
43047
+ border-bottom: 1px solid #000000;
43048
+ }
43049
+ .o-style-medium {
43050
+ border-bottom: 2px solid #000000;
43051
+ }
43052
+ .o-style-thick {
43053
+ border-bottom: 3px solid #000000;
43054
+ }
43055
+ .o-style-dashed {
43056
+ border-bottom: 1px dashed #000000;
43057
+ }
43058
+ .o-style-dotted {
43059
+ border-bottom: 1px dotted #000000;
43060
+ }
43061
+ .o-dropdown-border-type {
43062
+ &:not(.o-disabled):not(.active):hover {
43063
+ background-color: ${BG_HOVER_COLOR};
43064
+ }
43065
+ }
43066
+ .o-dropdown-border-check {
43067
+ width: 20px;
43068
+ font-size: 12px;
43069
+ }
43070
+ .o-border-style-dropdown {
43071
+ background: #ffffff;
43072
+ padding: 4px;
43073
+ .o-dropdown-line {
43074
+ .o-line-item.active {
43075
+ background-color: rgba(0, 0, 0, 0.2);
43076
+ }
43077
+ }
43078
+ }
43079
+ .o-border-picker-button {
43080
+ padding: 0px !important;
43081
+ margin: 5px 0px 0px 0px !important;
43082
+ height: 25px !important;
43083
+ }
43084
+ `;
43085
+ class BorderEditor extends owl.Component {
43086
+ static template = "o-spreadsheet-BorderEditor";
43087
+ static components = { ColorPickerWidget, Popover };
43088
+ BORDER_POSITIONS = BORDER_POSITIONS;
43089
+ lineStyleButtonRef = owl.useRef("lineStyleButton");
43090
+ borderStyles = borderStyles;
43091
+ state = owl.useState({
43092
+ activeTool: undefined,
43093
+ });
43094
+ toggleDropdownTool(tool) {
43095
+ const isOpen = this.state.activeTool === tool;
43096
+ this.state.activeTool = isOpen ? undefined : tool;
43097
+ }
43098
+ closeDropdown() {
43099
+ this.state.activeTool = undefined;
43100
+ }
43101
+ setBorderPosition(position) {
43102
+ this.props.onBorderPositionPicked(position);
43103
+ this.closeDropdown();
43104
+ }
43105
+ setBorderColor(color) {
43106
+ this.props.onBorderColorPicked(color);
43107
+ this.closeDropdown();
43108
+ }
43109
+ setBorderStyle(style) {
43110
+ this.props.onBorderStylePicked(style);
43111
+ this.closeDropdown();
43112
+ }
43113
+ get lineStylePickerPopoverProps() {
43114
+ return {
43115
+ anchorRect: this.lineStylePickerAnchorRect,
43116
+ positioning: "BottomLeft",
43117
+ verticalOffset: 0,
43118
+ };
43119
+ }
43120
+ get popoverProps() {
43121
+ return {
43122
+ anchorRect: this.props.anchorRect,
43123
+ maxHeight: this.props.maxHeight,
43124
+ positioning: "BottomLeft",
43125
+ verticalOffset: 0,
43126
+ };
43127
+ }
43128
+ get lineStylePickerAnchorRect() {
43129
+ const button = this.lineStyleButtonRef.el;
43130
+ if (button === null) {
43131
+ return { x: 0, y: 0, width: 0, height: 0 };
43132
+ }
43133
+ const buttonRect = button.getBoundingClientRect();
43134
+ return {
43135
+ x: buttonRect.x,
43136
+ y: buttonRect.y,
43137
+ width: buttonRect.width,
43138
+ height: buttonRect.height,
43139
+ };
43140
+ }
43141
+ }
43142
+ BorderEditor.props = {
43143
+ class: { type: String, optional: true },
43144
+ currentBorderColor: { type: String, optional: false },
43145
+ currentBorderStyle: { type: String, optional: false },
43146
+ currentBorderPosition: { type: String, optional: true },
43147
+ onBorderColorPicked: Function,
43148
+ onBorderStylePicked: Function,
43149
+ onBorderPositionPicked: Function,
43150
+ maxHeight: { type: Number, optional: true },
43151
+ anchorRect: Object,
43152
+ };
43153
+
43154
+ class BorderEditorWidget extends owl.Component {
43155
+ static template = "o-spreadsheet-BorderEditorWidget";
43156
+ static components = { BorderEditor };
43157
+ borderEditorButtonRef = owl.useRef("borderEditorButton");
43158
+ state = owl.useState({
43159
+ currentColor: DEFAULT_BORDER_DESC.color,
43160
+ currentStyle: DEFAULT_BORDER_DESC.style,
43161
+ currentPosition: undefined,
43162
+ });
43163
+ get borderEditorAnchorRect() {
43164
+ const button = this.borderEditorButtonRef.el;
43165
+ const buttonRect = button.getBoundingClientRect();
43166
+ return {
43167
+ x: buttonRect.x,
43168
+ y: buttonRect.y,
43169
+ width: buttonRect.width,
43170
+ height: buttonRect.height,
43171
+ };
43172
+ }
43173
+ onBorderPositionPicked(position) {
43174
+ this.state.currentPosition = position;
43175
+ this.updateBorder();
43176
+ }
43177
+ onBorderColorPicked(color) {
43178
+ this.state.currentColor = color;
43179
+ this.updateBorder();
43180
+ }
43181
+ onBorderStylePicked(style) {
43182
+ this.state.currentStyle = style;
43183
+ this.updateBorder();
43184
+ }
43185
+ updateBorder() {
43186
+ if (this.state.currentPosition === undefined) {
43187
+ return;
43188
+ }
43189
+ this.env.model.dispatch("SET_ZONE_BORDERS", {
43190
+ sheetId: this.env.model.getters.getActiveSheetId(),
43191
+ target: this.env.model.getters.getSelectedZones(),
43192
+ border: {
43193
+ position: this.state.currentPosition,
43194
+ color: this.state.currentColor,
43195
+ style: this.state.currentStyle,
43196
+ },
43197
+ });
43198
+ }
43199
+ }
43200
+ BorderEditorWidget.props = {
43201
+ toggleBorderEditor: Function,
43202
+ showBorderEditor: Boolean,
43203
+ disabled: { type: Boolean, optional: true },
43204
+ dropdownMaxHeight: { type: Number, optional: true },
43205
+ class: { type: String, optional: true },
43206
+ };
43207
+
42797
43208
  const COMPOSER_MAX_HEIGHT = 100;
43209
+ /* svg free of use from https://uxwing.com/formula-fx-icon/ */
43210
+ const FX_SVG = /*xml*/ `
43211
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
43212
+ <path d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='#BDBDBD'/>
43213
+ </svg>
43214
+ `;
42798
43215
  css /* scss */ `
42799
43216
  .o-topbar-composer {
42800
43217
  height: fit-content;
@@ -42802,9 +43219,10 @@
42802
43219
  border: 1px solid;
42803
43220
  z-index: ${ComponentsImportance.TopBarComposer};
42804
43221
 
42805
- .o-composer:empty:not(:focus)::before {
42806
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
42807
- content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -10 121.8 142.9' width='24' height='24' focusable='false'%3E%3Cpath d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='%23BDBDBD' /%3E%3C/svg%3E");
43222
+ .o-composer:empty:not(:focus):not(.active)::before {
43223
+ content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
43224
+ position: relative;
43225
+ top: 20%;
42808
43226
  }
42809
43227
  }
42810
43228
  `;
@@ -42927,11 +43345,6 @@
42927
43345
  dropdownStyle: String,
42928
43346
  };
42929
43347
 
42930
- // If we ever change these colors, make sure the filter tool stays green to match the icon in the grid
42931
- const ACTIVE_BG_COLOR = BACKGROUND_HEADER_FILTER_COLOR;
42932
- const ACTIVE_FONT_COLOR = FILTERS_COLOR;
42933
- const HOVERED_BG_COLOR = BG_HOVER_COLOR;
42934
- const HOVERED_FONT_COLOR = "#000";
42935
43348
  // -----------------------------------------------------------------------------
42936
43349
  // TopBar
42937
43350
  // -----------------------------------------------------------------------------
@@ -42941,27 +43354,6 @@
42941
43354
  font-size: 13px;
42942
43355
  font-weight: 500;
42943
43356
 
42944
- .o-topbar-hoverable {
42945
- cursor: pointer;
42946
- .o-icon {
42947
- color: ${ICONS_COLOR};
42948
- }
42949
- &:not(.o-disabled):not(.active):hover {
42950
- background-color: ${HOVERED_BG_COLOR};
42951
- color: ${HOVERED_FONT_COLOR};
42952
- .o-icon {
42953
- color: ${HOVERED_FONT_COLOR};
42954
- }
42955
- }
42956
- &.active {
42957
- background-color: ${ACTIVE_BG_COLOR};
42958
- color: ${ACTIVE_FONT_COLOR};
42959
- .o-icon {
42960
- color: ${ACTIVE_FONT_COLOR};
42961
- }
42962
- }
42963
- }
42964
-
42965
43357
  .o-topbar-top {
42966
43358
  border-bottom: 1px solid ${SEPARATOR_COLOR};
42967
43359
  padding: 2px 10px;
@@ -42971,6 +43363,11 @@
42971
43363
  .o-topbar-menu {
42972
43364
  padding: 4px 6px;
42973
43365
  margin: 0 2px;
43366
+
43367
+ &.active {
43368
+ background-color: ${BG_HOVER_COLOR};
43369
+ color: #000;
43370
+ }
42974
43371
  }
42975
43372
  }
42976
43373
  }
@@ -43048,6 +43445,7 @@
43048
43445
  TopBarComposer,
43049
43446
  FontSizeEditor,
43050
43447
  ActionButton,
43448
+ BorderEditorWidget,
43051
43449
  };
43052
43450
  state = owl.useState({
43053
43451
  menuState: { isOpen: false, position: null, menuItems: [] },
@@ -43123,7 +43521,9 @@
43123
43521
  this.state.activeTool = "";
43124
43522
  this.state.menuState.isOpen = true;
43125
43523
  this.state.menuState.position = { x: left, y: top + height };
43126
- this.state.menuState.menuItems = menu.children(this.env);
43524
+ this.state.menuState.menuItems = menu
43525
+ .children(this.env)
43526
+ .sort((a, b) => a.sequence - b.sequence);
43127
43527
  this.state.menuState.parentMenu = menu;
43128
43528
  this.isSelectingMenu = true;
43129
43529
  this.openedEl = ev.target;
@@ -43209,6 +43609,11 @@
43209
43609
  }
43210
43610
  }
43211
43611
 
43612
+ // If we ever change these colors, make sure the filter tool stays green to match the icon in the grid
43613
+ const ACTIVE_BG_COLOR = BACKGROUND_HEADER_FILTER_COLOR;
43614
+ const ACTIVE_FONT_COLOR = FILTERS_COLOR;
43615
+ const HOVERED_BG_COLOR = BG_HOVER_COLOR;
43616
+ const HOVERED_FONT_COLOR = "#000";
43212
43617
  css /* scss */ `
43213
43618
  .o-spreadsheet {
43214
43619
  position: relative;
@@ -43241,6 +43646,27 @@
43241
43646
  margin-top: ${MENU_SEPARATOR_PADDING}px;
43242
43647
  margin-bottom: ${MENU_SEPARATOR_PADDING}px;
43243
43648
  }
43649
+ .o-hoverable-button {
43650
+ border-radius: 2px;
43651
+ cursor: pointer;
43652
+ .o-icon {
43653
+ color: ${ICONS_COLOR};
43654
+ }
43655
+ &:not(.o-disabled):not(.active):hover {
43656
+ background-color: ${HOVERED_BG_COLOR};
43657
+ color: ${HOVERED_FONT_COLOR};
43658
+ .o-icon {
43659
+ color: ${HOVERED_FONT_COLOR};
43660
+ }
43661
+ }
43662
+ &.active {
43663
+ background-color: ${ACTIVE_BG_COLOR};
43664
+ color: ${ACTIVE_FONT_COLOR};
43665
+ .o-icon {
43666
+ color: ${ACTIVE_FONT_COLOR};
43667
+ }
43668
+ }
43669
+ }
43244
43670
  }
43245
43671
 
43246
43672
  .o-two-columns {
@@ -43390,6 +43816,9 @@
43390
43816
  }
43391
43817
  }
43392
43818
  openSidePanel(panel, panelProps) {
43819
+ if (this.sidePanel.isOpen && panel !== this.sidePanel.component) {
43820
+ this.sidePanel.panelProps?.onCloseSidePanel?.();
43821
+ }
43393
43822
  this.sidePanel.component = panel;
43394
43823
  this.sidePanel.panelProps = panelProps;
43395
43824
  this.sidePanel.isOpen = true;
@@ -44099,13 +44528,13 @@
44099
44528
  }
44100
44529
 
44101
44530
  class SelectiveHistory {
44531
+ HEAD_BRANCH;
44532
+ HEAD_OPERATION;
44533
+ tree;
44102
44534
  applyOperation;
44103
44535
  revertOperation;
44104
44536
  buildEmpty;
44105
44537
  buildTransformation;
44106
- HEAD_BRANCH;
44107
- HEAD_OPERATION;
44108
- tree;
44109
44538
  /**
44110
44539
  * The selective history is a data structure used to register changes/updates of a state.
44111
44540
  * Each change/update is called an "operation".
@@ -44126,14 +44555,15 @@
44126
44555
  * (used for internal implementation)
44127
44556
  * @param buildTransformation Factory used to build transformations
44128
44557
  */
44129
- constructor(initialOperationId, applyOperation, revertOperation, buildEmpty, buildTransformation) {
44130
- this.applyOperation = applyOperation;
44131
- this.revertOperation = revertOperation;
44132
- this.buildEmpty = buildEmpty;
44133
- this.buildTransformation = buildTransformation;
44558
+ constructor(args) {
44559
+ this.applyOperation = args.applyOperation;
44560
+ this.revertOperation = args.revertOperation;
44561
+ this.buildEmpty = args.buildEmpty;
44562
+ this.buildTransformation = args.buildTransformation;
44134
44563
  this.HEAD_BRANCH = new Branch(this.buildTransformation);
44135
- this.tree = new Tree(buildTransformation, this.HEAD_BRANCH);
44136
- const initial = new Operation(initialOperationId, buildEmpty(initialOperationId));
44564
+ this.tree = new Tree(this.buildTransformation, this.HEAD_BRANCH);
44565
+ const initialOperationId = args.initialOperationId;
44566
+ const initial = new Operation(initialOperationId, this.buildEmpty(initialOperationId));
44137
44567
  this.tree.insertOperationLast(this.HEAD_BRANCH, initial);
44138
44568
  this.HEAD_OPERATION = initial;
44139
44569
  }
@@ -44247,21 +44677,27 @@
44247
44677
  }
44248
44678
  }
44249
44679
 
44250
- function buildRevisionLog(initialRevisionId, recordChanges, dispatch) {
44251
- return new SelectiveHistory(initialRevisionId, (revision) => {
44252
- const commands = revision.commands.slice();
44253
- const { changes } = recordChanges(() => {
44254
- for (const command of commands) {
44255
- dispatch(command);
44256
- }
44257
- });
44258
- revision.setChanges(changes);
44259
- }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", []), {
44260
- with: (revision) => (toTransform) => {
44261
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand);
44680
+ function buildRevisionLog(args) {
44681
+ return new SelectiveHistory({
44682
+ initialOperationId: args.initialRevisionId,
44683
+ applyOperation: (revision) => {
44684
+ const commands = revision.commands.slice();
44685
+ const { changes } = args.recordChanges(() => {
44686
+ for (const command of commands) {
44687
+ args.dispatch(command);
44688
+ }
44689
+ });
44690
+ revision.setChanges(changes);
44262
44691
  },
44263
- without: (revision) => (toTransform) => {
44264
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand);
44692
+ revertOperation: (revision) => revertChanges([revision]),
44693
+ buildEmpty: (id) => new Revision(id, "empty", []),
44694
+ buildTransformation: {
44695
+ with: (revision) => (toTransform) => {
44696
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand, undefined, toTransform.timestamp);
44697
+ },
44698
+ without: (revision) => (toTransform) => {
44699
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand, undefined, toTransform.timestamp);
44700
+ },
44265
44701
  },
44266
44702
  });
44267
44703
  }
@@ -45934,11 +46370,21 @@
45934
46370
  for (let border of Object.values(borders)) {
45935
46371
  borderNodes.push(escapeXml /*xml*/ `
45936
46372
  <border>
45937
- <left ${formatBorderAttribute(border["left"])} />
45938
- <right ${formatBorderAttribute(border["right"])} />
45939
- <top ${formatBorderAttribute(border["top"])} />
45940
- <bottom ${formatBorderAttribute(border["bottom"])} />
45941
- <diagonal ${formatBorderAttribute(border["diagonal"])} />
46373
+ <left ${formatBorderAttribute(border["left"])}>
46374
+ ${addBorderColor(border["left"])}
46375
+ </left>
46376
+ <right ${formatBorderAttribute(border["right"])}>
46377
+ ${addBorderColor(border["right"])}
46378
+ </right>
46379
+ <top ${formatBorderAttribute(border["top"])}>
46380
+ ${addBorderColor(border["top"])}
46381
+ </top>
46382
+ <bottom ${formatBorderAttribute(border["bottom"])}>
46383
+ ${addBorderColor(border["bottom"])}
46384
+ </bottom>
46385
+ <diagonal ${formatBorderAttribute(border["diagonal"])}>
46386
+ ${addBorderColor(border["diagonal"])}
46387
+ </diagonal>
45942
46388
  </border>
45943
46389
  `);
45944
46390
  }
@@ -45952,10 +46398,15 @@
45952
46398
  if (!description) {
45953
46399
  return escapeXml ``;
45954
46400
  }
45955
- return formatAttributes([
45956
- ["style", description.style],
45957
- ["color", toXlsxHexColor(description.color.rgb)],
45958
- ]);
46401
+ return formatAttributes([["style", description.style]]);
46402
+ }
46403
+ function addBorderColor(description) {
46404
+ if (!description) {
46405
+ return escapeXml ``;
46406
+ }
46407
+ return escapeXml /*xml*/ `
46408
+ <color ${formatAttributes([["rgb", toXlsxHexColor(description.color.rgb)]])}/>
46409
+ `;
45959
46410
  }
45960
46411
  function addStyles(styles) {
45961
46412
  const styleNodes = [];
@@ -46565,6 +47016,7 @@
46565
47016
  coreGetters;
46566
47017
  uuidGenerator;
46567
47018
  handlers = [];
47019
+ uiHandlers = [];
46568
47020
  coreHandlers = [];
46569
47021
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
46570
47022
  super();
@@ -46599,21 +47051,25 @@
46599
47051
  this.setupCorePlugin(Plugin, workbookData);
46600
47052
  }
46601
47053
  Object.assign(this.getters, this.coreGetters);
47054
+ this.session.loadInitialMessages(stateUpdateMessages);
46602
47055
  for (let Plugin of statefulUIPluginRegistry.getAll()) {
46603
47056
  const plugin = this.setupUiPlugin(Plugin);
46604
47057
  this.statefulUIPlugins.push(plugin);
46605
47058
  this.handlers.push(plugin);
47059
+ this.uiHandlers.push(plugin);
46606
47060
  }
46607
47061
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
46608
47062
  const plugin = this.setupUiPlugin(Plugin);
46609
47063
  this.coreViewsPlugins.push(plugin);
46610
47064
  this.handlers.push(plugin);
47065
+ this.uiHandlers.push(plugin);
46611
47066
  this.coreHandlers.push(plugin);
46612
47067
  }
46613
47068
  for (let Plugin of featurePluginRegistry.getAll()) {
46614
47069
  const plugin = this.setupUiPlugin(Plugin);
46615
47070
  this.featurePlugins.push(plugin);
46616
47071
  this.handlers.push(plugin);
47072
+ this.uiHandlers.push(plugin);
46617
47073
  }
46618
47074
  this.uuidGenerator.setIsFastStrategy(false);
46619
47075
  // starting plugins
@@ -46626,8 +47082,6 @@
46626
47082
  // This should be done after construction of LocalHistory due to order of
46627
47083
  // events
46628
47084
  this.setupSessionEvents();
46629
- // Load the initial revisions
46630
- this.session.loadInitialMessages(stateUpdateMessages);
46631
47085
  this.joinSession();
46632
47086
  if (config.snapshotRequested) {
46633
47087
  this.session.snapshot(this.exportData());
@@ -46691,14 +47145,18 @@
46691
47145
  this.finalize();
46692
47146
  }
46693
47147
  setupSession(revisionId) {
46694
- const session = new Session(buildRevisionLog(revisionId, this.state.recordChanges.bind(this.state), (command) => {
46695
- const result = this.checkDispatchAllowed(command);
46696
- if (!result.isSuccessful) {
46697
- return;
46698
- }
46699
- this.isReplayingCommand = true;
46700
- this.dispatchToHandlers(this.coreHandlers, command);
46701
- this.isReplayingCommand = false;
47148
+ const session = new Session(buildRevisionLog({
47149
+ initialRevisionId: revisionId,
47150
+ recordChanges: this.state.recordChanges.bind(this.state),
47151
+ dispatch: (command) => {
47152
+ const result = this.checkDispatchAllowed(command);
47153
+ if (!result.isSuccessful) {
47154
+ return;
47155
+ }
47156
+ this.isReplayingCommand = true;
47157
+ this.dispatchToHandlers(this.coreHandlers, command);
47158
+ this.isReplayingCommand = false;
47159
+ },
46702
47160
  }), this.config.transportService, revisionId);
46703
47161
  return session;
46704
47162
  }
@@ -46780,7 +47238,7 @@
46780
47238
  return new DispatchResult(results.flat());
46781
47239
  }
46782
47240
  checkDispatchAllowedLocalCommand(command) {
46783
- const results = this.handlers.map((handler) => handler.allowDispatch(command));
47241
+ const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
46784
47242
  return new DispatchResult(results.flat());
46785
47243
  }
46786
47244
  finalize() {
@@ -47099,9 +47557,9 @@
47099
47557
  Object.defineProperty(exports, '__esModule', { value: true });
47100
47558
 
47101
47559
 
47102
- __info__.version = '16.3.0-alpha.7';
47103
- __info__.date = '2023-05-02T13:08:09.275Z';
47104
- __info__.hash = '77c8f5f';
47560
+ __info__.version = '16.3.0';
47561
+ __info__.date = '2023-05-17T14:44:57.860Z';
47562
+ __info__.hash = '33b37e4';
47105
47563
 
47106
47564
 
47107
47565
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);