@odoo/o-spreadsheet 16.3.0-alpha.1 → 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,8 +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
- const FIGURE_BORDER_SIZE = 1;
229
227
  // Fonts
230
228
  const DEFAULT_FONT_WEIGHT = "400";
231
229
  const DEFAULT_FONT_SIZE = 10;
@@ -235,7 +233,7 @@
235
233
  const DEFAULT_BORDER_DESC = ["thin", "#000"];
236
234
  const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
237
235
  // DateTimeRegex
238
- const DATETIME_FORMAT = /[ymd:]/;
236
+ const DATETIME_FORMAT = /[ymdhs:]/;
239
237
  // Ranges
240
238
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
241
239
  // Max Number of history steps kept in memory
@@ -245,6 +243,8 @@
245
243
  // Figure
246
244
  const DEFAULT_FIGURE_HEIGHT = 335;
247
245
  const DEFAULT_FIGURE_WIDTH = 536;
246
+ const FIGURE_BORDER_WIDTH = 1;
247
+ const MIN_FIG_SIZE = 80;
248
248
  // Chart
249
249
  const MAX_CHAR_LABEL = 20;
250
250
  const FIGURE_ID_SPLITTER = "??";
@@ -255,7 +255,6 @@
255
255
  const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#00A04A";
256
256
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#DC6965";
257
257
  const LINE_FILL_TRANSPARENCY = 0.4;
258
- const MIN_FIG_SIZE = 80;
259
258
  // session
260
259
  const DEBOUNCE_TIME = 200;
261
260
  const MESSAGE_VERSION = 1;
@@ -277,7 +276,8 @@
277
276
  ComponentsImportance[ComponentsImportance["TopBarComposer"] = 21] = "TopBarComposer";
278
277
  ComponentsImportance[ComponentsImportance["IconPicker"] = 25] = "IconPicker";
279
278
  ComponentsImportance[ComponentsImportance["Popover"] = 30] = "Popover";
280
- ComponentsImportance[ComponentsImportance["ChartAnchor"] = 1000] = "ChartAnchor";
279
+ ComponentsImportance[ComponentsImportance["FigureAnchor"] = 1000] = "FigureAnchor";
280
+ ComponentsImportance[ComponentsImportance["FigureSnapLine"] = 1001] = "FigureSnapLine";
281
281
  })(ComponentsImportance || (ComponentsImportance = {}));
282
282
  const DEFAULT_SHEETVIEW_SIZE = 1000;
283
283
  const MAXIMAL_FREEZABLE_RATIO = 0.85;
@@ -714,7 +714,7 @@
714
714
  * '\w' captures [0-9][a-z][A-Z] and _.
715
715
  * @param sheetName Name of the sheet
716
716
  */
