@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.
@@ -240,8 +240,8 @@ const HEADER_FONT_SIZE = 11;
240
240
  const DEFAULT_FONT = "'Roboto', arial";
241
241
  const DEFAULT_VERTICAL_ALIGN = "bottom";
242
242
  // Borders
243
- const DEFAULT_BORDER_DESC = ["thin", "#000"];
244
- const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
243
+ const DEFAULT_BORDER_DESC = { style: "thin", color: "#000000" };
244
+ const DEFAULT_FILTER_BORDER_DESC = { style: "thin", color: FILTERS_COLOR };
245
245
  // Ranges
246
246
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
247
247
  // Max Number of history steps kept in memory
@@ -1105,6 +1105,18 @@ function replaceSpecialSpaces(text) {
1105
1105
  text = text.replace(whiteSpaceRegexp, " ");
1106
1106
  text = text.replace(newLineRegex, NEWLINE);
1107
1107
  return text;
1108
+ }
1109
+ /**
1110
+ * Determine if the numbers are consecutive.
1111
+ */
1112
+ function isConsecutive(iterable) {
1113
+ const array = Array.from(iterable).sort((a, b) => a - b); // sort numerically rather than lexicographically
1114
+ for (let i = 1; i < array.length; i++) {
1115
+ if (array[i] - array[i - 1] !== 1) {
1116
+ return false;
1117
+ }
1118
+ }
1119
+ return true;
1108
1120
  }
1109
1121
 
1110
1122
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
@@ -4247,6 +4259,7 @@ const coreTypes = new Set([
4247
4259
  "SET_FORMATTING",
4248
4260
  "CLEAR_FORMATTING",
4249
4261
  "SET_BORDER",
4262
+ "SET_ZONE_BORDERS",
4250
4263
  /** CHART */
4251
4264
  "CREATE_CHART",
4252
4265
  "UPDATE_CHART",
@@ -4387,8 +4400,10 @@ exports.CommandResult = void 0;
4387
4400
  CommandResult[CommandResult["EmptySplitSeparator"] = 89] = "EmptySplitSeparator";
4388
4401
  CommandResult[CommandResult["SplitWillOverwriteContent"] = 90] = "SplitWillOverwriteContent";
4389
4402
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4403
+ CommandResult[CommandResult["NoActiveSheet"] = 92] = "NoActiveSheet";
4390
4404
  })(exports.CommandResult || (exports.CommandResult = {}));
4391
4405
 
4406
+ const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
4392
4407
  var DIRECTION;
