@odoo/o-spreadsheet 18.4.0-alpha.6 → 18.4.0-alpha.7

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.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.4.0-alpha.6
6
- * @date 2025-05-30T08:44:33.216Z
7
- * @hash eecf7e4
5
+ * @version 18.4.0-alpha.7
6
+ * @date 2025-06-06T09:32:44.285Z
7
+ * @hash 2bfbe64
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -280,8 +280,10 @@
280
280
  const MIN_COL_WIDTH = 5;
281
281
  const HEADER_HEIGHT = 26;
282
282
  const HEADER_WIDTH = 48;
283
- const TOPBAR_TOOLBAR_HEIGHT = 34;
284
- const BOTTOMBAR_HEIGHT = 36;
283
+ const DESKTOP_TOPBAR_TOOLBAR_HEIGHT = 34;
284
+ const MOBILE_TOPBAR_TOOLBAR_HEIGHT = 44;
285
+ const DESKTOP_BOTTOMBAR_HEIGHT = 36;
286
+ const MOBILE_BOTTOMBAR_HEIGHT = 44;
285
287
  const DEFAULT_CELL_WIDTH = 96;
286
288
  const DEFAULT_CELL_HEIGHT = 23;
287
289
  const SCROLLBAR_WIDTH = 15;
@@ -302,7 +304,8 @@
302
304
  // Menus
303
305
  const MENU_WIDTH = 250;
304
306
  const MENU_VERTICAL_PADDING = 6;
305
- const MENU_ITEM_HEIGHT = 26;
307
+ const DESKTOP_MENU_ITEM_HEIGHT = 26;
308
+ const MOBILE_MENU_ITEM_HEIGHT = 35;
306
309
  const MENU_ITEM_PADDING_HORIZONTAL = 11;
307
310
  const MENU_ITEM_PADDING_VERTICAL = 4;
308
311
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
@@ -400,6 +403,7 @@
400
403
  automaticAutofill: false,
401
404
  };
402
405
  const PIVOT_INDENT = 15;
406
+ const PIVOT_COLLAPSE_ICON_SIZE = 12;
403
407
  const DEFAULT_CURRENCY = {
404
408
  symbol: "$",
405
409
  position: "before",
@@ -20557,7 +20561,7 @@ stores.inject(MyMetaStore, storeInstance);
20557
20561
  description: _t("Converts a number to text according to a specified format."),
20558
20562
  args: [
20559
20563
  arg("number (number)", _t("The number, date or time to format.")),
20560
- arg("format (string)", _t("The pattern by which to format the number, enclosed in quotation marks.")),
20564
+ arg("format (string)", _t('The case-sensitive format of the result, enclosed in quotation marks. Examples: "0.00" rounded to 2 decimal places, "hh:mm:ss" for hour:minutes:seconds.')),
20561
20565
  ],
20562
20566
  compute: function (number, format) {
20563
20567
  const _number = toNumber(number, this.locale);
@@ -21664,8 +21668,6 @@ stores.inject(MyMetaStore, storeInstance);
21664
21668
  if (isNaN(value)) {
21665
21669
  continue;
21666
21670
  }
21667
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
21668
- const displayValue = options.callback(Number(value), axisId);
21669
21671
  const point = dataset.data[i];
21670
21672
  const xPosition = point.x;
21671
21673
  let yPosition = 0;
@@ -21689,7 +21691,8 @@ stores.inject(MyMetaStore, storeInstance);
21689
21691
  textsPositions[xPosition].push(yPosition);
21690
21692
  ctx.fillStyle = point.options.backgroundColor;
21691
21693
  ctx.strokeStyle = options.background || "#ffffff";
21692
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
21694
+ const valueToDisplay = options.callback(Number(value), dataset, i);
21695
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
21693
21696
  }
21694
21697
  }
21695
21698
  }
@@ -21706,7 +21709,7 @@ stores.inject(MyMetaStore, storeInstance);
21706
21709
  if (isNaN(value)) {
21707
21710
  continue;
21708
21711
  }
21709
- const displayValue = options.callback(value, dataset.xAxisID);
21712
+ const displayValue = options.callback(value, dataset, i);
21710
21713
  const point = dataset.data[i];
21711
21714
  const yPosition = point.y;
21712
21715
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -21744,7 +21747,7 @@ stores.inject(MyMetaStore, storeInstance);
21744
21747
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
21745
21748
  ctx.fillStyle = chartFontColor(options.background);
21746
21749
  ctx.strokeStyle = options.background || "#ffffff";
21747
- const displayValue = options.callback(value, "y");
21750
+ const displayValue = options.callback(value, dataset, i);
21748
21751
  drawTextWithBackground(displayValue, x, y, ctx);
21749
21752
  }
21750
21753
  }
@@ -23491,7 +23494,7 @@ stores.inject(MyMetaStore, storeInstance);
23491
23494
  * On Mac, this is the "meta" or "command" key.
23492
23495
  */
23493
23496
  function isCtrlKey(ev) {
23494
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
23497
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
23495
23498
  }
23496
23499
  /**
23497
23500
  * @param {MouseEvent} ev - The mouse event.
@@ -23530,6 +23533,23 @@ stores.inject(MyMetaStore, storeInstance);
23530
23533
  function isBrowserFirefox() {
23531
23534
  return /Firefox/i.test(navigator.userAgent);
23532
23535
  }
23536
+ // Mobile detection
23537
+ function maxTouchPoints() {
23538
+ return navigator.maxTouchPoints || 1;
23539
+ }
23540
+ function isAndroid() {
23541
+ return /Android/i.test(navigator.userAgent);
23542
+ }
23543
+ function isIOS() {
23544
+ return (/(iPad|iPhone|iPod)/i.test(navigator.userAgent) ||
23545
+ (navigator.platform === "MacIntel" && maxTouchPoints() > 1));
23546
+ }
23547
+ function isOtherMobileOS() {
23548
+ return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
23549
+ }
23550
+ function isMobileOS() {
23551
+ return isAndroid() || isIOS() || isOtherMobileOS();
23552
+ }
23533
23553
 
23534
23554
  /**
23535
23555
  * Convert a JS color hexadecimal to an excel compatible color.
@@ -25050,14 +25070,14 @@ stores.inject(MyMetaStore, storeInstance);
25050
25070
  ...getLegendDisplayOptions(definition),
25051
25071
  labels: {
25052
25072
  usePointStyle: true,
25053
- generateLabels: (c) => c.data.labels?.map((label, index) => ({
25073
+ generateLabels: (c) => (c.data.labels?.map((label, index) => ({
25054
25074
  text: truncateLabel(String(label)),
25055
25075
  strokeStyle: colors[index],
25056
25076
  fillStyle: colors[index],
25057
25077
  pointStyle: "rect",
25058
25078
  lineWidth: 2,
25059
25079
  fontColor,
25060
- })) || [],
25080
+ })) || []).filter((label) => label.text),
25061
25081
  filter: (legendItem, data) => {
25062
25082
  return "datasetIndex" in legendItem
25063
25083
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25217,7 +25237,8 @@ stores.inject(MyMetaStore, storeInstance);
25217
25237
  labels: {
25218
25238
  color: fontColor,
25219
25239
  usePointStyle: true,
25220
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
25240
+ generateLabels: (chart) => chart.data.datasets
25241
+ .map((dataset, index) => {
25221
25242
  if (isTrendLineAxis(dataset["xAxisID"])) {
25222
25243
  return {
25223
25244
  text: truncateLabel(dataset.label),
@@ -25239,7 +25260,8 @@ stores.inject(MyMetaStore, storeInstance);
25239
25260
  datasetIndex: index,
25240
25261
  ...legendLabelConfig,
25241
25262
  };
25242
- }),
25263
+ })
25264
+ .filter((label) => label.text),
25243
25265
  filter: (legendItem, data) => {
25244
25266
  return "datasetIndex" in legendItem
25245
25267
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25593,7 +25615,10 @@ stores.inject(MyMetaStore, storeInstance);
25593
25615
  horizontal: "horizontal" in definition && definition.horizontal,
25594
25616
  showValues: "showValues" in definition ? !!definition.showValues : false,
25595
25617
  background: definition.background,
25596
- callback: formatChartDatasetValue(axisFormats, locale),
25618
+ callback: (value, dataset) => {
25619
+ const axisId = getDatasetAxisId(definition, dataset);
25620
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
25621
+ },
25597
25622
  };
25598
25623
  }
25599
25624
  function getSunburstShowValues(definition, args) {
@@ -25611,6 +25636,45 @@ stores.inject(MyMetaStore, storeInstance);
25611
25636
  },
25612
25637
  };
25613
25638
  }
25639
+ function getPyramidChartShowValues(definition, args) {
25640
+ const { axisFormats, locale } = args;
25641
+ return {
25642
+ horizontal: true,
25643
+ showValues: "showValues" in definition ? !!definition.showValues : false,
25644
+ background: definition.background,
25645
+ callback: (value, dataset) => {
25646
+ value = Math.abs(Number(value));
25647
+ return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
25648
+ },
25649
+ };
25650
+ }
25651
+ function getWaterfallChartShowValues(definition, args) {
25652
+ const { axisFormats, locale, dataSetsValues } = args;
25653
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
25654
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
25655
+ return subtotalIndexes;
25656
+ }, []);
25657
+ return {
25658
+ showValues: "showValues" in definition ? !!definition.showValues : false,
25659
+ background: definition.background,
25660
+ callback: (value, dataset, index) => {
25661
+ const raw = dataset._dataset.data[index];
25662
+ const delta = raw[1] - raw[0];
25663
+ let sign = delta >= 0 ? "+" : "";
25664
+ if (definition.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
25665
+ sign = "";
25666
+ }
25667
+ return `${sign}${formatChartDatasetValue(axisFormats, locale)(delta, dataset.yAxisID)}`;
25668
+ },
25669
+ };
25670
+ }
25671
+ function getDatasetAxisId(definition, dataset) {
25672
+ if (dataset.rAxisID) {
25673
+ return dataset.rAxisID;
25674
+ }
25675
+ const axisId = "horizontal" in definition && definition.horizontal ? dataset.xAxisID : dataset.yAxisID;
25676
+ return axisId || "y";
25677
+ }
25614
25678
 
25615
25679
  function getChartTitle(definition) {
25616
25680
  const chartTitle = definition.title;
@@ -26013,6 +26077,7 @@ stores.inject(MyMetaStore, storeInstance);
26013
26077
  getPieChartTooltip: getPieChartTooltip,
26014
26078
  getPyramidChartData: getPyramidChartData,
26015
26079
  getPyramidChartScales: getPyramidChartScales,
26080
+ getPyramidChartShowValues: getPyramidChartShowValues,
26016
26081
  getPyramidChartTooltip: getPyramidChartTooltip,
26017
26082
  getRadarChartData: getRadarChartData,
26018
26083
  getRadarChartDatasets: getRadarChartDatasets,
@@ -26033,6 +26098,7 @@ stores.inject(MyMetaStore, storeInstance);
26033
26098
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
26034
26099
  getWaterfallChartLegend: getWaterfallChartLegend,
26035
26100
  getWaterfallChartScales: getWaterfallChartScales,
26101
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
26036
26102
  getWaterfallChartTooltip: getWaterfallChartTooltip,
26037
26103
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels,
26038
26104
  makeDatasetsCumulative: makeDatasetsCumulative
@@ -27345,7 +27411,7 @@ stores.inject(MyMetaStore, storeInstance);
27345
27411
  title: getChartTitle(definition),
27346
27412
  legend: getBarChartLegend(definition),
27347
27413
  tooltip: getPyramidChartTooltip(definition, chartData),
27348
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
27414
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
27349
27415
  },
27350
27416
  },
27351
27417
  };
@@ -28093,7 +28159,7 @@ stores.inject(MyMetaStore, storeInstance);
28093
28159
  title: getChartTitle(definition),
28094
28160
  legend: getWaterfallChartLegend(definition),
28095
28161
  tooltip: getWaterfallChartTooltip(definition, chartData),
28096
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
28162
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
28097
28163
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
28098
28164
  },
28099
28165
  },
@@ -28885,6 +28951,7 @@ stores.inject(MyMetaStore, storeInstance);
28885
28951
  env.openSidePanel("ChartPanel");
28886
28952
  },
28887
28953
  icon: "o-spreadsheet-Icon.EDIT",
28954
+ isEnabled: (env) => !env.isSmall,
28888
28955
  },
28889
28956
  getCopyMenuItem(figureId, env),
28890
28957
  getCutMenuItem(figureId, env),
@@ -29099,6 +29166,147 @@ stores.inject(MyMetaStore, storeInstance);
29099
29166
  };
29100
29167
  }
29101
29168
 
29169
+ //------------------------------------------------------------------------------
29170
+ // Context Menu Component
29171
+ //------------------------------------------------------------------------------
29172
+ css /* scss */ `
29173
+ .o-menu {
29174
+ background-color: white;
29175
+ user-select: none;
29176
+
29177
+ .o-menu-item {
29178
+ height: ${DESKTOP_MENU_ITEM_HEIGHT}px;
29179
+ padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
29180
+ cursor: pointer;
29181
+ user-select: none;
29182
+
29183
+ .o-menu-item-name {
29184
+ min-width: 40%;
29185
+ }
29186
+
29187
+ .o-menu-item-icon {
29188
+ display: inline-block;
29189
+ margin: 0px 8px 0px 0px;
29190
+ width: ${DESKTOP_MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29191
+ line-height: ${DESKTOP_MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29192
+ }
29193
+
29194
+ &:not(.disabled) {
29195
+ &:hover,
29196
+ &.o-menu-item-active {
29197
+ background-color: ${BUTTON_ACTIVE_BG};
29198
+ color: ${BUTTON_ACTIVE_TEXT_COLOR};
29199
+ }
29200
+ .o-menu-item-description {
29201
+ color: grey;
29202
+ }
29203
+ .o-menu-item-icon {
29204
+ .o-icon {
29205
+ color: ${ICONS_COLOR};
29206
+ }
29207
+ }
29208
+ }
29209
+ &.disabled {
29210
+ color: ${DISABLED_TEXT_COLOR};
29211
+ cursor: not-allowed;
29212
+ }
29213
+ }
29214
+ }
29215
+
29216
+ .o-spreadsheet-mobile {
29217
+ .o-menu-item {
29218
+ height: ${MOBILE_MENU_ITEM_HEIGHT}px;
29219
+ }
29220
+ }
29221
+ `;
29222
+ class Menu extends owl.Component {
29223
+ static template = "o-spreadsheet-Menu";
29224
+ static props = {
29225
+ menuItems: Array,
29226
+ onClose: Function,
29227
+ onClickMenu: { type: Function, optional: true },
29228
+ onMouseEnter: { type: Function, optional: true },
29229
+ onMouseOver: { type: Function, optional: true },
29230
+ onMouseLeave: { type: Function, optional: true },
29231
+ width: { type: Number, optional: true },
29232
+ isActive: { type: Function, optional: true },
29233
+ onScroll: { type: Function, optional: true },
29234
+ };
29235
+ static components = {};
29236
+ static defaultProps = {};
29237
+ hoveredMenu = undefined;
29238
+ setup() {
29239
+ owl.onWillUnmount(() => {
29240
+ this.hoveredMenu?.onStopHover?.(this.env);
29241
+ });
29242
+ }
29243
+ get menuItemsAndSeparators() {
29244
+ const menuItemsAndSeparators = [];
29245
+ for (let i = 0; i < this.props.menuItems.length; i++) {
29246
+ const menuItem = this.props.menuItems[i];
29247
+ if (menuItem.isVisible(this.env)) {
29248
+ menuItemsAndSeparators.push(menuItem);
29249
+ }
29250
+ if (menuItem.separator &&
29251
+ i !== this.props.menuItems.length - 1 && // no separator at the end
29252
+ menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
29253
+ ) {
29254
+ menuItemsAndSeparators.push("separator");
29255
+ }
29256
+ }
29257
+ if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
29258
+ menuItemsAndSeparators.pop();
29259
+ }
29260
+ if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
29261
+ return [];
29262
+ }
29263
+ return menuItemsAndSeparators;
29264
+ }
29265
+ get childrenHaveIcon() {
29266
+ return this.props.menuItems.some((menuItem) => !!this.getIconName(menuItem));
29267
+ }
29268
+ getIconName(menu) {
29269
+ if (menu.icon(this.env)) {
29270
+ return menu.icon(this.env);
29271
+ }
29272
+ if (menu.isActive?.(this.env)) {
29273
+ return "o-spreadsheet-Icon.CHECK";
29274
+ }
29275
+ return "";
29276
+ }
29277
+ getColor(menu) {
29278
+ return cssPropertiesToCss({ color: menu.textColor });
29279
+ }
29280
+ getIconColor(menu) {
29281
+ return cssPropertiesToCss({ color: menu.iconColor });
29282
+ }
29283
+ getName(menu) {
29284
+ return menu.name(this.env);
29285
+ }
29286
+ isRoot(menu) {
29287
+ return !menu.execute;
29288
+ }
29289
+ isEnabled(menu) {
29290
+ if (menu.isEnabled(this.env)) {
29291
+ return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
29292
+ }
29293
+ return false;
29294
+ }
29295
+ get menuStyle() {
29296
+ return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
29297
+ }
29298
+ onMouseEnter(menu, ev) {
29299
+ this.hoveredMenu = menu;
29300
+ menu.onStartHover?.(this.env);
29301
+ this.props.onMouseEnter?.(menu, ev);
29302
+ }
29303
+ onMouseLeave(menu, ev) {
29304
+ this.hoveredMenu = undefined;
29305
+ menu.onStopHover?.(this.env);
29306
+ this.props.onMouseLeave?.(menu, ev);
29307
+ }
29308
+ }
29309
+
29102
29310
  /**
29103
29311
  * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
29104
29312
  */
