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