4393
4408
  (function (DIRECTION) {
4394
4409
  DIRECTION["UP"] = "up";
@@ -5482,12 +5497,6 @@ css /* scss */ `
5482
5497
  cursor: pointer;
5483
5498
  user-select: none;
5484
5499
 
5485
- .o-menu-item-name {
5486
- overflow: hidden;
5487
- white-space: nowrap;
5488
- text-overflow: ellipsis;
5489
- }
5490
-
5491
5500
  &.o-menu-root {
5492
5501
  display: flex;
5493
5502
  justify-content: space-between;
@@ -5498,10 +5507,6 @@ css /* scss */ `
5498
5507
  margin: 0px 8px 0px 0px;
5499
5508
  width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5500
5509
  line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5501
-
5502
- .o-icon {
5503
- color: ${ICONS_COLOR};
5504
- }
5505
5510
  }
5506
5511
  .o-menu-item-root {
5507
5512
  width: 10px;
@@ -5515,6 +5520,11 @@ css /* scss */ `
5515
5520
  .o-menu-item-description {
5516
5521
  color: grey;
5517
5522
  }
5523
+ .o-menu-item-icon {
5524
+ .o-icon {
5525
+ color: ${ICONS_COLOR};
5526
+ }
5527
+ }
5518
5528
  }
5519
5529
  &.disabled {
5520
5530
  color: ${MENU_ITEM_DISABLED_COLOR};
@@ -6331,8 +6341,14 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6331
6341
  : undefined));
6332
6342
  }
6333
6343
  }
6344
+ else if (zone.left === zone.right && zone.top === zone.bottom) {
6345
+ // A single cell. If it's only the title, the dataset is not added.
6346
+ if (!dataSetsHaveTitle) {
6347
+ dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6348
+ }
6349
+ }
6334
6350
  else {
6335
- /* 1 cell, 1 row or 1 column */
6351
+ /* 1 row or 1 column */
6336
6352
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6337
6353
  ? {
6338
6354
  top: zone.top,
@@ -8943,7 +8959,7 @@ const DELETE_CELL_SHIFT_LEFT = (env) => {
8943
8959
  handlePasteResult(env, result);
8944
8960
  };
8945
8961
  const MENU_INSERT_ROWS_NAME = (env) => {
8946
- const number = getColumnsNumber(env);
8962
+ const number = getRowsNumber(env);
8947
8963
  return number === 1 ? _lt("Insert row") : _lt("Insert %s rows", number.toString());
8948
8964
  };
8949
8965
  const MENU_INSERT_ROWS_BEFORE_NAME = (env) => {
@@ -15308,7 +15324,11 @@ const LOOKUP = {
15308
15324
  : getNormalizedValueFromRowRange;
15309
15325
  const rangeLength = verticalSearch ? nbRow : nbCol;
15310
15326
  const index = dichotomicSearch(searchArray, _searchKey, "nextSmaller", "asc", rangeLength, getElement);
15311
- assertAvailable(searchArray[0][index], searchKey);
15327
+ if (index === -1)
15328
+ assertAvailable(undefined, searchKey);
15329
+ verticalSearch
15330
+ ? assertAvailable(searchArray[0][index], searchKey)
15331
+ : assertAvailable(searchArray[index][nbRow - 1], searchKey);
15312
15332
  if (resultRange === undefined) {
15313
15333
  return (verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1]);
15314
15334
  }
@@ -16215,75 +16235,97 @@ for (let category of categories) {
16215
16235
 
16216
16236
  const insertRow = {
16217
16237
  name: MENU_INSERT_ROWS_NAME,
16218
- isVisible: IS_ONLY_ONE_RANGE,
16238
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16239
+ IS_ONLY_ONE_RANGE(env) &&
16240
+ env.model.getters.getActiveCols().size === 0,
16219
16241
  icon: "o-spreadsheet-Icon.INSERT_ROW",
16220
16242
  };
16221
16243
  const rowInsertRowBefore = {
16222
16244
  name: ROW_INSERT_ROWS_BEFORE_NAME,
16223
16245
  execute: INSERT_ROWS_BEFORE_ACTION,
16246
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16247
+ IS_ONLY_ONE_RANGE(env) &&
16248
+ env.model.getters.getActiveCols().size === 0,
16249
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16224
16250
  };
16225
16251
  const topBarInsertRowsBefore = {
16226
16252
  ...rowInsertRowBefore,
16227
16253
  name: MENU_INSERT_ROWS_BEFORE_NAME,
16228
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16229
16254
  };
16230
16255
  const cellInsertRowsBefore = {
16231
16256
  ...rowInsertRowBefore,
16232
16257
  name: CELL_INSERT_ROWS_BEFORE_NAME,
16233
16258
  isVisible: IS_ONLY_ONE_RANGE,
16234
- icon: "o-spreadsheet-Icon.INSERT_ROW",
16259
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16235
16260
  };
16236
16261
  const rowInsertRowsAfter = {
16237
16262
  execute: INSERT_ROWS_AFTER_ACTION,
16238
16263
  name: ROW_INSERT_ROWS_AFTER_NAME,
16264
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16265
+ IS_ONLY_ONE_RANGE(env) &&
16266
+ env.model.getters.getActiveCols().size === 0,
16267
+ icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER",
16239
16268
  };
16240
16269
  const topBarInsertRowsAfter = {
16241
16270
  ...rowInsertRowsAfter,
16242
16271
  name: MENU_INSERT_ROWS_AFTER_NAME,
16243
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16244
16272
  };
16245
16273
  const insertCol = {
16246
16274
  name: MENU_INSERT_COLUMNS_NAME,
16247
- isVisible: IS_ONLY_ONE_RANGE,
16275
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16276
+ IS_ONLY_ONE_RANGE(env) &&
16277
+ env.model.getters.getActiveRows().size === 0,
16248
16278
  icon: "o-spreadsheet-Icon.INSERT_COL",
16249
16279
  };
16250
16280
  const colInsertColsBefore = {
16251
16281
  name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,
16252
16282
  execute: INSERT_COLUMNS_BEFORE_ACTION,
16283
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16284
+ IS_ONLY_ONE_RANGE(env) &&
16285
+ env.model.getters.getActiveRows().size === 0,
16286
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16253
16287
  };
16254
16288
  const topBarInsertColsBefore = {
16255
16289
  ...colInsertColsBefore,
16256
16290
  name: MENU_INSERT_COLUMNS_BEFORE_NAME,
16257
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16258
16291
  };
16259
16292
  const cellInsertColsBefore = {
16260
16293
  ...colInsertColsBefore,
16261
16294
  name: CELL_INSERT_COLUMNS_BEFORE_NAME,
16262
16295
  isVisible: IS_ONLY_ONE_RANGE,
16263
- icon: "o-spreadsheet-Icon.INSERT_COL",
16296
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16264
16297
  };
16265
16298
  const colInsertColsAfter = {
16266
16299
  name: COLUMN_INSERT_COLUMNS_AFTER_NAME,
16267
16300
  execute: INSERT_COLUMNS_AFTER_ACTION,
16301
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16302
+ IS_ONLY_ONE_RANGE(env) &&
16303
+ env.model.getters.getActiveRows().size === 0,
16304
+ icon: "o-spreadsheet-Icon.INSERT_COL_AFTER",
16268
16305
  };
16269
16306
  const topBarInsertColsAfter = {
16270
16307
  ...colInsertColsAfter,
16271
16308
  name: MENU_INSERT_COLUMNS_AFTER_NAME,
16272
16309
  execute: INSERT_COLUMNS_AFTER_ACTION,
16273
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16274
16310
  };
16275
16311
  const insertCell = {
16276
16312
  name: _lt("Insert cells"),
16277
- isVisible: IS_ONLY_ONE_RANGE,
16313
+ isVisible: (env) => IS_ONLY_ONE_RANGE(env) &&
16314
+ env.model.getters.getActiveCols().size === 0 &&
16315
+ env.model.getters.getActiveRows().size === 0,
16278
16316
  icon: "o-spreadsheet-Icon.INSERT_CELL",
16279
16317
  };
16280
16318
  const insertCellShiftDown = {
16281
16319
  name: _lt("Insert cells and shift down"),
16282
16320
  execute: INSERT_CELL_SHIFT_DOWN,
16321
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16322
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_DOWN",
16283
16323
  };
16284
16324
  const insertCellShiftRight = {
16285
16325
  name: _lt("Insert cells and shift right"),
16286
16326
  execute: INSERT_CELL_SHIFT_RIGHT,
16327
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16328
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_RIGHT",
16287
16329
  };
16288
16330
  const insertChart = {
16289
16331
  name: _lt("Chart"),
@@ -16481,6 +16523,7 @@ const splitToColumns = {
16481
16523
  sequence: 1,
16482
16524
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
16483
16525
  isEnabled: (env) => env.model.getters.isSingleColSelected(),
16526
+ icon: "o-spreadsheet-Icon.SPLIT_TEXT",
16484
16527
  };
16485
16528
 
16486
16529
  const formatNumberAutomatic = {
@@ -16696,60 +16739,6 @@ const clearFormat = {
16696
16739
  execute: FORMAT_CLEARFORMAT_ACTION,
16697
16740
  icon: "o-spreadsheet-Icon.CLEAR_FORMAT",
16698
16741
  };
16699
- const borders = {
16700
- name: _lt("Borders"),
16701
- icon: "o-spreadsheet-Icon.BORDERS",
16702
- };
16703
- const bordersAll = {
16704
- name: _lt("All borders"),
16705
- execute: (env) => setBorder(env, "all"),
16706
- icon: "o-spreadsheet-Icon.BORDERS",
16707
- };
16708
- const bordersInner = {
16709
- name: _lt("Inner borders"),
16710
- execute: (env) => setBorder(env, "hv"),
16711
- icon: "o-spreadsheet-Icon.BORDER_HV",
16712
- };
16713
- const bordersHorizontal = {
16714
- name: _lt("Horizontal borders"),
16715
- execute: (env) => setBorder(env, "h"),
16716
- icon: "o-spreadsheet-Icon.BORDER_H",
16717
- };
16718
- const bordersVertical = {
16719
- name: _lt("Vertical borders"),
16720
- execute: (env) => setBorder(env, "v"),
16721
- icon: "o-spreadsheet-Icon.BORDER_V",
16722
- };
16723
- const bordersExternal = {
16724
- name: _lt("External borders"),
16725
- execute: (env) => setBorder(env, "external"),
16726
- icon: "o-spreadsheet-Icon.BORDER_EXTERNAL",
16727
- };
16728
- const bordersLeft = {
16729
- name: _lt("Left borders"),
16730
- execute: (env) => setBorder(env, "left"),
16731
- icon: "o-spreadsheet-Icon.BORDER_LEFT",
16732
- };
16733
- const bordersTop = {
16734
- name: _lt("Top borders"),
16735
- execute: (env) => setBorder(env, "top"),
16736
- icon: "o-spreadsheet-Icon.BORDER_TOP",
16737
- };
16738
- const bordersRight = {
16739
- name: _lt("Right borders"),
16740
- execute: (env) => setBorder(env, "right"),
16741
- icon: "o-spreadsheet-Icon.BORDER_RIGHT",
16742
- };
16743
- const bordersBottom = {
16744
- name: _lt("Bottom borders"),
16745
- execute: (env) => setBorder(env, "bottom"),
16746
- icon: "o-spreadsheet-Icon.BORDER_BOTTOM",
16747
- };
16748
- const bordersClear = {
16749
- name: _lt("Clear borders"),
16750
- execute: (env) => setBorder(env, "clear"),
16751
- icon: "o-spreadsheet-Icon.BORDER_CLEAR",
16752
- };
16753
16742
  function fontSizeMenuBuilder() {
16754
16743
  return FONT_SIZES.map((fs) => {
16755
16744
  return {
@@ -16762,24 +16751,17 @@ function fontSizeMenuBuilder() {
16762
16751
  });
16763
16752
  }
16764
16753
  function isAutomaticFormatSelected(env) {
16765
- const activeCell = env.model.getters.getActiveCell();
16754
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16766
16755
  return !activeCell || !activeCell.format;
16767
16756
  }
16768
16757
  function isFormatSelected(env, format) {
16769
- const activeCell = env.model.getters.getActiveCell();
16770
- return activeCell && activeCell.format === format;
16758
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16759
+ return activeCell?.format === format;
16771
16760
  }
16772
16761
  function isFontSizeSelected(env, fontSize) {
16773
16762
  const currentFontSize = env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
16774
16763
  return currentFontSize === fontSize;
16775
16764
  }
16776
- function setBorder(env, command) {
16777
- env.model.dispatch("SET_FORMATTING", {
16778
- sheetId: env.model.getters.getActiveSheetId(),
16779
- target: env.model.getters.getSelectedZones(),
16780
- border: command,
16781
- });
16782
- }
16783
16765
  function getHorizontalAlign(env) {
16784
16766
  const style = env.model.getters.getCurrentStyle();
16785
16767
  if (style.align) {
@@ -16826,18 +16808,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
16826
16808
  fillColor: fillColor,
16827
16809
  formatCF: formatCF,
16828
16810
  paintFormat: paintFormat,
16829
- clearFormat: clearFormat,
16830
- borders: borders,
16831
- bordersAll: bordersAll,
16832
- bordersInner: bordersInner,
16833
- bordersHorizontal: bordersHorizontal,
16834
- bordersVertical: bordersVertical,
16835
- bordersExternal: bordersExternal,
16836
- bordersLeft: bordersLeft,
16837
- bordersTop: bordersTop,
16838
- bordersRight: bordersRight,
16839
- bordersBottom: bordersBottom,
16840
- bordersClear: bordersClear
16811
+ clearFormat: clearFormat
16841
16812
  });
16842
16813
 
16843
16814
  function interactiveFreezeColumnsRows(env, dimension, base) {
@@ -16853,6 +16824,7 @@ const hideCols = {
16853
16824
  name: HIDE_COLUMNS_NAME,
16854
16825
  execute: HIDE_COLUMNS_ACTION,
16855
16826
  isVisible: NOT_ALL_VISIBLE_COLS_SELECTED,
16827
+ icon: "o-spreadsheet-Icon.HIDE_COL",
16856
16828
  };
16857
16829
  const unhideCols = {
16858
16830
  name: _lt("Unhide columns"),
@@ -16874,6 +16846,7 @@ const hideRows = {
16874
16846
  name: HIDE_ROWS_NAME,
16875
16847
  execute: HIDE_ROWS_ACTION,
16876
16848
  isVisible: NOT_ALL_VISIBLE_ROWS_SELECTED,
16849
+ icon: "o-spreadsheet-Icon.HIDE_ROW",
16877
16850
  };
16878
16851
  const unhideRows = {
16879
16852
  name: _lt("Unhide rows"),
@@ -17077,19 +17050,21 @@ colMenuRegistry
17077
17050
  .add("delete_column", {
17078
17051
  ...deleteCols,
17079
17052
  sequence: 90,
17053
+ icon: "o-spreadsheet-Icon.DELETE",
17080
17054
  })
17081
17055
  .add("clear_column", {
17082
17056
  ...clearCols,
17083
17057
  sequence: 100,
17058
+ icon: "o-spreadsheet-Icon.CLEAR",
17084
17059
  })
17085
17060
  .add("hide_columns", {
17086
17061
  ...hideCols,
17087
- sequence: 85,
17062
+ sequence: 105,
17088
17063
  separator: true,
17089
17064
  })
17090
17065
  .add("unhide_columns", {
17091
17066
  ...unhideCols,
17092
- sequence: 86,
17067
+ sequence: 106,
17093
17068
  separator: true,
17094
17069
  })
17095
17070
  .add("conditional_formatting", {
@@ -17187,10 +17162,12 @@ rowMenuRegistry
17187
17162
  .add("delete_row", {
17188
17163
  ...deleteRows,
17189
17164
  sequence: 70,
17165
+ icon: "o-spreadsheet-Icon.DELETE",
17190
17166
  })
17191
17167
  .add("clear_row", {
17192
17168
  ...clearRows,
17193
17169
  sequence: 80,
17170
+ icon: "o-spreadsheet-Icon.CLEAR",
17194
17171
  })
17195
17172
  .add("hide_rows", {
17196
17173
  ...hideRows,
@@ -17573,7 +17550,7 @@ topbarMenuRegistry
17573
17550
  })
17574
17551
  .addChild("sort_range", ["data"], {
17575
17552
  ...sortRange,
17576
- sequence: 20,
17553
+ sequence: 10,
17577
17554
  separator: true,
17578
17555
  })
17579
17556
  .addChild("sort_ascending", ["data", "sort_range"], {
@@ -17586,16 +17563,16 @@ topbarMenuRegistry
17586
17563
  })
17587
17564
  .addChild("split_to_columns", ["data"], {
17588
17565
  ...splitToColumns,
17589
- sequence: 10,
17566
+ sequence: 20,
17590
17567
  separator: true,
17591
17568
  })
17592
17569
  .addChild("add_data_filter", ["data"], {
17593
17570
  ...addDataFilter,
17594
- sequence: 10,
17571
+ sequence: 30,
17595
17572
  })
17596
17573
  .addChild("remove_data_filter", ["data"], {
17597
17574
  ...removeDataFilter,
17598
- sequence: 10,
17575
+ sequence: 30,
17599
17576
  });
17600
17577
 
17601
17578
  class OTRegistry extends Registry {
@@ -17683,6 +17660,12 @@ css /* scss */ `
17683
17660
  height: 25px;
17684
17661
  }
17685
17662
  }
17663
+ /** Make the character a bit bigger
17664
+ compared to its neighbor INPUT box */
17665
+ .o-remove-selection {
17666
+ font-weight: bold;
17667
+ font-size: calc(100% + 4px);
17668
+ }
17686
17669
  }