@@ -29127,6 +29335,9 @@ stores.inject(MyMetaStore, storeInstance);
29127
29335
  height: zone.bottom - zone.top,
29128
29336
  };
29129
29337
  }
29338
+ function isPointInsideRect(x, y, rect) {
29339
+ return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
29340
+ }
29130
29341
 
29131
29342
  /**
29132
29343
  * Return the o-spreadsheet element position relative
@@ -29424,57 +29635,17 @@ stores.inject(MyMetaStore, storeInstance);
29424
29635
  }
29425
29636
 
29426
29637
  //------------------------------------------------------------------------------
29427
- // Context Menu Component
29638
+ // Context MenuPopover Component
29428
29639
  //------------------------------------------------------------------------------
29429
29640
  css /* scss */ `
29430
- .o-menu {
29431
- background-color: white;
29641
+ .o-menu-wrapper {
29432
29642
  padding: ${MENU_VERTICAL_PADDING}px 0px;
29433
- width: ${MENU_WIDTH}px;
29434
- user-select: none;
29435
-
29436
- .o-menu-item {
29437
- height: ${MENU_ITEM_HEIGHT}px;
29438
- padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
29439
- cursor: pointer;
29440
- user-select: none;
29441
-
29442
- .o-menu-item-name {
29443
- min-width: 40%;
29444
- }
29445
-
29446
- .o-menu-item-icon {
29447
- display: inline-block;
29448
- margin: 0px 8px 0px 0px;
29449
- width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29450
- line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29451
- }
29452
-
29453
- &:not(.disabled) {
29454
- &:hover,
29455
- &.o-menu-item-active {
29456
- background-color: ${BUTTON_ACTIVE_BG};
29457
- color: ${BUTTON_ACTIVE_TEXT_COLOR};
29458
- }
29459
- .o-menu-item-description {
29460
- color: grey;
29461
- }
29462
- .o-menu-item-icon {
29463
- .o-icon {
29464
- color: ${ICONS_COLOR};
29465
- }
29466
- }
29467
- }
29468
- &.disabled {
29469
- color: ${DISABLED_TEXT_COLOR};
29470
- cursor: not-allowed;
29471
- }
29472
- }
29643
+ background-color: white;
29473
29644
  }
29474
29645
  `;
29475
29646
  const TIMEOUT_DELAY = 250;
29476
- class Menu extends owl.Component {
29477
- static template = "o-spreadsheet-Menu";
29647
+ class MenuPopover extends owl.Component {
29648
+ static template = "o-spreadsheet-Menu-Popover";
29478
29649
  static props = {
29479
29650
  anchorRect: Object,
29480
29651
  popoverPositioning: { type: String, optional: true },
@@ -29487,7 +29658,7 @@ stores.inject(MyMetaStore, storeInstance);
29487
29658
  onMouseOver: { type: Function, optional: true },
29488
29659
  width: { type: Number, optional: true },
29489
29660
  };
29490
- static components = { Menu, Popover };
29661
+ static components = { MenuPopover, Menu, Popover };
29491
29662
  static defaultProps = {
29492
29663
  depth: 1,
29493
29664
  popoverPositioning: "top-right",
@@ -29514,27 +29685,18 @@ stores.inject(MyMetaStore, storeInstance);
29514
29685
  this.hoveredMenu?.onStopHover?.(this.env);
29515
29686
  });
29516
29687
  }
29517
- get menuItemsAndSeparators() {
29518
- const menuItemsAndSeparators = [];
29519
- for (let i = 0; i < this.props.menuItems.length; i++) {
29520
- const menuItem = this.props.menuItems[i];
29521
- if (menuItem.isVisible(this.env)) {
29522
- menuItemsAndSeparators.push(menuItem);
29523
- }
29524
- if (menuItem.separator &&
29525
- i !== this.props.menuItems.length - 1 && // no separator at the end
29526
- menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
29527
- ) {
29528
- menuItemsAndSeparators.push("separator");
29529
- }
29530
- }
29531
- if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
29532
- menuItemsAndSeparators.pop();
29533
- }
29534
- if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
29535
- return [];
29536
- }
29537
- return menuItemsAndSeparators;
29688
+ get menuProps() {
29689
+ return {
29690
+ menuItems: this.props.menuItems,
29691
+ onClose: this.close.bind(this),
29692
+ // @ts-ignore
29693
+ onClickMenu: this.onClickMenu.bind(this),
29694
+ onMouseOver: this.onMouseOver.bind(this),
29695
+ onMouseLeave: this.onMouseLeave.bind(this),
29696
+ width: this.props.width || MENU_WIDTH,
29697
+ isActive: this.isActive.bind(this),
29698
+ onScroll: this.onScroll.bind(this),
29699
+ };
29538
29700
  }
29539
29701
  get subMenuAnchorRect() {
29540
29702
  const anchorRect = Object.assign({}, this.subMenu.anchorRect);
@@ -29548,12 +29710,13 @@ stores.inject(MyMetaStore, storeInstance);
29548
29710
  x: this.props.anchorRect.x,
29549
29711
  y: this.props.anchorRect.y,
29550
29712
  width: isRoot ? this.props.anchorRect.width : this.props.width || MENU_WIDTH,
29551
- height: isRoot ? this.props.anchorRect.height : MENU_ITEM_HEIGHT,
29713
+ height: isRoot ? this.props.anchorRect.height : DESKTOP_MENU_ITEM_HEIGHT,
29552
29714
  },
29553
29715
  positioning: this.props.popoverPositioning,
29554
29716
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
29555
29717
  onPopoverHidden: () => this.closeSubMenu(),
29556
29718
  onPopoverMoved: () => this.closeSubMenu(),
29719
+ maxHeight: this.props.maxHeight,
29557
29720
  };
29558
29721
  }
29559
29722
  get childrenHaveIcon() {
@@ -29623,7 +29786,7 @@ stores.inject(MyMetaStore, storeInstance);
29623
29786
  x: getRefBoundingRect(this.menuRef).x,
29624
29787
  y: y - (this.subMenu.scrollOffset || 0),
29625
29788
  width: this.props.width || MENU_WIDTH,
29626
- height: MENU_ITEM_HEIGHT,
29789
+ height: DESKTOP_MENU_ITEM_HEIGHT,
29627
29790
  };
29628
29791
  this.subMenu.menuItems = menu.children(this.env);
29629
29792
  this.subMenu.isOpen = true;
@@ -29672,14 +29835,8 @@ stores.inject(MyMetaStore, storeInstance);
29672
29835
  this.subMenu.isHoveringChild = true;
29673
29836
  this.openingTimeOut.clear();
29674
29837
  }
29675
- onMouseEnter(menu, ev) {
29676
- this.hoveredMenu = menu;
29677
- menu.onStartHover?.(this.env);
29678
- }
29679
29838
  onMouseLeave(menu) {
29680
29839
  this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
29681
- this.hoveredMenu = undefined;
29682
- menu.onStopHover?.(this.env);
29683
29840
  }
29684
29841
  get menuStyle() {
29685
29842
  return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
@@ -29688,7 +29845,7 @@ stores.inject(MyMetaStore, storeInstance);
29688
29845
 
29689
29846
  class ChartDashboardMenu extends owl.Component {
29690
29847
  static template = "spreadsheet.ChartDashboardMenu";
29691
- static components = { Menu };
29848
+ static components = { MenuPopover };
29692
29849
  static props = { figureUI: Object };
29693
29850
  originalChartDefinition;
29694
29851
  fullScreenFigureStore;
@@ -29952,7 +30109,7 @@ stores.inject(MyMetaStore, storeInstance);
29952
30109
  onMouseDown: { type: Function, optional: true },
29953
30110
  onClickAnchor: { type: Function, optional: true },
29954
30111
  };
29955
- static components = { Menu };
30112
+ static components = { MenuPopover };
29956
30113
  static defaultProps = {
29957
30114
  onFigureDeleted: () => { },
29958
30115
  onMouseDown: () => { },
@@ -30039,7 +30196,14 @@ stores.inject(MyMetaStore, storeInstance);
30039
30196
  this.props.onClickAnchor(dirX, dirY, ev);
30040
30197
  }
30041
30198
  onMouseDown(ev) {
30042
- this.props.onMouseDown(ev);
30199
+ if (!this.env.isMobile()) {
30200
+ this.props.onMouseDown(ev);
30201
+ }
30202
+ }
30203
+ onClick(ev) {
30204
+ if (this.env.isMobile()) {
30205
+ this.props.onMouseDown(ev);
30206
+ }
30043
30207
  }
30044
30208
  onKeyDown(ev) {
30045
30209
  const keyDownShortcut = keyboardEventToShortcutString(ev);
@@ -31506,15 +31670,15 @@ stores.inject(MyMetaStore, storeInstance);
31506
31670
  const activeSheetId = this.getters.getActiveSheetId();
31507
31671
  return this.providers
31508
31672
  .flatMap((h) => h.highlights)
31509
- .filter((h) => h.sheetId === activeSheetId)
31673
+ .filter((h) => h.range.sheetId === activeSheetId)
31510
31674
  .map((highlight) => {
31511
- const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
31675
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.range.zone);
31512
31676
  const zone = numberOfRows * numberOfCols === 1
31513
- ? this.getters.expandZone(highlight.sheetId, highlight.zone)
31514
- : highlight.zone;
31677
+ ? this.getters.expandZone(highlight.range.sheetId, highlight.range.zone)
31678
+ : highlight.range.unboundedZone;
31515
31679
  return {
31516
31680
  ...highlight,
31517
- zone,
31681
+ range: this.model.getters.getRangeFromZone(highlight.range.sheetId, zone),
31518
31682
  };
31519
31683
  });
31520
31684
  }
@@ -31527,7 +31691,7 @@ stores.inject(MyMetaStore, storeInstance);
31527
31691
  drawLayer(ctx, layer) {
31528
31692
  if (layer === "Highlights") {
31529
31693
  for (const highlight of this.highlights) {
31530
- const rect = this.getters.getVisibleRect(highlight.zone);
31694
+ const rect = this.getters.getVisibleRect(highlight.range.zone);
31531
31695
  drawHighlight(ctx, highlight, rect);
31532
31696
  }
31533
31697
  }
@@ -32155,12 +32319,12 @@ stores.inject(MyMetaStore, storeInstance);
32155
32319
  rangeColor(xc, sheetName) {
32156
32320
  const refSheet = sheetName ? this.model.getters.getSheetIdByName(sheetName) : this.sheetId;
32157
32321
  const highlight = this.highlights.find((highlight) => {
32158
- if (highlight.sheetId !== refSheet)
32322
+ if (highlight.range.sheetId !== refSheet)
32159
32323
  return false;
32160
32324
  const range = this.model.getters.getRangeFromSheetXC(refSheet, xc);
32161
32325
  let zone = range.zone;
32162
32326
  zone = getZoneArea(zone) === 1 ? this.model.getters.expandZone(refSheet, zone) : zone;
32163
- return isEqual(zone, highlight.zone);
32327
+ return isEqual(zone, highlight.range.zone);
32164
32328
  });
32165
32329
  return highlight && highlight.color ? highlight.color : undefined;
32166
32330
  }
@@ -32270,11 +32434,10 @@ stores.inject(MyMetaStore, storeInstance);
32270
32434
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
32271
32435
  const zone = numberOfRows * numberOfCols === 1
32272
32436
  ? this.getters.expandZone(range.sheetId, range.zone)
32273
- : range.zone;
32437
+ : range.unboundedZone;
32274
32438
  return {
32275
- zone,
32439
+ range: this.model.getters.getRangeFromZone(range.sheetId, zone),
32276
32440
  color: rangeColor(rangeString),
32277
- sheetId: range.sheetId,
32278
32441
  interactive: true,
32279
32442
  };
32280
32443
  });
@@ -32527,11 +32690,15 @@ stores.inject(MyMetaStore, storeInstance);
32527
32690
  onInputContextMenu: { type: Function, optional: true },
32528
32691
  composerStore: Object,
32529
32692
  placeholder: { type: String, optional: true },
32693
+ inputMode: { type: String, optional: true },
32694
+ showAssistant: { type: Boolean, optional: true },
32530
32695
  };
32531
32696
  static components = { TextValueProvider, FunctionDescriptionProvider, SpeechBubble };
32532
32697
  static defaultProps = {
32533
32698
  inputStyle: "",
32534
32699
  isDefaultFocus: false,
32700
+ inputMode: "text",
32701
+ showAssistant: true,
32535
32702
  };
32536
32703
  DOMFocusableElementStore;
32537
32704
  composerRef = owl.useRef("o_composer");
@@ -32582,8 +32749,7 @@ stores.inject(MyMetaStore, storeInstance);
32582
32749
  assistantStyle["max-height"] = `${availableSpaceAbove - CLOSE_ICON_RADIUS}px`;
32583
32750
  // render top
32584
32751
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
32585
- assistantStyle.top = `-3px`;
32586
- assistantStyle.transform = `translate(0, -100%)`;
32752
+ assistantStyle.transform = `translate(0, calc(-100% - ${cellHeight + 3}px))`;
32587
32753
  }
32588
32754
  if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
32589
32755
  // render left
@@ -32862,6 +33028,9 @@ stores.inject(MyMetaStore, storeInstance);
32862
33028
  // not main button, probably a context menu
32863
33029
  return;
32864
33030
  }
33031
+ if (this.env.isMobile() && !isIOS()) {
33032
+ return;
33033
+ }
32865
33034
  this.contentHelper.removeSelection();
32866
33035
  }
