@odoo/o-spreadsheet 18.4.0-alpha.5 → 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.5
6
- * @date 2025-05-26T12:36:36.693Z
7
- * @hash 398e610
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",
@@ -8534,9 +8538,10 @@ const AGGREGATOR_NAMES = {
8534
8538
  avg: _t("Average"),
8535
8539
  sum: _t("Sum"),
8536
8540
  };
8541
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8537
8542
  const AGGREGATORS_BY_FIELD_TYPE = {
8538
- integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8539
- char: ["count_distinct", "count"],
8543
+ integer: NUMBER_CHAR_AGGREGATORS,
8544
+ char: NUMBER_CHAR_AGGREGATORS,
8540
8545
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8541
8546
  datetime: ["max", "min", "count_distinct", "count"],
8542
8547
  };
@@ -20557,7 +20562,7 @@ const TEXT = {
20557
20562
  description: _t("Converts a number to text according to a specified format."),
20558
20563
  args: [
20559
20564
  arg("number (number)", _t("The number, date or time to format.")),
20560
- arg("format (string)", _t("The pattern by which to format the number, enclosed in quotation marks.")),
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.')),
20561
20566
  ],
20562
20567
  compute: function (number, format) {
20563
20568
  const _number = toNumber(number, this.locale);
@@ -21664,8 +21669,6 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21664
21669
  if (isNaN(value)) {
21665
21670
  continue;
21666
21671
  }
21667
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
21668
- const displayValue = options.callback(Number(value), axisId);
21669
21672
  const point = dataset.data[i];
21670
21673
  const xPosition = point.x;
21671
21674
  let yPosition = 0;
@@ -21689,7 +21692,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21689
21692
  textsPositions[xPosition].push(yPosition);
21690
21693
  ctx.fillStyle = point.options.backgroundColor;
21691
21694
  ctx.strokeStyle = options.background || "#ffffff";
21692
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
21695
+ const valueToDisplay = options.callback(Number(value), dataset, i);
21696
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
21693
21697
  }
21694
21698
  }
21695
21699
  }
@@ -21706,7 +21710,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
21706
21710
  if (isNaN(value)) {
21707
21711
  continue;
21708
21712
  }
21709
- const displayValue = options.callback(value, dataset.xAxisID);
21713
+ const displayValue = options.callback(value, dataset, i);
21710
21714
  const point = dataset.data[i];
21711
21715
  const yPosition = point.y;
21712
21716
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -21744,7 +21748,7 @@ function drawPieChartValues(chart, options, ctx) {
21744
21748
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
21745
21749
  ctx.fillStyle = chartFontColor(options.background);
21746
21750
  ctx.strokeStyle = options.background || "#ffffff";
21747
- const displayValue = options.callback(value, "y");
21751
+ const displayValue = options.callback(value, dataset, i);
21748
21752
  drawTextWithBackground(displayValue, x, y, ctx);
21749
21753
  }
21750
21754
  }
@@ -23491,7 +23495,7 @@ function isMacOS() {
23491
23495
  * On Mac, this is the "meta" or "command" key.
23492
23496
  */
23493
23497
  function isCtrlKey(ev) {
23494
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
23498
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
23495
23499
  }
23496
23500
  /**
23497
23501
  * @param {MouseEvent} ev - The mouse event.
@@ -23530,6 +23534,23 @@ function downloadFile(dataUrl, fileName) {
23530
23534
  function isBrowserFirefox() {
23531
23535
  return /Firefox/i.test(navigator.userAgent);
23532
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
+ }
23533
23554
 
23534
23555
  /**
23535
23556
  * Convert a JS color hexadecimal to an excel compatible color.
@@ -25050,14 +25071,14 @@ function getPieChartLegend(definition, args) {
25050
25071
  ...getLegendDisplayOptions(definition),
25051
25072
  labels: {
25052
25073
  usePointStyle: true,
25053
- generateLabels: (c) => c.data.labels?.map((label, index) => ({
25074
+ generateLabels: (c) => (c.data.labels?.map((label, index) => ({
25054
25075
  text: truncateLabel(String(label)),
25055
25076
  strokeStyle: colors[index],
25056
25077
  fillStyle: colors[index],
25057
25078
  pointStyle: "rect",
25058
25079
  lineWidth: 2,
25059
25080
  fontColor,
25060
- })) || [],
25081
+ })) || []).filter((label) => label.text),
25061
25082
  filter: (legendItem, data) => {
25062
25083
  return "datasetIndex" in legendItem
25063
25084
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25217,7 +25238,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25217
25238
  labels: {
25218
25239
  color: fontColor,
25219
25240
  usePointStyle: true,
25220
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
25241
+ generateLabels: (chart) => chart.data.datasets
25242
+ .map((dataset, index) => {
25221
25243
  if (isTrendLineAxis(dataset["xAxisID"])) {
25222
25244
  return {
25223
25245
  text: truncateLabel(dataset.label),
@@ -25239,7 +25261,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25239
25261
  datasetIndex: index,
25240
25262
  ...legendLabelConfig,
25241
25263
  };
25242
- }),
25264
+ })
25265
+ .filter((label) => label.text),
25243
25266
  filter: (legendItem, data) => {
25244
25267
  return "datasetIndex" in legendItem
25245
25268
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25593,7 +25616,10 @@ function getChartShowValues(definition, args) {
25593
25616
  horizontal: "horizontal" in definition && definition.horizontal,
25594
25617
  showValues: "showValues" in definition ? !!definition.showValues : false,
25595
25618
  background: definition.background,
25596
- callback: formatChartDatasetValue(axisFormats, locale),
25619
+ callback: (value, dataset) => {
25620
+ const axisId = getDatasetAxisId(definition, dataset);
25621
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
25622
+ },
25597
25623
  };
25598
25624
  }
25599
25625
  function getSunburstShowValues(definition, args) {
@@ -25611,6 +25637,45 @@ function getSunburstShowValues(definition, args) {
25611
25637
  },
25612
25638
  };
25613
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
+ }
25614
25679
 
25615
25680
  function getChartTitle(definition) {
25616
25681
  const chartTitle = definition.title;
@@ -26013,6 +26078,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26013
26078
  getPieChartTooltip: getPieChartTooltip,
26014
26079
  getPyramidChartData: getPyramidChartData,
26015
26080
  getPyramidChartScales: getPyramidChartScales,
26081
+ getPyramidChartShowValues: getPyramidChartShowValues,
26016
26082
  getPyramidChartTooltip: getPyramidChartTooltip,
26017
26083
  getRadarChartData: getRadarChartData,
26018
26084
  getRadarChartDatasets: getRadarChartDatasets,
@@ -26033,6 +26099,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26033
26099
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
26034
26100
  getWaterfallChartLegend: getWaterfallChartLegend,
26035
26101
  getWaterfallChartScales: getWaterfallChartScales,
26102
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
26036
26103
  getWaterfallChartTooltip: getWaterfallChartTooltip,
26037
26104
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels,
26038
26105
  makeDatasetsCumulative: makeDatasetsCumulative
@@ -27345,7 +27412,7 @@ function createPyramidChartRuntime(chart, getters) {
27345
27412
  title: getChartTitle(definition),
27346
27413
  legend: getBarChartLegend(definition),
27347
27414
  tooltip: getPyramidChartTooltip(definition, chartData),
27348
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
27415
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
27349
27416
  },
27350
27417
  },
27351
27418
  };
@@ -28093,7 +28160,7 @@ function createWaterfallChartRuntime(chart, getters) {
28093
28160
  title: getChartTitle(definition),
28094
28161
  legend: getWaterfallChartLegend(definition),
28095
28162
  tooltip: getWaterfallChartTooltip(definition, chartData),
28096
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
28163
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
28097
28164
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
28098
28165
  },
28099
28166
  },
@@ -28885,6 +28952,7 @@ function getChartMenuActions(figureId, onFigureDeleted, env) {
28885
28952
  env.openSidePanel("ChartPanel");
28886
28953
  },
28887
28954
  icon: "o-spreadsheet-Icon.EDIT",
28955
+ isEnabled: (env) => !env.isSmall,
28888
28956
  },
28889
28957
  getCopyMenuItem(figureId, env),
28890
28958
  getCutMenuItem(figureId, env),
@@ -29099,6 +29167,147 @@ function useTimeOut() {
29099
29167
  };
29100
29168
  }
29101
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
+
29102
29311
  /**
29103
29312
  * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
29104
29313
  */
@@ -29127,6 +29336,9 @@ function zoneToRect(zone) {
29127
29336
  height: zone.bottom - zone.top,
29128
29337
  };
29129
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
+ }
29130
29342
 
29131
29343
  /**
29132
29344
  * Return the o-spreadsheet element position relative
@@ -29424,57 +29636,17 @@ class TopRightPopoverContext extends PopoverPositionContext {
29424
29636
  }
29425
29637
 
29426
29638
  //------------------------------------------------------------------------------
29427
- // Context Menu Component
29639
+ // Context MenuPopover Component
29428
29640
  //------------------------------------------------------------------------------
29429
29641
  css /* scss */ `
29430
- .o-menu {
29431
- background-color: white;
29642
+ .o-menu-wrapper {
29432
29643
  padding: ${MENU_VERTICAL_PADDING}px 0px;
29433
- width: ${MENU_WIDTH}px;
29434
- user-select: none;
29435
-
29436
- .o-menu-item {
29437
- height: ${MENU_ITEM_HEIGHT}px;
29438
- padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
29439
- cursor: pointer;
29440
- user-select: none;
29441
-
29442
- .o-menu-item-name {
29443
- min-width: 40%;
29444
- }
29445
-
29446
- .o-menu-item-icon {
29447
- display: inline-block;
29448
- margin: 0px 8px 0px 0px;
29449
- width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29450
- line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29451
- }
29452
-
29453
- &:not(.disabled) {
29454
- &:hover,
29455
- &.o-menu-item-active {
29456
- background-color: ${BUTTON_ACTIVE_BG};
29457
- color: ${BUTTON_ACTIVE_TEXT_COLOR};
29458
- }
29459
- .o-menu-item-description {
29460
- color: grey;
29461
- }
29462
- .o-menu-item-icon {
29463
- .o-icon {
29464
- color: ${ICONS_COLOR};
29465
- }
29466
- }
29467
- }
29468
- &.disabled {
29469
- color: ${DISABLED_TEXT_COLOR};
29470
- cursor: not-allowed;
29471
- }
29472
- }
29644
+ background-color: white;
29473
29645
  }
29474
29646
  `;
29475
29647
  const TIMEOUT_DELAY = 250;
29476
- class Menu extends owl.Component {
29477
- static template = "o-spreadsheet-Menu";
29648
+ class MenuPopover extends owl.Component {
29649
+ static template = "o-spreadsheet-Menu-Popover";
29478
29650
  static props = {
29479
29651
  anchorRect: Object,
29480
29652
  popoverPositioning: { type: String, optional: true },
@@ -29487,7 +29659,7 @@ class Menu extends owl.Component {
29487
29659
  onMouseOver: { type: Function, optional: true },
29488
29660
  width: { type: Number, optional: true },
29489
29661
  };
29490
- static components = { Menu, Popover };
29662
+ static components = { MenuPopover, Menu, Popover };
29491
29663
  static defaultProps = {
29492
29664
  depth: 1,
29493
29665
  popoverPositioning: "top-right",
@@ -29514,27 +29686,18 @@ class Menu extends owl.Component {
29514
29686
  this.hoveredMenu?.onStopHover?.(this.env);
29515
29687
  });
29516
29688
  }
29517
- get menuItemsAndSeparators() {
29518
- const menuItemsAndSeparators = [];
29519
- for (let i = 0; i < this.props.menuItems.length; i++) {
29520
- const menuItem = this.props.menuItems[i];
29521
- if (menuItem.isVisible(this.env)) {
29522
- menuItemsAndSeparators.push(menuItem);
29523
- }
29524
- if (menuItem.separator &&
29525
- i !== this.props.menuItems.length - 1 && // no separator at the end
29526
- menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
29527
- ) {
29528
- menuItemsAndSeparators.push("separator");
29529
- }
29530
- }
29531
- if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
29532
- menuItemsAndSeparators.pop();
29533
- }
29534
- if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
29535
- return [];
29536
- }
29537
- return menuItemsAndSeparators;
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
+ };
29538
29701
  }