17687
17670
  `;
17688
17671
  /**
@@ -17778,6 +17761,9 @@ class SelectionInput extends owl.Component {
17778
17761
  this.confirm();
17779
17762
  }
17780
17763
  }
17764
+ extractRanges(value) {
17765
+ return this.props.hasSingleRange ? value.split(",")[0] : value;
17766
+ }
17781
17767
  focus(rangeId) {
17782
17768
  this.state.isMissing = false;
17783
17769
  this.state.mode = "select-range";
@@ -17796,10 +17782,11 @@ class SelectionInput extends owl.Component {
17796
17782
  }
17797
17783
  onInputChanged(rangeId, ev) {
17798
17784
  const target = ev.target;
17785
+ const value = this.extractRanges(target.value);
17799
17786
  this.env.model.dispatch("CHANGE_RANGE", {
17800
17787
  id: this.id,
17801
17788
  rangeId,
17802
- value: target.value,
17789
+ value,
17803
17790
  });
17804
17791
  this.triggerChange();
17805
17792
  }
@@ -18692,6 +18679,18 @@ ChartPanel.props = {
18692
18679
  onCloseSidePanel: Function,
18693
18680
  };
18694
18681
 
18682
+ css /* scss */ `
18683
+ .o-spreadsheet {
18684
+ .o-icon {
18685
+ .small-text {
18686
+ font: bold 9px sans-serif;
18687
+ }
18688
+ .heavy-text {
18689
+ font: bold 16px sans-serif;
18690
+ }
18691
+ }
18692
+ }
18693
+ `;
18695
18694
  // -----------------------------------------------------------------------------
18696
18695
  // We need here the svg of the icons that we need to convert to images for the renderer
18697
18696
  // -----------------------------------------------------------------------------
@@ -20568,14 +20567,31 @@ class ContentEditableHelper {
20568
20567
  * */
20569
20568
  getCurrentSelection() {
20570
20569
  let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();
20571
- let startSizeBefore = this.findSizeBeforeElement(startElement);
20572
- let endSizeBefore = this.findSizeBeforeElement(endElement);
20570
+ let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
20571
+ let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
20573
20572
  return {
20574
- start: startSizeBefore + startSelectionOffset,
20575
- end: endSizeBefore + endSelectionOffset,
20573
+ start: startSizeBefore,
20574
+ end: endSizeBefore,
20576
20575
  };
20577
20576
  }
20578
- findSizeBeforeElement(nodeToFind) {
20577
+ /**
20578
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
20579
+ * The selected node is either a Text node or an Element node.
20580
+ *
20581
+ * case 1 -Text node:
20582
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
20583
+ * content length of all previous nodes.
20584
+ *
20585
+ * case 2 - Element node:
20586
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
20587
+ * all the bnodes prior to the selected node as well as the content of the child node before the offset.
20588
+ *
20589
+ * See the MDN documentation for more details.
20590
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
20591
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
20592
+ *
20593
+ */
20594
+ findSelectionIndex(nodeToFind, nodeOffset) {
20579
20595
  let usedCharacters = 0;
20580
20596
  let it = iterateChildren(this.el);
20581
20597
  let current = it.next();
@@ -20602,6 +20618,28 @@ class ContentEditableHelper {
20602
20618
  if (current.value !== nodeToFind) {
20603
20619
  throw new Error("Cannot find the node in the children of the element");
20604
20620
  }
20621
+ else {
20622
+ if (!current.value.hasChildNodes()) {
20623
+ usedCharacters += nodeOffset;
20624
+ }
20625
+ else {
20626
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
20627
+ usedCharacters += children.reduce((acc, child, index) => {
20628
+ if (child.textContent !== null) {
20629
+ // need to account for paragraph nodes that implicitely add a new line
20630
+ // except for the last paragraph
20631
+ let chars = child.textContent.length;
20632
+ if (child.nodeName === "P" && index !== children.length - 1) {
20633
+ chars++;
20634
+ }
20635
+ return acc + chars;
20636
+ }
20637
+ else {
20638
+ return acc;
20639
+ }
20640
+ }, 0);
20641
+ }
20642
+ }
20605
20643
  if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
20606
20644
  usedCharacters++;
20607
20645
  }
@@ -21402,13 +21440,13 @@ GridComposer.props = {
21402
21440
 
21403
21441
  const CSS$1 = css /* scss */ `
21404
21442
  .o-filter-icon {
21443
+ color: ${FILTERS_COLOR};
21405
21444
  position: absolute;
21406
21445
  display: flex;
21407
21446
  align-items: center;
21408
21447
  justify-content: center;
21409
21448
  width: ${FILTER_ICON_EDGE_LENGTH}px;
21410
21449
  height: ${FILTER_ICON_EDGE_LENGTH}px;
21411
- color: #4a4f59;
21412
21450
  }