717
- function getComposerSheetName(sheetName) {
717
+ function getCanonicalSheetName(sheetName) {
718
718
  var _a;
719
719
  if (((_a = sheetName.match(/\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {
720
720
  sheetName = `'${sheetName}'`;
@@ -735,8 +735,9 @@
735
735
  return DEFAULT_CELL_HEIGHT;
736
736
  }
737
737
  const fontSize = computeTextFontSizeInPixels(cell.style);
738
- const multiLineText = cell.content.split(NEWLINE);
739
- 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;
740
741
  }
741
742
  function computeTextWidth(context, text, style) {
742
743
  context.save();
@@ -1088,6 +1089,8 @@
1088
1089
  return text;
1089
1090
  }
1090
1091
 
1092
+ const RBA_REGEX = /rgba?\(|\s+|\)/gi;
1093
+ const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/g;
1091
1094
  const colors$1 = [
1092
1095
  "#eb6d00",
1093
1096
  "#0074d9",
@@ -1113,7 +1116,10 @@
1113
1116
  }
1114
1117
  /**
1115
1118
  * Converts any CSS color value to a standardized hex6 value.
1116
- * 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]
1117
1123
  *
1118
1124
  * toHex("#ABC")
1119
1125
  * >> "#AABBCC"
@@ -1124,27 +1130,31 @@
1124
1130
  * toHex("rgb(30, 80, 16)")
1125
1131
  * >> "#1E5010"
1126
1132
  *
1133
+ * * toHex("rgb(30, 80, 16, 0.5)")
1134
+ * >> "#1E501080"
1135
+ *
1127
1136
  */
1128
1137
  function toHex(color) {
1129
- if (color.includes("rgba")) {
1130
- throw new Error(`rgba() conversion currently not supported: ${color}`);
1131
- }
1132
- if (color.includes("rgb")) {
1133
- return rgbToHex6(color);
1138
+ let hexColor = color;
1139
+ if (color.startsWith("rgb")) {
1140
+ hexColor = rgbaStringToHex(color);
1134
1141
  }
1135
- color = color.replace("#", "").toUpperCase();
1136
- if (color.length === 3 || color.length === 4) {
1137
- 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}`;
1138
1148
  }
1139
- if (color.replace(/[a-f0-9]/gi, "") !== "") {
1140
- throw new Error("invalid color");
1149
+ if (!hexColor.match(HEX_MATCH)) {
1150
+ throw new Error(`invalid color input: ${color}`);
1141
1151
  }
1142
- return "#" + color;
1152
+ return hexColor;
1143
1153
  }
1144
1154
  function isColorValid(color) {
1145
1155
  try {
1146
- const { r, g, b, a } = colorToRGBA(color);
1147
- return (isColorValueValid(r) && isColorValueValid(g) && isColorValueValid(b) && isColorValueValid(a));
1156
+ toHex(color);
1157
+ return true;
1148
1158
  }
1149
1159
  catch (error) {
1150
1160
  return false;
@@ -1177,15 +1187,29 @@
1177
1187
  /**
1178
1188
  * Convert a CSS rgb color string to a standardized hex6 color value.
1179
1189
  *
1180
- * rgbToHex6("rgb(30, 80, 16)")
1190
+ * rgbaStringToHex("rgb(30, 80, 16)")
1181
1191
  * >> "#1E5010"
1192
+ *
1193
+ * rgbaStringToHex("rgba(30, 80, 16, 0.5)")
1194
+ * >> "#1E501080"
1195
+ *
1196
+ * DOES NOT SUPPORT NON INTEGER RGB VALUES
1182
1197
  */
1183
- function rgbToHex6(color) {
1184
- return ("#" +
1185
- concat(color
1186
- .slice(4, -1)
1187
- .split(",")
1188
- .map((valueString) => parseInt(valueString, 10).toString(16).padStart(2, "0"))).toUpperCase());
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();
1189
1213
  }
1190
1214
  /**
1191
1215
  * RGBA to HEX representation (#RRGGBBAA).
@@ -1207,7 +1231,7 @@
1207
1231
  a = "0" + a;
1208
1232
  if (a === "ff")
1209
1233
  a = "";
1210
- return "#" + r + g + b + a;
1234
+ return ("#" + r + g + b + a).toUpperCase();
1211
1235
  }
1212
1236
  /**
1213
1237
  * Color string to RGBA representation
@@ -1328,6 +1352,9 @@
1328
1352
  s = +(s * 100).toFixed(1);
1329
1353
  l = +(l * 100).toFixed(1);
1330
1354
  return { a: rgba.a, h, s, l };
1355
+ }
1356
+ function isSameColor(color1, color2) {
1357
+ return isColorValid(color1) && isColorValid(color2) && toHex(color1) === toHex(color2);
1331
1358
  }
1332
1359
 
1333
1360
  /** Reference of a cell (eg. A1, $B$5) */
@@ -1479,7 +1506,7 @@
1479
1506
  * formula, commas are used to separate arguments
1480
1507
  * - it does not support % symbol, in formulas % is an operator
1481
1508
  */
1482
- const formulaNumberRegexp = /^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+/;
1509
+ const formulaNumberRegexp = /(^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+)(?!\w|!)/;
1483
1510
  const pIntegerAndDecimals = "(\\d+(,\\d{3,})*(\\.\\d*)?)"; // pattern that match integer number with or without decimal digits
1484
1511
  const pOnlyDecimals = "(\\.\\d+)"; // pattern that match only expression with decimal digits
1485
1512
  const pScientificFormat = "(e(\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -3052,6 +3079,9 @@
3052
3079
  }
3053
3080
  return attributes;
3054
3081
  }
3082
+ /**
3083
+ * Transform CSS properties into a CSS string.
3084
+ */
3055
3085
  function cssPropertiesToCss(attributes) {
3056
3086
  const str = Object.entries(attributes)
3057
3087
  .filter(([attName, attValue]) => attValue !== undefined)
@@ -3085,6 +3115,7 @@
3085
3115
  const LINE_HEIGHT = 1.2;
3086
3116
  css /* scss */ `
3087
3117
  div.o-scorecard {
3118
+ font-family: ${DEFAULT_FONT};
3088
3119
  user-select: none;
3089
3120
  background-color: white;
3090
3121
  display: flex;
@@ -5072,6 +5103,10 @@
5072
5103
  function rectIntersection(rect1, rect2) {
5073
5104
  return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
5074
5105
  }
5106
+ /** Compute the union of the rectangles, ie. the smallest rectangle that contain them all */
5107
+ function rectUnion(...rects) {
5108
+ return zoneToRect(union(...rects.map(rectToZone)));
5109
+ }
5075
5110
  function rectToZone(rect) {
5076
5111
  return {
5077
5112
  left: rect.x,
@@ -5368,24 +5403,30 @@
5368
5403
  }
5369
5404
  });
5370
5405
  }
5371
- get visibleMenuItems() {
5372
- 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;
5373
5424
  }
5374
5425
  get subMenuPosition() {
5375
5426
  const position = Object.assign({}, this.subMenu.position);
5376
5427
  position.y -= this.subMenu.scrollOffset || 0;
5377
5428
  return position;
5378
5429
  }
5379
- get menuHeight() {
5380
- const menuItems = this.visibleMenuItems;
5381
- let menuItemsHeight = this.getMenuItemsHeight(menuItems);
5382
- // We don't display separator at the end of a menu
5383
- if (menuItems[menuItems.length - 1].separator) {
5384
- menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
5385
- }
5386
- const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
5387
- return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
5388
- }
5389
5430
  get popoverProps() {
5390
5431
  const isRoot = this.props.depth === 1;
5391
5432
  return {
@@ -5399,7 +5440,6 @@
5399
5440
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
5400
5441
  onPopoverHidden: () => this.closeSubMenu(),
5401
5442
  onPopoverMoved: () => this.closeSubMenu(),
5402
- maxHeight: this.menuHeight,
5403
5443
  };
5404
5444
  }
5405
5445
  getColor(menu) {
@@ -5415,14 +5455,6 @@
5415
5455
  this.closeSubMenu();
5416
5456
  this.props.onClose();
5417
5457
  }
5418
- /**
5419
- * Return the number of pixels between the top of the menu
5420
- * and the menu item at a given index.
5421
- */
5422
- subMenuVerticalPosition(menuIndex) {
5423
- const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
5424
- return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
5425
- }
5426
5458
  onExternalClick(ev) {
5427
5459
  // Don't close a root menu when clicked to open the submenus.
5428
5460
  const el = this.menuRef.el;
@@ -5432,10 +5464,6 @@
5432
5464
  ev.closedMenuId = this.props.menuId;
5433
5465
  this.close();
5434
5466
  }
5435
- getMenuItemsHeight(menuItems) {
5436
- const numberOfSeparators = menuItems.filter((m) => m.separator).length;
5437
- return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
5438
- }
5439
5467
  getName(menu) {
5440
5468
  return menu.name(this.env);
5441
5469
  }
@@ -5455,8 +5483,11 @@
5455
5483
  * If the given menu is not disabled, open it's submenu at the
5456
5484
  * correct position according to available surrounding space.
5457
5485
  */
5458
- openSubMenu(menu, menuIndex) {
5459
- 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;
5460
5491
  this.subMenu.position = {
5461
5492
  x: this.position.x + this.props.depth * MENU_WIDTH,
5462
5493
  y: y - (this.subMenu.scrollOffset || 0),
@@ -5473,20 +5504,20 @@
5473
5504
  this.subMenu.isOpen = false;
5474
5505
  this.subMenu.parentMenu = undefined;
5475
5506
  }
5476
- onClickMenu(menu, menuIndex) {
5507
+ onClickMenu(menu, menuIndex, ev) {
5477
5508
  if (this.isEnabled(menu)) {
5478
5509
  if (this.isRoot(menu)) {
5479
- this.openSubMenu(menu, menuIndex);
5510
+ this.openSubMenu(menu, menuIndex, ev);
5480
5511
  }
5481
5512
  else {
5482
5513
  this.activateMenu(menu);
5483
5514
  }
5484
5515
  }
5485
5516
  }
5486
- onMouseOver(menu, position) {
5517
+ onMouseOver(menu, position, ev) {
5487
5518
  if (menu.isEnabled(this.env)) {
5488
5519
  if (this.isRoot(menu)) {
5489
- this.openSubMenu(menu, position);
5520
+ this.openSubMenu(menu, position, ev);
5490
5521
  }
5491
5522
  else {
5492
5523
  this.closeSubMenu();
@@ -6063,8 +6094,8 @@
6063
6094
  const dataSets = [];
6064
6095
  for (const sheetXC of dataSetsString) {
6065
6096
  const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
6066
- const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName } = dataRange;
6067
- if (invalidSheetName) {
6097
+ const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
6098
+ if (invalidSheetName || invalidXc) {
6068
6099
  continue;
6069
6100
  }
6070
6101
  // It's a rectangle. We treat all columns (arbitrary) as different data series.
@@ -6089,14 +6120,8 @@
6089
6120
  : undefined));
6090
6121
  }
6091
6122
  }
6092
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6093
- // A single cell. If it's only the title, the dataset is not added.
6094
- if (!dataSetsHaveTitle) {
6095
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6096
- }
6097
- }
6098
6123
  else {
6099
- /* 1 row or 1 column */
6124
+ /* 1 cell, 1 row or 1 column */
6100
6125
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6101
6126
  ? {
6102
6127
  top: zone.top,
@@ -6150,6 +6175,18 @@
6150
6175
  range: getters.getRangeString(dataRange, "forceSheetReference"),
6151
6176
  };
6152
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
+ }
6153
6190
  /**
6154
6191
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
6155
6192
  * with an executed command
@@ -6234,6 +6271,20 @@
6234
6271
  }
6235
6272
  return 0 /* CommandResult.Success */;
6236
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
+ }
6237
6288
  // ---------------------------------------------------------------------------
6238
6289
  // Scorecard
6239
6290
  // ---------------------------------------------------------------------------
@@ -6603,6 +6654,7 @@
6603
6654
  this.legendPosition = definition.legendPosition;
6604
6655
  this.stacked = definition.stacked;
6605
6656
  this.aggregated = definition.aggregated;
6657
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
6606
6658
  }
6607
6659
  static transformDefinition(definition, executed) {
6608
6660
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -6670,11 +6722,13 @@
6670
6722
  const dataSets = this.dataSets
6671
6723
  .map((ds) => toExcelDataset(this.getters, ds))
6672
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));
6673
6726
  return {
6674
6727
  ...this.getDefinition(),
6675
6728
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6676
6729
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6677
6730
  dataSets,
6731
+ labelRange,
6678
6732
  };
6679
6733
  }
6680
6734
  updateRanges(applyChange) {
@@ -6742,6 +6796,11 @@
6742
6796
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
6743
6797
  let labels = labelValues.formattedValues;
6744
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
+ }
6745
6804
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
6746
6805
  if (chart.aggregated) {
6747
6806
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7164,6 +7223,7 @@
7164
7223
  this.labelsAsText = definition.labelsAsText;
7165
7224
  this.stacked = definition.stacked;
7166
7225
  this.aggregated = definition.aggregated;
7226
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7167
7227
  }
7168
7228
  static validateChartDefinition(validator, definition) {
7169
7229
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -7231,11 +7291,13 @@
7231
7291
  const dataSets = this.dataSets
7232
7292
  .map((ds) => toExcelDataset(this.getters, ds))
7233
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));
7234
7295
  return {
7235
7296
  ...this.getDefinition(),
7236
7297
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7237
7298
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7238
7299
  dataSets,
7300
+ labelRange,
7239
7301
  };
7240
7302
  }
7241
7303
  copyForSheetId(sheetId) {
@@ -7373,6 +7435,11 @@
7373
7435
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7374
7436
  let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
7375
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
+ }
7376
7443
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7377
7444
  if (axisType === "time") {
7378
7445
  ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
@@ -7431,6 +7498,7 @@
7431
7498
  this.background = definition.background;
7432
7499
  this.legendPosition = definition.legendPosition;
7433
7500
  this.aggregated = definition.aggregated;
7501
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7434
7502
  }
7435
7503
  static transformDefinition(definition, executed) {
7436
7504
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -7494,12 +7562,14 @@
7494
7562
  const dataSets = this.dataSets
7495
7563
  .map((ds) => toExcelDataset(this.getters, ds))
7496
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));
7497
7566
  return {
7498
7567
  ...this.getDefinition(),
7499
7568
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7500
7569
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7501
7570
  verticalAxisPosition: "left",
7502
7571
  dataSets,
7572
+ labelRange,
7503
7573
  };
7504
7574
  }
7505
7575
  updateRanges(applyChange) {
@@ -7545,6 +7615,11 @@
7545
7615
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7546
7616
  let labels = labelValues.formattedValues;
7547
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
+ }
7548
7623
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7549
7624
  if (chart.aggregated) {
7550
7625
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7801,6 +7876,10 @@
7801
7876
  }
