@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.
@@ -117,88 +117,88 @@ const FILTERS_COLOR = "#188038";
117
117
  const BACKGROUND_HEADER_FILTER_COLOR = "#E6F4EA";
118
118
  const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = "#CEEAD6";
119
119
  const SEPARATOR_COLOR = "#E0E2E4";
120
- // Color picker
120
+ // Color picker defaults as upper case HEX to match `toHex`helper
121
121
  const COLOR_PICKER_DEFAULTS = [
122
122
  "#000000",
123
123
  "#434343",
124
124
  "#666666",
125
125
  "#999999",
126
- "#b7b7b7",
127
- "#cccccc",
128
- "#d9d9d9",
129
- "#efefef",
130
- "#f3f3f3",
131
- "#ffffff",
126
+ "#B7B7B7",
127
+ "#CCCCCC",
128
+ "#D9D9D9",
129
+ "#EFEFEF",
130
+ "#F3F3F3",
131
+ "#FFFFFF",
132
132
  "#980000",
133
- "#ff0000",
134
- "#ff9900",
135
- "#ffff00",
136
- "#00ff00",
137
- "#00ffff",
138
- "#4a86e8",
139
- "#0000ff",
140
- "#9900ff",
141
- "#ff00ff",
142
- "#e6b8af",
143
- "#f4cccc",
144
- "#fce5cd",
145
- "#fff2cc",
146
- "#d9ead3",
147
- "#d0e0e3",
148
- "#c9daf8",
149
- "#cfe2f3",
150
- "#d9d2e9",
151
- "#ead1dc",
152
- "#dd7e6b",
153
- "#ea9999",
154
- "#f9cb9c",
155
- "#ffe599",
156
- "#b6d7a8",
157
- "#a2c4c9",
158
- "#a4c2f4",
159
- "#9fc5e8",
160
- "#b4a7d6",
161
- "#d5a6bd",
162
- "#cc4125",
163
- "#e06666",
164
- "#f6b26b",
165
- "#ffd966",
166
- "#93c47d",
167
- "#76a5af",
168
- "#6d9eeb",
169
- "#6fa8dc",
170
- "#8e7cc3",
171
- "#c27ba0",
172
- "#a61c00",
173
- "#cc0000",
174
- "#e69138",
175
- "#f1c232",
176
- "#6aa84f",
177
- "#45818e",
178
- "#3c78d8",
179
- "#3d85c6",
180
- "#674ea7",
181
- "#a64d79",
182
- "#85200c",
133
+ "#FF0000",
134
+ "#FF9900",
135
+ "#FFFF00",
136
+ "#00FF00",
137
+ "#00FFFF",
138
+ "#4A86E8",
139
+ "#0000FF",
140
+ "#9900FF",
141
+ "#FF00FF",
142
+ "#E6B8AF",
143
+ "#F4CCCC",
144
+ "#FCE5CD",
145
+ "#FFF2CC",
146
+ "#D9EAD3",
147
+ "#D0E0E3",
148
+ "#C9DAF8",
149
+ "#CFE2F3",
150
+ "#D9D2E9",
151
+ "#EAD1DC",
152
+ "#DD7E6B",
153
+ "#EA9999",
154
+ "#F9CB9C",
155
+ "#FFE599",
156
+ "#B6D7A8",
157
+ "#A2C4C9",
158
+ "#A4C2F4",
159
+ "#9FC5E8",
160
+ "#B4A7D6",
161
+ "#D5A6BD",
162
+ "#CC4125",
163
+ "#E06666",
164
+ "#F6B26B",
165
+ "#FFD966",
166
+ "#93C47D",
167
+ "#76A5AF",
168
+ "#6D9EEB",
169
+ "#6FA8DC",
170
+ "#8E7CC3",
171
+ "#C27BA0",
172
+ "#A61C00",
173
+ "#CC0000",
174
+ "#E69138",
175
+ "#F1C232",
176
+ "#6AA84F",
177
+ "#45818E",
178
+ "#3C78D8",
179
+ "#3D85C6",
180
+ "#674EA7",
181
+ "#A64D79",
182
+ "#85200C",
183
183
  "#990000",
184
- "#b45f06",
185
- "#bf9000",
186
- "#38761d",
187
- "#134f5c",
188
- "#1155cc",
189
- "#0b5394",
190
- "#351c75",
191
- "#741b47",
192
- "#5b0f00",
184
+ "#B45F06",
185
+ "#BF9000",
186
+ "#38761D",
187
+ "#134F5C",
188
+ "#1155CC",
189
+ "#0B5394",
190
+ "#351C75",
191
+ "#741B47",
192
+ "#5B0F00",
193
193
  "#660000",
194
- "#783f04",
195
- "#7f6000",
196
- "#274e13",
197
- "#0c343d",
198
- "#1c4587",
194
+ "#783F04",
195
+ "#7F6000",
196
+ "#274E13",
197
+ "#0C343D",
198
+ "#1C4587",
199
199
  "#073763",
200
- "#20124d",
201
- "#4c1130",
200
+ "#20124D",
201
+ "#4C1130",
202
202
  ];
203
203
  // Dimensions
204
204
  const MIN_ROW_HEIGHT = 10;
@@ -227,7 +227,6 @@ const MENU_VERTICAL_PADDING = 8;
227
227
  const MENU_ITEM_HEIGHT = 28;
228
228
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
229
229
  const MENU_SEPARATOR_PADDING = 5;
230
- const MENU_SEPARATOR_HEIGHT = MENU_SEPARATOR_BORDER_WIDTH + 2 * MENU_SEPARATOR_PADDING;
231
230
  // Fonts
232
231
  const DEFAULT_FONT_WEIGHT = "400";
233
232
  const DEFAULT_FONT_SIZE = 10;
@@ -237,7 +236,7 @@ const DEFAULT_FONT = "'Roboto', arial";
237
236
  const DEFAULT_BORDER_DESC = ["thin", "#000"];
238
237
  const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
239
238
  // DateTimeRegex
240
- const DATETIME_FORMAT = /[ymd:]/;
239
+ const DATETIME_FORMAT = /[ymdhs:]/;
241
240
  // Ranges
242
241
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
243
242
  // Max Number of history steps kept in memory
