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