7802
7877
  `;
7803
7878
  class ChartFigure extends owl.Component {
7879
+ onDoubleClick() {
7880
+ this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
7881
+ this.env.openSidePanel("ChartPanel");
7882
+ }
7804
7883
  get chartType() {
7805
7884
  return this.env.model.getters.getChartType(this.props.figure.id);
7806
7885
  }
@@ -8176,7 +8255,6 @@
8176
8255
  labelRangeXc = zoneToXc({
8177
8256
  ...zone,
8178
8257
  right: zone.left,
8179
- top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
8180
8258
  });
8181
8259
  }
8182
8260
  // Only display legend for several datasets.
@@ -15094,7 +15172,7 @@
15094
15172
  cellReference = rowPart + colPart;
15095
15173
  }
15096
15174
  if (sheet !== undefined) {
15097
- return `${getComposerSheetName(toString(sheet))}!${cellReference}`;
15175
+ return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
15098
15176
  }
15099
15177
  return cellReference;
15100
15178
  },
@@ -16919,6 +16997,22 @@
16919
16997
  aggregated: ev.target.checked,
16920
16998
  });
16921
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
+ }
16922
17016
  }
16923
17017
  LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
16924
17018
  LineBarPieConfigPanel.components = { SelectionInput };
@@ -17166,6 +17260,9 @@
17166
17260
  top,
17167
17261
  });
17168
17262
  }
17263
+ isSameColor(color1, color2) {
17264
+ return isSameColor(color1, color2);
17265
+ }
17169
17266
  }
17170
17267
  ColorPicker.template = "o-spreadsheet-ColorPicker";
17171
17268
  ColorPicker.defaultProps = {
@@ -18838,7 +18935,7 @@
18838
18935
  box-sizing: content-box;
18839
18936
 
18840
18937
  .o-fig-anchor {
18841
- z-index: ${ComponentsImportance.ChartAnchor};
18938
+ z-index: ${ComponentsImportance.FigureAnchor};
18842
18939
  position: absolute;
18843
18940
  width: ${ANCHOR_SIZE}px;
18844
18941
  height: ${ANCHOR_SIZE}px;
@@ -19407,6 +19504,8 @@
19407
19504
  start = 0;
19408
19505
  if (end > textLength)
19409
19506
  end = textLength;
19507
+ if (start > textLength)
19508
+ start = textLength;
19410
19509
  }
19411
19510
  let startNode = this.findChildAtCharacterIndex(start);
19412
19511
  let endNode = this.findChildAtCharacterIndex(end);
@@ -19676,6 +19775,7 @@
19676
19775
  padding-right: 2px;
19677
19776
 
19678
19777
  box-sizing: border-box;
19778
+ font-family: ${DEFAULT_FONT};
19679
19779
 
19680
19780
  caret-color: black;
19681
19781
  padding-left: 3px;
@@ -19705,13 +19805,6 @@
19705
19805
  pointer-events: none;
19706
19806
  }
19707
19807
  }
19708
-
19709
- .o-topbar-composer .o-composer:empty:not(:focus)::before {
19710
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
19711
- content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'%3E%3Cpath d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='%23BDBDBD' /%3E%3C/svg%3E");
19712
- position: relative;
19713
- top: 20%;
19714
- }
19715
19808
  `;