@@ -718,7 +717,7 @@ function getUnquotedSheetName(sheetName) {
718
717
  * '\w' captures [0-9][a-z][A-Z] and _.
719
718
  * @param sheetName Name of the sheet
720
719
  */
721
- function getComposerSheetName(sheetName) {
720
+ function getCanonicalSheetName(sheetName) {
722
721
  var _a;
723
722
  if (((_a = sheetName.match(/\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {
724
723
  sheetName = `'${sheetName}'`;
@@ -739,8 +738,9 @@ function getDefaultCellHeight(cell) {
739
738
  return DEFAULT_CELL_HEIGHT;
740
739
  }
741
740
  const fontSize = computeTextFontSizeInPixels(cell.style);
742
- const multiLineText = cell.content.split(NEWLINE);
743
- return computeTextLinesHeight(fontSize, multiLineText.length) + 2 * PADDING_AUTORESIZE_VERTICAL;
741
+ // the number of lines should be computed from the formula result, but it's not evaluated at this point
742
+ const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
743
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
744
744
  }
745
745
  function computeTextWidth(context, text, style) {
746
746
  context.save();
@@ -1092,6 +1092,8 @@ function replaceSpecialSpaces(text) {
1092
1092
  return text;
1093
1093
  }
1094
1094
 
1095
+ const RBA_REGEX = /rgba?\(|\s+|\)/gi;
1096
+ const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/g;
1095
1097
  const colors$1 = [
1096
1098
  "#eb6d00",
1097
1099
  "#0074d9",
@@ -1117,7 +1119,10 @@ function colorNumberString(color) {
1117
1119
  }
1118
1120
  /**
1119
1121
  * Converts any CSS color value to a standardized hex6 value.
1120
- * Accepts: hex3, hex6, hex8 and rgb (rgba is not supported)
1122
+ * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
1123
+ *
1124
+ * [1] under the form rgb(r, g, b, a?) or rgba(r, g, b, a?)
1125
+ * with r,g,b ∈ [0, 255] and a ∈ [0, 1]
1121
1126
  *
1122
1127
  * toHex("#ABC")
1123
1128
  * >> "#AABBCC"
@@ -1128,27 +1133,31 @@ function colorNumberString(color) {
1128
1133
  * toHex("rgb(30, 80, 16)")
1129
1134
  * >> "#1E5010"
1130
1135
  *
1136
+ * * toHex("rgb(30, 80, 16, 0.5)")
1137
+ * >> "#1E501080"
1138
+ *
1131
1139
  */
1132
1140
  function toHex(color) {
1133
- if (color.includes("rgba")) {
1134
- throw new Error(`rgba() conversion currently not supported: ${color}`);
1135
- }
1136
- if (color.includes("rgb")) {
1137
- return rgbToHex6(color);
1141
+ let hexColor = color;
1142
+ if (color.startsWith("rgb")) {
1143
+ hexColor = rgbaStringToHex(color);
1138
1144
  }
1139
- color = color.replace("#", "").toUpperCase();
1140
- if (color.length === 3 || color.length === 4) {
1141
- color = color.split("").reduce((acc, h) => acc + h + h, "");
1145
+ else {
1146
+ hexColor = color.replace("#", "").toUpperCase();
1147
+ if (hexColor.length === 3 || hexColor.length === 4) {
1148
+ hexColor = hexColor.split("").reduce((acc, h) => acc + h + h, "");
1149
+ }
1150
+ hexColor = `#${hexColor}`;
1142
1151
  }
1143
- if (color.replace(/[a-f0-9]/gi, "") !== "") {
1144
- throw new Error("invalid color");
1152
+ if (!hexColor.match(HEX_MATCH)) {
1153
+ throw new Error(`invalid color input: ${color}`);
1145
1154
  }
1146
- return "#" + color;
1155
+ return hexColor;
1147
1156
  }
1148
1157
  function isColorValid(color) {
1149
1158
  try {
1150
- const { r, g, b, a } = colorToRGBA(color);
1151
- return (isColorValueValid(r) && isColorValueValid(g) && isColorValueValid(b) && isColorValueValid(a));
1159
+ toHex(color);
1160
+ return true;
1152
1161
  }
1153
1162
  catch (error) {
1154
1163
  return false;
@@ -1181,15 +1190,29 @@ function relativeLuminance(color) {
1181
1190
  /**
1182
1191
  * Convert a CSS rgb color string to a standardized hex6 color value.
1183
1192
  *
1184
- * rgbToHex6("rgb(30, 80, 16)")
1193
+ * rgbaStringToHex("rgb(30, 80, 16)")
1185
1194
  * >> "#1E5010"
1195
+ *
1196
+ * rgbaStringToHex("rgba(30, 80, 16, 0.5)")
1197
+ * >> "#1E501080"
1198
+ *
1199
+ * DOES NOT SUPPORT NON INTEGER RGB VALUES
1186
1200
  */
1187
- function rgbToHex6(color) {
1188
- return ("#" +
1189
- concat(color
1190
- .slice(4, -1)
1191
- .split(",")
1192
- .map((valueString) => parseInt(valueString, 10).toString(16).padStart(2, "0"))).toUpperCase());
1201
+ function rgbaStringToHex(color) {
1202
+ const stringVals = color.replace(RBA_REGEX, "").split(",");
1203
+ let alphaHex = 255;
1204
+ if (stringVals.length !== 3 && stringVals.length !== 4) {
1205
+ throw new Error("invalid color");
1206
+ }
1207
+ else if (stringVals.length === 4) {
1208
+ const alpha = parseFloat(stringVals.pop() || "1");
1209
+ alphaHex = Math.round((alpha || 1) * 255);
1210
+ }
1211
+ const vals = stringVals.map((val) => parseInt(val, 10));
1212
+ if (alphaHex !== 255) {
1213
+ vals.push(alphaHex);
1214
+ }
1215
+ return "#" + concat(vals.map((value) => value.toString(16).padStart(2, "0"))).toUpperCase();
1193
1216
  }
1194
1217
  /**
1195
1218
  * RGBA to HEX representation (#RRGGBBAA).
@@ -1211,7 +1234,7 @@ function rgbaToHex(rgba) {
1211
1234
  a = "0" + a;
1212
1235
  if (a === "ff")
1213
1236
  a = "";
1214
- return "#" + r + g + b + a;
1237
+ return ("#" + r + g + b + a).toUpperCase();
1215
1238
  }
1216
1239
  /**
1217
1240
  * Color string to RGBA representation
@@ -1332,6 +1355,9 @@ function rgbaToHSLA(rgba) {
1332
1355
  s = +(s * 100).toFixed(1);
1333
1356
  l = +(l * 100).toFixed(1);
1334
1357
  return { a: rgba.a, h, s, l };
1358
+ }
1359
+ function isSameColor(color1, color2) {
1360
+ return isColorValid(color1) && isColorValid(color2) && toHex(color1) === toHex(color2);
1335
1361
  }
1336
1362
 
1337
1363
  /** Reference of a cell (eg. A1, $B$5) */
@@ -1483,7 +1509,7 @@ function scrollDelay(value) {
1483
1509
  * formula, commas are used to separate arguments
1484
1510
  * - it does not support % symbol, in formulas % is an operator
1485
1511
  */
1486
- const formulaNumberRegexp = /^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+/;
1512
+ const formulaNumberRegexp = /(^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+)(?!\w|!)/;
1487
1513
  const pIntegerAndDecimals = "(\\d+(,\\d{3,})*(\\.\\d*)?)"; // pattern that match integer number with or without decimal digits
1488
1514
  const pOnlyDecimals = "(\\.\\d+)"; // pattern that match only expression with decimal digits
1489
1515
  const pScientificFormat = "(e(\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -3092,6 +3118,7 @@ const CHART_PADDING_RATIO = 0.02;
3092
3118
  const LINE_HEIGHT = 1.2;
3093
3119
  css /* scss */ `
3094
3120
  div.o-scorecard {
3121
+ font-family: ${DEFAULT_FONT};
3095
3122
  user-select: none;
3096
3123
  background-color: white;
3097
3124
  display: flex;
@@ -5379,24 +5406,30 @@ class Menu extends owl.Component {
5379
5406
  }
5380
5407
  });
5381
5408
  }
5382
- get visibleMenuItems() {
5383
- return this.props.menuItems.filter((x) => x.isVisible(this.env));
5409
+ get menuItemsAndSeparators() {
5410
+ const menuItemsAndSeparators = [];
5411
+ for (let i = 0; i < this.props.menuItems.length; i++) {
5412
+ const menuItem = this.props.menuItems[i];
5413
+ if (menuItem.isVisible(this.env)) {
5414
+ menuItemsAndSeparators.push(menuItem);
5415
+ }
5416
+ if (menuItem.separator &&
5417
+ i !== this.props.menuItems.length - 1 && // no separator at the end
5418
+ menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
5419
+ ) {
5420
+ menuItemsAndSeparators.push("separator");
5421
+ }
5422
+ }
5423
+ if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
5424
+ return [];
5425
+ }
5426
+ return menuItemsAndSeparators;
5384
5427
  }
5385
5428
  get subMenuPosition() {
5386
5429
  const position = Object.assign({}, this.subMenu.position);
5387
5430
  position.y -= this.subMenu.scrollOffset || 0;
5388
5431
  return position;
5389
5432
  }
5390
- get menuHeight() {
5391
- const menuItems = this.visibleMenuItems;
5392
- let menuItemsHeight = this.getMenuItemsHeight(menuItems);
5393
- // We don't display separator at the end of a menu
5394
- if (menuItems[menuItems.length - 1].separator) {
5395
- menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
5396
- }
5397
- const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
5398
- return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
5399
- }
5400
5433
  get popoverProps() {
5401
5434
  const isRoot = this.props.depth === 1;
5402
5435
  return {
@@ -5410,7 +5443,6 @@ class Menu extends owl.Component {
5410
5443
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
5411
5444
  onPopoverHidden: () => this.closeSubMenu(),
5412
5445
  onPopoverMoved: () => this.closeSubMenu(),
5413
- maxHeight: this.menuHeight,
5414
5446
  };
5415
5447
  }
5416
5448
  getColor(menu) {
@@ -5426,14 +5458,6 @@ class Menu extends owl.Component {
5426
5458
  this.closeSubMenu();
5427
5459
  this.props.onClose();
5428
5460
  }
5429
- /**
5430
- * Return the number of pixels between the top of the menu
5431
- * and the menu item at a given index.
5432
- */
5433
- subMenuVerticalPosition(menuIndex) {
5434
- const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
5435
- return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
5436
- }
5437
5461
  onExternalClick(ev) {
5438
5462
  // Don't close a root menu when clicked to open the submenus.
5439
5463
  const el = this.menuRef.el;
@@ -5443,10 +5467,6 @@ class Menu extends owl.Component {
5443
5467
  ev.closedMenuId = this.props.menuId;
5444
5468
  this.close();
5445
5469
  }
5446
- getMenuItemsHeight(menuItems) {
5447
- const numberOfSeparators = menuItems.filter((m) => m.separator).length;
5448
- return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
5449
- }
5450
5470
  getName(menu) {
5451
5471
  return menu.name(this.env);
5452
5472
  }
@@ -5466,8 +5486,11 @@ class Menu extends owl.Component {
5466
5486
  * If the given menu is not disabled, open it's submenu at the
5467
5487
  * correct position according to available surrounding space.
5468
5488
  */
5469
- openSubMenu(menu, menuIndex) {
5470
- const y = this.subMenuVerticalPosition(menuIndex);
5489
+ openSubMenu(menu, menuIndex, ev) {
5490
+ const parentMenuEl = ev.currentTarget;
5491
+ if (!parentMenuEl)
5492
+ return;
5493
+ const y = parentMenuEl.getBoundingClientRect().top;
5471
5494
  this.subMenu.position = {
5472
5495
  x: this.position.x + this.props.depth * MENU_WIDTH,
5473
5496
  y: y - (this.subMenu.scrollOffset || 0),
@@ -5484,20 +5507,20 @@ class Menu extends owl.Component {
5484
5507
  this.subMenu.isOpen = false;
5485
5508
  this.subMenu.parentMenu = undefined;
5486
5509
  }
5487
- onClickMenu(menu, menuIndex) {
5510
+ onClickMenu(menu, menuIndex, ev) {
5488
5511
  if (this.isEnabled(menu)) {
5489
5512
  if (this.isRoot(menu)) {
5490
- this.openSubMenu(menu, menuIndex);
5513
+ this.openSubMenu(menu, menuIndex, ev);
5491
5514
  }
5492
5515
  else {
5493
5516
  this.activateMenu(menu);
5494
5517
  }
5495
5518
  }
5496
5519
  }
5497
- onMouseOver(menu, position) {
5520
+ onMouseOver(menu, position, ev) {
5498
5521
  if (menu.isEnabled(this.env)) {
5499
5522
  if (this.isRoot(menu)) {
5500
- this.openSubMenu(menu, position);
5523
+ this.openSubMenu(menu, position, ev);
5501
5524
  }
5502
5525
  else {
5503
5526
  this.closeSubMenu();
@@ -6074,8 +6097,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6074
6097
  const dataSets = [];
6075
6098
  for (const sheetXC of dataSetsString) {
6076
6099
  const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
6077
- const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName } = dataRange;
6078
- if (invalidSheetName) {
6100
+ const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
6101
+ if (invalidSheetName || invalidXc) {
6079
6102
  continue;
6080
6103
  }
6081
6104
  // It's a rectangle. We treat all columns (arbitrary) as different data series.
@@ -6100,14 +6123,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6100
6123
  : undefined));
6101
6124
  }
6102
6125
  }
6103
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6104
- // A single cell. If it's only the title, the dataset is not added.
6105
- if (!dataSetsHaveTitle) {
6106
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6107
- }
6108
- }
6109
6126
  else {
6110
- /* 1 row or 1 column */
6127
+ /* 1 cell, 1 row or 1 column */
6111
6128
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6112
6129
  ? {
6113
6130
  top: zone.top,
@@ -6161,6 +6178,18 @@ function toExcelDataset(getters, ds) {
6161
6178
  range: getters.getRangeString(dataRange, "forceSheetReference"),
6162
6179
  };
6163
6180
  }
6181
+ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
6182
+ if (!labelRange)
6183
+ return undefined;
6184
+ let zone = {
6185
+ ...labelRange.zone,
6186
+ };
6187
+ if (shouldRemoveFirstLabel && labelRange.zone.bottom > labelRange.zone.top) {
6188
+ zone.top = zone.top + 1;
6189
+ }
6190
+ const range = labelRange.clone({ zone });
6191
+ return getters.getRangeString(range, "forceSheetReference");
6192
+ }
6164
6193
  /**
6165
6194
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
6166
6195
  * with an executed command
@@ -6245,6 +6274,20 @@ function checkLabelRange(definition) {
6245
6274
  }
6246
6275
  return 0 /* CommandResult.Success */;
6247
6276
  }
6277
+ function shouldRemoveFirstLabel(labelRange, dataset, dataSetsHaveTitle) {
6278
+ if (!dataSetsHaveTitle)
6279
+ return false;
6280
+ if (!labelRange)
6281
+ return false;
6282
+ if (!dataset)
6283
+ return true;
6284
+ const datasetLength = getZoneArea(dataset.dataRange.zone);
6285
+ const labelLength = getZoneArea(labelRange.zone);
6286
+ if (labelLength < datasetLength) {
6287
+ return false;
6288
+ }
6289
+ return true;
6290
+ }
6248
6291
  // ---------------------------------------------------------------------------
6249
6292
  // Scorecard
6250
6293
  // ---------------------------------------------------------------------------
@@ -6614,6 +6657,7 @@ class BarChart extends AbstractChart {
6614
6657
  this.legendPosition = definition.legendPosition;
6615
6658
  this.stacked = definition.stacked;
6616
6659
  this.aggregated = definition.aggregated;
6660
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
6617
6661
  }
6618
6662
  static transformDefinition(definition, executed) {
6619
6663
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -6681,11 +6725,13 @@ class BarChart extends AbstractChart {
6681
6725
  const dataSets = this.dataSets
6682
6726
  .map((ds) => toExcelDataset(this.getters, ds))
6683
6727
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
6728
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
6684
6729
  return {
6685
6730
  ...this.getDefinition(),
6686
6731
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6687
6732
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6688
6733
  dataSets,
6734
+ labelRange,
6689
6735
  };
6690
6736
  }
6691
6737
  updateRanges(applyChange) {
@@ -6753,6 +6799,11 @@ function createBarChartRuntime(chart, getters) {
6753
6799
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
6754
6800
  let labels = labelValues.formattedValues;
6755
6801
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
6802
+ if (chart.dataSetsHaveTitle &&
6803
+ dataSetsValues[0] &&
6804
+ labels.length > dataSetsValues[0].data.length) {
6805
+ labels.shift();
6806
+ }
6756
6807
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
6757
6808
  if (chart.aggregated) {
6758
6809
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7175,6 +7226,7 @@ class LineChart extends AbstractChart {
7175
7226
  this.labelsAsText = definition.labelsAsText;
7176
7227
  this.stacked = definition.stacked;
7177
7228
  this.aggregated = definition.aggregated;
7229
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7178
7230
  }
7179
7231
  static validateChartDefinition(validator, definition) {
7180
7232
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -7242,11 +7294,13 @@ class LineChart extends AbstractChart {
7242
7294
  const dataSets = this.dataSets
7243
7295
  .map((ds) => toExcelDataset(this.getters, ds))
7244
7296
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7297
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7245
7298
  return {
7246
7299
  ...this.getDefinition(),
7247
7300
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7248
7301
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7249
7302
  dataSets,
7303
+ labelRange,
7250
7304
  };
7251
7305
  }
7252
7306
  copyForSheetId(sheetId) {
@@ -7384,6 +7438,11 @@ function createLineChartRuntime(chart, getters) {
7384
7438
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7385
7439
  let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
7386
7440
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7441
+ if (chart.dataSetsHaveTitle &&
7442
+ dataSetsValues[0] &&
7443
+ labels.length > dataSetsValues[0].data.length) {
7444
+ labels.shift();
7445
+ }
7387
7446
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7388
7447
  if (axisType === "time") {
7389
7448
  ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
@@ -7442,6 +7501,7 @@ class PieChart extends AbstractChart {
7442
7501
  this.background = definition.background;
7443
7502
  this.legendPosition = definition.legendPosition;
7444
7503
  this.aggregated = definition.aggregated;
7504
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7445
7505
  }
7446
7506
  static transformDefinition(definition, executed) {
7447
7507
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -7505,12 +7565,14 @@ class PieChart extends AbstractChart {
7505
7565
  const dataSets = this.dataSets
7506
7566
  .map((ds) => toExcelDataset(this.getters, ds))
7507
7567
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7568
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7508
7569
  return {
7509
7570
  ...this.getDefinition(),
7510
7571
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7511
7572
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7512
7573
  verticalAxisPosition: "left",
7513
7574
  dataSets,
7575
+ labelRange,
7514
7576
  };
7515
7577
  }
7516
7578
  updateRanges(applyChange) {
@@ -7556,6 +7618,11 @@ function createPieChartRuntime(chart, getters) {
7556
7618
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7557
7619
  let labels = labelValues.formattedValues;
7558
7620
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7621
+ if (chart.dataSetsHaveTitle &&
7622
+ dataSetsValues[0] &&
7623
+ labels.length > dataSetsValues[0].data.length) {
7624
+ labels.shift();
7625
+ }
7559
7626
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7560
7627
  if (chart.aggregated) {
7561
7628
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -8191,7 +8258,6 @@ function getSmartChartDefinition(zone, getters) {
8191
8258
  labelRangeXc = zoneToXc({
8192
8259
  ...zone,
8193
8260
  right: zone.left,
8194
- top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
8195
8261
  });
8196
8262
  }
8197
8263
  // Only display legend for several datasets.
@@ -15109,7 +15175,7 @@ const ADDRESS = {
15109
15175
  cellReference = rowPart + colPart;
15110
15176
  }
15111
15177
  if (sheet !== undefined) {
15112
- return `${getComposerSheetName(toString(sheet))}!${cellReference}`;
15178
+ return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
15113
15179
  }
15114
15180
  return cellReference;
15115
15181
  },
@@ -16934,6 +17000,22 @@ class LineBarPieConfigPanel extends owl.Component {
16934
17000
  aggregated: ev.target.checked,
16935
17001
  });
16936
17002
  }
17003
+ calculateHeaderPosition() {
17004
+ if (this.isDatasetInvalid || this.isLabelInvalid) {
17005
+ return undefined;
17006
+ }
17007
+ const getters = this.env.model.getters;
17008
+ const sheetId = getters.getActiveSheetId();
17009
+ const labelRange = createRange(getters, sheetId, this.labelRange);
17010
+ const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
17011
+ if (dataSets.length) {
17012
+ return dataSets[0].dataRange.zone.top + 1;
17013
+ }
17014
+ else if (labelRange) {
17015
+ return labelRange.zone.top + 1;
17016
+ }
17017
+ return undefined;
17018
+ }
16937
17019
  }
16938
17020
  LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
16939
17021
  LineBarPieConfigPanel.components = { SelectionInput };
@@ -17181,6 +17263,9 @@ class ColorPicker extends owl.Component {
17181
17263
  top,
17182
17264
  });
17183
17265
  }
17266
+ isSameColor(color1, color2) {
17267
+ return isSameColor(color1, color2);
17268
+ }
17184
17269
  }
17185
17270
  ColorPicker.template = "o-spreadsheet-ColorPicker";
17186
17271
  ColorPicker.defaultProps = {
@@ -19422,6 +19507,8 @@ class ContentEditableHelper {
19422
19507
  start = 0;
19423
19508
  if (end > textLength)
19424
19509
  end = textLength;
19510
+ if (start > textLength)
19511
+ start = textLength;
19425
19512
  }
19426
19513
  let startNode = this.findChildAtCharacterIndex(start);
19427
19514
  let endNode = this.findChildAtCharacterIndex(end);
@@ -19691,6 +19778,7 @@ css /* scss */ `
19691
19778
  padding-right: 2px;
19692
19779
 
19693
19780
  box-sizing: border-box;
19781
+ font-family: ${DEFAULT_FONT};
19694
19782
 
19695
19783
  caret-color: black;
19696
19784
  padding-left: 3px;
@@ -20133,7 +20221,7 @@ class Composer extends owl.Component {
20133
20221
  const highlights = this.env.model.getters.getHighlights();
20134
20222
  const refSheet = sheetName
20135
20223
  ? this.env.model.getters.getSheetIdByName(sheetName)
20136
- : this.env.model.getters.getEditionSheet();
20224
+ : this.env.model.getters.getCurrentEditedCell().sheetId;
20137
20225
  const highlight = highlights.find((highlight) => {
20138
20226
  if (highlight.sheetId !== refSheet)
20139
20227
  return false;
@@ -20294,7 +20382,7 @@ class GridComposer extends owl.Component {
20294
20382
  get cellReference() {
20295
20383
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20296
20384
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20297
- return `${prefixSheet ? getComposerSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20385
+ return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20298
20386
  }
20299
20387
  get cellReferenceStyle() {
20300
20388
  const { x: left, y: top } = this.rect;
@@ -20479,15 +20567,18 @@ function screenCoordinatesToInternal(getters, { x, y }) {
20479
20567
  return { x, y };
20480
20568
  }
20481
20569
 
20482
- function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { scrollX, scrollY }) {
20570
+ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { maxX, maxY }, { scrollX, scrollY }) {
20483
20571
  const minX = viewportX ? 0 : -scrollX;
20484
20572
  const minY = viewportY ? 0 : -scrollY;
20485
- let deltaX = mouseX - mouseInitialX;
20486
- const newX = Math.max(initialFigure.x + deltaX, minX);
20487
- let deltaY = mouseY - mouseInitialY;
20488
- const newY = Math.max(initialFigure.y + deltaY, minY);
20573
+ const deltaX = mouseX - mouseInitialX;
20574
+ const newX = clamp(initialFigure.x + deltaX, minX, maxX - initialFigure.width - scrollX);
20575
+ const deltaY = mouseY - mouseInitialY;
20576
+ const newY = clamp(initialFigure.y + deltaY, minY, maxY - initialFigure.height - scrollY);
20489
20577
  return { ...initialFigure, x: newX, y: newY };
20490
20578
  }
20579
+ function clamp(value, min, max) {
20580
+ return Math.min(Math.max(value, min), max);
20581
+ }
20491
20582
  function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
20492
20583
  let { x, y, width, height } = initialFigure;
20493
20584
  if (keepRatio && dirX != 0 && dirY != 0) {
@@ -20886,12 +20977,16 @@ class FiguresContainer extends owl.Component {
20886
20977
  }
20887
20978
  const sheetId = this.env.model.getters.getActiveSheetId();
20888
20979
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
20980
+ const maxDimensions = {
20981
+ maxX: this.env.model.getters.getColDimensions(sheetId, this.env.model.getters.getNumberCols(sheetId) - 1).end,
20982
+ maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end,
20983
+ };
20889
20984
  const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
20890
20985
  const initialFig = { ...figure, x, y };
20891
20986
  const onMouseMove = (ev) => {
20892
20987
  const getters = this.env.model.getters;
20893
20988
  const currentMousePosition = { x: ev.clientX, y: ev.clientY };
20894
- const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), getters.getActiveSheetScrollInfo());
20989
+ const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), maxDimensions, getters.getActiveSheetScrollInfo());
20895
20990
  const otherFigures = this.getOtherFigures(figure.id);
20896
20991
  const internalDragged = screenFigureToInternal(getters, draggedFigure);
20897
20992
  const snapResult = snapForMove(getters, internalDragged, otherFigures);
@@ -22585,6 +22680,10 @@ class Grid extends owl.Component {
22585
22680
  }
22586
22681
  }
22587
22682
  onInput(ev) {
22683
+ // the user meant to paste in the sheet, not open the composer with the pasted content
22684
+ if (!ev.isComposing && ev.inputType === "insertFromPaste") {
22685
+ return;
22686
+ }
22588
22687
  if (ev.data) {
22589
22688
  // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22590
22689
  // character
@@ -23433,6 +23532,70 @@ function fixXlsxUnicode(str) {
23433
23532
  });
23434
23533
  }
23435
23534
 
23535
+ 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;
23536
+ /**
23537
+ * Convert excel format to o_spreadsheet format
23538
+ *
23539
+ * Excel format are defined in openXML §18.8.31
23540
+ */
23541
+ function convertXlsxFormat(numFmtId, formats, warningManager) {
23542
+ var _a, _b, _c;
23543
+ if (numFmtId === 0) {
23544
+ return undefined;
23545
+ }
23546
+ // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23547
+ let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23548
+ if (format) {
23549
+ try {
23550
+ let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23551
+ convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23552
+ convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23553
+ // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23554
+ const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23555
+ const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23556
+ if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23557
+ throw new Error("Multiple escaped blocks in format");
23558
+ }
23559
+ convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23560
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
23561
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23562
+ convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23563
+ convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
23564
+ if (isXlsxDateFormat(convertedFormat)) {
23565
+ convertedFormat = convertDateFormat$1(convertedFormat);
23566
+ }
23567
+ if (isFormatSupported(convertedFormat)) {
23568
+ return convertedFormat;
23569
+ }
23570
+ }
23571
+ catch (e) { }
23572
+ }
23573
+ warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23574
+ return undefined;
23575
+ }
23576
+ function isFormatSupported(format) {
23577
+ try {
23578
+ formatValue(0, format);
23579
+ return true;
23580
+ }
23581
+ catch (e) {
23582
+ return false;
23583
+ }
23584
+ }
23585
+ function isXlsxDateFormat(format) {
23586
+ return format.match(XLSX_DATE_FORMAT_REGEX) !== null;
23587
+ }
23588
+ function convertDateFormat$1(format) {
23589
+ // Some of these aren't defined neither in the OpenXML spec not the Xlsx extension of OpenXML,
23590
+ // but can still occur and are supported by Excel/Google sheets
23591
+ format = format.toLowerCase();
23592
+ format = format.replace(/mmmmm/g, "mmm");
23593
+ format = format.replace(/am\/pm|a\/m/g, "a");
23594
+ format = format.replace(/hhhh/g, "hh");
23595
+ format = format.replace(/\bh\b/g, "hh");
23596
+ return format;
23597
+ }
23598
+
23436
23599
  function convertBorders(data, warningManager) {
23437
23600
  const borderArray = data.borders.map((border) => {
23438
23601
  addBorderWarnings(border, warningManager);
@@ -23495,41 +23658,6 @@ function convertFormats(data, warningManager) {
23495
23658
  }
23496
23659
  return arrayToObject(formats, 1);
23497
23660
  }
23498
- /**
23499
- * Convert excel format to o_spreadsheet format
23500
- *
23501
- * Excel format are defined in openXML §18.8.31
23502
- */
23503
- function convertXlsxFormat(numFmtId, formats, warningManager) {
23504
- var _a, _b, _c;
23505
- if (numFmtId === 0) {
23506
- return undefined;
23507
- }
23508
- // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23509
- let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23510
- if (format) {
23511
- try {
23512
- let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23513
- convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23514
- convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23515
- // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23516
- const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23517
- const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23518
- if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23519
- throw new Error("Multiple escaped blocks in format");
23520
- }
23521
- convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23522
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore with of next char for align purposes. Not supported ATM
23523
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23524
- convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23525
- formatValue(0, convertedFormat);
23526
- return convertedFormat;
23527
- }
23528
- catch (e) { }
23529
- }
23530
- warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23531
- return undefined;
23532
- }
23533
23661
  // ---------------------------------------------------------------------------
23534
23662
  // Warnings
23535
23663
  // ---------------------------------------------------------------------------
@@ -24084,16 +24212,18 @@ function convertFigure(figure, id, sheetData) {
24084
24212
  };
24085
24213
  }
24086
24214
  function convertChartData(chartData) {
24087
- var _a;
24088
- const labelRange = (_a = chartData.dataSets[0].label) === null || _a === void 0 ? void 0 : _a.replace(/\$/g, "");
24089
- let dataSets = chartData.dataSets.map((data) => data.range.replace(/\$/g, ""));
24215
+ const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
24216
+ const labelRange = chartData.labelRange
24217
+ ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
24218
+ : undefined;
24219
+ let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
24090
24220
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
24091
24221
  if (chartData.type === "pie") {
24092
24222
  dataSets.reverse();
24093
24223
  }
24094
24224
  return {
24095
24225
  dataSets,
24096
- dataSetsHaveTitle: false,
24226
+ dataSetsHaveTitle,
24097
24227
  labelRange,
24098
24228
  title: chartData.title || "",
24099
24229
  type: chartData.type,
@@ -24104,6 +24234,28 @@ function convertChartData(chartData) {
24104
24234
  aggregated: false,
24105
24235
  labelsAsText: false,
24106
24236
  };
24237
+ }
24238
+ function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
24239
+ let { sheetName, xc } = splitReference(range);
24240
+ if (sheetName) {
24241
+ sheetName = getCanonicalSheetName(sheetName) + "!";
24242
+ }
24243
+ else {
24244
+ sheetName = "";
24245
+ }
24246
+ let zone = toUnboundedZone(xc);
24247
+ if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
24248
+ const height = zone.bottom - zone.top + 1;
24249
+ const width = zone.right - zone.left + 1;
24250
+ if (height === 1) {
24251
+ zone = { ...zone, left: zone.left - 1 };
24252
+ }
24253
+ else if (width === 1) {
24254
+ zone = { ...zone, top: zone.top - 1 };
24255
+ }
24256
+ }
24257
+ const dataXC = zoneToXc(zone);
24258
+ return sheetName + dataXC;
24107
24259
  }
24108
24260
 
24109
24261
  /**
@@ -25171,6 +25323,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
25171
25323
  title: chartTitle,
25172
25324
  type: CHART_TYPE_CONVERSION_MAP[chartType],
25173
25325
  dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
25326
+ labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
25174
25327
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
25175
25328
  default: "ffffff",
25176
25329
  }).asString(),
@@ -25190,7 +25343,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
25190
25343
  extractChartDatasets(chartElement) {
25191
25344
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
25192
25345
  return {
25193
- label: this.extractChildTextContent(chartDataElement, "c:cat c:f"),
25346
+ label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
25194
25347
  range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
25195
25348
  };
25196
25349
  });
@@ -29107,6 +29260,39 @@ class FigurePlugin extends CorePlugin {
29107
29260
  case "DELETE_FIGURE":
29108
29261
  this.removeFigure(cmd.id, cmd.sheetId);
29109
29262
  break;
29263
+ case "REMOVE_COLUMNS_ROWS":
29264
+ this.onRowColDelete(cmd.sheetId, cmd.dimension);
29265
+ }
29266
+ }
29267
+ onRowColDelete(sheetId, dimension) {
29268
+ dimension === "ROW" ? this.onRowDeletion(sheetId) : this.onColDeletion(sheetId);
29269
+ }
29270
+ onRowDeletion(sheetId) {
29271
+ const numHeader = this.getters.getNumberRows(sheetId);
29272
+ let gridHeight = 0;
29273
+ for (let i = 0; i < numHeader; i++) {
29274
+ gridHeight += this.getters.getRowSize(sheetId, i);
29275
+ }
29276
+ const figures = this.getters.getFigures(sheetId);
29277
+ for (const figure of figures) {
29278
+ const newY = Math.min(figure.y, gridHeight - figure.height);
29279
+ if (newY !== figure.y) {
29280
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, y: newY });
29281
+ }
29282
+ }
29283
+ }
29284
+ onColDeletion(sheetId) {
29285
+ const numHeader = this.getters.getNumberCols(sheetId);
29286
+ let gridWidth = 0;
29287
+ for (let i = 0; i < numHeader; i++) {
29288
+ gridWidth += this.getters.getColSize(sheetId, i);
29289
+ }
29290
+ const figures = this.getters.getFigures(sheetId);
29291
+ for (const figure of figures) {
29292
+ const newX = Math.min(figure.x, gridWidth - figure.width);
29293
+ if (newX !== figure.x) {
29294
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x: newX });
29295
+ }
29110
29296
  }
29111
29297
  }
29112
29298
  updateFigure(sheetId, figure) {
@@ -30777,7 +30963,7 @@ class RangeAdapter {
30777
30963
  sheetName = rangeImpl.invalidSheetName;
30778
30964
  }
30779
30965
  else {
30780
- sheetName = getComposerSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30966
+ sheetName = getCanonicalSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30781
30967
  }
30782
30968
  }
30783
30969
  if (prefixSheet && !sheetName) {
@@ -31828,9 +32014,7 @@ class CustomColorsPlugin extends UIPlugin {
31828
32014
  // remove duplicates first to check validity on a reduced
31829
32015
  // set of colors, then normalize to HEX and remove duplicates
31830
32016
  // again
31831
- [...new Set([...usedColors, ...this.customColors])]
31832
- .filter(isColorValid)
31833
- .map((c) => toHex(c).toLowerCase())),
32017
+ [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
31834
32018
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
31835
32019
  }
31836
32020
  getColorsFromCells(cells) {
@@ -31891,7 +32075,7 @@ class CustomColorsPlugin extends UIPlugin {
31891
32075
  return [...chartsColors];
31892
32076
  }
31893
32077
  tryToAddColor(color) {
31894
- const formattedColor = toHex(color).toLowerCase();
32078
+ const formattedColor = toHex(color);
31895
32079
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
31896
32080
  this.customColors.add(formattedColor);
31897
32081
  }
@@ -32906,8 +33090,8 @@ class InternalViewport {
32906
33090
  this.getters = getters;
32907
33091
  this.sheetId = sheetId;
32908
33092
  this.boundaries = boundaries;
32909
- this.width = sizeInGrid.width;
32910
- this.height = sizeInGrid.height;
33093
+ this.viewportWidth = sizeInGrid.width;
33094
+ this.viewportHeight = sizeInGrid.height;
32911
33095
  this.offsetScrollbarX = offsets.x;
32912
33096
  this.offsetScrollbarY = offsets.y;
32913
33097
  this.canScrollVertically = options.canScrollVertically;
@@ -32918,6 +33102,10 @@ class InternalViewport {
32918
33102
  this.adjustViewportOffsetY();
32919
33103
  }
32920
33104
  // PUBLIC
33105
+ /** Returns the maximum size (in Pixels) of the viewport relative to its allocated client size
33106
+ * When the viewport grid size is smaller than its client width (resp. height), it will return
33107
+ * the client width (resp. height).
33108
+ */
32921
33109
  getMaxSize() {
32922
33110
  const lastCol = this.getters.findLastVisibleColRowIndex(this.sheetId, "COL", {
32923
33111
  first: this.boundaries.left,
@@ -32929,20 +33117,24 @@ class InternalViewport {
32929
33117
  });
32930
33118
  const { end: lastColEnd, size: lastColSize } = this.getters.getColDimensions(this.sheetId, lastCol);
32931
33119
  const { end: lastRowEnd, size: lastRowSize } = this.getters.getRowDimensions(this.sheetId, lastRow);
32932
- const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.width, 0);
33120
+ const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.viewportWidth, 0);
32933
33121
  const leftColSize = this.getters.getColSize(this.sheetId, leftColIndex);
32934
- const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.height, 0);
33122
+ const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.viewportHeight, 0);
32935
33123
  const topRowSize = this.getters.getRowSize(this.sheetId, leftRowIndex);
32936
- const width = lastColEnd -
32937
- this.offsetCorrectionX +
32938
- (this.canScrollHorizontally
32939
- ? Math.max(DEFAULT_CELL_WIDTH, Math.min(leftColSize, this.width - lastColSize))
32940
- : 0);
32941
- const height = lastRowEnd -
32942
- this.offsetCorrectionY +
32943
- (this.canScrollVertically
32944
- ? Math.max(DEFAULT_CELL_HEIGHT + 5, Math.min(topRowSize, this.height - lastRowSize))
32945
- : 0);
33124
+ let width = lastColEnd - this.offsetCorrectionX;
33125
+ if (this.canScrollHorizontally) {
33126
+ width += Math.max(DEFAULT_CELL_WIDTH, // leave some minimal space to let the user know they scrolled all the way
33127
+ Math.min(leftColSize, this.viewportWidth - lastColSize) // Add pixels that allows the snapping at maximum horizontal scroll
33128
+ );
33129
+ width = Math.max(width, this.viewportWidth); // if the viewport grid size is smaller than its client width, return client width
33130
+ }
33131
+ let height = lastRowEnd - this.offsetCorrectionY;
33132
+ if (this.canScrollVertically) {
33133
+ height += Math.max(DEFAULT_CELL_HEIGHT + 5, // leave some space to let the user know they scrolled all the way
33134
+ Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
33135
+ );
33136
+ height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
33137
+ }
32946
33138
  return { width, height };
32947
33139
  }
32948
33140
  /**
@@ -32951,7 +33143,7 @@ class InternalViewport {
32951
33143
  * It returns -1 if no column is found.
32952
33144
  */
32953
33145
  getColIndex(x, absolute = false) {
32954
- if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.width) {
33146
+ if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.viewportWidth) {
32955
33147
  return -1;
32956
33148
  }
32957
33149
  return this.searchHeaderIndex("COL", x - this.offsetCorrectionX, this.left, absolute);
@@ -32962,7 +33154,7 @@ class InternalViewport {
32962
33154
  * It returns -1 if no row is found.
32963
33155
  */
32964
33156
  getRowIndex(y, absolute = false) {
32965
- if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.height) {
33157
+ if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.viewportHeight) {
32966
33158
  return -1;
32967
33159
  }
32968
33160
  return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY, this.top, absolute);
@@ -32989,7 +33181,7 @@ class InternalViewport {
32989
33181
  adjustPositionX(col) {
32990
33182
  const sheetId = this.sheetId;
32991
33183
  const { start, end } = this.getters.getColDimensions(sheetId, col);
32992
- while (end > this.offsetX + this.offsetCorrectionX + this.width &&
33184
+ while (end > this.offsetX + this.offsetCorrectionX + this.viewportWidth &&
32993
33185
  this.offsetX + this.offsetCorrectionX < start) {
32994
33186
  this.offsetX = this.getters.getColDimensions(sheetId, this.left).end - this.offsetCorrectionX;
32995
33187
  this.offsetScrollbarX = this.offsetX;
@@ -33011,7 +33203,7 @@ class InternalViewport {
33011
33203
  adjustPositionY(row) {
33012
33204
  const sheetId = this.sheetId;
33013
33205
  while (this.getters.getRowDimensions(sheetId, row).end >
33014
- this.offsetY + this.height + this.offsetCorrectionY &&
33206
+ this.offsetY + this.viewportHeight + this.offsetCorrectionY &&
33015
33207
  this.offsetY + this.offsetCorrectionY < this.getters.getRowDimensions(sheetId, row).start) {
33016
33208
  this.offsetY = this.getters.getRowDimensions(sheetId, this.top).end - this.offsetCorrectionY;
33017
33209
  this.offsetScrollbarY = this.offsetY;
@@ -33049,8 +33241,8 @@ class InternalViewport {
33049
33241
  const x = this.getters.getColRowOffset("COL", this.zone.left, targetZone.left) +
33050
33242
  this.offsetCorrectionX;
33051
33243
  const y = this.getters.getColRowOffset("ROW", this.zone.top, targetZone.top) + this.offsetCorrectionY;
33052
- const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.width);
33053
- const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.height);
33244
+ const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
33245
+ const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
33054
33246
  return {
33055
33247
  x,
33056
33248
  y,
@@ -33113,12 +33305,12 @@ class InternalViewport {
33113
33305
  adjustViewportOffsetX() {
33114
33306
  if (this.canScrollHorizontally) {
33115
33307
  const { width: viewportWidth } = this.getMaxSize();
33116
- if (this.width + this.offsetScrollbarX > viewportWidth) {
33117
- this.offsetScrollbarX = Math.max(0, viewportWidth - this.width);
33308
+ if (this.viewportWidth + this.offsetScrollbarX > viewportWidth) {
33309
+ this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
33118
33310
  }
33119
33311
  }
33120
33312
  this.left = this.getColIndex(this.offsetScrollbarX, true);
33121
- this.right = this.getColIndex(this.offsetScrollbarX + this.width, true);
33313
+ this.right = this.getColIndex(this.offsetScrollbarX + this.viewportWidth, true);
33122
33314
  if (this.right === -1) {
33123
33315
  this.right = this.boundaries.right;
33124
33316
  }
@@ -33130,12 +33322,12 @@ class InternalViewport {
33130
33322
  adjustViewportOffsetY() {
33131
33323
  if (this.canScrollVertically) {
33132
33324
  const { height: paneHeight } = this.getMaxSize();
33133
- if (this.height + this.offsetScrollbarY > paneHeight) {
33134
- this.offsetScrollbarY = Math.max(0, paneHeight - this.height);
33325
+ if (this.viewportHeight + this.offsetScrollbarY > paneHeight) {
33326
+ this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
33135
33327
  }
33136
33328
  }
33137
33329
  this.top = this.getRowIndex(this.offsetScrollbarY, true);
33138
- this.bottom = this.getRowIndex(this.offsetScrollbarY + this.width, true);
33330
+ this.bottom = this.getRowIndex(this.offsetScrollbarY + this.viewportWidth, true);
33139
33331
  if (this.bottom === -1) {
33140
33332
  this.bottom = this.boundaries.bottom;
33141
33333
  }
@@ -33146,7 +33338,7 @@ class InternalViewport {
33146
33338
  adjustViewportZoneX() {
33147
33339
  const sheetId = this.sheetId;
33148
33340
  this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
33149
- this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.width, this.left));
33341
+ this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
33150
33342
  if (this.right === -1) {
33151
33343
  this.right = this.getters.getNumberCols(sheetId) - 1;
33152
33344
  }
@@ -33159,7 +33351,7 @@ class InternalViewport {
33159
33351
  adjustViewportZoneY() {
33160
33352
  const sheetId = this.sheetId;
33161
33353
  this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
33162
- this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.height, this.top));
33354
+ this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
33163
33355
  if (this.bottom === -1) {
33164
33356
  this.bottom = this.getters.getNumberRows(sheetId) - 1;
33165
33357
  }
@@ -33422,8 +33614,7 @@ class SheetViewPlugin extends UIPlugin {
33422
33614
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
33423
33615
  }
33424
33616
  /**
33425
- * Return the main viewport maximum size. That is the zone dimension
33426
- * with some bottom and right padding.
33617
+ * Return the main viewport maximum size relative to the client size.
33427
33618
  */
33428
33619
  getMainViewportRect() {
33429
33620
  const sheetId = this.getters.getActiveSheetId();
@@ -33439,8 +33630,8 @@ class SheetViewPlugin extends UIPlugin {
33439
33630
  const { width, height } = this.getMainViewportRect();
33440
33631
  const viewport = this.getMainInternalViewport(sheetId);
33441
33632
  return {
33442
- maxOffsetX: Math.max(0, width - viewport.width + 1),
33443
- maxOffsetY: Math.max(0, height - viewport.height + 1),
33633
+ maxOffsetX: Math.max(0, width - viewport.viewportWidth + 1),
33634
+ maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
33444
33635
  };
33445
33636
  }
33446
33637
  getColRowOffsetInViewport(dimension, referenceIndex, index) {
@@ -35660,7 +35851,7 @@ class SelectionInputPlugin extends UIPlugin {
35660
35851
  const inputSheetId = this.activeSheet;
35661
35852
  const sheetId = this.getters.getActiveSheetId();
35662
35853
  const sheetName = this.getters.getSheetName(sheetId);
35663
- this.add([sheetId === inputSheetId ? xc : `${getComposerSheetName(sheetName)}!${xc}`]);
35854
+ this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
35664
35855
  }
35665
35856
  handle(cmd) {
35666
35857
  switch (cmd.type) {
@@ -37907,13 +38098,19 @@ class ClipboardFigureState {
37907
38098
  */
37908
38099
  paste(target) {
37909
38100
  const sheetId = this.getters.getActiveSheetId();
38101
+ const { width, height } = this.copiedFigure;
38102
+ const numCols = this.getters.getNumberCols(sheetId);
38103
+ const numRows = this.getters.getNumberRows(sheetId);
38104
+ const targetX = this.getters.getColDimensions(sheetId, target[0].left).start;
38105
+ const targetY = this.getters.getRowDimensions(sheetId, target[0].top).start;
38106
+ const maxX = this.getters.getColDimensions(sheetId, numCols - 1).end;
38107
+ const maxY = this.getters.getRowDimensions(sheetId, numRows - 1).end;
37910
38108
  const position = {
37911
- x: this.getters.getColDimensions(sheetId, target[0].left).start,
37912
- y: this.getters.getRowDimensions(sheetId, target[0].top).start,
38109
+ x: maxX < width ? 0 : Math.min(targetX, maxX - width),
38110
+ y: maxY < height ? 0 : Math.min(targetY, maxY - height),
37913
38111
  };
37914
- const size = { height: this.copiedFigure.height, width: this.copiedFigure.width };
37915
38112
  const newId = new UuidGenerator().uuidv4();
37916
- this.copiedFigureContent.paste(sheetId, newId, position, size);
38113
+ this.copiedFigureContent.paste(sheetId, newId, position, { height, width });
37917
38114
  if (this.operation === "CUT") {
37918
38115
  this.dispatch("DELETE_FIGURE", {
37919
38116
  sheetId: this.copiedFigureContent.sheetId,
@@ -38279,7 +38476,7 @@ function loopThroughReferenceType(token) {
38279
38476
  return token;
38280
38477
  const { xc, sheetName } = splitReference(token.value);
38281
38478
  const [left, right] = xc.split(":");
38282
- const sheetRef = sheetName ? `${getComposerSheetName(sheetName)}!` : "";
38479
+ const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
38283
38480
  const updatedLeft = getTokenNextReferenceType(left);
38284
38481
  const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
38285
38482
  return { ...token, value: sheetRef + updatedLeft + updatedRight };
@@ -38538,9 +38735,6 @@ class EditionPlugin extends UIPlugin {
38538
38735
  }
38539
38736
  return this.currentContent;
38540
38737
  }
38541
- getEditionSheet() {
38542
- return this.sheetId;
38543
- }
38544
38738
  getComposerSelection() {
38545
38739
  return {
38546
38740
  start: this.selectionStart,
@@ -38824,8 +39018,8 @@ class EditionPlugin extends UIPlugin {
38824
39018
  getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
38825
39019
  const sheetId = this.getters.getActiveSheetId();
38826
39020
  let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
38827
- if (this.getters.getEditionSheet() !== this.getters.getActiveSheetId()) {
38828
- const sheetName = getComposerSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
39021
+ if (this.getters.getCurrentEditedCell().sheetId !== this.getters.getActiveSheetId()) {
39022
+ const sheetName = getCanonicalSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
38829
39023
  selectedXc = `${sheetName}!${selectedXc}`;
38830
39024
  }
38831
39025
  return selectedXc;
@@ -38839,7 +39033,7 @@ class EditionPlugin extends UIPlugin {
38839
39033
  _fixedParts.pop();
38840
39034
  }
38841
39035
  const newRange = range.clone({ parts: _fixedParts });
38842
- return this.getters.getSelectionRangeString(newRange, this.getters.getEditionSheet());
39036
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
38843
39037
  }
38844
39038
  /**
38845
39039
  * Replace the current selection by a new text.
@@ -38872,7 +39066,7 @@ class EditionPlugin extends UIPlugin {
38872
39066
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38873
39067
  return;
38874
39068
  }
38875
- const editionSheetId = this.getters.getEditionSheet();
39069
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38876
39070
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
38877
39071
  const colorsToKeep = {};
38878
39072
  for (const xc of XCs) {
@@ -38901,7 +39095,7 @@ class EditionPlugin extends UIPlugin {
38901
39095
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38902
39096
  return [];
38903
39097
  }
38904
- const editionSheetId = this.getters.getEditionSheet();
39098
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38905
39099
  const rangeColor = (rangeString) => {
38906
39100
  const colorIndex = this.colorIndexByRange[rangeString];
38907
39101
  return colors$1[colorIndex % colors$1.length];
@@ -38919,7 +39113,7 @@ class EditionPlugin extends UIPlugin {
38919
39113
  * Return ranges currently referenced in the composer
38920
39114
  */
38921
39115
  getReferencedRanges() {
38922
- const editionSheetId = this.getters.getEditionSheet();
39116
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38923
39117
  return this.currentTokens
38924
39118
  .filter((token) => token.type === "REFERENCE")
38925
39119
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -38978,7 +39172,6 @@ EditionPlugin.getters = [
38978
39172
  "isSelectingForComposer",
38979
39173
  "showSelectionIndicator",
38980
39174
  "getCurrentContent",
38981
- "getEditionSheet",
38982
39175
  "getComposerSelection",
38983
39176
  "getCurrentTokens",
38984
39177
  "getTokenAtCursor",
@@ -40301,10 +40494,6 @@ css /* scss */ `
40301
40494
  .o-bottom-bar-arrows {
40302
40495
  .o-bottom-bar-arrow {
40303
40496
  cursor: pointer;
40304
- &.o-disabled {
40305
- opacity: 0.4;
40306
- cursor: default;
40307
- }
40308
40497
  &:hover:not([class*="o-disabled"]) {
40309
40498
  .o-icon {
40310
40499
  opacity: 0.9;
@@ -41142,11 +41331,6 @@ css /* scss */ `
41142
41331
  margin: 0 6px;
41143
41332
  }
41144
41333
 
41145
- .o-disabled {
41146
- opacity: 0.6;
41147
- cursor: default;
41148
- }
41149
-
41150
41334
  .o-dropdown {
41151
41335
  position: relative;
41152
41336
  display: flex;
@@ -41521,10 +41705,12 @@ css /* scss */ `
41521
41705
  button {
41522
41706
  color: #333;
41523
41707
  }
41524
-
41525
- * {
41526
- font-family: "Roboto", "RobotoDraft", Helvetica, Arial, sans-serif;
41708
+ .o-disabled {
41709
+ opacity: 0.4;
41710
+ pointer: default;
41711
+ pointer-events: none;
41527
41712
  }
41713
+
41528
41714
  &,
41529
41715
  *,
41530
41716
  *:before,
@@ -44893,6 +45079,13 @@ class Model extends EventBus {
44893
45079
  * Internal status of the model. Important for command handling coordination
44894
45080
  */
44895
45081
  this.status = 0 /* Status.Ready */;
45082
+ /**
45083
+ * Check if a command can be dispatched, and returns a DispatchResult object with the possible
45084
+ * reasons the dispatch failed.
45085
+ */
45086
+ this.canDispatch = (type, payload) => {
45087
+ return this.checkDispatchAllowed({ ...payload, type });
45088
+ };
44896
45089
  /**
44897
45090
  * The dispatch method is the only entry point to manipulate data in the model.
44898
45091
  * This is through this method that commands are dispatched most of the time
@@ -45036,7 +45229,10 @@ class Model extends EventBus {
45036
45229
  owl.markRaw(this);
45037
45230
  }
45038
45231
  get handlers() {
45039
- return [this.range, ...this.corePlugins, ...this.allUIPlugins, this.history];
45232
+ return [...this.coreHandlers, ...this.allUIPlugins, this.history];
45233
+ }
45234
+ get coreHandlers() {
45235
+ return [this.range, ...this.corePlugins];
45040
45236
  }
45041
45237
  get allUIPlugins() {
45042
45238
  return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
@@ -45164,6 +45360,16 @@ class Model extends EventBus {
45164
45360
  * Check if the given command is allowed by all the plugins and the history.
45165
45361
  */
45166
45362
  checkDispatchAllowed(command) {
45363
+ if (isCoreCommand(command)) {
45364
+ return this.checkDispatchAllowedCoreCommand(command);
45365
+ }
45366
+ return this.checkDispatchAllowedLocalCommand(command);
45367
+ }
45368
+ checkDispatchAllowedCoreCommand(command) {
45369
+ const results = this.coreHandlers.map((handler) => handler.allowDispatch(command));
45370
+ return new DispatchResult(results.flat());
45371
+ }
45372
+ checkDispatchAllowedLocalCommand(command) {
45167
45373
  const results = this.handlers.map((handler) => handler.allowDispatch(command));
45168
45374
  return new DispatchResult(results.flat());
45169
45375
  }
@@ -45392,6 +45598,6 @@ exports.setTranslationMethod = setTranslationMethod;
45392
45598
  exports.tokenize = tokenize;
45393
45599
 
45394
45600
 
45395
- __info__.version = '16.3.0-alpha.2';
45396
- __info__.date = '2023-03-23T11:36:58.645Z';
45397
- __info__.hash = '59f25bb';
45601
+ __info__.version = '16.3.0-alpha.3';
45602
+ __info__.date = '2023-04-13T08:30:41.165Z';
45603
+ __info__.hash = '573e679';