@odoo/o-spreadsheet 16.3.0-alpha.2 → 16.3.0-alpha.3

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.
@@ -113,88 +113,88 @@ const FILTERS_COLOR = "#188038";
113
113
  const BACKGROUND_HEADER_FILTER_COLOR = "#E6F4EA";
114
114
  const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = "#CEEAD6";
115
115
  const SEPARATOR_COLOR = "#E0E2E4";
116
- // Color picker
116
+ // Color picker defaults as upper case HEX to match `toHex`helper
117
117
  const COLOR_PICKER_DEFAULTS = [
118
118
  "#000000",
119
119
  "#434343",
120
120
  "#666666",
121
121
  "#999999",
122
- "#b7b7b7",
123
- "#cccccc",
124
- "#d9d9d9",
125
- "#efefef",
126
- "#f3f3f3",
127
- "#ffffff",
122
+ "#B7B7B7",
123
+ "#CCCCCC",
124
+ "#D9D9D9",
125
+ "#EFEFEF",
126
+ "#F3F3F3",
127
+ "#FFFFFF",
128
128
  "#980000",
129
- "#ff0000",
130
- "#ff9900",
131
- "#ffff00",
132
- "#00ff00",
133
- "#00ffff",
134
- "#4a86e8",
135
- "#0000ff",
136
- "#9900ff",
137
- "#ff00ff",
138
- "#e6b8af",
139
- "#f4cccc",
140
- "#fce5cd",
141
- "#fff2cc",
142
- "#d9ead3",
143
- "#d0e0e3",
144
- "#c9daf8",
145
- "#cfe2f3",
146
- "#d9d2e9",
147
- "#ead1dc",
148
- "#dd7e6b",
149
- "#ea9999",
150
- "#f9cb9c",
151
- "#ffe599",
152
- "#b6d7a8",
153
- "#a2c4c9",
154
- "#a4c2f4",
155
- "#9fc5e8",
156
- "#b4a7d6",
157
- "#d5a6bd",
158
- "#cc4125",
159
- "#e06666",
160
- "#f6b26b",
161
- "#ffd966",
162
- "#93c47d",
163
- "#76a5af",
164
- "#6d9eeb",
165
- "#6fa8dc",
166
- "#8e7cc3",
167
- "#c27ba0",
168
- "#a61c00",
169
- "#cc0000",
170
- "#e69138",
171
- "#f1c232",
172
- "#6aa84f",
173
- "#45818e",
174
- "#3c78d8",
175
- "#3d85c6",
176
- "#674ea7",
177
- "#a64d79",
178
- "#85200c",
129
+ "#FF0000",
130
+ "#FF9900",
131
+ "#FFFF00",
132
+ "#00FF00",
133
+ "#00FFFF",
134
+ "#4A86E8",
135
+ "#0000FF",
136
+ "#9900FF",
137
+ "#FF00FF",
138
+ "#E6B8AF",
139
+ "#F4CCCC",
140
+ "#FCE5CD",
141
+ "#FFF2CC",
142
+ "#D9EAD3",
143
+ "#D0E0E3",
144
+ "#C9DAF8",
145
+ "#CFE2F3",
146
+ "#D9D2E9",
147
+ "#EAD1DC",
148
+ "#DD7E6B",
149
+ "#EA9999",
150
+ "#F9CB9C",
151
+ "#FFE599",
152
+ "#B6D7A8",
153
+ "#A2C4C9",
154
+ "#A4C2F4",
155
+ "#9FC5E8",
156
+ "#B4A7D6",
157
+ "#D5A6BD",
158
+ "#CC4125",
159
+ "#E06666",
160
+ "#F6B26B",
161
+ "#FFD966",
162
+ "#93C47D",
163
+ "#76A5AF",
164
+ "#6D9EEB",
165
+ "#6FA8DC",
166
+ "#8E7CC3",
167
+ "#C27BA0",
168
+ "#A61C00",
169
+ "#CC0000",
170
+ "#E69138",
171
+ "#F1C232",
172
+ "#6AA84F",
173
+ "#45818E",
174
+ "#3C78D8",
175
+ "#3D85C6",
176
+ "#674EA7",
177
+ "#A64D79",
178
+ "#85200C",
179
179
  "#990000",
180
- "#b45f06",
181
- "#bf9000",
182
- "#38761d",
183
- "#134f5c",
184
- "#1155cc",
185
- "#0b5394",
186
- "#351c75",
187
- "#741b47",
188
- "#5b0f00",
180
+ "#B45F06",
181
+ "#BF9000",
182
+ "#38761D",
183
+ "#134F5C",
184
+ "#1155CC",
185
+ "#0B5394",
186
+ "#351C75",
187
+ "#741B47",
188
+ "#5B0F00",
189
189
  "#660000",
190
- "#783f04",
191
- "#7f6000",
192
- "#274e13",
193
- "#0c343d",
194
- "#1c4587",
190
+ "#783F04",
191
+ "#7F6000",
192
+ "#274E13",
193
+ "#0C343D",
194
+ "#1C4587",
195
195
  "#073763",
196
- "#20124d",
197
- "#4c1130",
196
+ "#20124D",
197
+ "#4C1130",
198
198
  ];
199
199
  // Dimensions
200
200
  const MIN_ROW_HEIGHT = 10;
@@ -223,7 +223,6 @@ const MENU_VERTICAL_PADDING = 8;
223
223
  const MENU_ITEM_HEIGHT = 28;
224
224
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
225
225
  const MENU_SEPARATOR_PADDING = 5;
226
- const MENU_SEPARATOR_HEIGHT = MENU_SEPARATOR_BORDER_WIDTH + 2 * MENU_SEPARATOR_PADDING;
227
226
  // Fonts
228
227
  const DEFAULT_FONT_WEIGHT = "400";
229
228
  const DEFAULT_FONT_SIZE = 10;
@@ -233,7 +232,7 @@ const DEFAULT_FONT = "'Roboto', arial";
233
232
  const DEFAULT_BORDER_DESC = ["thin", "#000"];
234
233
  const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
235
234
  // DateTimeRegex
236
- const DATETIME_FORMAT = /[ymd:]/;
235
+ const DATETIME_FORMAT = /[ymdhs:]/;
237
236
  // Ranges
238
237
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
239
238
  // Max Number of history steps kept in memory