19716
19809
  class Composer extends owl.Component {
19717
19810
  constructor() {
@@ -20125,7 +20218,7 @@
20125
20218
  const highlights = this.env.model.getters.getHighlights();
20126
20219
  const refSheet = sheetName
20127
20220
  ? this.env.model.getters.getSheetIdByName(sheetName)
20128
- : this.env.model.getters.getEditionSheet();
20221
+ : this.env.model.getters.getCurrentEditedCell().sheetId;
20129
20222
  const highlight = highlights.find((highlight) => {
20130
20223
  if (highlight.sheetId !== refSheet)
20131
20224
  return false;
@@ -20286,7 +20379,7 @@
20286
20379
  get cellReference() {
20287
20380
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20288
20381
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20289
- 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)}`;
20290
20383
  }
20291
20384
  get cellReferenceStyle() {
20292
20385
  const { x: left, y: top } = this.rect;
@@ -20439,6 +20532,269 @@
20439
20532
  gridPosition: { type: Object, optional: true },
20440
20533
  };
20441
20534
 
20535
+ /**
20536
+ * Transform a figure with coordinates from the model, to coordinates as they are shown on the screen,
20537
+ * taking into account the scroll position of the active sheet and the frozen panes.
20538
+ */
20539
+ function internalFigureToScreen(getters, fig) {
20540
+ return { ...fig, ...internalToScreenCoordinates(getters, { x: fig.x, y: fig.y }) };
20541
+ }
20542
+ /**
20543
+ * Transform a figure with coordinates as they are shown on the screen, to coordinates as they are in the model,
20544
+ * taking into account the scroll position of the active sheet and the frozen panes.
20545
+ *
20546
+ * Note that this isn't exactly the reverse operation as internalFigureToScreen, because the figure will always be on top
20547
+ * of the frozen panes.
20548
+ */
20549
+ function screenFigureToInternal(getters, fig) {
20550
+ return { ...fig, ...screenCoordinatesToInternal(getters, { x: fig.x, y: fig.y }) };
20551
+ }
20552
+ function internalToScreenCoordinates(getters, { x, y }) {
20553
+ const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
20554
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
20555
+ x = x < viewportX ? x : x - scrollX;
20556
+ y = y < viewportY ? y : y - scrollY;
20557
+ return { x, y };
20558
+ }
20559
+ function screenCoordinatesToInternal(getters, { x, y }) {
20560
+ const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
20561
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
20562
+ x = viewportX && x < viewportX ? x : x + scrollX;
20563
+ y = viewportY && y < viewportY ? y : y + scrollY;
20564
+ return { x, y };
20565
+ }
20566
+
20567
+ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { maxX, maxY }, { scrollX, scrollY }) {
20568
+ const minX = viewportX ? 0 : -scrollX;
20569
+ const minY = viewportY ? 0 : -scrollY;
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);
20574
+ return { ...initialFigure, x: newX, y: newY };
20575
+ }
20576
+ function clamp(value, min, max) {
20577
+ return Math.min(Math.max(value, min), max);
20578
+ }
20579
+ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
20580
+ let { x, y, width, height } = initialFigure;
20581
+ if (keepRatio && dirX != 0 && dirY != 0) {
20582
+ const deltaX = Math.min(dirX * (mouseInitialX - mouseX), initialFigure.width - minFigSize);
20583
+ const deltaY = Math.min(dirY * (mouseInitialY - mouseY), initialFigure.height - minFigSize);
20584
+ const fraction = Math.min(deltaX / initialFigure.width, deltaY / initialFigure.height);
20585
+ width = initialFigure.width * (1 - fraction);
20586
+ height = initialFigure.height * (1 - fraction);
20587
+ if (dirX < 0) {
20588
+ x = initialFigure.x + initialFigure.width * fraction;
20589
+ }
20590
+ if (dirY < 0) {
20591
+ y = initialFigure.y + initialFigure.height * fraction;
20592
+ }
20593
+ }
20594
+ else {
20595
+ const deltaX = Math.max(dirX * (mouseX - mouseInitialX), minFigSize - initialFigure.width);
20596
+ const deltaY = Math.max(dirY * (mouseY - mouseInitialY), minFigSize - initialFigure.height);
20597
+ width = initialFigure.width + deltaX;
20598
+ height = initialFigure.height + deltaY;
20599
+ if (dirX < 0) {
20600
+ x = initialFigure.x - deltaX;
20601
+ }
20602
+ if (dirY < 0) {
20603
+ y = initialFigure.y - deltaY;
20604
+ }
20605
+ }
20606
+ return { ...initialFigure, x, y, width, height };
20607
+ }
20608
+
20609
+ const SNAP_MARGIN = 5;
20610
+ /**
20611
+ * Try to snap the given figure to other figures when moving the figure, and return the snapped
20612
+ * figure and the possible snap lines, if any were found
20613
+ */
20614
+ function snapForMove(getters, figureToSnap, otherFigures) {
20615
+ const snappedFigure = { ...figureToSnap };
20616
+ const verticalSnapLine = getSnapLine(getters, snappedFigure, ["hCenter", "right", "left"], otherFigures, ["hCenter", "right", "left"]);
20617
+ const horizontalSnapLine = getSnapLine(getters, snappedFigure, ["vCenter", "bottom", "top"], otherFigures, ["vCenter", "bottom", "top"]);
20618
+ const { y: viewportY, x: viewportX } = getters.getMainViewportCoordinates();
20619
+ const { scrollY, scrollX } = getters.getActiveSheetScrollInfo();
20620
+ // If the snap cause the figure to change pane, we need to also apply the scroll as an offset
20621
+ if (horizontalSnapLine) {
20622
+ snappedFigure.y -= horizontalSnapLine.snapOffset;
20623
+ const isBaseFigFrozenY = figureToSnap.y < viewportY;
20624
+ const isSnappedFrozenY = snappedFigure.y < viewportY;
20625
+ if (isBaseFigFrozenY && !isSnappedFrozenY)
20626
+ snappedFigure.y += scrollY;
20627
+ else if (!isBaseFigFrozenY && isSnappedFrozenY)
20628
+ snappedFigure.y -= scrollY;
20629
+ }
20630
+ if (verticalSnapLine) {
20631
+ snappedFigure.x -= verticalSnapLine.snapOffset;
20632
+ const isBaseFigFrozenX = figureToSnap.x < viewportX;
20633
+ const isSnappedFrozenX = snappedFigure.x < viewportX;
20634
+ if (isBaseFigFrozenX && !isSnappedFrozenX)
20635
+ snappedFigure.x += scrollX;
20636
+ else if (!isBaseFigFrozenX && isSnappedFrozenX)
20637
+ snappedFigure.x -= scrollX;
20638
+ }
20639
+ return { snappedFigure, verticalSnapLine, horizontalSnapLine };
20640
+ }
20641
+ /**
20642
+ * Try to snap the given figure to the other figures when resizing the figure, and return the snapped
20643
+ * figure and the possible snap lines, if any were found
20644
+ */
20645
+ function snapForResize(getters, resizeDirX, resizeDirY, figureToSnap, otherFigures) {
20646
+ const snappedFigure = { ...figureToSnap };
20647
+ // Vertical snap line
20648
+ const verticalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirX === -1 ? "left" : "right"], otherFigures, ["right", "left"]);
20649
+ if (verticalSnapLine) {
20650
+ if (resizeDirX === 1) {
20651
+ snappedFigure.width -= verticalSnapLine.snapOffset;
20652
+ }
20653
+ else if (resizeDirX === -1) {
20654
+ snappedFigure.x -= verticalSnapLine.snapOffset;
20655
+ snappedFigure.width += verticalSnapLine.snapOffset;
20656
+ }
20657
+ }
20658
+ // Horizontal snap line
20659
+ const horizontalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirY === -1 ? "top" : "bottom"], otherFigures, ["bottom", "top"]);
20660
+ if (horizontalSnapLine) {
20661
+ if (resizeDirY === 1) {
20662
+ snappedFigure.height -= horizontalSnapLine.snapOffset;
20663
+ }
20664
+ else if (resizeDirY === -1) {
20665
+ snappedFigure.y -= horizontalSnapLine.snapOffset;
20666
+ snappedFigure.height += horizontalSnapLine.snapOffset;
20667
+ }
20668
+ }
20669
+ snappedFigure.x = Math.round(snappedFigure.x);
20670
+ snappedFigure.y = Math.round(snappedFigure.y);
20671
+ snappedFigure.height = Math.round(snappedFigure.height);
20672
+ snappedFigure.width = Math.round(snappedFigure.width);
20673
+ return { snappedFigure, verticalSnapLine, horizontalSnapLine };
20674
+ }
20675
+ /**
20676
+ * Get the position of snap axes for the given figure
20677
+ *
20678
+ * @param figure the figure
20679
+ * @param axesTypes the list of axis types to return the positions of
20680
+ */
20681
+ function getVisibleAxes(getters, figure, axesTypes) {
20682
+ const axes = axesTypes.map((axisType) => getAxis(figure, axisType));
20683
+ return axes
20684
+ .filter((axis) => isAxisVisible(getters, figure, axis))
20685
+ .map((axis) => getAxisScreenPosition(getters, figure, axis));
20686
+ }
20687
+ /**
20688
+ * We need two positions for the figure axis :
20689
+ * - the position (core) of the axis in the figure. This is used to know whether or not the axis is
20690
+ * displayed, or is hidden by the scroll/the frozen panes
20691
+ * - the position in the screen, which is used to find snap matches. We cannot use the core position for this,
20692
+ * because figures partially in frozen panes aren't displayed at their actual coordinates
20693
+ */
20694
+ function getAxisScreenPosition(getters, figure, figureAxis) {
20695
+ const screenFigure = internalFigureToScreen(getters, figure);
20696
+ return getAxis(screenFigure, figureAxis.axisType);
20697
+ }
20698
+ function isAxisVisible(getters, figure, axis) {
20699
+ const { x: mainViewportX, y: mainViewportY } = getters.getMainViewportCoordinates();
20700
+ const axisStartEndPositions = [];
20701
+ switch (axis.axisType) {
20702
+ case "top":
20703
+ case "bottom":
20704
+ case "vCenter":
20705
+ if (figure.y < mainViewportY)
20706
+ return true;
20707
+ axisStartEndPositions.push({ x: figure.x, y: axis.position });
20708
+ axisStartEndPositions.push({ x: figure.x + figure.width, y: axis.position });
20709
+ break;
20710
+ case "left":
20711
+ case "right":
20712
+ case "hCenter":
20713
+ if (figure.x < mainViewportX)
20714
+ return true;
20715
+ axisStartEndPositions.push({ x: axis.position, y: figure.y });
20716
+ axisStartEndPositions.push({ x: axis.position, y: figure.y + figure.height });
20717
+ break;
20718
+ }
20719
+ return axisStartEndPositions.some(getters.isPositionVisible);
20720
+ }
20721
+ /**
20722
+ * Get a snap line for the given figure, if the figure can snap to any other figure
20723
+ *
20724
+ * @param figureToSnap figure to get the snap line for
20725
+ * @param figAxesTypes figure axes of the given figure to be considered to find a snap line
20726
+ * @param otherFigures figures to match against the snapped figure to find a snap line
20727
+ * @param otherAxesTypes figure axes of the other figures to be considered to find a snap line
20728
+ */
20729
+ function getSnapLine(getters, figureToSnap, figAxesTypes, otherFigures, otherAxesTypes) {
20730
+ const axesOfFigure = getVisibleAxes(getters, figureToSnap, figAxesTypes);
20731
+ let closestMatch = undefined;
20732
+ for (const otherFigure of otherFigures) {
20733
+ const axesOfOtherFig = getVisibleAxes(getters, otherFigure, otherAxesTypes);
20734
+ for (const axisOfFigure of axesOfFigure) {
20735
+ for (const axisOfOtherFig of axesOfOtherFig) {
20736
+ if (!canSnap(axisOfFigure.position, axisOfOtherFig.position))
20737
+ continue;
20738
+ const snapOffset = axisOfFigure.position - axisOfOtherFig.position;
20739
+ if (closestMatch && snapOffset === closestMatch.snapOffset) {
20740
+ closestMatch.matchedFigIds.push(otherFigure.id);
20741
+ }
20742
+ else if (!closestMatch || Math.abs(snapOffset) <= Math.abs(closestMatch.snapOffset)) {
20743
+ closestMatch = {
20744
+ matchedFigIds: [otherFigure.id],
20745
+ snapOffset,
20746
+ snappedAxisType: axisOfFigure.axisType,
20747
+ position: axisOfOtherFig.position,
20748
+ };
20749
+ }
20750
+ }
20751
+ }
20752
+ }
20753
+ return closestMatch;
20754
+ }
20755
+ /** Check if two axes are close enough to snap */
20756
+ function canSnap(axisPosition1, axisPosition2) {
20757
+ return Math.abs(axisPosition1 - axisPosition2) <= SNAP_MARGIN;
20758
+ }
20759
+ function getAxis(fig, axisType) {
20760
+ let position = 0;
20761
+ switch (axisType) {
20762
+ case "top":
20763
+ position = fig.y;
20764
+ break;
20765
+ case "bottom":
20766
+ position = fig.y + fig.height - FIGURE_BORDER_WIDTH;
20767
+ break;
20768
+ case "vCenter":
20769
+ position = fig.y + Math.floor(fig.height / 2) - FIGURE_BORDER_WIDTH;
20770
+ break;
20771
+ case "left":
20772
+ position = fig.x;
20773
+ break;
20774
+ case "right":
20775
+ position = fig.x + fig.width - FIGURE_BORDER_WIDTH;
20776
+ break;
20777
+ case "hCenter":
20778
+ position = fig.x + Math.floor(fig.width / 2) - FIGURE_BORDER_WIDTH;
20779
+ break;
20780
+ }
20781
+ return { position, axisType: axisType };
20782
+ }
20783
+
20784
+ css /*SCSS*/ `
20785
+ .o-figure-snap-line {
20786
+ position: relative;
20787
+ z-index: ${ComponentsImportance.FigureSnapLine};
20788
+ &.vertical {
20789
+ width: 0px;
20790
+ border-left: 1px dashed black;
20791
+ }
20792
+ &.horizontal {
20793
+ border-top: 1px dashed black;
20794
+ height: 0px;
20795
+ }
20796
+ }
20797
+ `;
20442
20798
  /**
20443
20799
  * Each figure ⭐ is positioned inside a container `div` placed and sized
20444
20800
  * according to the split pane the figure is part of, or a separate container for the figure
@@ -20503,11 +20859,9 @@
20503
20859
  constructor() {
20504
20860
  super(...arguments);
20505
20861
  this.dnd = owl.useState({
20506
- figId: undefined,
20507
- x: 0,
20508
- y: 0,
20509
- width: 0,
20510
- height: 0,
20862
+ draggedFigure: undefined,
20863
+ horizontalSnap: undefined,
20864
+ verticalSnap: undefined,
20511
20865
  });
20512
20866
  }
20513
20867
  setup() {
@@ -20523,9 +20877,11 @@
20523
20877
  });
20524
20878
  }
20525
20879
  getVisibleFigures() {
20880
+ var _a;
20526
20881
  const visibleFigures = this.env.model.getters.getVisibleFigures();
20527
- if (this.dnd.figId && !visibleFigures.some((figure) => figure.id === this.dnd.figId)) {
20528
- visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.figId));
20882
+ if (this.dnd.draggedFigure &&
20883
+ !visibleFigures.some((figure) => { var _a; return figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); })) {
20884
+ visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), (_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id));
20529
20885
  }
20530
20886
  return visibleFigures;
20531
20887
  }
@@ -20548,7 +20904,7 @@
20548
20904
  });
20549
20905
  }
20550
20906
  }
20551
- if (this.dnd.figId) {
20907
+ if (this.dnd.draggedFigure) {
20552
20908
  containers.push({
20553
20909
  type: "dnd",
20554
20910
  figures: [this.getDndFigure()],
@@ -20559,19 +20915,25 @@
20559
20915
  return containers;
20560
20916
  }
20561
20917
  getContainerStyle(container) {
20562
- const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
20563
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20564
- const left = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
20565
- const width = viewWidth - left;
20566
- const top = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
20567
- const height = viewHeight - top;
20918
+ return this.rectToCss(this.getContainerRect(container));
20919
+ }
20920
+ rectToCss(rect) {
20568
20921
  return cssPropertiesToCss({
20569
- left: `${left}px`,
20570
- top: `${top}px`,
20571
- width: `${width}px`,
20572
- height: `${height}px`,
20922
+ left: `${rect.x}px`,
20923
+ top: `${rect.y}px`,
20924
+ width: `${rect.width}px`,
20925
+ height: `${rect.height}px`,
20573
20926
  });
20574
20927
  }
20928
+ getContainerRect(container) {
20929
+ const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
20930
+ const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20931
+ const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
20932
+ const width = viewWidth - x;
20933
+ const y = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
20934
+ const height = viewHeight - y;
20935
+ return { x, y, width, height };
20936
+ }
20575
20937
  getInverseViewportPositionStyle(container) {
20576
20938
  const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20577
20939
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
@@ -20583,8 +20945,9 @@
20583
20945
  });
20584
20946
  }
20585
20947
  getFigureContainer(figure) {
20948
+ var _a;
20586
20949
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20587
- if (figure.id === this.dnd.figId) {
20950
+ if (figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id)) {
20588
20951
  return "dnd";
20589
20952
  }
20590
20953
  else if (figure.x < viewportX && figure.y < viewportY) {
@@ -20610,133 +20973,141 @@
20610
20973
  return;
20611
20974
  }
20612
20975
  const sheetId = this.env.model.getters.getActiveSheetId();
20613
- const mouseInitialX = ev.clientX;
20614
- const mouseInitialY = ev.clientY;
20615
- const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
20616
- this.dnd.x = dndInitialX;
20617
- this.dnd.y = dndInitialY;
20618
- this.dnd.width = figure.width;
20619
- this.dnd.height = figure.height;
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
+ };
20981
+ const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
20982
+ const initialFig = { ...figure, x, y };
20620
20983
  const onMouseMove = (ev) => {
20621
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20622
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20623
- const minX = viewportX ? 0 : -scrollX;
20624
- const minY = viewportY ? 0 : -scrollY;
20625
- this.dnd.figId = figure.id;
20626
- const newX = ev.clientX;
20627
- let deltaX = newX - mouseInitialX;
20628
- this.dnd.x = Math.max(dndInitialX + deltaX, minX);
20629
- const newY = ev.clientY;
20630
- let deltaY = newY - mouseInitialY;
20631
- this.dnd.y = Math.max(dndInitialY + deltaY, minY);
20984
+ const getters = this.env.model.getters;
20985
+ const currentMousePosition = { x: ev.clientX, y: ev.clientY };
20986
+ const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), maxDimensions, getters.getActiveSheetScrollInfo());
20987
+ const otherFigures = this.getOtherFigures(figure.id);
20988
+ const internalDragged = screenFigureToInternal(getters, draggedFigure);
20989
+ const snapResult = snapForMove(getters, internalDragged, otherFigures);
20990
+ this.dnd.draggedFigure = internalFigureToScreen(getters, snapResult.snappedFigure);
20991
+ this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
20992
+ this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
20632
20993
  };
20633
20994
  const onMouseUp = (ev) => {
20634
- if (!this.dnd.figId) {
20995
+ if (!this.dnd.draggedFigure) {
20635
20996
  return;
20636
20997
  }
20637
- let { x, y } = this.screenCoordinatesToInternal(this.dnd);
20638
- this.dnd.figId = undefined;
20998
+ let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
20999
+ this.dnd.draggedFigure = undefined;
21000
+ this.dnd.horizontalSnap = undefined;
21001
+ this.dnd.verticalSnap = undefined;
20639
21002
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
20640
21003
  };
20641
21004
  startDnd(onMouseMove, onMouseUp);
20642
21005
  }
21006
+ /**
21007
+ * Initialize the resize of a figure with mouse movements
21008
+ *
21009
+ * @param dirX X direction of the resize. -1 : resize from the left border of the figure, 0 : no resize in X, 1 :
21010
+ * resize from the right border of the figure
21011
+ * @param dirY Y direction of the resize. -1 : resize from the top border of the figure, 0 : no resize in Y, 1 :
21012
+ * resize from the bottom border of the figure
21013
+ * @param ev Mouse Event
21014
+ */
20643
21015
  startResize(figure, dirX, dirY, ev) {
20644
21016
  ev.stopPropagation();
20645
- const initialX = ev.clientX;
20646
- const initialY = ev.clientY;
20647
- const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
20648
- this.dnd.x = dndInitialX;
20649
- this.dnd.y = dndInitialY;
20650
- this.dnd.width = figure.width;
20651
- this.dnd.height = figure.height;
21017
+ const initialMousePosition = { x: ev.clientX, y: ev.clientY };
21018
+ const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
21019
+ const initialFig = { ...figure, x, y };
20652
21020
  const keepRatio = figureRegistry.get(figure.tag).keepRatio || false;
20653
21021
  const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;
20654
- let onMouseMove;
20655
- if (keepRatio && dirX != 0 && dirY != 0) {
20656
- onMouseMove = (ev) => {
20657
- this.dnd.figId = figure.id;
20658
- const deltaX = Math.min(dirX * (initialX - ev.clientX), figure.width - minFigSize);
20659
- const deltaY = Math.min(dirY * (initialY - ev.clientY), figure.height - minFigSize);
20660
- const fraction = Math.min(deltaX / figure.width, deltaY / figure.height);
20661
- this.dnd.width = figure.width * (1 - fraction);
20662
- this.dnd.height = figure.height * (1 - fraction);
20663
- if (dirX < 0) {
20664
- this.dnd.x = dndInitialX + figure.width * fraction;
20665
- }
20666
- if (dirY < 0) {
20667
- this.dnd.y = dndInitialY + figure.height * fraction;
20668
- }
20669
- };
20670
- }
20671
- else {
20672
- onMouseMove = (ev) => {
20673
- this.dnd.figId = figure.id;
20674
- const deltaX = Math.max(dirX * (ev.clientX - initialX), minFigSize - figure.width);
20675
- const deltaY = Math.max(dirY * (ev.clientY - initialY), minFigSize - figure.height);
20676
- this.dnd.width = figure.width + deltaX;
20677
- this.dnd.height = figure.height + deltaY;
20678
- if (dirX < 0) {
20679
- this.dnd.x = dndInitialX - deltaX;
20680
- }
20681
- if (dirY < 0) {
20682
- this.dnd.y = dndInitialY - deltaY;
20683
- }
20684
- };
20685
- }
21022
+ const onMouseMove = (ev) => {
21023
+ const currentMousePosition = { x: ev.clientX, y: ev.clientY };
21024
+ const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize);
21025
+ const otherFigures = this.getOtherFigures(figure.id);
21026
+ const snapResult = snapForResize(this.env.model.getters, dirX, dirY, draggedFigure, otherFigures);
21027
+ this.dnd.draggedFigure = snapResult.snappedFigure;
21028
+ this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
21029
+ this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
21030
+ };
20686
21031
  const onMouseUp = (ev) => {
20687
- if (!this.dnd.figId) {
21032
+ if (!this.dnd.draggedFigure) {
20688
21033
  return;
20689
21034
  }
20690
- this.dnd.figId = undefined;
20691
- let { x, y } = this.screenCoordinatesToInternal(this.dnd);
21035
+ let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
20692
21036
  const update = { x, y };
20693
21037
  if (dirX) {
20694
- update.width = this.dnd.width;
21038
+ update.width = this.dnd.draggedFigure.width;
20695
21039
  }
20696
21040
  if (dirY) {
20697
- update.height = this.dnd.height;
21041
+ update.height = this.dnd.draggedFigure.height;
20698
21042
  }
20699
21043
  this.env.model.dispatch("UPDATE_FIGURE", {
20700
21044
  sheetId: this.env.model.getters.getActiveSheetId(),
20701
21045
  id: figure.id,
20702
21046
  ...update,
20703
21047
  });
21048
+ this.dnd.draggedFigure = undefined;
21049
+ this.dnd.horizontalSnap = undefined;
21050
+ this.dnd.verticalSnap = undefined;
20704
21051
  };
20705
21052
  startDnd(onMouseMove, onMouseUp);
20706
21053
  }
21054
+ getOtherFigures(figId) {
21055
+ return this.getVisibleFigures().filter((f) => f.id !== figId);
21056
+ }
20707
21057
  getDndFigure() {
20708
- const figure = this.getVisibleFigures().find((fig) => fig.id === this.dnd.figId);
21058
+ const figure = this.getVisibleFigures().find((fig) => { var _a; return fig.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); });
20709
21059
  if (!figure)
20710
21060
  throw new Error("Dnd figure not found");
20711
21061
  return {
20712
21062
  ...figure,
20713
- x: this.dnd.x,
20714
- y: this.dnd.y,
20715
- width: this.dnd.width,
20716
- height: this.dnd.height,
21063
+ ...this.dnd.draggedFigure,
20717
21064
  };
20718
21065
  }
20719
21066
  getFigureStyle(figure) {
20720
- if (figure.id !== this.dnd.figId)
21067
+ var _a;
21068
+ if (figure.id !== ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id))
20721
21069
  return "";
20722
21070
  return cssPropertiesToCss({
20723
21071
  opacity: "0.9",
20724
21072
  cursor: "grabbing",
20725
21073
  });
20726
21074
  }
20727
- internalToScreenCoordinates({ x, y }) {
20728
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20729
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20730
- x = x < viewportX ? x : x - scrollX;
20731
- y = y < viewportY ? y : y - scrollY;
20732
- return { x, y };
21075
+ getSnap(snapLine) {
21076
+ if (!snapLine || !this.dnd.draggedFigure)
21077
+ return undefined;
21078
+ const figureVisibleRects = snapLine.matchedFigIds
21079
+ .map((id) => this.getVisibleFigures().find((fig) => fig.id === id))
21080
+ .filter(isDefined$1)
21081
+ .map((fig) => {
21082
+ const figOnSCreen = internalFigureToScreen(this.env.model.getters, fig);
21083
+ const container = this.getFigureContainer(fig);
21084
+ return rectIntersection(figOnSCreen, this.getContainerRect(container));
21085
+ })
21086
+ .filter(isDefined$1);
21087
+ const containerRect = rectUnion(this.dnd.draggedFigure, ...figureVisibleRects);
21088
+ return {
21089
+ line: snapLine,
21090
+ containerStyle: this.rectToCss(containerRect),
21091
+ lineStyle: this.getSnapLineStyle(snapLine, containerRect),
21092
+ };
20733
21093
  }
20734
- screenCoordinatesToInternal({ x, y }) {
20735
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20736
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20737
- x = viewportX && x < viewportX ? x : x + scrollX;
20738
- y = viewportY && y < viewportY ? y : y + scrollY;
20739
- return { x, y };
21094
+ getSnapLineStyle(snapLine, containerRect) {
21095
+ if (!snapLine)
21096
+ return "";
21097
+ if (["top", "vCenter", "bottom"].includes(snapLine.snappedAxisType)) {
21098
+ return cssPropertiesToCss({
21099
+ top: `${snapLine.position - containerRect.y}px`,
21100
+ left: `0px`,
21101
+ width: `100%`,
21102
+ });
21103
+ }
21104
+ else {
21105
+ return cssPropertiesToCss({
21106
+ top: `0px`,
21107
+ left: `${snapLine.position - containerRect.x}px`,
21108
+ height: `100%`,
21109
+ });
21110
+ }
20740
21111
  }
20741
21112
  }
20742
21113
  FiguresContainer.template = "o-spreadsheet-FiguresContainer";
@@ -20883,6 +21254,9 @@
20883
21254
  owl.onMounted(() => {
20884
21255
  resizeObserver.observe(this.gridOverlayEl);
20885
21256
  });
21257
+ owl.onWillUnmount(() => {
21258
+ resizeObserver.disconnect();
21259
+ });
20886
21260
  useTouchMove(this.gridOverlay, this.props.onGridMoved, () => {
20887
21261
  const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
20888
21262
  return scrollY > 0;
@@ -22303,6 +22677,10 @@
22303
22677
  }
22304
22678
  }
22305
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
+ }
22306
22684
  if (ev.data) {
22307
22685
  // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22308
22686
  // character
@@ -23151,6 +23529,70 @@
23151
23529
  });
23152
23530
  }
23153
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
+
23154
23596
  function convertBorders(data, warningManager) {
23155
23597
  const borderArray = data.borders.map((border) => {
23156
23598
  addBorderWarnings(border, warningManager);
@@ -23213,41 +23655,6 @@
23213
23655
  }
23214
23656
  return arrayToObject(formats, 1);
23215
23657
  }
23216
- /**
23217
- * Convert excel format to o_spreadsheet format
23218
- *
23219
- * Excel format are defined in openXML §18.8.31
23220
- */
23221
- function convertXlsxFormat(numFmtId, formats, warningManager) {
23222
- var _a, _b, _c;
23223
- if (numFmtId === 0) {
23224
- return undefined;
23225
- }
23226
- // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23227
- let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23228
- if (format) {
23229
- try {
23230
- let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23231
- convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23232
- convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23233
- // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23234
- const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23235
- const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23236
- if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23237
- throw new Error("Multiple escaped blocks in format");
23238
- }
23239
- convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23240
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore with of next char for align purposes. Not supported ATM
23241
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23242
- convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23243
- formatValue(0, convertedFormat);
23244
- return convertedFormat;
23245
- }
23246
- catch (e) { }
23247
- }
23248
- warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23249
- return undefined;
23250
- }
23251
23658
  // ---------------------------------------------------------------------------
23252
23659
  // Warnings
23253
23660
  // ---------------------------------------------------------------------------
@@ -23802,16 +24209,18 @@
23802
24209
  };
23803
24210
  }
23804
24211
  function convertChartData(chartData) {
23805
- var _a;
23806
- const labelRange = (_a = chartData.dataSets[0].label) === null || _a === void 0 ? void 0 : _a.replace(/\$/g, "");
23807
- 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));
23808
24217
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
23809
24218
  if (chartData.type === "pie") {
23810
24219
  dataSets.reverse();
23811
24220
  }
23812
24221
  return {
23813
24222
  dataSets,
23814
- dataSetsHaveTitle: false,
24223
+ dataSetsHaveTitle,
23815
24224
  labelRange,
23816
24225
  title: chartData.title || "",
23817
24226
  type: chartData.type,
@@ -23822,6 +24231,28 @@
23822
24231
  aggregated: false,
23823
24232
  labelsAsText: false,
23824
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;
23825
24256
  }
23826
24257
 
23827
24258
  /**
@@ -24889,6 +25320,7 @@
24889
25320
  title: chartTitle,
24890
25321
  type: CHART_TYPE_CONVERSION_MAP[chartType],
24891
25322
  dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
25323
+ labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
24892
25324
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
24893
25325
  default: "ffffff",
24894
25326
  }).asString(),
@@ -24908,7 +25340,7 @@
24908
25340
  extractChartDatasets(chartElement) {
24909
25341
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
24910
25342
  return {
24911
- label: this.extractChildTextContent(chartDataElement, "c:cat c:f"),
25343
+ label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
24912
25344
  range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
24913
25345
  };
24914
25346
  });
@@ -28825,6 +29257,39 @@
28825
29257
  case "DELETE_FIGURE":
28826
29258
  this.removeFigure(cmd.id, cmd.sheetId);
28827
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
+ }
28828
29293
  }
28829
29294
  }
28830
29295
  updateFigure(sheetId, figure) {
@@ -30239,6 +30704,7 @@
30239
30704
  }
30240
30705
  else if (range.zone[start] >= min && range.zone[end] <= max) {
30241
30706
  changeType = "REMOVE";
30707
+ newRange = range.clone({ ...this.getInvalidRange() });
30242
30708
  }
30243
30709
  else if (range.zone[start] <= max && range.zone[end] >= max) {
30244
30710
  const toRemove = max - range.zone[start] + 1;
@@ -30304,8 +30770,10 @@
30304
30770
  return { changeType: "NONE" };
30305
30771
  }
30306
30772
  const invalidSheetName = this.getters.getSheetName(cmd.sheetId);
30307
- const sheetId = "";
30308
- range = range.clone({ sheetId, invalidSheetName });
30773
+ range = range.clone({
30774
+ ...this.getInvalidRange(),
30775
+ invalidSheetName,
30776
+ });
30309
30777
  return { changeType: "REMOVE", range };
30310
30778
  }, cmd.sheetId);
30311
30779
  break;
@@ -30492,7 +30960,7 @@
30492
30960
  sheetName = rangeImpl.invalidSheetName;
30493
30961
  }
30494
30962
  else {
30495
- sheetName = getComposerSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30963
+ sheetName = getCanonicalSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30496
30964
  }
30497
30965
  }
30498
30966
  if (prefixSheet && !sheetName) {
@@ -30565,6 +31033,15 @@
30565
31033
  }
30566
31034
  return str;
30567
31035
  }
31036
+ getInvalidRange() {
31037
+ return {
31038
+ parts: [],
31039
+ prefixSheet: false,
31040
+ zone: { left: -1, top: -1, right: -1, bottom: -1 },
31041
+ sheetId: "",
31042
+ invalidXc: INCORRECT_RANGE_STRING,
31043
+ };
31044
+ }
30568
31045
  }
30569
31046
  RangeAdapter.getters = [
30570
31047
  "getRangeString",
@@ -31534,9 +32011,7 @@
31534
32011
  // remove duplicates first to check validity on a reduced
31535
32012
  // set of colors, then normalize to HEX and remove duplicates
31536
32013
  // again
31537
- [...new Set([...usedColors, ...this.customColors])]
31538
- .filter(isColorValid)
31539
- .map((c) => toHex(c).toLowerCase())),
32014
+ [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
31540
32015
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
31541
32016
  }
31542
32017
  getColorsFromCells(cells) {
@@ -31597,7 +32072,7 @@
31597
32072
  return [...chartsColors];
31598
32073
  }
31599
32074
  tryToAddColor(color) {
31600
- const formattedColor = toHex(color).toLowerCase();
32075
+ const formattedColor = toHex(color);
31601
32076
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
31602
32077
  this.customColors.add(formattedColor);
31603
32078
  }
@@ -32612,8 +33087,8 @@
32612
33087
  this.getters = getters;
32613
33088
  this.sheetId = sheetId;
32614
33089
  this.boundaries = boundaries;
32615
- this.width = sizeInGrid.width;
32616
- this.height = sizeInGrid.height;
33090
+ this.viewportWidth = sizeInGrid.width;
33091
+ this.viewportHeight = sizeInGrid.height;
32617
33092
  this.offsetScrollbarX = offsets.x;
32618
33093
  this.offsetScrollbarY = offsets.y;
32619
33094
  this.canScrollVertically = options.canScrollVertically;
@@ -32624,6 +33099,10 @@
32624
33099
  this.adjustViewportOffsetY();
32625
33100
  }
32626
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
+ */
32627
33106
  getMaxSize() {
32628
33107
  const lastCol = this.getters.findLastVisibleColRowIndex(this.sheetId, "COL", {
32629
33108
  first: this.boundaries.left,
@@ -32635,20 +33114,24 @@
32635
33114
  });
32636
33115
  const { end: lastColEnd, size: lastColSize } = this.getters.getColDimensions(this.sheetId, lastCol);
32637
33116
  const { end: lastRowEnd, size: lastRowSize } = this.getters.getRowDimensions(this.sheetId, lastRow);
32638
- const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.width, 0);
33117
+ const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.viewportWidth, 0);
32639
33118
  const leftColSize = this.getters.getColSize(this.sheetId, leftColIndex);
32640
- const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.height, 0);
33119
+ const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.viewportHeight, 0);
32641
33120
  const topRowSize = this.getters.getRowSize(this.sheetId, leftRowIndex);
32642
- const width = lastColEnd -
32643
- this.offsetCorrectionX +
32644
- (this.canScrollHorizontally
32645
- ? Math.max(DEFAULT_CELL_WIDTH, Math.min(leftColSize, this.width - lastColSize))
32646
- : 0);
32647
- const height = lastRowEnd -
32648
- this.offsetCorrectionY +
32649
- (this.canScrollVertically
32650
- ? Math.max(DEFAULT_CELL_HEIGHT + 5, Math.min(topRowSize, this.height - lastRowSize))
32651
- : 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
+ }
32652
33135
  return { width, height };
32653
33136
  }
32654
33137
  /**
@@ -32657,7 +33140,7 @@
32657
33140
  * It returns -1 if no column is found.
32658
33141
  */
32659
33142
  getColIndex(x, absolute = false) {
32660
- if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.width) {
33143
+ if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.viewportWidth) {
32661
33144
  return -1;
32662
33145
  }
32663
33146
  return this.searchHeaderIndex("COL", x - this.offsetCorrectionX, this.left, absolute);
@@ -32668,7 +33151,7 @@
32668
33151
  * It returns -1 if no row is found.
32669
33152
  */
32670
33153
  getRowIndex(y, absolute = false) {
32671
- if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.height) {
33154
+ if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.viewportHeight) {
32672
33155
  return -1;
32673
33156
  }
32674
33157
  return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY, this.top, absolute);
@@ -32695,7 +33178,7 @@
32695
33178
  adjustPositionX(col) {
32696
33179
  const sheetId = this.sheetId;
32697
33180
  const { start, end } = this.getters.getColDimensions(sheetId, col);
32698
- while (end > this.offsetX + this.offsetCorrectionX + this.width &&
33181
+ while (end > this.offsetX + this.offsetCorrectionX + this.viewportWidth &&
32699
33182
  this.offsetX + this.offsetCorrectionX < start) {
32700
33183
  this.offsetX = this.getters.getColDimensions(sheetId, this.left).end - this.offsetCorrectionX;
32701
33184
  this.offsetScrollbarX = this.offsetX;
@@ -32717,7 +33200,7 @@
32717
33200
  adjustPositionY(row) {
32718
33201
  const sheetId = this.sheetId;
32719
33202
  while (this.getters.getRowDimensions(sheetId, row).end >
32720
- this.offsetY + this.height + this.offsetCorrectionY &&
33203
+ this.offsetY + this.viewportHeight + this.offsetCorrectionY &&
32721
33204
  this.offsetY + this.offsetCorrectionY < this.getters.getRowDimensions(sheetId, row).start) {
32722
33205
  this.offsetY = this.getters.getRowDimensions(sheetId, this.top).end - this.offsetCorrectionY;
32723
33206
  this.offsetScrollbarY = this.offsetY;
@@ -32755,8 +33238,8 @@
32755
33238
  const x = this.getters.getColRowOffset("COL", this.zone.left, targetZone.left) +
32756
33239
  this.offsetCorrectionX;
32757
33240
  const y = this.getters.getColRowOffset("ROW", this.zone.top, targetZone.top) + this.offsetCorrectionY;
32758
- const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.width);
32759
- 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);
32760
33243
  return {
32761
33244
  x,
32762
33245
  y,
@@ -32819,12 +33302,12 @@
32819
33302
  adjustViewportOffsetX() {
32820
33303
  if (this.canScrollHorizontally) {
32821
33304
  const { width: viewportWidth } = this.getMaxSize();
32822
- if (this.width + this.offsetScrollbarX > viewportWidth) {
32823
- this.offsetScrollbarX = Math.max(0, viewportWidth - this.width);
33305
+ if (this.viewportWidth + this.offsetScrollbarX > viewportWidth) {
33306
+ this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
32824
33307
  }
32825
33308
  }
32826
33309
  this.left = this.getColIndex(this.offsetScrollbarX, true);
32827
- this.right = this.getColIndex(this.offsetScrollbarX + this.width, true);
33310
+ this.right = this.getColIndex(this.offsetScrollbarX + this.viewportWidth, true);
32828
33311
  if (this.right === -1) {
32829
33312
  this.right = this.boundaries.right;
32830
33313
  }
@@ -32836,12 +33319,12 @@
32836
33319
  adjustViewportOffsetY() {
32837
33320
  if (this.canScrollVertically) {
32838
33321
  const { height: paneHeight } = this.getMaxSize();
32839
- if (this.height + this.offsetScrollbarY > paneHeight) {
32840
- this.offsetScrollbarY = Math.max(0, paneHeight - this.height);
33322
+ if (this.viewportHeight + this.offsetScrollbarY > paneHeight) {
33323
+ this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
32841
33324
  }
32842
33325
  }
32843
33326
  this.top = this.getRowIndex(this.offsetScrollbarY, true);
32844
- this.bottom = this.getRowIndex(this.offsetScrollbarY + this.width, true);
33327
+ this.bottom = this.getRowIndex(this.offsetScrollbarY + this.viewportWidth, true);
32845
33328
  if (this.bottom === -1) {
32846
33329
  this.bottom = this.boundaries.bottom;
32847
33330
  }
@@ -32852,7 +33335,7 @@
32852
33335
  adjustViewportZoneX() {
32853
33336
  const sheetId = this.sheetId;
32854
33337
  this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
32855
- 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));
32856
33339
  if (this.right === -1) {
32857
33340
  this.right = this.getters.getNumberCols(sheetId) - 1;
32858
33341
  }
@@ -32865,7 +33348,7 @@
32865
33348
  adjustViewportZoneY() {
32866
33349
  const sheetId = this.sheetId;
32867
33350
  this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
32868
- 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));
32869
33352
  if (this.bottom === -1) {
32870
33353
  this.bottom = this.getters.getNumberRows(sheetId) - 1;
32871
33354
  }
@@ -33128,8 +33611,7 @@
33128
33611
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
33129
33612
  }
33130
33613
  /**
33131
- * Return the main viewport maximum size. That is the zone dimension
33132
- * with some bottom and right padding.
33614
+ * Return the main viewport maximum size relative to the client size.
33133
33615
  */
33134
33616
  getMainViewportRect() {
33135
33617
  const sheetId = this.getters.getActiveSheetId();
@@ -33145,8 +33627,8 @@
33145
33627
  const { width, height } = this.getMainViewportRect();
33146
33628
  const viewport = this.getMainInternalViewport(sheetId);
33147
33629
  return {
33148
- maxOffsetX: Math.max(0, width - viewport.width + 1),
33149
- 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),
33150
33632
  };
33151
33633
  }
33152
33634
  getColRowOffsetInViewport(dimension, referenceIndex, index) {
@@ -33461,6 +33943,20 @@
33461
33943
  }
33462
33944
  return result;
33463
33945
  }
33946
+ isPositionVisible(position) {
33947
+ const { scrollX, scrollY } = this.getters.getActiveSheetScrollInfo();
33948
+ const { x: mainViewportX, y: mainViewportY } = this.getters.getMainViewportCoordinates();
33949
+ const { width, height } = this.getters.getSheetViewDimension();
33950
+ if (position.x >= mainViewportX &&
33951
+ (position.x < mainViewportX + scrollX || position.x > width + scrollX + mainViewportX)) {
33952
+ return false;
33953
+ }
33954
+ if (position.y >= mainViewportY &&
33955
+ (position.y < mainViewportY + scrollY || position.y > height + scrollY + mainViewportY)) {
33956
+ return false;
33957
+ }
33958
+ return true;
33959
+ }
33464
33960
  getFrozenSheetViewRatio(sheetId) {
33465
33961
  const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
33466
33962
  const offsetCorrectionX = this.getters.getColDimensions(sheetId, xSplit).start;
@@ -33489,6 +33985,7 @@
33489
33985
  "getSheetViewVisibleCols",
33490
33986
  "getSheetViewVisibleRows",
33491
33987
  "getFrozenSheetViewRatio",
33988
+ "isPositionVisible",
33492
33989
  ];
33493
33990
 
33494
33991
  /**
@@ -35351,7 +35848,7 @@
35351
35848
  const inputSheetId = this.activeSheet;
35352
35849
  const sheetId = this.getters.getActiveSheetId();
35353
35850
  const sheetName = this.getters.getSheetName(sheetId);
35354
- this.add([sheetId === inputSheetId ? xc : `${getComposerSheetName(sheetName)}!${xc}`]);
35851
+ this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
35355
35852
  }
35356
35853
  handle(cmd) {
35357
35854
  switch (cmd.type) {
@@ -37598,13 +38095,19 @@
37598
38095
  */
37599
38096
  paste(target) {
37600
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;
37601
38105
  const position = {
37602
- x: this.getters.getColDimensions(sheetId, target[0].left).start,
37603
- 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),
37604
38108
  };
37605
- const size = { height: this.copiedFigure.height, width: this.copiedFigure.width };
37606
38109
  const newId = new UuidGenerator().uuidv4();
37607
- this.copiedFigureContent.paste(sheetId, newId, position, size);
38110
+ this.copiedFigureContent.paste(sheetId, newId, position, { height, width });
37608
38111
  if (this.operation === "CUT") {
37609
38112
  this.dispatch("DELETE_FIGURE", {
37610
38113
  sheetId: this.copiedFigureContent.sheetId,
@@ -37970,7 +38473,7 @@
37970
38473
  return token;
37971
38474
  const { xc, sheetName } = splitReference(token.value);
37972
38475
  const [left, right] = xc.split(":");
37973
- const sheetRef = sheetName ? `${getComposerSheetName(sheetName)}!` : "";
38476
+ const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
37974
38477
  const updatedLeft = getTokenNextReferenceType(left);
37975
38478
  const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
37976
38479
  return { ...token, value: sheetRef + updatedLeft + updatedRight };
@@ -38229,9 +38732,6 @@
38229
38732
  }
38230
38733
  return this.currentContent;
38231
38734
  }
38232
- getEditionSheet() {
38233
- return this.sheetId;
38234
- }
38235
38735
  getComposerSelection() {
38236
38736
  return {
38237
38737
  start: this.selectionStart,
@@ -38515,8 +39015,8 @@
38515
39015
  getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
38516
39016
  const sheetId = this.getters.getActiveSheetId();
38517
39017
  let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
38518
- if (this.getters.getEditionSheet() !== this.getters.getActiveSheetId()) {
38519
- 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()));
38520
39020
  selectedXc = `${sheetName}!${selectedXc}`;
38521
39021
  }
38522
39022
  return selectedXc;
@@ -38530,7 +39030,7 @@
38530
39030
  _fixedParts.pop();
38531
39031
  }
38532
39032
  const newRange = range.clone({ parts: _fixedParts });
38533
- return this.getters.getSelectionRangeString(newRange, this.getters.getEditionSheet());
39033
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
38534
39034
  }
38535
39035
  /**
38536
39036
  * Replace the current selection by a new text.
@@ -38563,7 +39063,7 @@
38563
39063
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38564
39064
  return;
38565
39065
  }
38566
- const editionSheetId = this.getters.getEditionSheet();
39066
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38567
39067
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
38568
39068
  const colorsToKeep = {};
38569
39069
  for (const xc of XCs) {
@@ -38592,7 +39092,7 @@
38592
39092
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38593
39093
  return [];
38594
39094
  }
38595
- const editionSheetId = this.getters.getEditionSheet();
39095
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38596
39096
  const rangeColor = (rangeString) => {
38597
39097
  const colorIndex = this.colorIndexByRange[rangeString];
38598
39098
  return colors$1[colorIndex % colors$1.length];
@@ -38610,7 +39110,7 @@
38610
39110
  * Return ranges currently referenced in the composer
38611
39111
  */
38612
39112
  getReferencedRanges() {
38613
- const editionSheetId = this.getters.getEditionSheet();
39113
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38614
39114
  return this.currentTokens
38615
39115
  .filter((token) => token.type === "REFERENCE")
38616
39116
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -38669,7 +39169,6 @@
38669
39169
  "isSelectingForComposer",
38670
39170
  "showSelectionIndicator",
38671
39171
  "getCurrentContent",
38672
- "getEditionSheet",
38673
39172
  "getComposerSelection",
38674
39173
  "getCurrentTokens",
38675
39174
  "getTokenAtCursor",
@@ -39992,10 +40491,6 @@
39992
40491
  .o-bottom-bar-arrows {
39993
40492
  .o-bottom-bar-arrow {
39994
40493
  cursor: pointer;
39995
- &.o-disabled {
39996
- opacity: 0.4;
39997
- cursor: default;
39998
- }
39999
40494
  &:hover:not([class*="o-disabled"]) {
40000
40495
  .o-icon {
40001
40496
  opacity: 0.9;
@@ -40588,6 +41083,11 @@
40588
41083
  margin-top: -1px;
40589
41084
  border: 1px solid;
40590
41085
  z-index: ${ComponentsImportance.TopBarComposer};
41086
+
41087
+ .o-composer:empty:not(:focus)::before {
41088
+ /* svg free of use from https://uxwing.com/formula-fx-icon/ */
41089
+ content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 -10 121.8 142.9' width='24' height='24' focusable='false'%3E%3Cpath d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='%23BDBDBD' /%3E%3C/svg%3E");
41090
+ }
40591
41091
  }
40592
41092
  `;
40593
41093
  class TopBarComposer extends owl.Component {
@@ -40828,11 +41328,6 @@
40828
41328
  margin: 0 6px;
40829
41329
  }
40830
41330
 
40831
- .o-disabled {
40832
- opacity: 0.6;
40833
- cursor: default;
40834
- }
40835
-
40836
41331
  .o-dropdown {
40837
41332
  position: relative;
40838
41333
  display: flex;
@@ -40994,6 +41489,7 @@
40994
41489
  this.state.menuState.parentMenu = menu;
40995
41490
  this.isSelectingMenu = true;
40996
41491
  this.openedEl = ev.target;
41492
+ this.env.model.dispatch("STOP_EDITION");
40997
41493
  }
40998
41494
  closeMenus() {
40999
41495
  this.state.activeTool = "";
@@ -41206,10 +41702,12 @@
41206
41702
  button {
41207
41703
  color: #333;
41208
41704
  }
41209
-
41210
- * {
41211
- font-family: "Roboto", "RobotoDraft", Helvetica, Arial, sans-serif;
41705
+ .o-disabled {
41706
+ opacity: 0.4;
41707
+ pointer: default;
41708
+ pointer-events: none;
41212
41709
  }
41710
+
41213
41711
  &,
41214
41712
  *,
41215
41713
  *:before,
@@ -41367,8 +41865,10 @@
41367
41865
  this.sidePanel.isOpen = true;
41368
41866
  }
41369
41867
  closeSidePanel() {
41868
+ var _a, _b;
41370
41869
  this.sidePanel.isOpen = false;
41371
41870
  this.focusGrid();
41871
+ (_b = (_a = this.sidePanel.panelProps) === null || _a === void 0 ? void 0 : _a.onCloseSidePanel) === null || _b === void 0 ? void 0 : _b.call(_a);
41372
41872
  }
41373
41873
  toggleSidePanel(panel, panelProps) {
41374
41874
  if (this.sidePanel.isOpen && panel === this.sidePanel.component) {
@@ -43807,7 +44307,7 @@
43807
44307
  if (currentPosition <= position && position < currentPosition + header.size) {
43808
44308
  return {
43809
44309
  index: parseInt(headerIndex),
43810
- offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
44310
+ offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
43811
44311
  };
43812
44312
  }
43813
44313
  else {
@@ -43816,7 +44316,7 @@
43816
44316
  }
43817
44317
  return {
43818
44318
  index: headers.length - 1,
43819
- offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
44319
+ offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
43820
44320
  };
43821
44321
  }
43822
44322
  function createChartDrawing(figure, sheet, chartRelId) {
@@ -44576,6 +45076,13 @@
44576
45076
  * Internal status of the model. Important for command handling coordination
44577
45077
  */
44578
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
+ };
44579
45086
  /**
44580
45087
  * The dispatch method is the only entry point to manipulate data in the model.
44581
45088
  * This is through this method that commands are dispatched most of the time
@@ -44719,7 +45226,10 @@
44719
45226
  owl.markRaw(this);
44720
45227
  }
44721
45228
  get handlers() {
44722
- 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];
44723
45233
  }
44724
45234
  get allUIPlugins() {
44725
45235
  return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
@@ -44847,6 +45357,16 @@
44847
45357
  * Check if the given command is allowed by all the plugins and the history.
44848
45358
  */
44849
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) {
44850
45370
  const results = this.handlers.map((handler) => handler.allowDispatch(command));
44851
45371
  return new DispatchResult(results.flat());
44852
45372
  }
@@ -45077,9 +45597,9 @@
45077
45597
  Object.defineProperty(exports, '__esModule', { value: true });
45078
45598
 
45079
45599
 
45080
- __info__.version = '16.3.0-alpha.1';
45081
- __info__.date = '2023-03-16T12:12:26.034Z';
45082
- __info__.hash = 'f41d64a';
45600
+ __info__.version = '16.3.0-alpha.3';
45601
+ __info__.date = '2023-04-13T08:30:41.165Z';
45602
+ __info__.hash = '573e679';
45083
45603
 
45084
45604
 
45085
45605
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);