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

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