21413
21451
  .o-filter-icon:hover {
21414
21452
  background: ${FILTERS_COLOR};
@@ -24139,17 +24177,17 @@ const CELL_TYPE_CONVERSION_MAP = {
24139
24177
  const BORDER_STYLE_CONVERSION_MAP = {
24140
24178
  dashDot: "thin",
24141
24179
  dashDotDot: "thin",
24142
- dashed: "thin",
24143
- dotted: "thin",
24180
+ dashed: "dashed",
24181
+ dotted: "dotted",
24144
24182
  double: "thin",
24145
24183
  hair: "thin",
24146
- medium: "thin",
24184
+ medium: "medium",
24147
24185
  mediumDashDot: "thin",
24148
24186
  mediumDashDotDot: "thin",
24149
24187
  mediumDashed: "thin",
24150
24188
  none: undefined,
24151
24189
  slantDashDot: "thin",
24152
- thick: "thin",
24190
+ thick: "thick",
24153
24191
  thin: "thin",
24154
24192
  };
24155
24193
  /** Conversion map Horizontal Alignment in XLSX <=> Horizontal Alignment in o_spreadsheet*/
@@ -24648,7 +24686,7 @@ function convertBorderDescr$1(borderDescr, warningManager) {
24648
24686
  return undefined;
24649
24687
  addBorderDescrWarnings(borderDescr, warningManager);
24650
24688
  const style = BORDER_STYLE_CONVERSION_MAP[borderDescr.style];
24651
- return style ? [style, convertColor(borderDescr.color)] : undefined;
24689
+ return style ? { style, color: convertColor(borderDescr.color) } : undefined;
24652
24690
  }
24653
24691
  function convertStyles(data, warningManager) {
24654
24692
  const stylesArray = data.styles.map((style) => {
@@ -26046,8 +26084,8 @@ function convertBorderDescr(descr) {
26046
26084
  return undefined;
26047
26085
  }
26048
26086
  return {
26049
- style: descr[0],
26050
- color: { rgb: descr[1] },
26087
+ style: descr.style,
26088
+ color: { rgb: descr.color },
26051
26089
  };
26052
26090
  }
26053
26091
  function extractStyle(cell, data) {
@@ -26625,7 +26663,7 @@ const TABLE_HEADER_STYLE = {
26625
26663
  const TABLE_HIGHLIGHTED_CELL_STYLE = {
26626
26664
  bold: true,
26627
26665
  };
26628
- const TABLE_BORDER_STYLE = ["thin", "#000000FF"];
26666
+ const TABLE_BORDER_STYLE = { style: "thin", color: "#000000FF" };
26629
26667
  /**
26630
26668
  * Convert the imported XLSX tables.
26631
26669
  *
@@ -28194,7 +28232,7 @@ function normalizeV9(formula) {
28194
28232
  * a breaking change is made in the way the state is handled, and an upgrade
28195
28233
  * function should be defined
28196
28234
  */
28197
- const CURRENT_VERSION = 12;
28235
+ const CURRENT_VERSION = 13;
28198
28236
  const INITIAL_SHEET_ID = "Sheet1";
28199
28237
  /**
28200
28238
  * This function tries to load anything that could look like a valid
@@ -28458,6 +28496,25 @@ const MIGRATIONS = [
28458
28496
  return data;
28459
28497
  },
28460
28498
  },
28499
+ {
28500
+ description: "Change Border description structure",
28501
+ from: 12,
28502
+ to: 13,
28503
+ applyMigration(data) {
28504
+ for (const borderId in data.borders) {
28505
+ const border = data.borders[borderId];
28506
+ for (const position in border) {
28507
+ if (Array.isArray(border[position])) {
28508
+ border[position] = {
28509
+ style: border[position][0],
28510
+ color: border[position][1],
28511
+ };
28512
+ }
28513
+ }
28514
+ }
28515
+ return data;
28516
+ },
28517
+ },
28461
28518
  ];
28462
28519
  /**
28463
28520
  * This function is used to repair faulty data independently of the migration.
@@ -28793,7 +28850,7 @@ class CorePlugin extends BasePlugin {
28793
28850
  * - borders
28794
28851
  */
28795
28852
  class BordersPlugin extends CorePlugin {
28796
- static getters = ["getCellBorder"];
28853
+ static getters = ["getCellBorder", "getBordersColors"];
28797
28854
  borders = {};
28798
28855
  // ---------------------------------------------------------------------------
28799
28856
  // Command Handling
@@ -28824,10 +28881,15 @@ class BordersPlugin extends CorePlugin {
28824
28881
  case "SET_BORDER":
28825
28882
  this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
28826
28883
  break;
28827
- case "SET_FORMATTING":
28884
+ case "SET_ZONE_BORDERS":
28828
28885
  if (cmd.border) {
28829
28886
  const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
28830
- this.setBorders(cmd.sheetId, target, cmd.border);
28887
+ this.setBorders(cmd.sheetId, target, cmd.border.position, cmd.border.color === ""
28888
+ ? undefined
28889
+ : {
28890
+ style: cmd.border.style || DEFAULT_BORDER_DESC.style,
28891
+ color: cmd.border.color || DEFAULT_BORDER_DESC.color,
28892
+ });
28831
28893
  }
28832
28894
  break;
28833
28895
  case "CLEAR_FORMATTING":
@@ -28916,6 +28978,23 @@ class BordersPlugin extends CorePlugin {
28916
28978
  }
28917
28979
  return border;
28918
28980
  }
28981
+ getBordersColors(sheetId) {
28982
+ const colors = [];
28983
+ const sheetBorders = this.borders[sheetId];
28984
+ if (sheetBorders) {
28985
+ for (const borders of sheetBorders.filter(isDefined$1)) {
28986
+ for (const cellBorder of borders) {
28987
+ if (cellBorder?.horizontal) {
28988
+ colors.push(cellBorder.horizontal.color);
28989
+ }
28990
+ if (cellBorder?.vertical) {
28991
+ colors.push(cellBorder.vertical.color);
28992
+ }
28993
+ }
28994
+ }
28995
+ }
28996
+ return colors;
28997
+ }
28919
28998
  // ---------------------------------------------------------------------------
28920
28999
  // Private
28921
29000
  // ---------------------------------------------------------------------------
@@ -29127,43 +29206,43 @@ class BordersPlugin extends CorePlugin {
29127
29206
  * Set the borders of a zone by computing the borders to add from the given
29128
29207
  * command
29129
29208
  */
29130
- setBorders(sheetId, zones, command) {
29131
- if (command === "clear") {
29209
+ setBorders(sheetId, zones, position, border) {
29210
+ if (position === "clear") {
29132
29211
  return this.clearBorders(sheetId, zones);
29133
29212
  }
29134
29213
  for (let zone of zones) {
29135
- if (command === "h" || command === "hv" || command === "all") {
29214
+ if (position === "h" || position === "hv" || position === "all") {
29136
29215
  for (let row = zone.top + 1; row <= zone.bottom; row++) {
29137
29216
  for (let col = zone.left; col <= zone.right; col++) {
29138
- this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });
29217
+ this.addBorder(sheetId, col, row, { top: border });
29139
29218
  }
29140
29219
  }
29141
29220
  }
29142
- if (command === "v" || command === "hv" || command === "all") {
29221
+ if (position === "v" || position === "hv" || position === "all") {
29143
29222
  for (let row = zone.top; row <= zone.bottom; row++) {
29144
29223
  for (let col = zone.left + 1; col <= zone.right; col++) {
29145
- this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });
29224
+ this.addBorder(sheetId, col, row, { left: border });
29146
29225
  }
29147
29226
  }
29148
29227
  }
29149
- if (command === "left" || command === "all" || command === "external") {
29228
+ if (position === "left" || position === "all" || position === "external") {
29150
29229
  for (let row = zone.top; row <= zone.bottom; row++) {
29151
- this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });
29230
+ this.addBorder(sheetId, zone.left, row, { left: border });
29152
29231
  }
29153
29232
  }
29154
- if (command === "right" || command === "all" || command === "external") {
29233
+ if (position === "right" || position === "all" || position === "external") {
29155
29234
  for (let row = zone.top; row <= zone.bottom; row++) {
29156
- this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });
29235
+ this.addBorder(sheetId, zone.right + 1, row, { left: border });
29157
29236
  }
29158
29237
  }
29159
- if (command === "top" || command === "all" || command === "external") {
29238
+ if (position === "top" || position === "all" || position === "external") {
29160
29239
  for (let col = zone.left; col <= zone.right; col++) {
29161
- this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });
29240
+ this.addBorder(sheetId, col, zone.top, { top: border });
29162
29241
  }
29163
29242
  }
29164
- if (command === "bottom" || command === "all" || command === "external") {
29243
+ if (position === "bottom" || position === "all" || position === "external") {
29165
29244
  for (let col = zone.left; col <= zone.right; col++) {
29166
- this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });
29245
+ this.addBorder(sheetId, col, zone.bottom + 1, { top: border });
29167
29246
  }
29168
29247
  }
29169
29248
  }
@@ -29177,16 +29256,22 @@ class BordersPlugin extends CorePlugin {
29177
29256
  const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });
29178
29257
  this.clearBorders(sheetId, [zone]);
29179
29258
  if (bordersTopLeft?.top) {
29180
- this.setBorders(sheetId, [{ ...zone, bottom: top }], "top");
29259
+ this.setBorders(sheetId, [{ ...zone, bottom: top }], "top", bordersTopLeft.top);
29181
29260
  }
29182
29261
  if (bordersTopLeft?.left) {
29183
- this.setBorders(sheetId, [{ ...zone, right: left }], "left");
29262
+ this.setBorders(sheetId, [{ ...zone, right: left }], "left", bordersTopLeft.left);
29263
+ }
29264
+ if (bordersBottomRight?.bottom) {
29265
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersBottomRight.bottom);
29184
29266
  }
29185
- if (bordersBottomRight?.bottom || bordersTopLeft?.bottom) {
29186
- this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom");
29267
+ else if (bordersTopLeft?.bottom) {
29268
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersTopLeft.bottom);
29187
29269
  }
29188
- if (bordersBottomRight?.right || bordersTopLeft?.right) {
29189
- this.setBorders(sheetId, [{ ...zone, left: right }], "right");
29270
+ if (bordersBottomRight?.right) {
29271
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersBottomRight.right);
29272
+ }
29273
+ else if (bordersTopLeft?.right) {
29274
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29190
29275
  }
29191
29276
  }
29192
29277
  // ---------------------------------------------------------------------------
@@ -32122,6 +32207,8 @@ class SheetPlugin extends CorePlugin {
32122
32207
  "getSheetSize",
32123
32208
  "getSheetZone",
32124
32209
  "getPaneDivisions",
32210
+ "checkZonesExistInSheet",
32211
+ "getCommandZones",
32125
32212
  ];
32126
32213
  sheetIdsMapName = {};
32127
32214
  orderedSheetIds = [];
@@ -32131,7 +32218,7 @@ class SheetPlugin extends CorePlugin {
32131
32218
  // Command Handling
32132
32219
  // ---------------------------------------------------------------------------
32133
32220
  allowDispatch(cmd) {
32134
- const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZones)(cmd);
32221
+ const genericChecks = this.chainValidations(this.checkSheetExists, this.checkZonesAreInSheet)(cmd);
32135
32222
  if (genericChecks !== 0 /* CommandResult.Success */) {
32136
32223
  return genericChecks;
32137
32224
  }
@@ -32471,6 +32558,37 @@ class SheetPlugin extends CorePlugin {
32471
32558
  .map(({ col, row }) => this.getCell({ sheetId, col, row }))
32472
32559
  .every((cell) => !cell || cell.content === "");
32473
32560
  }
32561
+ getCommandZones(cmd) {
32562
+ const zones = [];
32563
+ if ("zone" in cmd) {
32564
+ zones.push(cmd.zone);
32565
+ }
32566
+ if ("target" in cmd) {
32567
+ zones.push(...cmd.target);
32568
+ }
32569
+ if ("ranges" in cmd) {
32570
+ zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone));
32571
+ }
32572
+ if ("col" in cmd && "row" in cmd) {
32573
+ zones.push({ top: cmd.row, left: cmd.col, bottom: cmd.row, right: cmd.col });
32574
+ }
32575
+ return zones;
32576
+ }
32577
+ /**
32578
+ * Check if zones in the command are well formed and
32579
+ * not outside the sheet.
32580
+ */
32581
+ checkZonesExistInSheet(sheetId, zones) {
32582
+ if (!zones.every(isZoneValid))
32583
+ return 25 /* CommandResult.InvalidRange */;
32584
+ if (zones.length) {
32585
+ const sheetZone = this.getSheetZone(sheetId);
32586
+ return zones.every((zone) => isZoneInside(zone, sheetZone))
32587
+ ? 0 /* CommandResult.Success */
32588
+ : 18 /* CommandResult.TargetOutOfSheet */;
32589
+ }
32590
+ return 0 /* CommandResult.Success */;
32591
+ }
32474
32592
  updateCellPosition(cmd) {
32475
32593
  const { sheetId, cellId, col, row } = cmd;
32476
32594
  if (cellId) {
@@ -32919,9 +33037,6 @@ class SheetPlugin extends CorePlugin {
32919
33037
  }
32920
33038
  return { rowNumber, colNumber };
32921
33039
  }
32922
- // ----------------------------------------------------
32923
- // HIDE / SHOW
32924
- // ----------------------------------------------------
32925
33040
  /**
32926
33041
  * Check that any "sheetId" in the command matches an existing
32927
33042
  * sheet.
@@ -32939,27 +33054,10 @@ class SheetPlugin extends CorePlugin {
32939
33054
  * Check if zones in the command are well formed and
32940
33055
  * not outside the sheet.
32941
33056
  */
32942
- checkZones(cmd) {
32943
- const zones = [];
32944
- if ("zone" in cmd) {
32945
- zones.push(cmd.zone);
32946
- }
32947
- if ("target" in cmd && Array.isArray(cmd.target)) {
32948
- zones.push(...cmd.target);
32949
- }
32950
- if ("ranges" in cmd && Array.isArray(cmd.ranges)) {
32951
- zones.push(...cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData).zone));
32952
- }
32953
- if (!zones.every(isZoneValid)) {
32954
- return 25 /* CommandResult.InvalidRange */;
32955
- }
32956
- else if (zones.length && "sheetId" in cmd) {
32957
- const sheetZone = this.getSheetZone(cmd.sheetId);
32958
- return zones.every((zone) => isZoneInside(zone, sheetZone))
32959
- ? 0 /* CommandResult.Success */
32960
- : 18 /* CommandResult.TargetOutOfSheet */;
32961
- }
32962
- return 0 /* CommandResult.Success */;
33057
+ checkZonesAreInSheet(cmd) {
33058
+ if (!("sheetId" in cmd))
33059
+ return 0 /* CommandResult.Success */;
33060
+ return this.checkZonesExistInSheet(cmd.sheetId, this.getCommandZones(cmd));
32963
33061
  }
32964
33062
  }
32965
33063
 
@@ -33039,6 +33137,9 @@ class CustomColorsPlugin extends UIPlugin {
33039
33137
  case "UPDATE_CHART":
33040
33138
  case "CREATE_CHART":
33041
33139
  case "ADD_CONDITIONAL_FORMAT":
33140
+ case "SET_BORDER":
33141
+ case "SET_ZONE_BORDERS":
33142
+ case "SET_FORMATTING":
33042
33143
  this.shouldUpdateColors = true;
33043
33144
  }
33044
33145
  }