@@ -714,7 +713,7 @@ function getUnquotedSheetName(sheetName) {
714
713
  * '\w' captures [0-9][a-z][A-Z] and _.
715
714
  * @param sheetName Name of the sheet
716
715
  */
717
- function getComposerSheetName(sheetName) {
716
+ function getCanonicalSheetName(sheetName) {
718
717
  var _a;
719
718
  if (((_a = sheetName.match(/\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {
720
719
  sheetName = `'${sheetName}'`;
@@ -735,8 +734,9 @@ function getDefaultCellHeight(cell) {
735
734
  return DEFAULT_CELL_HEIGHT;
736
735
  }
737
736
  const fontSize = computeTextFontSizeInPixels(cell.style);
738
- const multiLineText = cell.content.split(NEWLINE);
739
- return computeTextLinesHeight(fontSize, multiLineText.length) + 2 * PADDING_AUTORESIZE_VERTICAL;
737
+ // the number of lines should be computed from the formula result, but it's not evaluated at this point
738
+ const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
739
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
740
740
  }
741
741
  function computeTextWidth(context, text, style) {
742
742
  context.save();
@@ -1088,6 +1088,8 @@ function replaceSpecialSpaces(text) {
1088
1088
  return text;
1089
1089
  }
1090
1090
 
1091
+ const RBA_REGEX = /rgba?\(|\s+|\)/gi;
1092
+ const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/g;
1091
1093
  const colors$1 = [
1092
1094
  "#eb6d00",
1093
1095
  "#0074d9",
@@ -1113,7 +1115,10 @@ function colorNumberString(color) {
1113
1115
  }
1114
1116
  /**
1115
1117
  * Converts any CSS color value to a standardized hex6 value.
1116
- * Accepts: hex3, hex6, hex8 and rgb (rgba is not supported)
1118
+ * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
1119
+ *
1120
+ * [1] under the form rgb(r, g, b, a?) or rgba(r, g, b, a?)
1121
+ * with r,g,b ∈ [0, 255] and a ∈ [0, 1]
1117
1122
  *
1118
1123
  * toHex("#ABC")
1119
1124
  * >> "#AABBCC"
@@ -1124,27 +1129,31 @@ function colorNumberString(color) {
1124
1129
  * toHex("rgb(30, 80, 16)")
1125
1130
  * >> "#1E5010"
1126
1131
  *
1132
+ * * toHex("rgb(30, 80, 16, 0.5)")
1133
+ * >> "#1E501080"
1134
+ *
1127
1135
  */
1128
1136
  function toHex(color) {
1129
- if (color.includes("rgba")) {
1130
- throw new Error(`rgba() conversion currently not supported: ${color}`);
1131
- }
1132
- if (color.includes("rgb")) {
1133
- return rgbToHex6(color);
1137
+ let hexColor = color;
1138
+ if (color.startsWith("rgb")) {
1139
+ hexColor = rgbaStringToHex(color);
1134
1140
  }
1135
- color = color.replace("#", "").toUpperCase();
1136
- if (color.length === 3 || color.length === 4) {
1137
- color = color.split("").reduce((acc, h) => acc + h + h, "");
1141
+ else {
1142
+ hexColor = color.replace("#", "").toUpperCase();
1143
+ if (hexColor.length === 3 || hexColor.length === 4) {
1144
+ hexColor = hexColor.split("").reduce((acc, h) => acc + h + h, "");
1145
+ }
1146
+ hexColor = `#${hexColor}`;
1138
1147
  }
1139
- if (color.replace(/[a-f0-9]/gi, "") !== "") {
1140
- throw new Error("invalid color");
1148
+ if (!hexColor.match(HEX_MATCH)) {
1149
+ throw new Error(`invalid color input: ${color}`);
1141
1150
  }
1142
- return "#" + color;
1151
+ return hexColor;
1143
1152
  }
1144
1153
  function isColorValid(color) {
1145
1154
  try {
1146
- const { r, g, b, a } = colorToRGBA(color);
1147
- return (isColorValueValid(r) && isColorValueValid(g) && isColorValueValid(b) && isColorValueValid(a));
1155
+ toHex(color);
1156
+ return true;
1148
1157
  }
1149
1158
  catch (error) {
1150
1159
  return false;
@@ -1177,15 +1186,29 @@ function relativeLuminance(color) {
1177
1186
  /**
1178
1187
  * Convert a CSS rgb color string to a standardized hex6 color value.
1179
1188
  *
1180
- * rgbToHex6("rgb(30, 80, 16)")
1189
+ * rgbaStringToHex("rgb(30, 80, 16)")
1181
1190
  * >> "#1E5010"
1191
+ *
1192
+ * rgbaStringToHex("rgba(30, 80, 16, 0.5)")
1193
+ * >> "#1E501080"
1194
+ *
1195
+ * DOES NOT SUPPORT NON INTEGER RGB VALUES
1182
1196
  */
1183
- function rgbToHex6(color) {
1184
- return ("#" +
1185
- concat(color
1186
- .slice(4, -1)
1187
- .split(",")
1188
- .map((valueString) => parseInt(valueString, 10).toString(16).padStart(2, "0"))).toUpperCase());
1197
+ function rgbaStringToHex(color) {
1198
+ const stringVals = color.replace(RBA_REGEX, "").split(",");
1199
+ let alphaHex = 255;
1200
+ if (stringVals.length !== 3 && stringVals.length !== 4) {
1201
+ throw new Error("invalid color");
1202
+ }
1203
+ else if (stringVals.length === 4) {
1204
+ const alpha = parseFloat(stringVals.pop() || "1");
1205
+ alphaHex = Math.round((alpha || 1) * 255);
1206
+ }
1207
+ const vals = stringVals.map((val) => parseInt(val, 10));
1208
+ if (alphaHex !== 255) {
1209
+ vals.push(alphaHex);
1210
+ }
1211
+ return "#" + concat(vals.map((value) => value.toString(16).padStart(2, "0"))).toUpperCase();
1189
1212
  }
1190
1213
  /**
1191
1214
  * RGBA to HEX representation (#RRGGBBAA).
@@ -1207,7 +1230,7 @@ function rgbaToHex(rgba) {
1207
1230
  a = "0" + a;
1208
1231
  if (a === "ff")
1209
1232
  a = "";
1210
- return "#" + r + g + b + a;
1233
+ return ("#" + r + g + b + a).toUpperCase();
1211
1234
  }
1212
1235
  /**
1213
1236
  * Color string to RGBA representation
@@ -1328,6 +1351,9 @@ function rgbaToHSLA(rgba) {
1328
1351
  s = +(s * 100).toFixed(1);
1329
1352
  l = +(l * 100).toFixed(1);
1330
1353
  return { a: rgba.a, h, s, l };
1354
+ }
1355
+ function isSameColor(color1, color2) {
1356
+ return isColorValid(color1) && isColorValid(color2) && toHex(color1) === toHex(color2);
1331
1357
  }
1332
1358
 
1333
1359
  /** Reference of a cell (eg. A1, $B$5) */
@@ -1479,7 +1505,7 @@ function scrollDelay(value) {
1479
1505
  * formula, commas are used to separate arguments
1480
1506
  * - it does not support % symbol, in formulas % is an operator
1481
1507
  */
1482
- const formulaNumberRegexp = /^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+/;
1508
+ const formulaNumberRegexp = /(^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+)(?!\w|!)/;
1483
1509
  const pIntegerAndDecimals = "(\\d+(,\\d{3,})*(\\.\\d*)?)"; // pattern that match integer number with or without decimal digits
1484
1510
  const pOnlyDecimals = "(\\.\\d+)"; // pattern that match only expression with decimal digits
1485
1511
  const pScientificFormat = "(e(\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -3088,6 +3114,7 @@ const CHART_PADDING_RATIO = 0.02;
3088
3114
  const LINE_HEIGHT = 1.2;
3089
3115
  css /* scss */ `
3090
3116
  div.o-scorecard {
3117
+ font-family: ${DEFAULT_FONT};
3091
3118
  user-select: none;
3092
3119
  background-color: white;
3093
3120
  display: flex;
@@ -5375,24 +5402,30 @@ class Menu extends Component {
5375
5402
  }
5376
5403
  });
5377
5404
  }
5378
- get visibleMenuItems() {
5379
- return this.props.menuItems.filter((x) => x.isVisible(this.env));
5405
+ get menuItemsAndSeparators() {
5406
+ const menuItemsAndSeparators = [];
5407
+ for (let i = 0; i < this.props.menuItems.length; i++) {
5408
+ const menuItem = this.props.menuItems[i];
5409
+ if (menuItem.isVisible(this.env)) {
5410
+ menuItemsAndSeparators.push(menuItem);
5411
+ }
5412
+ if (menuItem.separator &&
5413
+ i !== this.props.menuItems.length - 1 && // no separator at the end
5414
+ menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
5415
+ ) {
5416
+ menuItemsAndSeparators.push("separator");
5417
+ }
5418
+ }
5419
+ if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
5420
+ return [];
5421
+ }
5422
+ return menuItemsAndSeparators;
5380
5423
  }
5381
5424
  get subMenuPosition() {
5382
5425
  const position = Object.assign({}, this.subMenu.position);
5383
5426
  position.y -= this.subMenu.scrollOffset || 0;
5384
5427
  return position;
5385
5428
  }
5386
- get menuHeight() {
5387
- const menuItems = this.visibleMenuItems;
5388
- let menuItemsHeight = this.getMenuItemsHeight(menuItems);
5389
- // We don't display separator at the end of a menu
5390
- if (menuItems[menuItems.length - 1].separator) {
5391
- menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
5392
- }
5393
- const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
5394
- return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
5395
- }
5396
5429
  get popoverProps() {
5397
5430
  const isRoot = this.props.depth === 1;
5398
5431
  return {
@@ -5406,7 +5439,6 @@ class Menu extends Component {
5406
5439
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
5407
5440
  onPopoverHidden: () => this.closeSubMenu(),
5408
5441
  onPopoverMoved: () => this.closeSubMenu(),
5409
- maxHeight: this.menuHeight,
5410
5442
  };
5411
5443
  }
5412
5444
  getColor(menu) {
@@ -5422,14 +5454,6 @@ class Menu extends Component {
5422
5454
  this.closeSubMenu();
5423
5455
  this.props.onClose();
5424
5456
  }
5425
- /**
5426
- * Return the number of pixels between the top of the menu
5427
- * and the menu item at a given index.
5428
- */
5429
- subMenuVerticalPosition(menuIndex) {
5430
- const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
5431
- return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
5432
- }
5433
5457
  onExternalClick(ev) {
5434
5458
  // Don't close a root menu when clicked to open the submenus.
5435
5459
  const el = this.menuRef.el;
@@ -5439,10 +5463,6 @@ class Menu extends Component {
5439
5463
  ev.closedMenuId = this.props.menuId;
5440
5464
  this.close();
5441
5465
  }
5442
- getMenuItemsHeight(menuItems) {
5443
- const numberOfSeparators = menuItems.filter((m) => m.separator).length;
5444
- return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
5445
- }
5446
5466
  getName(menu) {
5447
5467
  return menu.name(this.env);
5448
5468
  }
@@ -5462,8 +5482,11 @@ class Menu extends Component {
5462
5482
  * If the given menu is not disabled, open it's submenu at the
5463
5483
  * correct position according to available surrounding space.
5464
5484
  */
5465
- openSubMenu(menu, menuIndex) {
5466
- const y = this.subMenuVerticalPosition(menuIndex);
5485
+ openSubMenu(menu, menuIndex, ev) {
5486
+ const parentMenuEl = ev.currentTarget;
5487
+ if (!parentMenuEl)
5488
+ return;
5489
+ const y = parentMenuEl.getBoundingClientRect().top;
5467
5490
  this.subMenu.position = {
5468
5491
  x: this.position.x + this.props.depth * MENU_WIDTH,
5469
5492
  y: y - (this.subMenu.scrollOffset || 0),
@@ -5480,20 +5503,20 @@ class Menu extends Component {
5480
5503
  this.subMenu.isOpen = false;
5481
5504
  this.subMenu.parentMenu = undefined;
5482
5505
  }
5483
- onClickMenu(menu, menuIndex) {
5506
+ onClickMenu(menu, menuIndex, ev) {
5484
5507
  if (this.isEnabled(menu)) {
5485
5508
  if (this.isRoot(menu)) {
5486
- this.openSubMenu(menu, menuIndex);
5509
+ this.openSubMenu(menu, menuIndex, ev);
5487
5510
  }
5488
5511
  else {
5489
5512
  this.activateMenu(menu);
5490
5513
  }
5491
5514
  }
5492
5515
  }
5493
- onMouseOver(menu, position) {
5516
+ onMouseOver(menu, position, ev) {
5494
5517
  if (menu.isEnabled(this.env)) {
5495
5518
  if (this.isRoot(menu)) {
5496
- this.openSubMenu(menu, position);
5519
+ this.openSubMenu(menu, position, ev);
5497
5520
  }
5498
5521
  else {
5499
5522
  this.closeSubMenu();
@@ -6070,8 +6093,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6070
6093
  const dataSets = [];
6071
6094
  for (const sheetXC of dataSetsString) {
6072
6095
  const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
6073
- const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName } = dataRange;
6074
- if (invalidSheetName) {
6096
+ const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
6097
+ if (invalidSheetName || invalidXc) {
6075
6098
  continue;
6076
6099
  }
6077
6100
  // It's a rectangle. We treat all columns (arbitrary) as different data series.
@@ -6096,14 +6119,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6096
6119
  : undefined));
6097
6120
  }
6098
6121
  }
6099
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6100
- // A single cell. If it's only the title, the dataset is not added.
6101
- if (!dataSetsHaveTitle) {
6102
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6103
- }
6104
- }
6105
6122
  else {
6106
- /* 1 row or 1 column */
6123
+ /* 1 cell, 1 row or 1 column */
6107
6124
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6108
6125
  ? {
6109
6126
  top: zone.top,
@@ -6157,6 +6174,18 @@ function toExcelDataset(getters, ds) {
6157
6174
  range: getters.getRangeString(dataRange, "forceSheetReference"),
6158
6175
  };
6159
6176
  }
6177
+ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
6178
+ if (!labelRange)
6179
+ return undefined;
6180
+ let zone = {
6181
+ ...labelRange.zone,
6182
+ };
6183
+ if (shouldRemoveFirstLabel && labelRange.zone.bottom > labelRange.zone.top) {
6184
+ zone.top = zone.top + 1;
6185
+ }
6186
+ const range = labelRange.clone({ zone });
6187
+ return getters.getRangeString(range, "forceSheetReference");
6188
+ }
6160
6189
  /**
6161
6190
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
6162
6191
  * with an executed command
@@ -6241,6 +6270,20 @@ function checkLabelRange(definition) {
6241
6270
  }
6242
6271
  return 0 /* CommandResult.Success */;
6243
6272
  }
6273
+ function shouldRemoveFirstLabel(labelRange, dataset, dataSetsHaveTitle) {
6274
+ if (!dataSetsHaveTitle)
6275
+ return false;
6276
+ if (!labelRange)
6277
+ return false;
6278
+ if (!dataset)
6279
+ return true;
6280
+ const datasetLength = getZoneArea(dataset.dataRange.zone);
6281
+ const labelLength = getZoneArea(labelRange.zone);
6282
+ if (labelLength < datasetLength) {
6283
+ return false;
6284
+ }
6285
+ return true;
6286
+ }
6244
6287
  // ---------------------------------------------------------------------------
6245
6288
  // Scorecard
6246
6289
  // ---------------------------------------------------------------------------
@@ -6610,6 +6653,7 @@ class BarChart extends AbstractChart {
6610
6653
  this.legendPosition = definition.legendPosition;
6611
6654
  this.stacked = definition.stacked;
6612
6655
  this.aggregated = definition.aggregated;
6656
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
6613
6657
  }
6614
6658
  static transformDefinition(definition, executed) {
6615
6659
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -6677,11 +6721,13 @@ class BarChart extends AbstractChart {
6677
6721
  const dataSets = this.dataSets
6678
6722
  .map((ds) => toExcelDataset(this.getters, ds))
6679
6723
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
6724
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
6680
6725
  return {
6681
6726
  ...this.getDefinition(),
6682
6727
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6683
6728
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6684
6729
  dataSets,
6730
+ labelRange,
6685
6731
  };
6686
6732
  }
6687
6733
  updateRanges(applyChange) {
@@ -6749,6 +6795,11 @@ function createBarChartRuntime(chart, getters) {
6749
6795
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
6750
6796
  let labels = labelValues.formattedValues;
6751
6797
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
6798
+ if (chart.dataSetsHaveTitle &&
6799
+ dataSetsValues[0] &&
6800
+ labels.length > dataSetsValues[0].data.length) {
6801
+ labels.shift();
6802
+ }
6752
6803
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
6753
6804
  if (chart.aggregated) {
6754
6805
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7171,6 +7222,7 @@ class LineChart extends AbstractChart {
7171
7222
  this.labelsAsText = definition.labelsAsText;
7172
7223
  this.stacked = definition.stacked;
7173
7224
  this.aggregated = definition.aggregated;
7225
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7174
7226
  }
7175
7227
  static validateChartDefinition(validator, definition) {
7176
7228
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -7238,11 +7290,13 @@ class LineChart extends AbstractChart {
7238
7290
  const dataSets = this.dataSets
7239
7291
  .map((ds) => toExcelDataset(this.getters, ds))
7240
7292
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7293
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7241
7294
  return {
7242
7295
  ...this.getDefinition(),
7243
7296
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7244
7297
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7245
7298
  dataSets,
7299
+ labelRange,
7246
7300
  };
7247
7301
  }
7248
7302
  copyForSheetId(sheetId) {
@@ -7380,6 +7434,11 @@ function createLineChartRuntime(chart, getters) {
7380
7434
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7381
7435
  let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
7382
7436
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7437
+ if (chart.dataSetsHaveTitle &&
7438
+ dataSetsValues[0] &&
7439
+ labels.length > dataSetsValues[0].data.length) {
7440
+ labels.shift();
7441
+ }
7383
7442
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7384
7443
  if (axisType === "time") {
7385
7444
  ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
@@ -7438,6 +7497,7 @@ class PieChart extends AbstractChart {
7438
7497
  this.background = definition.background;
7439
7498
  this.legendPosition = definition.legendPosition;
7440
7499
  this.aggregated = definition.aggregated;
7500
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7441
7501
  }
7442
7502
  static transformDefinition(definition, executed) {
7443
7503
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -7501,12 +7561,14 @@ class PieChart extends AbstractChart {
7501
7561
  const dataSets = this.dataSets
7502
7562
  .map((ds) => toExcelDataset(this.getters, ds))
7503
7563
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7564
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7504
7565
  return {
7505
7566
  ...this.getDefinition(),
7506
7567
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7507
7568
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7508
7569
  verticalAxisPosition: "left",
7509
7570
  dataSets,
7571
+ labelRange,
7510
7572
  };
7511
7573
  }
7512
7574
  updateRanges(applyChange) {
@@ -7552,6 +7614,11 @@ function createPieChartRuntime(chart, getters) {
7552
7614
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7553
7615
  let labels = labelValues.formattedValues;
7554
7616
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7617
+ if (chart.dataSetsHaveTitle &&
7618
+ dataSetsValues[0] &&
7619
+ labels.length > dataSetsValues[0].data.length) {
7620
+ labels.shift();
7621
+ }
7555
7622
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7556
7623
  if (chart.aggregated) {
7557
7624
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -8187,7 +8254,6 @@ function getSmartChartDefinition(zone, getters) {
8187
8254
  labelRangeXc = zoneToXc({
8188
8255
  ...zone,
8189
8256
  right: zone.left,
8190
- top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
8191
8257
  });
8192
8258
  }
8193
8259
  // Only display legend for several datasets.
@@ -15105,7 +15171,7 @@ const ADDRESS = {
15105
15171
  cellReference = rowPart + colPart;
15106
15172
  }
15107
15173
  if (sheet !== undefined) {
15108
- return `${getComposerSheetName(toString(sheet))}!${cellReference}`;
15174
+ return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
15109
15175
  }
15110
15176
  return cellReference;
15111
15177
  },
@@ -16930,6 +16996,22 @@ class LineBarPieConfigPanel extends Component {
16930
16996
  aggregated: ev.target.checked,
16931
16997
  });
16932
16998
  }
16999
+ calculateHeaderPosition() {
17000
+ if (this.isDatasetInvalid || this.isLabelInvalid) {
17001
+ return undefined;
17002
+ }
17003
+ const getters = this.env.model.getters;
17004
+ const sheetId = getters.getActiveSheetId();
17005
+ const labelRange = createRange(getters, sheetId, this.labelRange);
17006
+ const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
17007
+ if (dataSets.length) {
17008
+ return dataSets[0].dataRange.zone.top + 1;
17009
+ }
17010
+ else if (labelRange) {
17011
+ return labelRange.zone.top + 1;
17012
+ }
17013
+ return undefined;
17014
+ }
16933
17015
  }
16934
17016
  LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
16935
17017
  LineBarPieConfigPanel.components = { SelectionInput };
@@ -17177,6 +17259,9 @@ class ColorPicker extends Component {
17177
17259
  top,
17178
17260
  });
17179
17261
  }
17262
+ isSameColor(color1, color2) {
17263
+ return isSameColor(color1, color2);
17264
+ }
17180
17265
  }
17181
17266
  ColorPicker.template = "o-spreadsheet-ColorPicker";
17182
17267
  ColorPicker.defaultProps = {
@@ -19418,6 +19503,8 @@ class ContentEditableHelper {
19418
19503
  start = 0;
19419
19504
  if (end > textLength)
19420
19505
  end = textLength;
19506
+ if (start > textLength)
19507
+ start = textLength;
19421
19508
  }
19422
19509
  let startNode = this.findChildAtCharacterIndex(start);
19423
19510
  let endNode = this.findChildAtCharacterIndex(end);
@@ -19687,6 +19774,7 @@ css /* scss */ `
19687
19774
  padding-right: 2px;
19688
19775
 
19689
19776
  box-sizing: border-box;
19777
+ font-family: ${DEFAULT_FONT};
19690
19778
 
19691
19779
  caret-color: black;
19692
19780
  padding-left: 3px;
@@ -20129,7 +20217,7 @@ class Composer extends Component {
20129
20217
  const highlights = this.env.model.getters.getHighlights();
20130
20218
  const refSheet = sheetName
20131
20219
  ? this.env.model.getters.getSheetIdByName(sheetName)
20132
- : this.env.model.getters.getEditionSheet();
20220
+ : this.env.model.getters.getCurrentEditedCell().sheetId;
20133
20221
  const highlight = highlights.find((highlight) => {
20134
20222
  if (highlight.sheetId !== refSheet)
20135
20223
  return false;
@@ -20290,7 +20378,7 @@ class GridComposer extends Component {
20290
20378
  get cellReference() {
20291
20379
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20292
20380
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20293
- return `${prefixSheet ? getComposerSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20381
+ return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20294
20382
  }
20295
20383
  get cellReferenceStyle() {
20296
20384
  const { x: left, y: top } = this.rect;
@@ -20475,15 +20563,18 @@ function screenCoordinatesToInternal(getters, { x, y }) {
20475
20563
  return { x, y };
20476
20564
  }
20477
20565
 
20478
- function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { scrollX, scrollY }) {
20566
+ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { maxX, maxY }, { scrollX, scrollY }) {
20479
20567
  const minX = viewportX ? 0 : -scrollX;
20480
20568
  const minY = viewportY ? 0 : -scrollY;
20481
- let deltaX = mouseX - mouseInitialX;
20482
- const newX = Math.max(initialFigure.x + deltaX, minX);
20483
- let deltaY = mouseY - mouseInitialY;
20484
- const newY = Math.max(initialFigure.y + deltaY, minY);
20569
+ const deltaX = mouseX - mouseInitialX;
20570
+ const newX = clamp(initialFigure.x + deltaX, minX, maxX - initialFigure.width - scrollX);
20571
+ const deltaY = mouseY - mouseInitialY;
20572
+ const newY = clamp(initialFigure.y + deltaY, minY, maxY - initialFigure.height - scrollY);
20485
20573
  return { ...initialFigure, x: newX, y: newY };
20486
20574
  }
20575
+ function clamp(value, min, max) {
20576
+ return Math.min(Math.max(value, min), max);
20577
+ }
20487
20578
  function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
20488
20579
  let { x, y, width, height } = initialFigure;
20489
20580
  if (keepRatio && dirX != 0 && dirY != 0) {
@@ -20882,12 +20973,16 @@ class FiguresContainer extends Component {
20882
20973
  }
20883
20974
  const sheetId = this.env.model.getters.getActiveSheetId();
20884
20975
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
20976
+ const maxDimensions = {
20977
+ maxX: this.env.model.getters.getColDimensions(sheetId, this.env.model.getters.getNumberCols(sheetId) - 1).end,
20978
+ maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end,
20979
+ };
20885
20980
  const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
20886
20981
  const initialFig = { ...figure, x, y };
20887
20982
  const onMouseMove = (ev) => {
20888
20983
  const getters = this.env.model.getters;
20889
20984
  const currentMousePosition = { x: ev.clientX, y: ev.clientY };
20890
- const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), getters.getActiveSheetScrollInfo());
20985
+ const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), maxDimensions, getters.getActiveSheetScrollInfo());
20891
20986
  const otherFigures = this.getOtherFigures(figure.id);
20892
20987
  const internalDragged = screenFigureToInternal(getters, draggedFigure);
20893
20988
  const snapResult = snapForMove(getters, internalDragged, otherFigures);
@@ -22581,6 +22676,10 @@ class Grid extends Component {
22581
22676
  }
22582
22677
  }
22583
22678
  onInput(ev) {
22679
+ // the user meant to paste in the sheet, not open the composer with the pasted content
22680
+ if (!ev.isComposing && ev.inputType === "insertFromPaste") {
22681
+ return;
22682
+ }
22584
22683
  if (ev.data) {
22585
22684
  // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22586
22685
  // character
@@ -23429,6 +23528,70 @@ function fixXlsxUnicode(str) {
23429
23528
  });
23430
23529
  }
23431
23530
 
23531
+ const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
23532
+ /**
23533
+ * Convert excel format to o_spreadsheet format
23534
+ *
23535
+ * Excel format are defined in openXML §18.8.31
23536
+ */
23537
+ function convertXlsxFormat(numFmtId, formats, warningManager) {
23538
+ var _a, _b, _c;
23539
+ if (numFmtId === 0) {
23540
+ return undefined;
23541
+ }
23542
+ // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23543
+ let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23544
+ if (format) {
23545
+ try {
23546
+ let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23547
+ convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23548
+ convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23549
+ // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23550
+ const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23551
+ const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23552
+ if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23553
+ throw new Error("Multiple escaped blocks in format");
23554
+ }
23555
+ convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23556
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
23557
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23558
+ convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23559
+ convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
23560
+ if (isXlsxDateFormat(convertedFormat)) {
23561
+ convertedFormat = convertDateFormat$1(convertedFormat);
23562
+ }
23563
+ if (isFormatSupported(convertedFormat)) {
23564
+ return convertedFormat;
23565
+ }
23566
+ }
23567
+ catch (e) { }
23568
+ }
23569
+ warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23570
+ return undefined;
23571
+ }
23572
+ function isFormatSupported(format) {
23573
+ try {
23574
+ formatValue(0, format);
23575
+ return true;
23576
+ }
23577
+ catch (e) {
23578
+ return false;
23579
+ }
23580
+ }
23581
+ function isXlsxDateFormat(format) {
23582
+ return format.match(XLSX_DATE_FORMAT_REGEX) !== null;
23583
+ }
23584
+ function convertDateFormat$1(format) {
23585
+ // Some of these aren't defined neither in the OpenXML spec not the Xlsx extension of OpenXML,
23586
+ // but can still occur and are supported by Excel/Google sheets
23587
+ format = format.toLowerCase();
23588
+ format = format.replace(/mmmmm/g, "mmm");
23589
+ format = format.replace(/am\/pm|a\/m/g, "a");
23590
+ format = format.replace(/hhhh/g, "hh");
23591
+ format = format.replace(/\bh\b/g, "hh");
23592
+ return format;
23593
+ }
23594
+
23432
23595
  function convertBorders(data, warningManager) {
23433
23596
  const borderArray = data.borders.map((border) => {
23434
23597
  addBorderWarnings(border, warningManager);
@@ -23491,41 +23654,6 @@ function convertFormats(data, warningManager) {
23491
23654
  }
23492
23655
  return arrayToObject(formats, 1);
23493
23656
  }
23494
- /**
23495
- * Convert excel format to o_spreadsheet format
23496
- *
23497
- * Excel format are defined in openXML §18.8.31
23498
- */
23499
- function convertXlsxFormat(numFmtId, formats, warningManager) {
23500
- var _a, _b, _c;
23501
- if (numFmtId === 0) {
23502
- return undefined;
23503
- }
23504
- // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23505
- let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23506
- if (format) {
23507
- try {
23508
- let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23509
- convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23510
- convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23511
- // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23512
- const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23513
- const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23514
- if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23515
- throw new Error("Multiple escaped blocks in format");
23516
- }
23517
- convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23518
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore with of next char for align purposes. Not supported ATM
23519
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23520
- convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23521
- formatValue(0, convertedFormat);
23522
- return convertedFormat;
23523
- }
23524
- catch (e) { }
23525
- }
23526
- warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23527
- return undefined;
23528
- }
23529
23657
  // ---------------------------------------------------------------------------
23530
23658
  // Warnings
23531
23659
  // ---------------------------------------------------------------------------
@@ -24080,16 +24208,18 @@ function convertFigure(figure, id, sheetData) {
24080
24208
  };
24081
24209
  }
24082
24210
  function convertChartData(chartData) {
24083
- var _a;
24084
- const labelRange = (_a = chartData.dataSets[0].label) === null || _a === void 0 ? void 0 : _a.replace(/\$/g, "");
24085
- let dataSets = chartData.dataSets.map((data) => data.range.replace(/\$/g, ""));
24211
+ const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
24212
+ const labelRange = chartData.labelRange
24213
+ ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
24214
+ : undefined;
24215
+ let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
24086
24216
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
24087
24217
  if (chartData.type === "pie") {
24088
24218
  dataSets.reverse();
24089
24219
  }
24090
24220
  return {
24091
24221
  dataSets,
24092
- dataSetsHaveTitle: false,
24222
+ dataSetsHaveTitle,
24093
24223
  labelRange,
24094
24224
  title: chartData.title || "",
24095
24225
  type: chartData.type,
@@ -24100,6 +24230,28 @@ function convertChartData(chartData) {
24100
24230
  aggregated: false,
24101
24231
  labelsAsText: false,
24102
24232
  };
24233
+ }
24234
+ function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
24235
+ let { sheetName, xc } = splitReference(range);
24236
+ if (sheetName) {
24237
+ sheetName = getCanonicalSheetName(sheetName) + "!";
24238
+ }
24239
+ else {
24240
+ sheetName = "";
24241
+ }
24242
+ let zone = toUnboundedZone(xc);
24243
+ if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
24244
+ const height = zone.bottom - zone.top + 1;
24245
+ const width = zone.right - zone.left + 1;
24246
+ if (height === 1) {
24247
+ zone = { ...zone, left: zone.left - 1 };
24248
+ }
24249
+ else if (width === 1) {
24250
+ zone = { ...zone, top: zone.top - 1 };
24251
+ }
24252
+ }
24253
+ const dataXC = zoneToXc(zone);
24254
+ return sheetName + dataXC;
24103
24255
  }
24104
24256
 
24105
24257
  /**
@@ -25167,6 +25319,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
25167
25319
  title: chartTitle,
25168
25320
  type: CHART_TYPE_CONVERSION_MAP[chartType],
25169
25321
  dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
25322
+ labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
25170
25323
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
25171
25324
  default: "ffffff",
25172
25325
  }).asString(),
@@ -25186,7 +25339,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
25186
25339
  extractChartDatasets(chartElement) {
25187
25340
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
25188
25341
  return {
25189
- label: this.extractChildTextContent(chartDataElement, "c:cat c:f"),
25342
+ label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
25190
25343
  range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
25191
25344
  };
25192
25345
  });
@@ -29103,6 +29256,39 @@ class FigurePlugin extends CorePlugin {
29103
29256
  case "DELETE_FIGURE":
29104
29257
  this.removeFigure(cmd.id, cmd.sheetId);
29105
29258
  break;
29259
+ case "REMOVE_COLUMNS_ROWS":
29260
+ this.onRowColDelete(cmd.sheetId, cmd.dimension);
29261
+ }
29262
+ }
29263
+ onRowColDelete(sheetId, dimension) {
29264
+ dimension === "ROW" ? this.onRowDeletion(sheetId) : this.onColDeletion(sheetId);
29265
+ }
29266
+ onRowDeletion(sheetId) {
29267
+ const numHeader = this.getters.getNumberRows(sheetId);
29268
+ let gridHeight = 0;
29269
+ for (let i = 0; i < numHeader; i++) {
29270
+ gridHeight += this.getters.getRowSize(sheetId, i);
29271
+ }
29272
+ const figures = this.getters.getFigures(sheetId);
29273
+ for (const figure of figures) {
29274
+ const newY = Math.min(figure.y, gridHeight - figure.height);
29275
+ if (newY !== figure.y) {
29276
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, y: newY });
29277
+ }
29278
+ }
29279
+ }
29280
+ onColDeletion(sheetId) {
29281
+ const numHeader = this.getters.getNumberCols(sheetId);
29282
+ let gridWidth = 0;
29283
+ for (let i = 0; i < numHeader; i++) {
29284
+ gridWidth += this.getters.getColSize(sheetId, i);
29285
+ }
29286
+ const figures = this.getters.getFigures(sheetId);
29287
+ for (const figure of figures) {
29288
+ const newX = Math.min(figure.x, gridWidth - figure.width);
29289
+ if (newX !== figure.x) {
29290
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x: newX });
29291
+ }
29106
29292
  }
29107
29293
  }
29108
29294
  updateFigure(sheetId, figure) {
@@ -30773,7 +30959,7 @@ class RangeAdapter {
30773
30959
  sheetName = rangeImpl.invalidSheetName;
30774
30960
  }
30775
30961
  else {
30776
- sheetName = getComposerSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30962
+ sheetName = getCanonicalSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30777
30963
  }
30778
30964
  }
30779
30965
  if (prefixSheet && !sheetName) {
@@ -31824,9 +32010,7 @@ class CustomColorsPlugin extends UIPlugin {
31824
32010
  // remove duplicates first to check validity on a reduced
31825
32011
  // set of colors, then normalize to HEX and remove duplicates
31826
32012
  // again
31827
- [...new Set([...usedColors, ...this.customColors])]
31828
- .filter(isColorValid)
31829
- .map((c) => toHex(c).toLowerCase())),
32013
+ [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
31830
32014
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
31831
32015
  }
31832
32016
  getColorsFromCells(cells) {
@@ -31887,7 +32071,7 @@ class CustomColorsPlugin extends UIPlugin {
31887
32071
  return [...chartsColors];
31888
32072
  }
31889
32073
  tryToAddColor(color) {
31890
- const formattedColor = toHex(color).toLowerCase();
32074
+ const formattedColor = toHex(color);
31891
32075
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
31892
32076
  this.customColors.add(formattedColor);
31893
32077
  }
@@ -32902,8 +33086,8 @@ class InternalViewport {
32902
33086
  this.getters = getters;
32903
33087
  this.sheetId = sheetId;
32904
33088
  this.boundaries = boundaries;
32905
- this.width = sizeInGrid.width;
32906
- this.height = sizeInGrid.height;
33089
+ this.viewportWidth = sizeInGrid.width;
33090
+ this.viewportHeight = sizeInGrid.height;
32907
33091
  this.offsetScrollbarX = offsets.x;
32908
33092
  this.offsetScrollbarY = offsets.y;
32909
33093
  this.canScrollVertically = options.canScrollVertically;
@@ -32914,6 +33098,10 @@ class InternalViewport {
32914
33098
  this.adjustViewportOffsetY();
32915
33099
  }
32916
33100
  // PUBLIC
33101
+ /** Returns the maximum size (in Pixels) of the viewport relative to its allocated client size
33102
+ * When the viewport grid size is smaller than its client width (resp. height), it will return
33103
+ * the client width (resp. height).
33104
+ */
32917
33105
  getMaxSize() {
32918
33106
  const lastCol = this.getters.findLastVisibleColRowIndex(this.sheetId, "COL", {
32919
33107
  first: this.boundaries.left,
@@ -32925,20 +33113,24 @@ class InternalViewport {
32925
33113
  });
32926
33114
  const { end: lastColEnd, size: lastColSize } = this.getters.getColDimensions(this.sheetId, lastCol);
32927
33115
  const { end: lastRowEnd, size: lastRowSize } = this.getters.getRowDimensions(this.sheetId, lastRow);
32928
- const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.width, 0);
33116
+ const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.viewportWidth, 0);
32929
33117
  const leftColSize = this.getters.getColSize(this.sheetId, leftColIndex);
32930
- const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.height, 0);
33118
+ const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.viewportHeight, 0);
32931
33119
  const topRowSize = this.getters.getRowSize(this.sheetId, leftRowIndex);
32932
- const width = lastColEnd -
32933
- this.offsetCorrectionX +
32934
- (this.canScrollHorizontally
32935
- ? Math.max(DEFAULT_CELL_WIDTH, Math.min(leftColSize, this.width - lastColSize))
32936
- : 0);
32937
- const height = lastRowEnd -
32938
- this.offsetCorrectionY +
32939
- (this.canScrollVertically
32940
- ? Math.max(DEFAULT_CELL_HEIGHT + 5, Math.min(topRowSize, this.height - lastRowSize))
32941
- : 0);
33120
+ let width = lastColEnd - this.offsetCorrectionX;
33121
+ if (this.canScrollHorizontally) {
33122
+ width += Math.max(DEFAULT_CELL_WIDTH, // leave some minimal space to let the user know they scrolled all the way
33123
+ Math.min(leftColSize, this.viewportWidth - lastColSize) // Add pixels that allows the snapping at maximum horizontal scroll
33124
+ );
33125
+ width = Math.max(width, this.viewportWidth); // if the viewport grid size is smaller than its client width, return client width
33126
+ }
33127
+ let height = lastRowEnd - this.offsetCorrectionY;
33128
+ if (this.canScrollVertically) {
33129
+ height += Math.max(DEFAULT_CELL_HEIGHT + 5, // leave some space to let the user know they scrolled all the way
33130
+ Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
33131
+ );
33132
+ height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
33133
+ }
32942
33134
  return { width, height };
32943
33135
  }
32944
33136
  /**
@@ -32947,7 +33139,7 @@ class InternalViewport {
32947
33139
  * It returns -1 if no column is found.
32948
33140
  */
32949
33141
  getColIndex(x, absolute = false) {
32950
- if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.width) {
33142
+ if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.viewportWidth) {
32951
33143
  return -1;
32952
33144
  }
32953
33145
  return this.searchHeaderIndex("COL", x - this.offsetCorrectionX, this.left, absolute);
@@ -32958,7 +33150,7 @@ class InternalViewport {
32958
33150
  * It returns -1 if no row is found.
32959
33151
  */
32960
33152
  getRowIndex(y, absolute = false) {
32961
- if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.height) {
33153
+ if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.viewportHeight) {
32962
33154
  return -1;
32963
33155
  }
32964
33156
  return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY, this.top, absolute);
@@ -32985,7 +33177,7 @@ class InternalViewport {
32985
33177
  adjustPositionX(col) {
32986
33178
  const sheetId = this.sheetId;
32987
33179
  const { start, end } = this.getters.getColDimensions(sheetId, col);
32988
- while (end > this.offsetX + this.offsetCorrectionX + this.width &&
33180
+ while (end > this.offsetX + this.offsetCorrectionX + this.viewportWidth &&
32989
33181
  this.offsetX + this.offsetCorrectionX < start) {
32990
33182
  this.offsetX = this.getters.getColDimensions(sheetId, this.left).end - this.offsetCorrectionX;
32991
33183
  this.offsetScrollbarX = this.offsetX;
@@ -33007,7 +33199,7 @@ class InternalViewport {
33007
33199
  adjustPositionY(row) {
33008
33200
  const sheetId = this.sheetId;
33009
33201
  while (this.getters.getRowDimensions(sheetId, row).end >
33010
- this.offsetY + this.height + this.offsetCorrectionY &&
33202
+ this.offsetY + this.viewportHeight + this.offsetCorrectionY &&
33011
33203
  this.offsetY + this.offsetCorrectionY < this.getters.getRowDimensions(sheetId, row).start) {
33012
33204
  this.offsetY = this.getters.getRowDimensions(sheetId, this.top).end - this.offsetCorrectionY;
33013
33205
  this.offsetScrollbarY = this.offsetY;
@@ -33045,8 +33237,8 @@ class InternalViewport {
33045
33237
  const x = this.getters.getColRowOffset("COL", this.zone.left, targetZone.left) +
33046
33238
  this.offsetCorrectionX;
33047
33239
  const y = this.getters.getColRowOffset("ROW", this.zone.top, targetZone.top) + this.offsetCorrectionY;
33048
- const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.width);
33049
- const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.height);
33240
+ const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
33241
+ const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
33050
33242
  return {
33051
33243
  x,
33052
33244
  y,
@@ -33109,12 +33301,12 @@ class InternalViewport {
33109
33301
  adjustViewportOffsetX() {
33110
33302
  if (this.canScrollHorizontally) {
33111
33303
  const { width: viewportWidth } = this.getMaxSize();
33112
- if (this.width + this.offsetScrollbarX > viewportWidth) {
33113
- this.offsetScrollbarX = Math.max(0, viewportWidth - this.width);
33304
+ if (this.viewportWidth + this.offsetScrollbarX > viewportWidth) {
33305
+ this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
33114
33306
  }
33115
33307
  }
33116
33308
  this.left = this.getColIndex(this.offsetScrollbarX, true);
33117
- this.right = this.getColIndex(this.offsetScrollbarX + this.width, true);
33309
+ this.right = this.getColIndex(this.offsetScrollbarX + this.viewportWidth, true);
33118
33310
  if (this.right === -1) {
33119
33311
  this.right = this.boundaries.right;
33120
33312
  }
@@ -33126,12 +33318,12 @@ class InternalViewport {
33126
33318
  adjustViewportOffsetY() {
33127
33319
  if (this.canScrollVertically) {
33128
33320
  const { height: paneHeight } = this.getMaxSize();
33129
- if (this.height + this.offsetScrollbarY > paneHeight) {
33130
- this.offsetScrollbarY = Math.max(0, paneHeight - this.height);
33321
+ if (this.viewportHeight + this.offsetScrollbarY > paneHeight) {
33322
+ this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
33131
33323
  }
33132
33324
  }
33133
33325
  this.top = this.getRowIndex(this.offsetScrollbarY, true);
33134
- this.bottom = this.getRowIndex(this.offsetScrollbarY + this.width, true);
33326
+ this.bottom = this.getRowIndex(this.offsetScrollbarY + this.viewportWidth, true);
33135
33327
  if (this.bottom === -1) {
33136
33328
  this.bottom = this.boundaries.bottom;
33137
33329
  }
@@ -33142,7 +33334,7 @@ class InternalViewport {
33142
33334
  adjustViewportZoneX() {
33143
33335
  const sheetId = this.sheetId;
33144
33336
  this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
33145
- this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.width, this.left));
33337
+ this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
33146
33338
  if (this.right === -1) {
33147
33339
  this.right = this.getters.getNumberCols(sheetId) - 1;
33148
33340
  }
@@ -33155,7 +33347,7 @@ class InternalViewport {
33155
33347
  adjustViewportZoneY() {
33156
33348
  const sheetId = this.sheetId;
33157
33349
  this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
33158
- this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.height, this.top));
33350
+ this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
33159
33351
  if (this.bottom === -1) {
33160
33352
  this.bottom = this.getters.getNumberRows(sheetId) - 1;
33161
33353
  }
@@ -33418,8 +33610,7 @@ class SheetViewPlugin extends UIPlugin {
33418
33610
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
33419
33611
  }
33420
33612
  /**
33421
- * Return the main viewport maximum size. That is the zone dimension
33422
- * with some bottom and right padding.
33613
+ * Return the main viewport maximum size relative to the client size.
33423
33614
  */
33424
33615
  getMainViewportRect() {
33425
33616
  const sheetId = this.getters.getActiveSheetId();
@@ -33435,8 +33626,8 @@ class SheetViewPlugin extends UIPlugin {
33435
33626
  const { width, height } = this.getMainViewportRect();
33436
33627
  const viewport = this.getMainInternalViewport(sheetId);
33437
33628
  return {
33438
- maxOffsetX: Math.max(0, width - viewport.width + 1),
33439
- maxOffsetY: Math.max(0, height - viewport.height + 1),
33629
+ maxOffsetX: Math.max(0, width - viewport.viewportWidth + 1),
33630
+ maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
33440
33631
  };
33441
33632
  }
33442
33633
  getColRowOffsetInViewport(dimension, referenceIndex, index) {
@@ -35656,7 +35847,7 @@ class SelectionInputPlugin extends UIPlugin {
35656
35847
  const inputSheetId = this.activeSheet;
35657
35848
  const sheetId = this.getters.getActiveSheetId();
35658
35849
  const sheetName = this.getters.getSheetName(sheetId);
35659
- this.add([sheetId === inputSheetId ? xc : `${getComposerSheetName(sheetName)}!${xc}`]);
35850
+ this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
35660
35851
  }
35661
35852
  handle(cmd) {
35662
35853
  switch (cmd.type) {
@@ -37903,13 +38094,19 @@ class ClipboardFigureState {
37903
38094
  */
37904
38095
  paste(target) {
37905
38096
  const sheetId = this.getters.getActiveSheetId();
38097
+ const { width, height } = this.copiedFigure;
38098
+ const numCols = this.getters.getNumberCols(sheetId);
38099
+ const numRows = this.getters.getNumberRows(sheetId);
38100
+ const targetX = this.getters.getColDimensions(sheetId, target[0].left).start;
38101
+ const targetY = this.getters.getRowDimensions(sheetId, target[0].top).start;
38102
+ const maxX = this.getters.getColDimensions(sheetId, numCols - 1).end;
38103
+ const maxY = this.getters.getRowDimensions(sheetId, numRows - 1).end;
37906
38104
  const position = {
37907
- x: this.getters.getColDimensions(sheetId, target[0].left).start,
37908
- y: this.getters.getRowDimensions(sheetId, target[0].top).start,
38105
+ x: maxX < width ? 0 : Math.min(targetX, maxX - width),
38106
+ y: maxY < height ? 0 : Math.min(targetY, maxY - height),
37909
38107
  };
37910
- const size = { height: this.copiedFigure.height, width: this.copiedFigure.width };
37911
38108
  const newId = new UuidGenerator().uuidv4();
37912
- this.copiedFigureContent.paste(sheetId, newId, position, size);
38109
+ this.copiedFigureContent.paste(sheetId, newId, position, { height, width });
37913
38110
  if (this.operation === "CUT") {
37914
38111
  this.dispatch("DELETE_FIGURE", {
37915
38112
  sheetId: this.copiedFigureContent.sheetId,
@@ -38275,7 +38472,7 @@ function loopThroughReferenceType(token) {
38275
38472
  return token;
38276
38473
  const { xc, sheetName } = splitReference(token.value);
38277
38474
  const [left, right] = xc.split(":");
38278
- const sheetRef = sheetName ? `${getComposerSheetName(sheetName)}!` : "";
38475
+ const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
38279
38476
  const updatedLeft = getTokenNextReferenceType(left);
38280
38477
  const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
38281
38478
  return { ...token, value: sheetRef + updatedLeft + updatedRight };
@@ -38534,9 +38731,6 @@ class EditionPlugin extends UIPlugin {
38534
38731
  }
38535
38732
  return this.currentContent;
38536
38733
  }
38537
- getEditionSheet() {
38538
- return this.sheetId;
38539
- }
38540
38734
  getComposerSelection() {
38541
38735
  return {
38542
38736
  start: this.selectionStart,
@@ -38820,8 +39014,8 @@ class EditionPlugin extends UIPlugin {
38820
39014
  getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
38821
39015
  const sheetId = this.getters.getActiveSheetId();
38822
39016
  let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
38823
- if (this.getters.getEditionSheet() !== this.getters.getActiveSheetId()) {
38824
- const sheetName = getComposerSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
39017
+ if (this.getters.getCurrentEditedCell().sheetId !== this.getters.getActiveSheetId()) {
39018
+ const sheetName = getCanonicalSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
38825
39019
  selectedXc = `${sheetName}!${selectedXc}`;
38826
39020
  }
38827
39021
  return selectedXc;
@@ -38835,7 +39029,7 @@ class EditionPlugin extends UIPlugin {
38835
39029
  _fixedParts.pop();
38836
39030
  }
38837
39031
  const newRange = range.clone({ parts: _fixedParts });
38838
- return this.getters.getSelectionRangeString(newRange, this.getters.getEditionSheet());
39032
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
38839
39033
  }
38840
39034
  /**
38841
39035
  * Replace the current selection by a new text.
@@ -38868,7 +39062,7 @@ class EditionPlugin extends UIPlugin {
38868
39062
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38869
39063
  return;
38870
39064
  }
38871
- const editionSheetId = this.getters.getEditionSheet();
39065
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38872
39066
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
38873
39067
  const colorsToKeep = {};
38874
39068
  for (const xc of XCs) {
@@ -38897,7 +39091,7 @@ class EditionPlugin extends UIPlugin {
38897
39091
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38898
39092
  return [];
38899
39093
  }
38900
- const editionSheetId = this.getters.getEditionSheet();
39094
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38901
39095
  const rangeColor = (rangeString) => {
38902
39096
  const colorIndex = this.colorIndexByRange[rangeString];
38903
39097
  return colors$1[colorIndex % colors$1.length];
@@ -38915,7 +39109,7 @@ class EditionPlugin extends UIPlugin {
38915
39109
  * Return ranges currently referenced in the composer
38916
39110
  */
38917
39111
  getReferencedRanges() {
38918
- const editionSheetId = this.getters.getEditionSheet();
39112
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38919
39113
  return this.currentTokens
38920
39114
  .filter((token) => token.type === "REFERENCE")
38921
39115
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -38974,7 +39168,6 @@ EditionPlugin.getters = [
38974
39168
  "isSelectingForComposer",
38975
39169
  "showSelectionIndicator",
38976
39170
  "getCurrentContent",
38977
- "getEditionSheet",
38978
39171
  "getComposerSelection",
38979
39172
  "getCurrentTokens",
38980
39173
  "getTokenAtCursor",
@@ -40297,10 +40490,6 @@ css /* scss */ `
40297
40490
  .o-bottom-bar-arrows {
40298
40491
  .o-bottom-bar-arrow {
40299
40492
  cursor: pointer;
40300
- &.o-disabled {
40301
- opacity: 0.4;
40302
- cursor: default;
40303
- }
40304
40493
  &:hover:not([class*="o-disabled"]) {
40305
40494
  .o-icon {
40306
40495
  opacity: 0.9;
@@ -41138,11 +41327,6 @@ css /* scss */ `
41138
41327
  margin: 0 6px;
41139
41328
  }
41140
41329
 
41141
- .o-disabled {
41142
- opacity: 0.6;
41143
- cursor: default;
41144
- }
41145
-
41146
41330
  .o-dropdown {
41147
41331
  position: relative;
41148
41332
  display: flex;
@@ -41517,10 +41701,12 @@ css /* scss */ `
41517
41701
  button {
41518
41702
  color: #333;
41519
41703
  }
41520
-
41521
- * {
41522
- font-family: "Roboto", "RobotoDraft", Helvetica, Arial, sans-serif;
41704
+ .o-disabled {
41705
+ opacity: 0.4;
41706
+ pointer: default;
41707
+ pointer-events: none;
41523
41708
  }
41709
+
41524
41710
  &,
41525
41711
  *,
41526
41712
  *:before,
@@ -44889,6 +45075,13 @@ class Model extends EventBus {
44889
45075
  * Internal status of the model. Important for command handling coordination
44890
45076
  */
44891
45077
  this.status = 0 /* Status.Ready */;
45078
+ /**
45079
+ * Check if a command can be dispatched, and returns a DispatchResult object with the possible
45080
+ * reasons the dispatch failed.
45081
+ */
45082
+ this.canDispatch = (type, payload) => {
45083
+ return this.checkDispatchAllowed({ ...payload, type });
45084
+ };
44892
45085
  /**
44893
45086
  * The dispatch method is the only entry point to manipulate data in the model.
44894
45087
  * This is through this method that commands are dispatched most of the time
@@ -45032,7 +45225,10 @@ class Model extends EventBus {
45032
45225
  markRaw(this);
45033
45226
  }
45034
45227
  get handlers() {
45035
- return [this.range, ...this.corePlugins, ...this.allUIPlugins, this.history];
45228
+ return [...this.coreHandlers, ...this.allUIPlugins, this.history];
45229
+ }
45230
+ get coreHandlers() {
45231
+ return [this.range, ...this.corePlugins];
45036
45232
  }
45037
45233
  get allUIPlugins() {
45038
45234
  return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
@@ -45160,6 +45356,16 @@ class Model extends EventBus {
45160
45356
  * Check if the given command is allowed by all the plugins and the history.
45161
45357
  */
45162
45358
  checkDispatchAllowed(command) {
45359
+ if (isCoreCommand(command)) {
45360
+ return this.checkDispatchAllowedCoreCommand(command);
45361
+ }
45362
+ return this.checkDispatchAllowedLocalCommand(command);
45363
+ }
45364
+ checkDispatchAllowedCoreCommand(command) {
45365
+ const results = this.coreHandlers.map((handler) => handler.allowDispatch(command));
45366
+ return new DispatchResult(results.flat());
45367
+ }
45368
+ checkDispatchAllowedLocalCommand(command) {
45163
45369
  const results = this.handlers.map((handler) => handler.allowDispatch(command));
45164
45370
  return new DispatchResult(results.flat());
45165
45371
  }
@@ -45360,6 +45566,6 @@ function addFunction(functionName, functionDescription) {
45360
45566
  export { AbstractChart, CommandResult, CorePlugin, DATETIME_FORMAT, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setTranslationMethod, tokenize };
45361
45567
 
45362
45568
 
45363
- __info__.version = '16.3.0-alpha.2';
45364
- __info__.date = '2023-03-23T11:36:58.645Z';
45365
- __info__.hash = '59f25bb';
45569
+ __info__.version = '16.3.0-alpha.3';
45570
+ __info__.date = '2023-04-13T08:30:41.165Z';
45571
+ __info__.hash = '573e679';