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

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",
@@ -4385,6 +4398,7 @@ var CommandResult;
4385
4398
  CommandResult[CommandResult["NoSplitSeparatorInSelection"] = 91] = "NoSplitSeparatorInSelection";
4386
4399
  })(CommandResult || (CommandResult = {}));
4387
4400
 
4401
+ const borderStyles = ["thin", "medium", "thick", "dashed", "dotted"];
4388
4402
  var DIRECTION;
4389
4403
  (function (DIRECTION) {
4390
4404
  DIRECTION["UP"] = "up";
@@ -5478,12 +5492,6 @@ css /* scss */ `
5478
5492
  cursor: pointer;
5479
5493
  user-select: none;
5480
5494
 
5481
- .o-menu-item-name {
5482
- overflow: hidden;
5483
- white-space: nowrap;
5484
- text-overflow: ellipsis;
5485
- }
5486
-
5487
5495
  &.o-menu-root {
5488
5496
  display: flex;
5489
5497
  justify-content: space-between;
@@ -5494,10 +5502,6 @@ css /* scss */ `
5494
5502
  margin: 0px 8px 0px 0px;
5495
5503
  width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5496
5504
  line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
5497
-
5498
- .o-icon {
5499
- color: ${ICONS_COLOR};
5500
- }
5501
5505
  }
5502
5506
  .o-menu-item-root {
5503
5507
  width: 10px;
@@ -5511,6 +5515,11 @@ css /* scss */ `
5511
5515
  .o-menu-item-description {
5512
5516
  color: grey;
5513
5517
  }
5518
+ .o-menu-item-icon {
5519
+ .o-icon {
5520
+ color: ${ICONS_COLOR};
5521
+ }
5522
+ }
5514
5523
  }
5515
5524
  &.disabled {
5516
5525
  color: ${MENU_ITEM_DISABLED_COLOR};
@@ -6327,8 +6336,14 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6327
6336
  : undefined));
6328
6337
  }
6329
6338
  }
6339
+ else if (zone.left === zone.right && zone.top === zone.bottom) {
6340
+ // A single cell. If it's only the title, the dataset is not added.
6341
+ if (!dataSetsHaveTitle) {
6342
+ dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6343
+ }
6344
+ }
6330
6345
  else {
6331
- /* 1 cell, 1 row or 1 column */
6346
+ /* 1 row or 1 column */
6332
6347
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6333
6348
  ? {
6334
6349
  top: zone.top,
@@ -8939,7 +8954,7 @@ const DELETE_CELL_SHIFT_LEFT = (env) => {
8939
8954
  handlePasteResult(env, result);
8940
8955
  };
8941
8956
  const MENU_INSERT_ROWS_NAME = (env) => {
8942
- const number = getColumnsNumber(env);
8957
+ const number = getRowsNumber(env);
8943
8958
  return number === 1 ? _lt("Insert row") : _lt("Insert %s rows", number.toString());
8944
8959
  };
8945
8960
  const MENU_INSERT_ROWS_BEFORE_NAME = (env) => {
@@ -15304,7 +15319,11 @@ const LOOKUP = {
15304
15319
  : getNormalizedValueFromRowRange;
15305
15320
  const rangeLength = verticalSearch ? nbRow : nbCol;
15306
15321
  const index = dichotomicSearch(searchArray, _searchKey, "nextSmaller", "asc", rangeLength, getElement);
15307
- assertAvailable(searchArray[0][index], searchKey);
15322
+ if (index === -1)
15323
+ assertAvailable(undefined, searchKey);
15324
+ verticalSearch
15325
+ ? assertAvailable(searchArray[0][index], searchKey)
15326
+ : assertAvailable(searchArray[index][nbRow - 1], searchKey);
15308
15327
  if (resultRange === undefined) {
15309
15328
  return (verticalSearch ? searchArray[nbCol - 1][index] : searchArray[index][nbRow - 1]);
15310
15329
  }
@@ -16211,75 +16230,97 @@ for (let category of categories) {
16211
16230
 
16212
16231
  const insertRow = {
16213
16232
  name: MENU_INSERT_ROWS_NAME,
16214
- isVisible: IS_ONLY_ONE_RANGE,
16233
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16234
+ IS_ONLY_ONE_RANGE(env) &&
16235
+ env.model.getters.getActiveCols().size === 0,
16215
16236
  icon: "o-spreadsheet-Icon.INSERT_ROW",
16216
16237
  };
16217
16238
  const rowInsertRowBefore = {
16218
16239
  name: ROW_INSERT_ROWS_BEFORE_NAME,
16219
16240
  execute: INSERT_ROWS_BEFORE_ACTION,
16241
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16242
+ IS_ONLY_ONE_RANGE(env) &&
16243
+ env.model.getters.getActiveCols().size === 0,
16244
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16220
16245
  };
16221
16246
  const topBarInsertRowsBefore = {
16222
16247
  ...rowInsertRowBefore,
16223
16248
  name: MENU_INSERT_ROWS_BEFORE_NAME,
16224
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16225
16249
  };
16226
16250
  const cellInsertRowsBefore = {
16227
16251
  ...rowInsertRowBefore,
16228
16252
  name: CELL_INSERT_ROWS_BEFORE_NAME,
16229
16253
  isVisible: IS_ONLY_ONE_RANGE,
16230
- icon: "o-spreadsheet-Icon.INSERT_ROW",
16254
+ icon: "o-spreadsheet-Icon.INSERT_ROW_BEFORE",
16231
16255
  };
16232
16256
  const rowInsertRowsAfter = {
16233
16257
  execute: INSERT_ROWS_AFTER_ACTION,
16234
16258
  name: ROW_INSERT_ROWS_AFTER_NAME,
16259
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveRows()) &&
16260
+ IS_ONLY_ONE_RANGE(env) &&
16261
+ env.model.getters.getActiveCols().size === 0,
16262
+ icon: "o-spreadsheet-Icon.INSERT_ROW_AFTER",
16235
16263
  };
16236
16264
  const topBarInsertRowsAfter = {
16237
16265
  ...rowInsertRowsAfter,
16238
16266
  name: MENU_INSERT_ROWS_AFTER_NAME,
16239
- isVisible: (env) => env.model.getters.getActiveCols().size === 0,
16240
16267
  };
16241
16268
  const insertCol = {
16242
16269
  name: MENU_INSERT_COLUMNS_NAME,
16243
- isVisible: IS_ONLY_ONE_RANGE,
16270
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16271
+ IS_ONLY_ONE_RANGE(env) &&
16272
+ env.model.getters.getActiveRows().size === 0,
16244
16273
  icon: "o-spreadsheet-Icon.INSERT_COL",
16245
16274
  };
16246
16275
  const colInsertColsBefore = {
16247
16276
  name: COLUMN_INSERT_COLUMNS_BEFORE_NAME,
16248
16277
  execute: INSERT_COLUMNS_BEFORE_ACTION,
16278
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16279
+ IS_ONLY_ONE_RANGE(env) &&
16280
+ env.model.getters.getActiveRows().size === 0,
16281
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16249
16282
  };
16250
16283
  const topBarInsertColsBefore = {
16251
16284
  ...colInsertColsBefore,
16252
16285
  name: MENU_INSERT_COLUMNS_BEFORE_NAME,
16253
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16254
16286
  };
16255
16287
  const cellInsertColsBefore = {
16256
16288
  ...colInsertColsBefore,
16257
16289
  name: CELL_INSERT_COLUMNS_BEFORE_NAME,
16258
16290
  isVisible: IS_ONLY_ONE_RANGE,
16259
- icon: "o-spreadsheet-Icon.INSERT_COL",
16291
+ icon: "o-spreadsheet-Icon.INSERT_COL_BEFORE",
16260
16292
  };
16261
16293
  const colInsertColsAfter = {
16262
16294
  name: COLUMN_INSERT_COLUMNS_AFTER_NAME,
16263
16295
  execute: INSERT_COLUMNS_AFTER_ACTION,
16296
+ isVisible: (env) => isConsecutive(env.model.getters.getActiveCols()) &&
16297
+ IS_ONLY_ONE_RANGE(env) &&
16298
+ env.model.getters.getActiveRows().size === 0,
16299
+ icon: "o-spreadsheet-Icon.INSERT_COL_AFTER",
16264
16300
  };
16265
16301
  const topBarInsertColsAfter = {
16266
16302
  ...colInsertColsAfter,
16267
16303
  name: MENU_INSERT_COLUMNS_AFTER_NAME,
16268
16304
  execute: INSERT_COLUMNS_AFTER_ACTION,
16269
- isVisible: (env) => env.model.getters.getActiveRows().size === 0,
16270
16305
  };
16271
16306
  const insertCell = {
16272
16307
  name: _lt("Insert cells"),
16273
- isVisible: IS_ONLY_ONE_RANGE,
16308
+ isVisible: (env) => IS_ONLY_ONE_RANGE(env) &&
16309
+ env.model.getters.getActiveCols().size === 0 &&
16310
+ env.model.getters.getActiveRows().size === 0,
16274
16311
  icon: "o-spreadsheet-Icon.INSERT_CELL",
16275
16312
  };
16276
16313
  const insertCellShiftDown = {
16277
16314
  name: _lt("Insert cells and shift down"),
16278
16315
  execute: INSERT_CELL_SHIFT_DOWN,
16316
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16317
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_DOWN",
16279
16318
  };
16280
16319
  const insertCellShiftRight = {
16281
16320
  name: _lt("Insert cells and shift right"),
16282
16321
  execute: INSERT_CELL_SHIFT_RIGHT,
16322
+ isVisible: (env) => env.model.getters.getActiveRows().size === 0 && env.model.getters.getActiveCols().size === 0,
16323
+ icon: "o-spreadsheet-Icon.INSERT_CELL_SHIFT_RIGHT",
16283
16324
  };
16284
16325
  const insertChart = {
16285
16326
  name: _lt("Chart"),
@@ -16477,6 +16518,7 @@ const splitToColumns = {
16477
16518
  sequence: 1,
16478
16519
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
16479
16520
  isEnabled: (env) => env.model.getters.isSingleColSelected(),
16521
+ icon: "o-spreadsheet-Icon.SPLIT_TEXT",
16480
16522
  };
16481
16523
 
16482
16524
  const formatNumberAutomatic = {
@@ -16692,60 +16734,6 @@ const clearFormat = {
16692
16734
  execute: FORMAT_CLEARFORMAT_ACTION,
16693
16735
  icon: "o-spreadsheet-Icon.CLEAR_FORMAT",
16694
16736
  };
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
16737
  function fontSizeMenuBuilder() {
16750
16738
  return FONT_SIZES.map((fs) => {
16751
16739
  return {
@@ -16758,24 +16746,17 @@ function fontSizeMenuBuilder() {
16758
16746
  });
16759
16747
  }
16760
16748
  function isAutomaticFormatSelected(env) {
16761
- const activeCell = env.model.getters.getActiveCell();
16749
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16762
16750
  return !activeCell || !activeCell.format;
16763
16751
  }
16764
16752
  function isFormatSelected(env, format) {
16765
- const activeCell = env.model.getters.getActiveCell();
16766
- return activeCell && activeCell.format === format;
16753
+ const activeCell = env.model.getters.getCell(env.model.getters.getActivePosition());
16754
+ return activeCell?.format === format;
16767
16755
  }
16768
16756
  function isFontSizeSelected(env, fontSize) {
16769
16757
  const currentFontSize = env.model.getters.getCurrentStyle().fontSize || DEFAULT_FONT_SIZE;
16770
16758
  return currentFontSize === fontSize;
16771
16759
  }
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
16760
  function getHorizontalAlign(env) {
16780
16761
  const style = env.model.getters.getCurrentStyle();
16781
16762
  if (style.align) {
@@ -16822,18 +16803,7 @@ var ACTION_FORMAT = /*#__PURE__*/Object.freeze({
16822
16803
  fillColor: fillColor,
16823
16804
  formatCF: formatCF,
16824
16805
  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
16806
+ clearFormat: clearFormat
16837
16807
  });
16838
16808
 
16839
16809
  function interactiveFreezeColumnsRows(env, dimension, base) {
@@ -16849,6 +16819,7 @@ const hideCols = {
16849
16819
  name: HIDE_COLUMNS_NAME,
16850
16820
  execute: HIDE_COLUMNS_ACTION,
16851
16821
  isVisible: NOT_ALL_VISIBLE_COLS_SELECTED,
16822
+ icon: "o-spreadsheet-Icon.HIDE_COL",
16852
16823
  };
16853
16824
  const unhideCols = {
16854
16825
  name: _lt("Unhide columns"),
@@ -16870,6 +16841,7 @@ const hideRows = {
16870
16841
  name: HIDE_ROWS_NAME,
16871
16842
  execute: HIDE_ROWS_ACTION,
16872
16843
  isVisible: NOT_ALL_VISIBLE_ROWS_SELECTED,
16844
+ icon: "o-spreadsheet-Icon.HIDE_ROW",
16873
16845
  };
16874
16846
  const unhideRows = {
16875
16847
  name: _lt("Unhide rows"),
@@ -17073,19 +17045,21 @@ colMenuRegistry
17073
17045
  .add("delete_column", {
17074
17046
  ...deleteCols,
17075
17047
  sequence: 90,
17048
+ icon: "o-spreadsheet-Icon.DELETE",
17076
17049
  })
17077
17050
  .add("clear_column", {
17078
17051
  ...clearCols,
17079
17052
  sequence: 100,
17053
+ icon: "o-spreadsheet-Icon.CLEAR",
17080
17054
  })
17081
17055
  .add("hide_columns", {
17082
17056
  ...hideCols,
17083
- sequence: 85,
17057
+ sequence: 105,
17084
17058
  separator: true,
17085
17059
  })
17086
17060
  .add("unhide_columns", {
17087
17061
  ...unhideCols,
17088
- sequence: 86,
17062
+ sequence: 106,
17089
17063
  separator: true,
17090
17064
  })
17091
17065
  .add("conditional_formatting", {
@@ -17183,10 +17157,12 @@ rowMenuRegistry
17183
17157
  .add("delete_row", {
17184
17158
  ...deleteRows,
17185
17159
  sequence: 70,
17160
+ icon: "o-spreadsheet-Icon.DELETE",
17186
17161
  })
17187
17162
  .add("clear_row", {
17188
17163
  ...clearRows,
17189
17164
  sequence: 80,
17165
+ icon: "o-spreadsheet-Icon.CLEAR",
17190
17166
  })
17191
17167
  .add("hide_rows", {
17192
17168
  ...hideRows,
@@ -17679,6 +17655,12 @@ css /* scss */ `
17679
17655
  height: 25px;
17680
17656
  }
17681
17657
  }