29539
29702
  get subMenuAnchorRect() {
29540
29703
  const anchorRect = Object.assign({}, this.subMenu.anchorRect);
@@ -29548,12 +29711,13 @@ class Menu extends owl.Component {
29548
29711
  x: this.props.anchorRect.x,
29549
29712
  y: this.props.anchorRect.y,
29550
29713
  width: isRoot ? this.props.anchorRect.width : this.props.width || MENU_WIDTH,
29551
- height: isRoot ? this.props.anchorRect.height : MENU_ITEM_HEIGHT,
29714
+ height: isRoot ? this.props.anchorRect.height : DESKTOP_MENU_ITEM_HEIGHT,
29552
29715
  },
29553
29716
  positioning: this.props.popoverPositioning,
29554
29717
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
29555
29718
  onPopoverHidden: () => this.closeSubMenu(),
29556
29719
  onPopoverMoved: () => this.closeSubMenu(),
29720
+ maxHeight: this.props.maxHeight,
29557
29721
  };
29558
29722
  }
29559
29723
  get childrenHaveIcon() {
@@ -29623,7 +29787,7 @@ class Menu extends owl.Component {
29623
29787
  x: getRefBoundingRect(this.menuRef).x,
29624
29788
  y: y - (this.subMenu.scrollOffset || 0),
29625
29789
  width: this.props.width || MENU_WIDTH,
29626
- height: MENU_ITEM_HEIGHT,
29790
+ height: DESKTOP_MENU_ITEM_HEIGHT,
29627
29791
  };
29628
29792
  this.subMenu.menuItems = menu.children(this.env);
29629
29793
  this.subMenu.isOpen = true;
@@ -29672,14 +29836,8 @@ class Menu extends owl.Component {
29672
29836
  this.subMenu.isHoveringChild = true;
29673
29837
  this.openingTimeOut.clear();
29674
29838
  }
29675
- onMouseEnter(menu, ev) {
29676
- this.hoveredMenu = menu;
29677
- menu.onStartHover?.(this.env);
29678
- }
29679
29839
  onMouseLeave(menu) {
29680
29840
  this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
29681
- this.hoveredMenu = undefined;
29682
- menu.onStopHover?.(this.env);
29683
29841
  }
29684
29842
  get menuStyle() {
29685
29843
  return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
@@ -29688,7 +29846,7 @@ class Menu extends owl.Component {
29688
29846
 
29689
29847
  class ChartDashboardMenu extends owl.Component {
29690
29848
  static template = "spreadsheet.ChartDashboardMenu";
29691
- static components = { Menu };
29849
+ static components = { MenuPopover };
29692
29850
  static props = { figureUI: Object };
29693
29851
  originalChartDefinition;
29694
29852
  fullScreenFigureStore;
@@ -29952,7 +30110,7 @@ class FigureComponent extends owl.Component {
29952
30110
  onMouseDown: { type: Function, optional: true },
29953
30111
  onClickAnchor: { type: Function, optional: true },
29954
30112
  };
29955
- static components = { Menu };
30113
+ static components = { MenuPopover };
29956
30114
  static defaultProps = {
29957
30115
  onFigureDeleted: () => { },
29958
30116
  onMouseDown: () => { },
@@ -30039,7 +30197,14 @@ class FigureComponent extends owl.Component {
30039
30197
  this.props.onClickAnchor(dirX, dirY, ev);
30040
30198
  }
30041
30199
  onMouseDown(ev) {
30042
- 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
+ }
30043
30208
  }
30044
30209
  onKeyDown(ev) {
30045
30210
  const keyDownShortcut = keyboardEventToShortcutString(ev);
@@ -31506,15 +31671,15 @@ class HighlightStore extends SpreadsheetStore {
31506
31671
  const activeSheetId = this.getters.getActiveSheetId();
31507
31672
  return this.providers
31508
31673
  .flatMap((h) => h.highlights)
31509
- .filter((h) => h.sheetId === activeSheetId)
31674
+ .filter((h) => h.range.sheetId === activeSheetId)
31510
31675
  .map((highlight) => {
31511
- const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
31676
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.range.zone);
31512
31677
  const zone = numberOfRows * numberOfCols === 1
31513
- ? this.getters.expandZone(highlight.sheetId, highlight.zone)
31514
- : highlight.zone;
31678
+ ? this.getters.expandZone(highlight.range.sheetId, highlight.range.zone)
31679
+ : highlight.range.unboundedZone;
31515
31680
  return {
31516
31681
  ...highlight,
31517
- zone,
31682
+ range: this.model.getters.getRangeFromZone(highlight.range.sheetId, zone),
31518
31683
  };
31519
31684
  });
31520
31685
  }
@@ -31527,7 +31692,7 @@ class HighlightStore extends SpreadsheetStore {
31527
31692
  drawLayer(ctx, layer) {
31528
31693
  if (layer === "Highlights") {
31529
31694
  for (const highlight of this.highlights) {
31530
- const rect = this.getters.getVisibleRect(highlight.zone);
31695
+ const rect = this.getters.getVisibleRect(highlight.range.zone);
31531
31696
  drawHighlight(ctx, highlight, rect);
31532
31697
  }
31533
31698
  }
@@ -32155,12 +32320,12 @@ class AbstractComposerStore extends SpreadsheetStore {
32155
32320
  rangeColor(xc, sheetName) {
32156
32321
  const refSheet = sheetName ? this.model.getters.getSheetIdByName(sheetName) : this.sheetId;
32157
32322
  const highlight = this.highlights.find((highlight) => {
32158
- if (highlight.sheetId !== refSheet)
32323
+ if (highlight.range.sheetId !== refSheet)
32159
32324
  return false;
32160
32325
  const range = this.model.getters.getRangeFromSheetXC(refSheet, xc);
32161
32326
  let zone = range.zone;
32162
32327
  zone = getZoneArea(zone) === 1 ? this.model.getters.expandZone(refSheet, zone) : zone;
32163
- return isEqual(zone, highlight.zone);
32328
+ return isEqual(zone, highlight.range.zone);
32164
32329
  });
32165
32330
  return highlight && highlight.color ? highlight.color : undefined;
32166
32331
  }
@@ -32270,11 +32435,10 @@ class AbstractComposerStore extends SpreadsheetStore {
32270
32435
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
32271
32436
  const zone = numberOfRows * numberOfCols === 1
32272
32437
  ? this.getters.expandZone(range.sheetId, range.zone)
32273
- : range.zone;
32438
+ : range.unboundedZone;
32274
32439
  return {
32275
- zone,
32440
+ range: this.model.getters.getRangeFromZone(range.sheetId, zone),
32276
32441
  color: rangeColor(rangeString),
32277
- sheetId: range.sheetId,
32278
32442
  interactive: true,
32279
32443
  };
32280
32444
  });
@@ -32527,11 +32691,15 @@ class Composer extends owl.Component {
32527
32691
  onInputContextMenu: { type: Function, optional: true },
32528
32692
  composerStore: Object,
32529
32693
  placeholder: { type: String, optional: true },
32694
+ inputMode: { type: String, optional: true },
32695
+ showAssistant: { type: Boolean, optional: true },
32530
32696
  };
32531
32697
  static components = { TextValueProvider, FunctionDescriptionProvider, SpeechBubble };
32532
32698
  static defaultProps = {
32533
32699
  inputStyle: "",
32534
32700
  isDefaultFocus: false,
32701
+ inputMode: "text",
32702
+ showAssistant: true,
32535
32703
  };
32536
32704
  DOMFocusableElementStore;
32537
32705
  composerRef = owl.useRef("o_composer");
@@ -32582,8 +32750,7 @@ class Composer extends owl.Component {
32582
32750
  assistantStyle["max-height"] = `${availableSpaceAbove - CLOSE_ICON_RADIUS}px`;
32583
32751
  // render top
32584
32752
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
32585
- assistantStyle.top = `-3px`;
32586
- assistantStyle.transform = `translate(0, -100%)`;
32753
+ assistantStyle.transform = `translate(0, calc(-100% - ${cellHeight + 3}px))`;
32587
32754
  }
32588
32755
  if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
32589
32756
  // render left
@@ -32862,6 +33029,9 @@ class Composer extends owl.Component {
32862
33029
  // not main button, probably a context menu
32863
33030
  return;
32864
33031
  }
33032
+ if (this.env.isMobile() && !isIOS()) {
33033
+ return;
33034
+ }
32865
33035
  this.contentHelper.removeSelection();
32866
33036
  }
32867
33037
  onMouseup() {
@@ -33485,7 +33655,7 @@ class ListCriterionForm extends CriterionForm {
33485
33655
  */
33486
33656
  function startDnd(onPointerMove, onPointerUp) {
33487
33657
  const removeListeners = () => {
33488
- window.removeEventListener("pointerup", _onPointerUp);
33658
+ window.removeEventListener("pointerup", _onPointerUp, { capture: true });
33489
33659
  window.removeEventListener("dragstart", _onDragStart);
33490
33660
  window.removeEventListener("pointermove", onPointerMove);
33491
33661
  window.removeEventListener("wheel", onPointerMove);
@@ -33497,7 +33667,7 @@ function startDnd(onPointerMove, onPointerUp) {
33497
33667
  function _onDragStart(ev) {
33498
33668
  ev.preventDefault();
33499
33669
  }
33500
- window.addEventListener("pointerup", _onPointerUp);
33670
+ window.addEventListener("pointerup", _onPointerUp, { capture: true });
33501
33671
  window.addEventListener("dragstart", _onDragStart);
33502
33672
  window.addEventListener("pointermove", onPointerMove);
33503
33673
  // mouse wheel on window is by default a passive event.
@@ -34119,9 +34289,9 @@ class SelectionInputStore extends SpreadsheetStore {
34119
34289
  .filter((reference) => this.shouldBeHighlighted(this.inputSheetId, reference));
34120
34290
  return XCs.map((xc) => {
34121
34291
  const { sheetName } = splitReference(xc);
34292
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId;
34122
34293
  return {
34123
- zone: this.getters.getRangeFromSheetXC(this.inputSheetId, xc).zone,
34124
- sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId,
34294
+ range: this.getters.getRangeFromSheetXC(sheetId, xc),
34125
34295
  color,
34126
34296
  interactive: true,
34127
34297
  };
@@ -34590,7 +34760,7 @@ function getCriterionMenuItems(callback, availableTypes) {
34590
34760
  return createActions(actionSpecs);
34591
34761
  }
34592
34762
 
34593
- /** This component looks like a select input, but on click it opens a Menu with the items given as props instead of a dropdown */
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 */
34594
34764
  class SelectMenu extends owl.Component {
34595
34765
  static template = "o-spreadsheet-SelectMenu";
34596
34766
  static props = {
@@ -34598,7 +34768,7 @@ class SelectMenu extends owl.Component {
34598
34768
  selectedValue: String,
34599
34769
  class: { type: String, optional: true },
34600
34770
  };
34601
- static components = { Menu };
34771
+ static components = { MenuPopover };
34602
34772
  menuId = new UuidGenerator().uuidv4();
34603
34773
  selectRef = owl.useRef("select");
34604
34774
  state = owl.useState({
@@ -35346,7 +35516,7 @@ class MenuItemRegistry extends Registry {
35346
35516
  }
35347
35517
 
35348
35518
  //------------------------------------------------------------------------------
35349
- // Link Menu Registry
35519
+ // Link MenuPopover Registry
35350
35520
  //------------------------------------------------------------------------------
35351
35521
  const linkMenuRegistry = new MenuItemRegistry();
35352
35522
  linkMenuRegistry.add("sheet", {
@@ -35408,7 +35578,7 @@ class LinkEditor extends owl.Component {
35408
35578
  cellPosition: Object,
35409
35579
  onClosed: { type: Function, optional: true },
35410
35580
  };
35411
- static components = { Menu };
35581
+ static components = { MenuPopover };
35412
35582
  menuItems = linkMenuRegistry.getMenuItems();
35413
35583
  link = owl.useState(this.defaultState);
35414
35584
  menu = owl.useState({
@@ -35865,58 +36035,149 @@ css /* scss */ `
35865
36035
  const ARROW_DOWN = {
35866
36036
  width: 448,
35867
36037
  height: 512,
35868
- fillColor: "#E06666",
35869
- path: "M413.1 222.5l22.2 22.2c9.4 9.4 9.4 24.6 0 33.9L241 473c-9.4 9.4-24.6 9.4-33.9 0L12.7 278.6c-9.4-9.4-9.4-24.6 0-33.9l22.2-22.2c9.5-9.5 25-9.3 34.3.4L184 343.4V56c0-13.3 10.7-24 24-24h32c13.3 0 24 10.7 24 24v287.4l114.8-120.5c9.3-9.8 24.8-10 34.3-.4z",
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
+ ],
35870
36044
  };
35871
36045
  const ARROW_UP = {
35872
36046
  width: 448,
35873
36047
  height: 512,
35874
- fillColor: "#6AA84F",
35875
- path: "M34.9 289.5l-22.2-22.2c-9.4-9.4-9.4-24.6 0-33.9L207 39c9.4-9.4 24.6-9.4 33.9 0l194.3 194.3c9.4 9.4 9.4 24.6 0 33.9L413 289.4c-9.5 9.5-25 9.3-34.3-.4L264 168.6V456c0 13.3-10.7 24-24 24h-32c-13.3 0-24-10.7-24-24V168.6L69.2 289.1c-9.3 9.8-24.8 10-34.3.4z",
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
+ ],
35876
36054
  };
35877
36055
  const ARROW_RIGHT = {
35878
36056
  width: 448,
35879
36057
  height: 512,
35880
- fillColor: "#F0AD4E",
35881
- path: "M190.5 66.9l22.2-22.2c9.4-9.4 24.6-9.4 33.9 0L441 239c9.4 9.4 9.4 24.6 0 33.9L246.6 467.3c-9.4 9.4-24.6 9.4-33.9 0l-22.2-22.2c-9.5-9.5-9.3-25 .4-34.3L311.4 296H24c-13.3 0-24-10.7-24-24v-32c0-13.3 10.7-24 24-24h287.4L190.9 101.2c-9.8-9.3-10-24.8-.4-34.3z",
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
+ ],
35882
36064
  };
35883
36065
  const SMILE = {
35884
36066
  width: 496,
35885
36067
  height: 512,
35886
- fillColor: "#6AA84F",
35887
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160 0c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm4 72.6c-20.8 25-51.5 39.4-84 39.4s-63.2-14.3-84-39.4c-8.5-10.2-23.7-11.5-33.8-3.1-10.2 8.5-11.5 23.6-3.1 33.8 30 36 74.1 56.6 120.9 56.6s90.9-20.6 120.9-56.6c8.5-10.2 7.1-25.3-3.1-33.8-10.1-8.4-25.3-7.1-33.8 3.1z",
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
+ ],
35888
36074
  };
35889
36075
  const MEH = {
35890
36076
  width: 496,
35891
36077
  height: 512,
35892
- fillColor: "#F0AD4E",
35893
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm8 144H160c-13.2 0-24 10.8-24 24s10.8 24 24 24h176c13.2 0 24-10.8 24-24s-10.8-24-24-24z",
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
+ ],
35894
36084
  };
35895
36085
  const FROWN = {
35896
36086
  width: 496,
35897
36087
  height: 512,
35898
- fillColor: "#E06666",
35899
- path: "M248 8C111 8 0 119 0 256s111 248 248 248 248-111 248-248S385 8 248 8zm0 448c-110.3 0-200-89.7-200-200S137.7 56 248 56s200 89.7 200 200-89.7 200-200 200zm-80-216c17.7 0 32-14.3 32-32s-14.3-32-32-32-32 14.3-32 32 14.3 32 32 32zm160-64c-17.7 0-32 14.3-32 32s14.3 32 32 32 32-14.3 32-32-14.3-32-32-32zm-80 128c-40.2 0-78 17.7-103.8 48.6-8.5 10.2-7.1 25.3 3.1 33.8 10.2 8.4 25.3 7.1 33.8-3.1 16.6-19.9 41-31.4 66.9-31.4s50.3 11.4 66.9 31.4c8.1 9.7 23.1 11.9 33.8 3.1 10.2-8.5 11.5-23.6 3.1-33.8C326 321.7 288.2 304 248 304z",
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
+ ],
35900
36094
  };
35901
36095
  const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
35902
36096
  const GREEN_DOT = {
35903
36097
  width: 512,
35904
36098
  height: 512,
35905
- fillColor: "#6AA84F",
35906
- path: DOT_PATH,
36099
+ paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
35907
36100
  };
35908
36101
  const YELLOW_DOT = {
35909
36102
  width: 512,
35910
36103
  height: 512,
35911
- fillColor: "#F0AD4E",
35912
- path: DOT_PATH,
36104
+ paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
35913
36105
  };
35914
36106
  const RED_DOT = {
35915
36107
  width: 512,
35916
36108
  height: 512,
35917
- fillColor: "#E06666",
35918
- 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
+ ],
35919
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" }],
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
+ }
35920
36181
  const ICONS = {
35921
36182
  arrowGood: {
35922
36183
  template: "ARROW_UP",
@@ -35972,6 +36233,15 @@ const ICON_SETS = {
35972
36233
  bad: "dotBad",
35973
36234
  },
35974
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
+ }
35975
36245
 
35976
36246
  /**
35977
36247
  * Map of the different types of conversions warnings and their name in error messages
@@ -41943,6 +42213,7 @@ const findAndReplace = {
41943
42213
  execute: (env) => {
41944
42214
  env.openSidePanel("FindAndReplace", {});
41945
42215
  },
42216
+ isEnabled: (env) => !env.isSmall,
41946
42217
  icon: "o-spreadsheet-Icon.SEARCH",
41947
42218
  };
41948
42219
  const deleteValues = {
@@ -42199,11 +42470,13 @@ const insertCellShiftRight = {
42199
42470
  const insertChart = {
42200
42471
  name: _t("Chart"),
42201
42472
  execute: CREATE_CHART,
42473
+ isEnabled: (env) => !env.isSmall,
42202
42474
  icon: "o-spreadsheet-Icon.INSERT_CHART",
42203
42475
  };
42204
42476
  const insertPivot = {
42205
42477
  name: _t("Pivot table"),
42206
42478
  execute: CREATE_PIVOT,
42479
+ isEnabled: (env) => !env.isSmall,
42207
42480
  icon: "o-spreadsheet-Icon.PIVOT",
42208
42481
  };
42209
42482
  const insertImage = {
@@ -42211,12 +42484,14 @@ const insertImage = {
42211
42484
  description: "Ctrl+O",
42212
42485
  execute: CREATE_IMAGE,
42213
42486
  isVisible: (env) => env.imageProvider !== undefined,
42487
+ isEnabled: (env) => !env.isSmall,
42214
42488
  icon: "o-spreadsheet-Icon.INSERT_IMAGE",
42215
42489
  };
42216
42490
  const insertTable = {
42217
42491
  name: () => _t("Table"),
42218
42492
  execute: INSERT_TABLE,
42219
42493
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
42494
+ isEnabled: (env) => !env.isSmall,
42220
42495
  icon: "o-spreadsheet-Icon.PAINT_TABLE",
42221
42496
  };
42222
42497
  const insertFunction = {
@@ -42322,6 +42597,7 @@ const insertDropdown = {
42322
42597
  },
42323
42598
  });
42324
42599
  },
42600
+ isEnabled: (env) => !env.isSmall,
42325
42601
  icon: "o-spreadsheet-Icon.INSERT_DROPDOWN",
42326
42602
  };
42327
42603
  const insertSheet = {
@@ -42591,7 +42867,7 @@ const pivotProperties = {
42591
42867
  isVisible: (env) => {
42592
42868
  const position = env.model.getters.getActivePosition();
42593
42869
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
42594
- return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42870
+ return (!env.isSmall && pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42595
42871
  },
42596
42872
  isReadonlyAllowed: true,
42597
42873
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -42709,7 +42985,7 @@ function isPivotSortMenuItemActive(env, order) {
42709
42985
  }
42710
42986
 
42711
42987
  //------------------------------------------------------------------------------
42712
- // Context Menu Registry
42988
+ // Context MenuPopover Registry
42713
42989
  //------------------------------------------------------------------------------
42714
42990
  const cellMenuRegistry = new MenuItemRegistry();
42715
42991
  cellMenuRegistry
@@ -42792,6 +43068,7 @@ cellMenuRegistry
42792
43068
  .add("edit_table", {
42793
43069
  ...editTable,
42794
43070
  isVisible: SELECTION_CONTAINS_SINGLE_TABLE,
43071
+ isEnabled: (env) => !env.isSmall,
42795
43072
  sequence: 140,
42796
43073
  })
42797
43074
  .add("delete_table", {
@@ -42860,6 +43137,7 @@ const removeDuplicates = {
42860
43137
  }
42861
43138
  env.openSidePanel("RemoveDuplicates", {});
42862
43139
  },
43140
+ isEnabled: (env) => !env.isSmall,
42863
43141
  };
42864
43142
  const trimWhitespace = {
42865
43143
  name: _t("Trim whitespace"),
@@ -42887,7 +43165,7 @@ const splitToColumns = {
42887
43165
  name: _t("Split text to columns"),
42888
43166
  sequence: 1,
42889
43167
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
42890
- isEnabled: (env) => env.model.getters.isSingleColSelected(),
43168
+ isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
42891
43169
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
42892
43170
  };
42893
43171
  const reinsertDynamicPivotMenu = {
@@ -42974,7 +43252,7 @@ const formatNumberAccounting = createFormatActionSpec({
42974
43252
  const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
42975
43253
  const formatCustomCurrency = {
42976
43254
  name: _t("Custom currency"),
42977
- isVisible: (env) => env.loadCurrencies !== undefined,
43255
+ isVisible: (env) => env.loadCurrencies !== undefined && !env.isSmall,
42978
43256
  execute: (env) => env.openSidePanel("CustomCurrency", {}),
42979
43257
  };
42980
43258
  const formatNumberDate = createFormatActionSpec({
@@ -43197,6 +43475,7 @@ const formatWrappingClip = {
43197
43475
  const formatCF = {
43198
43476
  name: _t("Conditional formatting"),
43199
43477
  execute: OPEN_CF_SIDEPANEL_ACTION,
43478
+ isEnabled: (env) => !env.isSmall,
43200
43479
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
43201
43480
  };
43202
43481
  const clearFormat = {
@@ -43580,8 +43859,7 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
43580
43859
  }
43581
43860
  return [
43582
43861
  {
43583
- sheetId: position.sheetId,
43584
- zone,
43862
+ range: this.model.getters.getRangeFromZone(position.sheetId, zone),
43585
43863
  dashed: cell.value === CellErrorType.SpilledBlocked,
43586
43864
  color: "#17A2B8",
43587
43865
  noFill: true,
@@ -43666,6 +43944,7 @@ function useDragAndDropBeyondTheViewport(env) {
43666
43944
  let previousEvClientPosition;
43667
43945
  let startingX;
43668
43946
  let startingY;
43947
+ let scrollDirection = "all";
43669
43948
  const getters = env.model.getters;
43670
43949
  let cleanUpFns = [];
43671
43950
  const cleanUp = () => {
@@ -43691,55 +43970,59 @@ function useDragAndDropBeyondTheViewport(env) {
43691
43970
  let timeoutDelay = MAX_DELAY;
43692
43971
  const x = currentEv.clientX - position.left;
43693
43972
  let colIndex = getters.getColIndex(x);
43694
- const previousX = previousEvClientPosition.clientX - position.left;
43695
- const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);
43696
- if (edgeScrollInfoX.canEdgeScroll) {
43697
- canEdgeScroll = true;
43698
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);
43699
- let newTarget = colIndex;
43700
- switch (edgeScrollInfoX.direction) {
43701
- case "reset":
43702
- colIndex = newTarget = xSplit;
43703
- break;
43704
- case 1:
43705
- colIndex = right;
43706
- newTarget = left + 1;
43707
- break;
43708
- case -1:
43709
- colIndex = left - 1;
43710
- while (env.model.getters.isColHidden(sheetId, colIndex)) {
43711
- colIndex--;
43712
- }
43713
- newTarget = colIndex;
43714
- break;
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;
43715
43997
  }
43716
- scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43717
43998
  }
43718
43999
  const y = currentEv.clientY - position.top;
43719
44000
  let rowIndex = getters.getRowIndex(y);
43720
- const previousY = previousEvClientPosition.clientY - position.top;
43721
- const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);
43722
- if (edgeScrollInfoY.canEdgeScroll) {
43723
- canEdgeScroll = true;
43724
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);
43725
- let newTarget = rowIndex;
43726
- switch (edgeScrollInfoY.direction) {
43727
- case "reset":
43728
- rowIndex = newTarget = ySplit;
43729
- break;
43730
- case 1:
43731
- rowIndex = bottom;
43732
- newTarget = top + 1;
43733
- break;
43734
- case -1:
43735
- rowIndex = top - 1;
43736
- while (env.model.getters.isRowHidden(sheetId, rowIndex)) {
43737
- rowIndex--;
43738
- }
43739
- newTarget = rowIndex;
43740
- break;
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;
43741
44025
  }
43742
- scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43743
44026
  }
43744
44027
  if (!canEdgeScroll) {
43745
44028
  colIndex = adjustIndexWithinBounds(colIndex, x, getters.getNumberCols(sheetId) - 1);
@@ -43759,9 +44042,10 @@ function useDragAndDropBeyondTheViewport(env) {
43759
44042
  pointerUpCallback?.();
43760
44043
  cleanUp();
43761
44044
  };
43762
- const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp) => {
44045
+ const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp, startScrollDirection = "all") => {
43763
44046
  cleanUp();
43764
44047
  const position = gridOverlayPosition();
44048
+ scrollDirection = startScrollDirection;
43765
44049
  startingX = initialPointerCoordinates.clientX - position.left;
43766
44050
  startingY = initialPointerCoordinates.clientY - position.top;
43767
44051
  previousEvClientPosition = {
@@ -44239,7 +44523,7 @@ class GridComposer extends owl.Component {
44239
44523
  });
44240
44524
  }
44241
44525
  get shouldDisplayCellReference() {
44242
- return this.isCellReferenceVisible;
44526
+ return !this.env.isMobile() && this.isCellReferenceVisible;
44243
44527
  }
44244
44528
  get cellReference() {
44245
44529
  const { col, row, sheetId } = this.composerStore.currentEditedCell;
@@ -44280,11 +44564,12 @@ class GridComposer extends owl.Component {
44280
44564
  onInputContextMenu: this.props.onInputContextMenu,
44281
44565
  composerStore: this.composerStore,
44282
44566
  inputStyle: `max-height: ${maxHeight}px;`,
44567
+ inputMode: this.composerStore.editionMode === "inactive" ? "none" : undefined,
44283
44568
  };
44284
44569
  }
44285
44570
  get containerStyle() {
44286
- if (this.composerStore.editionMode === "inactive") {
44287
- 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
44288
44573
  }
44289
44574
  const _isFormula = isFormula(this.composerStore.currentContent);
44290
44575
  const cell = this.env.model.getters.getActiveCell();
@@ -44313,11 +44598,13 @@ class GridComposer extends owl.Component {
44313
44598
  *
44314
44599
  * The +-1 are there to include cell borders in the composer sizing/positioning
44315
44600
  */
44601
+ const minHeight = Math.min(height + 1, maxHeight);
44602
+ const minWidth = Math.min(width + 1, maxWidth);
44316
44603
  return cssPropertiesToCss({
44317
44604
  left: `${left - 1}px`,
44318
44605
  top: `${top}px`,
44319
- "min-width": `${width + 1}px`,
44320
- "min-height": `${height + 1}px`,
44606
+ "min-width": `${minWidth}px`,
44607
+ "min-height": `${minHeight}px`,
44321
44608
  "max-width": `${maxWidth}px`,
44322
44609
  "max-height": `${maxHeight}px`,
44323
44610
  background,
@@ -44814,6 +45101,9 @@ class FiguresContainer extends owl.Component {
44814
45101
  if (!selectResult.isSuccessful) {
44815
45102
  return;
44816
45103
  }
45104
+ if (this.env.isMobile()) {
45105
+ return;
45106
+ }
44817
45107
  const sheetId = this.env.model.getters.getActiveSheetId();
44818
45108
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
44819
45109
  const initialScrollPosition = this.env.model.getters.getActiveSheetScrollInfo();
@@ -45106,78 +45396,6 @@ class GridAddRowsFooter extends owl.Component {
45106
45396
  }
45107
45397
  }
45108
45398
 
45109
- class GridCellIcon extends owl.Component {
45110
- static template = "o-spreadsheet-GridCellIcon";
45111
- static props = {
45112
- icon: Object,
45113
- verticalAlign: { type: String, optional: true },
45114
- slots: Object,
45115
- };
45116
- get iconStyle() {
45117
- const cellPosition = this.props.icon.position;
45118
- const merge = this.env.model.getters.getMerge(cellPosition);
45119
- const zone = merge || positionToZone(cellPosition);
45120
- const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
45121
- const x = this.getIconHorizontalPosition(rect, cellPosition);
45122
- const y = this.getIconVerticalPosition(rect, cellPosition);
45123
- return cssPropertiesToCss({
45124
- top: `${y}px`,
45125
- left: `${x}px`,
45126
- width: `${this.props.icon.size}px`,
45127
- height: `${this.props.icon.size}px`,
45128
- });
45129
- }
45130
- getIconVerticalPosition(rect, cellPosition) {
45131
- const start = rect.y;
45132
- const end = rect.y + rect.height;
45133
- const cell = this.env.model.getters.getCell(cellPosition);
45134
- const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
45135
- switch (align) {
45136
- case "bottom":
45137
- return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
45138
- case "top":
45139
- return start + GRID_ICON_MARGIN;
45140
- default:
45141
- const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
45142
- return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
45143
- }
45144
- }
45145
- getIconHorizontalPosition(rect, cellPosition) {
45146
- const start = rect.x;
45147
- const end = rect.x + rect.width;
45148
- const cell = this.env.model.getters.getCell(cellPosition);
45149
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
45150
- const align = this.props.icon.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
45151
- switch (align) {
45152
- case "right":
45153
- return end - this.props.icon.size - this.props.icon.margin;
45154
- case "left":
45155
- return start + this.props.icon.margin;
45156
- default:
45157
- const centeringOffset = Math.floor((end - start - this.props.icon.size) / 2);
45158
- return end - this.props.icon.size - centeringOffset;
45159
- }
45160
- }
45161
- isPositionVisible(position) {
45162
- const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
45163
- return !(rect.width === 0 || rect.height === 0);
45164
- }
45165
- }
45166
-
45167
- class GridCellIconOverlay extends owl.Component {
45168
- static template = "o-spreadsheet-GridCellIconOverlay";
45169
- static props = {};
45170
- static components = { GridCellIcon };
45171
- get icons() {
45172
- const icons = [];
45173
- for (const position of this.env.model.getters.getVisibleCellPositions()) {
45174
- const cellIcons = this.env.model.getters.getCellIcons(position);
45175
- icons.push(...cellIcons.filter((icon) => icon.component));
45176
- }
45177
- return icons;
45178
- }
45179
- }
45180
-
45181
45399
  /**
45182
45400
  * Manages an event listener on a ref. Useful for hooks that want to manage
45183
45401
  * event listeners, especially more than one. Prefer using t-on directly in
@@ -45266,7 +45484,7 @@ class PaintFormatStore extends SpreadsheetStore {
45266
45484
  return [];
45267
45485
  }
45268
45486
  return data.zones.map((zone) => ({
45269
- zone,
45487
+ range: this.model.getters.getRangeFromZone(data.sheetId, zone),
45270
45488
  color: SELECTION_BORDER_COLOR,
45271
45489
  dashed: true,
45272
45490
  sheetId: data.sheetId,
@@ -45289,7 +45507,7 @@ class HoveredTableStore extends SpreadsheetStore {
45289
45507
  }
45290
45508
  }
45291
45509
  hover(position) {
45292
- if (position.col === this.col && position.row === this.row) {
45510
+ if (!this.getters.isDashboard() || (position.col === this.col && position.row === this.row)) {
45293
45511
  return "noStateChange";
45294
45512
  }
45295
45513
  this.col = position.col;
@@ -45323,6 +45541,14 @@ class HoveredTableStore extends SpreadsheetStore {
45323
45541
  }
45324
45542
  }
45325
45543
 
45544
+ class HoveredIconStore extends SpreadsheetStore {
45545
+ mutators = ["setHoveredIcon"];
45546
+ hoveredIcon = undefined;
45547
+ setHoveredIcon(icon) {
45548
+ this.hoveredIcon = icon;
45549
+ }
45550
+ }
45551
+
45326
45552
  const CURSOR_SVG = /*xml*/ `
45327
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>
45328
45554
  `;
@@ -45394,10 +45620,11 @@ function useCellHovered(env, gridRef) {
45394
45620
  return pause();
45395
45621
  }
45396
45622
  }
45397
- useRefListener(gridRef, "pointermove", updateMousePosition);
45623
+ useRefListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(ev));
45398
45624
  useRefListener(gridRef, "mouseleave", onMouseLeave);
45399
45625
  useRefListener(gridRef, "mouseenter", resume);
45400
45626
  useRefListener(gridRef, "pointerdown", recompute);
45627
+ useRefListener(gridRef, "pointerdown", (ev) => env.isMobile() && updateMousePosition(ev));
45401
45628
  owl.useExternalListener(window, "click", handleGlobalClick);
45402
45629
  function handleGlobalClick(e) {
45403
45630
  const target = e.target;
@@ -45426,11 +45653,11 @@ class GridOverlay extends owl.Component {
45426
45653
  onGridMoved: Function,
45427
45654
  gridOverlayDimensions: String,
45428
45655
  slots: { type: Object, optional: true },
45656
+ getGridSize: Function,
45429
45657
  };
45430
45658
  static components = {
45431
45659
  FiguresContainer,
45432
45660
  GridAddRowsFooter,
45433
- GridCellIconOverlay,
45434
45661
  };
45435
45662
  static defaultProps = {
45436
45663
  onCellDoubleClicked: () => { },
@@ -45442,15 +45669,17 @@ class GridOverlay extends owl.Component {
45442
45669
  gridOverlay = owl.useRef("gridOverlay");
45443
45670
  cellPopovers;
45444
45671
  paintFormatStore;
45672
+ hoveredIconStore;
45445
45673
  setup() {
45446
45674
  useCellHovered(this.env, this.gridOverlay);
45447
45675
  const resizeObserver = new ResizeObserver(() => {
45448
45676
  const boundingRect = this.gridOverlayEl.getBoundingClientRect();
45677
+ const { width, height } = this.props.getGridSize();
45449
45678
  this.props.onGridResized({
45450
45679
  x: boundingRect.left,
45451
45680
  y: boundingRect.top,
45452
- height: this.gridOverlayEl.clientHeight,
45453
- width: this.gridOverlayEl.clientWidth,
45681
+ height: height,
45682
+ width: width,
45454
45683
  });
45455
45684
  });
45456
45685
  owl.onMounted(() => {
@@ -45461,6 +45690,7 @@ class GridOverlay extends owl.Component {
45461
45690
  });
45462
45691
  this.cellPopovers = useStore(CellPopoverStore);
45463
45692
  this.paintFormatStore = useStore(PaintFormatStore);
45693
+ this.hoveredIconStore = useStore(HoveredIconStore);
45464
45694
  }
45465
45695
  get gridOverlayEl() {
45466
45696
  if (!this.gridOverlay.el) {
@@ -45469,26 +45699,59 @@ class GridOverlay extends owl.Component {
45469
45699
  return this.gridOverlay.el;
45470
45700
  }
45471
45701
  get style() {
45472
- return this.props.gridOverlayDimensions;
45702
+ return (this.props.gridOverlayDimensions +
45703
+ cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "default" }));
45473
45704
  }
45474
45705
  get isPaintingFormat() {
45475
45706
  return this.paintFormatStore.isActive;
45476
45707
  }
45477
- onMouseDown(ev) {
45478
- 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()) {
45479
45720
  // not main button, probably a context menu
45480
45721
  return;
45481
45722
  }
45482
- if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
45483
- 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;
45484
45729
  }
45730
+ this.onCellClicked(ev);
45731
+ }
45732
+ onCellClicked(ev) {
45733
+ const openedPopover = this.cellPopovers.persistentCellPopover;
45485
45734
  const [col, row] = this.getCartesianCoordinates(ev);
45486
45735
  this.props.onCellClicked(col, row, {
45487
45736
  expandZone: ev.shiftKey,
45488
45737
  addZone: isCtrlKey(ev),
45489
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
+ }
45490
45750
  }
45491
45751
  onDoubleClick(ev) {
45752
+ if (this.getInteractiveIconAtEvent(ev)) {
45753
+ return;
45754
+ }
45492
45755
  const [col, row] = this.getCartesianCoordinates(ev);
45493
45756
  this.props.onCellDoubleClicked(col, row);
45494
45757
  }
@@ -45504,6 +45767,24 @@ class GridOverlay extends owl.Component {
45504
45767
  const rowIndex = this.env.model.getters.getRowIndex(y);
45505
45768
  return [colIndex, rowIndex];
45506
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
+ }
45507
45788
  }
45508
45789
 
45509
45790
  class GridPopover extends owl.Component {
@@ -45664,6 +45945,9 @@ class AbstractResizer extends owl.Component {
45664
45945
  this.state.waitingForMove = false;
45665
45946
  }
45666
45947
  onMouseMove(ev) {
45948
+ if (this.env.isMobile()) {
45949
+ return;
45950
+ }
45667
45951
  if (this.state.isResizing || this.state.isMoving || this.state.isSelecting) {
45668
45952
  return;
45669
45953
  }
@@ -45708,7 +45992,21 @@ class AbstractResizer extends owl.Component {
45708
45992
  };
45709
45993
  startDnd(onMouseMove, onMouseUp);
45710
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
+ }
45711
46006
  select(ev) {
46007
+ if (this.env.isMobile()) {
46008
+ return;
46009
+ }
45712
46010
  if (ev.button > 0) {
45713
46011
  // not main button, probably a context menu
45714
46012
  return;
@@ -45777,6 +46075,9 @@ class AbstractResizer extends owl.Component {
45777
46075
  this.dragNDropGrid.start(ev, mouseMoveMovement, mouseUpMovement);
45778
46076
  }
45779
46077
  startSelection(ev, index) {
46078
+ if (this.env.isMobile()) {
46079
+ return;
46080
+ }
45780
46081
  this.state.isSelecting = true;
45781
46082
  if (ev.shiftKey) {
45782
46083
  this._increaseSelection(index);
@@ -46222,11 +46523,13 @@ class GridRenderer {
46222
46523
  renderer;
46223
46524
  fingerprints;
46224
46525
  hoveredTables;
46526
+ hoveredIcon;
46225
46527
  constructor(get) {
46226
46528
  this.getters = get(ModelStore).getters;
46227
46529
  this.renderer = get(RendererStore);
46228
46530
  this.fingerprints = get(FormulaFingerprintStore);
46229
46531
  this.hoveredTables = get(HoveredTableStore);
46532
+ this.hoveredIcon = get(HoveredIconStore);
46230
46533
  this.renderer.register(this);
46231
46534
  }
46232
46535
  get renderingLayers() {
@@ -46463,7 +46766,7 @@ class GridRenderer {
46463
46766
  // compute vertical align start point parameter:
46464
46767
  const textLineHeight = computeTextFontSizeInPixels(style);
46465
46768
  const numberOfLines = box.content.textLines.length;
46466
- let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
46769
+ let y = this.getters.computeTextYCoordinate(box, textLineHeight, style.verticalAlign, numberOfLines);
46467
46770
  // use the horizontal and the vertical start points to:
46468
46771
  // fill text / fill strikethrough / fill underline
46469
46772
  for (const brokenLine of box.content.textLines) {
@@ -46480,7 +46783,12 @@ class GridRenderer {
46480
46783
  const { ctx } = renderingContext;
46481
46784
  for (const box of boxes) {
46482
46785
  for (const icon of Object.values(box.icons)) {
46483
- 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) {
46484
46792
  continue;
46485
46793
  }
46486
46794
  ctx.save();
@@ -46488,46 +46796,17 @@ class GridRenderer {
46488
46796
  ctx.rect(box.x, box.y, box.width, box.height);
46489
46797
  ctx.clip();
46490
46798
  const iconSize = icon.size;
46491
- const iconY = this.computeTextYCoordinate(box, iconSize);
46492
- const svg = icon.svg;
46493
- let x;
46494
- if (icon.horizontalAlign === "left") {
46495
- x = box.x + icon.margin;
46496
- }
46497
- else if (icon.horizontalAlign === "right") {
46498
- x = box.x + box.width - iconSize - icon.margin;
46499
- }
46500
- else {
46501
- x = box.x + (box.width - iconSize) / 2;
46502
- }
46503
- ctx.translate(x, iconY);
46799
+ const { x, y } = this.getters.getCellIconRect(icon);
46800
+ ctx.translate(x, y);
46504
46801
  ctx.scale(iconSize / svg.width, iconSize / svg.height);
46505
- ctx.fillStyle = svg.fillColor;
46506
- 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
+ }
46507
46806
  ctx.restore();
46508
46807
  }
46509
46808
  }
46510
46809
  }
46511
- /** Computes the vertical start point from which a text line should be draw.
46512
- *
46513
- * Note that in case the cell does not have enough spaces to display its text lines,
46514
- * (wrapping cell case) then the vertical align should be at the top.
46515
- * */
46516
- computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {
46517
- const y = box.y + 1;
46518
- const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
46519
- const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
46520
- const verticalAlign = box.verticalAlign || DEFAULT_VERTICAL_ALIGN;
46521
- if (hasEnoughSpaces) {
46522
- if (verticalAlign === "middle") {
46523
- return y + (box.height - textHeight) / 2;
46524
- }
46525
- if (verticalAlign === "bottom") {
46526
- return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;
46527
- }
46528
- }
46529
- return y + MIN_CELL_TEXT_MARGIN;
46530
- }
46531
46810
  drawHeaders(renderingContext) {
46532
46811
  const { ctx, thinLineWidth } = renderingContext;
46533
46812
  const visibleCols = this.getters.getSheetViewVisibleCols();
@@ -46964,6 +47243,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46964
47243
  const deltaX = lastX - clientX;
46965
47244
  const deltaY = lastY - clientY;
46966
47245
  const elapsedTime = currentTime - lastTime;
47246
+ if (!elapsedTime) {
47247
+ return;
47248
+ }
46967
47249
  velocityX = deltaX / elapsedTime;
46968
47250
  velocityY = deltaY / elapsedTime;
46969
47251
  lastX = clientX;
@@ -46984,6 +47266,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46984
47266
  function onTouchEnd(ev) {
46985
47267
  isMouseDown = false;
46986
47268
  lastX = lastY = 0;
47269
+ if (resetTimeout) {
47270
+ clearTimeout(resetTimeout);
47271
+ }
47272
+ velocityX *= 1.2;
47273
+ velocityY *= 1.2;
46987
47274
  requestAnimationFrame(scroll);
46988
47275
  }
46989
47276
  function scroll() {
@@ -47068,12 +47355,16 @@ class Border extends owl.Component {
47068
47355
  }
47069
47356
  }
47070
47357
 
47358
+ const MOBILE_HANDLER_WIDTH = 40;
47071
47359
  css /* scss */ `
47072
47360
  .o-corner {
47073
47361
  position: absolute;
47074
- height: 8px;
47075
- width: 8px;
47362
+ }
47363
+
47364
+ .o-corner-button {
47076
47365
  border: 1px solid white;
47366
+ height: ${AUTOFILL_EDGE_LENGTH}px;
47367
+ width: ${AUTOFILL_EDGE_LENGTH}px;
47077
47368
  }
47078
47369
  .o-corner-nw,
47079
47370
  .o-corner-se {
@@ -47100,34 +47391,61 @@ class Corner extends owl.Component {
47100
47391
  isResizing: Boolean,
47101
47392
  onResizeHighlight: Function,
47102
47393
  };
47103
- isTop = this.props.orientation[0] === "n";
47104
- isLeft = this.props.orientation[1] === "w";
47105
- 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() {
47106
47402
  const z = this.props.zone;
47107
- const col = this.isLeft ? z.left : z.right;
47108
- const row = this.isTop ? z.top : z.bottom;
47109
47403
  const rect = this.env.model.getters.getVisibleRect({
47110
- left: col,
47111
- right: col,
47112
- top: row,
47113
- 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,
47114
47408
  });
47115
47409
  // Don't show if not visible in the viewport
47116
47410
  if (rect.width * rect.height === 0) {
47117
- 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`;
47118
47424
  }
47119
- const leftValue = this.isLeft ? rect.x : rect.x + rect.width;
47120
- const topValue = this.isTop ? rect.y : rect.y + rect.height;
47121
- return cssPropertiesToCss({
47122
- left: `${leftValue - AUTOFILL_EDGE_LENGTH / 2}px`,
47123
- top: `${topValue - AUTOFILL_EDGE_LENGTH / 2}px`,
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 = {
47124
47432
  "background-color": this.props.color,
47125
- });
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);
47126
47439
  }
47127
47440
  onMouseDown(ev) {
47128
- this.props.onResizeHighlight(ev, this.isLeft, this.isTop);
47441
+ this.props.onResizeHighlight(ev, this.dirX, this.dirY);
47129
47442
  }
47130
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
+ }
47131
47449
 
47132
47450
  css /*SCSS*/ `
47133
47451
  .o-highlight {
@@ -47137,7 +47455,7 @@ css /*SCSS*/ `
47137
47455
  class Highlight extends owl.Component {
47138
47456
  static template = "o-spreadsheet-Highlight";
47139
47457
  static props = {
47140
- zone: Object,
47458
+ range: Object,
47141
47459
  color: String,
47142
47460
  };
47143
47461
  static components = {
@@ -47148,26 +47466,49 @@ class Highlight extends owl.Component {
47148
47466
  shiftingMode: "none",
47149
47467
  });
47150
47468
  dragNDropGrid = useDragAndDropBeyondTheViewport(this.env);
47151
- 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) {
47152
47485
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47153
47486
  this.highlightState.shiftingMode = "isResizing";
47154
- const z = this.props.zone;
47155
- const pivotCol = isLeft ? z.right : z.left;
47156
- const pivotRow = isTop ? z.bottom : z.top;
47157
- let lastCol = isLeft ? z.left : z.right;
47158
- let lastRow = isTop ? z.top : z.bottom;
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;
47159
47492
  let currentZone = z;
47493
+ let scrollDirection = "all";
47494
+ if (this.env.isMobile()) {
47495
+ scrollDirection = dirX === 0 ? "vertical" : dirY === 0 ? "horizontal" : "all";
47496
+ }
47160
47497
  this.env.model.dispatch("START_CHANGE_HIGHLIGHT", { zone: currentZone });
47161
47498
  const mouseMove = (col, row) => {
47162
47499
  if (lastCol !== col || lastRow !== row) {
47163
- lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
47164
- lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
47165
- const newZone = {
47166
- left: Math.min(pivotCol, lastCol),
47167
- top: Math.min(pivotRow, lastRow),
47168
- right: Math.max(pivotCol, lastCol),
47169
- bottom: Math.max(pivotRow, lastRow),
47170
- };
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 };
47171
47512
  if (!isEqual(newZone, currentZone)) {
47172
47513
  this.env.model.selection.selectZone({
47173
47514
  cell: { col: newZone.left, row: newZone.top },
@@ -47180,11 +47521,11 @@ class Highlight extends owl.Component {
47180
47521
  const mouseUp = () => {
47181
47522
  this.highlightState.shiftingMode = "none";
47182
47523
  };
47183
- this.dragNDropGrid.start(ev, mouseMove, mouseUp);
47524
+ this.dragNDropGrid.start(ev, mouseMove, mouseUp, scrollDirection);
47184
47525
  }
47185
47526
  onMoveHighlight(ev) {
47186
47527
  this.highlightState.shiftingMode = "isMoving";
47187
- const z = this.props.zone;
47528
+ const z = this.props.range.zone;
47188
47529
  const position = gridOverlayPosition();
47189
47530
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47190
47531
  const initCol = this.env.model.getters.getColIndex(ev.clientX - position.left);
@@ -47406,6 +47747,18 @@ class VerticalScrollBar extends owl.Component {
47406
47747
  }
47407
47748
  }
47408
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
+
47409
47762
  class Section extends owl.Component {
47410
47763
  static template = "o_spreadsheet.Section";
47411
47764
  static props = {
@@ -48351,6 +48704,7 @@ class ColorPickerWidget extends owl.Component {
48351
48704
 
48352
48705
  css /* scss */ `
48353
48706
  .o-font-size-editor {
48707
+ width: max-content !important;
48354
48708
  height: calc(100% - 4px);
48355
48709
  input.o-font-size {
48356
48710
  outline: none;
@@ -50499,8 +50853,7 @@ class ConditionalFormatPreview extends owl.Component {
50499
50853
  get highlights() {
50500
50854
  const sheetId = this.env.model.getters.getActiveSheetId();
50501
50855
  return this.props.conditionalFormat.ranges.map((range) => ({
50502
- sheetId,
50503
- zone: this.env.model.getters.getRangeFromSheetXC(sheetId, range).zone,
50856
+ range: this.env.model.getters.getRangeFromSheetXC(sheetId, range),
50504
50857
  color: HIGHLIGHT_COLOR,
50505
50858
  fillAlpha: 0.06,
50506
50859
  }));
@@ -51458,8 +51811,7 @@ class DataValidationPreview extends owl.Component {
51458
51811
  }
51459
51812
  get highlights() {
51460
51813
  return this.props.rule.ranges.map((range) => ({
51461
- sheetId: this.env.model.getters.getActiveSheetId(),
51462
- zone: range.zone,
51814
+ range,
51463
51815
  color: HIGHLIGHT_COLOR,
51464
51816
  fillAlpha: 0.06,
51465
51817
  }));
@@ -51788,13 +52140,15 @@ class FindAndReplaceStore extends SpreadsheetStore {
51788
52140
  if (this.selectedMatchIndex === null) {
51789
52141
  return;
51790
52142
  }
52143
+ this.preserveSelectedMatchIndex = true;
52144
+ this.shouldFinalizeUpdateSelection = true;
51791
52145
  this.model.dispatch("REPLACE_SEARCH", {
51792
52146
  searchString: this.toSearch,
51793
52147
  replaceWith: this.toReplace,
51794
52148
  matches: [this.searchMatches[this.selectedMatchIndex]],
51795
52149
  searchOptions: this.searchOptions,
51796
52150
  });
51797
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
52151
+ this.preserveSelectedMatchIndex = false;
51798
52152
  }
51799
52153
  /**
51800
52154
  * Apply the replace function to all the matches one time.
@@ -51866,8 +52220,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51866
52220
  const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
51867
52221
  if (width > 0 && height > 0) {
51868
52222
  highlights.push({
51869
- sheetId,
51870
- zone: zoneWithMerge,
52223
+ range: this.model.getters.getRangeFromZone(sheetId, zoneWithMerge),
51871
52224
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51872
52225
  noBorder: index !== this.selectedMatchIndex,
51873
52226
  thinLine: true,
@@ -51879,8 +52232,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51879
52232
  const range = this.searchOptions.specificRange;
51880
52233
  if (range && range.sheetId === sheetId) {
51881
52234
  highlights.push({
51882
- sheetId,
51883
- zone: range.zone,
52235
+ range,
51884
52236
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51885
52237
  noFill: true,
51886
52238
  thinLine: true,
@@ -52254,7 +52606,11 @@ function getPivotHighlights(getters, pivotId) {
52254
52606
  const sheetId = getters.getActiveSheetId();
52255
52607
  const pivotCellPositions = getVisiblePivotCellPositions(getters, pivotId);
52256
52608
  const mergedZones = mergeContiguousZones(pivotCellPositions.map(positionToZone));
52257
- 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
+ }));
52258
52614
  }
52259
52615
  function getVisiblePivotCellPositions(getters, pivotId) {
52260
52616
  const positions = [];
@@ -52523,7 +52879,7 @@ class TextInput extends owl.Component {
52523
52879
 
52524
52880
  class CogWheelMenu extends owl.Component {
52525
52881
  static template = "o-spreadsheet-CogWheelMenu";
52526
- static components = { Menu };
52882
+ static components = { MenuPopover };
52527
52883
  static props = {
52528
52884
  items: Array,
52529
52885
  };
@@ -54389,12 +54745,7 @@ class SpreadsheetPivot {
54389
54745
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
54390
54746
  }
54391
54747
  else {
54392
- if (field.type === "char") {
54393
- entry[field.name] = { ...cell, value: cell.formattedValue || null };
54394
- }
54395
- else {
54396
- entry[field.name] = cell;
54397
- }
54748
+ entry[field.name] = cell;
54398
54749
  }
54399
54750
  }
54400
54751
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -55098,6 +55449,7 @@ function createTableStyleContextMenuActions(env, styleId) {
55098
55449
  id: "editTableStyle",
55099
55450
  name: _t("Edit table style"),
55100
55451
  execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
55452
+ isEnabled: (env) => !env.isSmall,
55101
55453
  icon: "o-spreadsheet-Icon.EDIT",
55102
55454
  },
55103
55455
  {
@@ -55219,7 +55571,7 @@ css /* scss */ `
55219
55571
  `;
55220
55572
  class TableStylePreview extends owl.Component {
55221
55573
  static template = "o-spreadsheet-TableStylePreview";
55222
- static components = { Menu };
55574
+ static components = { MenuPopover };
55223
55575
  static props = {
55224
55576
  tableConfig: Object,
55225
55577
  tableStyle: Object,
@@ -55790,6 +56142,17 @@ sidePanelRegistry.add("PivotMeasureDisplayPanel", {
55790
56142
  },
55791
56143
  });
55792
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
+
55793
56156
  const DEFAULT_SIDE_PANEL_SIZE = 350;
55794
56157
  const MIN_SHEET_VIEW_WIDTH = 150;
55795
56158
  class SidePanelStore extends SpreadsheetStore {
@@ -55797,6 +56160,7 @@ class SidePanelStore extends SpreadsheetStore {
55797
56160
  initialPanelProps = {};
55798
56161
  componentTag = "";
55799
56162
  panelSize = DEFAULT_SIDE_PANEL_SIZE;
56163
+ screenWidthStore = this.get(ScreenWidthStore);
55800
56164
  get isOpen() {
55801
56165
  if (!this.componentTag) {
55802
56166
  return false;
@@ -55818,6 +56182,9 @@ class SidePanelStore extends SpreadsheetStore {
55818
56182
  return undefined;
55819
56183
  }
55820
56184
  open(componentTag, panelProps = {}) {
56185
+ if (this.screenWidthStore.isSmall) {
56186
+ return;
56187
+ }
55821
56188
  const state = this.computeState(componentTag, panelProps);
55822
56189
  if (!state.isOpen) {
55823
56190
  return;
@@ -55932,8 +56299,7 @@ class TableResizer extends owl.Component {
55932
56299
  return [];
55933
56300
  return [
55934
56301
  {
55935
- zone: this.state.highlightZone,
55936
- sheetId: this.props.table.range.sheetId,
56302
+ range: this.env.model.getters.getRangeFromZone(this.props.table.range.sheetId, this.state.highlightZone),
55937
56303
  color: COLOR,
55938
56304
  noFill: true,
55939
56305
  },
@@ -55955,13 +56321,14 @@ class Grid extends owl.Component {
55955
56321
  static template = "o-spreadsheet-Grid";
55956
56322
  static props = {
55957
56323
  exposeFocus: Function,
56324
+ getGridSize: Function,
55958
56325
  };
55959
56326
  static components = {
55960
56327
  GridComposer,
55961
56328
  GridOverlay,
55962
56329
  GridPopover,
55963
56330
  HeadersOverlay,
55964
- Menu,
56331
+ MenuPopover,
55965
56332
  Autofill,
55966
56333
  ClientTag,
55967
56334
  Highlight,
@@ -55969,6 +56336,7 @@ class Grid extends owl.Component {
55969
56336
  VerticalScrollBar,
55970
56337
  HorizontalScrollBar,
55971
56338
  TableResizer,
56339
+ Selection,
55972
56340
  };
55973
56341
  HEADER_HEIGHT = HEADER_HEIGHT;
55974
56342
  HEADER_WIDTH = HEADER_WIDTH;
@@ -56251,8 +56619,8 @@ class Grid extends owl.Component {
56251
56619
  }
56252
56620
  onGridResized({ height, width }) {
56253
56621
  this.env.model.dispatch("RESIZE_SHEETVIEW", {
56254
- width: width,
56255
- height: height,
56622
+ width: width - HEADER_WIDTH,
56623
+ height: height - HEADER_HEIGHT,
56256
56624
  gridOffsetX: HEADER_WIDTH,
56257
56625
  gridOffsetY: HEADER_HEIGHT,
56258
56626
  });
@@ -56306,6 +56674,9 @@ class Grid extends owl.Component {
56306
56674
  else {
56307
56675
  this.env.model.selection.selectCell(col, row);
56308
56676
  }
56677
+ if (this.env.isMobile()) {
56678
+ return;
56679
+ }
56309
56680
  let prevCol = col;
56310
56681
  let prevRow = row;
56311
56682
  const onMouseMove = (col, row, ev) => {
@@ -56591,6 +56962,9 @@ class Grid extends owl.Component {
56591
56962
  const sheetId = this.env.model.getters.getActiveSheetId();
56592
56963
  return this.env.model.getters.getCoreTables(sheetId).filter(isStaticTable);
56593
56964
  }
56965
+ get displaySelectionHandler() {
56966
+ return this.env.isMobile() && this.composerFocusStore.activeComposer.editionMode === "inactive";
56967
+ }
56594
56968
  }
56595
56969
 
56596
56970
  /**
@@ -62517,7 +62891,7 @@ class PivotCorePlugin extends CorePlugin {
62517
62891
  break;
62518
62892
  }
62519
62893
  case "UPDATE_PIVOT": {
62520
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
62894
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
62521
62895
  this.compileCalculatedMeasures(cmd.pivot.measures);
62522
62896
  break;
62523
62897
  }
@@ -62588,7 +62962,10 @@ class PivotCorePlugin extends CorePlugin {
62588
62962
  // Private
62589
62963
  // -------------------------------------------------------------------------
62590
62964
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
62591
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
62965
+ this.history.update("pivots", pivotId, {
62966
+ definition: this.repairSortedColumn(deepCopy(pivot)),
62967
+ formulaId,
62968
+ });
62592
62969
  this.compileCalculatedMeasures(pivot.measures);
62593
62970
  this.history.update("formulaIds", formulaId, pivotId);
62594
62971
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -62677,6 +63054,7 @@ class PivotCorePlugin extends CorePlugin {
62677
63054
  }
62678
63055
  }
62679
63056
  checkSortedColumnInMeasures(definition) {
63057
+ definition = this.repairSortedColumn(definition);
62680
63058
  const measures = definition.measures.map((measure) => measure.id);
62681
63059
  if (definition.sortedColumn && !measures.includes(definition.sortedColumn.measure)) {
62682
63060
  return "InvalidDefinition" /* CommandResult.InvalidDefinition */;
@@ -62690,6 +63068,26 @@ class PivotCorePlugin extends CorePlugin {
62690
63068
  }
62691
63069
  return "Success" /* CommandResult.Success */;
62692
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
+ }
62693
63091
  // ---------------------------------------------------------------------
62694
63092
  // Import/Export
62695
63093
  // ---------------------------------------------------------------------
@@ -65557,186 +65955,6 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
65557
65955
  }
65558
65956
  }
65559
65957
 
65560
- const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
65561
- css /* scss */ `
65562
- .o-dv-checkbox {
65563
- margin: ${MARGIN}px;
65564
- /* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
65565
- position: absolute;
65566
- }
65567
- `;
65568
- class DataValidationCheckbox extends owl.Component {
65569
- static template = "o-spreadsheet-DataValidationCheckbox";
65570
- static components = {
65571
- Checkbox,
65572
- };
65573
- static props = {
65574
- cellPosition: Object,
65575
- };
65576
- onCheckboxChange(value) {
65577
- const { sheetId, col, row } = this.props.cellPosition;
65578
- const cellContent = value ? "TRUE" : "FALSE";
65579
- this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
65580
- }
65581
- get checkBoxValue() {
65582
- return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
65583
- }
65584
- get isDisabled() {
65585
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
65586
- return this.env.model.getters.isReadonly() || !!cell?.isFormula;
65587
- }
65588
- }
65589
-
65590
- const ICON_WIDTH = 13;
65591
- css /* scss */ `
65592
- .o-dv-list-icon {
65593
- color: ${TEXT_BODY_MUTED};
65594
- border-radius: 1px;
65595
- height: ${GRID_ICON_EDGE_LENGTH}px;
65596
- width: ${GRID_ICON_EDGE_LENGTH}px;
65597
-
65598
- &:hover {
65599
- color: #ffffff;
65600
- background-color: ${TEXT_BODY_MUTED};
65601
- }
65602
-
65603
- svg {
65604
- width: ${ICON_WIDTH}px;
65605
- height: ${ICON_WIDTH}px;
65606
- }
65607
- }
65608
- `;
65609
- class DataValidationListIcon extends owl.Component {
65610
- static template = "o-spreadsheet-DataValidationListIcon";
65611
- static props = {
65612
- cellPosition: Object,
65613
- };
65614
- onClick() {
65615
- const { col, row } = this.props.cellPosition;
65616
- this.env.model.selection.selectCell(col, row);
65617
- this.env.startCellEdition();
65618
- }
65619
- }
65620
-
65621
- css /* scss */ `
65622
- .o-filter-icon {
65623
- color: ${FILTERS_COLOR};
65624
- display: flex;
65625
- align-items: center;
65626
- justify-content: center;
65627
- width: ${GRID_ICON_EDGE_LENGTH}px;
65628
- height: ${GRID_ICON_EDGE_LENGTH}px;
65629
-
65630
- &:hover {
65631
- background: ${FILTERS_COLOR};
65632
- color: #fff;
65633
- }
65634
-
65635
- &.o-high-contrast {
65636
- color: #defade;
65637
- }
65638
- &.o-high-contrast:hover {
65639
- color: ${FILTERS_COLOR};
65640
- background: #fff;
65641
- }
65642
- }
65643
- .o-filter-icon:hover {
65644
- background: ${FILTERS_COLOR};
65645
- color: #fff;
65646
- }
65647
- `;
65648
- class FilterIcon extends owl.Component {
65649
- static template = "o-spreadsheet-FilterIcon";
65650
- static props = {
65651
- cellPosition: Object,
65652
- };
65653
- cellPopovers;
65654
- setup() {
65655
- this.cellPopovers = useStore(CellPopoverStore);
65656
- }
65657
- onClick() {
65658
- const position = this.props.cellPosition;
65659
- const activePopover = this.cellPopovers.persistentCellPopover;
65660
- const { col, row } = position;
65661
- if (activePopover.isOpen &&
65662
- activePopover.col === col &&
65663
- activePopover.row === row &&
65664
- activePopover.type === "FilterMenu") {
65665
- this.cellPopovers.close();
65666
- return;
65667
- }
65668
- this.cellPopovers.open({ col, row }, "FilterMenu");
65669
- }
65670
- get isFilterActive() {
65671
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
65672
- }
65673
- get iconClass() {
65674
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
65675
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
65676
- return luminance < 0.45 ? "o-high-contrast" : "";
65677
- }
65678
- }
65679
-
65680
- css /* scss */ `
65681
- .o-spreadsheet {
65682
- .o-pivot-collapse-icon {
65683
- cursor: pointer;
65684
- width: 11px;
65685
- height: 11px;
65686
- border: 1px solid #777;
65687
- background-color: #eee;
65688
- margin: 3px 0 3px 6px;
65689
-
65690
- .o-icon {
65691
- width: 5px;
65692
- height: 5px;
65693
- }
65694
- }
65695
- }
65696
- `;
65697
- class PivotCollapseIcon extends owl.Component {
65698
- static template = "o-spreadsheet-PivotCollapseIcon";
65699
- static props = {
65700
- cellPosition: Object,
65701
- };
65702
- onClick() {
65703
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65704
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65705
- if (!pivotId || pivotCell.type !== "HEADER") {
65706
- return;
65707
- }
65708
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65709
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
65710
- ? [...definition.collapsedDomains[pivotCell.dimension]]
65711
- : [];
65712
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
65713
- if (index !== -1) {
65714
- collapsedDomains.splice(index, 1);
65715
- }
65716
- else {
65717
- collapsedDomains.push(pivotCell.domain);
65718
- }
65719
- const newDomains = definition.collapsedDomains
65720
- ? { ...definition.collapsedDomains }
65721
- : { COL: [], ROW: [] };
65722
- newDomains[pivotCell.dimension] = collapsedDomains;
65723
- this.env.model.dispatch("UPDATE_PIVOT", {
65724
- pivotId,
65725
- pivot: { ...definition, collapsedDomains: newDomains },
65726
- });
65727
- }
65728
- get isCollapsed() {
65729
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65730
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65731
- if (!pivotId || pivotCell.type !== "HEADER") {
65732
- return false;
65733
- }
65734
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65735
- const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
65736
- return domains?.some((domain) => deepEquals(domain, pivotCell.domain));
65737
- }
65738
- }
65739
-
65740
65958
  /**
65741
65959
  * Registry to draw icons on cells
65742
65960
  */
@@ -65744,14 +65962,25 @@ const iconsOnCellRegistry = new Registry();
65744
65962
  iconsOnCellRegistry.add("data_validation_checkbox", (getters, position) => {
65745
65963
  const hasIcon = getters.isCellValidCheckbox(position);
65746
65964
  if (hasIcon) {
65965
+ const value = !!getters.getEvaluatedCell(position).value;
65747
65966
  return {
65748
- svg: undefined,
65967
+ svg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED,
65968
+ hoverSvg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED_HOVERED,
65749
65969
  priority: 2,
65750
65970
  horizontalAlign: "center",
65751
65971
  size: GRID_ICON_EDGE_LENGTH,
65752
65972
  margin: GRID_ICON_MARGIN,
65753
- component: DataValidationCheckbox,
65754
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
+ },
65755
65984
  };
65756
65985
  }
65757
65986
  return undefined;
@@ -65760,13 +65989,19 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65760
65989
  const hasIcon = !getters.isReadonly() && getters.cellHasListDataValidationIcon(position);
65761
65990
  if (hasIcon) {
65762
65991
  return {
65763
- svg: undefined,
65992
+ svg: CARET_DOWN,
65993
+ hoverSvg: HOVERED_CARET_DOWN,
65764
65994
  priority: 2,
65765
65995
  horizontalAlign: "right",
65766
65996
  size: GRID_ICON_EDGE_LENGTH,
65767
65997
  margin: GRID_ICON_MARGIN,
65768
- component: DataValidationListIcon,
65769
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",
65770
66005
  };
65771
66006
  }
65772
66007
  return undefined;
@@ -65774,14 +66009,30 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65774
66009
  iconsOnCellRegistry.add("filter_icon", (getters, position) => {
65775
66010
  const hasIcon = getters.isFilterHeader(position);
65776
66011
  if (hasIcon) {
66012
+ const isFilterActive = getters.isFilterActive(position);
66013
+ const cellStyle = getters.getCellComputedStyle(position);
66014
+ const isHighContrast = relativeLuminance(cellStyle.fillColor || "#fff") < 0.45;
65777
66015
  return {
65778
- svg: undefined,
66016
+ type: "filter_icon",
66017
+ svg: getDataFilterIcon(isFilterActive, isHighContrast, false),
66018
+ hoverSvg: getDataFilterIcon(isFilterActive, isHighContrast, true),
65779
66019
  priority: 3,
65780
66020
  horizontalAlign: "right",
65781
66021
  size: GRID_ICON_EDGE_LENGTH,
65782
66022
  margin: GRID_ICON_MARGIN,
65783
- component: FilterIcon,
65784
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
+ },
65785
66036
  };
65786
66037
  }
65787
66038
  return undefined;
@@ -65791,6 +66042,7 @@ iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
65791
66042
  if (icon) {
65792
66043
  const style = getters.getCellStyle(position);
65793
66044
  return {
66045
+ type: "conditional_formatting",
65794
66046
  svg: ICONS[icon].svg,
65795
66047
  priority: 1,
65796
66048
  horizontalAlign: "left",
@@ -65811,23 +66063,55 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
65811
66063
  const definition = getters.getPivotCoreDefinition(pivotId);
65812
66064
  const isDashboard = getters.isDashboard();
65813
66065
  const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
65814
- 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;
65815
66070
  return {
66071
+ type: "pivot_collapse",
65816
66072
  priority: 4,
65817
66073
  horizontalAlign: "left",
65818
- size: !!component || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
65819
- ? GRID_ICON_EDGE_LENGTH
66074
+ size: hasIcon || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
66075
+ ? PIVOT_COLLAPSE_ICON_SIZE
65820
66076
  : 0,
65821
- margin: pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0,
65822
- component,
66077
+ margin: hasIcon ? GRID_ICON_MARGIN * 2 + indent : indent,
66078
+ svg: hasIcon ? getPivotIconSvg(isCollapsed, false) : undefined,
66079
+ hoverSvg: hasIcon ? getPivotIconSvg(isCollapsed, true) : undefined,
65823
66080
  position,
66081
+ onClick: togglePivotCollapse,
65824
66082
  };
65825
66083
  }
65826
66084
  return undefined;
65827
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
+ }
65828
66112
 
65829
66113
  class CellIconPlugin extends CoreViewPlugin {
65830
- static getters = ["doesCellHaveGridIcon", "getCellIcons"];
66114
+ static getters = ["doesCellHaveGridIcon", "getCellIcons", "getCellIconRect"];
65831
66115
  cellIconsCache = {};
65832
66116
  handle(cmd) {
65833
66117
  if (cmd.type !== "SET_VIEWPORT_OFFSET") {
@@ -65847,6 +66131,29 @@ class CellIconPlugin extends CoreViewPlugin {
65847
66131
  }
65848
66132
  return this.cellIconsCache[position.sheetId][position.col][position.row];
65849
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
+ }
65850
66157
  computeCellIcons(position) {
65851
66158
  const icons = { left: undefined, right: undefined, center: undefined };
65852
66159
  const callbacks = iconsOnCellRegistry.getAll();
@@ -70073,6 +70380,7 @@ class SheetUIPlugin extends UIPlugin {
70073
70380
  "getCellText",
70074
70381
  "getCellMultiLineText",
70075
70382
  "getContiguousZone",
70383
+ "computeTextYCoordinate",
70076
70384
  ];
70077
70385
  ctx = document.createElement("canvas").getContext("2d");
70078
70386
  // ---------------------------------------------------------------------------
@@ -70175,6 +70483,25 @@ class SheetUIPlugin extends UIPlugin {
70175
70483
  });
70176
70484
  return splitTextToWidth(this.ctx, text, style, args.wrapText ? args.maxWidth : undefined);
70177
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
+ }
70178
70505
  /**
70179
70506
  * Expands the given zone until bordered by empty cells or reached the sheet boundaries.
70180
70507
  */
@@ -72200,6 +72527,7 @@ class GridSelectionPlugin extends UIPlugin {
72200
72527
  "getElementsFromSelection",
72201
72528
  "tryGetActiveSheetId",
72202
72529
  "isGridSelectionActive",
72530
+ "getSelectecUnboundedZone",
72203
72531
  ];
72204
72532
  gridSelection = {
72205
72533
  anchor: {
@@ -72211,12 +72539,14 @@ class GridSelectionPlugin extends UIPlugin {
72211
72539
  selectedFigureId = null;
72212
72540
  sheetsData = {};
72213
72541
  moveClient;
72542
+ isUnbounded;
72214
72543
  // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
72215
72544
  // the main command.
72216
72545
  activeSheet = null;
72217
72546
  constructor(config) {
72218
72547
  super(config);
72219
72548
  this.moveClient = config.moveClient;
72549
+ this.isUnbounded = false;
72220
72550
  }
72221
72551
  // ---------------------------------------------------------------------------
72222
72552
  // Command Handling
@@ -72242,6 +72572,7 @@ class GridSelectionPlugin extends UIPlugin {
72242
72572
  handleEvent(event) {
72243
72573
  const anchor = event.anchor;
72244
72574
  let zones = [];
72575
+ this.isUnbounded = event.options?.unbounded || false;
72245
72576
  switch (event.mode) {
72246
72577
  case "overrideSelection":
72247
72578
  zones = [anchor.zone];
@@ -72431,6 +72762,12 @@ class GridSelectionPlugin extends UIPlugin {
72431
72762
  getSelectedZone() {
72432
72763
  return deepCopy(this.gridSelection.anchor.zone);
72433
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
+ }
72434
72771
  getSelection() {
72435
72772
  return deepCopy(this.gridSelection);
72436
72773
  }
@@ -72624,11 +72961,6 @@ class GridSelectionPlugin extends UIPlugin {
72624
72961
  },
72625
72962
  ];
72626
72963
  const sheetId = this.getActiveSheetId();
72627
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
72628
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
72629
- if (!data) {
72630
- return;
72631
- }
72632
72964
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
72633
72965
  const pasteTarget = [
72634
72966
  {
@@ -72638,7 +72970,14 @@ class GridSelectionPlugin extends UIPlugin {
72638
72970
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
72639
72971
  },
72640
72972
  ];
72641
- 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
+ }
72642
72981
  const selection = pasteTarget[0];
72643
72982
  const col = selection.left;
72644
72983
  const row = selection.top;
@@ -73184,6 +73523,7 @@ class SheetViewPlugin extends UIPlugin {
73184
73523
  "getRect",
73185
73524
  "getFigureUI",
73186
73525
  "getPositionAnchorOffset",
73526
+ "getGridOffset",
73187
73527
  ];
73188
73528
  viewports = {};
73189
73529
  /**
@@ -73373,6 +73713,9 @@ class SheetViewPlugin extends UIPlugin {
73373
73713
  height: this.sheetViewHeight,
73374
73714
  };
73375
73715
  }
73716
+ getGridOffset() {
73717
+ return { x: this.gridOffsetX, y: this.gridOffsetY };
73718
+ }
73376
73719
  /** type as pane, not viewport but basically pane extends viewport */
73377
73720
  getActiveMainViewport() {
73378
73721
  const sheetId = this.getters.getActiveSheetId();
@@ -74751,6 +75094,7 @@ topbarMenuRegistry
74751
75094
  name: _t("Settings"),
74752
75095
  sequence: 100,
74753
75096
  execute: (env) => env.openSidePanel("Settings"),
75097
+ isEnabled: (env) => !env.isSmall,
74754
75098
  icon: "o-spreadsheet-Icon.COG",
74755
75099
  })
74756
75100
  // ---------------------------------------------------------------------
@@ -75168,6 +75512,7 @@ topbarMenuRegistry
75168
75512
  execute: (env) => {
75169
75513
  env.openSidePanel("DataValidation");
75170
75514
  },
75515
+ isEnabled: (env) => !env.isSmall,
75171
75516
  icon: "o-spreadsheet-Icon.DATA_VALIDATION",
75172
75517
  sequence: 30,
75173
75518
  separator: true,
@@ -75191,6 +75536,7 @@ topbarMenuRegistry
75191
75536
  sequence: sequence + index,
75192
75537
  isReadonlyAllowed: true,
75193
75538
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
75539
+ isEnabled: (env) => !env.isSmall,
75194
75540
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
75195
75541
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
75196
75542
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -75462,7 +75808,7 @@ css /* scss */ `
75462
75808
  .o-sheet {
75463
75809
  padding: 0 15px;
75464
75810
  padding-right: 10px;
75465
- height: ${BOTTOMBAR_HEIGHT}px;
75811
+ height: ${DESKTOP_BOTTOMBAR_HEIGHT}px;
75466
75812
  border-left: 1px solid #c1c1c1;
75467
75813
  border-right: 1px solid #c1c1c1;
75468
75814
  margin-left: -1px;
@@ -75506,6 +75852,10 @@ css /* scss */ `
75506
75852
  width: calc(100% - 1px);
75507
75853
  }
75508
75854
  }
75855
+
75856
+ .o-spreadshet-mobile .o-sheet {
75857
+ height: ${MOBILE_BOTTOMBAR_HEIGHT}px;
75858
+ }
75509
75859
  `;
75510
75860
  class BottomBarSheet extends owl.Component {
75511
75861
  static template = "o-spreadsheet-BottomBarSheet";
@@ -75560,7 +75910,16 @@ class BottomBarSheet extends owl.Component {
75560
75910
  this.stopEdition();
75561
75911
  }
75562
75912
  }
75913
+ onClick() {
75914
+ if (!this.env.isMobile()) {
75915
+ return;
75916
+ }
75917
+ this.activateSheet();
75918
+ }
75563
75919
  onMouseDown(ev) {
75920
+ if (this.env.isMobile()) {
75921
+ return;
75922
+ }
75564
75923
  this.activateSheet();
75565
75924
  this.props.onMouseDown(ev);
75566
75925
  }
@@ -75904,8 +76263,8 @@ css /* scss */ `
75904
76263
  }
75905
76264
  }
75906
76265
 
75907
- .mobile.o-spreadsheet-bottom-bar {
75908
- padding-left: 1rem;
76266
+ .o-spreadsheet-mobile .o-spreadsheet-bottom-bar {
76267
+ padding-left: 0;
75909
76268
 
75910
76269
  .add-sheet-container {
75911
76270
  order: 2;
@@ -75922,10 +76281,8 @@ css /* scss */ `
75922
76281
  `;
75923
76282
  class BottomBar extends owl.Component {
75924
76283
  static template = "o-spreadsheet-BottomBar";
75925
- static props = {
75926
- onClick: Function,
75927
- };
75928
- static components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
76284
+ static props = { onClick: Function };
76285
+ static components = { MenuPopover, Ripple, BottomBarSheet, BottomBarStatistic };
75929
76286
  bottomBarRef = owl.useRef("bottomBar");
75930
76287
  sheetListRef = owl.useRef("sheetList");
75931
76288
  dragAndDrop = useDragAndDropListItems();
@@ -76062,6 +76419,9 @@ class BottomBar extends owl.Component {
76062
76419
  if (event.button !== 0 || this.env.model.getters.isReadonly())
76063
76420
  return;
76064
76421
  this.closeMenu();
76422
+ if (this.env.isMobile()) {
76423
+ return;
76424
+ }
76065
76425
  const visibleSheets = this.getVisibleSheets();
76066
76426
  const sheetRects = this.getSheetItemRects();
76067
76427
  const sheets = visibleSheets.map((sheet, index) => ({
@@ -76181,7 +76541,7 @@ css /* scss */ `
76181
76541
  `;
76182
76542
  class SpreadsheetDashboard extends owl.Component {
76183
76543
  static template = "o-spreadsheet-SpreadsheetDashboard";
76184
- static props = {};
76544
+ static props = { getGridSize: Function };
76185
76545
  static components = {
76186
76546
  GridOverlay,
76187
76547
  GridPopover,
@@ -76468,7 +76828,7 @@ class HeaderGroupContainer extends owl.Component {
76468
76828
  dimension: String,
76469
76829
  layers: Array,
76470
76830
  };
76471
- static components = { RowGroup, ColGroup, Menu };
76831
+ static components = { RowGroup, ColGroup, MenuPopover };
76472
76832
  menu = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
76473
76833
  getLayerOffset(layerIndex) {
76474
76834
  return layerIndex * GROUP_LAYER_WIDTH;
@@ -76684,6 +77044,158 @@ class SidePanel extends owl.Component {
76684
77044
  }
76685
77045
  }
76686
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
+
76687
77199
  const COMPOSER_MAX_HEIGHT = 100;
76688
77200
  /* svg free of use from https://uxwing.com/formula-fx-icon/ */
76689
77201
  const FX_SVG = /*xml*/ `
@@ -76693,7 +77205,7 @@ const FX_SVG = /*xml*/ `
76693
77205
  `;
76694
77206
  css /* scss */ `
76695
77207
  .o-topbar-composer-container {
76696
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
77208
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
76697
77209
  }
76698
77210
 
76699
77211
  .o-topbar-composer {
@@ -76748,7 +77260,7 @@ class TopBarComposer extends owl.Component {
76748
77260
  "max-height": `${COMPOSER_MAX_HEIGHT}px`,
76749
77261
  "line-height": "24px",
76750
77262
  };
76751
- style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
77263
+ style.height = this.focus === "inactive" ? `${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
76752
77264
  return cssPropertiesToCss(style);
76753
77265
  }
76754
77266
  get containerStyle() {
@@ -77251,7 +77763,7 @@ class TopBarFontSizeEditor extends owl.Component {
77251
77763
 
77252
77764
  class NumberFormatsTool extends owl.Component {
77253
77765
  static template = "o-spreadsheet-NumberFormatsTool";
77254
- static components = { Menu, ActionButton };
77766
+ static components = { MenuPopover, ActionButton };
77255
77767
  static props = { class: String };
77256
77768
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
77257
77769
  topBarToolStore;
@@ -77301,7 +77813,7 @@ topBarToolBarRegistry
77301
77813
  .addChild("edit", {
77302
77814
  component: PaintFormatButton,
77303
77815
  props: {
77304
- class: "o-hoverable-button o-toolbar-button",
77816
+ class: "o-hoverable-button o-toolbar-button o-mobile-disabled",
77305
77817
  },
77306
77818
  sequence: 3,
77307
77819
  })
@@ -77460,7 +77972,7 @@ topBarToolBarRegistry
77460
77972
  .add("misc")
77461
77973
  .addChild("misc", {
77462
77974
  component: TableDropdownButton,
77463
- 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" },
77464
77976
  sequence: 1,
77465
77977
  })
77466
77978
  .addChild("misc", {
@@ -77480,6 +77992,7 @@ css /* scss */ `
77480
77992
  border-right: 1px solid ${SEPARATOR_COLOR};
77481
77993
  width: 0;
77482
77994
  margin: 0 6px;
77995
+ height: 30px;
77483
77996
  }
77484
77997
 
77485
77998
  .o-toolbar-button {
@@ -77512,7 +78025,7 @@ css /* scss */ `
77512
78025
 
77513
78026
  .irregularity-map {
77514
78027
  border-top: 1px solid ${SEPARATOR_COLOR};
77515
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78028
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77516
78029
 
77517
78030
  .alert-info {
77518
78031
  border-left: 3px solid ${ALERT_INFO_BORDER};
@@ -77525,7 +78038,7 @@ css /* scss */ `
77525
78038
 
77526
78039
  /* Toolbar */
77527
78040
  .o-topbar-toolbar {
77528
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78041
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77529
78042
 
77530
78043
  .o-readonly-toolbar {
77531
78044
  background-color: ${BACKGROUND_HEADER_COLOR};
@@ -77539,6 +78052,24 @@ css /* scss */ `
77539
78052
  }
77540
78053
  }
77541
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
+ }
77542
78073
  `;
77543
78074
  class TopBar extends owl.Component {
77544
78075
  static template = "o-spreadsheet-TopBar";
@@ -77547,7 +78078,7 @@ class TopBar extends owl.Component {
77547
78078
  dropdownMaxHeight: Number,
77548
78079
  };
77549
78080
  static components = {
77550
- Menu,
78081
+ MenuPopover,
77551
78082
  TopBarComposer,
77552
78083
  Popover,
77553
78084
  };
@@ -77627,7 +78158,7 @@ class TopBar extends owl.Component {
77627
78158
  // TODO : manage click events better. We need this piece of code
77628
78159
  // otherwise the event opening the menu would close it on the same frame.
77629
78160
  // And we cannot stop the event propagation because it's used in an
77630
- // 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
77631
78162
  // clicking on the top bar
77632
78163
  if (this.openedEl === ev.target) {
77633
78164
  return;
@@ -78053,6 +78584,11 @@ css /* scss */ `
78053
78584
  color: ${TEXT_BODY};
78054
78585
  }
78055
78586
  }
78587
+
78588
+ .o-spreadsheet-topbar-wrapper,
78589
+ .o-spreadsheet-bottombar-wrapper {
78590
+ z-index: ${ComponentsImportance.ScrollBar + 1};
78591
+ }
78056
78592
  `;
78057
78593
  class Spreadsheet extends owl.Component {
78058
78594
  static template = "o-spreadsheet-Spreadsheet";
@@ -78066,6 +78602,7 @@ class Spreadsheet extends owl.Component {
78066
78602
  TopBar,
78067
78603
  Grid,
78068
78604
  BottomBar,
78605
+ SmallBottomBar,
78069
78606
  SidePanel,
78070
78607
  SpreadsheetDashboard,
78071
78608
  HeaderGroupContainer,
@@ -78089,12 +78626,25 @@ class Spreadsheet extends owl.Component {
78089
78626
  else {
78090
78627
  properties["grid-template-rows"] = `min-content auto min-content`;
78091
78628
  }
78092
- 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}`;
78093
78631
  return cssPropertiesToCss(properties);
78094
78632
  }
78095
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
+ }
78096
78642
  const stores = useStoreProvider();
78097
78643
  stores.inject(ModelStore, this.model);
78644
+ const env = this.env;
78645
+ stores.get(ScreenWidthStore).setSmallThreshhold(() => {
78646
+ return env.isSmall;
78647
+ });
78098
78648
  this.notificationStore = useStore(NotificationStore);
78099
78649
  this.composerFocusStore = useStore(ComposerFocusStore);
78100
78650
  this.sidePanel = useStore(SidePanelStore);
@@ -78112,15 +78662,8 @@ class Spreadsheet extends owl.Component {
78112
78662
  notifyUser: (notification) => this.notificationStore.notifyUser(notification),
78113
78663
  askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
78114
78664
  raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
78665
+ isMobile: isMobileOS,
78115
78666
  });
78116
- if (!("isSmall" in this.env)) {
78117
- const screenSize = useScreenWidth();
78118
- owl.useSubEnv({
78119
- get isSmall() {
78120
- return screenSize.isSmall;
78121
- },
78122
- });
78123
- }
78124
78667
  this.notificationStore.updateNotificationCallbacks({ ...this.props });
78125
78668
  owl.useEffect(() => {
78126
78669
  /**
@@ -78226,6 +78769,24 @@ class Spreadsheet extends owl.Component {
78226
78769
  const sheetId = this.env.model.getters.getActiveSheetId();
78227
78770
  return this.env.model.getters.getVisibleGroupLayers(sheetId, "COL");
78228
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
+ }
78229
78790
  }
78230
78791
 
78231
78792
  function inverseCommand(cmd) {
@@ -82488,7 +83049,7 @@ const SPREADSHEET_DIMENSIONS = {
82488
83049
  MIN_COL_WIDTH,
82489
83050
  HEADER_HEIGHT,
82490
83051
  HEADER_WIDTH,
82491
- BOTTOMBAR_HEIGHT,
83052
+ DESKTOP_BOTTOMBAR_HEIGHT,
82492
83053
  DEFAULT_CELL_WIDTH,
82493
83054
  DEFAULT_CELL_HEIGHT,
82494
83055
  SCROLLBAR_WIDTH,
@@ -82634,7 +83195,7 @@ const components = {
82634
83195
  FunnelChartDesignPanel,
82635
83196
  ChartTypePicker,
82636
83197
  FigureComponent,
82637
- Menu,
83198
+ MenuPopover,
82638
83199
  Popover,
82639
83200
  SelectionInput,
82640
83201
  ValidationMessages,
@@ -82745,6 +83306,6 @@ exports.tokenColors = tokenColors;
82745
83306
  exports.tokenize = tokenize;
82746
83307
 
82747
83308
 
82748
- __info__.version = "18.4.0-alpha.5";
82749
- __info__.date = "2025-05-26T12:36:36.693Z";
82750
- __info__.hash = "398e610";
83309
+ __info__.version = "18.4.0-alpha.7";
83310
+ __info__.date = "2025-06-06T09:32:44.285Z";
83311
+ __info__.hash = "2bfbe64";