@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.
@@ -113,88 +113,88 @@ const FILTERS_COLOR = "#188038";
113
113
  const BACKGROUND_HEADER_FILTER_COLOR = "#E6F4EA";
114
114
  const BACKGROUND_HEADER_SELECTED_FILTER_COLOR = "#CEEAD6";
115
115
  const SEPARATOR_COLOR = "#E0E2E4";
116
- // Color picker
116
+ // Color picker defaults as upper case HEX to match `toHex`helper
117
117
  const COLOR_PICKER_DEFAULTS = [
118
118
  "#000000",
119
119
  "#434343",
120
120
  "#666666",
121
121
  "#999999",
122
- "#b7b7b7",
123
- "#cccccc",
124
- "#d9d9d9",
125
- "#efefef",
126
- "#f3f3f3",
127
- "#ffffff",
122
+ "#B7B7B7",
123
+ "#CCCCCC",
124
+ "#D9D9D9",
125
+ "#EFEFEF",
126
+ "#F3F3F3",
127
+ "#FFFFFF",
128
128
  "#980000",
129
- "#ff0000",
130
- "#ff9900",
131
- "#ffff00",
132
- "#00ff00",
133
- "#00ffff",
134
- "#4a86e8",
135
- "#0000ff",
136
- "#9900ff",
137
- "#ff00ff",
138
- "#e6b8af",
139
- "#f4cccc",
140
- "#fce5cd",
141
- "#fff2cc",
142
- "#d9ead3",
143
- "#d0e0e3",
144
- "#c9daf8",
145
- "#cfe2f3",
146
- "#d9d2e9",
147
- "#ead1dc",
148
- "#dd7e6b",
149
- "#ea9999",
150
- "#f9cb9c",
151
- "#ffe599",
152
- "#b6d7a8",
153
- "#a2c4c9",
154
- "#a4c2f4",
155
- "#9fc5e8",
156
- "#b4a7d6",
157
- "#d5a6bd",
158
- "#cc4125",
159
- "#e06666",
160
- "#f6b26b",
161
- "#ffd966",
162
- "#93c47d",
163
- "#76a5af",
164
- "#6d9eeb",
165
- "#6fa8dc",
166
- "#8e7cc3",
167
- "#c27ba0",
168
- "#a61c00",
169
- "#cc0000",
170
- "#e69138",
171
- "#f1c232",
172
- "#6aa84f",
173
- "#45818e",
174
- "#3c78d8",
175
- "#3d85c6",
176
- "#674ea7",
177
- "#a64d79",
178
- "#85200c",
129
+ "#FF0000",
130
+ "#FF9900",
131
+ "#FFFF00",
132
+ "#00FF00",
133
+ "#00FFFF",
134
+ "#4A86E8",
135
+ "#0000FF",
136
+ "#9900FF",
137
+ "#FF00FF",
138
+ "#E6B8AF",
139
+ "#F4CCCC",
140
+ "#FCE5CD",
141
+ "#FFF2CC",
142
+ "#D9EAD3",
143
+ "#D0E0E3",
144
+ "#C9DAF8",
145
+ "#CFE2F3",
146
+ "#D9D2E9",
147
+ "#EAD1DC",
148
+ "#DD7E6B",
149
+ "#EA9999",
150
+ "#F9CB9C",
151
+ "#FFE599",
152
+ "#B6D7A8",
153
+ "#A2C4C9",
154
+ "#A4C2F4",
155
+ "#9FC5E8",
156
+ "#B4A7D6",
157
+ "#D5A6BD",
158
+ "#CC4125",
159
+ "#E06666",
160
+ "#F6B26B",
161
+ "#FFD966",
162
+ "#93C47D",
163
+ "#76A5AF",
164
+ "#6D9EEB",
165
+ "#6FA8DC",
166
+ "#8E7CC3",
167
+ "#C27BA0",
168
+ "#A61C00",
169
+ "#CC0000",
170
+ "#E69138",
171
+ "#F1C232",
172
+ "#6AA84F",
173
+ "#45818E",
174
+ "#3C78D8",
175
+ "#3D85C6",
176
+ "#674EA7",
177
+ "#A64D79",
178
+ "#85200C",
179
179
  "#990000",
180
- "#b45f06",
181
- "#bf9000",
182
- "#38761d",
183
- "#134f5c",
184
- "#1155cc",
185
- "#0b5394",
186
- "#351c75",
187
- "#741b47",
188
- "#5b0f00",
180
+ "#B45F06",
181
+ "#BF9000",
182
+ "#38761D",
183
+ "#134F5C",
184
+ "#1155CC",
185
+ "#0B5394",
186
+ "#351C75",
187
+ "#741B47",
188
+ "#5B0F00",
189
189
  "#660000",
190
- "#783f04",
191
- "#7f6000",
192
- "#274e13",
193
- "#0c343d",
194
- "#1c4587",
190
+ "#783F04",
191
+ "#7F6000",
192
+ "#274E13",
193
+ "#0C343D",
194
+ "#1C4587",
195
195
  "#073763",
196
- "#20124d",
197
- "#4c1130",
196
+ "#20124D",
197
+ "#4C1130",
198
198
  ];
199
199
  // Dimensions
200
200
  const MIN_ROW_HEIGHT = 10;
@@ -223,8 +223,6 @@ const MENU_VERTICAL_PADDING = 8;
223
223
  const MENU_ITEM_HEIGHT = 28;
224
224
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
225
225
  const MENU_SEPARATOR_PADDING = 5;
226
- const MENU_SEPARATOR_HEIGHT = MENU_SEPARATOR_BORDER_WIDTH + 2 * MENU_SEPARATOR_PADDING;
227
- const FIGURE_BORDER_SIZE = 1;
228
226
  // Fonts
229
227
  const DEFAULT_FONT_WEIGHT = "400";
230
228
  const DEFAULT_FONT_SIZE = 10;
@@ -234,7 +232,7 @@ const DEFAULT_FONT = "'Roboto', arial";
234
232
  const DEFAULT_BORDER_DESC = ["thin", "#000"];
235
233
  const DEFAULT_FILTER_BORDER_DESC = ["thin", FILTERS_COLOR];
236
234
  // DateTimeRegex
237
- const DATETIME_FORMAT = /[ymd:]/;
235
+ const DATETIME_FORMAT = /[ymdhs:]/;
238
236
  // Ranges
239
237
  const INCORRECT_RANGE_STRING = CellErrorType.InvalidReference;
240
238
  // Max Number of history steps kept in memory
@@ -244,6 +242,8 @@ const DEFAULT_REVISION_ID = "START_REVISION";
244
242
  // Figure
245
243
  const DEFAULT_FIGURE_HEIGHT = 335;
246
244
  const DEFAULT_FIGURE_WIDTH = 536;
245
+ const FIGURE_BORDER_WIDTH = 1;
246
+ const MIN_FIG_SIZE = 80;
247
247
  // Chart
248
248
  const MAX_CHAR_LABEL = 20;
249
249
  const FIGURE_ID_SPLITTER = "??";
@@ -254,7 +254,6 @@ const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
254
254
  const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#00A04A";
255
255
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#DC6965";
256
256
  const LINE_FILL_TRANSPARENCY = 0.4;
257
- const MIN_FIG_SIZE = 80;
258
257
  // session
259
258
  const DEBOUNCE_TIME = 200;
260
259
  const MESSAGE_VERSION = 1;
@@ -276,7 +275,8 @@ var ComponentsImportance;
276
275
  ComponentsImportance[ComponentsImportance["TopBarComposer"] = 21] = "TopBarComposer";
277
276
  ComponentsImportance[ComponentsImportance["IconPicker"] = 25] = "IconPicker";
278
277
  ComponentsImportance[ComponentsImportance["Popover"] = 30] = "Popover";
279
- ComponentsImportance[ComponentsImportance["ChartAnchor"] = 1000] = "ChartAnchor";
278
+ ComponentsImportance[ComponentsImportance["FigureAnchor"] = 1000] = "FigureAnchor";
279
+ ComponentsImportance[ComponentsImportance["FigureSnapLine"] = 1001] = "FigureSnapLine";
280
280
  })(ComponentsImportance || (ComponentsImportance = {}));
281
281
  const DEFAULT_SHEETVIEW_SIZE = 1000;
282
282
  const MAXIMAL_FREEZABLE_RATIO = 0.85;
