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