17658
+ /** Make the character a bit bigger
17659
+ compared to its neighbor INPUT box */
17660
+ .o-remove-selection {
17661
+ font-weight: bold;
17662
+ font-size: calc(100% + 4px);
17663
+ }
17682
17664
  }
17683
17665
  `;
17684
17666
  /**
@@ -17774,6 +17756,9 @@ class SelectionInput extends Component {
17774
17756
  this.confirm();
17775
17757
  }
17776
17758
  }
17759
+ extractRanges(value) {
17760
+ return this.props.hasSingleRange ? value.split(",")[0] : value;
17761
+ }
17777
17762
  focus(rangeId) {
17778
17763
  this.state.isMissing = false;
17779
17764
  this.state.mode = "select-range";
@@ -17792,10 +17777,11 @@ class SelectionInput extends Component {
17792
17777
  }
17793
17778
  onInputChanged(rangeId, ev) {
17794
17779
  const target = ev.target;
17780
+ const value = this.extractRanges(target.value);
17795
17781
  this.env.model.dispatch("CHANGE_RANGE", {
17796
17782
  id: this.id,
17797
17783
  rangeId,
17798
- value: target.value,
17784
+ value,
17799
17785
  });
17800
17786
  this.triggerChange();
17801
17787
  }
@@ -20564,14 +20550,31 @@ class ContentEditableHelper {
20564
20550
  * */
20565
20551
  getCurrentSelection() {
20566
20552
  let { startElement, endElement, startSelectionOffset, endSelectionOffset } = this.getStartAndEndSelection();
20567
- let startSizeBefore = this.findSizeBeforeElement(startElement);
20568
- let endSizeBefore = this.findSizeBeforeElement(endElement);
20553
+ let startSizeBefore = this.findSelectionIndex(startElement, startSelectionOffset);
20554
+ let endSizeBefore = this.findSelectionIndex(endElement, endSelectionOffset);
20569
20555
  return {
20570
- start: startSizeBefore + startSelectionOffset,
20571
- end: endSizeBefore + endSelectionOffset,
20556
+ start: startSizeBefore,
20557
+ end: endSizeBefore,
20572
20558
  };
20573
20559
  }
20574
- findSizeBeforeElement(nodeToFind) {
20560
+ /**
20561
+ * Computes the text 'index' inside this.el based on the currently selected node and its offset.
20562
+ * The selected node is either a Text node or an Element node.
20563
+ *
20564
+ * case 1 -Text node:
20565
+ * the offset is the number of characters from the start of the node. We have to add this offset to the
20566
+ * content length of all previous nodes.
20567
+ *
20568
+ * case 2 - Element node:
20569
+ * the offset is the number of child nodes before the selected node. We have to add the content length of
20570
+ * all the bnodes prior to the selected node as well as the content of the child node before the offset.
20571
+ *
20572
+ * See the MDN documentation for more details.
20573
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/startOffset
20574
+ * https://developer.mozilla.org/en-US/docs/Web/API/Range/endOffset
20575
+ *
20576
+ */
20577
+ findSelectionIndex(nodeToFind, nodeOffset) {
20575
20578
  let usedCharacters = 0;
20576
20579
  let it = iterateChildren(this.el);
20577
20580
  let current = it.next();
@@ -20598,6 +20601,28 @@ class ContentEditableHelper {
20598
20601
  if (current.value !== nodeToFind) {
20599
20602
  throw new Error("Cannot find the node in the children of the element");
20600
20603
  }
20604
+ else {
20605
+ if (!current.value.hasChildNodes()) {
20606
+ usedCharacters += nodeOffset;
20607
+ }
20608
+ else {
20609
+ const children = [...current.value.childNodes].slice(0, nodeOffset);
20610
+ usedCharacters += children.reduce((acc, child, index) => {
20611
+ if (child.textContent !== null) {
20612
+ // need to account for paragraph nodes that implicitely add a new line
20613
+ // except for the last paragraph
20614
+ let chars = child.textContent.length;
20615
+ if (child.nodeName === "P" && index !== children.length - 1) {
20616
+ chars++;
20617
+ }
20618
+ return acc + chars;
20619
+ }
20620
+ else {
20621
+ return acc;
20622
+ }
20623
+ }, 0);
20624
+ }
20625
+ }
20601
20626
  if (nodeToFind.nodeName === "P" && !isFirstParagraph && nodeToFind.textContent == "") {
20602
20627
  usedCharacters++;
20603
20628
  }
@@ -21398,13 +21423,13 @@ GridComposer.props = {
21398
21423
 
21399
21424
  const CSS$1 = css /* scss */ `