@@ -713,7 +713,7 @@ function getUnquotedSheetName(sheetName) {
713
713
  * '\w' captures [0-9][a-z][A-Z] and _.
714
714
  * @param sheetName Name of the sheet
715
715
  */
716
- function getComposerSheetName(sheetName) {
716
+ function getCanonicalSheetName(sheetName) {
717
717
  var _a;
718
718
  if (((_a = sheetName.match(/\w/g)) === null || _a === void 0 ? void 0 : _a.length) !== sheetName.length) {
719
719
  sheetName = `'${sheetName}'`;
@@ -734,8 +734,9 @@ function getDefaultCellHeight(cell) {
734
734
  return DEFAULT_CELL_HEIGHT;
735
735
  }
736
736
  const fontSize = computeTextFontSizeInPixels(cell.style);
737
- const multiLineText = cell.content.split(NEWLINE);
738
- return computeTextLinesHeight(fontSize, multiLineText.length) + 2 * PADDING_AUTORESIZE_VERTICAL;
737
+ // the number of lines should be computed from the formula result, but it's not evaluated at this point
738
+ const numberOfLines = cell.isFormula ? 1 : cell.content.split(NEWLINE).length;
739
+ return computeTextLinesHeight(fontSize, numberOfLines) + 2 * PADDING_AUTORESIZE_VERTICAL;
739
740
  }
740
741
  function computeTextWidth(context, text, style) {
741
742
  context.save();
@@ -1087,6 +1088,8 @@ function replaceSpecialSpaces(text) {
1087
1088
  return text;
1088
1089
  }
1089
1090
 
1091
+ const RBA_REGEX = /rgba?\(|\s+|\)/gi;
1092
+ const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/g;
1090
1093
  const colors$1 = [
1091
1094
  "#eb6d00",
1092
1095
  "#0074d9",
@@ -1112,7 +1115,10 @@ function colorNumberString(color) {
1112
1115
  }
1113
1116
  /**
1114
1117
  * Converts any CSS color value to a standardized hex6 value.
1115
- * Accepts: hex3, hex6, hex8 and rgb (rgba is not supported)
1118
+ * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
1119
+ *
1120
+ * [1] under the form rgb(r, g, b, a?) or rgba(r, g, b, a?)
1121
+ * with r,g,b ∈ [0, 255] and a ∈ [0, 1]
1116
1122
  *
1117
1123
  * toHex("#ABC")
1118
1124
  * >> "#AABBCC"
@@ -1123,27 +1129,31 @@ function colorNumberString(color) {
1123
1129
  * toHex("rgb(30, 80, 16)")
1124
1130
  * >> "#1E5010"
1125
1131
  *
1132
+ * * toHex("rgb(30, 80, 16, 0.5)")
1133
+ * >> "#1E501080"
1134
+ *
1126
1135
  */
1127
1136
  function toHex(color) {
1128
- if (color.includes("rgba")) {
1129
- throw new Error(`rgba() conversion currently not supported: ${color}`);
1130
- }
1131
- if (color.includes("rgb")) {
1132
- return rgbToHex6(color);
1137
+ let hexColor = color;
1138
+ if (color.startsWith("rgb")) {
1139
+ hexColor = rgbaStringToHex(color);
1133
1140
  }
1134
- color = color.replace("#", "").toUpperCase();
1135
- if (color.length === 3 || color.length === 4) {
1136
- color = color.split("").reduce((acc, h) => acc + h + h, "");
1141
+ else {
1142
+ hexColor = color.replace("#", "").toUpperCase();
1143
+ if (hexColor.length === 3 || hexColor.length === 4) {
1144
+ hexColor = hexColor.split("").reduce((acc, h) => acc + h + h, "");
1145
+ }
1146
+ hexColor = `#${hexColor}`;
1137
1147
  }
1138
- if (color.replace(/[a-f0-9]/gi, "") !== "") {
1139
- throw new Error("invalid color");
1148
+ if (!hexColor.match(HEX_MATCH)) {
1149
+ throw new Error(`invalid color input: ${color}`);
1140
1150
  }
1141
- return "#" + color;
1151
+ return hexColor;
1142
1152
  }
1143
1153
  function isColorValid(color) {
1144
1154
  try {
1145
- const { r, g, b, a } = colorToRGBA(color);
1146
- return (isColorValueValid(r) && isColorValueValid(g) && isColorValueValid(b) && isColorValueValid(a));
1155
+ toHex(color);
1156
+ return true;
1147
1157
  }
1148
1158
  catch (error) {
1149
1159
  return false;
@@ -1176,15 +1186,29 @@ function relativeLuminance(color) {
1176
1186
  /**
1177
1187
  * Convert a CSS rgb color string to a standardized hex6 color value.
1178
1188
  *
1179
- * rgbToHex6("rgb(30, 80, 16)")
1189
+ * rgbaStringToHex("rgb(30, 80, 16)")
1180
1190
  * >> "#1E5010"
1191
+ *
1192
+ * rgbaStringToHex("rgba(30, 80, 16, 0.5)")
1193
+ * >> "#1E501080"
1194
+ *
1195
+ * DOES NOT SUPPORT NON INTEGER RGB VALUES
1181
1196
  */
1182
- function rgbToHex6(color) {
1183
- return ("#" +
1184
- concat(color
1185
- .slice(4, -1)
1186
- .split(",")
1187
- .map((valueString) => parseInt(valueString, 10).toString(16).padStart(2, "0"))).toUpperCase());
1197
+ function rgbaStringToHex(color) {
1198
+ const stringVals = color.replace(RBA_REGEX, "").split(",");
1199
+ let alphaHex = 255;
1200
+ if (stringVals.length !== 3 && stringVals.length !== 4) {
1201
+ throw new Error("invalid color");
1202
+ }
1203
+ else if (stringVals.length === 4) {
1204
+ const alpha = parseFloat(stringVals.pop() || "1");
1205
+ alphaHex = Math.round((alpha || 1) * 255);
1206
+ }
1207
+ const vals = stringVals.map((val) => parseInt(val, 10));
1208
+ if (alphaHex !== 255) {
1209
+ vals.push(alphaHex);
1210
+ }
1211
+ return "#" + concat(vals.map((value) => value.toString(16).padStart(2, "0"))).toUpperCase();
1188
1212
  }
1189
1213
  /**
1190
1214
  * RGBA to HEX representation (#RRGGBBAA).
@@ -1206,7 +1230,7 @@ function rgbaToHex(rgba) {
1206
1230
  a = "0" + a;
1207
1231
  if (a === "ff")
1208
1232
  a = "";
1209
- return "#" + r + g + b + a;
1233
+ return ("#" + r + g + b + a).toUpperCase();
1210
1234
  }
1211
1235
  /**
1212
1236
  * Color string to RGBA representation
@@ -1327,6 +1351,9 @@ function rgbaToHSLA(rgba) {
1327
1351
  s = +(s * 100).toFixed(1);
1328
1352
  l = +(l * 100).toFixed(1);
1329
1353
  return { a: rgba.a, h, s, l };
1354
+ }
1355
+ function isSameColor(color1, color2) {
1356
+ return isColorValid(color1) && isColorValid(color2) && toHex(color1) === toHex(color2);
1330
1357
  }
1331
1358
 
1332
1359
  /** Reference of a cell (eg. A1, $B$5) */
@@ -1478,7 +1505,7 @@ function scrollDelay(value) {
1478
1505
  * formula, commas are used to separate arguments
1479
1506
  * - it does not support % symbol, in formulas % is an operator
1480
1507
  */
1481
- const formulaNumberRegexp = /^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+/;
1508
+ const formulaNumberRegexp = /(^-?\d+(\.?\d*(e\d+)?)?|^-?\.\d+)(?!\w|!)/;
1482
1509
  const pIntegerAndDecimals = "(\\d+(,\\d{3,})*(\\.\\d*)?)"; // pattern that match integer number with or without decimal digits
1483
1510
  const pOnlyDecimals = "(\\.\\d+)"; // pattern that match only expression with decimal digits
1484
1511
  const pScientificFormat = "(e(\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -3051,6 +3078,9 @@ function cellTextStyleToCss(style) {
3051
3078
  }
3052
3079
  return attributes;
3053
3080
  }
3081
+ /**
3082
+ * Transform CSS properties into a CSS string.
3083
+ */
3054
3084
  function cssPropertiesToCss(attributes) {
3055
3085
  const str = Object.entries(attributes)
3056
3086
  .filter(([attName, attValue]) => attValue !== undefined)
@@ -3084,6 +3114,7 @@ const CHART_PADDING_RATIO = 0.02;
3084
3114
  const LINE_HEIGHT = 1.2;
3085
3115
  css /* scss */ `
3086
3116
  div.o-scorecard {
3117
+ font-family: ${DEFAULT_FONT};
3087
3118
  user-select: none;
3088
3119
  background-color: white;
3089
3120
  display: flex;
@@ -5071,6 +5102,10 @@ function usePopoverContainer() {
5071
5102
  function rectIntersection(rect1, rect2) {
5072
5103
  return zoneToRect(intersection(rectToZone(rect1), rectToZone(rect2)));
5073
5104
  }
5105
+ /** Compute the union of the rectangles, ie. the smallest rectangle that contain them all */
5106
+ function rectUnion(...rects) {
5107
+ return zoneToRect(union(...rects.map(rectToZone)));
5108
+ }
5074
5109
  function rectToZone(rect) {
5075
5110
  return {
5076
5111
  left: rect.x,
@@ -5367,24 +5402,30 @@ class Menu extends Component {
5367
5402
  }
5368
5403
  });
5369
5404
  }
5370
- get visibleMenuItems() {
5371
- return this.props.menuItems.filter((x) => x.isVisible(this.env));
5405
+ get menuItemsAndSeparators() {
5406
+ const menuItemsAndSeparators = [];
5407
+ for (let i = 0; i < this.props.menuItems.length; i++) {
5408
+ const menuItem = this.props.menuItems[i];
5409
+ if (menuItem.isVisible(this.env)) {
5410
+ menuItemsAndSeparators.push(menuItem);
5411
+ }
5412
+ if (menuItem.separator &&
5413
+ i !== this.props.menuItems.length - 1 && // no separator at the end
5414
+ menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
5415
+ ) {
5416
+ menuItemsAndSeparators.push("separator");
5417
+ }
5418
+ }
5419
+ if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
5420
+ return [];
5421
+ }
5422
+ return menuItemsAndSeparators;
5372
5423
  }
5373
5424
  get subMenuPosition() {
5374
5425
  const position = Object.assign({}, this.subMenu.position);
5375
5426
  position.y -= this.subMenu.scrollOffset || 0;
5376
5427
  return position;
5377
5428
  }
5378
- get menuHeight() {
5379
- const menuItems = this.visibleMenuItems;
5380
- let menuItemsHeight = this.getMenuItemsHeight(menuItems);
5381
- // We don't display separator at the end of a menu
5382
- if (menuItems[menuItems.length - 1].separator) {
5383
- menuItemsHeight -= MENU_SEPARATOR_HEIGHT;
5384
- }
5385
- const menuHeight = 2 * MENU_VERTICAL_PADDING + menuItemsHeight;
5386
- return this.props.maxHeight ? Math.min(menuHeight, this.props.maxHeight) : menuHeight;
5387
- }
5388
5429
  get popoverProps() {
5389
5430
  const isRoot = this.props.depth === 1;
5390
5431
  return {
@@ -5398,7 +5439,6 @@ class Menu extends Component {
5398
5439
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
5399
5440
  onPopoverHidden: () => this.closeSubMenu(),
5400
5441
  onPopoverMoved: () => this.closeSubMenu(),
5401
- maxHeight: this.menuHeight,
5402
5442
  };
5403
5443
  }
5404
5444
  getColor(menu) {
@@ -5414,14 +5454,6 @@ class Menu extends Component {
5414
5454
  this.closeSubMenu();
5415
5455
  this.props.onClose();
5416
5456
  }
5417
- /**
5418
- * Return the number of pixels between the top of the menu
5419
- * and the menu item at a given index.
5420
- */
5421
- subMenuVerticalPosition(menuIndex) {
5422
- const menusAbove = this.visibleMenuItems.slice(0, menuIndex);
5423
- return this.position.y + this.getMenuItemsHeight(menusAbove) + MENU_VERTICAL_PADDING;
5424
- }
5425
5457
  onExternalClick(ev) {
5426
5458
  // Don't close a root menu when clicked to open the submenus.
5427
5459
  const el = this.menuRef.el;
@@ -5431,10 +5463,6 @@ class Menu extends Component {
5431
5463
  ev.closedMenuId = this.props.menuId;
5432
5464
  this.close();
5433
5465
  }
5434
- getMenuItemsHeight(menuItems) {
5435
- const numberOfSeparators = menuItems.filter((m) => m.separator).length;
5436
- return MENU_ITEM_HEIGHT * menuItems.length + MENU_SEPARATOR_HEIGHT * numberOfSeparators;
5437
- }
5438
5466
  getName(menu) {
5439
5467
  return menu.name(this.env);
5440
5468
  }
@@ -5454,8 +5482,11 @@ class Menu extends Component {
5454
5482
  * If the given menu is not disabled, open it's submenu at the
5455
5483
  * correct position according to available surrounding space.
5456
5484
  */
5457
- openSubMenu(menu, menuIndex) {
5458
- const y = this.subMenuVerticalPosition(menuIndex);
5485
+ openSubMenu(menu, menuIndex, ev) {
5486
+ const parentMenuEl = ev.currentTarget;
5487
+ if (!parentMenuEl)
5488
+ return;
5489
+ const y = parentMenuEl.getBoundingClientRect().top;
5459
5490
  this.subMenu.position = {
5460
5491
  x: this.position.x + this.props.depth * MENU_WIDTH,
5461
5492
  y: y - (this.subMenu.scrollOffset || 0),
@@ -5472,20 +5503,20 @@ class Menu extends Component {
5472
5503
  this.subMenu.isOpen = false;
5473
5504
  this.subMenu.parentMenu = undefined;
5474
5505
  }
5475
- onClickMenu(menu, menuIndex) {
5506
+ onClickMenu(menu, menuIndex, ev) {
5476
5507
  if (this.isEnabled(menu)) {
5477
5508
  if (this.isRoot(menu)) {
5478
- this.openSubMenu(menu, menuIndex);
5509
+ this.openSubMenu(menu, menuIndex, ev);
5479
5510
  }
5480
5511
  else {
5481
5512
  this.activateMenu(menu);
5482
5513
  }
5483
5514
  }
5484
5515
  }
5485
- onMouseOver(menu, position) {
5516
+ onMouseOver(menu, position, ev) {
5486
5517
  if (menu.isEnabled(this.env)) {
5487
5518
  if (this.isRoot(menu)) {
5488
- this.openSubMenu(menu, position);
5519
+ this.openSubMenu(menu, position, ev);
5489
5520
  }
5490
5521
  else {
5491
5522
  this.closeSubMenu();
@@ -6062,8 +6093,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6062
6093
  const dataSets = [];
6063
6094
  for (const sheetXC of dataSetsString) {
6064
6095
  const dataRange = getters.getRangeFromSheetXC(sheetId, sheetXC);
6065
- const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName } = dataRange;
6066
- if (invalidSheetName) {
6096
+ const { unboundedZone: zone, sheetId: dataSetSheetId, invalidSheetName, invalidXc } = dataRange;
6097
+ if (invalidSheetName || invalidXc) {
6067
6098
  continue;
6068
6099
  }
6069
6100
  // It's a rectangle. We treat all columns (arbitrary) as different data series.
@@ -6088,14 +6119,8 @@ function createDataSets(getters, dataSetsString, sheetId, dataSetsHaveTitle) {
6088
6119
  : undefined));
6089
6120
  }
6090
6121
  }
6091
- else if (zone.left === zone.right && zone.top === zone.bottom) {
6092
- // A single cell. If it's only the title, the dataset is not added.
6093
- if (!dataSetsHaveTitle) {
6094
- dataSets.push(createDataSet(getters, dataSetSheetId, zone, undefined));
6095
- }
6096
- }
6097
6122
  else {
6098
- /* 1 row or 1 column */
6123
+ /* 1 cell, 1 row or 1 column */
6099
6124
  dataSets.push(createDataSet(getters, dataSetSheetId, zone, dataSetsHaveTitle
6100
6125
  ? {
6101
6126
  top: zone.top,
@@ -6149,6 +6174,18 @@ function toExcelDataset(getters, ds) {
6149
6174
  range: getters.getRangeString(dataRange, "forceSheetReference"),
6150
6175
  };
6151
6176
  }
6177
+ function toExcelLabelRange(getters, labelRange, shouldRemoveFirstLabel) {
6178
+ if (!labelRange)
6179
+ return undefined;
6180
+ let zone = {
6181
+ ...labelRange.zone,
6182
+ };
6183
+ if (shouldRemoveFirstLabel && labelRange.zone.bottom > labelRange.zone.top) {
6184
+ zone.top = zone.top + 1;
6185
+ }
6186
+ const range = labelRange.clone({ zone });
6187
+ return getters.getRangeString(range, "forceSheetReference");
6188
+ }
6152
6189
  /**
6153
6190
  * Transform a chart definition which supports dataSets (dataSets and LabelRange)
6154
6191
  * with an executed command
@@ -6233,6 +6270,20 @@ function checkLabelRange(definition) {
6233
6270
  }
6234
6271
  return 0 /* CommandResult.Success */;
6235
6272
  }
6273
+ function shouldRemoveFirstLabel(labelRange, dataset, dataSetsHaveTitle) {
6274
+ if (!dataSetsHaveTitle)
6275
+ return false;
6276
+ if (!labelRange)
6277
+ return false;
6278
+ if (!dataset)
6279
+ return true;
6280
+ const datasetLength = getZoneArea(dataset.dataRange.zone);
6281
+ const labelLength = getZoneArea(labelRange.zone);
6282
+ if (labelLength < datasetLength) {
6283
+ return false;
6284
+ }
6285
+ return true;
6286
+ }
6236
6287
  // ---------------------------------------------------------------------------
6237
6288
  // Scorecard
6238
6289
  // ---------------------------------------------------------------------------
@@ -6602,6 +6653,7 @@ class BarChart extends AbstractChart {
6602
6653
  this.legendPosition = definition.legendPosition;
6603
6654
  this.stacked = definition.stacked;
6604
6655
  this.aggregated = definition.aggregated;
6656
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
6605
6657
  }
6606
6658
  static transformDefinition(definition, executed) {
6607
6659
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -6669,11 +6721,13 @@ class BarChart extends AbstractChart {
6669
6721
  const dataSets = this.dataSets
6670
6722
  .map((ds) => toExcelDataset(this.getters, ds))
6671
6723
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
6724
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
6672
6725
  return {
6673
6726
  ...this.getDefinition(),
6674
6727
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
6675
6728
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
6676
6729
  dataSets,
6730
+ labelRange,
6677
6731
  };
6678
6732
  }
6679
6733
  updateRanges(applyChange) {
@@ -6741,6 +6795,11 @@ function createBarChartRuntime(chart, getters) {
6741
6795
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
6742
6796
  let labels = labelValues.formattedValues;
6743
6797
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
6798
+ if (chart.dataSetsHaveTitle &&
6799
+ dataSetsValues[0] &&
6800
+ labels.length > dataSetsValues[0].data.length) {
6801
+ labels.shift();
6802
+ }
6744
6803
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
6745
6804
  if (chart.aggregated) {
6746
6805
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7163,6 +7222,7 @@ class LineChart extends AbstractChart {
7163
7222
  this.labelsAsText = definition.labelsAsText;
7164
7223
  this.stacked = definition.stacked;
7165
7224
  this.aggregated = definition.aggregated;
7225
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7166
7226
  }
7167
7227
  static validateChartDefinition(validator, definition) {
7168
7228
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -7230,11 +7290,13 @@ class LineChart extends AbstractChart {
7230
7290
  const dataSets = this.dataSets
7231
7291
  .map((ds) => toExcelDataset(this.getters, ds))
7232
7292
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7293
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7233
7294
  return {
7234
7295
  ...this.getDefinition(),
7235
7296
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7236
7297
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7237
7298
  dataSets,
7299
+ labelRange,
7238
7300
  };
7239
7301
  }
7240
7302
  copyForSheetId(sheetId) {
@@ -7372,6 +7434,11 @@ function createLineChartRuntime(chart, getters) {
7372
7434
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7373
7435
  let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
7374
7436
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7437
+ if (chart.dataSetsHaveTitle &&
7438
+ dataSetsValues[0] &&
7439
+ labels.length > dataSetsValues[0].data.length) {
7440
+ labels.shift();
7441
+ }
7375
7442
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7376
7443
  if (axisType === "time") {
7377
7444
  ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
@@ -7430,6 +7497,7 @@ class PieChart extends AbstractChart {
7430
7497
  this.background = definition.background;
7431
7498
  this.legendPosition = definition.legendPosition;
7432
7499
  this.aggregated = definition.aggregated;
7500
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
7433
7501
  }
7434
7502
  static transformDefinition(definition, executed) {
7435
7503
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -7493,12 +7561,14 @@ class PieChart extends AbstractChart {
7493
7561
  const dataSets = this.dataSets
7494
7562
  .map((ds) => toExcelDataset(this.getters, ds))
7495
7563
  .filter((ds) => ds.range !== ""); // && range !== INCORRECT_RANGE_STRING ? show incorrect #ref ?
7564
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
7496
7565
  return {
7497
7566
  ...this.getDefinition(),
7498
7567
  backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
7499
7568
  fontColor: toXlsxHexColor(chartFontColor(this.background)),
7500
7569
  verticalAxisPosition: "left",
7501
7570
  dataSets,
7571
+ labelRange,
7502
7572
  };
7503
7573
  }
7504
7574
  updateRanges(applyChange) {
@@ -7544,6 +7614,11 @@ function createPieChartRuntime(chart, getters) {
7544
7614
  const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
7545
7615
  let labels = labelValues.formattedValues;
7546
7616
  let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
7617
+ if (chart.dataSetsHaveTitle &&
7618
+ dataSetsValues[0] &&
7619
+ labels.length > dataSetsValues[0].data.length) {
7620
+ labels.shift();
7621
+ }
7547
7622
  ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
7548
7623
  if (chart.aggregated) {
7549
7624
  ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
@@ -7800,6 +7875,10 @@ css /* scss */ `
7800
7875
  }
7801
7876
  `;
7802
7877
  class ChartFigure extends Component {
7878
+ onDoubleClick() {
7879
+ this.env.model.dispatch("SELECT_FIGURE", { id: this.props.figure.id });
7880
+ this.env.openSidePanel("ChartPanel");
7881
+ }
7803
7882
  get chartType() {
7804
7883
  return this.env.model.getters.getChartType(this.props.figure.id);
7805
7884
  }
@@ -8175,7 +8254,6 @@ function getSmartChartDefinition(zone, getters) {
8175
8254
  labelRangeXc = zoneToXc({
8176
8255
  ...zone,
8177
8256
  right: zone.left,
8178
- top: dataSetsHaveTitle ? zone.top + 1 : zone.top,
8179
8257
  });
8180
8258
  }
8181
8259
  // Only display legend for several datasets.
@@ -15093,7 +15171,7 @@ const ADDRESS = {
15093
15171
  cellReference = rowPart + colPart;
15094
15172
  }
15095
15173
  if (sheet !== undefined) {
15096
- return `${getComposerSheetName(toString(sheet))}!${cellReference}`;
15174
+ return `${getCanonicalSheetName(toString(sheet))}!${cellReference}`;
15097
15175
  }
15098
15176
  return cellReference;
15099
15177
  },
@@ -16918,6 +16996,22 @@ class LineBarPieConfigPanel extends Component {
16918
16996
  aggregated: ev.target.checked,
16919
16997
  });
16920
16998
  }
16999
+ calculateHeaderPosition() {
17000
+ if (this.isDatasetInvalid || this.isLabelInvalid) {
17001
+ return undefined;
17002
+ }
17003
+ const getters = this.env.model.getters;
17004
+ const sheetId = getters.getActiveSheetId();
17005
+ const labelRange = createRange(getters, sheetId, this.labelRange);
17006
+ const dataSets = createDataSets(getters, this.dataSeriesRanges, sheetId, this.props.definition.dataSetsHaveTitle);
17007
+ if (dataSets.length) {
17008
+ return dataSets[0].dataRange.zone.top + 1;
17009
+ }
17010
+ else if (labelRange) {
17011
+ return labelRange.zone.top + 1;
17012
+ }
17013
+ return undefined;
17014
+ }
16921
17015
  }
16922
17016
  LineBarPieConfigPanel.template = "o-spreadsheet-LineBarPieConfigPanel";
16923
17017
  LineBarPieConfigPanel.components = { SelectionInput };
@@ -17165,6 +17259,9 @@ class ColorPicker extends Component {
17165
17259
  top,
17166
17260
  });
17167
17261
  }
17262
+ isSameColor(color1, color2) {
17263
+ return isSameColor(color1, color2);
17264
+ }
17168
17265
  }
17169
17266
  ColorPicker.template = "o-spreadsheet-ColorPicker";
17170
17267
  ColorPicker.defaultProps = {
@@ -18837,7 +18934,7 @@ css /*SCSS*/ `
18837
18934
  box-sizing: content-box;
18838
18935
 
18839
18936
  .o-fig-anchor {
18840
- z-index: ${ComponentsImportance.ChartAnchor};
18937
+ z-index: ${ComponentsImportance.FigureAnchor};
18841
18938
  position: absolute;
18842
18939
  width: ${ANCHOR_SIZE}px;
18843
18940
  height: ${ANCHOR_SIZE}px;
@@ -19406,6 +19503,8 @@ class ContentEditableHelper {
19406
19503
  start = 0;
19407
19504
  if (end > textLength)
19408
19505
  end = textLength;
19506
+ if (start > textLength)
19507
+ start = textLength;
19409
19508
  }
19410
19509
  let startNode = this.findChildAtCharacterIndex(start);
19411
19510
  let endNode = this.findChildAtCharacterIndex(end);
@@ -19675,6 +19774,7 @@ css /* scss */ `
19675
19774
  padding-right: 2px;
19676
19775
 
19677
19776
  box-sizing: border-box;
19777
+ font-family: ${DEFAULT_FONT};
19678
19778
 
19679
19779
  caret-color: black;
19680
19780
  padding-left: 3px;
@@ -19704,13 +19804,6 @@ css /* scss */ `
19704
19804
  pointer-events: none;
19705
19805
  }
19706
19806
  }
19707
-
19708
- .o-topbar-composer .o-composer:empty:not(:focus)::before {
19709
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
19710
- 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");
19711
- position: relative;
19712
- top: 20%;
19713
- }
19714
19807
  `;
19715
19808
  class Composer extends Component {
19716
19809
  constructor() {
@@ -20124,7 +20217,7 @@ class Composer extends Component {
20124
20217
  const highlights = this.env.model.getters.getHighlights();
20125
20218
  const refSheet = sheetName
20126
20219
  ? this.env.model.getters.getSheetIdByName(sheetName)
20127
- : this.env.model.getters.getEditionSheet();
20220
+ : this.env.model.getters.getCurrentEditedCell().sheetId;
20128
20221
  const highlight = highlights.find((highlight) => {
20129
20222
  if (highlight.sheetId !== refSheet)
20130
20223
  return false;
@@ -20285,7 +20378,7 @@ class GridComposer extends Component {
20285
20378
  get cellReference() {
20286
20379
  const { col, row, sheetId } = this.env.model.getters.getCurrentEditedCell();
20287
20380
  const prefixSheet = sheetId !== this.env.model.getters.getActiveSheetId();
20288
- return `${prefixSheet ? getComposerSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20381
+ return `${prefixSheet ? getCanonicalSheetName(this.env.model.getters.getSheetName(sheetId)) + "!" : ""}${toXC(col, row)}`;
20289
20382
  }
20290
20383
  get cellReferenceStyle() {
20291
20384
  const { x: left, y: top } = this.rect;
@@ -20438,6 +20531,269 @@ FilterIconsOverlay.props = {
20438
20531
  gridPosition: { type: Object, optional: true },
20439
20532
  };
20440
20533
 
20534
+ /**
20535
+ * Transform a figure with coordinates from the model, to coordinates as they are shown on the screen,
20536
+ * taking into account the scroll position of the active sheet and the frozen panes.
20537
+ */
20538
+ function internalFigureToScreen(getters, fig) {
20539
+ return { ...fig, ...internalToScreenCoordinates(getters, { x: fig.x, y: fig.y }) };
20540
+ }
20541
+ /**
20542
+ * Transform a figure with coordinates as they are shown on the screen, to coordinates as they are in the model,
20543
+ * taking into account the scroll position of the active sheet and the frozen panes.
20544
+ *
20545
+ * Note that this isn't exactly the reverse operation as internalFigureToScreen, because the figure will always be on top
20546
+ * of the frozen panes.
20547
+ */
20548
+ function screenFigureToInternal(getters, fig) {
20549
+ return { ...fig, ...screenCoordinatesToInternal(getters, { x: fig.x, y: fig.y }) };
20550
+ }
20551
+ function internalToScreenCoordinates(getters, { x, y }) {
20552
+ const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
20553
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
20554
+ x = x < viewportX ? x : x - scrollX;
20555
+ y = y < viewportY ? y : y - scrollY;
20556
+ return { x, y };
20557
+ }
20558
+ function screenCoordinatesToInternal(getters, { x, y }) {
20559
+ const { x: viewportX, y: viewportY } = getters.getMainViewportCoordinates();
20560
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
20561
+ x = viewportX && x < viewportX ? x : x + scrollX;
20562
+ y = viewportY && y < viewportY ? y : y + scrollY;
20563
+ return { x, y };
20564
+ }
20565
+
20566
+ function dragFigureForMove({ x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, initialFigure, { x: viewportX, y: viewportY }, { maxX, maxY }, { scrollX, scrollY }) {
20567
+ const minX = viewportX ? 0 : -scrollX;
20568
+ const minY = viewportY ? 0 : -scrollY;
20569
+ const deltaX = mouseX - mouseInitialX;
20570
+ const newX = clamp(initialFigure.x + deltaX, minX, maxX - initialFigure.width - scrollX);
20571
+ const deltaY = mouseY - mouseInitialY;
20572
+ const newY = clamp(initialFigure.y + deltaY, minY, maxY - initialFigure.height - scrollY);
20573
+ return { ...initialFigure, x: newX, y: newY };
20574
+ }
20575
+ function clamp(value, min, max) {
20576
+ return Math.min(Math.max(value, min), max);
20577
+ }
20578
+ function dragFigureForResize(initialFigure, dirX, dirY, { x: mouseX, y: mouseY }, { x: mouseInitialX, y: mouseInitialY }, keepRatio, minFigSize) {
20579
+ let { x, y, width, height } = initialFigure;
20580
+ if (keepRatio && dirX != 0 && dirY != 0) {
20581
+ const deltaX = Math.min(dirX * (mouseInitialX - mouseX), initialFigure.width - minFigSize);
20582
+ const deltaY = Math.min(dirY * (mouseInitialY - mouseY), initialFigure.height - minFigSize);
20583
+ const fraction = Math.min(deltaX / initialFigure.width, deltaY / initialFigure.height);
20584
+ width = initialFigure.width * (1 - fraction);
20585
+ height = initialFigure.height * (1 - fraction);
20586
+ if (dirX < 0) {
20587
+ x = initialFigure.x + initialFigure.width * fraction;
20588
+ }
20589
+ if (dirY < 0) {
20590
+ y = initialFigure.y + initialFigure.height * fraction;
20591
+ }
20592
+ }
20593
+ else {
20594
+ const deltaX = Math.max(dirX * (mouseX - mouseInitialX), minFigSize - initialFigure.width);
20595
+ const deltaY = Math.max(dirY * (mouseY - mouseInitialY), minFigSize - initialFigure.height);
20596
+ width = initialFigure.width + deltaX;
20597
+ height = initialFigure.height + deltaY;
20598
+ if (dirX < 0) {
20599
+ x = initialFigure.x - deltaX;
20600
+ }
20601
+ if (dirY < 0) {
20602
+ y = initialFigure.y - deltaY;
20603
+ }
20604
+ }
20605
+ return { ...initialFigure, x, y, width, height };
20606
+ }
20607
+
20608
+ const SNAP_MARGIN = 5;
20609
+ /**
20610
+ * Try to snap the given figure to other figures when moving the figure, and return the snapped
20611
+ * figure and the possible snap lines, if any were found
20612
+ */
20613
+ function snapForMove(getters, figureToSnap, otherFigures) {
20614
+ const snappedFigure = { ...figureToSnap };
20615
+ const verticalSnapLine = getSnapLine(getters, snappedFigure, ["hCenter", "right", "left"], otherFigures, ["hCenter", "right", "left"]);
20616
+ const horizontalSnapLine = getSnapLine(getters, snappedFigure, ["vCenter", "bottom", "top"], otherFigures, ["vCenter", "bottom", "top"]);
20617
+ const { y: viewportY, x: viewportX } = getters.getMainViewportCoordinates();
20618
+ const { scrollY, scrollX } = getters.getActiveSheetScrollInfo();
20619
+ // If the snap cause the figure to change pane, we need to also apply the scroll as an offset
20620
+ if (horizontalSnapLine) {
20621
+ snappedFigure.y -= horizontalSnapLine.snapOffset;
20622
+ const isBaseFigFrozenY = figureToSnap.y < viewportY;
20623
+ const isSnappedFrozenY = snappedFigure.y < viewportY;
20624
+ if (isBaseFigFrozenY && !isSnappedFrozenY)
20625
+ snappedFigure.y += scrollY;
20626
+ else if (!isBaseFigFrozenY && isSnappedFrozenY)
20627
+ snappedFigure.y -= scrollY;
20628
+ }
20629
+ if (verticalSnapLine) {
20630
+ snappedFigure.x -= verticalSnapLine.snapOffset;
20631
+ const isBaseFigFrozenX = figureToSnap.x < viewportX;
20632
+ const isSnappedFrozenX = snappedFigure.x < viewportX;
20633
+ if (isBaseFigFrozenX && !isSnappedFrozenX)
20634
+ snappedFigure.x += scrollX;
20635
+ else if (!isBaseFigFrozenX && isSnappedFrozenX)
20636
+ snappedFigure.x -= scrollX;
20637
+ }
20638
+ return { snappedFigure, verticalSnapLine, horizontalSnapLine };
20639
+ }
20640
+ /**
20641
+ * Try to snap the given figure to the other figures when resizing the figure, and return the snapped
20642
+ * figure and the possible snap lines, if any were found
20643
+ */
20644
+ function snapForResize(getters, resizeDirX, resizeDirY, figureToSnap, otherFigures) {
20645
+ const snappedFigure = { ...figureToSnap };
20646
+ // Vertical snap line
20647
+ const verticalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirX === -1 ? "left" : "right"], otherFigures, ["right", "left"]);
20648
+ if (verticalSnapLine) {
20649
+ if (resizeDirX === 1) {
20650
+ snappedFigure.width -= verticalSnapLine.snapOffset;
20651
+ }
20652
+ else if (resizeDirX === -1) {
20653
+ snappedFigure.x -= verticalSnapLine.snapOffset;
20654
+ snappedFigure.width += verticalSnapLine.snapOffset;
20655
+ }
20656
+ }
20657
+ // Horizontal snap line
20658
+ const horizontalSnapLine = getSnapLine(getters, snappedFigure, [resizeDirY === -1 ? "top" : "bottom"], otherFigures, ["bottom", "top"]);
20659
+ if (horizontalSnapLine) {
20660
+ if (resizeDirY === 1) {
20661
+ snappedFigure.height -= horizontalSnapLine.snapOffset;
20662
+ }
20663
+ else if (resizeDirY === -1) {
20664
+ snappedFigure.y -= horizontalSnapLine.snapOffset;
20665
+ snappedFigure.height += horizontalSnapLine.snapOffset;
20666
+ }
20667
+ }
20668
+ snappedFigure.x = Math.round(snappedFigure.x);
20669
+ snappedFigure.y = Math.round(snappedFigure.y);
20670
+ snappedFigure.height = Math.round(snappedFigure.height);
20671
+ snappedFigure.width = Math.round(snappedFigure.width);
20672
+ return { snappedFigure, verticalSnapLine, horizontalSnapLine };
20673
+ }
20674
+ /**
20675
+ * Get the position of snap axes for the given figure
20676
+ *
20677
+ * @param figure the figure
20678
+ * @param axesTypes the list of axis types to return the positions of
20679
+ */
20680
+ function getVisibleAxes(getters, figure, axesTypes) {
20681
+ const axes = axesTypes.map((axisType) => getAxis(figure, axisType));
20682
+ return axes
20683
+ .filter((axis) => isAxisVisible(getters, figure, axis))
20684
+ .map((axis) => getAxisScreenPosition(getters, figure, axis));
20685
+ }
20686
+ /**
20687
+ * We need two positions for the figure axis :
20688
+ * - the position (core) of the axis in the figure. This is used to know whether or not the axis is
20689
+ * displayed, or is hidden by the scroll/the frozen panes
20690
+ * - the position in the screen, which is used to find snap matches. We cannot use the core position for this,
20691
+ * because figures partially in frozen panes aren't displayed at their actual coordinates
20692
+ */
20693
+ function getAxisScreenPosition(getters, figure, figureAxis) {
20694
+ const screenFigure = internalFigureToScreen(getters, figure);
20695
+ return getAxis(screenFigure, figureAxis.axisType);
20696
+ }
20697
+ function isAxisVisible(getters, figure, axis) {
20698
+ const { x: mainViewportX, y: mainViewportY } = getters.getMainViewportCoordinates();
20699
+ const axisStartEndPositions = [];
20700
+ switch (axis.axisType) {
20701
+ case "top":
20702
+ case "bottom":
20703
+ case "vCenter":
20704
+ if (figure.y < mainViewportY)
20705
+ return true;
20706
+ axisStartEndPositions.push({ x: figure.x, y: axis.position });
20707
+ axisStartEndPositions.push({ x: figure.x + figure.width, y: axis.position });
20708
+ break;
20709
+ case "left":
20710
+ case "right":
20711
+ case "hCenter":
20712
+ if (figure.x < mainViewportX)
20713
+ return true;
20714
+ axisStartEndPositions.push({ x: axis.position, y: figure.y });
20715
+ axisStartEndPositions.push({ x: axis.position, y: figure.y + figure.height });
20716
+ break;
20717
+ }
20718
+ return axisStartEndPositions.some(getters.isPositionVisible);
20719
+ }
20720
+ /**
20721
+ * Get a snap line for the given figure, if the figure can snap to any other figure
20722
+ *
20723
+ * @param figureToSnap figure to get the snap line for
20724
+ * @param figAxesTypes figure axes of the given figure to be considered to find a snap line
20725
+ * @param otherFigures figures to match against the snapped figure to find a snap line
20726
+ * @param otherAxesTypes figure axes of the other figures to be considered to find a snap line
20727
+ */
20728
+ function getSnapLine(getters, figureToSnap, figAxesTypes, otherFigures, otherAxesTypes) {
20729
+ const axesOfFigure = getVisibleAxes(getters, figureToSnap, figAxesTypes);
20730
+ let closestMatch = undefined;
20731
+ for (const otherFigure of otherFigures) {
20732
+ const axesOfOtherFig = getVisibleAxes(getters, otherFigure, otherAxesTypes);
20733
+ for (const axisOfFigure of axesOfFigure) {
20734
+ for (const axisOfOtherFig of axesOfOtherFig) {
20735
+ if (!canSnap(axisOfFigure.position, axisOfOtherFig.position))
20736
+ continue;
20737
+ const snapOffset = axisOfFigure.position - axisOfOtherFig.position;
20738
+ if (closestMatch && snapOffset === closestMatch.snapOffset) {
20739
+ closestMatch.matchedFigIds.push(otherFigure.id);
20740
+ }
20741
+ else if (!closestMatch || Math.abs(snapOffset) <= Math.abs(closestMatch.snapOffset)) {
20742
+ closestMatch = {
20743
+ matchedFigIds: [otherFigure.id],
20744
+ snapOffset,
20745
+ snappedAxisType: axisOfFigure.axisType,
20746
+ position: axisOfOtherFig.position,
20747
+ };
20748
+ }
20749
+ }
20750
+ }
20751
+ }
20752
+ return closestMatch;
20753
+ }
20754
+ /** Check if two axes are close enough to snap */
20755
+ function canSnap(axisPosition1, axisPosition2) {
20756
+ return Math.abs(axisPosition1 - axisPosition2) <= SNAP_MARGIN;
20757
+ }
20758
+ function getAxis(fig, axisType) {
20759
+ let position = 0;
20760
+ switch (axisType) {
20761
+ case "top":
20762
+ position = fig.y;
20763
+ break;
20764
+ case "bottom":
20765
+ position = fig.y + fig.height - FIGURE_BORDER_WIDTH;
20766
+ break;
20767
+ case "vCenter":
20768
+ position = fig.y + Math.floor(fig.height / 2) - FIGURE_BORDER_WIDTH;
20769
+ break;
20770
+ case "left":
20771
+ position = fig.x;
20772
+ break;
20773
+ case "right":
20774
+ position = fig.x + fig.width - FIGURE_BORDER_WIDTH;
20775
+ break;
20776
+ case "hCenter":
20777
+ position = fig.x + Math.floor(fig.width / 2) - FIGURE_BORDER_WIDTH;
20778
+ break;
20779
+ }
20780
+ return { position, axisType: axisType };
20781
+ }
20782
+
20783
+ css /*SCSS*/ `
20784
+ .o-figure-snap-line {
20785
+ position: relative;
20786
+ z-index: ${ComponentsImportance.FigureSnapLine};
20787
+ &.vertical {
20788
+ width: 0px;
20789
+ border-left: 1px dashed black;
20790
+ }
20791
+ &.horizontal {
20792
+ border-top: 1px dashed black;
20793
+ height: 0px;
20794
+ }
20795
+ }
20796
+ `;
20441
20797
  /**
20442
20798
  * Each figure ⭐ is positioned inside a container `div` placed and sized
20443
20799
  * according to the split pane the figure is part of, or a separate container for the figure
@@ -20502,11 +20858,9 @@ class FiguresContainer extends Component {
20502
20858
  constructor() {
20503
20859
  super(...arguments);
20504
20860
  this.dnd = useState({
20505
- figId: undefined,
20506
- x: 0,
20507
- y: 0,
20508
- width: 0,
20509
- height: 0,
20861
+ draggedFigure: undefined,
20862
+ horizontalSnap: undefined,
20863
+ verticalSnap: undefined,
20510
20864
  });
20511
20865
  }
20512
20866
  setup() {
@@ -20522,9 +20876,11 @@ class FiguresContainer extends Component {
20522
20876
  });
20523
20877
  }
20524
20878
  getVisibleFigures() {
20879
+ var _a;
20525
20880
  const visibleFigures = this.env.model.getters.getVisibleFigures();
20526
- if (this.dnd.figId && !visibleFigures.some((figure) => figure.id === this.dnd.figId)) {
20527
- visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.figId));
20881
+ if (this.dnd.draggedFigure &&
20882
+ !visibleFigures.some((figure) => { var _a; return figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); })) {
20883
+ visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), (_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id));
20528
20884
  }
20529
20885
  return visibleFigures;
20530
20886
  }
@@ -20547,7 +20903,7 @@ class FiguresContainer extends Component {
20547
20903
  });
20548
20904
  }
20549
20905
  }
20550
- if (this.dnd.figId) {
20906
+ if (this.dnd.draggedFigure) {
20551
20907
  containers.push({
20552
20908
  type: "dnd",
20553
20909
  figures: [this.getDndFigure()],
@@ -20558,19 +20914,25 @@ class FiguresContainer extends Component {
20558
20914
  return containers;
20559
20915
  }
20560
20916
  getContainerStyle(container) {
20561
- const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
20562
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20563
- const left = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
20564
- const width = viewWidth - left;
20565
- const top = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
20566
- const height = viewHeight - top;
20917
+ return this.rectToCss(this.getContainerRect(container));
20918
+ }
20919
+ rectToCss(rect) {
20567
20920
  return cssPropertiesToCss({
20568
- left: `${left}px`,
20569
- top: `${top}px`,
20570
- width: `${width}px`,
20571
- height: `${height}px`,
20921
+ left: `${rect.x}px`,
20922
+ top: `${rect.y}px`,
20923
+ width: `${rect.width}px`,
20924
+ height: `${rect.height}px`,
20572
20925
  });
20573
20926
  }
20927
+ getContainerRect(container) {
20928
+ const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
20929
+ const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20930
+ const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
20931
+ const width = viewWidth - x;
20932
+ const y = ["bottomRight", "bottomLeft"].includes(container) ? viewportY : 0;
20933
+ const height = viewHeight - y;
20934
+ return { x, y, width, height };
20935
+ }
20574
20936
  getInverseViewportPositionStyle(container) {
20575
20937
  const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20576
20938
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
@@ -20582,8 +20944,9 @@ class FiguresContainer extends Component {
20582
20944
  });
20583
20945
  }
20584
20946
  getFigureContainer(figure) {
20947
+ var _a;
20585
20948
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20586
- if (figure.id === this.dnd.figId) {
20949
+ if (figure.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id)) {
20587
20950
  return "dnd";
20588
20951
  }
20589
20952
  else if (figure.x < viewportX && figure.y < viewportY) {
@@ -20609,133 +20972,141 @@ class FiguresContainer extends Component {
20609
20972
  return;
20610
20973
  }
20611
20974
  const sheetId = this.env.model.getters.getActiveSheetId();
20612
- const mouseInitialX = ev.clientX;
20613
- const mouseInitialY = ev.clientY;
20614
- const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
20615
- this.dnd.x = dndInitialX;
20616
- this.dnd.y = dndInitialY;
20617
- this.dnd.width = figure.width;
20618
- this.dnd.height = figure.height;
20975
+ const initialMousePosition = { x: ev.clientX, y: ev.clientY };
20976
+ const maxDimensions = {
20977
+ maxX: this.env.model.getters.getColDimensions(sheetId, this.env.model.getters.getNumberCols(sheetId) - 1).end,
20978
+ maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end,
20979
+ };
20980
+ const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
20981
+ const initialFig = { ...figure, x, y };
20619
20982
  const onMouseMove = (ev) => {
20620
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20621
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20622
- const minX = viewportX ? 0 : -scrollX;
20623
- const minY = viewportY ? 0 : -scrollY;
20624
- this.dnd.figId = figure.id;
20625
- const newX = ev.clientX;
20626
- let deltaX = newX - mouseInitialX;
20627
- this.dnd.x = Math.max(dndInitialX + deltaX, minX);
20628
- const newY = ev.clientY;
20629
- let deltaY = newY - mouseInitialY;
20630
- this.dnd.y = Math.max(dndInitialY + deltaY, minY);
20983
+ const getters = this.env.model.getters;
20984
+ const currentMousePosition = { x: ev.clientX, y: ev.clientY };
20985
+ const draggedFigure = dragFigureForMove(currentMousePosition, initialMousePosition, initialFig, this.env.model.getters.getMainViewportCoordinates(), maxDimensions, getters.getActiveSheetScrollInfo());
20986
+ const otherFigures = this.getOtherFigures(figure.id);
20987
+ const internalDragged = screenFigureToInternal(getters, draggedFigure);
20988
+ const snapResult = snapForMove(getters, internalDragged, otherFigures);
20989
+ this.dnd.draggedFigure = internalFigureToScreen(getters, snapResult.snappedFigure);
20990
+ this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
20991
+ this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
20631
20992
  };
20632
20993
  const onMouseUp = (ev) => {
20633
- if (!this.dnd.figId) {
20994
+ if (!this.dnd.draggedFigure) {
20634
20995
  return;
20635
20996
  }
20636
- let { x, y } = this.screenCoordinatesToInternal(this.dnd);
20637
- this.dnd.figId = undefined;
20997
+ let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
20998
+ this.dnd.draggedFigure = undefined;
20999
+ this.dnd.horizontalSnap = undefined;
21000
+ this.dnd.verticalSnap = undefined;
20638
21001
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
20639
21002
  };
20640
21003
  startDnd(onMouseMove, onMouseUp);
20641
21004
  }
21005
+ /**
21006
+ * Initialize the resize of a figure with mouse movements
21007
+ *
21008
+ * @param dirX X direction of the resize. -1 : resize from the left border of the figure, 0 : no resize in X, 1 :
21009
+ * resize from the right border of the figure
21010
+ * @param dirY Y direction of the resize. -1 : resize from the top border of the figure, 0 : no resize in Y, 1 :
21011
+ * resize from the bottom border of the figure
21012
+ * @param ev Mouse Event
21013
+ */
20642
21014
  startResize(figure, dirX, dirY, ev) {
20643
21015
  ev.stopPropagation();
20644
- const initialX = ev.clientX;
20645
- const initialY = ev.clientY;
20646
- const { x: dndInitialX, y: dndInitialY } = this.internalToScreenCoordinates(figure);
20647
- this.dnd.x = dndInitialX;
20648
- this.dnd.y = dndInitialY;
20649
- this.dnd.width = figure.width;
20650
- this.dnd.height = figure.height;
21016
+ const initialMousePosition = { x: ev.clientX, y: ev.clientY };
21017
+ const { x, y } = internalFigureToScreen(this.env.model.getters, figure);
21018
+ const initialFig = { ...figure, x, y };
20651
21019
  const keepRatio = figureRegistry.get(figure.tag).keepRatio || false;
20652
21020
  const minFigSize = figureRegistry.get(figure.tag).minFigSize || MIN_FIG_SIZE;
20653
- let onMouseMove;
20654
- if (keepRatio && dirX != 0 && dirY != 0) {
20655
- onMouseMove = (ev) => {
20656
- this.dnd.figId = figure.id;
20657
- const deltaX = Math.min(dirX * (initialX - ev.clientX), figure.width - minFigSize);
20658
- const deltaY = Math.min(dirY * (initialY - ev.clientY), figure.height - minFigSize);
20659
- const fraction = Math.min(deltaX / figure.width, deltaY / figure.height);
20660
- this.dnd.width = figure.width * (1 - fraction);
20661
- this.dnd.height = figure.height * (1 - fraction);
20662
- if (dirX < 0) {
20663
- this.dnd.x = dndInitialX + figure.width * fraction;
20664
- }
20665
- if (dirY < 0) {
20666
- this.dnd.y = dndInitialY + figure.height * fraction;
20667
- }
20668
- };
20669
- }
20670
- else {
20671
- onMouseMove = (ev) => {
20672
- this.dnd.figId = figure.id;
20673
- const deltaX = Math.max(dirX * (ev.clientX - initialX), minFigSize - figure.width);
20674
- const deltaY = Math.max(dirY * (ev.clientY - initialY), minFigSize - figure.height);
20675
- this.dnd.width = figure.width + deltaX;
20676
- this.dnd.height = figure.height + deltaY;
20677
- if (dirX < 0) {
20678
- this.dnd.x = dndInitialX - deltaX;
20679
- }
20680
- if (dirY < 0) {
20681
- this.dnd.y = dndInitialY - deltaY;
20682
- }
20683
- };
20684
- }
21021
+ const onMouseMove = (ev) => {
21022
+ const currentMousePosition = { x: ev.clientX, y: ev.clientY };
21023
+ const draggedFigure = dragFigureForResize(initialFig, dirX, dirY, currentMousePosition, initialMousePosition, keepRatio, minFigSize);
21024
+ const otherFigures = this.getOtherFigures(figure.id);
21025
+ const snapResult = snapForResize(this.env.model.getters, dirX, dirY, draggedFigure, otherFigures);
21026
+ this.dnd.draggedFigure = snapResult.snappedFigure;
21027
+ this.dnd.horizontalSnap = this.getSnap(snapResult.horizontalSnapLine);
21028
+ this.dnd.verticalSnap = this.getSnap(snapResult.verticalSnapLine);
21029
+ };
20685
21030
  const onMouseUp = (ev) => {
20686
- if (!this.dnd.figId) {
21031
+ if (!this.dnd.draggedFigure) {
20687
21032
  return;
20688
21033
  }
20689
- this.dnd.figId = undefined;
20690
- let { x, y } = this.screenCoordinatesToInternal(this.dnd);
21034
+ let { x, y } = screenFigureToInternal(this.env.model.getters, this.dnd.draggedFigure);
20691
21035
  const update = { x, y };
20692
21036
  if (dirX) {
20693
- update.width = this.dnd.width;
21037
+ update.width = this.dnd.draggedFigure.width;
20694
21038
  }
20695
21039
  if (dirY) {
20696
- update.height = this.dnd.height;
21040
+ update.height = this.dnd.draggedFigure.height;
20697
21041
  }
20698
21042
  this.env.model.dispatch("UPDATE_FIGURE", {
20699
21043
  sheetId: this.env.model.getters.getActiveSheetId(),
20700
21044
  id: figure.id,
20701
21045
  ...update,
20702
21046
  });
21047
+ this.dnd.draggedFigure = undefined;
21048
+ this.dnd.horizontalSnap = undefined;
21049
+ this.dnd.verticalSnap = undefined;
20703
21050
  };
20704
21051
  startDnd(onMouseMove, onMouseUp);
20705
21052
  }
21053
+ getOtherFigures(figId) {
21054
+ return this.getVisibleFigures().filter((f) => f.id !== figId);
21055
+ }
20706
21056
  getDndFigure() {
20707
- const figure = this.getVisibleFigures().find((fig) => fig.id === this.dnd.figId);
21057
+ const figure = this.getVisibleFigures().find((fig) => { var _a; return fig.id === ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id); });
20708
21058
  if (!figure)
20709
21059
  throw new Error("Dnd figure not found");
20710
21060
  return {
20711
21061
  ...figure,
20712
- x: this.dnd.x,
20713
- y: this.dnd.y,
20714
- width: this.dnd.width,
20715
- height: this.dnd.height,
21062
+ ...this.dnd.draggedFigure,
20716
21063
  };
20717
21064
  }
20718
21065
  getFigureStyle(figure) {
20719
- if (figure.id !== this.dnd.figId)
21066
+ var _a;
21067
+ if (figure.id !== ((_a = this.dnd.draggedFigure) === null || _a === void 0 ? void 0 : _a.id))
20720
21068
  return "";
20721
21069
  return cssPropertiesToCss({
20722
21070
  opacity: "0.9",
20723
21071
  cursor: "grabbing",
20724
21072
  });
20725
21073
  }
20726
- internalToScreenCoordinates({ x, y }) {
20727
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20728
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20729
- x = x < viewportX ? x : x - scrollX;
20730
- y = y < viewportY ? y : y - scrollY;
20731
- return { x, y };
21074
+ getSnap(snapLine) {
21075
+ if (!snapLine || !this.dnd.draggedFigure)
21076
+ return undefined;
21077
+ const figureVisibleRects = snapLine.matchedFigIds
21078
+ .map((id) => this.getVisibleFigures().find((fig) => fig.id === id))
21079
+ .filter(isDefined$1)
21080
+ .map((fig) => {
21081
+ const figOnSCreen = internalFigureToScreen(this.env.model.getters, fig);
21082
+ const container = this.getFigureContainer(fig);
21083
+ return rectIntersection(figOnSCreen, this.getContainerRect(container));
21084
+ })
21085
+ .filter(isDefined$1);
21086
+ const containerRect = rectUnion(this.dnd.draggedFigure, ...figureVisibleRects);
21087
+ return {
21088
+ line: snapLine,
21089
+ containerStyle: this.rectToCss(containerRect),
21090
+ lineStyle: this.getSnapLineStyle(snapLine, containerRect),
21091
+ };
20732
21092
  }
20733
- screenCoordinatesToInternal({ x, y }) {
20734
- const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
20735
- const { scrollX, scrollY } = this.env.model.getters.getActiveSheetScrollInfo();
20736
- x = viewportX && x < viewportX ? x : x + scrollX;
20737
- y = viewportY && y < viewportY ? y : y + scrollY;
20738
- return { x, y };
21093
+ getSnapLineStyle(snapLine, containerRect) {
21094
+ if (!snapLine)
21095
+ return "";
21096
+ if (["top", "vCenter", "bottom"].includes(snapLine.snappedAxisType)) {
21097
+ return cssPropertiesToCss({
21098
+ top: `${snapLine.position - containerRect.y}px`,
21099
+ left: `0px`,
21100
+ width: `100%`,
21101
+ });
21102
+ }
21103
+ else {
21104
+ return cssPropertiesToCss({
21105
+ top: `0px`,
21106
+ left: `${snapLine.position - containerRect.x}px`,
21107
+ height: `100%`,
21108
+ });
21109
+ }
20739
21110
  }
20740
21111
  }
20741
21112
  FiguresContainer.template = "o-spreadsheet-FiguresContainer";
@@ -20882,6 +21253,9 @@ class GridOverlay extends Component {
20882
21253
  onMounted(() => {
20883
21254
  resizeObserver.observe(this.gridOverlayEl);
20884
21255
  });
21256
+ onWillUnmount(() => {
21257
+ resizeObserver.disconnect();
21258
+ });
20885
21259
  useTouchMove(this.gridOverlay, this.props.onGridMoved, () => {
20886
21260
  const { scrollY } = this.env.model.getters.getActiveSheetDOMScrollInfo();
20887
21261
  return scrollY > 0;
@@ -22302,6 +22676,10 @@ class Grid extends Component {
22302
22676
  }
22303
22677
  }
22304
22678
  onInput(ev) {
22679
+ // the user meant to paste in the sheet, not open the composer with the pasted content
22680
+ if (!ev.isComposing && ev.inputType === "insertFromPaste") {
22681
+ return;
22682
+ }
22305
22683
  if (ev.data) {
22306
22684
  // if the user types a character on the grid, it means he wants to start composing the selected cell with that
22307
22685
  // character
@@ -23150,6 +23528,70 @@ function fixXlsxUnicode(str) {
23150
23528
  });
23151
23529
  }
23152
23530
 
23531
+ const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
23532
+ /**
23533
+ * Convert excel format to o_spreadsheet format
23534
+ *
23535
+ * Excel format are defined in openXML §18.8.31
23536
+ */
23537
+ function convertXlsxFormat(numFmtId, formats, warningManager) {
23538
+ var _a, _b, _c;
23539
+ if (numFmtId === 0) {
23540
+ return undefined;
23541
+ }
23542
+ // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23543
+ let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23544
+ if (format) {
23545
+ try {
23546
+ let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23547
+ convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23548
+ convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23549
+ // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23550
+ const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23551
+ const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23552
+ if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23553
+ throw new Error("Multiple escaped blocks in format");
23554
+ }
23555
+ convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23556
+ convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore width of next char for align purposes. Not supported ATM
23557
+ convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23558
+ convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23559
+ convertedFormat = convertedFormat.replace(/\\./g, (match) => match[1]); // unescape other characters
23560
+ if (isXlsxDateFormat(convertedFormat)) {
23561
+ convertedFormat = convertDateFormat$1(convertedFormat);
23562
+ }
23563
+ if (isFormatSupported(convertedFormat)) {
23564
+ return convertedFormat;
23565
+ }
23566
+ }
23567
+ catch (e) { }
23568
+ }
23569
+ warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23570
+ return undefined;
23571
+ }
23572
+ function isFormatSupported(format) {
23573
+ try {
23574
+ formatValue(0, format);
23575
+ return true;
23576
+ }
23577
+ catch (e) {
23578
+ return false;
23579
+ }
23580
+ }
23581
+ function isXlsxDateFormat(format) {
23582
+ return format.match(XLSX_DATE_FORMAT_REGEX) !== null;
23583
+ }
23584
+ function convertDateFormat$1(format) {
23585
+ // Some of these aren't defined neither in the OpenXML spec not the Xlsx extension of OpenXML,
23586
+ // but can still occur and are supported by Excel/Google sheets
23587
+ format = format.toLowerCase();
23588
+ format = format.replace(/mmmmm/g, "mmm");
23589
+ format = format.replace(/am\/pm|a\/m/g, "a");
23590
+ format = format.replace(/hhhh/g, "hh");
23591
+ format = format.replace(/\bh\b/g, "hh");
23592
+ return format;
23593
+ }
23594
+
23153
23595
  function convertBorders(data, warningManager) {
23154
23596
  const borderArray = data.borders.map((border) => {
23155
23597
  addBorderWarnings(border, warningManager);
@@ -23212,41 +23654,6 @@ function convertFormats(data, warningManager) {
23212
23654
  }
23213
23655
  return arrayToObject(formats, 1);
23214
23656
  }
23215
- /**
23216
- * Convert excel format to o_spreadsheet format
23217
- *
23218
- * Excel format are defined in openXML §18.8.31
23219
- */
23220
- function convertXlsxFormat(numFmtId, formats, warningManager) {
23221
- var _a, _b, _c;
23222
- if (numFmtId === 0) {
23223
- return undefined;
23224
- }
23225
- // Format is either defined in the imported data, or the formatId is defined in openXML §18.8.30
23226
- let format = XLSX_FORMATS_CONVERSION_MAP[numFmtId] || ((_a = formats.find((f) => f.id === numFmtId)) === null || _a === void 0 ? void 0 : _a.format);
23227
- if (format) {
23228
- try {
23229
- let convertedFormat = format.replace(/(.*?);.*/, "$1"); // only take first part of multi-part format
23230
- convertedFormat = convertedFormat.replace(/\[(.*)-[A-Z0-9]{3}\]/g, "[$1]"); // remove currency and locale/date system/number system info (ECMA §18.8.31)
23231
- convertedFormat = convertedFormat.replace(/\[\$\]/g, ""); // remove empty bocks
23232
- // Quotes in format escape sequences of characters. ATM we only support [$...] blocks to escape characters, and only one of them per format
23233
- const numberOfQuotes = ((_b = convertedFormat.match(/"/g)) === null || _b === void 0 ? void 0 : _b.length) || 0;
23234
- const numberOfOpenBrackets = ((_c = convertedFormat.match(/\[/g)) === null || _c === void 0 ? void 0 : _c.length) || 0;
23235
- if (numberOfQuotes / 2 + numberOfOpenBrackets > 1) {
23236
- throw new Error("Multiple escaped blocks in format");
23237
- }
23238
- convertedFormat = convertedFormat.replace(/"(.*)"/g, "[$$$1]"); // replace '"..."' by '[$...]'
23239
- convertedFormat = convertedFormat.replace(/_.{1}/g, ""); // _ == ignore with of next char for align purposes. Not supported ATM
23240
- convertedFormat = convertedFormat.replace(/\*.{1}/g, ""); // * == repeat next character enough to fill the line. Not supported ATM
23241
- convertedFormat = convertedFormat.replace(/\\ /g, " "); // unescape spaces
23242
- formatValue(0, convertedFormat);
23243
- return convertedFormat;
23244
- }
23245
- catch (e) { }
23246
- }
23247
- warningManager.generateNotSupportedWarning(WarningTypes.NumFmtIdNotSupported, format || `nmFmtId ${numFmtId}`);
23248
- return undefined;
23249
- }
23250
23657
  // ---------------------------------------------------------------------------
23251
23658
  // Warnings
23252
23659
  // ---------------------------------------------------------------------------
@@ -23801,16 +24208,18 @@ function convertFigure(figure, id, sheetData) {
23801
24208
  };
23802
24209
  }
23803
24210
  function convertChartData(chartData) {
23804
- var _a;
23805
- const labelRange = (_a = chartData.dataSets[0].label) === null || _a === void 0 ? void 0 : _a.replace(/\$/g, "");
23806
- let dataSets = chartData.dataSets.map((data) => data.range.replace(/\$/g, ""));
24211
+ const dataSetsHaveTitle = chartData.dataSets[0].label !== undefined;
24212
+ const labelRange = chartData.labelRange
24213
+ ? convertExcelRangeToSheetXC(chartData.labelRange, dataSetsHaveTitle)
24214
+ : undefined;
24215
+ let dataSets = chartData.dataSets.map((data) => convertExcelRangeToSheetXC(data.range, dataSetsHaveTitle));
23807
24216
  // For doughnut charts, in chartJS first dataset = outer dataset, in excel first dataset = inner dataset
23808
24217
  if (chartData.type === "pie") {
23809
24218
  dataSets.reverse();
23810
24219
  }
23811
24220
  return {
23812
24221
  dataSets,
23813
- dataSetsHaveTitle: false,
24222
+ dataSetsHaveTitle,
23814
24223
  labelRange,
23815
24224
  title: chartData.title || "",
23816
24225
  type: chartData.type,
@@ -23821,6 +24230,28 @@ function convertChartData(chartData) {
23821
24230
  aggregated: false,
23822
24231
  labelsAsText: false,
23823
24232
  };
24233
+ }
24234
+ function convertExcelRangeToSheetXC(range, dataSetsHaveTitle) {
24235
+ let { sheetName, xc } = splitReference(range);
24236
+ if (sheetName) {
24237
+ sheetName = getCanonicalSheetName(sheetName) + "!";
24238
+ }
24239
+ else {
24240
+ sheetName = "";
24241
+ }
24242
+ let zone = toUnboundedZone(xc);
24243
+ if (dataSetsHaveTitle && zone.bottom !== undefined && zone.right !== undefined) {
24244
+ const height = zone.bottom - zone.top + 1;
24245
+ const width = zone.right - zone.left + 1;
24246
+ if (height === 1) {
24247
+ zone = { ...zone, left: zone.left - 1 };
24248
+ }
24249
+ else if (width === 1) {
24250
+ zone = { ...zone, top: zone.top - 1 };
24251
+ }
24252
+ }
24253
+ const dataXC = zoneToXc(zone);
24254
+ return sheetName + dataXC;
23824
24255
  }
23825
24256
 
23826
24257
  /**
@@ -24888,6 +25319,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
24888
25319
  title: chartTitle,
24889
25320
  type: CHART_TYPE_CONVERSION_MAP[chartType],
24890
25321
  dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
25322
+ labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
24891
25323
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
24892
25324
  default: "ffffff",
24893
25325
  }).asString(),
@@ -24907,7 +25339,7 @@ class XlsxChartExtractor extends XlsxBaseExtractor {
24907
25339
  extractChartDatasets(chartElement) {
24908
25340
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
24909
25341
  return {
24910
- label: this.extractChildTextContent(chartDataElement, "c:cat c:f"),
25342
+ label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
24911
25343
  range: this.extractChildTextContent(chartDataElement, "c:val c:f", { required: true }),
24912
25344
  };
24913
25345
  });
@@ -28824,6 +29256,39 @@ class FigurePlugin extends CorePlugin {
28824
29256
  case "DELETE_FIGURE":
28825
29257
  this.removeFigure(cmd.id, cmd.sheetId);
28826
29258
  break;
29259
+ case "REMOVE_COLUMNS_ROWS":
29260
+ this.onRowColDelete(cmd.sheetId, cmd.dimension);
29261
+ }
29262
+ }
29263
+ onRowColDelete(sheetId, dimension) {
29264
+ dimension === "ROW" ? this.onRowDeletion(sheetId) : this.onColDeletion(sheetId);
29265
+ }
29266
+ onRowDeletion(sheetId) {
29267
+ const numHeader = this.getters.getNumberRows(sheetId);
29268
+ let gridHeight = 0;
29269
+ for (let i = 0; i < numHeader; i++) {
29270
+ gridHeight += this.getters.getRowSize(sheetId, i);
29271
+ }
29272
+ const figures = this.getters.getFigures(sheetId);
29273
+ for (const figure of figures) {
29274
+ const newY = Math.min(figure.y, gridHeight - figure.height);
29275
+ if (newY !== figure.y) {
29276
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, y: newY });
29277
+ }
29278
+ }
29279
+ }
29280
+ onColDeletion(sheetId) {
29281
+ const numHeader = this.getters.getNumberCols(sheetId);
29282
+ let gridWidth = 0;
29283
+ for (let i = 0; i < numHeader; i++) {
29284
+ gridWidth += this.getters.getColSize(sheetId, i);
29285
+ }
29286
+ const figures = this.getters.getFigures(sheetId);
29287
+ for (const figure of figures) {
29288
+ const newX = Math.min(figure.x, gridWidth - figure.width);
29289
+ if (newX !== figure.x) {
29290
+ this.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x: newX });
29291
+ }
28827
29292
  }
28828
29293
  }
28829
29294
  updateFigure(sheetId, figure) {
@@ -30238,6 +30703,7 @@ class RangeAdapter {
30238
30703
  }
30239
30704
  else if (range.zone[start] >= min && range.zone[end] <= max) {
30240
30705
  changeType = "REMOVE";
30706
+ newRange = range.clone({ ...this.getInvalidRange() });
30241
30707
  }
30242
30708
  else if (range.zone[start] <= max && range.zone[end] >= max) {
30243
30709
  const toRemove = max - range.zone[start] + 1;
@@ -30303,8 +30769,10 @@ class RangeAdapter {
30303
30769
  return { changeType: "NONE" };
30304
30770
  }
30305
30771
  const invalidSheetName = this.getters.getSheetName(cmd.sheetId);
30306
- const sheetId = "";
30307
- range = range.clone({ sheetId, invalidSheetName });
30772
+ range = range.clone({
30773
+ ...this.getInvalidRange(),
30774
+ invalidSheetName,
30775
+ });
30308
30776
  return { changeType: "REMOVE", range };
30309
30777
  }, cmd.sheetId);
30310
30778
  break;
@@ -30491,7 +30959,7 @@ class RangeAdapter {
30491
30959
  sheetName = rangeImpl.invalidSheetName;
30492
30960
  }
30493
30961
  else {
30494
- sheetName = getComposerSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30962
+ sheetName = getCanonicalSheetName(this.getters.getSheetName(rangeImpl.sheetId));
30495
30963
  }
30496
30964
  }
30497
30965
  if (prefixSheet && !sheetName) {
@@ -30564,6 +31032,15 @@ class RangeAdapter {
30564
31032
  }
30565
31033
  return str;
30566
31034
  }
31035
+ getInvalidRange() {
31036
+ return {
31037
+ parts: [],
31038
+ prefixSheet: false,
31039
+ zone: { left: -1, top: -1, right: -1, bottom: -1 },
31040
+ sheetId: "",
31041
+ invalidXc: INCORRECT_RANGE_STRING,
31042
+ };
31043
+ }
30567
31044
  }
30568
31045
  RangeAdapter.getters = [
30569
31046
  "getRangeString",
@@ -31533,9 +32010,7 @@ class CustomColorsPlugin extends UIPlugin {
31533
32010
  // remove duplicates first to check validity on a reduced
31534
32011
  // set of colors, then normalize to HEX and remove duplicates
31535
32012
  // again
31536
- [...new Set([...usedColors, ...this.customColors])]
31537
- .filter(isColorValid)
31538
- .map((c) => toHex(c).toLowerCase())),
32013
+ [...new Set([...usedColors, ...this.customColors])].filter(isColorValid).map(toHex)),
31539
32014
  ]).filter((color) => !COLOR_PICKER_DEFAULTS.includes(color));
31540
32015
  }
31541
32016
  getColorsFromCells(cells) {
@@ -31596,7 +32071,7 @@ class CustomColorsPlugin extends UIPlugin {
31596
32071
  return [...chartsColors];
31597
32072
  }
31598
32073
  tryToAddColor(color) {
31599
- const formattedColor = toHex(color).toLowerCase();
32074
+ const formattedColor = toHex(color);
31600
32075
  if (color && !COLOR_PICKER_DEFAULTS.includes(formattedColor)) {
31601
32076
  this.customColors.add(formattedColor);
31602
32077
  }
@@ -32611,8 +33086,8 @@ class InternalViewport {
32611
33086
  this.getters = getters;
32612
33087
  this.sheetId = sheetId;
32613
33088
  this.boundaries = boundaries;
32614
- this.width = sizeInGrid.width;
32615
- this.height = sizeInGrid.height;
33089
+ this.viewportWidth = sizeInGrid.width;
33090
+ this.viewportHeight = sizeInGrid.height;
32616
33091
  this.offsetScrollbarX = offsets.x;
32617
33092
  this.offsetScrollbarY = offsets.y;
32618
33093
  this.canScrollVertically = options.canScrollVertically;
@@ -32623,6 +33098,10 @@ class InternalViewport {
32623
33098
  this.adjustViewportOffsetY();
32624
33099
  }
32625
33100
  // PUBLIC
33101
+ /** Returns the maximum size (in Pixels) of the viewport relative to its allocated client size
33102
+ * When the viewport grid size is smaller than its client width (resp. height), it will return
33103
+ * the client width (resp. height).
33104
+ */
32626
33105
  getMaxSize() {
32627
33106
  const lastCol = this.getters.findLastVisibleColRowIndex(this.sheetId, "COL", {
32628
33107
  first: this.boundaries.left,
@@ -32634,20 +33113,24 @@ class InternalViewport {
32634
33113
  });
32635
33114
  const { end: lastColEnd, size: lastColSize } = this.getters.getColDimensions(this.sheetId, lastCol);
32636
33115
  const { end: lastRowEnd, size: lastRowSize } = this.getters.getRowDimensions(this.sheetId, lastRow);
32637
- const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.width, 0);
33116
+ const leftColIndex = this.searchHeaderIndex("COL", lastColEnd - this.viewportWidth, 0);
32638
33117
  const leftColSize = this.getters.getColSize(this.sheetId, leftColIndex);
32639
- const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.height, 0);
33118
+ const leftRowIndex = this.searchHeaderIndex("ROW", lastRowEnd - this.viewportHeight, 0);
32640
33119
  const topRowSize = this.getters.getRowSize(this.sheetId, leftRowIndex);
32641
- const width = lastColEnd -
32642
- this.offsetCorrectionX +
32643
- (this.canScrollHorizontally
32644
- ? Math.max(DEFAULT_CELL_WIDTH, Math.min(leftColSize, this.width - lastColSize))
32645
- : 0);
32646
- const height = lastRowEnd -
32647
- this.offsetCorrectionY +
32648
- (this.canScrollVertically
32649
- ? Math.max(DEFAULT_CELL_HEIGHT + 5, Math.min(topRowSize, this.height - lastRowSize))
32650
- : 0);
33120
+ let width = lastColEnd - this.offsetCorrectionX;
33121
+ if (this.canScrollHorizontally) {
33122
+ width += Math.max(DEFAULT_CELL_WIDTH, // leave some minimal space to let the user know they scrolled all the way
33123
+ Math.min(leftColSize, this.viewportWidth - lastColSize) // Add pixels that allows the snapping at maximum horizontal scroll
33124
+ );
33125
+ width = Math.max(width, this.viewportWidth); // if the viewport grid size is smaller than its client width, return client width
33126
+ }
33127
+ let height = lastRowEnd - this.offsetCorrectionY;
33128
+ if (this.canScrollVertically) {
33129
+ height += Math.max(DEFAULT_CELL_HEIGHT + 5, // leave some space to let the user know they scrolled all the way
33130
+ Math.min(topRowSize, this.viewportHeight - lastRowSize) // Add pixels that allows the snapping at maximum vertical scroll
33131
+ );
33132
+ height = Math.max(height, this.viewportHeight); // if the viewport grid size is smaller than its client height, return client height
33133
+ }
32651
33134
  return { width, height };
32652
33135
  }
32653
33136
  /**
@@ -32656,7 +33139,7 @@ class InternalViewport {
32656
33139
  * It returns -1 if no column is found.
32657
33140
  */
32658
33141
  getColIndex(x, absolute = false) {
32659
- if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.width) {
33142
+ if (x < this.offsetCorrectionX || x > this.offsetCorrectionX + this.viewportWidth) {
32660
33143
  return -1;
32661
33144
  }
32662
33145
  return this.searchHeaderIndex("COL", x - this.offsetCorrectionX, this.left, absolute);
@@ -32667,7 +33150,7 @@ class InternalViewport {
32667
33150
  * It returns -1 if no row is found.
32668
33151
  */
32669
33152
  getRowIndex(y, absolute = false) {
32670
- if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.height) {
33153
+ if (y < this.offsetCorrectionY || y > this.offsetCorrectionY + this.viewportHeight) {
32671
33154
  return -1;
32672
33155
  }
32673
33156
  return this.searchHeaderIndex("ROW", y - this.offsetCorrectionY, this.top, absolute);
@@ -32694,7 +33177,7 @@ class InternalViewport {
32694
33177
  adjustPositionX(col) {
32695
33178
  const sheetId = this.sheetId;
32696
33179
  const { start, end } = this.getters.getColDimensions(sheetId, col);
32697
- while (end > this.offsetX + this.offsetCorrectionX + this.width &&
33180
+ while (end > this.offsetX + this.offsetCorrectionX + this.viewportWidth &&
32698
33181
  this.offsetX + this.offsetCorrectionX < start) {
32699
33182
  this.offsetX = this.getters.getColDimensions(sheetId, this.left).end - this.offsetCorrectionX;
32700
33183
  this.offsetScrollbarX = this.offsetX;
@@ -32716,7 +33199,7 @@ class InternalViewport {
32716
33199
  adjustPositionY(row) {
32717
33200
  const sheetId = this.sheetId;
32718
33201
  while (this.getters.getRowDimensions(sheetId, row).end >
32719
- this.offsetY + this.height + this.offsetCorrectionY &&
33202
+ this.offsetY + this.viewportHeight + this.offsetCorrectionY &&
32720
33203
  this.offsetY + this.offsetCorrectionY < this.getters.getRowDimensions(sheetId, row).start) {
32721
33204
  this.offsetY = this.getters.getRowDimensions(sheetId, this.top).end - this.offsetCorrectionY;
32722
33205
  this.offsetScrollbarY = this.offsetY;
@@ -32754,8 +33237,8 @@ class InternalViewport {
32754
33237
  const x = this.getters.getColRowOffset("COL", this.zone.left, targetZone.left) +
32755
33238
  this.offsetCorrectionX;
32756
33239
  const y = this.getters.getColRowOffset("ROW", this.zone.top, targetZone.top) + this.offsetCorrectionY;
32757
- const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.width);
32758
- const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.height);
33240
+ const width = Math.min(this.getters.getColRowOffset("COL", targetZone.left, targetZone.right + 1), this.viewportWidth);
33241
+ const height = Math.min(this.getters.getColRowOffset("ROW", targetZone.top, targetZone.bottom + 1), this.viewportHeight);
32759
33242
  return {
32760
33243
  x,
32761
33244
  y,
@@ -32818,12 +33301,12 @@ class InternalViewport {
32818
33301
  adjustViewportOffsetX() {
32819
33302
  if (this.canScrollHorizontally) {
32820
33303
  const { width: viewportWidth } = this.getMaxSize();
32821
- if (this.width + this.offsetScrollbarX > viewportWidth) {
32822
- this.offsetScrollbarX = Math.max(0, viewportWidth - this.width);
33304
+ if (this.viewportWidth + this.offsetScrollbarX > viewportWidth) {
33305
+ this.offsetScrollbarX = Math.max(0, viewportWidth - this.viewportWidth);
32823
33306
  }
32824
33307
  }
32825
33308
  this.left = this.getColIndex(this.offsetScrollbarX, true);
32826
- this.right = this.getColIndex(this.offsetScrollbarX + this.width, true);
33309
+ this.right = this.getColIndex(this.offsetScrollbarX + this.viewportWidth, true);
32827
33310
  if (this.right === -1) {
32828
33311
  this.right = this.boundaries.right;
32829
33312
  }
@@ -32835,12 +33318,12 @@ class InternalViewport {
32835
33318
  adjustViewportOffsetY() {
32836
33319
  if (this.canScrollVertically) {
32837
33320
  const { height: paneHeight } = this.getMaxSize();
32838
- if (this.height + this.offsetScrollbarY > paneHeight) {
32839
- this.offsetScrollbarY = Math.max(0, paneHeight - this.height);
33321
+ if (this.viewportHeight + this.offsetScrollbarY > paneHeight) {
33322
+ this.offsetScrollbarY = Math.max(0, paneHeight - this.viewportHeight);
32840
33323
  }
32841
33324
  }
32842
33325
  this.top = this.getRowIndex(this.offsetScrollbarY, true);
32843
- this.bottom = this.getRowIndex(this.offsetScrollbarY + this.width, true);
33326
+ this.bottom = this.getRowIndex(this.offsetScrollbarY + this.viewportWidth, true);
32844
33327
  if (this.bottom === -1) {
32845
33328
  this.bottom = this.boundaries.bottom;
32846
33329
  }
@@ -32851,7 +33334,7 @@ class InternalViewport {
32851
33334
  adjustViewportZoneX() {
32852
33335
  const sheetId = this.sheetId;
32853
33336
  this.left = this.searchHeaderIndex("COL", this.offsetScrollbarX, this.boundaries.left);
32854
- this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.width, this.left));
33337
+ this.right = Math.min(this.boundaries.right, this.searchHeaderIndex("COL", this.viewportWidth, this.left));
32855
33338
  if (this.right === -1) {
32856
33339
  this.right = this.getters.getNumberCols(sheetId) - 1;
32857
33340
  }
@@ -32864,7 +33347,7 @@ class InternalViewport {
32864
33347
  adjustViewportZoneY() {
32865
33348
  const sheetId = this.sheetId;
32866
33349
  this.top = this.searchHeaderIndex("ROW", this.offsetScrollbarY, this.boundaries.top);
32867
- this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.height, this.top));
33350
+ this.bottom = Math.min(this.boundaries.bottom, this.searchHeaderIndex("ROW", this.viewportHeight, this.top));
32868
33351
  if (this.bottom === -1) {
32869
33352
  this.bottom = this.getters.getNumberRows(sheetId) - 1;
32870
33353
  }
@@ -33127,8 +33610,7 @@ class SheetViewPlugin extends UIPlugin {
33127
33610
  return [...new Set(viewports.map((v) => range(v.top, v.bottom + 1)).flat())].filter((row) => !this.getters.isHeaderHidden(sheetId, "ROW", row));
33128
33611
  }
33129
33612
  /**
33130
- * Return the main viewport maximum size. That is the zone dimension
33131
- * with some bottom and right padding.
33613
+ * Return the main viewport maximum size relative to the client size.
33132
33614
  */
33133
33615
  getMainViewportRect() {
33134
33616
  const sheetId = this.getters.getActiveSheetId();
@@ -33144,8 +33626,8 @@ class SheetViewPlugin extends UIPlugin {
33144
33626
  const { width, height } = this.getMainViewportRect();
33145
33627
  const viewport = this.getMainInternalViewport(sheetId);
33146
33628
  return {
33147
- maxOffsetX: Math.max(0, width - viewport.width + 1),
33148
- maxOffsetY: Math.max(0, height - viewport.height + 1),
33629
+ maxOffsetX: Math.max(0, width - viewport.viewportWidth + 1),
33630
+ maxOffsetY: Math.max(0, height - viewport.viewportHeight + 1),
33149
33631
  };
33150
33632
  }
33151
33633
  getColRowOffsetInViewport(dimension, referenceIndex, index) {
@@ -33460,6 +33942,20 @@ class SheetViewPlugin extends UIPlugin {
33460
33942
  }
33461
33943
  return result;
33462
33944
  }
33945
+ isPositionVisible(position) {
33946
+ const { scrollX, scrollY } = this.getters.getActiveSheetScrollInfo();
33947
+ const { x: mainViewportX, y: mainViewportY } = this.getters.getMainViewportCoordinates();
33948
+ const { width, height } = this.getters.getSheetViewDimension();
33949
+ if (position.x >= mainViewportX &&
33950
+ (position.x < mainViewportX + scrollX || position.x > width + scrollX + mainViewportX)) {
33951
+ return false;
33952
+ }
33953
+ if (position.y >= mainViewportY &&
33954
+ (position.y < mainViewportY + scrollY || position.y > height + scrollY + mainViewportY)) {
33955
+ return false;
33956
+ }
33957
+ return true;
33958
+ }
33463
33959
  getFrozenSheetViewRatio(sheetId) {
33464
33960
  const { xSplit, ySplit } = this.getters.getPaneDivisions(sheetId);
33465
33961
  const offsetCorrectionX = this.getters.getColDimensions(sheetId, xSplit).start;
@@ -33488,6 +33984,7 @@ SheetViewPlugin.getters = [
33488
33984
  "getSheetViewVisibleCols",
33489
33985
  "getSheetViewVisibleRows",
33490
33986
  "getFrozenSheetViewRatio",
33987
+ "isPositionVisible",
33491
33988
  ];
33492
33989
 
33493
33990
  /**
@@ -35350,7 +35847,7 @@ class SelectionInputPlugin extends UIPlugin {
35350
35847
  const inputSheetId = this.activeSheet;
35351
35848
  const sheetId = this.getters.getActiveSheetId();
35352
35849
  const sheetName = this.getters.getSheetName(sheetId);
35353
- this.add([sheetId === inputSheetId ? xc : `${getComposerSheetName(sheetName)}!${xc}`]);
35850
+ this.add([sheetId === inputSheetId ? xc : `${getCanonicalSheetName(sheetName)}!${xc}`]);
35354
35851
  }
35355
35852
  handle(cmd) {
35356
35853
  switch (cmd.type) {
@@ -37597,13 +38094,19 @@ class ClipboardFigureState {
37597
38094
  */
37598
38095
  paste(target) {
37599
38096
  const sheetId = this.getters.getActiveSheetId();
38097
+ const { width, height } = this.copiedFigure;
38098
+ const numCols = this.getters.getNumberCols(sheetId);
38099
+ const numRows = this.getters.getNumberRows(sheetId);
38100
+ const targetX = this.getters.getColDimensions(sheetId, target[0].left).start;
38101
+ const targetY = this.getters.getRowDimensions(sheetId, target[0].top).start;
38102
+ const maxX = this.getters.getColDimensions(sheetId, numCols - 1).end;
38103
+ const maxY = this.getters.getRowDimensions(sheetId, numRows - 1).end;
37600
38104
  const position = {
37601
- x: this.getters.getColDimensions(sheetId, target[0].left).start,
37602
- y: this.getters.getRowDimensions(sheetId, target[0].top).start,
38105
+ x: maxX < width ? 0 : Math.min(targetX, maxX - width),
38106
+ y: maxY < height ? 0 : Math.min(targetY, maxY - height),
37603
38107
  };
37604
- const size = { height: this.copiedFigure.height, width: this.copiedFigure.width };
37605
38108
  const newId = new UuidGenerator().uuidv4();
37606
- this.copiedFigureContent.paste(sheetId, newId, position, size);
38109
+ this.copiedFigureContent.paste(sheetId, newId, position, { height, width });
37607
38110
  if (this.operation === "CUT") {
37608
38111
  this.dispatch("DELETE_FIGURE", {
37609
38112
  sheetId: this.copiedFigureContent.sheetId,
@@ -37969,7 +38472,7 @@ function loopThroughReferenceType(token) {
37969
38472
  return token;
37970
38473
  const { xc, sheetName } = splitReference(token.value);
37971
38474
  const [left, right] = xc.split(":");
37972
- const sheetRef = sheetName ? `${getComposerSheetName(sheetName)}!` : "";
38475
+ const sheetRef = sheetName ? `${getCanonicalSheetName(sheetName)}!` : "";
37973
38476
  const updatedLeft = getTokenNextReferenceType(left);
37974
38477
  const updatedRight = right ? `:${getTokenNextReferenceType(right)}` : "";
37975
38478
  return { ...token, value: sheetRef + updatedLeft + updatedRight };
@@ -38228,9 +38731,6 @@ class EditionPlugin extends UIPlugin {
38228
38731
  }
38229
38732
  return this.currentContent;
38230
38733
  }
38231
- getEditionSheet() {
38232
- return this.sheetId;
38233
- }
38234
38734
  getComposerSelection() {
38235
38735
  return {
38236
38736
  start: this.selectionStart,
@@ -38514,8 +39014,8 @@ class EditionPlugin extends UIPlugin {
38514
39014
  getZoneReference(zone, fixedParts = [{ colFixed: false, rowFixed: false }]) {
38515
39015
  const sheetId = this.getters.getActiveSheetId();
38516
39016
  let selectedXc = this.getters.zoneToXC(sheetId, zone, fixedParts);
38517
- if (this.getters.getEditionSheet() !== this.getters.getActiveSheetId()) {
38518
- const sheetName = getComposerSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
39017
+ if (this.getters.getCurrentEditedCell().sheetId !== this.getters.getActiveSheetId()) {
39018
+ const sheetName = getCanonicalSheetName(this.getters.getSheetName(this.getters.getActiveSheetId()));
38519
39019
  selectedXc = `${sheetName}!${selectedXc}`;
38520
39020
  }
38521
39021
  return selectedXc;
@@ -38529,7 +39029,7 @@ class EditionPlugin extends UIPlugin {
38529
39029
  _fixedParts.pop();
38530
39030
  }
38531
39031
  const newRange = range.clone({ parts: _fixedParts });
38532
- return this.getters.getSelectionRangeString(newRange, this.getters.getEditionSheet());
39032
+ return this.getters.getSelectionRangeString(newRange, this.getters.getCurrentEditedCell().sheetId);
38533
39033
  }
38534
39034
  /**
38535
39035
  * Replace the current selection by a new text.
@@ -38562,7 +39062,7 @@ class EditionPlugin extends UIPlugin {
38562
39062
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38563
39063
  return;
38564
39064
  }
38565
- const editionSheetId = this.getters.getEditionSheet();
39065
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38566
39066
  const XCs = this.getReferencedRanges().map((range) => this.getters.getRangeString(range, editionSheetId));
38567
39067
  const colorsToKeep = {};
38568
39068
  for (const xc of XCs) {
@@ -38591,7 +39091,7 @@ class EditionPlugin extends UIPlugin {
38591
39091
  if (!this.currentContent.startsWith("=") || this.mode === "inactive") {
38592
39092
  return [];
38593
39093
  }
38594
- const editionSheetId = this.getters.getEditionSheet();
39094
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38595
39095
  const rangeColor = (rangeString) => {
38596
39096
  const colorIndex = this.colorIndexByRange[rangeString];
38597
39097
  return colors$1[colorIndex % colors$1.length];
@@ -38609,7 +39109,7 @@ class EditionPlugin extends UIPlugin {
38609
39109
  * Return ranges currently referenced in the composer
38610
39110
  */
38611
39111
  getReferencedRanges() {
38612
- const editionSheetId = this.getters.getEditionSheet();
39112
+ const editionSheetId = this.getters.getCurrentEditedCell().sheetId;
38613
39113
  return this.currentTokens
38614
39114
  .filter((token) => token.type === "REFERENCE")
38615
39115
  .map((token) => this.getters.getRangeFromSheetXC(editionSheetId, token.value));
@@ -38668,7 +39168,6 @@ EditionPlugin.getters = [
38668
39168
  "isSelectingForComposer",
38669
39169
  "showSelectionIndicator",
38670
39170
  "getCurrentContent",
38671
- "getEditionSheet",
38672
39171
  "getComposerSelection",
38673
39172
  "getCurrentTokens",
38674
39173
  "getTokenAtCursor",
@@ -39991,10 +40490,6 @@ css /* scss */ `
39991
40490
  .o-bottom-bar-arrows {
39992
40491
  .o-bottom-bar-arrow {
39993
40492
  cursor: pointer;
39994
- &.o-disabled {
39995
- opacity: 0.4;
39996
- cursor: default;
39997
- }
39998
40493
  &:hover:not([class*="o-disabled"]) {
39999
40494
  .o-icon {
40000
40495
  opacity: 0.9;
@@ -40587,6 +41082,11 @@ css /* scss */ `
40587
41082
  margin-top: -1px;
40588
41083
  border: 1px solid;
40589
41084
  z-index: ${ComponentsImportance.TopBarComposer};
41085
+
41086
+ .o-composer:empty:not(:focus)::before {
41087
+ /* svg free of use from https://uxwing.com/formula-fx-icon/ */
41088
+ 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");
41089
+ }
40590
41090
  }
40591
41091
  `;
40592
41092
  class TopBarComposer extends Component {
@@ -40827,11 +41327,6 @@ css /* scss */ `
40827
41327
  margin: 0 6px;
40828
41328
  }
40829
41329
 
40830
- .o-disabled {
40831
- opacity: 0.6;
40832
- cursor: default;
40833
- }
40834
-
40835
41330
  .o-dropdown {
40836
41331
  position: relative;
40837
41332
  display: flex;
@@ -40993,6 +41488,7 @@ class TopBar extends Component {
40993
41488
  this.state.menuState.parentMenu = menu;
40994
41489
  this.isSelectingMenu = true;
40995
41490
  this.openedEl = ev.target;
41491
+ this.env.model.dispatch("STOP_EDITION");
40996
41492
  }
40997
41493
  closeMenus() {
40998
41494
  this.state.activeTool = "";
@@ -41205,10 +41701,12 @@ css /* scss */ `
41205
41701
  button {
41206
41702
  color: #333;
41207
41703
  }
41208
-
41209
- * {
41210
- font-family: "Roboto", "RobotoDraft", Helvetica, Arial, sans-serif;
41704
+ .o-disabled {
41705
+ opacity: 0.4;
41706
+ pointer: default;
41707
+ pointer-events: none;
41211
41708
  }
41709
+
41212
41710
  &,
41213
41711
  *,
41214
41712
  *:before,
@@ -41366,8 +41864,10 @@ class Spreadsheet extends Component {
41366
41864
  this.sidePanel.isOpen = true;
41367
41865
  }
41368
41866
  closeSidePanel() {
41867
+ var _a, _b;
41369
41868
  this.sidePanel.isOpen = false;
41370
41869
  this.focusGrid();
41870
+ (_b = (_a = this.sidePanel.panelProps) === null || _a === void 0 ? void 0 : _a.onCloseSidePanel) === null || _b === void 0 ? void 0 : _b.call(_a);
41371
41871
  }
41372
41872
  toggleSidePanel(panel, panelProps) {
41373
41873
  if (this.sidePanel.isOpen && panel === this.sidePanel.component) {
@@ -43806,7 +44306,7 @@ function figureCoordinates(headers, position) {
43806
44306
  if (currentPosition <= position && position < currentPosition + header.size) {
43807
44307
  return {
43808
44308
  index: parseInt(headerIndex),
43809
- offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
44309
+ offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
43810
44310
  };
43811
44311
  }
43812
44312
  else {
@@ -43815,7 +44315,7 @@ function figureCoordinates(headers, position) {
43815
44315
  }
43816
44316
  return {
43817
44317
  index: headers.length - 1,
43818
- offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_SIZE),
44318
+ offset: convertDotValueToEMU(position - currentPosition + FIGURE_BORDER_WIDTH),
43819
44319
  };
43820
44320
  }
43821
44321
  function createChartDrawing(figure, sheet, chartRelId) {
@@ -44575,6 +45075,13 @@ class Model extends EventBus {
44575
45075
  * Internal status of the model. Important for command handling coordination
44576
45076
  */
44577
45077
  this.status = 0 /* Status.Ready */;
45078
+ /**
45079
+ * Check if a command can be dispatched, and returns a DispatchResult object with the possible
45080
+ * reasons the dispatch failed.
45081
+ */
45082
+ this.canDispatch = (type, payload) => {
45083
+ return this.checkDispatchAllowed({ ...payload, type });
45084
+ };
44578
45085
  /**
44579
45086
  * The dispatch method is the only entry point to manipulate data in the model.
44580
45087
  * This is through this method that commands are dispatched most of the time
@@ -44718,7 +45225,10 @@ class Model extends EventBus {
44718
45225
  markRaw(this);
44719
45226
  }
44720
45227
  get handlers() {
44721
- return [this.range, ...this.corePlugins, ...this.allUIPlugins, this.history];
45228
+ return [...this.coreHandlers, ...this.allUIPlugins, this.history];
45229
+ }
45230
+ get coreHandlers() {
45231
+ return [this.range, ...this.corePlugins];
44722
45232
  }
44723
45233
  get allUIPlugins() {
44724
45234
  return [...this.statefulUIPlugins, ...this.coreViewsPlugins, ...this.featurePlugins];
@@ -44846,6 +45356,16 @@ class Model extends EventBus {
44846
45356
  * Check if the given command is allowed by all the plugins and the history.
44847
45357
  */
44848
45358
  checkDispatchAllowed(command) {
45359
+ if (isCoreCommand(command)) {
45360
+ return this.checkDispatchAllowedCoreCommand(command);
45361
+ }
45362
+ return this.checkDispatchAllowedLocalCommand(command);
45363
+ }
45364
+ checkDispatchAllowedCoreCommand(command) {
45365
+ const results = this.coreHandlers.map((handler) => handler.allowDispatch(command));
45366
+ return new DispatchResult(results.flat());
45367
+ }
45368
+ checkDispatchAllowedLocalCommand(command) {
44849
45369
  const results = this.handlers.map((handler) => handler.allowDispatch(command));
44850
45370
  return new DispatchResult(results.flat());
44851
45371
  }
@@ -45046,6 +45566,6 @@ function addFunction(functionName, functionDescription) {
45046
45566
  export { AbstractChart, CommandResult, CorePlugin, DATETIME_FORMAT, DispatchResult, EvaluationError, Model, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, UIPlugin, __info__, addFunction, astToFormula, compile, components, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, invalidateEvaluationCommands, links, load, parse, readonlyAllowedCommands, registries, setTranslationMethod, tokenize };
45047
45567
 
45048
45568
 
45049
- __info__.version = '16.3.0-alpha.1';
45050
- __info__.date = '2023-03-16T12:12:26.034Z';
45051
- __info__.hash = 'f41d64a';
45569
+ __info__.version = '16.3.0-alpha.3';
45570
+ __info__.date = '2023-04-13T08:30:41.165Z';
45571
+ __info__.hash = '573e679';