32867
33036
  onMouseup() {
@@ -33485,7 +33654,7 @@ stores.inject(MyMetaStore, storeInstance);
33485
33654
  */
33486
33655
  function startDnd(onPointerMove, onPointerUp) {
33487
33656
  const removeListeners = () => {
33488
- window.removeEventListener("pointerup", _onPointerUp);
33657
+ window.removeEventListener("pointerup", _onPointerUp, { capture: true });
33489
33658
  window.removeEventListener("dragstart", _onDragStart);
33490
33659
  window.removeEventListener("pointermove", onPointerMove);
33491
33660
  window.removeEventListener("wheel", onPointerMove);
@@ -33497,7 +33666,7 @@ stores.inject(MyMetaStore, storeInstance);
33497
33666
  function _onDragStart(ev) {
33498
33667
  ev.preventDefault();
33499
33668
  }
33500
- window.addEventListener("pointerup", _onPointerUp);
33669
+ window.addEventListener("pointerup", _onPointerUp, { capture: true });
33501
33670
  window.addEventListener("dragstart", _onDragStart);
33502
33671
  window.addEventListener("pointermove", onPointerMove);
33503
33672
  // mouse wheel on window is by default a passive event.
@@ -34119,9 +34288,9 @@ stores.inject(MyMetaStore, storeInstance);
34119
34288
  .filter((reference) => this.shouldBeHighlighted(this.inputSheetId, reference));
34120
34289
  return XCs.map((xc) => {
34121
34290
  const { sheetName } = splitReference(xc);
34291
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId;
34122
34292
  return {
34123
- zone: this.getters.getRangeFromSheetXC(this.inputSheetId, xc).zone,
34124
- sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId,
34293
+ range: this.getters.getRangeFromSheetXC(sheetId, xc),
34125
34294
  color,
34126
34295
  interactive: true,
34127
34296
  };
@@ -34590,7 +34759,7 @@ stores.inject(MyMetaStore, storeInstance);
34590
34759
  return createActions(actionSpecs);
34591
34760
  }
34592
34761
 
34593
- /** This component looks like a select input, but on click it opens a Menu with the items given as props instead of a dropdown */
34762
+ /** This component looks like a select input, but on click it opens a MenuPopover with the items given as props instead of a dropdown */
34594
34763
  class SelectMenu extends owl.Component {
34595
34764
  static template = "o-spreadsheet-SelectMenu";
34596
34765
  static props = {
@@ -34598,7 +34767,7 @@ stores.inject(MyMetaStore, storeInstance);
34598
34767
  selectedValue: String,
34599
34768
  class: { type: String, optional: true },
34600
34769
  };
34601
- static components = { Menu };
34770
+ static components = { MenuPopover };
34602
34771
  menuId = new UuidGenerator().uuidv4();
34603
34772
  selectRef = owl.useRef("select");
34604
34773
  state = owl.useState({
@@ -35346,7 +35515,7 @@ stores.inject(MyMetaStore, storeInstance);
35346
35515
  }
35347
35516
 
35348
35517
  //------------------------------------------------------------------------------
35349
- // Link Menu Registry
35518
+ // Link MenuPopover Registry
35350
35519
  //------------------------------------------------------------------------------
35351
35520
  const linkMenuRegistry = new MenuItemRegistry();
35352
35521
  linkMenuRegistry.add("sheet", {
@@ -35408,7 +35577,7 @@ stores.inject(MyMetaStore, storeInstance);
35408
35577
  cellPosition: Object,
35409
35578
  onClosed: { type: Function, optional: true },
35410
35579
  };
35411
- static components = { Menu };
35580
+ static components = { MenuPopover };
35412
35581
  menuItems = linkMenuRegistry.getMenuItems();
35413
35582
  link = owl.useState(this.defaultState);
35414
35583
  menu = owl.useState({
@@ -35865,58 +36034,149 @@ stores.inject(MyMetaStore, storeInstance);
35865
36034
  const ARROW_DOWN = {
35866
36035
  width: 448,
35867
36036
  height: 512,
35868
- fillColor: "#E06666",
35869
- path: "M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z",
36037
+ paths: [
36038
+ {
36039
+ fillColor: "#E06666",
36040
+ path: "M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z",
36041
+ },
36042
+ ],
35870
36043
  };
35871
36044
  const ARROW_UP = {
35872
36045
  width: 448,
35873
36046
  height: 512,
35874
- fillColor: "#6AA84F",
35875
- path: "M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z",
36047
+ paths: [
36048
+ {
36049
+ fillColor: "#6AA84F",
36050
+ path: "M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z",
36051
+ },
36052
+ ],
35876
36053
  };
35877
36054
  const ARROW_RIGHT = {
35878
36055
  width: 448,
35879
36056
  height: 512,
35880
- fillColor: "#F0AD4E",
35881
- path: "M190.5 66.9l22.2-22.2c9.4-9.4 24.6-9.4 33.9 0L441 239c9.4 9.4 9.4 24.6 0 33.9L246.6 467.3c-9.4 9.4-24.6 9.4-33.9 0l-22.2-22.2c-9.5-9.5-9.3-25 .4-34.3L311.4 296H24c-13.3 0-24-10.7-24-24v-32c0-13.3 10.7-24 24-24h287.4L190.9 101.2c-9.8-9.3-10-24.8-.4-34.3z",
36057
+ paths: [
36058
+ {
36059
+ fillColor: "#F0AD4E",
36060
+ path: "M190.5 66.9l22.2-22.2c9.4-9.4 24.6-9.4 33.9 0L441 239c9.4 9.4 9.4 24.6 0 33.9L246.6 467.3c-9.4 9.4-24.6 9.4-33.9 0l-22.2-22.2c-9.5-9.5-9.3-25 .4-34.3L311.4 296H24c-13.3 0-24-10.7-24-24v-32c0-13.3 10.7-24 24-24h287.4L190.9 101.2c-9.8-9.3-10-24.8-.4-34.3z",
36061
+ },
36062
+ ],
35882
36063
  };
35883
36064
  const SMILE = {
35884
36065
  width: 496,
35885
36066
  height: 512,
35886
- fillColor: "#6AA84F",
35887
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z",
36067
+ paths: [
36068
+ {
36069
+ fillColor: "#6AA84F",
36070
+ path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z",
36071
+ },
36072
+ ],
35888
36073
  };
35889
36074
  const MEH = {
35890
36075
  width: 496,
35891
36076
  height: 512,
35892
- fillColor: "#F0AD4E",
35893
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z",
36077
+ paths: [
36078
+ {
36079
+ fillColor: "#F0AD4E",
36080
+ path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z",
36081
+ },
36082
+ ],
35894
36083
  };
35895
36084
  const FROWN = {
35896
36085
  width: 496,
35897
36086
  height: 512,
35898
- fillColor: "#E06666",
35899
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z",
36087
+ paths: [
36088
+ {
36089
+ fillColor: "#E06666",
36090
+ path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z",
36091
+ },
36092
+ ],
35900
36093
  };
35901
36094
  const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
35902
36095
  const GREEN_DOT = {
35903
36096
  width: 512,
35904
36097
  height: 512,
35905
- fillColor: "#6AA84F",
35906
- path: DOT_PATH,
36098
+ paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
35907
36099
  };
35908
36100
  const YELLOW_DOT = {
35909
36101
  width: 512,
35910
36102
  height: 512,
35911
- fillColor: "#F0AD4E",
35912
- path: DOT_PATH,
36103
+ paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
35913
36104
  };
35914
36105
  const RED_DOT = {
35915
36106
  width: 512,
35916
36107
  height: 512,
35917
- fillColor: "#E06666",
35918
- path: DOT_PATH,
36108
+ paths: [{ fillColor: "#E06666", path: DOT_PATH }],
36109
+ };
36110
+ const CARET_DOWN = {
36111
+ width: 512,
36112
+ height: 512,
36113
+ paths: [{ fillColor: TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
36114
+ };
36115
+ const HOVERED_CARET_DOWN = {
36116
+ width: 512,
36117
+ height: 512,
36118
+ paths: [
36119
+ { fillColor: TEXT_BODY_MUTED, path: "M15 15 h482 v482 h-482" },
36120
+ { fillColor: "#fff", path: "M120 195 h270 l-135 130" },
36121
+ ],
36122
+ };
36123
+ const CHECKBOX_UNCHECKED = {
36124
+ width: 512,
36125
+ height: 512,
36126
+ paths: [{ fillColor: GRAY_300, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
36127
+ };
36128
+ const CHECKBOX_UNCHECKED_HOVERED = {
36129
+ width: 512,
36130
+ height: 512,
36131
+ paths: [{ fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
35919
36132
  };
36133
+ const CHECKBOX_CHECKED = {
36134
+ width: 512,
36135
+ height: 512,
36136
+ paths: [
36137
+ { fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422" },
36138
+ { fillColor: "#FFF", path: "M165,240 l45,45 l135,-135 h60 l-195,195 l-105,-105" },
36139
+ ],
36140
+ };
36141
+ function getPivotIconSvg(isCollapsed, isHovered) {
36142
+ const symbolPath = isCollapsed
36143
+ ? "M149,235 h213 v43 h-213 M235,149 h43 v213 h-43" // +
36144
+ : "M149,235 h213 v43 h-213"; // -
36145
+ return {
36146
+ width: 512,
36147
+ height: 512,
36148
+ paths: [
36149
+ { path: "M21,21 h469 v469 h-469", fillColor: isHovered ? GRAY_900 : "#777" }, // borders
36150
+ { path: "M64,64 v384 h384 v-384", fillColor: isHovered ? GRAY_200 : "#eee" }, // background
36151
+ { path: symbolPath, fillColor: isHovered ? GRAY_900 : "#777" },
36152
+ ],
36153
+ };
36154
+ }
36155
+ function getDataFilterIcon(isActive, isHighContrast, isHovered) {
36156
+ const symbolPath = isActive
36157
+ ? "M18.6 3.5H4.29c-.7 0-1.06.85-.56 1.35l6.1 6.1v6.8c0 .26.13.5.34.65l2.64 1.85a.79.79 0 0 0 1.25-.65v-8.64l6.1-6.1a.79.79 0 0 0-.56-1.35"
36158
+ : "M 339.667 681 L 510.333 681 L 510.333 595.667 L 339.667 595.667 L 339.667 681 Z M 41 169 L 41 254.333 L 809 254.333 L 809 169 L 41 169 Z M 169 467.667 L 681 467.667 L 681 382.333 L 169 382.333 L 169 467.667 Z";
36159
+ const hoverBackgroundPath = isActive ? "M0,0 h24 v24 h-24" : "M0,0 h850 v850 h-850";
36160
+ const colors = { iconColor: FILTERS_COLOR, hoverBackgroundColor: FILTERS_COLOR };
36161
+ if (isHovered && !isHighContrast) {
36162
+ colors.iconColor = "#fff";
36163
+ }
36164
+ else if (!isHovered && isHighContrast) {
36165
+ colors.iconColor = "#defade";
36166
+ }
36167
+ else if (isHovered && isHighContrast) {
36168
+ colors.iconColor = FILTERS_COLOR;
36169
+ colors.hoverBackgroundColor = "#fff";
36170
+ }
36171
+ return {
36172
+ width: isActive ? 24 : 850,
36173
+ height: isActive ? 24 : 850,
36174
+ paths: [
36175
+ isHovered ? { path: hoverBackgroundPath, fillColor: colors.hoverBackgroundColor } : undefined,
36176
+ { path: symbolPath, fillColor: colors.iconColor },
36177
+ ].filter(isDefined),
36178
+ };
36179
+ }
35920
36180
  const ICONS = {
35921
36181
  arrowGood: {
35922
36182
  template: "ARROW_UP",
@@ -35972,6 +36232,15 @@ stores.inject(MyMetaStore, storeInstance);
35972
36232
  bad: "dotBad",
35973
36233
  },
35974
36234
  };
36235
+ const path2DCache = {};
36236
+ function getPath2D(svgPath) {
36237
+ if (path2DCache[svgPath]) {
36238
+ return path2DCache[svgPath];
36239
+ }
36240
+ const path2D = new Path2D(svgPath);
36241
+ path2DCache[svgPath] = path2D;
36242
+ return path2D;
36243
+ }
35975
36244
 
35976
36245
  /**
35977
36246
  * Map of the different types of conversions warnings and their name in error messages
@@ -41943,6 +42212,7 @@ stores.inject(MyMetaStore, storeInstance);
41943
42212
  execute: (env) => {
41944
42213
  env.openSidePanel("FindAndReplace", {});
41945
42214
  },
42215
+ isEnabled: (env) => !env.isSmall,
41946
42216
  icon: "o-spreadsheet-Icon.SEARCH",
41947
42217
  };
41948
42218
  const deleteValues = {
@@ -42199,11 +42469,13 @@ stores.inject(MyMetaStore, storeInstance);
42199
42469
  const insertChart = {
42200
42470
  name: _t("Chart"),
42201
42471
  execute: CREATE_CHART,
42472
+ isEnabled: (env) => !env.isSmall,
42202
42473
  icon: "o-spreadsheet-Icon.INSERT_CHART",
42203
42474
  };
42204
42475
  const insertPivot = {
42205
42476
  name: _t("Pivot table"),
42206
42477
  execute: CREATE_PIVOT,
42478
+ isEnabled: (env) => !env.isSmall,
42207
42479
  icon: "o-spreadsheet-Icon.PIVOT",
42208
42480
  };
42209
42481
  const insertImage = {
@@ -42211,12 +42483,14 @@ stores.inject(MyMetaStore, storeInstance);
42211
42483
  description: "Ctrl+O",
42212
42484
  execute: CREATE_IMAGE,
42213
42485
  isVisible: (env) => env.imageProvider !== undefined,
42486
+ isEnabled: (env) => !env.isSmall,
42214
42487
  icon: "o-spreadsheet-Icon.INSERT_IMAGE",
42215
42488
  };
42216
42489
  const insertTable = {
42217
42490
  name: () => _t("Table"),
42218
42491
  execute: INSERT_TABLE,
42219
42492
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
42493
+ isEnabled: (env) => !env.isSmall,
42220
42494
  icon: "o-spreadsheet-Icon.PAINT_TABLE",
42221
42495
  };
42222
42496
  const insertFunction = {
@@ -42322,6 +42596,7 @@ stores.inject(MyMetaStore, storeInstance);
42322
42596
  },
42323
42597
  });
42324
42598
  },
42599
+ isEnabled: (env) => !env.isSmall,
42325
42600
  icon: "o-spreadsheet-Icon.INSERT_DROPDOWN",
42326
42601
  };
42327
42602
  const insertSheet = {
@@ -42591,7 +42866,7 @@ stores.inject(MyMetaStore, storeInstance);
42591
42866
  isVisible: (env) => {
42592
42867
  const position = env.model.getters.getActivePosition();
42593
42868
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
42594
- return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42869
+ return (!env.isSmall && pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42595
42870
  },
42596
42871
  isReadonlyAllowed: true,
42597
42872
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -42709,7 +42984,7 @@ stores.inject(MyMetaStore, storeInstance);
42709
42984
  }
42710
42985
 
42711
42986
  //------------------------------------------------------------------------------
42712
- // Context Menu Registry
42987
+ // Context MenuPopover Registry
42713
42988
  //------------------------------------------------------------------------------
42714
42989
  const cellMenuRegistry = new MenuItemRegistry();
42715
42990
  cellMenuRegistry
@@ -42792,6 +43067,7 @@ stores.inject(MyMetaStore, storeInstance);
42792
43067
  .add("edit_table", {
42793
43068
  ...editTable,
42794
43069
  isVisible: SELECTION_CONTAINS_SINGLE_TABLE,
43070
+ isEnabled: (env) => !env.isSmall,
42795
43071
  sequence: 140,
42796
43072
  })
42797
43073
  .add("delete_table", {
@@ -42860,6 +43136,7 @@ stores.inject(MyMetaStore, storeInstance);
42860
43136
  }
42861
43137
  env.openSidePanel("RemoveDuplicates", {});
42862
43138
  },
43139
+ isEnabled: (env) => !env.isSmall,
42863
43140
  };
42864
43141
  const trimWhitespace = {
42865
43142
  name: _t("Trim whitespace"),
@@ -42887,7 +43164,7 @@ stores.inject(MyMetaStore, storeInstance);
42887
43164
  name: _t("Split text to columns"),
42888
43165
  sequence: 1,
42889
43166
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
42890
- isEnabled: (env) => env.model.getters.isSingleColSelected(),
43167
+ isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
42891
43168
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
42892
43169
  };
42893
43170
  const reinsertDynamicPivotMenu = {
@@ -42974,7 +43251,7 @@ stores.inject(MyMetaStore, storeInstance);
42974
43251
  const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
42975
43252
  const formatCustomCurrency = {
42976
43253
  name: _t("Custom currency"),
42977
- isVisible: (env) => env.loadCurrencies !== undefined,
43254
+ isVisible: (env) => env.loadCurrencies !== undefined && !env.isSmall,
42978
43255
  execute: (env) => env.openSidePanel("CustomCurrency", {}),
42979
43256
  };
42980
43257
  const formatNumberDate = createFormatActionSpec({
@@ -43197,6 +43474,7 @@ stores.inject(MyMetaStore, storeInstance);
43197
43474
  const formatCF = {
43198
43475
  name: _t("Conditional formatting"),
43199
43476
  execute: OPEN_CF_SIDEPANEL_ACTION,
43477
+ isEnabled: (env) => !env.isSmall,
43200
43478
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
43201
43479
  };
43202
43480
  const clearFormat = {
@@ -43580,8 +43858,7 @@ stores.inject(MyMetaStore, storeInstance);
43580
43858
  }
43581
43859
  return [
43582
43860
  {
43583
- sheetId: position.sheetId,
43584
- zone,
43861
+ range: this.model.getters.getRangeFromZone(position.sheetId, zone),
43585
43862
  dashed: cell.value === CellErrorType.SpilledBlocked,
43586
43863
  color: "#17A2B8",
43587
43864
  noFill: true,
@@ -43666,6 +43943,7 @@ stores.inject(MyMetaStore, storeInstance);
43666
43943
  let previousEvClientPosition;
43667
43944
  let startingX;
43668
43945
  let startingY;
43946
+ let scrollDirection = "all";
43669
43947
  const getters = env.model.getters;
43670
43948
  let cleanUpFns = [];
43671
43949
  const cleanUp = () => {
@@ -43691,55 +43969,59 @@ stores.inject(MyMetaStore, storeInstance);
43691
43969
  let timeoutDelay = MAX_DELAY;
43692
43970
  const x = currentEv.clientX - position.left;
43693
43971
  let colIndex = getters.getColIndex(x);
43694
- const previousX = previousEvClientPosition.clientX - position.left;
43695
- const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);
43696
- if (edgeScrollInfoX.canEdgeScroll) {
43697
- canEdgeScroll = true;
43698
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);
43699
- let newTarget = colIndex;
43700
- switch (edgeScrollInfoX.direction) {
43701
- case "reset":
43702
- colIndex = newTarget = xSplit;
43703
- break;
43704
- case 1:
43705
- colIndex = right;
43706
- newTarget = left + 1;
43707
- break;
43708
- case -1:
43709
- colIndex = left - 1;
43710
- while (env.model.getters.isColHidden(sheetId, colIndex)) {
43711
- colIndex--;
43712
- }
43713
- newTarget = colIndex;
43714
- break;
43972
+ if (scrollDirection !== "vertical") {
43973
+ const previousX = previousEvClientPosition.clientX - position.left;
43974
+ const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);
43975
+ if (edgeScrollInfoX.canEdgeScroll) {
43976
+ canEdgeScroll = true;
43977
+ timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);
43978
+ let newTarget = colIndex;
43979
+ switch (edgeScrollInfoX.direction) {
43980
+ case "reset":
43981
+ colIndex = newTarget = xSplit;
43982
+ break;
43983
+ case 1:
43984
+ colIndex = right;
43985
+ newTarget = left + 1;
43986
+ break;
43987
+ case -1:
43988
+ colIndex = left - 1;
43989
+ while (env.model.getters.isColHidden(sheetId, colIndex)) {
43990
+ colIndex--;
43991
+ }
43992
+ newTarget = colIndex;
43993
+ break;
43994
+ }
43995
+ scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43715
43996
  }
43716
- scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43717
43997
  }
43718
43998
  const y = currentEv.clientY - position.top;
43719
43999
  let rowIndex = getters.getRowIndex(y);
43720
- const previousY = previousEvClientPosition.clientY - position.top;
43721
- const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);
43722
- if (edgeScrollInfoY.canEdgeScroll) {
43723
- canEdgeScroll = true;
43724
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);
43725
- let newTarget = rowIndex;
43726
- switch (edgeScrollInfoY.direction) {
43727
- case "reset":
43728
- rowIndex = newTarget = ySplit;
43729
- break;
43730
- case 1:
43731
- rowIndex = bottom;
43732
- newTarget = top + 1;
43733
- break;
43734
- case -1:
43735
- rowIndex = top - 1;
43736
- while (env.model.getters.isRowHidden(sheetId, rowIndex)) {
43737
- rowIndex--;
43738
- }
43739
- newTarget = rowIndex;
43740
- break;
44000
+ if (scrollDirection !== "horizontal") {
44001
+ const previousY = previousEvClientPosition.clientY - position.top;
44002
+ const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);
44003
+ if (edgeScrollInfoY.canEdgeScroll) {
44004
+ canEdgeScroll = true;
44005
+ timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);
44006
+ let newTarget = rowIndex;
44007
+ switch (edgeScrollInfoY.direction) {
44008
+ case "reset":
44009
+ rowIndex = newTarget = ySplit;
44010
+ break;
44011
+ case 1:
44012
+ rowIndex = bottom;
44013
+ newTarget = top + 1;
44014
+ break;
44015
+ case -1:
44016
+ rowIndex = top - 1;
44017
+ while (env.model.getters.isRowHidden(sheetId, rowIndex)) {
44018
+ rowIndex--;
44019
+ }
44020
+ newTarget = rowIndex;
44021
+ break;
44022
+ }
44023
+ scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43741
44024
  }
43742
- scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43743
44025
  }
43744
44026
  if (!canEdgeScroll) {
43745
44027
  colIndex = adjustIndexWithinBounds(colIndex, x, getters.getNumberCols(sheetId) - 1);
@@ -43759,9 +44041,10 @@ stores.inject(MyMetaStore, storeInstance);
43759
44041
  pointerUpCallback?.();
43760
44042
  cleanUp();
43761
44043
  };
43762
- const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp) => {
44044
+ const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp, startScrollDirection = "all") => {
43763
44045
  cleanUp();
43764
44046
  const position = gridOverlayPosition();
44047
+ scrollDirection = startScrollDirection;
43765
44048
  startingX = initialPointerCoordinates.clientX - position.left;
43766
44049
  startingY = initialPointerCoordinates.clientY - position.top;
43767
44050
  previousEvClientPosition = {
@@ -44239,7 +44522,7 @@ stores.inject(MyMetaStore, storeInstance);
44239
44522
  });
44240
44523
  }
44241
44524
  get shouldDisplayCellReference() {
44242
- return this.isCellReferenceVisible;
44525
+ return !this.env.isMobile() && this.isCellReferenceVisible;
44243
44526
  }
44244
44527
  get cellReference() {
44245
44528
  const { col, row, sheetId } = this.composerStore.currentEditedCell;
@@ -44280,11 +44563,12 @@ stores.inject(MyMetaStore, storeInstance);
44280
44563
  onInputContextMenu: this.props.onInputContextMenu,
44281
44564
  composerStore: this.composerStore,
44282
44565
  inputStyle: `max-height: ${maxHeight}px;`,
44566
+ inputMode: this.composerStore.editionMode === "inactive" ? "none" : undefined,
44283
44567
  };
44284
44568
  }
44285
44569
  get containerStyle() {
44286
- if (this.composerStore.editionMode === "inactive") {
44287
- return `z-index: -1000;`;
44570
+ if (this.composerStore.editionMode === "inactive" || this.env.isMobile()) {
44571
+ return `z-index: -1000; opacity: 0;`; // opacity 0 for safari on ios
44288
44572
  }
44289
44573
  const _isFormula = isFormula(this.composerStore.currentContent);
44290
44574
  const cell = this.env.model.getters.getActiveCell();
@@ -44313,11 +44597,13 @@ stores.inject(MyMetaStore, storeInstance);
44313
44597
  *
44314
44598
  * The +-1 are there to include cell borders in the composer sizing/positioning
44315
44599
  */
44600
+ const minHeight = Math.min(height + 1, maxHeight);
44601
+ const minWidth = Math.min(width + 1, maxWidth);
44316
44602
  return cssPropertiesToCss({
44317
44603
  left: `${left - 1}px`,
44318
44604
  top: `${top}px`,
44319
- "min-width": `${width + 1}px`,
44320
- "min-height": `${height + 1}px`,
44605
+ "min-width": `${minWidth}px`,
44606
+ "min-height": `${minHeight}px`,
44321
44607
  "max-width": `${maxWidth}px`,
44322
44608
  "max-height": `${maxHeight}px`,
44323
44609
  background,
@@ -44814,6 +45100,9 @@ stores.inject(MyMetaStore, storeInstance);
44814
45100
  if (!selectResult.isSuccessful) {
44815
45101
  return;
44816
45102
  }
45103
+ if (this.env.isMobile()) {
45104
+ return;
45105
+ }
44817
45106
  const sheetId = this.env.model.getters.getActiveSheetId();
44818
45107
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
44819
45108
  const initialScrollPosition = this.env.model.getters.getActiveSheetScrollInfo();
@@ -45106,78 +45395,6 @@ stores.inject(MyMetaStore, storeInstance);
45106
45395
  }
45107
45396
  }
45108
45397
 
45109
- class GridCellIcon extends owl.Component {
45110
- static template = "o-spreadsheet-GridCellIcon";
45111
- static props = {
45112
- icon: Object,
45113
- verticalAlign: { type: String, optional: true },
45114
- slots: Object,
45115
- };
45116
- get iconStyle() {
45117
- const cellPosition = this.props.icon.position;
45118
- const merge = this.env.model.getters.getMerge(cellPosition);
45119
- const zone = merge || positionToZone(cellPosition);
45120
- const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
45121
- const x = this.getIconHorizontalPosition(rect, cellPosition);
45122
- const y = this.getIconVerticalPosition(rect, cellPosition);
45123
- return cssPropertiesToCss({
45124
- top: `${y}px`,
45125
- left: `${x}px`,
45126
- width: `${this.props.icon.size}px`,
45127
- height: `${this.props.icon.size}px`,
45128
- });
45129
- }
45130
- getIconVerticalPosition(rect, cellPosition) {
45131
- const start = rect.y;
45132
- const end = rect.y + rect.height;
45133
- const cell = this.env.model.getters.getCell(cellPosition);
45134
- const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
45135
- switch (align) {
45136
- case "bottom":
45137
- return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
45138
- case "top":
45139
- return start + GRID_ICON_MARGIN;
45140
- default:
45141
- const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
45142
- return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
45143
- }
45144
- }
45145
- getIconHorizontalPosition(rect, cellPosition) {
45146
- const start = rect.x;
45147
- const end = rect.x + rect.width;
45148
- const cell = this.env.model.getters.getCell(cellPosition);
45149
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
45150
- const align = this.props.icon.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
45151
- switch (align) {
45152
- case "right":
45153
- return end - this.props.icon.size - this.props.icon.margin;
45154
- case "left":
45155
- return start + this.props.icon.margin;
45156
- default:
45157
- const centeringOffset = Math.floor((end - start - this.props.icon.size) / 2);
45158
- return end - this.props.icon.size - centeringOffset;
45159
- }
45160
- }
45161
- isPositionVisible(position) {
45162
- const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
45163
- return !(rect.width === 0 || rect.height === 0);
45164
- }
45165
- }
45166
-
45167
- class GridCellIconOverlay extends owl.Component {
45168
- static template = "o-spreadsheet-GridCellIconOverlay";
45169
- static props = {};
45170
- static components = { GridCellIcon };
45171
- get icons() {
45172
- const icons = [];
45173
- for (const position of this.env.model.getters.getVisibleCellPositions()) {
45174
- const cellIcons = this.env.model.getters.getCellIcons(position);
45175
- icons.push(...cellIcons.filter((icon) => icon.component));
45176
- }
45177
- return icons;
45178
- }
45179
- }
45180
-
45181
45398
  /**
45182
45399
  * Manages an event listener on a ref. Useful for hooks that want to manage
45183
45400
  * event listeners, especially more than one. Prefer using t-on directly in
@@ -45266,7 +45483,7 @@ stores.inject(MyMetaStore, storeInstance);
45266
45483
  return [];
45267
45484
  }
45268
45485
  return data.zones.map((zone) => ({
45269
- zone,
45486
+ range: this.model.getters.getRangeFromZone(data.sheetId, zone),
45270
45487
  color: SELECTION_BORDER_COLOR,
45271
45488
  dashed: true,
45272
45489
  sheetId: data.sheetId,
@@ -45289,7 +45506,7 @@ stores.inject(MyMetaStore, storeInstance);
45289
45506
  }
45290
45507
  }
45291
45508
  hover(position) {
45292
- if (position.col === this.col && position.row === this.row) {
45509
+ if (!this.getters.isDashboard() || (position.col === this.col && position.row === this.row)) {
45293
45510
  return "noStateChange";
45294
45511
  }
45295
45512
  this.col = position.col;
@@ -45323,6 +45540,14 @@ stores.inject(MyMetaStore, storeInstance);
45323
45540
  }
45324
45541
  }
45325
45542
 
45543
+ class HoveredIconStore extends SpreadsheetStore {
45544
+ mutators = ["setHoveredIcon"];
45545
+ hoveredIcon = undefined;
45546
+ setHoveredIcon(icon) {
45547
+ this.hoveredIcon = icon;
45548
+ }
45549
+ }
45550
+
45326
45551
  const CURSOR_SVG = /*xml*/ `
45327
45552
  <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="16"><path d="M6.5.4c1.3-.8 2.9-.1 3.8 1.4l2.9 5.1c.2.4.9 1.6-.4 2.3l-1.6.9 1.8 3.1c.2.4.1 1-.2 1.2l-1.6 1c-.3.1-.9 0-1.1-.4l-1.8-3.1-1.6 1c-.6.4-1.7 0-2.2-.8L0 4.3"/><path fill="#fff" d="M9.1 2a1.4 1.1 60 0 0-1.7-.6L5.5 2.5l.9 1.6-1 .6-.9-1.6-.6.4 1.8 3.1-1.3.7-1.8-3.1-1 .6 3.8 6.6 6.8-3.98M3.9 8.8 10.82 5l.795 1.4-6.81 3.96"/></svg>
45328
45553
  `;
@@ -45394,10 +45619,11 @@ stores.inject(MyMetaStore, storeInstance);
45394
45619
  return pause();
45395
45620
  }
45396
45621
  }
45397
- useRefListener(gridRef, "pointermove", updateMousePosition);
45622
+ useRefListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(ev));
45398
45623
  useRefListener(gridRef, "mouseleave", onMouseLeave);
45399
45624
  useRefListener(gridRef, "mouseenter", resume);
45400
45625
  useRefListener(gridRef, "pointerdown", recompute);
45626
+ useRefListener(gridRef, "pointerdown", (ev) => env.isMobile() && updateMousePosition(ev));
45401
45627
  owl.useExternalListener(window, "click", handleGlobalClick);
45402
45628
  function handleGlobalClick(e) {
45403
45629
  const target = e.target;
@@ -45426,11 +45652,11 @@ stores.inject(MyMetaStore, storeInstance);
45426
45652
  onGridMoved: Function,
45427
45653
  gridOverlayDimensions: String,
45428
45654
  slots: { type: Object, optional: true },
45655
+ getGridSize: Function,
45429
45656
  };
45430
45657
  static components = {
45431
45658
  FiguresContainer,
45432
45659
  GridAddRowsFooter,
45433
- GridCellIconOverlay,
45434
45660
  };
45435
45661
  static defaultProps = {
45436
45662
  onCellDoubleClicked: () => { },
@@ -45442,15 +45668,17 @@ stores.inject(MyMetaStore, storeInstance);
45442
45668
  gridOverlay = owl.useRef("gridOverlay");
45443
45669
  cellPopovers;
45444
45670
  paintFormatStore;
45671
+ hoveredIconStore;
45445
45672
  setup() {
45446
45673
  useCellHovered(this.env, this.gridOverlay);
45447
45674
  const resizeObserver = new ResizeObserver(() => {
45448
45675
  const boundingRect = this.gridOverlayEl.getBoundingClientRect();
45676
+ const { width, height } = this.props.getGridSize();
45449
45677
  this.props.onGridResized({
45450
45678
  x: boundingRect.left,
45451
45679
  y: boundingRect.top,
45452
- height: this.gridOverlayEl.clientHeight,
45453
- width: this.gridOverlayEl.clientWidth,
45680
+ height: height,
45681
+ width: width,
45454
45682
  });
45455
45683
  });
45456
45684
  owl.onMounted(() => {
@@ -45461,6 +45689,7 @@ stores.inject(MyMetaStore, storeInstance);
45461
45689
  });
45462
45690
  this.cellPopovers = useStore(CellPopoverStore);
45463
45691
  this.paintFormatStore = useStore(PaintFormatStore);
45692
+ this.hoveredIconStore = useStore(HoveredIconStore);
45464
45693
  }
45465
45694
  get gridOverlayEl() {
45466
45695
  if (!this.gridOverlay.el) {
@@ -45469,26 +45698,59 @@ stores.inject(MyMetaStore, storeInstance);
45469
45698
  return this.gridOverlay.el;
45470
45699
  }
45471
45700
  get style() {
45472
- return this.props.gridOverlayDimensions;
45701
+ return (this.props.gridOverlayDimensions +
45702
+ cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "default" }));
45473
45703
  }
45474
45704
  get isPaintingFormat() {
45475
45705
  return this.paintFormatStore.isActive;
45476
45706
  }
45477
- onMouseDown(ev) {
45478
- if (ev.button > 0) {
45707
+ onPointerMove(ev) {
45708
+ if (this.env.isMobile()) {
45709
+ return;
45710
+ }
45711
+ const icon = this.getInteractiveIconAtEvent(ev);
45712
+ const hoveredIcon = icon?.type ? { id: icon.type, position: icon.position } : undefined;
45713
+ if (!deepEquals(hoveredIcon, this.hoveredIconStore.hoveredIcon)) {
45714
+ this.hoveredIconStore.setHoveredIcon(hoveredIcon);
45715
+ }
45716
+ }
45717
+ onPointerDown(ev) {
45718
+ if (ev.button > 0 || this.env.isMobile()) {
45479
45719
  // not main button, probably a context menu
45480
45720
  return;
45481
45721
  }
45482
- if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
45483
- this.cellPopovers.close();
45722
+ this.onCellClicked(ev);
45723
+ }
45724
+ onClick(ev) {
45725
+ if (ev.button > 0 || !this.env.isMobile()) {
45726
+ // not main button, probably a context menu
45727
+ return;
45484
45728
  }
45729
+ this.onCellClicked(ev);
45730
+ }
45731
+ onCellClicked(ev) {
45732
+ const openedPopover = this.cellPopovers.persistentCellPopover;
45485
45733
  const [col, row] = this.getCartesianCoordinates(ev);
45486
45734
  this.props.onCellClicked(col, row, {
45487
45735
  expandZone: ev.shiftKey,
45488
45736
  addZone: isCtrlKey(ev),
45489
45737
  }, ev);
45738
+ const clickedIcon = this.getInteractiveIconAtEvent(ev);
45739
+ if (clickedIcon?.onClick) {
45740
+ clickedIcon.onClick(clickedIcon.position, this.env);
45741
+ }
45742
+ if (ev.target === this.gridOverlay.el &&
45743
+ this.cellPopovers.isOpen &&
45744
+ deepEquals(openedPopover, this.cellPopovers.persistentCellPopover)) {
45745
+ // Only close the popover if props.click/icon.click didn't open a new one
45746
+ this.cellPopovers.close();
45747
+ return;
45748
+ }
45490
45749
  }
45491
45750
  onDoubleClick(ev) {
45751
+ if (this.getInteractiveIconAtEvent(ev)) {
45752
+ return;
45753
+ }
45492
45754
  const [col, row] = this.getCartesianCoordinates(ev);
45493
45755
  this.props.onCellDoubleClicked(col, row);
45494
45756
  }
@@ -45504,6 +45766,24 @@ stores.inject(MyMetaStore, storeInstance);
45504
45766
  const rowIndex = this.env.model.getters.getRowIndex(y);
45505
45767
  return [colIndex, rowIndex];
45506
45768
  }
45769
+ getInteractiveIconAtEvent(ev) {
45770
+ const gridOverLayRect = getRefBoundingRect(this.gridOverlay);
45771
+ const gridOffset = this.env.model.getters.getGridOffset();
45772
+ const x = ev.clientX - gridOverLayRect.x + gridOffset.x;
45773
+ const y = ev.clientY - gridOverLayRect.y + gridOffset.y;
45774
+ const [col, row] = this.getCartesianCoordinates(ev);
45775
+ const sheetId = this.env.model.getters.getActiveSheetId();
45776
+ let position = { col, row, sheetId };
45777
+ const merge = this.env.model.getters.getMerge(position);
45778
+ if (merge) {
45779
+ position = { col: merge.left, row: merge.top, sheetId };
45780
+ }
45781
+ const icons = this.env.model.getters.getCellIcons(position);
45782
+ const icon = icons.find((icon) => {
45783
+ return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon));
45784
+ });
45785
+ return icon?.onClick ? icon : undefined;
45786
+ }
45507
45787
  }
45508
45788
 
45509
45789
  class GridPopover extends owl.Component {
@@ -45664,6 +45944,9 @@ stores.inject(MyMetaStore, storeInstance);
45664
45944
  this.state.waitingForMove = false;
45665
45945
  }
45666
45946
  onMouseMove(ev) {
45947
+ if (this.env.isMobile()) {
45948
+ return;
45949
+ }
45667
45950
  if (this.state.isResizing || this.state.isMoving || this.state.isSelecting) {
45668
45951
  return;
45669
45952
  }
@@ -45708,7 +45991,21 @@ stores.inject(MyMetaStore, storeInstance);
45708
45991
  };
45709
45992
  startDnd(onMouseMove, onMouseUp);
45710
45993
  }
45994
+ onClick(ev) {
45995
+ if (!this.env.isMobile()) {
45996
+ return;
45997
+ }
45998
+ if (ev.button > 0) {
45999
+ // not main button, probably a context menu
46000
+ return;
46001
+ }
46002
+ const index = this._getElementIndex(this._getEvOffset(ev));
46003
+ this._selectElement(index, false);
46004
+ }
45711
46005
  select(ev) {
46006
+ if (this.env.isMobile()) {
46007
+ return;
46008
+ }
45712
46009
  if (ev.button > 0) {
45713
46010
  // not main button, probably a context menu
45714
46011
  return;
@@ -45777,6 +46074,9 @@ stores.inject(MyMetaStore, storeInstance);
45777
46074
  this.dragNDropGrid.start(ev, mouseMoveMovement, mouseUpMovement);
45778
46075
  }
45779
46076
  startSelection(ev, index) {
46077
+ if (this.env.isMobile()) {
46078
+ return;
46079
+ }
45780
46080
  this.state.isSelecting = true;
45781
46081
  if (ev.shiftKey) {
45782
46082
  this._increaseSelection(index);
@@ -46222,11 +46522,13 @@ stores.inject(MyMetaStore, storeInstance);
46222
46522
  renderer;
46223
46523
  fingerprints;
46224
46524
  hoveredTables;
46525
+ hoveredIcon;
46225
46526
  constructor(get) {
46226
46527
  this.getters = get(ModelStore).getters;
46227
46528
  this.renderer = get(RendererStore);
46228
46529
  this.fingerprints = get(FormulaFingerprintStore);
46229
46530
  this.hoveredTables = get(HoveredTableStore);
46531
+ this.hoveredIcon = get(HoveredIconStore);
46230
46532
  this.renderer.register(this);
46231
46533
  }
46232
46534
  get renderingLayers() {
@@ -46463,7 +46765,7 @@ stores.inject(MyMetaStore, storeInstance);
46463
46765
  // compute vertical align start point parameter:
46464
46766
  const textLineHeight = computeTextFontSizeInPixels(style);
46465
46767
  const numberOfLines = box.content.textLines.length;
46466
- let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
46768
+ let y = this.getters.computeTextYCoordinate(box, textLineHeight, style.verticalAlign, numberOfLines);
46467
46769
  // use the horizontal and the vertical start points to:
46468
46770
  // fill text / fill strikethrough / fill underline
46469
46771
  for (const brokenLine of box.content.textLines) {
@@ -46480,7 +46782,12 @@ stores.inject(MyMetaStore, storeInstance);
46480
46782
  const { ctx } = renderingContext;
46481
46783
  for (const box of boxes) {
46482
46784
  for (const icon of Object.values(box.icons)) {
46483
- if (!icon || !icon.svg) {
46785
+ if (!icon) {
46786
+ continue;
46787
+ }
46788
+ const isHovered = deepEquals({ id: icon.type, position: icon.position }, this.hoveredIcon.hoveredIcon);
46789
+ const svg = isHovered ? icon.hoverSvg || icon.svg : icon.svg;
46790
+ if (!svg) {
46484
46791
  continue;
46485
46792
  }
46486
46793
  ctx.save();
@@ -46488,46 +46795,17 @@ stores.inject(MyMetaStore, storeInstance);
46488
46795
  ctx.rect(box.x, box.y, box.width, box.height);
46489
46796
  ctx.clip();
46490
46797
  const iconSize = icon.size;
46491
- const iconY = this.computeTextYCoordinate(box, iconSize);
46492
- const svg = icon.svg;
46493
- let x;
46494
- if (icon.horizontalAlign === "left") {
46495
- x = box.x + icon.margin;
46496
- }
46497
- else if (icon.horizontalAlign === "right") {
46498
- x = box.x + box.width - iconSize - icon.margin;
46499
- }
46500
- else {
46501
- x = box.x + (box.width - iconSize) / 2;
46502
- }
46503
- ctx.translate(x, iconY);
46798
+ const { x, y } = this.getters.getCellIconRect(icon);
46799
+ ctx.translate(x, y);
46504
46800
  ctx.scale(iconSize / svg.width, iconSize / svg.height);
46505
- ctx.fillStyle = svg.fillColor;
46506
- ctx.fill(new Path2D(svg.path));
46801
+ for (const path of svg.paths) {
46802
+ ctx.fillStyle = path.fillColor;
46803
+ ctx.fill(getPath2D(path.path));
46804
+ }
46507
46805
  ctx.restore();
46508
46806
  }
46509
46807
  }
46510
46808
  }
46511
- /** Computes the vertical start point from which a text line should be draw.
46512
- *
46513
- * Note that in case the cell does not have enough spaces to display its text lines,
46514
- * (wrapping cell case) then the vertical align should be at the top.
46515
- * */
46516
- computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {
46517
- const y = box.y + 1;
46518
- const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
46519
- const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
46520
- const verticalAlign = box.verticalAlign || DEFAULT_VERTICAL_ALIGN;
46521
- if (hasEnoughSpaces) {
46522
- if (verticalAlign === "middle") {
46523
- return y + (box.height - textHeight) / 2;
46524
- }
46525
- if (verticalAlign === "bottom") {
46526
- return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;
46527
- }
46528
- }
46529
- return y + MIN_CELL_TEXT_MARGIN;
46530
- }
46531
46809
  drawHeaders(renderingContext) {
46532
46810
  const { ctx, thinLineWidth } = renderingContext;
46533
46811
  const visibleCols = this.getters.getSheetViewVisibleCols();
@@ -46964,6 +47242,9 @@ stores.inject(MyMetaStore, storeInstance);
46964
47242
  const deltaX = lastX - clientX;
46965
47243
  const deltaY = lastY - clientY;
46966
47244
  const elapsedTime = currentTime - lastTime;
47245
+ if (!elapsedTime) {
47246
+ return;
47247
+ }
46967
47248
  velocityX = deltaX / elapsedTime;
46968
47249
  velocityY = deltaY / elapsedTime;
46969
47250
  lastX = clientX;
@@ -46984,6 +47265,11 @@ stores.inject(MyMetaStore, storeInstance);
46984
47265
  function onTouchEnd(ev) {
46985
47266
  isMouseDown = false;
46986
47267
  lastX = lastY = 0;
47268
+ if (resetTimeout) {
47269
+ clearTimeout(resetTimeout);
47270
+ }
47271
+ velocityX *= 1.2;
47272
+ velocityY *= 1.2;
46987
47273
  requestAnimationFrame(scroll);
46988
47274
  }
46989
47275
  function scroll() {
@@ -47068,12 +47354,16 @@ stores.inject(MyMetaStore, storeInstance);
47068
47354
  }
47069
47355
  }
47070
47356
 
47357
+ const MOBILE_HANDLER_WIDTH = 40;
47071
47358
  css /* scss */ `
47072
47359
  .o-corner {
47073
47360
  position: absolute;
47074
- height: 8px;
47075
- width: 8px;
47361
+ }
47362
+
47363
+ .o-corner-button {
47076
47364
  border: 1px solid white;
47365
+ height: ${AUTOFILL_EDGE_LENGTH}px;
47366
+ width: ${AUTOFILL_EDGE_LENGTH}px;
47077
47367
  }
47078
47368
  .o-corner-nw,
47079
47369
  .o-corner-se {
@@ -47100,34 +47390,61 @@ stores.inject(MyMetaStore, storeInstance);
47100
47390
  isResizing: Boolean,
47101
47391
  onResizeHighlight: Function,
47102
47392
  };
47103
- isTop = this.props.orientation[0] === "n";
47104
- isLeft = this.props.orientation[1] === "w";
47105
- get style() {
47393
+ dirX;
47394
+ dirY;
47395
+ setup() {
47396
+ const { dirX, dirY } = orientationToDir(this.props.orientation);
47397
+ this.dirX = dirX;
47398
+ this.dirY = dirY;
47399
+ }
47400
+ get handlerStyle() {
47106
47401
  const z = this.props.zone;
47107
- const col = this.isLeft ? z.left : z.right;
47108
- const row = this.isTop ? z.top : z.bottom;
47109
47402
  const rect = this.env.model.getters.getVisibleRect({
47110
- left: col,
47111
- right: col,
47112
- top: row,
47113
- bottom: row,
47403
+ left: this.dirX === 1 ? z.right : z.left,
47404
+ right: this.dirX === -1 ? z.left : z.right,
47405
+ top: this.dirY === 1 ? z.bottom : z.top,
47406
+ bottom: this.dirY === -1 ? z.top : z.bottom,
47114
47407
  });
47115
47408
  // Don't show if not visible in the viewport
47116
47409
  if (rect.width * rect.height === 0) {
47117
- return `display:none`;
47410
+ return `display: none !important;`;
47411
+ }
47412
+ const leftValue = rect.x + rect.width / 2 + (this.dirX * rect.width) / 2;
47413
+ const topValue = rect.y + rect.height / 2 + (this.dirY * rect.height) / 2;
47414
+ const edgeLength = this.getHandlerEdgeLength();
47415
+ const css = {
47416
+ left: `${leftValue - edgeLength / 2}px`,
47417
+ top: `${topValue - edgeLength / 2}px`,
47418
+ height: `${edgeLength}px`,
47419
+ width: `${edgeLength}px`,
47420
+ };
47421
+ if (this.env.isMobile()) {
47422
+ css["border-radius"] = `${edgeLength / 2}px`;
47118
47423
  }
47119
- const leftValue = this.isLeft ? rect.x : rect.x + rect.width;
47120
- const topValue = this.isTop ? rect.y : rect.y + rect.height;
47121
- return cssPropertiesToCss({
47122
- left: `${leftValue - AUTOFILL_EDGE_LENGTH / 2}px`,
47123
- top: `${topValue - AUTOFILL_EDGE_LENGTH / 2}px`,
47424
+ return cssPropertiesToCss(css);
47425
+ }
47426
+ getHandlerEdgeLength() {
47427
+ return this.env.isMobile() ? MOBILE_HANDLER_WIDTH : AUTOFILL_EDGE_LENGTH;
47428
+ }
47429
+ get buttonLook() {
47430
+ const css = {
47124
47431
  "background-color": this.props.color,
47125
- });
47432
+ cursor: `${this.props.orientation}-resize`,
47433
+ };
47434
+ if (this.env.isMobile()) {
47435
+ css["border-radius"] = `${AUTOFILL_EDGE_LENGTH / 2}px`;
47436
+ }
47437
+ return cssPropertiesToCss(css);
47126
47438
  }
47127
47439
  onMouseDown(ev) {
47128
- this.props.onResizeHighlight(ev, this.isLeft, this.isTop);
47440
+ this.props.onResizeHighlight(ev, this.dirX, this.dirY);
47129
47441
  }
47130
47442
  }
47443
+ function orientationToDir(or) {
47444
+ const dirX = or.includes("w") ? -1 : or.includes("e") ? 1 : 0;
47445
+ const dirY = or.includes("n") ? -1 : or.includes("s") ? 1 : 0;
47446
+ return { dirX, dirY };
47447
+ }
47131
47448
 
47132
47449
  css /*SCSS*/ `
47133
47450
  .o-highlight {
@@ -47137,7 +47454,7 @@ stores.inject(MyMetaStore, storeInstance);
47137
47454
  class Highlight extends owl.Component {
47138
47455
  static template = "o-spreadsheet-Highlight";
47139
47456
  static props = {
47140
- zone: Object,
47457
+ range: Object,
47141
47458
  color: String,
47142
47459
  };
47143
47460
  static components = {
@@ -47148,26 +47465,49 @@ stores.inject(MyMetaStore, storeInstance);
47148
47465
  shiftingMode: "none",
47149
47466
  });
47150
47467
  dragNDropGrid = useDragAndDropBeyondTheViewport(this.env);
47151
- onResizeHighlight(ev, isLeft, isTop) {
47468
+ get cornerOrientations() {
47469
+ if (!this.env.isMobile()) {
47470
+ return ["nw", "ne", "sw", "se"];
47471
+ }
47472
+ const z = this.props.range.unboundedZone;
47473
+ if (z.bottom === undefined) {
47474
+ return ["w", "e"];
47475
+ }
47476
+ else if (z.right === undefined) {
47477
+ return ["n", "s"];
47478
+ }
47479
+ else {
47480
+ return ["nw", "se"];
47481
+ }
47482
+ }
47483
+ onResizeHighlight(ev, dirX, dirY) {
47152
47484
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47153
47485
  this.highlightState.shiftingMode = "isResizing";
47154
- const z = this.props.zone;
47155
- const pivotCol = isLeft ? z.right : z.left;
47156
- const pivotRow = isTop ? z.bottom : z.top;
47157
- let lastCol = isLeft ? z.left : z.right;
47158
- let lastRow = isTop ? z.top : z.bottom;
47486
+ const z = this.props.range.zone;
47487
+ const pivotCol = dirX === 1 ? z.left : z.right;
47488
+ const pivotRow = dirY === 1 ? z.top : z.bottom;
47489
+ let lastCol = dirX === 1 ? z.right : z.left;
47490
+ let lastRow = dirY === 1 ? z.bottom : z.top;
47159
47491
  let currentZone = z;
47492
+ let scrollDirection = "all";
47493
+ if (this.env.isMobile()) {
47494
+ scrollDirection = dirX === 0 ? "vertical" : dirY === 0 ? "horizontal" : "all";
47495
+ }
47160
47496
  this.env.model.dispatch("START_CHANGE_HIGHLIGHT", { zone: currentZone });
47161
47497
  const mouseMove = (col, row) => {
47162
47498
  if (lastCol !== col || lastRow !== row) {
47163
- lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
47164
- lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
47165
- const newZone = {
47166
- left: Math.min(pivotCol, lastCol),
47167
- top: Math.min(pivotRow, lastRow),
47168
- right: Math.max(pivotCol, lastCol),
47169
- bottom: Math.max(pivotRow, lastRow),
47170
- };
47499
+ let { left, right, top, bottom } = currentZone;
47500
+ if (scrollDirection !== "horizontal") {
47501
+ lastRow = lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
47502
+ top = Math.min(pivotRow, lastRow);
47503
+ bottom = Math.max(pivotRow, lastRow);
47504
+ }
47505
+ if (scrollDirection !== "vertical") {
47506
+ lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
47507
+ left = Math.min(pivotCol, lastCol);
47508
+ right = Math.max(pivotCol, lastCol);
47509
+ }
47510
+ const newZone = { left, right, top, bottom };
47171
47511
  if (!isEqual(newZone, currentZone)) {
47172
47512
  this.env.model.selection.selectZone({
47173
47513
  cell: { col: newZone.left, row: newZone.top },
@@ -47180,11 +47520,11 @@ stores.inject(MyMetaStore, storeInstance);
47180
47520
  const mouseUp = () => {
47181
47521
  this.highlightState.shiftingMode = "none";
47182
47522
  };
47183
- this.dragNDropGrid.start(ev, mouseMove, mouseUp);
47523
+ this.dragNDropGrid.start(ev, mouseMove, mouseUp, scrollDirection);
47184
47524
  }
47185
47525
  onMoveHighlight(ev) {
47186
47526
  this.highlightState.shiftingMode = "isMoving";
47187
- const z = this.props.zone;
47527
+ const z = this.props.range.zone;
47188
47528
  const position = gridOverlayPosition();
47189
47529
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47190
47530
  const initCol = this.env.model.getters.getColIndex(ev.clientX - position.left);
@@ -47406,6 +47746,18 @@ stores.inject(MyMetaStore, storeInstance);
47406
47746
  }
47407
47747
  }
47408
47748
 
47749
+ class Selection extends owl.Component {
47750
+ static template = "o-spreadsheet-Selection";
47751
+ static props = {};
47752
+ static components = { Highlight };
47753
+ get highlightProps() {
47754
+ const sheetId = this.env.model.getters.getActiveSheetId();
47755
+ const zone = this.env.model.getters.getUnboundedZone(sheetId, this.env.model.getters.getSelectedZone());
47756
+ const range = this.env.model.getters.getRangeFromZone(sheetId, zone);
47757
+ return { range, color: SELECTION_BORDER_COLOR };
47758
+ }
47759
+ }
47760
+
47409
47761
  class Section extends owl.Component {
47410
47762
  static template = "o_spreadsheet.Section";
47411
47763
  static props = {
@@ -48351,6 +48703,7 @@ stores.inject(MyMetaStore, storeInstance);
48351
48703
 
48352
48704
  css /* scss */ `
48353
48705
  .o-font-size-editor {
48706
+ width: max-content !important;
48354
48707
  height: calc(100% - 4px);
48355
48708
  input.o-font-size {
48356
48709
  outline: none;
@@ -50499,8 +50852,7 @@ stores.inject(MyMetaStore, storeInstance);
50499
50852
  get highlights() {
50500
50853
  const sheetId = this.env.model.getters.getActiveSheetId();
50501
50854
  return this.props.conditionalFormat.ranges.map((range) => ({
50502
- sheetId,
50503
- zone: this.env.model.getters.getRangeFromSheetXC(sheetId, range).zone,
50855
+ range: this.env.model.getters.getRangeFromSheetXC(sheetId, range),
50504
50856
  color: HIGHLIGHT_COLOR,
50505
50857
  fillAlpha: 0.06,
50506
50858
  }));
@@ -51458,8 +51810,7 @@ stores.inject(MyMetaStore, storeInstance);
51458
51810
  }
51459
51811
  get highlights() {
51460
51812
  return this.props.rule.ranges.map((range) => ({
51461
- sheetId: this.env.model.getters.getActiveSheetId(),
51462
- zone: range.zone,
51813
+ range,
51463
51814
  color: HIGHLIGHT_COLOR,
51464
51815
  fillAlpha: 0.06,
51465
51816
  }));
@@ -51788,13 +52139,15 @@ stores.inject(MyMetaStore, storeInstance);
51788
52139
  if (this.selectedMatchIndex === null) {
51789
52140
  return;
51790
52141
  }
52142
+ this.preserveSelectedMatchIndex = true;
52143
+ this.shouldFinalizeUpdateSelection = true;
51791
52144
  this.model.dispatch("REPLACE_SEARCH", {
51792
52145
  searchString: this.toSearch,
51793
52146
  replaceWith: this.toReplace,
51794
52147
  matches: [this.searchMatches[this.selectedMatchIndex]],
51795
52148
  searchOptions: this.searchOptions,
51796
52149
  });
51797
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
52150
+ this.preserveSelectedMatchIndex = false;
51798
52151
  }
51799
52152
  /**
51800
52153
  * Apply the replace function to all the matches one time.
@@ -51866,8 +52219,7 @@ stores.inject(MyMetaStore, storeInstance);
51866
52219
  const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
51867
52220
  if (width > 0 && height > 0) {
51868
52221
  highlights.push({
51869
- sheetId,
51870
- zone: zoneWithMerge,
52222
+ range: this.model.getters.getRangeFromZone(sheetId, zoneWithMerge),
51871
52223
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51872
52224
  noBorder: index !== this.selectedMatchIndex,
51873
52225
  thinLine: true,
@@ -51879,8 +52231,7 @@ stores.inject(MyMetaStore, storeInstance);
51879
52231
  const range = this.searchOptions.specificRange;
51880
52232
  if (range && range.sheetId === sheetId) {
51881
52233
  highlights.push({
51882
- sheetId,
51883
- zone: range.zone,
52234
+ range,
51884
52235
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51885
52236
  noFill: true,
51886
52237
  thinLine: true,
@@ -52254,7 +52605,11 @@ stores.inject(MyMetaStore, storeInstance);
52254
52605
  const sheetId = getters.getActiveSheetId();
52255
52606
  const pivotCellPositions = getVisiblePivotCellPositions(getters, pivotId);
52256
52607
  const mergedZones = mergeContiguousZones(pivotCellPositions.map(positionToZone));
52257
- return mergedZones.map((zone) => ({ sheetId, zone, noFill: true, color: HIGHLIGHT_COLOR }));
52608
+ return mergedZones.map((zone) => ({
52609
+ range: getters.getRangeFromZone(sheetId, zone),
52610
+ noFill: true,
52611
+ color: HIGHLIGHT_COLOR,
52612
+ }));
52258
52613
  }
52259
52614
  function getVisiblePivotCellPositions(getters, pivotId) {
52260
52615
  const positions = [];
@@ -52523,7 +52878,7 @@ stores.inject(MyMetaStore, storeInstance);
52523
52878
 
52524
52879
  class CogWheelMenu extends owl.Component {
52525
52880
  static template = "o-spreadsheet-CogWheelMenu";
52526
- static components = { Menu };
52881
+ static components = { MenuPopover };
52527
52882
  static props = {
52528
52883
  items: Array,
52529
52884
  };
@@ -55093,6 +55448,7 @@ stores.inject(MyMetaStore, storeInstance);
55093
55448
  id: "editTableStyle",
55094
55449
  name: _t("Edit table style"),
55095
55450
  execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
55451
+ isEnabled: (env) => !env.isSmall,
55096
55452
  icon: "o-spreadsheet-Icon.EDIT",
55097
55453
  },
55098
55454
  {
@@ -55214,7 +55570,7 @@ stores.inject(MyMetaStore, storeInstance);
55214
55570
  `;
55215
55571
  class TableStylePreview extends owl.Component {
55216
55572
  static template = "o-spreadsheet-TableStylePreview";
55217
- static components = { Menu };
55573
+ static components = { MenuPopover };
55218
55574
  static props = {
55219
55575
  tableConfig: Object,
55220
55576
  tableStyle: Object,
@@ -55785,6 +56141,17 @@ stores.inject(MyMetaStore, storeInstance);
55785
56141
  },
55786
56142
  });
55787
56143
 
56144
+ class ScreenWidthStore {
56145
+ mutators = ["setSmallThreshhold"];
56146
+ _isSmallCallback = () => false;
56147
+ get isSmall() {
56148
+ return this._isSmallCallback();
56149
+ }
56150
+ setSmallThreshhold(isSmall) {
56151
+ this._isSmallCallback = isSmall;
56152
+ }
56153
+ }
56154
+
55788
56155
  const DEFAULT_SIDE_PANEL_SIZE = 350;
55789
56156
  const MIN_SHEET_VIEW_WIDTH = 150;
55790
56157
  class SidePanelStore extends SpreadsheetStore {
@@ -55792,6 +56159,7 @@ stores.inject(MyMetaStore, storeInstance);
55792
56159
  initialPanelProps = {};
55793
56160
  componentTag = "";
55794
56161
  panelSize = DEFAULT_SIDE_PANEL_SIZE;
56162
+ screenWidthStore = this.get(ScreenWidthStore);
55795
56163
  get isOpen() {
55796
56164
  if (!this.componentTag) {
55797
56165
  return false;
@@ -55813,6 +56181,9 @@ stores.inject(MyMetaStore, storeInstance);
55813
56181
  return undefined;
55814
56182
  }
55815
56183
  open(componentTag, panelProps = {}) {
56184
+ if (this.screenWidthStore.isSmall) {
56185
+ return;
56186
+ }
55816
56187
  const state = this.computeState(componentTag, panelProps);
55817
56188
  if (!state.isOpen) {
55818
56189
  return;
@@ -55927,8 +56298,7 @@ stores.inject(MyMetaStore, storeInstance);
55927
56298
  return [];
55928
56299
  return [
55929
56300
  {
55930
- zone: this.state.highlightZone,
55931
- sheetId: this.props.table.range.sheetId,
56301
+ range: this.env.model.getters.getRangeFromZone(this.props.table.range.sheetId, this.state.highlightZone),
55932
56302
  color: COLOR,
55933
56303
  noFill: true,
55934
56304
  },
@@ -55950,13 +56320,14 @@ stores.inject(MyMetaStore, storeInstance);
55950
56320
  static template = "o-spreadsheet-Grid";
55951
56321
  static props = {
55952
56322
  exposeFocus: Function,
56323
+ getGridSize: Function,
55953
56324
  };
55954
56325
  static components = {
55955
56326
  GridComposer,
55956
56327
  GridOverlay,
55957
56328
  GridPopover,
55958
56329
  HeadersOverlay,
55959
- Menu,
56330
+ MenuPopover,
55960
56331
  Autofill,
55961
56332
  ClientTag,
55962
56333
  Highlight,
@@ -55964,6 +56335,7 @@ stores.inject(MyMetaStore, storeInstance);
55964
56335
  VerticalScrollBar,
55965
56336
  HorizontalScrollBar,
55966
56337
  TableResizer,
56338
+ Selection,
55967
56339
  };
55968
56340
  HEADER_HEIGHT = HEADER_HEIGHT;
55969
56341
  HEADER_WIDTH = HEADER_WIDTH;
@@ -56246,8 +56618,8 @@ stores.inject(MyMetaStore, storeInstance);
56246
56618
  }
56247
56619
  onGridResized({ height, width }) {
56248
56620
  this.env.model.dispatch("RESIZE_SHEETVIEW", {
56249
- width: width,
56250
- height: height,
56621
+ width: width - HEADER_WIDTH,
56622
+ height: height - HEADER_HEIGHT,
56251
56623
  gridOffsetX: HEADER_WIDTH,
56252
56624
  gridOffsetY: HEADER_HEIGHT,
56253
56625
  });
@@ -56301,6 +56673,9 @@ stores.inject(MyMetaStore, storeInstance);
56301
56673
  else {
56302
56674
  this.env.model.selection.selectCell(col, row);
56303
56675
  }
56676
+ if (this.env.isMobile()) {
56677
+ return;
56678
+ }
56304
56679
  let prevCol = col;
56305
56680
  let prevRow = row;
56306
56681
  const onMouseMove = (col, row, ev) => {
@@ -56586,6 +56961,9 @@ stores.inject(MyMetaStore, storeInstance);
56586
56961
  const sheetId = this.env.model.getters.getActiveSheetId();
56587
56962
  return this.env.model.getters.getCoreTables(sheetId).filter(isStaticTable);
56588
56963
  }
56964
+ get displaySelectionHandler() {
56965
+ return this.env.isMobile() && this.composerFocusStore.activeComposer.editionMode === "inactive";
56966
+ }
56589
56967
  }
56590
56968
 
56591
56969
  /**
@@ -62512,7 +62890,7 @@ stores.inject(MyMetaStore, storeInstance);
62512
62890
  break;
62513
62891
  }
62514
62892
  case "UPDATE_PIVOT": {
62515
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
62893
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
62516
62894
  this.compileCalculatedMeasures(cmd.pivot.measures);
62517
62895
  break;
62518
62896
  }
@@ -62583,7 +62961,10 @@ stores.inject(MyMetaStore, storeInstance);
62583
62961
  // Private
62584
62962
  // -------------------------------------------------------------------------
62585
62963
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
62586
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
62964
+ this.history.update("pivots", pivotId, {
62965
+ definition: this.repairSortedColumn(deepCopy(pivot)),
62966
+ formulaId,
62967
+ });
62587
62968
  this.compileCalculatedMeasures(pivot.measures);
62588
62969
  this.history.update("formulaIds", formulaId, pivotId);
62589
62970
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -62672,6 +63053,7 @@ stores.inject(MyMetaStore, storeInstance);
62672
63053
  }
62673
63054
  }
62674
63055
  checkSortedColumnInMeasures(definition) {
63056
+ definition = this.repairSortedColumn(definition);
62675
63057
  const measures = definition.measures.map((measure) => measure.id);
62676
63058
  if (definition.sortedColumn && !measures.includes(definition.sortedColumn.measure)) {
62677
63059
  return "InvalidDefinition" /* CommandResult.InvalidDefinition */;
@@ -62685,6 +63067,26 @@ stores.inject(MyMetaStore, storeInstance);
62685
63067
  }
62686
63068
  return "Success" /* CommandResult.Success */;
62687
63069
  }
63070
+ repairSortedColumn(definition) {
63071
+ if (definition.sortedColumn) {
63072
+ // Fix for an upgrade issue: the sortedColumn measure was not updated
63073
+ // from using fieldName to using id. If the sortedColumn measure matches
63074
+ // a measure fieldName in the definition, update it to use the measure's id instead
63075
+ // of its fieldName.
63076
+ // TODO: add an upgrade step to fix this in master and remove this code
63077
+ const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
63078
+ if (sortedMeasure) {
63079
+ return {
63080
+ ...definition,
63081
+ sortedColumn: {
63082
+ ...definition.sortedColumn,
63083
+ measure: sortedMeasure.id,
63084
+ },
63085
+ };
63086
+ }
63087
+ }
63088
+ return definition;
63089
+ }
62688
63090
  // ---------------------------------------------------------------------
62689
63091
  // Import/Export
62690
63092
  // ---------------------------------------------------------------------
@@ -65552,186 +65954,6 @@ stores.inject(MyMetaStore, storeInstance);
65552
65954
  }
65553
65955
  }
65554
65956
 
65555
- const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
65556
- css /* scss */ `
65557
- .o-dv-checkbox {
65558
- margin: ${MARGIN}px;
65559
- /* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
65560
- position: absolute;
65561
- }
65562
- `;
65563
- class DataValidationCheckbox extends owl.Component {
65564
- static template = "o-spreadsheet-DataValidationCheckbox";
65565
- static components = {
65566
- Checkbox,
65567
- };
65568
- static props = {
65569
- cellPosition: Object,
65570
- };
65571
- onCheckboxChange(value) {
65572
- const { sheetId, col, row } = this.props.cellPosition;
65573
- const cellContent = value ? "TRUE" : "FALSE";
65574
- this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
65575
- }
65576
- get checkBoxValue() {
65577
- return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
65578
- }
65579
- get isDisabled() {
65580
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
65581
- return this.env.model.getters.isReadonly() || !!cell?.isFormula;
65582
- }
65583
- }
65584
-
65585
- const ICON_WIDTH = 13;
65586
- css /* scss */ `
65587
- .o-dv-list-icon {
65588
- color: ${TEXT_BODY_MUTED};
65589
- border-radius: 1px;
65590
- height: ${GRID_ICON_EDGE_LENGTH}px;
65591
- width: ${GRID_ICON_EDGE_LENGTH}px;
65592
-
65593
- &:hover {
65594
- color: #ffffff;
65595
- background-color: ${TEXT_BODY_MUTED};
65596
- }
65597
-
65598
- svg {
65599
- width: ${ICON_WIDTH}px;
65600
- height: ${ICON_WIDTH}px;
65601
- }
65602
- }
65603
- `;
65604
- class DataValidationListIcon extends owl.Component {
65605
- static template = "o-spreadsheet-DataValidationListIcon";
65606
- static props = {
65607
- cellPosition: Object,
65608
- };
65609
- onClick() {
65610
- const { col, row } = this.props.cellPosition;
65611
- this.env.model.selection.selectCell(col, row);
65612
- this.env.startCellEdition();
65613
- }
65614
- }
65615
-
65616
- css /* scss */ `
65617
- .o-filter-icon {
65618
- color: ${FILTERS_COLOR};
65619
- display: flex;
65620
- align-items: center;
65621
- justify-content: center;
65622
- width: ${GRID_ICON_EDGE_LENGTH}px;
65623
- height: ${GRID_ICON_EDGE_LENGTH}px;
65624
-
65625
- &:hover {
65626
- background: ${FILTERS_COLOR};
65627
- color: #fff;
65628
- }
65629
-
65630
- &.o-high-contrast {
65631
- color: #defade;
65632
- }
65633
- &.o-high-contrast:hover {
65634
- color: ${FILTERS_COLOR};
65635
- background: #fff;
65636
- }
65637
- }
65638
- .o-filter-icon:hover {
65639
- background: ${FILTERS_COLOR};
65640
- color: #fff;
65641
- }
65642
- `;
65643
- class FilterIcon extends owl.Component {
65644
- static template = "o-spreadsheet-FilterIcon";
65645
- static props = {
65646
- cellPosition: Object,
65647
- };
65648
- cellPopovers;
65649
- setup() {
65650
- this.cellPopovers = useStore(CellPopoverStore);
65651
- }
65652
- onClick() {
65653
- const position = this.props.cellPosition;
65654
- const activePopover = this.cellPopovers.persistentCellPopover;
65655
- const { col, row } = position;
65656
- if (activePopover.isOpen &&
65657
- activePopover.col === col &&
65658
- activePopover.row === row &&
65659
- activePopover.type === "FilterMenu") {
65660
- this.cellPopovers.close();
65661
- return;
65662
- }
65663
- this.cellPopovers.open({ col, row }, "FilterMenu");
65664
- }
65665
- get isFilterActive() {
65666
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
65667
- }
65668
- get iconClass() {
65669
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
65670
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
65671
- return luminance < 0.45 ? "o-high-contrast" : "";
65672
- }
65673
- }
65674
-
65675
- css /* scss */ `
65676
- .o-spreadsheet {
65677
- .o-pivot-collapse-icon {
65678
- cursor: pointer;
65679
- width: 11px;
65680
- height: 11px;
65681
- border: 1px solid #777;
65682
- background-color: #eee;
65683
- margin: 3px 0 3px 6px;
65684
-
65685
- .o-icon {
65686
- width: 5px;
65687
- height: 5px;
65688
- }
65689
- }
65690
- }
65691
- `;
65692
- class PivotCollapseIcon extends owl.Component {
65693
- static template = "o-spreadsheet-PivotCollapseIcon";
65694
- static props = {
65695
- cellPosition: Object,
65696
- };
65697
- onClick() {
65698
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65699
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65700
- if (!pivotId || pivotCell.type !== "HEADER") {
65701
- return;
65702
- }
65703
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65704
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
65705
- ? [...definition.collapsedDomains[pivotCell.dimension]]
65706
- : [];
65707
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
65708
- if (index !== -1) {
65709
- collapsedDomains.splice(index, 1);
65710
- }
65711
- else {
65712
- collapsedDomains.push(pivotCell.domain);
65713
- }
65714
- const newDomains = definition.collapsedDomains
65715
- ? { ...definition.collapsedDomains }
65716
- : { COL: [], ROW: [] };
65717
- newDomains[pivotCell.dimension] = collapsedDomains;
65718
- this.env.model.dispatch("UPDATE_PIVOT", {
65719
- pivotId,
65720
- pivot: { ...definition, collapsedDomains: newDomains },
65721
- });
65722
- }
65723
- get isCollapsed() {
65724
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65725
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65726
- if (!pivotId || pivotCell.type !== "HEADER") {
65727
- return false;
65728
- }
65729
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65730
- const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
65731
- return domains?.some((domain) => deepEquals(domain, pivotCell.domain));
65732
- }
65733
- }
65734
-
65735
65957
  /**
65736
65958
  * Registry to draw icons on cells
65737
65959
  */
@@ -65739,14 +65961,25 @@ stores.inject(MyMetaStore, storeInstance);
65739
65961
  iconsOnCellRegistry.add("data_validation_checkbox", (getters, position) => {
65740
65962
  const hasIcon = getters.isCellValidCheckbox(position);
65741
65963
  if (hasIcon) {
65964
+ const value = !!getters.getEvaluatedCell(position).value;
65742
65965
  return {
65743
- svg: undefined,
65966
+ svg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED,
65967
+ hoverSvg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED_HOVERED,
65744
65968
  priority: 2,
65745
65969
  horizontalAlign: "center",
65746
65970
  size: GRID_ICON_EDGE_LENGTH,
65747
65971
  margin: GRID_ICON_MARGIN,
65748
- component: DataValidationCheckbox,
65749
65972
  position,
65973
+ type: "data_validation_checkbox",
65974
+ onClick: (position, env) => {
65975
+ const cell = env.model.getters.getCell(position);
65976
+ const isDisabled = env.model.getters.isReadonly() || !!cell?.isFormula;
65977
+ if (isDisabled) {
65978
+ return;
65979
+ }
65980
+ const cellContent = value ? "FALSE" : "TRUE";
65981
+ env.model.dispatch("UPDATE_CELL", { ...position, content: cellContent });
65982
+ },
65750
65983
  };
65751
65984
  }
65752
65985
  return undefined;
@@ -65755,13 +65988,19 @@ stores.inject(MyMetaStore, storeInstance);
65755
65988
  const hasIcon = !getters.isReadonly() && getters.cellHasListDataValidationIcon(position);
65756
65989
  if (hasIcon) {
65757
65990
  return {
65758
- svg: undefined,
65991
+ svg: CARET_DOWN,
65992
+ hoverSvg: HOVERED_CARET_DOWN,
65759
65993
  priority: 2,
65760
65994
  horizontalAlign: "right",
65761
65995
  size: GRID_ICON_EDGE_LENGTH,
65762
65996
  margin: GRID_ICON_MARGIN,
65763
- component: DataValidationListIcon,
65764
65997
  position,
65998
+ onClick: (position, env) => {
65999
+ const { col, row } = position;
66000
+ env.model.selection.selectCell(col, row);
66001
+ env.startCellEdition();
66002
+ },
66003
+ type: "data_validation_list_icon",
65765
66004
  };
65766
66005
  }
65767
66006
  return undefined;
@@ -65769,14 +66008,30 @@ stores.inject(MyMetaStore, storeInstance);
65769
66008
  iconsOnCellRegistry.add("filter_icon", (getters, position) => {
65770
66009
  const hasIcon = getters.isFilterHeader(position);
65771
66010
  if (hasIcon) {
66011
+ const isFilterActive = getters.isFilterActive(position);
66012
+ const cellStyle = getters.getCellComputedStyle(position);
66013
+ const isHighContrast = relativeLuminance(cellStyle.fillColor || "#fff") < 0.45;
65772
66014
  return {
65773
- svg: undefined,
66015
+ type: "filter_icon",
66016
+ svg: getDataFilterIcon(isFilterActive, isHighContrast, false),
66017
+ hoverSvg: getDataFilterIcon(isFilterActive, isHighContrast, true),
65774
66018
  priority: 3,
65775
66019
  horizontalAlign: "right",
65776
66020
  size: GRID_ICON_EDGE_LENGTH,
65777
66021
  margin: GRID_ICON_MARGIN,
65778
- component: FilterIcon,
65779
66022
  position,
66023
+ onClick: (position, env) => {
66024
+ const cellPopovers = env.getStore(CellPopoverStore);
66025
+ const activePopover = cellPopovers.persistentCellPopover;
66026
+ if (activePopover.isOpen &&
66027
+ activePopover.col === position.col &&
66028
+ activePopover.row === position.row &&
66029
+ activePopover.type === "FilterMenu") {
66030
+ cellPopovers.close();
66031
+ return;
66032
+ }
66033
+ cellPopovers.open(position, "FilterMenu");
66034
+ },
65780
66035
  };
65781
66036
  }
65782
66037
  return undefined;
@@ -65786,6 +66041,7 @@ stores.inject(MyMetaStore, storeInstance);
65786
66041
  if (icon) {
65787
66042
  const style = getters.getCellStyle(position);
65788
66043
  return {
66044
+ type: "conditional_formatting",
65789
66045
  svg: ICONS[icon].svg,
65790
66046
  priority: 1,
65791
66047
  horizontalAlign: "left",
@@ -65806,23 +66062,55 @@ stores.inject(MyMetaStore, storeInstance);
65806
66062
  const definition = getters.getPivotCoreDefinition(pivotId);
65807
66063
  const isDashboard = getters.isDashboard();
65808
66064
  const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
65809
- const component = !isDashboard && pivotCell.domain.length !== fields.length ? PivotCollapseIcon : undefined;
66065
+ const hasIcon = !isDashboard && pivotCell.domain.length !== fields.length;
66066
+ const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
66067
+ const isCollapsed = domains.some((domain) => deepEquals(domain, pivotCell.domain));
66068
+ const indent = pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0;
65810
66069
  return {
66070
+ type: "pivot_collapse",
65811
66071
  priority: 4,
65812
66072
  horizontalAlign: "left",
65813
- size: !!component || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
65814
- ? GRID_ICON_EDGE_LENGTH
66073
+ size: hasIcon || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
66074
+ ? PIVOT_COLLAPSE_ICON_SIZE
65815
66075
  : 0,
65816
- margin: pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0,
65817
- component,
66076
+ margin: hasIcon ? GRID_ICON_MARGIN * 2 + indent : indent,
66077
+ svg: hasIcon ? getPivotIconSvg(isCollapsed, false) : undefined,
66078
+ hoverSvg: hasIcon ? getPivotIconSvg(isCollapsed, true) : undefined,
65818
66079
  position,
66080
+ onClick: togglePivotCollapse,
65819
66081
  };
65820
66082
  }
65821
66083
  return undefined;
65822
66084
  });
66085
+ function togglePivotCollapse(position, env) {
66086
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
66087
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
66088
+ if (!pivotId || pivotCell.type !== "HEADER") {
66089
+ return;
66090
+ }
66091
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
66092
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
66093
+ ? [...definition.collapsedDomains[pivotCell.dimension]]
66094
+ : [];
66095
+ const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
66096
+ if (index !== -1) {
66097
+ collapsedDomains.splice(index, 1);
66098
+ }
66099
+ else {
66100
+ collapsedDomains.push(pivotCell.domain);
66101
+ }
66102
+ const newDomains = definition.collapsedDomains
66103
+ ? { ...definition.collapsedDomains }
66104
+ : { COL: [], ROW: [] };
66105
+ newDomains[pivotCell.dimension] = collapsedDomains;
66106
+ env.model.dispatch("UPDATE_PIVOT", {
66107
+ pivotId,
66108
+ pivot: { ...definition, collapsedDomains: newDomains },
66109
+ });
66110
+ }
65823
66111
 
65824
66112
  class CellIconPlugin extends CoreViewPlugin {
65825
- static getters = ["doesCellHaveGridIcon", "getCellIcons"];
66113
+ static getters = ["doesCellHaveGridIcon", "getCellIcons", "getCellIconRect"];
65826
66114
  cellIconsCache = {};
65827
66115
  handle(cmd) {
65828
66116
  if (cmd.type !== "SET_VIEWPORT_OFFSET") {
@@ -65842,6 +66130,29 @@ stores.inject(MyMetaStore, storeInstance);
65842
66130
  }
65843
66131
  return this.cellIconsCache[position.sheetId][position.col][position.row];
65844
66132
  }
66133
+ getCellIconRect(icon) {
66134
+ const cellPosition = icon.position;
66135
+ const merge = this.getters.getMerge(cellPosition);
66136
+ const zone = merge || positionToZone(cellPosition);
66137
+ const cellRect = this.getters.getRect(zone);
66138
+ const cell = this.getters.getCell(cellPosition);
66139
+ const x = this.getIconHorizontalPosition(cellRect, icon.horizontalAlign, icon);
66140
+ const y = this.getters.computeTextYCoordinate(cellRect, icon.size, cell?.style?.verticalAlign);
66141
+ return { x: x, y: y, width: icon.size, height: icon.size };
66142
+ }
66143
+ getIconHorizontalPosition(rect, align, icon) {
66144
+ const start = rect.x;
66145
+ const end = rect.x + rect.width;
66146
+ switch (align) {
66147
+ case "right":
66148
+ return end - icon.margin - icon.size;
66149
+ case "left":
66150
+ return start + icon.margin;
66151
+ default:
66152
+ const centeringOffset = Math.floor((end - start - icon.size) / 2);
66153
+ return end - icon.size - centeringOffset;
66154
+ }
66155
+ }
65845
66156
  computeCellIcons(position) {
65846
66157
  const icons = { left: undefined, right: undefined, center: undefined };
65847
66158
  const callbacks = iconsOnCellRegistry.getAll();
@@ -70068,6 +70379,7 @@ stores.inject(MyMetaStore, storeInstance);
70068
70379
  "getCellText",
70069
70380
  "getCellMultiLineText",
70070
70381
  "getContiguousZone",
70382
+ "computeTextYCoordinate",
70071
70383
  ];
70072
70384
  ctx = document.createElement("canvas").getContext("2d");
70073
70385
  // ---------------------------------------------------------------------------
@@ -70170,6 +70482,25 @@ stores.inject(MyMetaStore, storeInstance);
70170
70482
  });
70171
70483
  return splitTextToWidth(this.ctx, text, style, args.wrapText ? args.maxWidth : undefined);
70172
70484
  }
70485
+ /** Computes the vertical start point from which a text line should be draw in a cell.
70486
+ *
70487
+ * Note that in case the cell does not have enough spaces to display its text lines,
70488
+ * (wrapping cell case) then the vertical align should be at the top.
70489
+ * */
70490
+ computeTextYCoordinate(cellRect, textLineHeight, verticalAlign = DEFAULT_VERTICAL_ALIGN, numberOfLines = 1) {
70491
+ const y = cellRect.y + 1; // +1 to skip the cell grid line at the top
70492
+ const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
70493
+ const hasEnoughSpaces = cellRect.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
70494
+ if (hasEnoughSpaces) {
70495
+ if (verticalAlign === "middle") {
70496
+ return Math.ceil(y + (cellRect.height - textHeight) / 2);
70497
+ }
70498
+ if (verticalAlign === "bottom") {
70499
+ return y + cellRect.height - textHeight - MIN_CELL_TEXT_MARGIN;
70500
+ }
70501
+ }
70502
+ return y + MIN_CELL_TEXT_MARGIN;
70503
+ }
70173
70504
  /**
70174
70505
  * Expands the given zone until bordered by empty cells or reached the sheet boundaries.
70175
70506
  */
@@ -72195,6 +72526,7 @@ stores.inject(MyMetaStore, storeInstance);
72195
72526
  "getElementsFromSelection",
72196
72527
  "tryGetActiveSheetId",
72197
72528
  "isGridSelectionActive",
72529
+ "getSelectecUnboundedZone",
72198
72530
  ];
72199
72531
  gridSelection = {
72200
72532
  anchor: {
@@ -72206,12 +72538,14 @@ stores.inject(MyMetaStore, storeInstance);
72206
72538
  selectedFigureId = null;
72207
72539
  sheetsData = {};
72208
72540
  moveClient;
72541
+ isUnbounded;
72209
72542
  // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
72210
72543
  // the main command.
72211
72544
  activeSheet = null;
72212
72545
  constructor(config) {
72213
72546
  super(config);
72214
72547
  this.moveClient = config.moveClient;
72548
+ this.isUnbounded = false;
72215
72549
  }
72216
72550
  // ---------------------------------------------------------------------------
72217
72551
  // Command Handling
@@ -72237,6 +72571,7 @@ stores.inject(MyMetaStore, storeInstance);
72237
72571
  handleEvent(event) {
72238
72572
  const anchor = event.anchor;
72239
72573
  let zones = [];
72574
+ this.isUnbounded = event.options?.unbounded || false;
72240
72575
  switch (event.mode) {
72241
72576
  case "overrideSelection":
72242
72577
  zones = [anchor.zone];
@@ -72426,6 +72761,12 @@ stores.inject(MyMetaStore, storeInstance);
72426
72761
  getSelectedZone() {
72427
72762
  return deepCopy(this.gridSelection.anchor.zone);
72428
72763
  }
72764
+ getSelectecUnboundedZone() {
72765
+ const zone = this.isUnbounded
72766
+ ? this.getters.getUnboundedZone(this.activeSheet.id, this.gridSelection.anchor.zone)
72767
+ : this.gridSelection.anchor.zone;
72768
+ return deepCopy(zone);
72769
+ }
72429
72770
  getSelection() {
72430
72771
  return deepCopy(this.gridSelection);
72431
72772
  }
@@ -72619,11 +72960,6 @@ stores.inject(MyMetaStore, storeInstance);
72619
72960
  },
72620
72961
  ];
72621
72962
  const sheetId = this.getActiveSheetId();
72622
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
72623
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
72624
- if (!data) {
72625
- return;
72626
- }
72627
72963
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
72628
72964
  const pasteTarget = [
72629
72965
  {
@@ -72633,7 +72969,14 @@ stores.inject(MyMetaStore, storeInstance);
72633
72969
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
72634
72970
  },
72635
72971
  ];
72636
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
72972
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
72973
+ const handler = new Handler(this.getters, this.dispatch);
72974
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
72975
+ if (!data) {
72976
+ continue;
72977
+ }
72978
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
72979
+ }
72637
72980
  const selection = pasteTarget[0];
72638
72981
  const col = selection.left;
72639
72982
  const row = selection.top;
@@ -73179,6 +73522,7 @@ stores.inject(MyMetaStore, storeInstance);
73179
73522
  "getRect",
73180
73523
  "getFigureUI",
73181
73524
  "getPositionAnchorOffset",
73525
+ "getGridOffset",
73182
73526
  ];
73183
73527
  viewports = {};
73184
73528
  /**
@@ -73368,6 +73712,9 @@ stores.inject(MyMetaStore, storeInstance);
73368
73712
  height: this.sheetViewHeight,
73369
73713
  };
73370
73714
  }
73715
+ getGridOffset() {
73716
+ return { x: this.gridOffsetX, y: this.gridOffsetY };
73717
+ }
73371
73718
  /** type as pane, not viewport but basically pane extends viewport */
73372
73719
  getActiveMainViewport() {
73373
73720
  const sheetId = this.getters.getActiveSheetId();
@@ -74746,6 +75093,7 @@ stores.inject(MyMetaStore, storeInstance);
74746
75093
  name: _t("Settings"),
74747
75094
  sequence: 100,
74748
75095
  execute: (env) => env.openSidePanel("Settings"),
75096
+ isEnabled: (env) => !env.isSmall,
74749
75097
  icon: "o-spreadsheet-Icon.COG",
74750
75098
  })
74751
75099
  // ---------------------------------------------------------------------
@@ -75163,6 +75511,7 @@ stores.inject(MyMetaStore, storeInstance);
75163
75511
  execute: (env) => {
75164
75512
  env.openSidePanel("DataValidation");
75165
75513
  },
75514
+ isEnabled: (env) => !env.isSmall,
75166
75515
  icon: "o-spreadsheet-Icon.DATA_VALIDATION",
75167
75516
  sequence: 30,
75168
75517
  separator: true,
@@ -75186,6 +75535,7 @@ stores.inject(MyMetaStore, storeInstance);
75186
75535
  sequence: sequence + index,
75187
75536
  isReadonlyAllowed: true,
75188
75537
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
75538
+ isEnabled: (env) => !env.isSmall,
75189
75539
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
75190
75540
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
75191
75541
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -75457,7 +75807,7 @@ stores.inject(MyMetaStore, storeInstance);
75457
75807
  .o-sheet {
75458
75808
  padding: 0 15px;
75459
75809
  padding-right: 10px;
75460
- height: ${BOTTOMBAR_HEIGHT}px;
75810
+ height: ${DESKTOP_BOTTOMBAR_HEIGHT}px;
75461
75811
  border-left: 1px solid #c1c1c1;
75462
75812
  border-right: 1px solid #c1c1c1;
75463
75813
  margin-left: -1px;
@@ -75501,6 +75851,10 @@ stores.inject(MyMetaStore, storeInstance);
75501
75851
  width: calc(100% - 1px);
75502
75852
  }
75503
75853
  }
75854
+
75855
+ .o-spreadshet-mobile .o-sheet {
75856
+ height: ${MOBILE_BOTTOMBAR_HEIGHT}px;
75857
+ }
75504
75858
  `;
75505
75859
  class BottomBarSheet extends owl.Component {
75506
75860
  static template = "o-spreadsheet-BottomBarSheet";
@@ -75555,7 +75909,16 @@ stores.inject(MyMetaStore, storeInstance);
75555
75909
  this.stopEdition();
75556
75910
  }
75557
75911
  }
75912
+ onClick() {
75913
+ if (!this.env.isMobile()) {
75914
+ return;
75915
+ }
75916
+ this.activateSheet();
75917
+ }
75558
75918
  onMouseDown(ev) {
75919
+ if (this.env.isMobile()) {
75920
+ return;
75921
+ }
75559
75922
  this.activateSheet();
75560
75923
  this.props.onMouseDown(ev);
75561
75924
  }
@@ -75899,8 +76262,8 @@ stores.inject(MyMetaStore, storeInstance);
75899
76262
  }
75900
76263
  }
75901
76264
 
75902
- .mobile.o-spreadsheet-bottom-bar {
75903
- padding-left: 1rem;
76265
+ .o-spreadsheet-mobile .o-spreadsheet-bottom-bar {
76266
+ padding-left: 0;
75904
76267
 
75905
76268
  .add-sheet-container {
75906
76269
  order: 2;
@@ -75917,10 +76280,8 @@ stores.inject(MyMetaStore, storeInstance);
75917
76280
  `;
75918
76281
  class BottomBar extends owl.Component {
75919
76282
  static template = "o-spreadsheet-BottomBar";
75920
- static props = {
75921
- onClick: Function,
75922
- };
75923
- static components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
76283
+ static props = { onClick: Function };
76284
+ static components = { MenuPopover, Ripple, BottomBarSheet, BottomBarStatistic };
75924
76285
  bottomBarRef = owl.useRef("bottomBar");
75925
76286
  sheetListRef = owl.useRef("sheetList");
75926
76287
  dragAndDrop = useDragAndDropListItems();
@@ -76057,6 +76418,9 @@ stores.inject(MyMetaStore, storeInstance);
76057
76418
  if (event.button !== 0 || this.env.model.getters.isReadonly())
76058
76419
  return;
76059
76420
  this.closeMenu();
76421
+ if (this.env.isMobile()) {
76422
+ return;
76423
+ }
76060
76424
  const visibleSheets = this.getVisibleSheets();
76061
76425
  const sheetRects = this.getSheetItemRects();
76062
76426
  const sheets = visibleSheets.map((sheet, index) => ({
@@ -76176,7 +76540,7 @@ stores.inject(MyMetaStore, storeInstance);
76176
76540
  `;
76177
76541
  class SpreadsheetDashboard extends owl.Component {
76178
76542
  static template = "o-spreadsheet-SpreadsheetDashboard";
76179
- static props = {};
76543
+ static props = { getGridSize: Function };
76180
76544
  static components = {
76181
76545
  GridOverlay,
76182
76546
  GridPopover,
@@ -76463,7 +76827,7 @@ stores.inject(MyMetaStore, storeInstance);
76463
76827
  dimension: String,
76464
76828
  layers: Array,
76465
76829
  };
76466
- static components = { RowGroup, ColGroup, Menu };
76830
+ static components = { RowGroup, ColGroup, MenuPopover };
76467
76831
  menu = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
76468
76832
  getLayerOffset(layerIndex) {
76469
76833
  return layerIndex * GROUP_LAYER_WIDTH;
@@ -76679,6 +77043,158 @@ stores.inject(MyMetaStore, storeInstance);
76679
77043
  }
76680
77044
  }
76681
77045
 
77046
+ class RibbonMenu extends owl.Component {
77047
+ static template = "o-spreadsheet-RibbonMenu";
77048
+ static props = {
77049
+ onClose: Function,
77050
+ };
77051
+ static components = { Menu };
77052
+ rootItems = topbarMenuRegistry.getMenuItems();
77053
+ menuRef = owl.useRef("menu");
77054
+ state = owl.useState({
77055
+ menuItems: this.rootItems,
77056
+ title: _t("Menu Bar"),
77057
+ parentState: undefined,
77058
+ });
77059
+ setup() {
77060
+ owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
77061
+ }
77062
+ onExternalClick(ev) {
77063
+ if (!this.menuRef.el?.contains(ev.target)) {
77064
+ this.props.onClose();
77065
+ }
77066
+ }
77067
+ onClickMenu(menu) {
77068
+ const children = menu.children(this.env);
77069
+ if (children.length) {
77070
+ this.state.parentState = { ...this.state };
77071
+ this.state.menuItems = children;
77072
+ this.state.title = menu.name(this.env);
77073
+ }
77074
+ else {
77075
+ this.state.menuItems = this.rootItems;
77076
+ this.state.title = undefined;
77077
+ this.state.parentState = undefined;
77078
+ menu.execute?.(this.env);
77079
+ this.props.onClose();
77080
+ }
77081
+ }
77082
+ get menuProps() {
77083
+ return {
77084
+ menuItems: this.state.menuItems,
77085
+ onClose: this.props.onClose,
77086
+ onClickMenu: this.onClickMenu.bind(this),
77087
+ };
77088
+ }
77089
+ get style() {
77090
+ return cssPropertiesToCss({
77091
+ height: `${this.props.height}px`,
77092
+ });
77093
+ }
77094
+ onClickBack() {
77095
+ if (!this.state.parentState) {
77096
+ this.props.onClose();
77097
+ return;
77098
+ }
77099
+ this.state.menuItems = this.state.parentState.menuItems;
77100
+ this.state.title = this.state.parentState.title;
77101
+ this.state.parentState = this.state.parentState.parentState;
77102
+ }
77103
+ get backTitle() {
77104
+ return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
77105
+ }
77106
+ }
77107
+
77108
+ css `
77109
+ .o-small-composer {
77110
+ z-index: ${ComponentsImportance.TopBarComposer};
77111
+ }
77112
+ `;
77113
+ class SmallBottomBar extends owl.Component {
77114
+ static components = { Composer, BottomBar, Ripple, RibbonMenu };
77115
+ static template = "o-spreadsheet-SmallBottomBar";
77116
+ static props = {
77117
+ onClick: Function,
77118
+ };
77119
+ composerFocusStore;
77120
+ composerStore;
77121
+ composerInterface;
77122
+ composerRef = owl.useRef("bottombarComposer");
77123
+ menuState = owl.useState({
77124
+ isOpen: false,
77125
+ });
77126
+ setup() {
77127
+ this.composerFocusStore = useStore(ComposerFocusStore);
77128
+ const composerStore = useStore(CellComposerStore);
77129
+ this.composerStore = composerStore;
77130
+ this.composerInterface = {
77131
+ id: "bottombarComposer",
77132
+ get editionMode() {
77133
+ return composerStore.editionMode;
77134
+ },
77135
+ startEdition: this.composerStore.startEdition,
77136
+ setCurrentContent: this.composerStore.setCurrentContent,
77137
+ stopEdition: this.composerStore.stopEdition,
77138
+ };
77139
+ owl.useEffect(() => {
77140
+ if (
77141
+ // we hide the grid composer on mobile so we need to autofocus this composer
77142
+ this.env.isMobile() &&
77143
+ !this.menuState.isOpen &&
77144
+ this.composerStore.editionMode !== "inactive" &&
77145
+ this.composerFocusStore.activeComposer !== this.composerInterface) {
77146
+ this.composerFocusStore.focusComposer(this.composerInterface, {
77147
+ focusMode: "contentFocus",
77148
+ });
77149
+ }
77150
+ });
77151
+ }
77152
+ get focus() {
77153
+ return this.composerFocusStore.activeComposer === this.composerInterface
77154
+ ? this.composerFocusStore.focusMode
77155
+ : "inactive";
77156
+ }
77157
+ get rect() {
77158
+ return this.composerRef.el
77159
+ ? getBoundingRectAsPOJO(this.composerRef.el)
77160
+ : { x: 0, y: 0, width: 0, height: 0 };
77161
+ }
77162
+ get composerProps() {
77163
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
77164
+ return {
77165
+ rect: { ...this.rect },
77166
+ delimitation: {
77167
+ width,
77168
+ height,
77169
+ },
77170
+ focus: this.focus,
77171
+ composerStore: this.composerStore,
77172
+ onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
77173
+ focusMode: "contentFocus",
77174
+ }),
77175
+ isDefaultFocus: false,
77176
+ inputStyle: cssPropertiesToCss({
77177
+ height: this.focus === "inactive" ? "26px" : "fit-content",
77178
+ "max-height": `130px`,
77179
+ }),
77180
+ showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
77181
+ };
77182
+ }
77183
+ get symbols() {
77184
+ return ["=", "(", ")", ":", "-", "/", "*", ",", "+", "$", "."];
77185
+ }
77186
+ insertSymbol(symbol) {
77187
+ this.composerStore.replaceComposerCursorSelection(symbol);
77188
+ this.composerFocusStore.focusComposer(this.composerInterface, {
77189
+ focusMode: "contentFocus",
77190
+ });
77191
+ }
77192
+ toggleRibbon() {
77193
+ this.composerStore.cancelEdition();
77194
+ this.menuState.isOpen = !this.menuState.isOpen;
77195
+ }
77196
+ }
77197
+
76682
77198
  const COMPOSER_MAX_HEIGHT = 100;
76683
77199
  /* svg free of use from https://uxwing.com/formula-fx-icon/ */
76684
77200
  const FX_SVG = /*xml*/ `
@@ -76688,7 +77204,7 @@ stores.inject(MyMetaStore, storeInstance);
76688
77204
  `;
76689
77205
  css /* scss */ `
76690
77206
  .o-topbar-composer-container {
76691
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
77207
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
76692
77208
  }
76693
77209
 
76694
77210
  .o-topbar-composer {
@@ -76743,7 +77259,7 @@ stores.inject(MyMetaStore, storeInstance);
76743
77259
  "max-height": `${COMPOSER_MAX_HEIGHT}px`,
76744
77260
  "line-height": "24px",
76745
77261
  };
76746
- style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
77262
+ style.height = this.focus === "inactive" ? `${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
76747
77263
  return cssPropertiesToCss(style);
76748
77264
  }
76749
77265
  get containerStyle() {
@@ -77246,7 +77762,7 @@ stores.inject(MyMetaStore, storeInstance);
77246
77762
 
77247
77763
  class NumberFormatsTool extends owl.Component {
77248
77764
  static template = "o-spreadsheet-NumberFormatsTool";
77249
- static components = { Menu, ActionButton };
77765
+ static components = { MenuPopover, ActionButton };
77250
77766
  static props = { class: String };
77251
77767
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
77252
77768
  topBarToolStore;
@@ -77296,7 +77812,7 @@ stores.inject(MyMetaStore, storeInstance);
77296
77812
  .addChild("edit", {
77297
77813
  component: PaintFormatButton,
77298
77814
  props: {
77299
- class: "o-hoverable-button o-toolbar-button",
77815
+ class: "o-hoverable-button o-toolbar-button o-mobile-disabled",
77300
77816
  },
77301
77817
  sequence: 3,
77302
77818
  })
@@ -77455,7 +77971,7 @@ stores.inject(MyMetaStore, storeInstance);
77455
77971
  .add("misc")
77456
77972
  .addChild("misc", {
77457
77973
  component: TableDropdownButton,
77458
- props: { class: "o-toolbar-button o-hoverable-button o-menu-item-button" },
77974
+ props: { class: "o-toolbar-button o-hoverable-button o-menu-item-button o-mobile-disabled" },
77459
77975
  sequence: 1,
77460
77976
  })
77461
77977
  .addChild("misc", {
@@ -77475,6 +77991,7 @@ stores.inject(MyMetaStore, storeInstance);
77475
77991
  border-right: 1px solid ${SEPARATOR_COLOR};
77476
77992
  width: 0;
77477
77993
  margin: 0 6px;
77994
+ height: 30px;
77478
77995
  }
77479
77996
 
77480
77997
  .o-toolbar-button {
@@ -77507,7 +78024,7 @@ stores.inject(MyMetaStore, storeInstance);
77507
78024
 
77508
78025
  .irregularity-map {
77509
78026
  border-top: 1px solid ${SEPARATOR_COLOR};
77510
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78027
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77511
78028
 
77512
78029
  .alert-info {
77513
78030
  border-left: 3px solid ${ALERT_INFO_BORDER};
@@ -77520,7 +78037,7 @@ stores.inject(MyMetaStore, storeInstance);
77520
78037
 
77521
78038
  /* Toolbar */
77522
78039
  .o-topbar-toolbar {
77523
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78040
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77524
78041
 
77525
78042
  .o-readonly-toolbar {
77526
78043
  background-color: ${BACKGROUND_HEADER_COLOR};
@@ -77534,6 +78051,24 @@ stores.inject(MyMetaStore, storeInstance);
77534
78051
  }
77535
78052
  }
77536
78053
  }
78054
+
78055
+ .o-spreadsheet-mobile {
78056
+ .o-topbar-toolbar {
78057
+ height: ${MOBILE_TOPBAR_TOOLBAR_HEIGHT}px;
78058
+ }
78059
+ .o-topbar-divider {
78060
+ border-width: 2px;
78061
+ border-radius: 4px;
78062
+ }
78063
+
78064
+ .o-toolbar-button {
78065
+ height: 35px;
78066
+ width: 31px;
78067
+ .o-toolbar-button.o-mobile-disabled * {
78068
+ color: ${DISABLED_TEXT_COLOR};
78069
+ cursor: not-allowed;
78070
+ }
78071
+ }
77537
78072
  `;
77538
78073
  class TopBar extends owl.Component {
77539
78074
  static template = "o-spreadsheet-TopBar";
@@ -77542,7 +78077,7 @@ stores.inject(MyMetaStore, storeInstance);
77542
78077
  dropdownMaxHeight: Number,
77543
78078
  };
77544
78079
  static components = {
77545
- Menu,
78080
+ MenuPopover,
77546
78081
  TopBarComposer,
77547
78082
  Popover,
77548
78083
  };
@@ -77622,7 +78157,7 @@ stores.inject(MyMetaStore, storeInstance);
77622
78157
  // TODO : manage click events better. We need this piece of code
77623
78158
  // otherwise the event opening the menu would close it on the same frame.
77624
78159
  // And we cannot stop the event propagation because it's used in an
77625
- // external listener of the Menu component to close the context menu when
78160
+ // external listener of the MenuPopover component to close the context menu when
77626
78161
  // clicking on the top bar
77627
78162
  if (this.openedEl === ev.target) {
77628
78163
  return;
@@ -78048,6 +78583,11 @@ stores.inject(MyMetaStore, storeInstance);
78048
78583
  color: ${TEXT_BODY};
78049
78584
  }
78050
78585
  }
78586
+
78587
+ .o-spreadsheet-topbar-wrapper,
78588
+ .o-spreadsheet-bottombar-wrapper {
78589
+ z-index: ${ComponentsImportance.ScrollBar + 1};
78590
+ }
78051
78591
  `;
78052
78592
  class Spreadsheet extends owl.Component {
78053
78593
  static template = "o-spreadsheet-Spreadsheet";
@@ -78061,6 +78601,7 @@ stores.inject(MyMetaStore, storeInstance);
78061
78601
  TopBar,
78062
78602
  Grid,
78063
78603
  BottomBar,
78604
+ SmallBottomBar,
78064
78605
  SidePanel,
78065
78606
  SpreadsheetDashboard,
78066
78607
  HeaderGroupContainer,
@@ -78084,12 +78625,25 @@ stores.inject(MyMetaStore, storeInstance);
78084
78625
  else {
78085
78626
  properties["grid-template-rows"] = `min-content auto min-content`;
78086
78627
  }
78087
- properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
78628
+ const columnWidth = this.sidePanel.isOpen ? `${this.sidePanel.panelSize}px` : "auto";
78629
+ properties["grid-template-columns"] = `auto ${columnWidth}`;
78088
78630
  return cssPropertiesToCss(properties);
78089
78631
  }
78090
78632
  setup() {
78633
+ if (!("isSmall" in this.env)) {
78634
+ const screenSize = useScreenWidth();
78635
+ owl.useSubEnv({
78636
+ get isSmall() {
78637
+ return screenSize.isSmall;
78638
+ },
78639
+ });
78640
+ }
78091
78641
  const stores = useStoreProvider();
78092
78642
  stores.inject(ModelStore, this.model);
78643
+ const env = this.env;
78644
+ stores.get(ScreenWidthStore).setSmallThreshhold(() => {
78645
+ return env.isSmall;
78646
+ });
78093
78647
  this.notificationStore = useStore(NotificationStore);
78094
78648
  this.composerFocusStore = useStore(ComposerFocusStore);
78095
78649
  this.sidePanel = useStore(SidePanelStore);
@@ -78107,15 +78661,8 @@ stores.inject(MyMetaStore, storeInstance);
78107
78661
  notifyUser: (notification) => this.notificationStore.notifyUser(notification),
78108
78662
  askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
78109
78663
  raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
78664
+ isMobile: isMobileOS,
78110
78665
  });
78111
- if (!("isSmall" in this.env)) {
78112
- const screenSize = useScreenWidth();
78113
- owl.useSubEnv({
78114
- get isSmall() {
78115
- return screenSize.isSmall;
78116
- },
78117
- });
78118
- }
78119
78666
  this.notificationStore.updateNotificationCallbacks({ ...this.props });
78120
78667
  owl.useEffect(() => {
78121
78668
  /**
@@ -78221,6 +78768,24 @@ stores.inject(MyMetaStore, storeInstance);
78221
78768
  const sheetId = this.env.model.getters.getActiveSheetId();
78222
78769
  return this.env.model.getters.getVisibleGroupLayers(sheetId, "COL");
78223
78770
  }
78771
+ getGridSize() {
78772
+ const topBarHeight = this.spreadsheetRef.el
78773
+ ?.querySelector(".o-spreadsheet-topbar-wrapper")
78774
+ ?.getBoundingClientRect().height || 0;
78775
+ const bottomBarHeight = this.spreadsheetRef.el
78776
+ ?.querySelector(".o-spreadsheet-bottombar-wrapper")
78777
+ ?.getBoundingClientRect().height || 0;
78778
+ const gridWidth = this.spreadsheetRef.el?.querySelector(".o-grid")?.getBoundingClientRect().width || 0;
78779
+ const gridHeight = (this.spreadsheetRef.el?.getBoundingClientRect().height || 0) -
78780
+ (this.spreadsheetRef.el?.querySelector(".o-column-groups")?.getBoundingClientRect().height ||
78781
+ 0) -
78782
+ topBarHeight -
78783
+ bottomBarHeight;
78784
+ return {
78785
+ width: Math.max(gridWidth - SCROLLBAR_WIDTH, 0),
78786
+ height: Math.max(gridHeight - SCROLLBAR_WIDTH, 0),
78787
+ };
78788
+ }
78224
78789
  }
78225
78790
 
78226
78791
  function inverseCommand(cmd) {
@@ -82483,7 +83048,7 @@ stores.inject(MyMetaStore, storeInstance);
82483
83048
  MIN_COL_WIDTH,
82484
83049
  HEADER_HEIGHT,
82485
83050
  HEADER_WIDTH,
82486
- BOTTOMBAR_HEIGHT,
83051
+ DESKTOP_BOTTOMBAR_HEIGHT,
82487
83052
  DEFAULT_CELL_WIDTH,
82488
83053
  DEFAULT_CELL_HEIGHT,
82489
83054
  SCROLLBAR_WIDTH,
@@ -82629,7 +83194,7 @@ stores.inject(MyMetaStore, storeInstance);
82629
83194
  FunnelChartDesignPanel,
82630
83195
  ChartTypePicker,
82631
83196
  FigureComponent,
82632
- Menu,
83197
+ MenuPopover,
82633
83198
  Popover,
82634
83199
  SelectionInput,
82635
83200
  ValidationMessages,
@@ -82740,9 +83305,9 @@ stores.inject(MyMetaStore, storeInstance);
82740
83305
  exports.tokenize = tokenize;
82741
83306
 
82742
83307
 
82743
- __info__.version = "18.4.0-alpha.6";
82744
- __info__.date = "2025-05-30T08:44:33.216Z";
82745
- __info__.hash = "eecf7e4";
83308
+ __info__.version = "18.4.0-alpha.7";
83309
+ __info__.date = "2025-06-06T09:32:44.285Z";
83310
+ __info__.hash = "2bfbe64";
82746
83311
 
82747
83312
 
82748
83313
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);