@@ -33053,8 +33154,7 @@ class CustomColorsPlugin extends UIPlugin {
33053
33154
  getCustomColors() {
33054
33155
  let usedColors = [];
33055
33156
  for (const sheetId of this.getters.getSheetIds()) {
33056
- const cells = Object.values(this.getters.getCells(sheetId));
33057
- usedColors = usedColors.concat(this.getColorsFromCells(cells), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
33157
+ usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
33058
33158
  }
33059
33159
  return sortWithClusters([
33060
33160
  ...new Set(
@@ -33064,7 +33164,8 @@ class CustomColorsPlugin extends UIPlugin {
33064
33164
  [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
33065
33165
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
33066
33166
  }
33067
- getColorsFromCells(cells) {
33167
+ getColorsFromCells(sheetId) {
33168
+ const cells = Object.values(this.getters.getCells(sheetId));
33068
33169
  const colors = new Set();
33069
33170
  for (const cell of cells) {
33070
33171
  if (cell.style?.textColor) {
@@ -33074,6 +33175,9 @@ class CustomColorsPlugin extends UIPlugin {
33074
33175
  colors.add(cell.style.fillColor);
33075
33176
  }
33076
33177
  }
33178
+ for (const color of this.getters.getBordersColors(sheetId)) {
33179
+ colors.add(color);
33180
+ }
33077
33181
  return [...colors];
33078
33182
  }
33079
33183
  getFormattingColors(sheetId) {
@@ -33347,7 +33451,7 @@ class EvaluationPlugin extends UIPlugin {
33347
33451
  // magic "empty" value
33348
33452
  // Returning {value: null} instead of undefined will ensure that we don't
33349
33453
  // fall back on the default value of the argument provided to the formula's compute function
33350
- return { value: null };
33454
+ return { value: null, format: cell?.format };
33351
33455
  }
33352
33456
  return getEvaluatedCell(cell);
33353
33457
  }
@@ -33544,7 +33648,7 @@ class EvaluationConditionalFormatPlugin extends UIPlugin {
33544
33648
  }
33545
33649
  break;
33546
33650
  case "PASTE_CONDITIONAL_FORMAT":
33547
- this.pasteCf(cmd.origin, cmd.target, cmd.operation);
33651
+ this.pasteCf(cmd.originPosition, cmd.targetPosition, cmd.operation);
33548
33652
  break;
33549
33653
  }
33550
33654
  }
@@ -36176,6 +36280,7 @@ function transformPositionWithMerge(cmd, executed) {
36176
36280
 
36177
36281
  class Revision {
36178
36282
  rootCommand;
36283
+ timestamp;
36179
36284
  id;
36180
36285
  clientId;
36181
36286
  _commands = [];
@@ -36189,8 +36294,9 @@ class Revision {
36189
36294
  * - clientId: Client who initiated the action
36190
36295
  * - changes: List of changes applied on the state.
36191
36296
  */
36192
- constructor(id, clientId, commands, rootCommand, changes) {
36297
+ constructor(id, clientId, commands, rootCommand, changes, timestamp) {
36193
36298
  this.rootCommand = rootCommand;
36299
+ this.timestamp = timestamp;
36194
36300
  this.id = id;
36195
36301
  this.clientId = clientId;
36196
36302
  this._commands = [...commands];
@@ -36262,7 +36368,7 @@ class Session extends EventBus {
36262
36368
  save(rootCommand, commands, changes) {
36263
36369
  if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())
36264
36370
  return;
36265
- const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes);
36371
+ const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
36266
36372
  this.revisions.append(revision.id, revision);
36267
36373
  this.trigger("new-local-state-update", { id: revision.id });
36268
36374
  this.sendUpdateMessage({
@@ -36313,13 +36419,9 @@ class Session extends EventBus {
36313
36419
  }
36314
36420
  loadInitialMessages(messages) {
36315
36421
  this.isReplayingInitialRevisions = true;
36316
- this.on("unexpected-revision-id", this, ({ revisionId }) => {
36317
- throw new Error(`The spreadsheet could not be loaded. Revision ${revisionId} is corrupted.`);
36318
- });
36319
36422
  for (const message of messages) {
36320
36423
  this.onMessageReceived(message);
36321
36424
  }
36322
- this.off("unexpected-revision-id", this);
36323
36425
  this.isReplayingInitialRevisions = false;
36324
36426
  }
36325
36427
  /**
@@ -36405,6 +36507,10 @@ class Session extends EventBus {
36405
36507
  onMessageReceived(message) {
36406
36508
  if (this.isAlreadyProcessed(message))
36407
36509
  return;
36510
+ if (this.isWrongServerRevisionId(message)) {
36511
+ this.trigger("unexpected-revision-id");
36512
+ return;
36513
+ }
36408
36514
  switch (message.type) {
36409
36515
  case "CLIENT_MOVED":
36410
36516
  this.onClientMoved(message);
@@ -36431,12 +36537,8 @@ class Session extends EventBus {
36431
36537
  });
36432
36538
  break;
36433
36539
  case "REMOTE_REVISION":
36434
- if (message.serverRevisionId !== this.serverRevisionId) {
36435
- this.trigger("unexpected-revision-id", { revisionId: message.serverRevisionId });
36436
- return;
36437
- }
36438
- const { clientId, commands } = message;
36439
- const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE");
36540
+ const { clientId, commands, timestamp } = message;
36541
+ const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE", undefined, timestamp);
36440
36542
  if (revision.clientId !== this.clientId) {
36441
36543
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36442
36544
  const pendingCommands = this.pendingMessages
@@ -36449,7 +36551,7 @@ class Session extends EventBus {
36449
36551
  }
36450
36552
  break;
36451
36553
  case "SNAPSHOT_CREATED": {
36452
- const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT");
36554
+ const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT", undefined, Date.now());
36453
36555
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36454
36556
  this.dropPendingHistoryMessages();
36455
36557
  this.trigger("snapshot");
@@ -36566,6 +36668,17 @@ class Session extends EventBus {
36566
36668
  return false;
36567
36669
  }
36568
36670
  }
36671
+ isWrongServerRevisionId(message) {
36672
+ switch (message.type) {
36673
+ case "REMOTE_REVISION":
36674
+ case "REVISION_REDONE":
36675
+ case "REVISION_UNDONE":
36676
+ case "SNAPSHOT_CREATED":
36677
+ return message.serverRevisionId !== this.serverRevisionId;
36678
+ default:
36679
+ return false;
36680
+ }
36681
+ }
36569
36682
  dropPendingHistoryMessages() {
36570
36683
  this.waitingUndoRedoAck = false;
36571
36684
  this.pendingMessages = this.pendingMessages.filter(({ type }) => type !== "REVISION_REDONE" && type !== "REVISION_UNDONE");
@@ -37281,7 +37394,7 @@ class RendererPlugin extends UIPlugin {
37281
37394
  }
37282
37395
  }
37283
37396
  drawBorders(renderingContext) {
37284
- const { ctx, thinLineWidth } = renderingContext;
37397
+ const { ctx } = renderingContext;
37285
37398
  for (let box of this.boxes) {
37286
37399
  const border = box.border;
37287
37400
  if (border) {
@@ -37300,13 +37413,66 @@ class RendererPlugin extends UIPlugin {
37300
37413
  }
37301
37414
  }
37302
37415
  }
37303
- function drawBorder([style, color], x1, y1, x2, y2) {
37416
+ /**
37417
+ * Following https://usefulangle.com/post/17/html5-canvas-drawing-1px-crisp-straight-lines,
37418
+ * we need to make sure that a "single" pixel line is drawn on a "half" pixel coordinate,
37419
+ * while a "double" pixel line is drawn on a "full" pixel coordinate. As, in the rendering
37420
+ * process, we always had 0.5 before rendering line (to make sure it is drawn on a "half"
37421
+ * pixel), we need to correct this behavior for the "medium" and the "dotted" styles, as
37422
+ * they are drawing a two pixels width line.
37423
+ * We also adapt here the coordinates of the line to make sure corner are correctly drawn,
37424
+ * avoiding a "round corners" effect. This is done by subtracting 1 pixel to the origin of
37425
+ * each line and adding 1 pixel to the end of each line (depending on the direction of the
37426
+ * line).
37427
+ */
37428
+ function drawBorder({ style, color }, x1, y1, x2, y2) {
37304
37429
  ctx.strokeStyle = color;
37305
- ctx.lineWidth = (style === "thin" ? 2 : 3) * thinLineWidth;
37430
+ switch (style) {
37431
+ case "medium":
37432
+ ctx.lineWidth = 2;
37433
+ x1 += y1 === y2 ? -0.5 : 0.5;
37434
+ x2 += y1 === y2 ? 1.5 : 0.5;
37435
+ y1 += x1 === x2 ? -0.5 : 0.5;
37436
+ y2 += x1 === x2 ? 1.5 : 0.5;
37437
+ break;
37438
+ case "thick":
37439
+ ctx.lineWidth = 3;
37440
+ if (y1 === y2) {
37441
+ x1--;
37442
+ x2++;
37443
+ }
37444
+ if (x1 === x2) {
37445
+ y1--;
37446
+ y2++;
37447
+ }
37448
+ break;
37449
+ case "dashed":
37450
+ ctx.lineWidth = 1;
37451
+ ctx.setLineDash([1, 3]);
37452
+ break;
37453
+ case "dotted":
37454
+ ctx.lineWidth = 1;
37455
+ if (y1 === y2) {
37456
+ x1 += 0.5;
37457
+ x2 += 0.5;
37458
+ }
37459
+ if (x1 === x2) {
37460
+ y1 += 0.5;
37461
+ y2 += 0.5;
37462
+ }
37463
+ ctx.setLineDash([1, 1]);
37464
+ break;
37465
+ case "thin":
37466
+ default:
37467
+ ctx.lineWidth = 1;
37468
+ break;
37469
+ }
37306
37470
  ctx.beginPath();
37307
37471
  ctx.moveTo(x1, y1);
37308
37472
  ctx.lineTo(x2, y2);
37309
37473
  ctx.stroke();
37474
+ ctx.lineWidth = 1;
37475
+ ctx.setLineDash([]);
37310
37476
  }
37311
37477
  }
37312
37478
  drawTexts(renderingContext) {
@@ -37784,6 +37950,9 @@ class SelectionInputPlugin extends UIPlugin {
37784
37950
  activeSheet;
37785
37951
  willAddNewRange = false;
37786
37952
  constructor(config, initialRanges, inputHasSingleRange) {
37953
+ if (inputHasSingleRange && initialRanges.length > 1) {
37954
+ throw new Error("Input with a single range cannot be instantiated with several range references.");
37955
+ }
37787
37956
  super(config);
37788
37957
  this.inputHasSingleRange = inputHasSingleRange;
37789
37958
  this.insertNewRange(0, initialRanges);
@@ -37803,6 +37972,11 @@ class SelectionInputPlugin extends UIPlugin {
37803
37972
  return 30 /* CommandResult.MaximumRangesReached */;
37804
37973
  }
37805
37974
  break;
37975
+ case "CHANGE_RANGE":
37976
+ if (this.inputHasSingleRange && cmd.value.split(",").length > 1) {
37977
+ return 30 /* CommandResult.MaximumRangesReached */;
37978
+ }
37979
+ break;
37806
37980
  }
37807
37981
  return 0 /* CommandResult.Success */;
37808
37982
  }
@@ -38459,18 +38633,7 @@ class SheetUIPlugin extends UIPlugin {
38459
38633
  // Command Handling
38460
38634
  // ---------------------------------------------------------------------------
38461
38635
  allowDispatch(cmd) {
38462
- switch (cmd.type) {
38463
- case "AUTORESIZE_ROWS":
38464
- case "AUTORESIZE_COLUMNS":
38465
- try {
38466
- this.getters.getSheet(cmd.sheetId);
38467
- break;
38468
- }
38469
- catch (error) {
38470
- return 27 /* CommandResult.InvalidSheetId */;
38471
- }
38472
- }
38473
- return 0 /* CommandResult.Success */;
38636
+ return this.chainValidations(this.checkSheetExists, this.checkZonesAreInSheet)(cmd);
38474
38637
  }
38475
38638
  handle(cmd) {
38476
38639
  switch (cmd.type) {
@@ -38670,6 +38833,31 @@ class SheetUIPlugin extends UIPlugin {
38670
38833
  splitWord.push(wordPart);
38671
38834
  return splitWord;
38672
38835
  }
38836
+ /**
38837
+ * Check that any "sheetId" in the command matches an existing
38838
+ * sheet.
38839
+ */
38840
+ checkSheetExists(cmd) {
38841
+ if ("sheetId" in cmd && this.getters.tryGetSheet(cmd.sheetId) === undefined) {
38842
+ return 27 /* CommandResult.InvalidSheetId */;
38843
+ }
38844
+ return 0 /* CommandResult.Success */;
38845
+ }
38846
+ /**
38847
+ * Check if zones in the command are well formed and
38848
+ * not outside the sheet.
38849
+ */
38850
+ checkZonesAreInSheet(cmd) {
38851
+ const sheetId = "sheetId" in cmd ? cmd.sheetId : this.getters.tryGetActiveSheetId();
38852
+ const zones = this.getters.getCommandZones(cmd);
38853
+ if (!sheetId && zones.length > 0) {
38854
+ return 92 /* CommandResult.NoActiveSheet */;
38855
+ }
38856
+ if (sheetId && zones.length > 0) {
38857
+ return this.getters.checkZonesExistInSheet(sheetId, zones);
38858
+ }
38859
+ return 0 /* CommandResult.Success */;
38860
+ }
38673
38861
  }
38674
38862
 
38675
38863
  const genericRepeatsTransforms = [
@@ -39494,8 +39682,8 @@ class ClipboardCellsState extends ClipboardCellsAbstractState {
39494
39682
  this.pasteCell(origin, position, this.operation, clipboardOptions);
39495
39683
  if (shouldPasteCF) {
39496
39684
  this.dispatch("PASTE_CONDITIONAL_FORMAT", {
39497
- origin: origin.position,
39498
- target: position,
39685
+ originPosition: origin.position,
39686
+ targetPosition: position,
39499
39687
  operation: this.operation,
39500
39688
  });
39501
39689
  }
@@ -39886,7 +40074,7 @@ class ClipboardPlugin extends UIPlugin {
39886
40074
  allowDispatch(cmd) {
39887
40075
  switch (cmd.type) {
39888
40076
  case "CUT":
39889
- const zones = cmd.target || this.getters.getSelectedZones();
40077
+ const zones = cmd.cutTarget || this.getters.getSelectedZones();
39890
40078
  const state = this.getClipboardState(zones, cmd.type);
39891
40079
  return state.isCutAllowed(zones);
39892
40080
  case "PASTE":
@@ -39916,7 +40104,7 @@ class ClipboardPlugin extends UIPlugin {
39916
40104
  switch (cmd.type) {
39917
40105
  case "COPY":
39918
40106
  case "CUT":
39919
- const zones = ("target" in cmd && cmd.target) || this.getters.getSelectedZones();
40107
+ const zones = ("cutTarget" in cmd && cmd.cutTarget) || this.getters.getSelectedZones();
39920
40108
  this.state = this.getClipboardState(zones, cmd.type);
39921
40109
  this.status = "visible";
39922
40110
  break;
@@ -40872,6 +41060,7 @@ class GridSelectionPlugin extends UIPlugin {
40872
41060
  "isSelected",
40873
41061
  "isSingleColSelected",
40874
41062
  "getElementsFromSelection",
41063
+ "tryGetActiveSheetId",
40875
41064
  ];
40876
41065
  gridSelection = {
40877
41066
  anchor: {
@@ -41087,6 +41276,9 @@ class GridSelectionPlugin extends UIPlugin {
41087
41276
  getActiveSheetId() {
41088
41277
  return this.activeSheet.id;
41089
41278
  }
41279
+ tryGetActiveSheetId() {
41280
+ return this.activeSheet?.id;
41281
+ }
41090
41282
  getActiveCell() {
41091
41283
  return this.getters.getEvaluatedCell(this.getActivePosition());
41092
41284
  }
@@ -41289,7 +41481,8 @@ class GridSelectionPlugin extends UIPlugin {
41289
41481
  },
41290
41482
  zone: selectedZone,
41291
41483
  };
41292
- this.setSelectionMixin(anchor, [selectedZone]);
41484
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "left", [...cmd.elements]));
41485
+ this.setSelectionMixin(anchor, selections);
41293
41486
  }
41294
41487
  onRowsRemoved(cmd) {
41295
41488
  const { cell, zone } = this.gridSelection.anchor;
@@ -41303,13 +41496,18 @@ class GridSelectionPlugin extends UIPlugin {
41303
41496
  },
41304
41497
  zone: selectedZone,
41305
41498
  };
41306
- this.setSelectionMixin(anchor, [selectedZone]);
41499
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "top", [...cmd.elements]));
41500
+ this.setSelectionMixin(anchor, selections);
41307
41501
  }
41308
41502
  onAddElements(cmd) {
41309
- const selection = this.gridSelection.anchor.zone;
41310
- const zone = updateSelectionOnInsertion(selection, cmd.dimension === "COL" ? "left" : "top", cmd.base, cmd.position, cmd.quantity);
41311
- const anchor = { cell: { col: zone.left, row: zone.top }, zone };
41312
- this.setSelectionMixin(anchor, [zone]);
41503
+ const start = cmd.dimension === "COL" ? "left" : "top";
41504
+ const anchorZone = updateSelectionOnInsertion(this.gridSelection.anchor.zone, start, cmd.base, cmd.position, cmd.quantity);
41505
+ const selection = this.gridSelection.zones.map((zone) => updateSelectionOnInsertion(zone, start, cmd.base, cmd.position, cmd.quantity));
41506
+ const anchor = {
41507
+ cell: { col: anchorZone.left, row: anchorZone.top },
41508
+ zone: anchorZone,
41509
+ };
41510
+ this.setSelectionMixin(anchor, selection);
41313
41511
  }
41314
41512
  onMoveElements(cmd) {
41315
41513
  const thickness = cmd.elements.length;
@@ -41327,7 +41525,7 @@ class GridSelectionPlugin extends UIPlugin {
41327
41525
  const deltaCol = isBasedBefore && isCol ? thickness : 0;
41328
41526
  const deltaRow = isBasedBefore && !isCol ? thickness : 0;
41329
41527
  this.dispatch("CUT", {
41330
- target: [
41528
+ cutTarget: [
41331
41529
  {
41332
41530
  left: isCol ? start + deltaCol : 0,
41333
41531
  right: isCol ? end + deltaCol : this.getters.getNumberCols(cmd.sheetId) - 1,
@@ -42555,7 +42753,6 @@ css /* scss */ `
42555
42753
  color: dimgrey;
42556
42754
  }
42557
42755
  .o-sidePanelClose {
42558
- font-size: 1.5rem;
42559
42756
  padding: 5px 10px;
42560
42757
  cursor: pointer;
42561
42758
  &:hover {
@@ -42740,7 +42937,6 @@ css /* scss */ `
42740
42937
  justify-content: center;
42741
42938
  align-items: center;
42742
42939
  margin: 2px;
42743
- padding: 0px 3px;
42744
42940
  border-radius: 2px;
42745
42941
  min-width: 20px;
42746
42942
  }
@@ -42797,7 +42993,228 @@ ActionButton.props = {
42797
42993
  onClick: { type: Function, optional: true },
42798
42994
  };
42799
42995
 
42996
+ /**
42997
+ * List the available borders positions and the corresponding icons.
42998
+ * The structure of this array is defined to match the order/lines we want
42999
+ * to display in the topbar's border tool.
43000
+ */
43001
+ const BORDER_POSITIONS = [
43002
+ [
43003
+ ["all", "o-spreadsheet-Icon.BORDERS"],
43004
+ ["hv", "o-spreadsheet-Icon.BORDER_HV"],
43005
+ ["h", "o-spreadsheet-Icon.BORDER_H"],
43006
+ ["v", "o-spreadsheet-Icon.BORDER_V"],
43007
+ ["external", "o-spreadsheet-Icon.BORDER_EXTERNAL"],
43008
+ ],
43009
+ [
43010
+ ["left", "o-spreadsheet-Icon.BORDER_LEFT"],
43011
+ ["top", "o-spreadsheet-Icon.BORDER_TOP"],
43012
+ ["right", "o-spreadsheet-Icon.BORDER_RIGHT"],
43013
+ ["bottom", "o-spreadsheet-Icon.BORDER_BOTTOM"],
43014
+ ["clear", "o-spreadsheet-Icon.BORDER_CLEAR"],
43015
+ ],
43016
+ ];
43017
+ // -----------------------------------------------------------------------------
43018
+ // Border Editor
43019
+ // -----------------------------------------------------------------------------
43020
+ css /* scss */ `
43021
+ .o-border-selector {
43022
+ padding: 4px;
43023
+ }
43024
+ .o-divider {
43025
+ border-right: 1px solid #e0e2e4;
43026
+ margin: 0 6px;
43027
+ }
43028
+ .o-border-selector-section {
43029
+ .o-dropdown-line {
43030
+ height: 30px;
43031
+ margin: 1px;
43032
+ .o-line-item {
43033
+ padding: 4px;
43034
+ width: 18px;
43035
+ height: 18px;
43036
+ }
43037
+ }
43038
+ .o-border-style-tool {
43039
+ padding: 0px 3px;
43040
+ margin: 2px;
43041
+ height: 25px;
43042
+ }
43043
+ }
43044
+ .o-style-preview {
43045
+ margin: 7px 5px 7px 5px;
43046
+ width: 60px;
43047
+ height: 5px;
43048
+ }
43049
+ .o-style-thin {
43050
+ border-bottom: 1px solid #000000;
43051
+ }
43052
+ .o-style-medium {
43053
+ border-bottom: 2px solid #000000;
43054
+ }
43055
+ .o-style-thick {
43056
+ border-bottom: 3px solid #000000;
43057
+ }
43058
+ .o-style-dashed {
43059
+ border-bottom: 1px dashed #000000;
43060
+ }
43061
+ .o-style-dotted {
43062
+ border-bottom: 1px dotted #000000;
43063
+ }
43064
+ .o-dropdown-border-type {
43065
+ &:not(.o-disabled):not(.active):hover {
43066
+ background-color: ${BG_HOVER_COLOR};
43067
+ }
43068
+ }
43069
+ .o-dropdown-border-check {
43070
+ width: 20px;
43071
+ font-size: 12px;
43072
+ }
43073
+ .o-border-style-dropdown {
43074
+ background: #ffffff;
43075
+ padding: 4px;
43076
+ .o-dropdown-line {
43077
+ .o-line-item.active {
43078
+ background-color: rgba(0, 0, 0, 0.2);
43079
+ }
43080
+ }
43081
+ }
43082
+ .o-border-picker-button {
43083
+ padding: 0px !important;
43084
+ margin: 5px 0px 0px 0px !important;
43085
+ height: 25px !important;
43086
+ }
43087
+ `;
43088
+ class BorderEditor extends owl.Component {
43089
+ static template = "o-spreadsheet-BorderEditor";
43090
+ static components = { ColorPickerWidget, Popover };
43091
+ BORDER_POSITIONS = BORDER_POSITIONS;
43092
+ lineStyleButtonRef = owl.useRef("lineStyleButton");
43093
+ borderStyles = borderStyles;
43094
+ state = owl.useState({
43095
+ activeTool: undefined,
43096
+ });
43097
+ toggleDropdownTool(tool) {
43098
+ const isOpen = this.state.activeTool === tool;
43099
+ this.state.activeTool = isOpen ? undefined : tool;
43100
+ }
43101
+ closeDropdown() {
43102
+ this.state.activeTool = undefined;
43103
+ }
43104
+ setBorderPosition(position) {
43105
+ this.props.onBorderPositionPicked(position);
43106
+ this.closeDropdown();
43107
+ }
43108
+ setBorderColor(color) {
43109
+ this.props.onBorderColorPicked(color);
43110
+ this.closeDropdown();
43111
+ }
43112
+ setBorderStyle(style) {
43113
+ this.props.onBorderStylePicked(style);
43114
+ this.closeDropdown();
43115
+ }
43116
+ get lineStylePickerPopoverProps() {
43117
+ return {
43118
+ anchorRect: this.lineStylePickerAnchorRect,
43119
+ positioning: "BottomLeft",
43120
+ verticalOffset: 0,
43121
+ };
43122
+ }
43123
+ get popoverProps() {
43124
+ return {
43125
+ anchorRect: this.props.anchorRect,
43126
+ maxHeight: this.props.maxHeight,
43127
+ positioning: "BottomLeft",
43128
+ verticalOffset: 0,
43129
+ };
43130
+ }
43131
+ get lineStylePickerAnchorRect() {
43132
+ const button = this.lineStyleButtonRef.el;
43133
+ if (button === null) {
43134
+ return { x: 0, y: 0, width: 0, height: 0 };
43135
+ }
43136
+ const buttonRect = button.getBoundingClientRect();
43137
+ return {
43138
+ x: buttonRect.x,
43139
+ y: buttonRect.y,
43140
+ width: buttonRect.width,
43141
+ height: buttonRect.height,
43142
+ };
43143
+ }
43144
+ }
43145
+ BorderEditor.props = {
43146
+ class: { type: String, optional: true },
43147
+ currentBorderColor: { type: String, optional: false },
43148
+ currentBorderStyle: { type: String, optional: false },
43149
+ currentBorderPosition: { type: String, optional: true },
43150
+ onBorderColorPicked: Function,
43151
+ onBorderStylePicked: Function,
43152
+ onBorderPositionPicked: Function,
43153
+ maxHeight: { type: Number, optional: true },
43154
+ anchorRect: Object,
43155
+ };
43156
+
43157
+ class BorderEditorWidget extends owl.Component {
43158
+ static template = "o-spreadsheet-BorderEditorWidget";
43159
+ static components = { BorderEditor };
43160
+ borderEditorButtonRef = owl.useRef("borderEditorButton");
43161
+ state = owl.useState({
43162
+ currentColor: DEFAULT_BORDER_DESC.color,
43163
+ currentStyle: DEFAULT_BORDER_DESC.style,
43164
+ currentPosition: undefined,
43165
+ });
43166
+ get borderEditorAnchorRect() {
43167
+ const button = this.borderEditorButtonRef.el;
43168
+ const buttonRect = button.getBoundingClientRect();
43169
+ return {
43170
+ x: buttonRect.x,
43171
+ y: buttonRect.y,
43172
+ width: buttonRect.width,
43173
+ height: buttonRect.height,
43174
+ };
43175
+ }
43176
+ onBorderPositionPicked(position) {
43177
+ this.state.currentPosition = position;
43178
+ this.updateBorder();
43179
+ }
43180
+ onBorderColorPicked(color) {
43181
+ this.state.currentColor = color;
43182
+ this.updateBorder();
43183
+ }
43184
+ onBorderStylePicked(style) {
43185
+ this.state.currentStyle = style;
43186
+ this.updateBorder();
43187
+ }
43188
+ updateBorder() {
43189
+ if (this.state.currentPosition === undefined) {
43190
+ return;
43191
+ }
43192
+ this.env.model.dispatch("SET_ZONE_BORDERS", {
43193
+ sheetId: this.env.model.getters.getActiveSheetId(),
43194
+ target: this.env.model.getters.getSelectedZones(),
43195
+ border: {
43196
+ position: this.state.currentPosition,
43197
+ color: this.state.currentColor,
43198
+ style: this.state.currentStyle,
43199
+ },
43200
+ });
43201
+ }
43202
+ }
43203
+ BorderEditorWidget.props = {
43204
+ toggleBorderEditor: Function,
43205
+ showBorderEditor: Boolean,
43206
+ disabled: { type: Boolean, optional: true },
43207
+ dropdownMaxHeight: { type: Number, optional: true },
43208
+ class: { type: String, optional: true },
43209
+ };
43210
+
42800
43211
  const COMPOSER_MAX_HEIGHT = 100;
43212
+ /* svg free of use from https://uxwing.com/formula-fx-icon/ */
43213
+ const FX_SVG = /*xml*/ `
43214
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
43215
+ <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'/>
43216
+ </svg>
43217
+ `;
42801
43218
  css /* scss */ `
42802
43219
  .o-topbar-composer {
42803
43220
  height: fit-content;
@@ -42805,9 +43222,10 @@ css /* scss */ `
42805
43222
  border: 1px solid;
42806
43223
  z-index: ${ComponentsImportance.TopBarComposer};
42807
43224
 
42808
- .o-composer:empty:not(:focus)::before {
42809
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
42810
- 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");
43225
+ .o-composer:empty:not(:focus):not(.active)::before {
43226
+ content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
43227
+ position: relative;
43228
+ top: 20%;
42811
43229
  }
42812
43230
  }
42813
43231
  `;
@@ -42930,11 +43348,6 @@ FontSizeEditor.props = {
42930
43348
  dropdownStyle: String,
42931
43349
  };
42932
43350
 
42933
- // If we ever change these colors, make sure the filter tool stays green to match the icon in the grid
42934
- const ACTIVE_BG_COLOR = BACKGROUND_HEADER_FILTER_COLOR;
42935
- const ACTIVE_FONT_COLOR = FILTERS_COLOR;
42936
- const HOVERED_BG_COLOR = BG_HOVER_COLOR;
42937
- const HOVERED_FONT_COLOR = "#000";
42938
43351
  // -----------------------------------------------------------------------------
42939
43352
  // TopBar
42940
43353
  // -----------------------------------------------------------------------------
@@ -42944,27 +43357,6 @@ css /* scss */ `
42944
43357
  font-size: 13px;
42945
43358
  font-weight: 500;
42946
43359
 
42947
- .o-topbar-hoverable {
42948
- cursor: pointer;
42949
- .o-icon {
42950
- color: ${ICONS_COLOR};
42951
- }
42952
- &:not(.o-disabled):not(.active):hover {
42953
- background-color: ${HOVERED_BG_COLOR};
42954
- color: ${HOVERED_FONT_COLOR};
42955
- .o-icon {
42956
- color: ${HOVERED_FONT_COLOR};
42957
- }
42958
- }
42959
- &.active {
42960
- background-color: ${ACTIVE_BG_COLOR};
42961
- color: ${ACTIVE_FONT_COLOR};
42962
- .o-icon {
42963
- color: ${ACTIVE_FONT_COLOR};
42964
- }
42965
- }
42966
- }
42967
-
42968
43360
  .o-topbar-top {
42969
43361
  border-bottom: 1px solid ${SEPARATOR_COLOR};
42970
43362
  padding: 2px 10px;
@@ -42974,6 +43366,11 @@ css /* scss */ `
42974
43366
  .o-topbar-menu {
42975
43367
  padding: 4px 6px;
42976
43368
  margin: 0 2px;
43369
+
43370
+ &.active {
43371
+ background-color: ${BG_HOVER_COLOR};
43372
+ color: #000;
43373
+ }
42977
43374
  }
42978
43375
  }
42979
43376
  }
@@ -43051,6 +43448,7 @@ class TopBar extends owl.Component {
43051
43448
  TopBarComposer,
43052
43449
  FontSizeEditor,
43053
43450
  ActionButton,
43451
+ BorderEditorWidget,
43054
43452
  };
43055
43453
  state = owl.useState({
43056
43454
  menuState: { isOpen: false, position: null, menuItems: [] },
@@ -43126,7 +43524,9 @@ class TopBar extends owl.Component {
43126
43524
  this.state.activeTool = "";
43127
43525
  this.state.menuState.isOpen = true;
43128
43526
  this.state.menuState.position = { x: left, y: top + height };
43129
- this.state.menuState.menuItems = menu.children(this.env);
43527
+ this.state.menuState.menuItems = menu
43528
+ .children(this.env)
43529
+ .sort((a, b) => a.sequence - b.sequence);
43130
43530
  this.state.menuState.parentMenu = menu;
43131
43531
  this.isSelectingMenu = true;
43132
43532
  this.openedEl = ev.target;
@@ -43212,6 +43612,11 @@ class WebClipboardWrapper {
43212
43612
  }
43213
43613
  }
43214
43614
 
43615
+ // If we ever change these colors, make sure the filter tool stays green to match the icon in the grid
43616
+ const ACTIVE_BG_COLOR = BACKGROUND_HEADER_FILTER_COLOR;
43617
+ const ACTIVE_FONT_COLOR = FILTERS_COLOR;
43618
+ const HOVERED_BG_COLOR = BG_HOVER_COLOR;
43619
+ const HOVERED_FONT_COLOR = "#000";
43215
43620
  css /* scss */ `
43216
43621
  .o-spreadsheet {
43217
43622
  position: relative;
@@ -43244,6 +43649,27 @@ css /* scss */ `
43244
43649
  margin-top: ${MENU_SEPARATOR_PADDING}px;
43245
43650
  margin-bottom: ${MENU_SEPARATOR_PADDING}px;
43246
43651
  }
43652
+ .o-hoverable-button {
43653
+ border-radius: 2px;
43654
+ cursor: pointer;
43655
+ .o-icon {
43656
+ color: ${ICONS_COLOR};
43657
+ }
43658
+ &:not(.o-disabled):not(.active):hover {
43659
+ background-color: ${HOVERED_BG_COLOR};
43660
+ color: ${HOVERED_FONT_COLOR};
43661
+ .o-icon {
43662
+ color: ${HOVERED_FONT_COLOR};
43663
+ }
43664
+ }
43665
+ &.active {
43666
+ background-color: ${ACTIVE_BG_COLOR};
43667
+ color: ${ACTIVE_FONT_COLOR};
43668
+ .o-icon {
43669
+ color: ${ACTIVE_FONT_COLOR};
43670
+ }
43671
+ }
43672
+ }
43247
43673
  }
43248
43674
 
43249
43675
  .o-two-columns {
@@ -43393,6 +43819,9 @@ class Spreadsheet extends owl.Component {
43393
43819
  }
43394
43820
  }
43395
43821
  openSidePanel(panel, panelProps) {
43822
+ if (this.sidePanel.isOpen && panel !== this.sidePanel.component) {
43823
+ this.sidePanel.panelProps?.onCloseSidePanel?.();
43824
+ }
43396
43825
  this.sidePanel.component = panel;
43397
43826
  this.sidePanel.panelProps = panelProps;
43398
43827
  this.sidePanel.isOpen = true;
@@ -44102,13 +44531,13 @@ class Tree {
44102
44531
  }
44103
44532
 
44104
44533
  class SelectiveHistory {
44534
+ HEAD_BRANCH;
44535
+ HEAD_OPERATION;
44536
+ tree;
44105
44537
  applyOperation;
44106
44538
  revertOperation;
44107
44539
  buildEmpty;
44108
44540
  buildTransformation;
44109
- HEAD_BRANCH;
44110
- HEAD_OPERATION;
44111
- tree;
44112
44541
  /**
44113
44542
  * The selective history is a data structure used to register changes/updates of a state.
44114
44543
  * Each change/update is called an "operation".
@@ -44129,14 +44558,15 @@ class SelectiveHistory {
44129
44558
  * (used for internal implementation)
44130
44559
  * @param buildTransformation Factory used to build transformations
44131
44560
  */
44132
- constructor(initialOperationId, applyOperation, revertOperation, buildEmpty, buildTransformation) {
44133
- this.applyOperation = applyOperation;
44134
- this.revertOperation = revertOperation;
44135
- this.buildEmpty = buildEmpty;
44136
- this.buildTransformation = buildTransformation;
44561
+ constructor(args) {
44562
+ this.applyOperation = args.applyOperation;
44563
+ this.revertOperation = args.revertOperation;
44564
+ this.buildEmpty = args.buildEmpty;
44565
+ this.buildTransformation = args.buildTransformation;
44137
44566
  this.HEAD_BRANCH = new Branch(this.buildTransformation);
44138
- this.tree = new Tree(buildTransformation, this.HEAD_BRANCH);
44139
- const initial = new Operation(initialOperationId, buildEmpty(initialOperationId));
44567
+ this.tree = new Tree(this.buildTransformation, this.HEAD_BRANCH);
44568
+ const initialOperationId = args.initialOperationId;
44569
+ const initial = new Operation(initialOperationId, this.buildEmpty(initialOperationId));
44140
44570
  this.tree.insertOperationLast(this.HEAD_BRANCH, initial);
44141
44571
  this.HEAD_OPERATION = initial;
44142
44572
  }
@@ -44250,21 +44680,27 @@ class SelectiveHistory {
44250
44680
  }
44251
44681
  }
44252
44682
 
44253
- function buildRevisionLog(initialRevisionId, recordChanges, dispatch) {
44254
- return new SelectiveHistory(initialRevisionId, (revision) => {
44255
- const commands = revision.commands.slice();
44256
- const { changes } = recordChanges(() => {
44257
- for (const command of commands) {
44258
- dispatch(command);
44259
- }
44260
- });
44261
- revision.setChanges(changes);
44262
- }, (revision) => revertChanges([revision]), (id) => new Revision(id, "empty", []), {
44263
- with: (revision) => (toTransform) => {
44264
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand);
44683
+ function buildRevisionLog(args) {
44684
+ return new SelectiveHistory({
44685
+ initialOperationId: args.initialRevisionId,
44686
+ applyOperation: (revision) => {
44687
+ const commands = revision.commands.slice();
44688
+ const { changes } = args.recordChanges(() => {
44689
+ for (const command of commands) {
44690
+ args.dispatch(command);
44691
+ }
44692
+ });
44693
+ revision.setChanges(changes);
44265
44694
  },
44266
- without: (revision) => (toTransform) => {
44267
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand);
44695
+ revertOperation: (revision) => revertChanges([revision]),
44696
+ buildEmpty: (id) => new Revision(id, "empty", []),
44697
+ buildTransformation: {
44698
+ with: (revision) => (toTransform) => {
44699
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand, undefined, toTransform.timestamp);
44700
+ },
44701
+ without: (revision) => (toTransform) => {
44702
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand, undefined, toTransform.timestamp);
44703
+ },
44268
44704
  },
44269
44705
  });
44270
44706
  }
@@ -45937,11 +46373,21 @@ function addBorders(borders) {
45937
46373
  for (let border of Object.values(borders)) {
45938
46374
  borderNodes.push(escapeXml /*xml*/ `
45939
46375
  <border>
45940
- <left ${formatBorderAttribute(border["left"])} />
45941
- <right ${formatBorderAttribute(border["right"])} />
45942
- <top ${formatBorderAttribute(border["top"])} />
45943
- <bottom ${formatBorderAttribute(border["bottom"])} />
45944
- <diagonal ${formatBorderAttribute(border["diagonal"])} />
46376
+ <left ${formatBorderAttribute(border["left"])}>
46377
+ ${addBorderColor(border["left"])}
46378
+ </left>
46379
+ <right ${formatBorderAttribute(border["right"])}>
46380
+ ${addBorderColor(border["right"])}
46381
+ </right>
46382
+ <top ${formatBorderAttribute(border["top"])}>
46383
+ ${addBorderColor(border["top"])}
46384
+ </top>
46385
+ <bottom ${formatBorderAttribute(border["bottom"])}>
46386
+ ${addBorderColor(border["bottom"])}
46387
+ </bottom>
46388
+ <diagonal ${formatBorderAttribute(border["diagonal"])}>
46389
+ ${addBorderColor(border["diagonal"])}
46390
+ </diagonal>
45945
46391
  </border>
45946
46392
  `);
45947
46393
  }
@@ -45955,10 +46401,15 @@ function formatBorderAttribute(description) {
45955
46401
  if (!description) {
45956
46402
  return escapeXml ``;
45957
46403
  }
45958
- return formatAttributes([
45959
- ["style", description.style],
45960
- ["color", toXlsxHexColor(description.color.rgb)],
45961
- ]);
46404
+ return formatAttributes([["style", description.style]]);
46405
+ }
46406
+ function addBorderColor(description) {
46407
+ if (!description) {
46408
+ return escapeXml ``;
46409
+ }
46410
+ return escapeXml /*xml*/ `
46411
+ <color ${formatAttributes([["rgb", toXlsxHexColor(description.color.rgb)]])}/>
46412
+ `;
45962
46413
  }
45963
46414
  function addStyles(styles) {
45964
46415
  const styleNodes = [];
@@ -46568,6 +47019,7 @@ class Model extends EventBus {
46568
47019
  coreGetters;
46569
47020
  uuidGenerator;
46570
47021
  handlers = [];
47022
+ uiHandlers = [];
46571
47023
  coreHandlers = [];
46572
47024
  constructor(data = {}, config = {}, stateUpdateMessages = [], uuidGenerator = new UuidGenerator(), verboseImport = true) {
46573
47025
  super();
@@ -46602,21 +47054,25 @@ class Model extends EventBus {
46602
47054
  this.setupCorePlugin(Plugin, workbookData);
46603
47055
  }
46604
47056
  Object.assign(this.getters, this.coreGetters);
47057
+ this.session.loadInitialMessages(stateUpdateMessages);
46605
47058
  for (let Plugin of statefulUIPluginRegistry.getAll()) {
46606
47059
  const plugin = this.setupUiPlugin(Plugin);
46607
47060
  this.statefulUIPlugins.push(plugin);
46608
47061
  this.handlers.push(plugin);
47062
+ this.uiHandlers.push(plugin);
46609
47063
  }
46610
47064
  for (let Plugin of coreViewsPluginRegistry.getAll()) {
46611
47065
  const plugin = this.setupUiPlugin(Plugin);
46612
47066
  this.coreViewsPlugins.push(plugin);
46613
47067
  this.handlers.push(plugin);
47068
+ this.uiHandlers.push(plugin);
46614
47069
  this.coreHandlers.push(plugin);
46615
47070
  }
46616
47071
  for (let Plugin of featurePluginRegistry.getAll()) {
46617
47072
  const plugin = this.setupUiPlugin(Plugin);
46618
47073
  this.featurePlugins.push(plugin);
46619
47074
  this.handlers.push(plugin);
47075
+ this.uiHandlers.push(plugin);
46620
47076
  }
46621
47077
  this.uuidGenerator.setIsFastStrategy(false);
46622
47078
  // starting plugins
@@ -46629,8 +47085,6 @@ class Model extends EventBus {
46629
47085
  // This should be done after construction of LocalHistory due to order of
46630
47086
  // events
46631
47087
  this.setupSessionEvents();
46632
- // Load the initial revisions
46633
- this.session.loadInitialMessages(stateUpdateMessages);
46634
47088
  this.joinSession();
46635
47089
  if (config.snapshotRequested) {
46636
47090
  this.session.snapshot(this.exportData());
@@ -46694,14 +47148,18 @@ class Model extends EventBus {
46694
47148
  this.finalize();
46695
47149
  }
46696
47150
  setupSession(revisionId) {
46697
- const session = new Session(buildRevisionLog(revisionId, this.state.recordChanges.bind(this.state), (command) => {
46698
- const result = this.checkDispatchAllowed(command);
46699
- if (!result.isSuccessful) {
46700
- return;
46701
- }
46702
- this.isReplayingCommand = true;
46703
- this.dispatchToHandlers(this.coreHandlers, command);
46704
- this.isReplayingCommand = false;
47151
+ const session = new Session(buildRevisionLog({
47152
+ initialRevisionId: revisionId,
47153
+ recordChanges: this.state.recordChanges.bind(this.state),
47154
+ dispatch: (command) => {
47155
+ const result = this.checkDispatchAllowed(command);
47156
+ if (!result.isSuccessful) {
47157
+ return;
47158
+ }
47159
+ this.isReplayingCommand = true;
47160
+ this.dispatchToHandlers(this.coreHandlers, command);
47161
+ this.isReplayingCommand = false;
47162
+ },
46705
47163
  }), this.config.transportService, revisionId);
46706
47164
  return session;
46707
47165
  }
@@ -46783,7 +47241,7 @@ class Model extends EventBus {
46783
47241
  return new DispatchResult(results.flat());
46784
47242
  }
46785
47243
  checkDispatchAllowedLocalCommand(command) {
46786
- const results = this.handlers.map((handler) => handler.allowDispatch(command));
47244
+ const results = this.uiHandlers.map((handler) => handler.allowDispatch(command));
46787
47245
  return new DispatchResult(results.flat());
46788
47246
  }
46789
47247
  finalize() {
@@ -47100,6 +47558,6 @@ exports.setTranslationMethod = setTranslationMethod;
47100
47558
  exports.tokenize = tokenize;
47101
47559
 
47102
47560
 
47103
- __info__.version = '16.3.0-alpha.7';
47104
- __info__.date = '2023-05-02T13:08:09.275Z';
47105
- __info__.hash = '77c8f5f';
47561
+ __info__.version = '16.3.0';
47562
+ __info__.date = '2023-05-17T14:44:57.860Z';
47563
+ __info__.hash = '33b37e4';