21400
21425
  .o-filter-icon {
21426
+ color: ${FILTERS_COLOR};
21401
21427
  position: absolute;
21402
21428
  display: flex;
21403
21429
  align-items: center;
21404
21430
  justify-content: center;
21405
21431
  width: ${FILTER_ICON_EDGE_LENGTH}px;
21406
21432
  height: ${FILTER_ICON_EDGE_LENGTH}px;
21407
- color: #4a4f59;
21408
21433
  }
21409
21434
  .o-filter-icon:hover {
21410
21435
  background: ${FILTERS_COLOR};
@@ -24135,17 +24160,17 @@ const CELL_TYPE_CONVERSION_MAP = {
24135
24160
  const BORDER_STYLE_CONVERSION_MAP = {
24136
24161
  dashDot: "thin",
24137
24162
  dashDotDot: "thin",
24138
- dashed: "thin",
24139
- dotted: "thin",
24163
+ dashed: "dashed",
24164
+ dotted: "dotted",
24140
24165
  double: "thin",
24141
24166
  hair: "thin",
24142
- medium: "thin",
24167
+ medium: "medium",
24143
24168
  mediumDashDot: "thin",
24144
24169
  mediumDashDotDot: "thin",
24145
24170
  mediumDashed: "thin",
24146
24171
  none: undefined,
24147
24172
  slantDashDot: "thin",
24148
- thick: "thin",
24173
+ thick: "thick",
24149
24174
  thin: "thin",
24150
24175
  };
24151
24176
  /** Conversion map Horizontal Alignment in XLSX <=> Horizontal Alignment in o_spreadsheet*/
@@ -24644,7 +24669,7 @@ function convertBorderDescr$1(borderDescr, warningManager) {
24644
24669
  return undefined;
24645
24670
  addBorderDescrWarnings(borderDescr, warningManager);
24646
24671
  const style = BORDER_STYLE_CONVERSION_MAP[borderDescr.style];
24647
- return style ? [style, convertColor(borderDescr.color)] : undefined;
24672
+ return style ? { style, color: convertColor(borderDescr.color) } : undefined;
24648
24673
  }
24649
24674
  function convertStyles(data, warningManager) {
24650
24675
  const stylesArray = data.styles.map((style) => {
@@ -26042,8 +26067,8 @@ function convertBorderDescr(descr) {
26042
26067
  return undefined;
26043
26068
  }
26044
26069
  return {
26045
- style: descr[0],
26046
- color: { rgb: descr[1] },
26070
+ style: descr.style,
26071
+ color: { rgb: descr.color },
26047
26072
  };
26048
26073
  }
26049
26074
  function extractStyle(cell, data) {
@@ -26621,7 +26646,7 @@ const TABLE_HEADER_STYLE = {
26621
26646
  const TABLE_HIGHLIGHTED_CELL_STYLE = {
26622
26647
  bold: true,
26623
26648
  };
26624
- const TABLE_BORDER_STYLE = ["thin", "#000000FF"];
26649
+ const TABLE_BORDER_STYLE = { style: "thin", color: "#000000FF" };
26625
26650
  /**
26626
26651
  * Convert the imported XLSX tables.
26627
26652
  *
@@ -28190,7 +28215,7 @@ function normalizeV9(formula) {
28190
28215
  * a breaking change is made in the way the state is handled, and an upgrade
28191
28216
  * function should be defined
28192
28217
  */
28193
- const CURRENT_VERSION = 12;
28218
+ const CURRENT_VERSION = 13;
28194
28219
  const INITIAL_SHEET_ID = "Sheet1";
28195
28220
  /**
28196
28221
  * This function tries to load anything that could look like a valid
@@ -28454,6 +28479,25 @@ const MIGRATIONS = [
28454
28479
  return data;
28455
28480
  },
28456
28481
  },
28482
+ {
28483
+ description: "Change Border description structure",
28484
+ from: 12,
28485
+ to: 13,
28486
+ applyMigration(data) {
28487
+ for (const borderId in data.borders) {
28488
+ const border = data.borders[borderId];
28489
+ for (const position in border) {
28490
+ if (Array.isArray(border[position])) {
28491
+ border[position] = {
28492
+ style: border[position][0],
28493
+ color: border[position][1],
28494
+ };
28495
+ }
28496
+ }
28497
+ }
28498
+ return data;
28499
+ },
28500
+ },
28457
28501
  ];
28458
28502
  /**
28459
28503
  * This function is used to repair faulty data independently of the migration.
@@ -28789,7 +28833,7 @@ class CorePlugin extends BasePlugin {
28789
28833
  * - borders
28790
28834
  */
28791
28835
  class BordersPlugin extends CorePlugin {
28792
- static getters = ["getCellBorder"];
28836
+ static getters = ["getCellBorder", "getBordersColors"];
28793
28837
  borders = {};
28794
28838
  // ---------------------------------------------------------------------------
28795
28839
  // Command Handling
@@ -28820,10 +28864,15 @@ class BordersPlugin extends CorePlugin {
28820
28864
  case "SET_BORDER":
28821
28865
  this.setBorder(cmd.sheetId, cmd.col, cmd.row, cmd.border);
28822
28866
  break;
28823
- case "SET_FORMATTING":
28867
+ case "SET_ZONE_BORDERS":
28824
28868
  if (cmd.border) {
28825
28869
  const target = cmd.target.map((zone) => this.getters.expandZone(cmd.sheetId, zone));
28826
- this.setBorders(cmd.sheetId, target, cmd.border);
28870
+ this.setBorders(cmd.sheetId, target, cmd.border.position, cmd.border.color === ""
28871
+ ? undefined
28872
+ : {
28873
+ style: cmd.border.style || DEFAULT_BORDER_DESC.style,
28874
+ color: cmd.border.color || DEFAULT_BORDER_DESC.color,
28875
+ });
28827
28876
  }
28828
28877
  break;
28829
28878
  case "CLEAR_FORMATTING":
@@ -28912,6 +28961,23 @@ class BordersPlugin extends CorePlugin {
28912
28961
  }
28913
28962
  return border;
28914
28963
  }
28964
+ getBordersColors(sheetId) {
28965
+ const colors = [];
28966
+ const sheetBorders = this.borders[sheetId];
28967
+ if (sheetBorders) {
28968
+ for (const borders of sheetBorders.filter(isDefined$1)) {
28969
+ for (const cellBorder of borders) {
28970
+ if (cellBorder?.horizontal) {
28971
+ colors.push(cellBorder.horizontal.color);
28972
+ }
28973
+ if (cellBorder?.vertical) {
28974
+ colors.push(cellBorder.vertical.color);
28975
+ }
28976
+ }
28977
+ }
28978
+ }
28979
+ return colors;
28980
+ }
28915
28981
  // ---------------------------------------------------------------------------
28916
28982
  // Private
28917
28983
  // ---------------------------------------------------------------------------
@@ -29123,43 +29189,43 @@ class BordersPlugin extends CorePlugin {
29123
29189
  * Set the borders of a zone by computing the borders to add from the given
29124
29190
  * command
29125
29191
  */
29126
- setBorders(sheetId, zones, command) {
29127
- if (command === "clear") {
29192
+ setBorders(sheetId, zones, position, border) {
29193
+ if (position === "clear") {
29128
29194
  return this.clearBorders(sheetId, zones);
29129
29195
  }
29130
29196
  for (let zone of zones) {
29131
- if (command === "h" || command === "hv" || command === "all") {
29197
+ if (position === "h" || position === "hv" || position === "all") {
29132
29198
  for (let row = zone.top + 1; row <= zone.bottom; row++) {
29133
29199
  for (let col = zone.left; col <= zone.right; col++) {
29134
- this.addBorder(sheetId, col, row, { top: DEFAULT_BORDER_DESC });
29200
+ this.addBorder(sheetId, col, row, { top: border });
29135
29201
  }
29136
29202
  }
29137
29203
  }
29138
- if (command === "v" || command === "hv" || command === "all") {
29204
+ if (position === "v" || position === "hv" || position === "all") {
29139
29205
  for (let row = zone.top; row <= zone.bottom; row++) {
29140
29206
  for (let col = zone.left + 1; col <= zone.right; col++) {
29141
- this.addBorder(sheetId, col, row, { left: DEFAULT_BORDER_DESC });
29207
+ this.addBorder(sheetId, col, row, { left: border });
29142
29208
  }
29143
29209
  }
29144
29210
  }
29145
- if (command === "left" || command === "all" || command === "external") {
29211
+ if (position === "left" || position === "all" || position === "external") {
29146
29212
  for (let row = zone.top; row <= zone.bottom; row++) {
29147
- this.addBorder(sheetId, zone.left, row, { left: DEFAULT_BORDER_DESC });
29213
+ this.addBorder(sheetId, zone.left, row, { left: border });
29148
29214
  }
29149
29215
  }
29150
- if (command === "right" || command === "all" || command === "external") {
29216
+ if (position === "right" || position === "all" || position === "external") {
29151
29217
  for (let row = zone.top; row <= zone.bottom; row++) {
29152
- this.addBorder(sheetId, zone.right + 1, row, { left: DEFAULT_BORDER_DESC });
29218
+ this.addBorder(sheetId, zone.right + 1, row, { left: border });
29153
29219
  }
29154
29220
  }
29155
- if (command === "top" || command === "all" || command === "external") {
29221
+ if (position === "top" || position === "all" || position === "external") {
29156
29222
  for (let col = zone.left; col <= zone.right; col++) {
29157
- this.addBorder(sheetId, col, zone.top, { top: DEFAULT_BORDER_DESC });
29223
+ this.addBorder(sheetId, col, zone.top, { top: border });
29158
29224
  }
29159
29225
  }
29160
- if (command === "bottom" || command === "all" || command === "external") {
29226
+ if (position === "bottom" || position === "all" || position === "external") {
29161
29227
  for (let col = zone.left; col <= zone.right; col++) {
29162
- this.addBorder(sheetId, col, zone.bottom + 1, { top: DEFAULT_BORDER_DESC });
29228
+ this.addBorder(sheetId, col, zone.bottom + 1, { top: border });
29163
29229
  }
29164
29230
  }
29165
29231
  }
@@ -29173,16 +29239,22 @@ class BordersPlugin extends CorePlugin {
29173
29239
  const bordersBottomRight = this.getCellBorder({ sheetId, col: right, row: bottom });
29174
29240
  this.clearBorders(sheetId, [zone]);
29175
29241
  if (bordersTopLeft?.top) {
29176
- this.setBorders(sheetId, [{ ...zone, bottom: top }], "top");
29242
+ this.setBorders(sheetId, [{ ...zone, bottom: top }], "top", bordersTopLeft.top);
29177
29243
  }
29178
29244
  if (bordersTopLeft?.left) {
29179
- this.setBorders(sheetId, [{ ...zone, right: left }], "left");
29245
+ this.setBorders(sheetId, [{ ...zone, right: left }], "left", bordersTopLeft.left);
29246
+ }
29247
+ if (bordersBottomRight?.bottom) {
29248
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersBottomRight.bottom);
29249
+ }
29250
+ else if (bordersTopLeft?.bottom) {
29251
+ this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom", bordersTopLeft.bottom);
29180
29252
  }
29181
- if (bordersBottomRight?.bottom || bordersTopLeft?.bottom) {
29182
- this.setBorders(sheetId, [{ ...zone, top: bottom }], "bottom");
29253
+ if (bordersBottomRight?.right) {
29254
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersBottomRight.right);
29183
29255
  }
29184
- if (bordersBottomRight?.right || bordersTopLeft?.right) {
29185
- this.setBorders(sheetId, [{ ...zone, left: right }], "right");
29256
+ else if (bordersTopLeft?.right) {
29257
+ this.setBorders(sheetId, [{ ...zone, left: right }], "right", bordersTopLeft.right);
29186
29258
  }
29187
29259
  }
29188
29260
  // ---------------------------------------------------------------------------
@@ -33035,6 +33107,9 @@ class CustomColorsPlugin extends UIPlugin {
33035
33107
  case "UPDATE_CHART":
33036
33108
  case "CREATE_CHART":
33037
33109
  case "ADD_CONDITIONAL_FORMAT":
33110
+ case "SET_BORDER":
33111
+ case "SET_ZONE_BORDERS":
33112
+ case "SET_FORMATTING":
33038
33113
  this.shouldUpdateColors = true;
33039
33114
  }
33040
33115
  }
@@ -33049,8 +33124,7 @@ class CustomColorsPlugin extends UIPlugin {
33049
33124
  getCustomColors() {
33050
33125
  let usedColors = [];
33051
33126
  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));
33127
+ usedColors = usedColors.concat(this.getColorsFromCells(sheetId), this.getFormattingColors(sheetId), this.getChartColors(sheetId));
33054
33128
  }
33055
33129
  return sortWithClusters([
33056
33130
  ...new Set(
@@ -33060,7 +33134,8 @@ class CustomColorsPlugin extends UIPlugin {
33060
33134
  [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
33061
33135
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
33062
33136
  }
33063
- getColorsFromCells(cells) {
33137
+ getColorsFromCells(sheetId) {
33138
+ const cells = Object.values(this.getters.getCells(sheetId));
33064
33139
  const colors = new Set();
33065
33140
  for (const cell of cells) {
33066
33141
  if (cell.style?.textColor) {
@@ -33070,6 +33145,9 @@ class CustomColorsPlugin extends UIPlugin {
33070
33145
  colors.add(cell.style.fillColor);
33071
33146
  }
33072
33147
  }
33148
+ for (const color of this.getters.getBordersColors(sheetId)) {
33149
+ colors.add(color);
33150
+ }
33073
33151
  return [...colors];
33074
33152
  }
33075
33153
  getFormattingColors(sheetId) {
@@ -33343,7 +33421,7 @@ class EvaluationPlugin extends UIPlugin {
33343
33421
  // magic "empty" value
33344
33422
  // Returning {value: null} instead of undefined will ensure that we don't
33345
33423
  // fall back on the default value of the argument provided to the formula's compute function
33346
- return { value: null };
33424
+ return { value: null, format: cell?.format };
33347
33425
  }
33348
33426
  return getEvaluatedCell(cell);
33349
33427
  }
@@ -36172,6 +36250,7 @@ function transformPositionWithMerge(cmd, executed) {
36172
36250
 
36173
36251
  class Revision {
36174
36252
  rootCommand;
36253
+ timestamp;
36175
36254
  id;
36176
36255
  clientId;
36177
36256
  _commands = [];
@@ -36185,8 +36264,9 @@ class Revision {
36185
36264
  * - clientId: Client who initiated the action
36186
36265
  * - changes: List of changes applied on the state.
36187
36266
  */
36188
- constructor(id, clientId, commands, rootCommand, changes) {
36267
+ constructor(id, clientId, commands, rootCommand, changes, timestamp) {
36189
36268
  this.rootCommand = rootCommand;
36269
+ this.timestamp = timestamp;
36190
36270
  this.id = id;
36191
36271
  this.clientId = clientId;
36192
36272
  this._commands = [...commands];
@@ -36258,7 +36338,7 @@ class Session extends EventBus {
36258
36338
  save(rootCommand, commands, changes) {
36259
36339
  if (!commands.length || !changes.length || !this.canApplyOptimisticUpdate())
36260
36340
  return;
36261
- const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes);
36341
+ const revision = new Revision(this.uuidGenerator.uuidv4(), this.clientId, commands, rootCommand, changes, Date.now());
36262
36342
  this.revisions.append(revision.id, revision);
36263
36343
  this.trigger("new-local-state-update", { id: revision.id });
36264
36344
  this.sendUpdateMessage({
@@ -36309,13 +36389,9 @@ class Session extends EventBus {
36309
36389
  }
36310
36390
  loadInitialMessages(messages) {
36311
36391
  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
36392
  for (const message of messages) {
36316
36393
  this.onMessageReceived(message);
36317
36394
  }
36318
- this.off("unexpected-revision-id", this);
36319
36395
  this.isReplayingInitialRevisions = false;
36320
36396
  }
36321
36397
  /**
@@ -36401,6 +36477,10 @@ class Session extends EventBus {
36401
36477
  onMessageReceived(message) {
36402
36478
  if (this.isAlreadyProcessed(message))
36403
36479
  return;
36480
+ if (this.isWrongServerRevisionId(message)) {
36481
+ this.trigger("unexpected-revision-id");
36482
+ return;
36483
+ }
36404
36484
  switch (message.type) {
36405
36485
  case "CLIENT_MOVED":
36406
36486
  this.onClientMoved(message);
@@ -36427,12 +36507,8 @@ class Session extends EventBus {
36427
36507
  });
36428
36508
  break;
36429
36509
  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");
36510
+ const { clientId, commands, timestamp } = message;
36511
+ const revision = new Revision(message.nextRevisionId, clientId, commands, "REMOTE", undefined, timestamp);
36436
36512
  if (revision.clientId !== this.clientId) {
36437
36513
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36438
36514
  const pendingCommands = this.pendingMessages
@@ -36445,7 +36521,7 @@ class Session extends EventBus {
36445
36521
  }
36446
36522
  break;
36447
36523
  case "SNAPSHOT_CREATED": {
36448
- const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT");
36524
+ const revision = new Revision(message.nextRevisionId, "server", [], "SNAPSHOT", undefined, Date.now());
36449
36525
  this.revisions.insert(revision.id, revision, message.serverRevisionId);
36450
36526
  this.dropPendingHistoryMessages();
36451
36527
  this.trigger("snapshot");
@@ -36562,6 +36638,17 @@ class Session extends EventBus {
36562
36638
  return false;
36563
36639
  }
36564
36640
  }
36641
+ isWrongServerRevisionId(message) {
36642
+ switch (message.type) {
36643
+ case "REMOTE_REVISION":
36644
+ case "REVISION_REDONE":
36645
+ case "REVISION_UNDONE":
36646
+ case "SNAPSHOT_CREATED":
36647
+ return message.serverRevisionId !== this.serverRevisionId;
36648
+ default:
36649
+ return false;
36650
+ }
36651
+ }
36565
36652
  dropPendingHistoryMessages() {
36566
36653
  this.waitingUndoRedoAck = false;
36567
36654
  this.pendingMessages = this.pendingMessages.filter(({ type }) => type !== "REVISION_REDONE" && type !== "REVISION_UNDONE");
@@ -37277,7 +37364,7 @@ class RendererPlugin extends UIPlugin {
37277
37364
  }
37278
37365
  }
37279
37366
  drawBorders(renderingContext) {
37280
- const { ctx, thinLineWidth } = renderingContext;
37367
+ const { ctx } = renderingContext;
37281
37368
  for (let box of this.boxes) {
37282
37369
  const border = box.border;
37283
37370
  if (border) {
@@ -37296,13 +37383,66 @@ class RendererPlugin extends UIPlugin {
37296
37383
  }
37297
37384
  }
37298
37385
  }
37299
- function drawBorder([style, color], x1, y1, x2, y2) {
37386
+ /**
37387
+ * Following https://usefulangle.com/post/17/html5-canvas-drawing-1px-crisp-straight-lines,
37388
+ * we need to make sure that a "single" pixel line is drawn on a "half" pixel coordinate,
37389
+ * while a "double" pixel line is drawn on a "full" pixel coordinate. As, in the rendering
37390
+ * process, we always had 0.5 before rendering line (to make sure it is drawn on a "half"
37391
+ * pixel), we need to correct this behavior for the "medium" and the "dotted" styles, as
37392
+ * they are drawing a two pixels width line.
37393
+ * We also adapt here the coordinates of the line to make sure corner are correctly drawn,
37394
+ * avoiding a "round corners" effect. This is done by subtracting 1 pixel to the origin of
37395
+ * each line and adding 1 pixel to the end of each line (depending on the direction of the
37396
+ * line).
37397
+ */
37398
+ function drawBorder({ style, color }, x1, y1, x2, y2) {
37300
37399
  ctx.strokeStyle = color;
37301
- ctx.lineWidth = (style === "thin" ? 2 : 3) * thinLineWidth;
37400
+ switch (style) {
37401
+ case "medium":
37402
+ ctx.lineWidth = 2;
37403
+ x1 += y1 === y2 ? -0.5 : 0.5;
37404
+ x2 += y1 === y2 ? 1.5 : 0.5;
37405
+ y1 += x1 === x2 ? -0.5 : 0.5;
37406
+ y2 += x1 === x2 ? 1.5 : 0.5;
37407
+ break;
37408
+ case "thick":
37409
+ ctx.lineWidth = 3;
37410
+ if (y1 === y2) {
37411
+ x1--;
37412
+ x2++;
37413
+ }
37414
+ if (x1 === x2) {
37415
+ y1--;
37416
+ y2++;
37417
+ }
37418
+ break;
37419
+ case "dashed":
37420
+ ctx.lineWidth = 1;
37421
+ ctx.setLineDash([1, 3]);
37422
+ break;
37423
+ case "dotted":
37424
+ ctx.lineWidth = 1;
37425
+ if (y1 === y2) {
37426
+ x1 += 0.5;
37427
+ x2 += 0.5;
37428
+ }
37429
+ if (x1 === x2) {
37430
+ y1 += 0.5;
37431
+ y2 += 0.5;
37432
+ }
37433
+ ctx.setLineDash([1, 1]);
37434
+ break;
37435
+ case "thin":
37436
+ default:
37437
+ ctx.lineWidth = 1;
37438
+ break;
37439
+ }
37302
37440
  ctx.beginPath();
37303
37441
  ctx.moveTo(x1, y1);
37304
37442
  ctx.lineTo(x2, y2);
37305
37443
  ctx.stroke();
37444
+ ctx.lineWidth = 1;
37445
+ ctx.setLineDash([]);
37306
37446
  }
37307
37447
  }
37308
37448
  drawTexts(renderingContext) {
@@ -37780,6 +37920,9 @@ class SelectionInputPlugin extends UIPlugin {
37780
37920
  activeSheet;
37781
37921
  willAddNewRange = false;
37782
37922
  constructor(config, initialRanges, inputHasSingleRange) {
37923
+ if (inputHasSingleRange && initialRanges.length > 1) {
37924
+ throw new Error("Input with a single range cannot be instantiated with several range references.");
37925
+ }
37783
37926
  super(config);
37784
37927
  this.inputHasSingleRange = inputHasSingleRange;
37785
37928
  this.insertNewRange(0, initialRanges);
@@ -37799,6 +37942,11 @@ class SelectionInputPlugin extends UIPlugin {
37799
37942
  return 30 /* CommandResult.MaximumRangesReached */;
37800
37943
  }
37801
37944
  break;
37945
+ case "CHANGE_RANGE":
37946
+ if (this.inputHasSingleRange && cmd.value.split(",").length > 1) {
37947
+ return 30 /* CommandResult.MaximumRangesReached */;
37948
+ }
37949
+ break;
37802
37950
  }
37803
37951
  return 0 /* CommandResult.Success */;
37804
37952
  }
@@ -41285,7 +41433,8 @@ class GridSelectionPlugin extends UIPlugin {
41285
41433
  },
41286
41434
  zone: selectedZone,
41287
41435
  };
41288
- this.setSelectionMixin(anchor, [selectedZone]);
41436
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "left", [...cmd.elements]));
41437
+ this.setSelectionMixin(anchor, selections);
41289
41438
  }
41290
41439
  onRowsRemoved(cmd) {
41291
41440
  const { cell, zone } = this.gridSelection.anchor;
@@ -41299,13 +41448,18 @@ class GridSelectionPlugin extends UIPlugin {
41299
41448
  },
41300
41449
  zone: selectedZone,
41301
41450
  };
41302
- this.setSelectionMixin(anchor, [selectedZone]);
41451
+ const selections = this.gridSelection.zones.map((zone) => updateSelectionOnDeletion(zone, "top", [...cmd.elements]));
41452
+ this.setSelectionMixin(anchor, selections);
41303
41453
  }
41304
41454
  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]);
41455
+ const start = cmd.dimension === "COL" ? "left" : "top";
41456
+ const anchorZone = updateSelectionOnInsertion(this.gridSelection.anchor.zone, start, cmd.base, cmd.position, cmd.quantity);
41457
+ const selection = this.gridSelection.zones.map((zone) => updateSelectionOnInsertion(zone, start, cmd.base, cmd.position, cmd.quantity));
41458
+ const anchor = {
41459
+ cell: { col: anchorZone.left, row: anchorZone.top },
41460
+ zone: anchorZone,
41461
+ };
41462
+ this.setSelectionMixin(anchor, selection);
41309
41463
  }
41310
41464
  onMoveElements(cmd) {
41311
41465
  const thickness = cmd.elements.length;
@@ -42551,7 +42705,6 @@ css /* scss */ `
42551
42705
  color: dimgrey;
42552
42706
  }
42553
42707
  .o-sidePanelClose {
42554
- font-size: 1.5rem;
42555
42708
  padding: 5px 10px;
42556
42709
  cursor: pointer;
42557
42710
  &:hover {
@@ -42736,7 +42889,6 @@ css /* scss */ `
42736
42889
  justify-content: center;
42737
42890
  align-items: center;
42738
42891
  margin: 2px;
42739
- padding: 0px 3px;
42740
42892
  border-radius: 2px;
42741
42893
  min-width: 20px;
42742
42894
  }
@@ -42793,7 +42945,228 @@ ActionButton.props = {
42793
42945
  onClick: { type: Function, optional: true },
42794
42946
  };
42795
42947
 
42948
+ /**
42949
+ * List the available borders positions and the corresponding icons.
42950
+ * The structure of this array is defined to match the order/lines we want
42951
+ * to display in the topbar's border tool.
42952
+ */
42953
+ const BORDER_POSITIONS = [
42954
+ [
42955
+ ["all", "o-spreadsheet-Icon.BORDERS"],
42956
+ ["hv", "o-spreadsheet-Icon.BORDER_HV"],
42957
+ ["h", "o-spreadsheet-Icon.BORDER_H"],
42958
+ ["v", "o-spreadsheet-Icon.BORDER_V"],
42959
+ ["external", "o-spreadsheet-Icon.BORDER_EXTERNAL"],
42960
+ ],
42961
+ [
42962
+ ["left", "o-spreadsheet-Icon.BORDER_LEFT"],
42963
+ ["top", "o-spreadsheet-Icon.BORDER_TOP"],
42964
+ ["right", "o-spreadsheet-Icon.BORDER_RIGHT"],
42965
+ ["bottom", "o-spreadsheet-Icon.BORDER_BOTTOM"],
42966
+ ["clear", "o-spreadsheet-Icon.BORDER_CLEAR"],
42967
+ ],
42968
+ ];
42969
+ // -----------------------------------------------------------------------------
42970
+ // Border Editor
42971
+ // -----------------------------------------------------------------------------
42972
+ css /* scss */ `
42973
+ .o-border-selector {
42974
+ padding: 4px;
42975
+ }
42976
+ .o-divider {
42977
+ border-right: 1px solid #e0e2e4;
42978
+ margin: 0 6px;
42979
+ }
42980
+ .o-border-selector-section {
42981
+ .o-dropdown-line {
42982
+ height: 30px;
42983
+ margin: 1px;
42984
+ .o-line-item {
42985
+ padding: 4px;
42986
+ width: 18px;
42987
+ height: 18px;
42988
+ }
42989
+ }
42990
+ .o-border-style-tool {
42991
+ padding: 0px 3px;
42992
+ margin: 2px;
42993
+ height: 25px;
42994
+ }
42995
+ }
42996
+ .o-style-preview {
42997
+ margin: 7px 5px 7px 5px;
42998
+ width: 60px;
42999
+ height: 5px;
43000
+ }
43001
+ .o-style-thin {
43002
+ border-bottom: 1px solid #000000;
43003
+ }
43004
+ .o-style-medium {
43005
+ border-bottom: 2px solid #000000;
43006
+ }
43007
+ .o-style-thick {
43008
+ border-bottom: 3px solid #000000;
43009
+ }
43010
+ .o-style-dashed {
43011
+ border-bottom: 1px dashed #000000;
43012
+ }
43013
+ .o-style-dotted {
43014
+ border-bottom: 1px dotted #000000;
43015
+ }
43016
+ .o-dropdown-border-type {
43017
+ &:not(.o-disabled):not(.active):hover {
43018
+ background-color: ${BG_HOVER_COLOR};
43019
+ }
43020
+ }
43021
+ .o-dropdown-border-check {
43022
+ width: 20px;
43023
+ font-size: 12px;
43024
+ }
43025
+ .o-border-style-dropdown {
43026
+ background: #ffffff;
43027
+ padding: 4px;
43028
+ .o-dropdown-line {
43029
+ .o-line-item.active {
43030
+ background-color: rgba(0, 0, 0, 0.2);
43031
+ }
43032
+ }
43033
+ }
43034
+ .o-border-picker-button {
43035
+ padding: 0px !important;
43036
+ margin: 5px 0px 0px 0px !important;
43037
+ height: 25px !important;
43038
+ }
43039
+ `;
43040
+ class BorderEditor extends Component {
43041
+ static template = "o-spreadsheet-BorderEditor";
43042
+ static components = { ColorPickerWidget, Popover };
43043
+ BORDER_POSITIONS = BORDER_POSITIONS;
43044
+ lineStyleButtonRef = useRef("lineStyleButton");
43045
+ borderStyles = borderStyles;
43046
+ state = useState({
43047
+ activeTool: undefined,
43048
+ });
43049
+ toggleDropdownTool(tool) {
43050
+ const isOpen = this.state.activeTool === tool;
43051
+ this.state.activeTool = isOpen ? undefined : tool;
43052
+ }
43053
+ closeDropdown() {
43054
+ this.state.activeTool = undefined;
43055
+ }
43056
+ setBorderPosition(position) {
43057
+ this.props.onBorderPositionPicked(position);
43058
+ this.closeDropdown();
43059
+ }
43060
+ setBorderColor(color) {
43061
+ this.props.onBorderColorPicked(color);
43062
+ this.closeDropdown();
43063
+ }
43064
+ setBorderStyle(style) {
43065
+ this.props.onBorderStylePicked(style);
43066
+ this.closeDropdown();
43067
+ }
43068
+ get lineStylePickerPopoverProps() {
43069
+ return {
43070
+ anchorRect: this.lineStylePickerAnchorRect,
43071
+ positioning: "BottomLeft",
43072
+ verticalOffset: 0,
43073
+ };
43074
+ }
43075
+ get popoverProps() {
43076
+ return {
43077
+ anchorRect: this.props.anchorRect,
43078
+ maxHeight: this.props.maxHeight,
43079
+ positioning: "BottomLeft",
43080
+ verticalOffset: 0,
43081
+ };
43082
+ }
43083
+ get lineStylePickerAnchorRect() {
43084
+ const button = this.lineStyleButtonRef.el;
43085
+ if (button === null) {
43086
+ return { x: 0, y: 0, width: 0, height: 0 };
43087
+ }
43088
+ const buttonRect = button.getBoundingClientRect();
43089
+ return {
43090
+ x: buttonRect.x,
43091
+ y: buttonRect.y,
43092
+ width: buttonRect.width,
43093
+ height: buttonRect.height,
43094
+ };
43095
+ }
43096
+ }
43097
+ BorderEditor.props = {
43098
+ class: { type: String, optional: true },
43099
+ currentBorderColor: { type: String, optional: false },
43100
+ currentBorderStyle: { type: String, optional: false },
43101
+ currentBorderPosition: { type: String, optional: true },
43102
+ onBorderColorPicked: Function,
43103
+ onBorderStylePicked: Function,
43104
+ onBorderPositionPicked: Function,
43105
+ maxHeight: { type: Number, optional: true },
43106
+ anchorRect: Object,
43107
+ };
43108
+
43109
+ class BorderEditorWidget extends Component {
43110
+ static template = "o-spreadsheet-BorderEditorWidget";
43111
+ static components = { BorderEditor };
43112
+ borderEditorButtonRef = useRef("borderEditorButton");
43113
+ state = useState({
43114
+ currentColor: DEFAULT_BORDER_DESC.color,
43115
+ currentStyle: DEFAULT_BORDER_DESC.style,
43116
+ currentPosition: undefined,
43117
+ });
43118
+ get borderEditorAnchorRect() {
43119
+ const button = this.borderEditorButtonRef.el;
43120
+ const buttonRect = button.getBoundingClientRect();
43121
+ return {
43122
+ x: buttonRect.x,
43123
+ y: buttonRect.y,
43124
+ width: buttonRect.width,
43125
+ height: buttonRect.height,
43126
+ };
43127
+ }
43128
+ onBorderPositionPicked(position) {
43129
+ this.state.currentPosition = position;
43130
+ this.updateBorder();
43131
+ }
43132
+ onBorderColorPicked(color) {
43133
+ this.state.currentColor = color;
43134
+ this.updateBorder();
43135
+ }
43136
+ onBorderStylePicked(style) {
43137
+ this.state.currentStyle = style;
43138
+ this.updateBorder();
43139
+ }
43140
+ updateBorder() {
43141
+ if (this.state.currentPosition === undefined) {
43142
+ return;
43143
+ }
43144
+ this.env.model.dispatch("SET_ZONE_BORDERS", {
43145
+ sheetId: this.env.model.getters.getActiveSheetId(),
43146
+ target: this.env.model.getters.getSelectedZones(),
43147
+ border: {
43148
+ position: this.state.currentPosition,
43149
+ color: this.state.currentColor,
43150
+ style: this.state.currentStyle,
43151
+ },
43152
+ });
43153
+ }
43154
+ }
43155
+ BorderEditorWidget.props = {
43156
+ toggleBorderEditor: Function,
43157
+ showBorderEditor: Boolean,
43158
+ disabled: { type: Boolean, optional: true },
43159
+ dropdownMaxHeight: { type: Number, optional: true },
43160
+ class: { type: String, optional: true },
43161
+ };
43162
+
42796
43163
  const COMPOSER_MAX_HEIGHT = 100;
43164
+ /* svg free of use from https://uxwing.com/formula-fx-icon/ */
43165
+ const FX_SVG = /*xml*/ `
43166
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
43167
+ <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'/>
43168
+ </svg>
43169
+ `;
42797
43170
  css /* scss */ `
42798
43171
  .o-topbar-composer {
42799
43172
  height: fit-content;
@@ -42801,9 +43174,10 @@ css /* scss */ `
42801
43174
  border: 1px solid;
42802
43175
  z-index: ${ComponentsImportance.TopBarComposer};
42803
43176
 
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");
43177
+ .o-composer:empty:not(:focus):not(.active)::before {
43178
+ content: url("data:image/svg+xml,${encodeURIComponent(FX_SVG)}");
43179
+ position: relative;
43180
+ top: 20%;
42807
43181
  }
42808
43182
  }
42809
43183
  `;
@@ -42926,11 +43300,6 @@ FontSizeEditor.props = {
42926
43300
  dropdownStyle: String,
42927
43301
  };
42928
43302
 
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
43303
  // -----------------------------------------------------------------------------
42935
43304
  // TopBar
42936
43305
  // -----------------------------------------------------------------------------
@@ -42940,27 +43309,6 @@ css /* scss */ `
42940
43309
  font-size: 13px;
42941
43310
  font-weight: 500;
42942
43311
 
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
43312
  .o-topbar-top {
42965
43313
  border-bottom: 1px solid ${SEPARATOR_COLOR};
42966
43314
  padding: 2px 10px;
@@ -42970,6 +43318,11 @@ css /* scss */ `
42970
43318
  .o-topbar-menu {
42971
43319
  padding: 4px 6px;
42972
43320
  margin: 0 2px;
43321
+
43322
+ &.active {
43323
+ background-color: ${BG_HOVER_COLOR};
43324
+ color: #000;
43325
+ }
42973
43326
  }
42974
43327
  }
42975
43328
  }
@@ -43047,6 +43400,7 @@ class TopBar extends Component {
43047
43400
  TopBarComposer,
43048
43401
  FontSizeEditor,
43049
43402
  ActionButton,
43403
+ BorderEditorWidget,
43050
43404
  };
43051
43405
  state = useState({
43052
43406
  menuState: { isOpen: false, position: null, menuItems: [] },
@@ -43122,7 +43476,9 @@ class TopBar extends Component {
43122
43476
  this.state.activeTool = "";
43123
43477
  this.state.menuState.isOpen = true;
43124
43478
  this.state.menuState.position = { x: left, y: top + height };
43125
- this.state.menuState.menuItems = menu.children(this.env);
43479
+ this.state.menuState.menuItems = menu
43480
+ .children(this.env)
43481
+ .sort((a, b) => a.sequence - b.sequence);
43126
43482
  this.state.menuState.parentMenu = menu;
43127
43483
  this.isSelectingMenu = true;
43128
43484
  this.openedEl = ev.target;
@@ -43208,6 +43564,11 @@ class WebClipboardWrapper {
43208
43564
  }
43209
43565
  }
43210
43566
 
43567
+ // If we ever change these colors, make sure the filter tool stays green to match the icon in the grid
43568
+ const ACTIVE_BG_COLOR = BACKGROUND_HEADER_FILTER_COLOR;
43569
+ const ACTIVE_FONT_COLOR = FILTERS_COLOR;
43570
+ const HOVERED_BG_COLOR = BG_HOVER_COLOR;
43571
+ const HOVERED_FONT_COLOR = "#000";
43211
43572
  css /* scss */ `
43212
43573
  .o-spreadsheet {
43213
43574
  position: relative;
@@ -43240,6 +43601,27 @@ css /* scss */ `
43240
43601
  margin-top: ${MENU_SEPARATOR_PADDING}px;
43241
43602
  margin-bottom: ${MENU_SEPARATOR_PADDING}px;
43242
43603
  }
43604
+ .o-hoverable-button {
43605
+ border-radius: 2px;
43606
+ cursor: pointer;
43607
+ .o-icon {
43608
+ color: ${ICONS_COLOR};
43609
+ }
43610
+ &:not(.o-disabled):not(.active):hover {
43611
+ background-color: ${HOVERED_BG_COLOR};
43612
+ color: ${HOVERED_FONT_COLOR};
43613
+ .o-icon {
43614
+ color: ${HOVERED_FONT_COLOR};
43615
+ }
43616
+ }
43617
+ &.active {
43618
+ background-color: ${ACTIVE_BG_COLOR};
43619
+ color: ${ACTIVE_FONT_COLOR};
43620
+ .o-icon {
43621
+ color: ${ACTIVE_FONT_COLOR};
43622
+ }
43623
+ }
43624
+ }
43243
43625
  }
43244
43626
 
43245
43627
  .o-two-columns {
@@ -43389,6 +43771,9 @@ class Spreadsheet extends Component {
43389
43771
  }
43390
43772
  }
43391
43773
  openSidePanel(panel, panelProps) {
43774
+ if (this.sidePanel.isOpen && panel !== this.sidePanel.component) {
43775
+ this.sidePanel.panelProps?.onCloseSidePanel?.();
43776
+ }
43392
43777
  this.sidePanel.component = panel;
43393
43778
  this.sidePanel.panelProps = panelProps;
43394
43779
  this.sidePanel.isOpen = true;
@@ -44098,13 +44483,13 @@ class Tree {
44098
44483
  }
44099
44484
 
44100
44485
  class SelectiveHistory {
44486
+ HEAD_BRANCH;
44487
+ HEAD_OPERATION;
44488
+ tree;
44101
44489
  applyOperation;
44102
44490
  revertOperation;
44103
44491
  buildEmpty;
44104
44492
  buildTransformation;
44105
- HEAD_BRANCH;
44106
- HEAD_OPERATION;
44107
- tree;
44108
44493
  /**
44109
44494
  * The selective history is a data structure used to register changes/updates of a state.
44110
44495
  * Each change/update is called an "operation".
@@ -44125,14 +44510,15 @@ class SelectiveHistory {
44125
44510
  * (used for internal implementation)
44126
44511
  * @param buildTransformation Factory used to build transformations
44127
44512
  */
44128
- constructor(initialOperationId, applyOperation, revertOperation, buildEmpty, buildTransformation) {
44129
- this.applyOperation = applyOperation;
44130
- this.revertOperation = revertOperation;
44131
- this.buildEmpty = buildEmpty;
44132
- this.buildTransformation = buildTransformation;
44513
+ constructor(args) {
44514
+ this.applyOperation = args.applyOperation;
44515
+ this.revertOperation = args.revertOperation;
44516
+ this.buildEmpty = args.buildEmpty;
44517
+ this.buildTransformation = args.buildTransformation;
44133
44518
  this.HEAD_BRANCH = new Branch(this.buildTransformation);
44134
- this.tree = new Tree(buildTransformation, this.HEAD_BRANCH);
44135
- const initial = new Operation(initialOperationId, buildEmpty(initialOperationId));
44519
+ this.tree = new Tree(this.buildTransformation, this.HEAD_BRANCH);
44520
+ const initialOperationId = args.initialOperationId;
44521
+ const initial = new Operation(initialOperationId, this.buildEmpty(initialOperationId));
44136
44522
  this.tree.insertOperationLast(this.HEAD_BRANCH, initial);
44137
44523
  this.HEAD_OPERATION = initial;
44138
44524
  }
@@ -44246,21 +44632,27 @@ class SelectiveHistory {
44246
44632
  }
44247
44633
  }
44248
44634
 
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);
44635
+ function buildRevisionLog(args) {
44636
+ return new SelectiveHistory({
44637
+ initialOperationId: args.initialRevisionId,
44638
+ applyOperation: (revision) => {
44639
+ const commands = revision.commands.slice();
44640
+ const { changes } = args.recordChanges(() => {
44641
+ for (const command of commands) {
44642
+ args.dispatch(command);
44643
+ }
44644
+ });
44645
+ revision.setChanges(changes);
44261
44646
  },
44262
- without: (revision) => (toTransform) => {
44263
- return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand);
44647
+ revertOperation: (revision) => revertChanges([revision]),
44648
+ buildEmpty: (id) => new Revision(id, "empty", []),
44649
+ buildTransformation: {
44650
+ with: (revision) => (toTransform) => {
44651
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands), toTransform.rootCommand, undefined, toTransform.timestamp);
44652
+ },
44653
+ without: (revision) => (toTransform) => {
44654
+ return new Revision(toTransform.id, toTransform.clientId, transformAll(toTransform.commands, revision.commands.map(inverseCommand).flat()), toTransform.rootCommand, undefined, toTransform.timestamp);
44655
+ },
44264
44656
  },
44265
44657
  });
44266
44658
  }
@@ -45933,11 +46325,21 @@ function addBorders(borders) {
45933
46325
  for (let border of Object.values(borders)) {
45934
46326
  borderNodes.push(escapeXml /*xml*/ `
45935
46327
  <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"])} />
46328
+ <left ${formatBorderAttribute(border["left"])}>
46329
+ ${addBorderColor(border["left"])}
46330
+ </left>
46331
+ <right ${formatBorderAttribute(border["right"])}>
46332
+ ${addBorderColor(border["right"])}
46333
+ </right>
46334
+ <top ${formatBorderAttribute(border["top"])}>
46335
+ ${addBorderColor(border["top"])}
46336
+ </top>
46337
+ <bottom ${formatBorderAttribute(border["bottom"])}>
46338
+ ${addBorderColor(border["bottom"])}
46339
+ </bottom>
46340
+ <diagonal ${formatBorderAttribute(border["diagonal"])}>
46341
+ ${addBorderColor(border["diagonal"])}
46342
+ </diagonal>
45941
46343
  </border>
45942
46344
  `);
45943
46345
  }
@@ -45951,10 +46353,15 @@ function formatBorderAttribute(description) {
45951
46353
  if (!description) {
45952
46354
  return escapeXml ``;
45953
46355
  }
45954
- return formatAttributes([
45955
- ["style", description.style],
45956
- ["color", toXlsxHexColor(description.color.rgb)],
45957
- ]);
46356
+ return formatAttributes([["style", description.style]]);
46357
+ }
46358
+ function addBorderColor(description) {
46359
+ if (!description) {
46360
+ return escapeXml ``;
46361
+ }
46362
+ return escapeXml /*xml*/ `
46363
+ <color ${formatAttributes([["rgb", toXlsxHexColor(description.color.rgb)]])}/>
46364
+ `;
45958
46365
  }
45959
46366
  function addStyles(styles) {
45960
46367
  const styleNodes = [];
@@ -46598,6 +47005,7 @@ class Model extends EventBus {
46598
47005
  this.setupCorePlugin(Plugin, workbookData);
46599
47006
  }
46600
47007
  Object.assign(this.getters, this.coreGetters);
47008
+ this.session.loadInitialMessages(stateUpdateMessages);
46601
47009
  for (let Plugin of statefulUIPluginRegistry.getAll()) {
46602
47010
  const plugin = this.setupUiPlugin(Plugin);
46603
47011
  this.statefulUIPlugins.push(plugin);
@@ -46625,8 +47033,6 @@ class Model extends EventBus {
46625
47033
  // This should be done after construction of LocalHistory due to order of
46626
47034
  // events
46627
47035
  this.setupSessionEvents();
46628
- // Load the initial revisions
46629
- this.session.loadInitialMessages(stateUpdateMessages);
46630
47036
  this.joinSession();
46631
47037
  if (config.snapshotRequested) {
46632
47038
  this.session.snapshot(this.exportData());
@@ -46690,14 +47096,18 @@ class Model extends EventBus {
46690
47096
  this.finalize();
46691
47097
  }
46692
47098
  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;
47099
+ const session = new Session(buildRevisionLog({
47100
+ initialRevisionId: revisionId,
47101
+ recordChanges: this.state.recordChanges.bind(this.state),
47102
+ dispatch: (command) => {
47103
+ const result = this.checkDispatchAllowed(command);
47104
+ if (!result.isSuccessful) {
47105
+ return;
47106
+ }
47107
+ this.isReplayingCommand = true;
47108
+ this.dispatchToHandlers(this.coreHandlers, command);
47109
+ this.isReplayingCommand = false;
47110
+ },
46701
47111
  }), this.config.transportService, revisionId);
46702
47112
  return session;
46703
47113
  }
@@ -47068,6 +47478,6 @@ function addFunction(functionName, functionDescription) {
47068
47478
  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
47479
 
47070
47480
 
47071
- __info__.version = '16.3.0-alpha.7';
47072
- __info__.date = '2023-05-02T13:08:09.275Z';
47073
- __info__.hash = '77c8f5f';
47481
+ __info__.version = '16.3.0-alpha.8';
47482
+ __info__.date = '2023-05-12T10:59:42.550Z';
47483
+ __info__.hash = 'afee7d5';