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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.4.0-alpha.5
6
- * @date 2025-05-26T12:36:36.693Z
7
- * @hash 398e610
5
+ * @version 18.4.0-alpha.7
6
+ * @date 2025-06-06T09:32:44.285Z
7
+ * @hash 2bfbe64
8
8
  */
9
9
 
10
10
  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",
@@ -8532,9 +8536,10 @@ const AGGREGATOR_NAMES = {
8532
8536
  avg: _t("Average"),
8533
8537
  sum: _t("Sum"),
8534
8538
  };
8539
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8535
8540
  const AGGREGATORS_BY_FIELD_TYPE = {
8536
- integer: ["max", "min", "avg", "sum", "count_distinct", "count"],
8537
- char: ["count_distinct", "count"],
8541
+ integer: NUMBER_CHAR_AGGREGATORS,
8542
+ char: NUMBER_CHAR_AGGREGATORS,
8538
8543
  boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8539
8544
  datetime: ["max", "min", "count_distinct", "count"],
8540
8545
  };
@@ -20555,7 +20560,7 @@ const TEXT = {
20555
20560
  description: _t("Converts a number to text according to a specified format."),
20556
20561
  args: [
20557
20562
  arg("number (number)", _t("The number, date or time to format.")),
20558
- 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.')),
20559
20564
  ],
20560
20565
  compute: function (number, format) {
20561
20566
  const _number = toNumber(number, this.locale);
@@ -21662,8 +21667,6 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21662
21667
  if (isNaN(value)) {
21663
21668
  continue;
21664
21669
  }
21665
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
21666
- const displayValue = options.callback(Number(value), axisId);
21667
21670
  const point = dataset.data[i];
21668
21671
  const xPosition = point.x;
21669
21672
  let yPosition = 0;
@@ -21687,7 +21690,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21687
21690
  textsPositions[xPosition].push(yPosition);
21688
21691
  ctx.fillStyle = point.options.backgroundColor;
21689
21692
  ctx.strokeStyle = options.background || "#ffffff";
21690
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
21693
+ const valueToDisplay = options.callback(Number(value), dataset, i);
21694
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
21691
21695
  }
21692
21696
  }
21693
21697
  }
@@ -21704,7 +21708,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
21704
21708
  if (isNaN(value)) {
21705
21709
  continue;
21706
21710
  }
21707
- const displayValue = options.callback(value, dataset.xAxisID);
21711
+ const displayValue = options.callback(value, dataset, i);
21708
21712
  const point = dataset.data[i];
21709
21713
  const yPosition = point.y;
21710
21714
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -21742,7 +21746,7 @@ function drawPieChartValues(chart, options, ctx) {
21742
21746
  const y = bar.y + midRadius * Math.sin(midAngle) + 7;
21743
21747
  ctx.fillStyle = chartFontColor(options.background);
21744
21748
  ctx.strokeStyle = options.background || "#ffffff";
21745
- const displayValue = options.callback(value, "y");
21749
+ const displayValue = options.callback(value, dataset, i);
21746
21750
  drawTextWithBackground(displayValue, x, y, ctx);
21747
21751
  }
21748
21752
  }
@@ -23489,7 +23493,7 @@ function isMacOS() {
23489
23493
  * On Mac, this is the "meta" or "command" key.
23490
23494
  */
23491
23495
  function isCtrlKey(ev) {
23492
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
23496
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
23493
23497
  }
23494
23498
  /**
23495
23499
  * @param {MouseEvent} ev - The mouse event.
@@ -23528,6 +23532,23 @@ function downloadFile(dataUrl, fileName) {
23528
23532
  function isBrowserFirefox() {
23529
23533
  return /Firefox/i.test(navigator.userAgent);
23530
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
+ }
23531
23552
 
23532
23553
  /**
23533
23554
  * Convert a JS color hexadecimal to an excel compatible color.
@@ -25048,14 +25069,14 @@ function getPieChartLegend(definition, args) {
25048
25069
  ...getLegendDisplayOptions(definition),
25049
25070
  labels: {
25050
25071
  usePointStyle: true,
25051
- generateLabels: (c) => c.data.labels?.map((label, index) => ({
25072
+ generateLabels: (c) => (c.data.labels?.map((label, index) => ({
25052
25073
  text: truncateLabel(String(label)),
25053
25074
  strokeStyle: colors[index],
25054
25075
  fillStyle: colors[index],
25055
25076
  pointStyle: "rect",
25056
25077
  lineWidth: 2,
25057
25078
  fontColor,
25058
- })) || [],
25079
+ })) || []).filter((label) => label.text),
25059
25080
  filter: (legendItem, data) => {
25060
25081
  return "datasetIndex" in legendItem
25061
25082
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25215,7 +25236,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25215
25236
  labels: {
25216
25237
  color: fontColor,
25217
25238
  usePointStyle: true,
25218
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
25239
+ generateLabels: (chart) => chart.data.datasets
25240
+ .map((dataset, index) => {
25219
25241
  if (isTrendLineAxis(dataset["xAxisID"])) {
25220
25242
  return {
25221
25243
  text: truncateLabel(dataset.label),
@@ -25237,7 +25259,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25237
25259
  datasetIndex: index,
25238
25260
  ...legendLabelConfig,
25239
25261
  };
25240
- }),
25262
+ })
25263
+ .filter((label) => label.text),
25241
25264
  filter: (legendItem, data) => {
25242
25265
  return "datasetIndex" in legendItem
25243
25266
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25591,7 +25614,10 @@ function getChartShowValues(definition, args) {
25591
25614
  horizontal: "horizontal" in definition && definition.horizontal,
25592
25615
  showValues: "showValues" in definition ? !!definition.showValues : false,
25593
25616
  background: definition.background,
25594
- callback: formatChartDatasetValue(axisFormats, locale),
25617
+ callback: (value, dataset) => {
25618
+ const axisId = getDatasetAxisId(definition, dataset);
25619
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
25620
+ },
25595
25621
  };
25596
25622
  }
25597
25623
  function getSunburstShowValues(definition, args) {
@@ -25609,6 +25635,45 @@ function getSunburstShowValues(definition, args) {
25609
25635
  },
25610
25636
  };
25611
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
+ }
25612
25677
 
25613
25678
  function getChartTitle(definition) {
25614
25679
  const chartTitle = definition.title;
@@ -26011,6 +26076,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26011
26076
  getPieChartTooltip: getPieChartTooltip,
26012
26077
  getPyramidChartData: getPyramidChartData,
26013
26078
  getPyramidChartScales: getPyramidChartScales,
26079
+ getPyramidChartShowValues: getPyramidChartShowValues,
26014
26080
  getPyramidChartTooltip: getPyramidChartTooltip,
26015
26081
  getRadarChartData: getRadarChartData,
26016
26082
  getRadarChartDatasets: getRadarChartDatasets,
@@ -26031,6 +26097,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26031
26097
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
26032
26098
  getWaterfallChartLegend: getWaterfallChartLegend,
26033
26099
  getWaterfallChartScales: getWaterfallChartScales,
26100
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
26034
26101
  getWaterfallChartTooltip: getWaterfallChartTooltip,
26035
26102
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels,
26036
26103
  makeDatasetsCumulative: makeDatasetsCumulative
@@ -27343,7 +27410,7 @@ function createPyramidChartRuntime(chart, getters) {
27343
27410
  title: getChartTitle(definition),
27344
27411
  legend: getBarChartLegend(definition),
27345
27412
  tooltip: getPyramidChartTooltip(definition, chartData),
27346
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
27413
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
27347
27414
  },
27348
27415
  },
27349
27416
  };
@@ -28091,7 +28158,7 @@ function createWaterfallChartRuntime(chart, getters) {
28091
28158
  title: getChartTitle(definition),
28092
28159
  legend: getWaterfallChartLegend(definition),
28093
28160
  tooltip: getWaterfallChartTooltip(definition, chartData),
28094
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
28161
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
28095
28162
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
28096
28163
  },
28097
28164
  },
@@ -28883,6 +28950,7 @@ function getChartMenuActions(figureId, onFigureDeleted, env) {
28883
28950
  env.openSidePanel("ChartPanel");
28884
28951
  },
28885
28952
  icon: "o-spreadsheet-Icon.EDIT",
28953
+ isEnabled: (env) => !env.isSmall,
28886
28954
  },
28887
28955
  getCopyMenuItem(figureId, env),
28888
28956
  getCutMenuItem(figureId, env),
@@ -29097,6 +29165,147 @@ function useTimeOut() {
29097
29165
  };
29098
29166
  }
29099
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
+
29100
29309
  /**
29101
29310
  * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
29102
29311
  */
@@ -29125,6 +29334,9 @@ function zoneToRect(zone) {
29125
29334
  height: zone.bottom - zone.top,
29126
29335
  };
29127
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
+ }
29128
29340
 
29129
29341
  /**
29130
29342
  * Return the o-spreadsheet element position relative
@@ -29422,57 +29634,17 @@ class TopRightPopoverContext extends PopoverPositionContext {
29422
29634
  }
29423
29635
 
29424
29636
  //------------------------------------------------------------------------------
29425
- // Context Menu Component
29637
+ // Context MenuPopover Component
29426
29638
  //------------------------------------------------------------------------------
29427
29639
  css /* scss */ `
29428
- .o-menu {
29429
- background-color: white;
29640
+ .o-menu-wrapper {
29430
29641
  padding: ${MENU_VERTICAL_PADDING}px 0px;
29431
- width: ${MENU_WIDTH}px;
29432
- user-select: none;
29433
-
29434
- .o-menu-item {
29435
- height: ${MENU_ITEM_HEIGHT}px;
29436
- padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
29437
- cursor: pointer;
29438
- user-select: none;
29439
-
29440
- .o-menu-item-name {
29441
- min-width: 40%;
29442
- }
29443
-
29444
- .o-menu-item-icon {
29445
- display: inline-block;
29446
- margin: 0px 8px 0px 0px;
29447
- width: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29448
- line-height: ${MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29449
- }
29450
-
29451
- &:not(.disabled) {
29452
- &:hover,
29453
- &.o-menu-item-active {
29454
- background-color: ${BUTTON_ACTIVE_BG};
29455
- color: ${BUTTON_ACTIVE_TEXT_COLOR};
29456
- }
29457
- .o-menu-item-description {
29458
- color: grey;
29459
- }
29460
- .o-menu-item-icon {
29461
- .o-icon {
29462
- color: ${ICONS_COLOR};
29463
- }
29464
- }
29465
- }
29466
- &.disabled {
29467
- color: ${DISABLED_TEXT_COLOR};
29468
- cursor: not-allowed;
29469
- }
29470
- }
29642
+ background-color: white;
29471
29643
  }
29472
29644
  `;
29473
29645
  const TIMEOUT_DELAY = 250;
29474
- class Menu extends Component {
29475
- static template = "o-spreadsheet-Menu";
29646
+ class MenuPopover extends Component {
29647
+ static template = "o-spreadsheet-Menu-Popover";
29476
29648
  static props = {
29477
29649
  anchorRect: Object,
29478
29650
  popoverPositioning: { type: String, optional: true },
@@ -29485,7 +29657,7 @@ class Menu extends Component {
29485
29657
  onMouseOver: { type: Function, optional: true },
29486
29658
  width: { type: Number, optional: true },
29487
29659
  };
29488
- static components = { Menu, Popover };
29660
+ static components = { MenuPopover, Menu, Popover };
29489
29661
  static defaultProps = {
29490
29662
  depth: 1,
29491
29663
  popoverPositioning: "top-right",
@@ -29512,27 +29684,18 @@ class Menu extends Component {
29512
29684
  this.hoveredMenu?.onStopHover?.(this.env);
29513
29685
  });
29514
29686
  }
29515
- get menuItemsAndSeparators() {
29516
- const menuItemsAndSeparators = [];
29517
- for (let i = 0; i < this.props.menuItems.length; i++) {
29518
- const menuItem = this.props.menuItems[i];
29519
- if (menuItem.isVisible(this.env)) {
29520
- menuItemsAndSeparators.push(menuItem);
29521
- }
29522
- if (menuItem.separator &&
29523
- i !== this.props.menuItems.length - 1 && // no separator at the end
29524
- menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
29525
- ) {
29526
- menuItemsAndSeparators.push("separator");
29527
- }
29528
- }
29529
- if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
29530
- menuItemsAndSeparators.pop();
29531
- }
29532
- if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
29533
- return [];
29534
- }
29535
- 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
+ };
29536
29699
  }
29537
29700
  get subMenuAnchorRect() {
29538
29701
  const anchorRect = Object.assign({}, this.subMenu.anchorRect);
@@ -29546,12 +29709,13 @@ class Menu extends Component {
29546
29709
  x: this.props.anchorRect.x,
29547
29710
  y: this.props.anchorRect.y,
29548
29711
  width: isRoot ? this.props.anchorRect.width : this.props.width || MENU_WIDTH,
29549
- height: isRoot ? this.props.anchorRect.height : MENU_ITEM_HEIGHT,
29712
+ height: isRoot ? this.props.anchorRect.height : DESKTOP_MENU_ITEM_HEIGHT,
29550
29713
  },
29551
29714
  positioning: this.props.popoverPositioning,
29552
29715
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
29553
29716
  onPopoverHidden: () => this.closeSubMenu(),
29554
29717
  onPopoverMoved: () => this.closeSubMenu(),
29718
+ maxHeight: this.props.maxHeight,
29555
29719
  };
29556
29720
  }
29557
29721
  get childrenHaveIcon() {
@@ -29621,7 +29785,7 @@ class Menu extends Component {
29621
29785
  x: getRefBoundingRect(this.menuRef).x,
29622
29786
  y: y - (this.subMenu.scrollOffset || 0),
29623
29787
  width: this.props.width || MENU_WIDTH,
29624
- height: MENU_ITEM_HEIGHT,
29788
+ height: DESKTOP_MENU_ITEM_HEIGHT,
29625
29789
  };
29626
29790
  this.subMenu.menuItems = menu.children(this.env);
29627
29791
  this.subMenu.isOpen = true;
@@ -29670,14 +29834,8 @@ class Menu extends Component {
29670
29834
  this.subMenu.isHoveringChild = true;
29671
29835
  this.openingTimeOut.clear();
29672
29836
  }
29673
- onMouseEnter(menu, ev) {
29674
- this.hoveredMenu = menu;
29675
- menu.onStartHover?.(this.env);
29676
- }
29677
29837
  onMouseLeave(menu) {
29678
29838
  this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
29679
- this.hoveredMenu = undefined;
29680
- menu.onStopHover?.(this.env);
29681
29839
  }
29682
29840
  get menuStyle() {
29683
29841
  return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
@@ -29686,7 +29844,7 @@ class Menu extends Component {
29686
29844
 
29687
29845
  class ChartDashboardMenu extends Component {
29688
29846
  static template = "spreadsheet.ChartDashboardMenu";
29689
- static components = { Menu };
29847
+ static components = { MenuPopover };
29690
29848
  static props = { figureUI: Object };
29691
29849
  originalChartDefinition;
29692
29850
  fullScreenFigureStore;
@@ -29950,7 +30108,7 @@ class FigureComponent extends Component {
29950
30108
  onMouseDown: { type: Function, optional: true },
29951
30109
  onClickAnchor: { type: Function, optional: true },
29952
30110
  };
29953
- static components = { Menu };
30111
+ static components = { MenuPopover };
29954
30112
  static defaultProps = {
29955
30113
  onFigureDeleted: () => { },
29956
30114
  onMouseDown: () => { },
@@ -30037,7 +30195,14 @@ class FigureComponent extends Component {
30037
30195
  this.props.onClickAnchor(dirX, dirY, ev);
30038
30196
  }
30039
30197
  onMouseDown(ev) {
30040
- 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
+ }
30041
30206
  }
30042
30207
  onKeyDown(ev) {
30043
30208
  const keyDownShortcut = keyboardEventToShortcutString(ev);
@@ -31504,15 +31669,15 @@ class HighlightStore extends SpreadsheetStore {
31504
31669
  const activeSheetId = this.getters.getActiveSheetId();
31505
31670
  return this.providers
31506
31671
  .flatMap((h) => h.highlights)
31507
- .filter((h) => h.sheetId === activeSheetId)
31672
+ .filter((h) => h.range.sheetId === activeSheetId)
31508
31673
  .map((highlight) => {
31509
- const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
31674
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.range.zone);
31510
31675
  const zone = numberOfRows * numberOfCols === 1
31511
- ? this.getters.expandZone(highlight.sheetId, highlight.zone)
31512
- : highlight.zone;
31676
+ ? this.getters.expandZone(highlight.range.sheetId, highlight.range.zone)
31677
+ : highlight.range.unboundedZone;
31513
31678
  return {
31514
31679
  ...highlight,
31515
- zone,
31680
+ range: this.model.getters.getRangeFromZone(highlight.range.sheetId, zone),
31516
31681
  };
31517
31682
  });
31518
31683
  }
@@ -31525,7 +31690,7 @@ class HighlightStore extends SpreadsheetStore {
31525
31690
  drawLayer(ctx, layer) {
31526
31691
  if (layer === "Highlights") {
31527
31692
  for (const highlight of this.highlights) {
31528
- const rect = this.getters.getVisibleRect(highlight.zone);
31693
+ const rect = this.getters.getVisibleRect(highlight.range.zone);
31529
31694
  drawHighlight(ctx, highlight, rect);
31530
31695
  }
31531
31696
  }
@@ -32153,12 +32318,12 @@ class AbstractComposerStore extends SpreadsheetStore {
32153
32318
  rangeColor(xc, sheetName) {
32154
32319
  const refSheet = sheetName ? this.model.getters.getSheetIdByName(sheetName) : this.sheetId;
32155
32320
  const highlight = this.highlights.find((highlight) => {
32156
- if (highlight.sheetId !== refSheet)
32321
+ if (highlight.range.sheetId !== refSheet)
32157
32322
  return false;
32158
32323
  const range = this.model.getters.getRangeFromSheetXC(refSheet, xc);
32159
32324
  let zone = range.zone;
32160
32325
  zone = getZoneArea(zone) === 1 ? this.model.getters.expandZone(refSheet, zone) : zone;
32161
- return isEqual(zone, highlight.zone);
32326
+ return isEqual(zone, highlight.range.zone);
32162
32327
  });
32163
32328
  return highlight && highlight.color ? highlight.color : undefined;
32164
32329
  }
@@ -32268,11 +32433,10 @@ class AbstractComposerStore extends SpreadsheetStore {
32268
32433
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
32269
32434
  const zone = numberOfRows * numberOfCols === 1
32270
32435
  ? this.getters.expandZone(range.sheetId, range.zone)
32271
- : range.zone;
32436
+ : range.unboundedZone;
32272
32437
  return {
32273
- zone,
32438
+ range: this.model.getters.getRangeFromZone(range.sheetId, zone),
32274
32439
  color: rangeColor(rangeString),
32275
- sheetId: range.sheetId,
32276
32440
  interactive: true,
32277
32441
  };
32278
32442
  });
@@ -32525,11 +32689,15 @@ class Composer extends Component {
32525
32689
  onInputContextMenu: { type: Function, optional: true },
32526
32690
  composerStore: Object,
32527
32691
  placeholder: { type: String, optional: true },
32692
+ inputMode: { type: String, optional: true },
32693
+ showAssistant: { type: Boolean, optional: true },
32528
32694
  };
32529
32695
  static components = { TextValueProvider, FunctionDescriptionProvider, SpeechBubble };
32530
32696
  static defaultProps = {
32531
32697
  inputStyle: "",
32532
32698
  isDefaultFocus: false,
32699
+ inputMode: "text",
32700
+ showAssistant: true,
32533
32701
  };
32534
32702
  DOMFocusableElementStore;
32535
32703
  composerRef = useRef("o_composer");
@@ -32580,8 +32748,7 @@ class Composer extends Component {
32580
32748
  assistantStyle["max-height"] = `${availableSpaceAbove - CLOSE_ICON_RADIUS}px`;
32581
32749
  // render top
32582
32750
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
32583
- assistantStyle.top = `-3px`;
32584
- assistantStyle.transform = `translate(0, -100%)`;
32751
+ assistantStyle.transform = `translate(0, calc(-100% - ${cellHeight + 3}px))`;
32585
32752
  }
32586
32753
  if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
32587
32754
  // render left
@@ -32860,6 +33027,9 @@ class Composer extends Component {
32860
33027
  // not main button, probably a context menu
32861
33028
  return;
32862
33029
  }
33030
+ if (this.env.isMobile() && !isIOS()) {
33031
+ return;
33032
+ }
32863
33033
  this.contentHelper.removeSelection();
32864
33034
  }
32865
33035
  onMouseup() {
@@ -33483,7 +33653,7 @@ class ListCriterionForm extends CriterionForm {
33483
33653
  */
33484
33654
  function startDnd(onPointerMove, onPointerUp) {
33485
33655
  const removeListeners = () => {
33486
- window.removeEventListener("pointerup", _onPointerUp);
33656
+ window.removeEventListener("pointerup", _onPointerUp, { capture: true });
33487
33657
  window.removeEventListener("dragstart", _onDragStart);
33488
33658
  window.removeEventListener("pointermove", onPointerMove);
33489
33659
  window.removeEventListener("wheel", onPointerMove);
@@ -33495,7 +33665,7 @@ function startDnd(onPointerMove, onPointerUp) {
33495
33665
  function _onDragStart(ev) {
33496
33666
  ev.preventDefault();
33497
33667
  }
33498
- window.addEventListener("pointerup", _onPointerUp);
33668
+ window.addEventListener("pointerup", _onPointerUp, { capture: true });
33499
33669
  window.addEventListener("dragstart", _onDragStart);
33500
33670
  window.addEventListener("pointermove", onPointerMove);
33501
33671
  // mouse wheel on window is by default a passive event.
@@ -34117,9 +34287,9 @@ class SelectionInputStore extends SpreadsheetStore {
34117
34287
  .filter((reference) => this.shouldBeHighlighted(this.inputSheetId, reference));
34118
34288
  return XCs.map((xc) => {
34119
34289
  const { sheetName } = splitReference(xc);
34290
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId;
34120
34291
  return {
34121
- zone: this.getters.getRangeFromSheetXC(this.inputSheetId, xc).zone,
34122
- sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId,
34292
+ range: this.getters.getRangeFromSheetXC(sheetId, xc),
34123
34293
  color,
34124
34294
  interactive: true,
34125
34295
  };
@@ -34588,7 +34758,7 @@ function getCriterionMenuItems(callback, availableTypes) {
34588
34758
  return createActions(actionSpecs);
34589
34759
  }
34590
34760
 
34591
- /** 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 */
34592
34762
  class SelectMenu extends Component {
34593
34763
  static template = "o-spreadsheet-SelectMenu";
34594
34764
  static props = {
@@ -34596,7 +34766,7 @@ class SelectMenu extends Component {
34596
34766
  selectedValue: String,
34597
34767
  class: { type: String, optional: true },
34598
34768
  };
34599
- static components = { Menu };
34769
+ static components = { MenuPopover };
34600
34770
  menuId = new UuidGenerator().uuidv4();
34601
34771
  selectRef = useRef("select");
34602
34772
  state = useState({
@@ -35344,7 +35514,7 @@ class MenuItemRegistry extends Registry {
35344
35514
  }
35345
35515
 
35346
35516
  //------------------------------------------------------------------------------
35347
- // Link Menu Registry
35517
+ // Link MenuPopover Registry
35348
35518
  //------------------------------------------------------------------------------
35349
35519
  const linkMenuRegistry = new MenuItemRegistry();
35350
35520
  linkMenuRegistry.add("sheet", {
@@ -35406,7 +35576,7 @@ class LinkEditor extends Component {
35406
35576
  cellPosition: Object,
35407
35577
  onClosed: { type: Function, optional: true },
35408
35578
  };
35409
- static components = { Menu };
35579
+ static components = { MenuPopover };
35410
35580
  menuItems = linkMenuRegistry.getMenuItems();
35411
35581
  link = useState(this.defaultState);
35412
35582
  menu = useState({
@@ -35863,58 +36033,149 @@ css /* scss */ `
35863
36033
  const ARROW_DOWN = {
35864
36034
  width: 448,
35865
36035
  height: 512,
35866
- fillColor: "#E06666",
35867
- 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
+ ],
35868
36042
  };
35869
36043
  const ARROW_UP = {
35870
36044
  width: 448,
35871
36045
  height: 512,
35872
- fillColor: "#6AA84F",
35873
- 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
+ ],
35874
36052
  };
35875
36053
  const ARROW_RIGHT = {
35876
36054
  width: 448,
35877
36055
  height: 512,
35878
- fillColor: "#F0AD4E",
35879
- 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
+ ],
35880
36062
  };
35881
36063
  const SMILE = {
35882
36064
  width: 496,
35883
36065
  height: 512,
35884
- fillColor: "#6AA84F",
35885
- 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
+ ],
35886
36072
  };
35887
36073
  const MEH = {
35888
36074
  width: 496,
35889
36075
  height: 512,
35890
- fillColor: "#F0AD4E",
35891
- 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
+ ],
35892
36082
  };
35893
36083
  const FROWN = {
35894
36084
  width: 496,
35895
36085
  height: 512,
35896
- fillColor: "#E06666",
35897
- 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
+ ],
35898
36092
  };
35899
36093
  const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
35900
36094
  const GREEN_DOT = {
35901
36095
  width: 512,
35902
36096
  height: 512,
35903
- fillColor: "#6AA84F",
35904
- path: DOT_PATH,
36097
+ paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
35905
36098
  };
35906
36099
  const YELLOW_DOT = {
35907
36100
  width: 512,
35908
36101
  height: 512,
35909
- fillColor: "#F0AD4E",
35910
- path: DOT_PATH,
36102
+ paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
35911
36103
  };
35912
36104
  const RED_DOT = {
35913
36105
  width: 512,
35914
36106
  height: 512,
35915
- fillColor: "#E06666",
35916
- 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
+ ],
35917
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" }],
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
+ }
35918
36179
  const ICONS = {
35919
36180
  arrowGood: {
35920
36181
  template: "ARROW_UP",
@@ -35970,6 +36231,15 @@ const ICON_SETS = {
35970
36231
  bad: "dotBad",
35971
36232
  },
35972
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
+ }
35973
36243
 
35974
36244
  /**
35975
36245
  * Map of the different types of conversions warnings and their name in error messages
@@ -41941,6 +42211,7 @@ const findAndReplace = {
41941
42211
  execute: (env) => {
41942
42212
  env.openSidePanel("FindAndReplace", {});
41943
42213
  },
42214
+ isEnabled: (env) => !env.isSmall,
41944
42215
  icon: "o-spreadsheet-Icon.SEARCH",
41945
42216
  };
41946
42217
  const deleteValues = {
@@ -42197,11 +42468,13 @@ const insertCellShiftRight = {
42197
42468
  const insertChart = {
42198
42469
  name: _t("Chart"),
42199
42470
  execute: CREATE_CHART,
42471
+ isEnabled: (env) => !env.isSmall,
42200
42472
  icon: "o-spreadsheet-Icon.INSERT_CHART",
42201
42473
  };
42202
42474
  const insertPivot = {
42203
42475
  name: _t("Pivot table"),
42204
42476
  execute: CREATE_PIVOT,
42477
+ isEnabled: (env) => !env.isSmall,
42205
42478
  icon: "o-spreadsheet-Icon.PIVOT",
42206
42479
  };
42207
42480
  const insertImage = {
@@ -42209,12 +42482,14 @@ const insertImage = {
42209
42482
  description: "Ctrl+O",
42210
42483
  execute: CREATE_IMAGE,
42211
42484
  isVisible: (env) => env.imageProvider !== undefined,
42485
+ isEnabled: (env) => !env.isSmall,
42212
42486
  icon: "o-spreadsheet-Icon.INSERT_IMAGE",
42213
42487
  };
42214
42488
  const insertTable = {
42215
42489
  name: () => _t("Table"),
42216
42490
  execute: INSERT_TABLE,
42217
42491
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
42492
+ isEnabled: (env) => !env.isSmall,
42218
42493
  icon: "o-spreadsheet-Icon.PAINT_TABLE",
42219
42494
  };
42220
42495
  const insertFunction = {
@@ -42320,6 +42595,7 @@ const insertDropdown = {
42320
42595
  },
42321
42596
  });
42322
42597
  },
42598
+ isEnabled: (env) => !env.isSmall,
42323
42599
  icon: "o-spreadsheet-Icon.INSERT_DROPDOWN",
42324
42600
  };
42325
42601
  const insertSheet = {
@@ -42589,7 +42865,7 @@ const pivotProperties = {
42589
42865
  isVisible: (env) => {
42590
42866
  const position = env.model.getters.getActivePosition();
42591
42867
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
42592
- return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42868
+ return (!env.isSmall && pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42593
42869
  },
42594
42870
  isReadonlyAllowed: true,
42595
42871
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -42707,7 +42983,7 @@ function isPivotSortMenuItemActive(env, order) {
42707
42983
  }
42708
42984
 
42709
42985
  //------------------------------------------------------------------------------
42710
- // Context Menu Registry
42986
+ // Context MenuPopover Registry
42711
42987
  //------------------------------------------------------------------------------
42712
42988
  const cellMenuRegistry = new MenuItemRegistry();
42713
42989
  cellMenuRegistry
@@ -42790,6 +43066,7 @@ cellMenuRegistry
42790
43066
  .add("edit_table", {
42791
43067
  ...editTable,
42792
43068
  isVisible: SELECTION_CONTAINS_SINGLE_TABLE,
43069
+ isEnabled: (env) => !env.isSmall,
42793
43070
  sequence: 140,
42794
43071
  })
42795
43072
  .add("delete_table", {
@@ -42858,6 +43135,7 @@ const removeDuplicates = {
42858
43135
  }
42859
43136
  env.openSidePanel("RemoveDuplicates", {});
42860
43137
  },
43138
+ isEnabled: (env) => !env.isSmall,
42861
43139
  };
42862
43140
  const trimWhitespace = {
42863
43141
  name: _t("Trim whitespace"),
@@ -42885,7 +43163,7 @@ const splitToColumns = {
42885
43163
  name: _t("Split text to columns"),
42886
43164
  sequence: 1,
42887
43165
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
42888
- isEnabled: (env) => env.model.getters.isSingleColSelected(),
43166
+ isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
42889
43167
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
42890
43168
  };
42891
43169
  const reinsertDynamicPivotMenu = {
@@ -42972,7 +43250,7 @@ const formatNumberAccounting = createFormatActionSpec({
42972
43250
  const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
42973
43251
  const formatCustomCurrency = {
42974
43252
  name: _t("Custom currency"),
42975
- isVisible: (env) => env.loadCurrencies !== undefined,
43253
+ isVisible: (env) => env.loadCurrencies !== undefined && !env.isSmall,
42976
43254
  execute: (env) => env.openSidePanel("CustomCurrency", {}),
42977
43255
  };
42978
43256
  const formatNumberDate = createFormatActionSpec({
@@ -43195,6 +43473,7 @@ const formatWrappingClip = {
43195
43473
  const formatCF = {
43196
43474
  name: _t("Conditional formatting"),
43197
43475
  execute: OPEN_CF_SIDEPANEL_ACTION,
43476
+ isEnabled: (env) => !env.isSmall,
43198
43477
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
43199
43478
  };
43200
43479
  const clearFormat = {
@@ -43578,8 +43857,7 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
43578
43857
  }
43579
43858
  return [
43580
43859
  {
43581
- sheetId: position.sheetId,
43582
- zone,
43860
+ range: this.model.getters.getRangeFromZone(position.sheetId, zone),
43583
43861
  dashed: cell.value === CellErrorType.SpilledBlocked,
43584
43862
  color: "#17A2B8",
43585
43863
  noFill: true,
@@ -43664,6 +43942,7 @@ function useDragAndDropBeyondTheViewport(env) {
43664
43942
  let previousEvClientPosition;
43665
43943
  let startingX;
43666
43944
  let startingY;
43945
+ let scrollDirection = "all";
43667
43946
  const getters = env.model.getters;
43668
43947
  let cleanUpFns = [];
43669
43948
  const cleanUp = () => {
@@ -43689,55 +43968,59 @@ function useDragAndDropBeyondTheViewport(env) {
43689
43968
  let timeoutDelay = MAX_DELAY;
43690
43969
  const x = currentEv.clientX - position.left;
43691
43970
  let colIndex = getters.getColIndex(x);
43692
- const previousX = previousEvClientPosition.clientX - position.left;
43693
- const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);
43694
- if (edgeScrollInfoX.canEdgeScroll) {
43695
- canEdgeScroll = true;
43696
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);
43697
- let newTarget = colIndex;
43698
- switch (edgeScrollInfoX.direction) {
43699
- case "reset":
43700
- colIndex = newTarget = xSplit;
43701
- break;
43702
- case 1:
43703
- colIndex = right;
43704
- newTarget = left + 1;
43705
- break;
43706
- case -1:
43707
- colIndex = left - 1;
43708
- while (env.model.getters.isColHidden(sheetId, colIndex)) {
43709
- colIndex--;
43710
- }
43711
- newTarget = colIndex;
43712
- 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;
43713
43995
  }
43714
- scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43715
43996
  }
43716
43997
  const y = currentEv.clientY - position.top;
43717
43998
  let rowIndex = getters.getRowIndex(y);
43718
- const previousY = previousEvClientPosition.clientY - position.top;
43719
- const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);
43720
- if (edgeScrollInfoY.canEdgeScroll) {
43721
- canEdgeScroll = true;
43722
- timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);
43723
- let newTarget = rowIndex;
43724
- switch (edgeScrollInfoY.direction) {
43725
- case "reset":
43726
- rowIndex = newTarget = ySplit;
43727
- break;
43728
- case 1:
43729
- rowIndex = bottom;
43730
- newTarget = top + 1;
43731
- break;
43732
- case -1:
43733
- rowIndex = top - 1;
43734
- while (env.model.getters.isRowHidden(sheetId, rowIndex)) {
43735
- rowIndex--;
43736
- }
43737
- newTarget = rowIndex;
43738
- 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;
43739
44023
  }
43740
- scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43741
44024
  }
43742
44025
  if (!canEdgeScroll) {
43743
44026
  colIndex = adjustIndexWithinBounds(colIndex, x, getters.getNumberCols(sheetId) - 1);
@@ -43757,9 +44040,10 @@ function useDragAndDropBeyondTheViewport(env) {
43757
44040
  pointerUpCallback?.();
43758
44041
  cleanUp();
43759
44042
  };
43760
- const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp) => {
44043
+ const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp, startScrollDirection = "all") => {
43761
44044
  cleanUp();
43762
44045
  const position = gridOverlayPosition();
44046
+ scrollDirection = startScrollDirection;
43763
44047
  startingX = initialPointerCoordinates.clientX - position.left;
43764
44048
  startingY = initialPointerCoordinates.clientY - position.top;
43765
44049
  previousEvClientPosition = {
@@ -44237,7 +44521,7 @@ class GridComposer extends Component {
44237
44521
  });
44238
44522
  }
44239
44523
  get shouldDisplayCellReference() {
44240
- return this.isCellReferenceVisible;
44524
+ return !this.env.isMobile() && this.isCellReferenceVisible;
44241
44525
  }
44242
44526
  get cellReference() {
44243
44527
  const { col, row, sheetId } = this.composerStore.currentEditedCell;
@@ -44278,11 +44562,12 @@ class GridComposer extends Component {
44278
44562
  onInputContextMenu: this.props.onInputContextMenu,
44279
44563
  composerStore: this.composerStore,
44280
44564
  inputStyle: `max-height: ${maxHeight}px;`,
44565
+ inputMode: this.composerStore.editionMode === "inactive" ? "none" : undefined,
44281
44566
  };
44282
44567
  }
44283
44568
  get containerStyle() {
44284
- if (this.composerStore.editionMode === "inactive") {
44285
- 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
44286
44571
  }
44287
44572
  const _isFormula = isFormula(this.composerStore.currentContent);
44288
44573
  const cell = this.env.model.getters.getActiveCell();
@@ -44311,11 +44596,13 @@ class GridComposer extends Component {
44311
44596
  *
44312
44597
  * The +-1 are there to include cell borders in the composer sizing/positioning
44313
44598
  */
44599
+ const minHeight = Math.min(height + 1, maxHeight);
44600
+ const minWidth = Math.min(width + 1, maxWidth);
44314
44601
  return cssPropertiesToCss({
44315
44602
  left: `${left - 1}px`,
44316
44603
  top: `${top}px`,
44317
- "min-width": `${width + 1}px`,
44318
- "min-height": `${height + 1}px`,
44604
+ "min-width": `${minWidth}px`,
44605
+ "min-height": `${minHeight}px`,
44319
44606
  "max-width": `${maxWidth}px`,
44320
44607
  "max-height": `${maxHeight}px`,
44321
44608
  background,
@@ -44812,6 +45099,9 @@ class FiguresContainer extends Component {
44812
45099
  if (!selectResult.isSuccessful) {
44813
45100
  return;
44814
45101
  }
45102
+ if (this.env.isMobile()) {
45103
+ return;
45104
+ }
44815
45105
  const sheetId = this.env.model.getters.getActiveSheetId();
44816
45106
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
44817
45107
  const initialScrollPosition = this.env.model.getters.getActiveSheetScrollInfo();
@@ -45104,78 +45394,6 @@ class GridAddRowsFooter extends Component {
45104
45394
  }
45105
45395
  }
45106
45396
 
45107
- class GridCellIcon extends Component {
45108
- static template = "o-spreadsheet-GridCellIcon";
45109
- static props = {
45110
- icon: Object,
45111
- verticalAlign: { type: String, optional: true },
45112
- slots: Object,
45113
- };
45114
- get iconStyle() {
45115
- const cellPosition = this.props.icon.position;
45116
- const merge = this.env.model.getters.getMerge(cellPosition);
45117
- const zone = merge || positionToZone(cellPosition);
45118
- const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
45119
- const x = this.getIconHorizontalPosition(rect, cellPosition);
45120
- const y = this.getIconVerticalPosition(rect, cellPosition);
45121
- return cssPropertiesToCss({
45122
- top: `${y}px`,
45123
- left: `${x}px`,
45124
- width: `${this.props.icon.size}px`,
45125
- height: `${this.props.icon.size}px`,
45126
- });
45127
- }
45128
- getIconVerticalPosition(rect, cellPosition) {
45129
- const start = rect.y;
45130
- const end = rect.y + rect.height;
45131
- const cell = this.env.model.getters.getCell(cellPosition);
45132
- const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
45133
- switch (align) {
45134
- case "bottom":
45135
- return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
45136
- case "top":
45137
- return start + GRID_ICON_MARGIN;
45138
- default:
45139
- const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
45140
- return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
45141
- }
45142
- }
45143
- getIconHorizontalPosition(rect, cellPosition) {
45144
- const start = rect.x;
45145
- const end = rect.x + rect.width;
45146
- const cell = this.env.model.getters.getCell(cellPosition);
45147
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
45148
- const align = this.props.icon.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
45149
- switch (align) {
45150
- case "right":
45151
- return end - this.props.icon.size - this.props.icon.margin;
45152
- case "left":
45153
- return start + this.props.icon.margin;
45154
- default:
45155
- const centeringOffset = Math.floor((end - start - this.props.icon.size) / 2);
45156
- return end - this.props.icon.size - centeringOffset;
45157
- }
45158
- }
45159
- isPositionVisible(position) {
45160
- const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
45161
- return !(rect.width === 0 || rect.height === 0);
45162
- }
45163
- }
45164
-
45165
- class GridCellIconOverlay extends Component {
45166
- static template = "o-spreadsheet-GridCellIconOverlay";
45167
- static props = {};
45168
- static components = { GridCellIcon };
45169
- get icons() {
45170
- const icons = [];
45171
- for (const position of this.env.model.getters.getVisibleCellPositions()) {
45172
- const cellIcons = this.env.model.getters.getCellIcons(position);
45173
- icons.push(...cellIcons.filter((icon) => icon.component));
45174
- }
45175
- return icons;
45176
- }
45177
- }
45178
-
45179
45397
  /**
45180
45398
  * Manages an event listener on a ref. Useful for hooks that want to manage
45181
45399
  * event listeners, especially more than one. Prefer using t-on directly in
@@ -45264,7 +45482,7 @@ class PaintFormatStore extends SpreadsheetStore {
45264
45482
  return [];
45265
45483
  }
45266
45484
  return data.zones.map((zone) => ({
45267
- zone,
45485
+ range: this.model.getters.getRangeFromZone(data.sheetId, zone),
45268
45486
  color: SELECTION_BORDER_COLOR,
45269
45487
  dashed: true,
45270
45488
  sheetId: data.sheetId,
@@ -45287,7 +45505,7 @@ class HoveredTableStore extends SpreadsheetStore {
45287
45505
  }
45288
45506
  }
45289
45507
  hover(position) {
45290
- if (position.col === this.col && position.row === this.row) {
45508
+ if (!this.getters.isDashboard() || (position.col === this.col && position.row === this.row)) {
45291
45509
  return "noStateChange";
45292
45510
  }
45293
45511
  this.col = position.col;
@@ -45321,6 +45539,14 @@ class HoveredTableStore extends SpreadsheetStore {
45321
45539
  }
45322
45540
  }
45323
45541
 
45542
+ class HoveredIconStore extends SpreadsheetStore {
45543
+ mutators = ["setHoveredIcon"];
45544
+ hoveredIcon = undefined;
45545
+ setHoveredIcon(icon) {
45546
+ this.hoveredIcon = icon;
45547
+ }
45548
+ }
45549
+
45324
45550
  const CURSOR_SVG = /*xml*/ `
45325
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>
45326
45552
  `;
@@ -45392,10 +45618,11 @@ function useCellHovered(env, gridRef) {
45392
45618
  return pause();
45393
45619
  }
45394
45620
  }
45395
- useRefListener(gridRef, "pointermove", updateMousePosition);
45621
+ useRefListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(ev));
45396
45622
  useRefListener(gridRef, "mouseleave", onMouseLeave);
45397
45623
  useRefListener(gridRef, "mouseenter", resume);
45398
45624
  useRefListener(gridRef, "pointerdown", recompute);
45625
+ useRefListener(gridRef, "pointerdown", (ev) => env.isMobile() && updateMousePosition(ev));
45399
45626
  useExternalListener(window, "click", handleGlobalClick);
45400
45627
  function handleGlobalClick(e) {
45401
45628
  const target = e.target;
@@ -45424,11 +45651,11 @@ class GridOverlay extends Component {
45424
45651
  onGridMoved: Function,
45425
45652
  gridOverlayDimensions: String,
45426
45653
  slots: { type: Object, optional: true },
45654
+ getGridSize: Function,
45427
45655
  };
45428
45656
  static components = {
45429
45657
  FiguresContainer,
45430
45658
  GridAddRowsFooter,
45431
- GridCellIconOverlay,
45432
45659
  };
45433
45660
  static defaultProps = {
45434
45661
  onCellDoubleClicked: () => { },
@@ -45440,15 +45667,17 @@ class GridOverlay extends Component {
45440
45667
  gridOverlay = useRef("gridOverlay");
45441
45668
  cellPopovers;
45442
45669
  paintFormatStore;
45670
+ hoveredIconStore;
45443
45671
  setup() {
45444
45672
  useCellHovered(this.env, this.gridOverlay);
45445
45673
  const resizeObserver = new ResizeObserver(() => {
45446
45674
  const boundingRect = this.gridOverlayEl.getBoundingClientRect();
45675
+ const { width, height } = this.props.getGridSize();
45447
45676
  this.props.onGridResized({
45448
45677
  x: boundingRect.left,
45449
45678
  y: boundingRect.top,
45450
- height: this.gridOverlayEl.clientHeight,
45451
- width: this.gridOverlayEl.clientWidth,
45679
+ height: height,
45680
+ width: width,
45452
45681
  });
45453
45682
  });
45454
45683
  onMounted(() => {
@@ -45459,6 +45688,7 @@ class GridOverlay extends Component {
45459
45688
  });
45460
45689
  this.cellPopovers = useStore(CellPopoverStore);
45461
45690
  this.paintFormatStore = useStore(PaintFormatStore);
45691
+ this.hoveredIconStore = useStore(HoveredIconStore);
45462
45692
  }
45463
45693
  get gridOverlayEl() {
45464
45694
  if (!this.gridOverlay.el) {
@@ -45467,26 +45697,59 @@ class GridOverlay extends Component {
45467
45697
  return this.gridOverlay.el;
45468
45698
  }
45469
45699
  get style() {
45470
- return this.props.gridOverlayDimensions;
45700
+ return (this.props.gridOverlayDimensions +
45701
+ cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "default" }));
45471
45702
  }
45472
45703
  get isPaintingFormat() {
45473
45704
  return this.paintFormatStore.isActive;
45474
45705
  }
45475
- onMouseDown(ev) {
45476
- 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()) {
45477
45718
  // not main button, probably a context menu
45478
45719
  return;
45479
45720
  }
45480
- if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
45481
- 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;
45482
45727
  }
45728
+ this.onCellClicked(ev);
45729
+ }
45730
+ onCellClicked(ev) {
45731
+ const openedPopover = this.cellPopovers.persistentCellPopover;
45483
45732
  const [col, row] = this.getCartesianCoordinates(ev);
45484
45733
  this.props.onCellClicked(col, row, {
45485
45734
  expandZone: ev.shiftKey,
45486
45735
  addZone: isCtrlKey(ev),
45487
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
+ }
45488
45748
  }
45489
45749
  onDoubleClick(ev) {
45750
+ if (this.getInteractiveIconAtEvent(ev)) {
45751
+ return;
45752
+ }
45490
45753
  const [col, row] = this.getCartesianCoordinates(ev);
45491
45754
  this.props.onCellDoubleClicked(col, row);
45492
45755
  }
@@ -45502,6 +45765,24 @@ class GridOverlay extends Component {
45502
45765
  const rowIndex = this.env.model.getters.getRowIndex(y);
45503
45766
  return [colIndex, rowIndex];
45504
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
+ }
45505
45786
  }
45506
45787
 
45507
45788
  class GridPopover extends Component {
@@ -45662,6 +45943,9 @@ class AbstractResizer extends Component {
45662
45943
  this.state.waitingForMove = false;
45663
45944
  }
45664
45945
  onMouseMove(ev) {
45946
+ if (this.env.isMobile()) {
45947
+ return;
45948
+ }
45665
45949
  if (this.state.isResizing || this.state.isMoving || this.state.isSelecting) {
45666
45950
  return;
45667
45951
  }
@@ -45706,7 +45990,21 @@ class AbstractResizer extends Component {
45706
45990
  };
45707
45991
  startDnd(onMouseMove, onMouseUp);
45708
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
+ }
45709
46004
  select(ev) {
46005
+ if (this.env.isMobile()) {
46006
+ return;
46007
+ }
45710
46008
  if (ev.button > 0) {
45711
46009
  // not main button, probably a context menu
45712
46010
  return;
@@ -45775,6 +46073,9 @@ class AbstractResizer extends Component {
45775
46073
  this.dragNDropGrid.start(ev, mouseMoveMovement, mouseUpMovement);
45776
46074
  }
45777
46075
  startSelection(ev, index) {
46076
+ if (this.env.isMobile()) {
46077
+ return;
46078
+ }
45778
46079
  this.state.isSelecting = true;
45779
46080
  if (ev.shiftKey) {
45780
46081
  this._increaseSelection(index);
@@ -46220,11 +46521,13 @@ class GridRenderer {
46220
46521
  renderer;
46221
46522
  fingerprints;
46222
46523
  hoveredTables;
46524
+ hoveredIcon;
46223
46525
  constructor(get) {
46224
46526
  this.getters = get(ModelStore).getters;
46225
46527
  this.renderer = get(RendererStore);
46226
46528
  this.fingerprints = get(FormulaFingerprintStore);
46227
46529
  this.hoveredTables = get(HoveredTableStore);
46530
+ this.hoveredIcon = get(HoveredIconStore);
46228
46531
  this.renderer.register(this);
46229
46532
  }
46230
46533
  get renderingLayers() {
@@ -46461,7 +46764,7 @@ class GridRenderer {
46461
46764
  // compute vertical align start point parameter:
46462
46765
  const textLineHeight = computeTextFontSizeInPixels(style);
46463
46766
  const numberOfLines = box.content.textLines.length;
46464
- let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
46767
+ let y = this.getters.computeTextYCoordinate(box, textLineHeight, style.verticalAlign, numberOfLines);
46465
46768
  // use the horizontal and the vertical start points to:
46466
46769
  // fill text / fill strikethrough / fill underline
46467
46770
  for (const brokenLine of box.content.textLines) {
@@ -46478,7 +46781,12 @@ class GridRenderer {
46478
46781
  const { ctx } = renderingContext;
46479
46782
  for (const box of boxes) {
46480
46783
  for (const icon of Object.values(box.icons)) {
46481
- 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) {
46482
46790
  continue;
46483
46791
  }
46484
46792
  ctx.save();
@@ -46486,46 +46794,17 @@ class GridRenderer {
46486
46794
  ctx.rect(box.x, box.y, box.width, box.height);
46487
46795
  ctx.clip();
46488
46796
  const iconSize = icon.size;
46489
- const iconY = this.computeTextYCoordinate(box, iconSize);
46490
- const svg = icon.svg;
46491
- let x;
46492
- if (icon.horizontalAlign === "left") {
46493
- x = box.x + icon.margin;
46494
- }
46495
- else if (icon.horizontalAlign === "right") {
46496
- x = box.x + box.width - iconSize - icon.margin;
46497
- }
46498
- else {
46499
- x = box.x + (box.width - iconSize) / 2;
46500
- }
46501
- ctx.translate(x, iconY);
46797
+ const { x, y } = this.getters.getCellIconRect(icon);
46798
+ ctx.translate(x, y);
46502
46799
  ctx.scale(iconSize / svg.width, iconSize / svg.height);
46503
- ctx.fillStyle = svg.fillColor;
46504
- 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
+ }
46505
46804
  ctx.restore();
46506
46805
  }
46507
46806
  }
46508
46807
  }
46509
- /** Computes the vertical start point from which a text line should be draw.
46510
- *
46511
- * Note that in case the cell does not have enough spaces to display its text lines,
46512
- * (wrapping cell case) then the vertical align should be at the top.
46513
- * */
46514
- computeTextYCoordinate(box, textLineHeight, numberOfLines = 1) {
46515
- const y = box.y + 1;
46516
- const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
46517
- const hasEnoughSpaces = box.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
46518
- const verticalAlign = box.verticalAlign || DEFAULT_VERTICAL_ALIGN;
46519
- if (hasEnoughSpaces) {
46520
- if (verticalAlign === "middle") {
46521
- return y + (box.height - textHeight) / 2;
46522
- }
46523
- if (verticalAlign === "bottom") {
46524
- return y + box.height - textHeight - MIN_CELL_TEXT_MARGIN;
46525
- }
46526
- }
46527
- return y + MIN_CELL_TEXT_MARGIN;
46528
- }
46529
46808
  drawHeaders(renderingContext) {
46530
46809
  const { ctx, thinLineWidth } = renderingContext;
46531
46810
  const visibleCols = this.getters.getSheetViewVisibleCols();
@@ -46962,6 +47241,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46962
47241
  const deltaX = lastX - clientX;
46963
47242
  const deltaY = lastY - clientY;
46964
47243
  const elapsedTime = currentTime - lastTime;
47244
+ if (!elapsedTime) {
47245
+ return;
47246
+ }
46965
47247
  velocityX = deltaX / elapsedTime;
46966
47248
  velocityY = deltaY / elapsedTime;
46967
47249
  lastX = clientX;
@@ -46982,6 +47264,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46982
47264
  function onTouchEnd(ev) {
46983
47265
  isMouseDown = false;
46984
47266
  lastX = lastY = 0;
47267
+ if (resetTimeout) {
47268
+ clearTimeout(resetTimeout);
47269
+ }
47270
+ velocityX *= 1.2;
47271
+ velocityY *= 1.2;
46985
47272
  requestAnimationFrame(scroll);
46986
47273
  }
46987
47274
  function scroll() {
@@ -47066,12 +47353,16 @@ class Border extends Component {
47066
47353
  }
47067
47354
  }
47068
47355
 
47356
+ const MOBILE_HANDLER_WIDTH = 40;
47069
47357
  css /* scss */ `
47070
47358
  .o-corner {
47071
47359
  position: absolute;
47072
- height: 8px;
47073
- width: 8px;
47360
+ }
47361
+
47362
+ .o-corner-button {
47074
47363
  border: 1px solid white;
47364
+ height: ${AUTOFILL_EDGE_LENGTH}px;
47365
+ width: ${AUTOFILL_EDGE_LENGTH}px;
47075
47366
  }
47076
47367
  .o-corner-nw,
47077
47368
  .o-corner-se {
@@ -47098,34 +47389,61 @@ class Corner extends Component {
47098
47389
  isResizing: Boolean,
47099
47390
  onResizeHighlight: Function,
47100
47391
  };
47101
- isTop = this.props.orientation[0] === "n";
47102
- isLeft = this.props.orientation[1] === "w";
47103
- 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() {
47104
47400
  const z = this.props.zone;
47105
- const col = this.isLeft ? z.left : z.right;
47106
- const row = this.isTop ? z.top : z.bottom;
47107
47401
  const rect = this.env.model.getters.getVisibleRect({
47108
- left: col,
47109
- right: col,
47110
- top: row,
47111
- 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,
47112
47406
  });
47113
47407
  // Don't show if not visible in the viewport
47114
47408
  if (rect.width * rect.height === 0) {
47115
- 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`;
47116
47422
  }
47117
- const leftValue = this.isLeft ? rect.x : rect.x + rect.width;
47118
- const topValue = this.isTop ? rect.y : rect.y + rect.height;
47119
- return cssPropertiesToCss({
47120
- left: `${leftValue - AUTOFILL_EDGE_LENGTH / 2}px`,
47121
- 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 = {
47122
47430
  "background-color": this.props.color,
47123
- });
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);
47124
47437
  }
47125
47438
  onMouseDown(ev) {
47126
- this.props.onResizeHighlight(ev, this.isLeft, this.isTop);
47439
+ this.props.onResizeHighlight(ev, this.dirX, this.dirY);
47127
47440
  }
47128
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
+ }
47129
47447
 
47130
47448
  css /*SCSS*/ `
47131
47449
  .o-highlight {
@@ -47135,7 +47453,7 @@ css /*SCSS*/ `
47135
47453
  class Highlight extends Component {
47136
47454
  static template = "o-spreadsheet-Highlight";
47137
47455
  static props = {
47138
- zone: Object,
47456
+ range: Object,
47139
47457
  color: String,
47140
47458
  };
47141
47459
  static components = {
@@ -47146,26 +47464,49 @@ class Highlight extends Component {
47146
47464
  shiftingMode: "none",
47147
47465
  });
47148
47466
  dragNDropGrid = useDragAndDropBeyondTheViewport(this.env);
47149
- 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) {
47150
47483
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47151
47484
  this.highlightState.shiftingMode = "isResizing";
47152
- const z = this.props.zone;
47153
- const pivotCol = isLeft ? z.right : z.left;
47154
- const pivotRow = isTop ? z.bottom : z.top;
47155
- let lastCol = isLeft ? z.left : z.right;
47156
- 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;
47157
47490
  let currentZone = z;
47491
+ let scrollDirection = "all";
47492
+ if (this.env.isMobile()) {
47493
+ scrollDirection = dirX === 0 ? "vertical" : dirY === 0 ? "horizontal" : "all";
47494
+ }
47158
47495
  this.env.model.dispatch("START_CHANGE_HIGHLIGHT", { zone: currentZone });
47159
47496
  const mouseMove = (col, row) => {
47160
47497
  if (lastCol !== col || lastRow !== row) {
47161
- lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
47162
- lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
47163
- const newZone = {
47164
- left: Math.min(pivotCol, lastCol),
47165
- top: Math.min(pivotRow, lastRow),
47166
- right: Math.max(pivotCol, lastCol),
47167
- bottom: Math.max(pivotRow, lastRow),
47168
- };
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 };
47169
47510
  if (!isEqual(newZone, currentZone)) {
47170
47511
  this.env.model.selection.selectZone({
47171
47512
  cell: { col: newZone.left, row: newZone.top },
@@ -47178,11 +47519,11 @@ class Highlight extends Component {
47178
47519
  const mouseUp = () => {
47179
47520
  this.highlightState.shiftingMode = "none";
47180
47521
  };
47181
- this.dragNDropGrid.start(ev, mouseMove, mouseUp);
47522
+ this.dragNDropGrid.start(ev, mouseMove, mouseUp, scrollDirection);
47182
47523
  }
47183
47524
  onMoveHighlight(ev) {
47184
47525
  this.highlightState.shiftingMode = "isMoving";
47185
- const z = this.props.zone;
47526
+ const z = this.props.range.zone;
47186
47527
  const position = gridOverlayPosition();
47187
47528
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47188
47529
  const initCol = this.env.model.getters.getColIndex(ev.clientX - position.left);
@@ -47404,6 +47745,18 @@ class VerticalScrollBar extends Component {
47404
47745
  }
47405
47746
  }
47406
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
+
47407
47760
  class Section extends Component {
47408
47761
  static template = "o_spreadsheet.Section";
47409
47762
  static props = {
@@ -48349,6 +48702,7 @@ class ColorPickerWidget extends Component {
48349
48702
 
48350
48703
  css /* scss */ `
48351
48704
  .o-font-size-editor {
48705
+ width: max-content !important;
48352
48706
  height: calc(100% - 4px);
48353
48707
  input.o-font-size {
48354
48708
  outline: none;
@@ -50497,8 +50851,7 @@ class ConditionalFormatPreview extends Component {
50497
50851
  get highlights() {
50498
50852
  const sheetId = this.env.model.getters.getActiveSheetId();
50499
50853
  return this.props.conditionalFormat.ranges.map((range) => ({
50500
- sheetId,
50501
- zone: this.env.model.getters.getRangeFromSheetXC(sheetId, range).zone,
50854
+ range: this.env.model.getters.getRangeFromSheetXC(sheetId, range),
50502
50855
  color: HIGHLIGHT_COLOR,
50503
50856
  fillAlpha: 0.06,
50504
50857
  }));
@@ -51456,8 +51809,7 @@ class DataValidationPreview extends Component {
51456
51809
  }
51457
51810
  get highlights() {
51458
51811
  return this.props.rule.ranges.map((range) => ({
51459
- sheetId: this.env.model.getters.getActiveSheetId(),
51460
- zone: range.zone,
51812
+ range,
51461
51813
  color: HIGHLIGHT_COLOR,
51462
51814
  fillAlpha: 0.06,
51463
51815
  }));
@@ -51786,13 +52138,15 @@ class FindAndReplaceStore extends SpreadsheetStore {
51786
52138
  if (this.selectedMatchIndex === null) {
51787
52139
  return;
51788
52140
  }
52141
+ this.preserveSelectedMatchIndex = true;
52142
+ this.shouldFinalizeUpdateSelection = true;
51789
52143
  this.model.dispatch("REPLACE_SEARCH", {
51790
52144
  searchString: this.toSearch,
51791
52145
  replaceWith: this.toReplace,
51792
52146
  matches: [this.searchMatches[this.selectedMatchIndex]],
51793
52147
  searchOptions: this.searchOptions,
51794
52148
  });
51795
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
52149
+ this.preserveSelectedMatchIndex = false;
51796
52150
  }
51797
52151
  /**
51798
52152
  * Apply the replace function to all the matches one time.
@@ -51864,8 +52218,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51864
52218
  const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
51865
52219
  if (width > 0 && height > 0) {
51866
52220
  highlights.push({
51867
- sheetId,
51868
- zone: zoneWithMerge,
52221
+ range: this.model.getters.getRangeFromZone(sheetId, zoneWithMerge),
51869
52222
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51870
52223
  noBorder: index !== this.selectedMatchIndex,
51871
52224
  thinLine: true,
@@ -51877,8 +52230,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51877
52230
  const range = this.searchOptions.specificRange;
51878
52231
  if (range && range.sheetId === sheetId) {
51879
52232
  highlights.push({
51880
- sheetId,
51881
- zone: range.zone,
52233
+ range,
51882
52234
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51883
52235
  noFill: true,
51884
52236
  thinLine: true,
@@ -52252,7 +52604,11 @@ function getPivotHighlights(getters, pivotId) {
52252
52604
  const sheetId = getters.getActiveSheetId();
52253
52605
  const pivotCellPositions = getVisiblePivotCellPositions(getters, pivotId);
52254
52606
  const mergedZones = mergeContiguousZones(pivotCellPositions.map(positionToZone));
52255
- 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
+ }));
52256
52612
  }
52257
52613
  function getVisiblePivotCellPositions(getters, pivotId) {
52258
52614
  const positions = [];
@@ -52521,7 +52877,7 @@ class TextInput extends Component {
52521
52877
 
52522
52878
  class CogWheelMenu extends Component {
52523
52879
  static template = "o-spreadsheet-CogWheelMenu";
52524
- static components = { Menu };
52880
+ static components = { MenuPopover };
52525
52881
  static props = {
52526
52882
  items: Array,
52527
52883
  };
@@ -54387,12 +54743,7 @@ class SpreadsheetPivot {
54387
54743
  entry[field.name] = { value: null, type: CellValueType.empty, formattedValue: "" };
54388
54744
  }
54389
54745
  else {
54390
- if (field.type === "char") {
54391
- entry[field.name] = { ...cell, value: cell.formattedValue || null };
54392
- }
54393
- else {
54394
- entry[field.name] = cell;
54395
- }
54746
+ entry[field.name] = cell;
54396
54747
  }
54397
54748
  }
54398
54749
  entry["__count"] = { value: 1, type: CellValueType.number, formattedValue: "1" };
@@ -55096,6 +55447,7 @@ function createTableStyleContextMenuActions(env, styleId) {
55096
55447
  id: "editTableStyle",
55097
55448
  name: _t("Edit table style"),
55098
55449
  execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
55450
+ isEnabled: (env) => !env.isSmall,
55099
55451
  icon: "o-spreadsheet-Icon.EDIT",
55100
55452
  },
55101
55453
  {
@@ -55217,7 +55569,7 @@ css /* scss */ `
55217
55569
  `;
55218
55570
  class TableStylePreview extends Component {
55219
55571
  static template = "o-spreadsheet-TableStylePreview";
55220
- static components = { Menu };
55572
+ static components = { MenuPopover };
55221
55573
  static props = {
55222
55574
  tableConfig: Object,
55223
55575
  tableStyle: Object,
@@ -55788,6 +56140,17 @@ sidePanelRegistry.add("PivotMeasureDisplayPanel", {
55788
56140
  },
55789
56141
  });
55790
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
+
55791
56154
  const DEFAULT_SIDE_PANEL_SIZE = 350;
55792
56155
  const MIN_SHEET_VIEW_WIDTH = 150;
55793
56156
  class SidePanelStore extends SpreadsheetStore {
@@ -55795,6 +56158,7 @@ class SidePanelStore extends SpreadsheetStore {
55795
56158
  initialPanelProps = {};
55796
56159
  componentTag = "";
55797
56160
  panelSize = DEFAULT_SIDE_PANEL_SIZE;
56161
+ screenWidthStore = this.get(ScreenWidthStore);
55798
56162
  get isOpen() {
55799
56163
  if (!this.componentTag) {
55800
56164
  return false;
@@ -55816,6 +56180,9 @@ class SidePanelStore extends SpreadsheetStore {
55816
56180
  return undefined;
55817
56181
  }
55818
56182
  open(componentTag, panelProps = {}) {
56183
+ if (this.screenWidthStore.isSmall) {
56184
+ return;
56185
+ }
55819
56186
  const state = this.computeState(componentTag, panelProps);
55820
56187
  if (!state.isOpen) {
55821
56188
  return;
@@ -55930,8 +56297,7 @@ class TableResizer extends Component {
55930
56297
  return [];
55931
56298
  return [
55932
56299
  {
55933
- zone: this.state.highlightZone,
55934
- sheetId: this.props.table.range.sheetId,
56300
+ range: this.env.model.getters.getRangeFromZone(this.props.table.range.sheetId, this.state.highlightZone),
55935
56301
  color: COLOR,
55936
56302
  noFill: true,
55937
56303
  },
@@ -55953,13 +56319,14 @@ class Grid extends Component {
55953
56319
  static template = "o-spreadsheet-Grid";
55954
56320
  static props = {
55955
56321
  exposeFocus: Function,
56322
+ getGridSize: Function,
55956
56323
  };
55957
56324
  static components = {
55958
56325
  GridComposer,
55959
56326
  GridOverlay,
55960
56327
  GridPopover,
55961
56328
  HeadersOverlay,
55962
- Menu,
56329
+ MenuPopover,
55963
56330
  Autofill,
55964
56331
  ClientTag,
55965
56332
  Highlight,
@@ -55967,6 +56334,7 @@ class Grid extends Component {
55967
56334
  VerticalScrollBar,
55968
56335
  HorizontalScrollBar,
55969
56336
  TableResizer,
56337
+ Selection,
55970
56338
  };
55971
56339
  HEADER_HEIGHT = HEADER_HEIGHT;
55972
56340
  HEADER_WIDTH = HEADER_WIDTH;
@@ -56249,8 +56617,8 @@ class Grid extends Component {
56249
56617
  }
56250
56618
  onGridResized({ height, width }) {
56251
56619
  this.env.model.dispatch("RESIZE_SHEETVIEW", {
56252
- width: width,
56253
- height: height,
56620
+ width: width - HEADER_WIDTH,
56621
+ height: height - HEADER_HEIGHT,
56254
56622
  gridOffsetX: HEADER_WIDTH,
56255
56623
  gridOffsetY: HEADER_HEIGHT,
56256
56624
  });
@@ -56304,6 +56672,9 @@ class Grid extends Component {
56304
56672
  else {
56305
56673
  this.env.model.selection.selectCell(col, row);
56306
56674
  }
56675
+ if (this.env.isMobile()) {
56676
+ return;
56677
+ }
56307
56678
  let prevCol = col;
56308
56679
  let prevRow = row;
56309
56680
  const onMouseMove = (col, row, ev) => {
@@ -56589,6 +56960,9 @@ class Grid extends Component {
56589
56960
  const sheetId = this.env.model.getters.getActiveSheetId();
56590
56961
  return this.env.model.getters.getCoreTables(sheetId).filter(isStaticTable);
56591
56962
  }
56963
+ get displaySelectionHandler() {
56964
+ return this.env.isMobile() && this.composerFocusStore.activeComposer.editionMode === "inactive";
56965
+ }
56592
56966
  }
56593
56967
 
56594
56968
  /**
@@ -62515,7 +62889,7 @@ class PivotCorePlugin extends CorePlugin {
62515
62889
  break;
62516
62890
  }
62517
62891
  case "UPDATE_PIVOT": {
62518
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
62892
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
62519
62893
  this.compileCalculatedMeasures(cmd.pivot.measures);
62520
62894
  break;
62521
62895
  }
@@ -62586,7 +62960,10 @@ class PivotCorePlugin extends CorePlugin {
62586
62960
  // Private
62587
62961
  // -------------------------------------------------------------------------
62588
62962
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
62589
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
62963
+ this.history.update("pivots", pivotId, {
62964
+ definition: this.repairSortedColumn(deepCopy(pivot)),
62965
+ formulaId,
62966
+ });
62590
62967
  this.compileCalculatedMeasures(pivot.measures);
62591
62968
  this.history.update("formulaIds", formulaId, pivotId);
62592
62969
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -62675,6 +63052,7 @@ class PivotCorePlugin extends CorePlugin {
62675
63052
  }
62676
63053
  }
62677
63054
  checkSortedColumnInMeasures(definition) {
63055
+ definition = this.repairSortedColumn(definition);
62678
63056
  const measures = definition.measures.map((measure) => measure.id);
62679
63057
  if (definition.sortedColumn && !measures.includes(definition.sortedColumn.measure)) {
62680
63058
  return "InvalidDefinition" /* CommandResult.InvalidDefinition */;
@@ -62688,6 +63066,26 @@ class PivotCorePlugin extends CorePlugin {
62688
63066
  }
62689
63067
  return "Success" /* CommandResult.Success */;
62690
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
+ }
62691
63089
  // ---------------------------------------------------------------------
62692
63090
  // Import/Export
62693
63091
  // ---------------------------------------------------------------------
@@ -65555,186 +65953,6 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
65555
65953
  }
65556
65954
  }
65557
65955
 
65558
- const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
65559
- css /* scss */ `
65560
- .o-dv-checkbox {
65561
- margin: ${MARGIN}px;
65562
- /* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
65563
- position: absolute;
65564
- }
65565
- `;
65566
- class DataValidationCheckbox extends Component {
65567
- static template = "o-spreadsheet-DataValidationCheckbox";
65568
- static components = {
65569
- Checkbox,
65570
- };
65571
- static props = {
65572
- cellPosition: Object,
65573
- };
65574
- onCheckboxChange(value) {
65575
- const { sheetId, col, row } = this.props.cellPosition;
65576
- const cellContent = value ? "TRUE" : "FALSE";
65577
- this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
65578
- }
65579
- get checkBoxValue() {
65580
- return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
65581
- }
65582
- get isDisabled() {
65583
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
65584
- return this.env.model.getters.isReadonly() || !!cell?.isFormula;
65585
- }
65586
- }
65587
-
65588
- const ICON_WIDTH = 13;
65589
- css /* scss */ `
65590
- .o-dv-list-icon {
65591
- color: ${TEXT_BODY_MUTED};
65592
- border-radius: 1px;
65593
- height: ${GRID_ICON_EDGE_LENGTH}px;
65594
- width: ${GRID_ICON_EDGE_LENGTH}px;
65595
-
65596
- &:hover {
65597
- color: #ffffff;
65598
- background-color: ${TEXT_BODY_MUTED};
65599
- }
65600
-
65601
- svg {
65602
- width: ${ICON_WIDTH}px;
65603
- height: ${ICON_WIDTH}px;
65604
- }
65605
- }
65606
- `;
65607
- class DataValidationListIcon extends Component {
65608
- static template = "o-spreadsheet-DataValidationListIcon";
65609
- static props = {
65610
- cellPosition: Object,
65611
- };
65612
- onClick() {
65613
- const { col, row } = this.props.cellPosition;
65614
- this.env.model.selection.selectCell(col, row);
65615
- this.env.startCellEdition();
65616
- }
65617
- }
65618
-
65619
- css /* scss */ `
65620
- .o-filter-icon {
65621
- color: ${FILTERS_COLOR};
65622
- display: flex;
65623
- align-items: center;
65624
- justify-content: center;
65625
- width: ${GRID_ICON_EDGE_LENGTH}px;
65626
- height: ${GRID_ICON_EDGE_LENGTH}px;
65627
-
65628
- &:hover {
65629
- background: ${FILTERS_COLOR};
65630
- color: #fff;
65631
- }
65632
-
65633
- &.o-high-contrast {
65634
- color: #defade;
65635
- }
65636
- &.o-high-contrast:hover {
65637
- color: ${FILTERS_COLOR};
65638
- background: #fff;
65639
- }
65640
- }
65641
- .o-filter-icon:hover {
65642
- background: ${FILTERS_COLOR};
65643
- color: #fff;
65644
- }
65645
- `;
65646
- class FilterIcon extends Component {
65647
- static template = "o-spreadsheet-FilterIcon";
65648
- static props = {
65649
- cellPosition: Object,
65650
- };
65651
- cellPopovers;
65652
- setup() {
65653
- this.cellPopovers = useStore(CellPopoverStore);
65654
- }
65655
- onClick() {
65656
- const position = this.props.cellPosition;
65657
- const activePopover = this.cellPopovers.persistentCellPopover;
65658
- const { col, row } = position;
65659
- if (activePopover.isOpen &&
65660
- activePopover.col === col &&
65661
- activePopover.row === row &&
65662
- activePopover.type === "FilterMenu") {
65663
- this.cellPopovers.close();
65664
- return;
65665
- }
65666
- this.cellPopovers.open({ col, row }, "FilterMenu");
65667
- }
65668
- get isFilterActive() {
65669
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
65670
- }
65671
- get iconClass() {
65672
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
65673
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
65674
- return luminance < 0.45 ? "o-high-contrast" : "";
65675
- }
65676
- }
65677
-
65678
- css /* scss */ `
65679
- .o-spreadsheet {
65680
- .o-pivot-collapse-icon {
65681
- cursor: pointer;
65682
- width: 11px;
65683
- height: 11px;
65684
- border: 1px solid #777;
65685
- background-color: #eee;
65686
- margin: 3px 0 3px 6px;
65687
-
65688
- .o-icon {
65689
- width: 5px;
65690
- height: 5px;
65691
- }
65692
- }
65693
- }
65694
- `;
65695
- class PivotCollapseIcon extends Component {
65696
- static template = "o-spreadsheet-PivotCollapseIcon";
65697
- static props = {
65698
- cellPosition: Object,
65699
- };
65700
- onClick() {
65701
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65702
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65703
- if (!pivotId || pivotCell.type !== "HEADER") {
65704
- return;
65705
- }
65706
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65707
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
65708
- ? [...definition.collapsedDomains[pivotCell.dimension]]
65709
- : [];
65710
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
65711
- if (index !== -1) {
65712
- collapsedDomains.splice(index, 1);
65713
- }
65714
- else {
65715
- collapsedDomains.push(pivotCell.domain);
65716
- }
65717
- const newDomains = definition.collapsedDomains
65718
- ? { ...definition.collapsedDomains }
65719
- : { COL: [], ROW: [] };
65720
- newDomains[pivotCell.dimension] = collapsedDomains;
65721
- this.env.model.dispatch("UPDATE_PIVOT", {
65722
- pivotId,
65723
- pivot: { ...definition, collapsedDomains: newDomains },
65724
- });
65725
- }
65726
- get isCollapsed() {
65727
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65728
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65729
- if (!pivotId || pivotCell.type !== "HEADER") {
65730
- return false;
65731
- }
65732
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65733
- const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
65734
- return domains?.some((domain) => deepEquals(domain, pivotCell.domain));
65735
- }
65736
- }
65737
-
65738
65956
  /**
65739
65957
  * Registry to draw icons on cells
65740
65958
  */
@@ -65742,14 +65960,25 @@ const iconsOnCellRegistry = new Registry();
65742
65960
  iconsOnCellRegistry.add("data_validation_checkbox", (getters, position) => {
65743
65961
  const hasIcon = getters.isCellValidCheckbox(position);
65744
65962
  if (hasIcon) {
65963
+ const value = !!getters.getEvaluatedCell(position).value;
65745
65964
  return {
65746
- svg: undefined,
65965
+ svg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED,
65966
+ hoverSvg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED_HOVERED,
65747
65967
  priority: 2,
65748
65968
  horizontalAlign: "center",
65749
65969
  size: GRID_ICON_EDGE_LENGTH,
65750
65970
  margin: GRID_ICON_MARGIN,
65751
- component: DataValidationCheckbox,
65752
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
+ },
65753
65982
  };
65754
65983
  }
65755
65984
  return undefined;
@@ -65758,13 +65987,19 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65758
65987
  const hasIcon = !getters.isReadonly() && getters.cellHasListDataValidationIcon(position);
65759
65988
  if (hasIcon) {
65760
65989
  return {
65761
- svg: undefined,
65990
+ svg: CARET_DOWN,
65991
+ hoverSvg: HOVERED_CARET_DOWN,
65762
65992
  priority: 2,
65763
65993
  horizontalAlign: "right",
65764
65994
  size: GRID_ICON_EDGE_LENGTH,
65765
65995
  margin: GRID_ICON_MARGIN,
65766
- component: DataValidationListIcon,
65767
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",
65768
66003
  };
65769
66004
  }
65770
66005
  return undefined;
@@ -65772,14 +66007,30 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65772
66007
  iconsOnCellRegistry.add("filter_icon", (getters, position) => {
65773
66008
  const hasIcon = getters.isFilterHeader(position);
65774
66009
  if (hasIcon) {
66010
+ const isFilterActive = getters.isFilterActive(position);
66011
+ const cellStyle = getters.getCellComputedStyle(position);
66012
+ const isHighContrast = relativeLuminance(cellStyle.fillColor || "#fff") < 0.45;
65775
66013
  return {
65776
- svg: undefined,
66014
+ type: "filter_icon",
66015
+ svg: getDataFilterIcon(isFilterActive, isHighContrast, false),
66016
+ hoverSvg: getDataFilterIcon(isFilterActive, isHighContrast, true),
65777
66017
  priority: 3,
65778
66018
  horizontalAlign: "right",
65779
66019
  size: GRID_ICON_EDGE_LENGTH,
65780
66020
  margin: GRID_ICON_MARGIN,
65781
- component: FilterIcon,
65782
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
+ },
65783
66034
  };
65784
66035
  }
65785
66036
  return undefined;
@@ -65789,6 +66040,7 @@ iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
65789
66040
  if (icon) {
65790
66041
  const style = getters.getCellStyle(position);
65791
66042
  return {
66043
+ type: "conditional_formatting",
65792
66044
  svg: ICONS[icon].svg,
65793
66045
  priority: 1,
65794
66046
  horizontalAlign: "left",
@@ -65809,23 +66061,55 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
65809
66061
  const definition = getters.getPivotCoreDefinition(pivotId);
65810
66062
  const isDashboard = getters.isDashboard();
65811
66063
  const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
65812
- 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;
65813
66068
  return {
66069
+ type: "pivot_collapse",
65814
66070
  priority: 4,
65815
66071
  horizontalAlign: "left",
65816
- size: !!component || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
65817
- ? GRID_ICON_EDGE_LENGTH
66072
+ size: hasIcon || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
66073
+ ? PIVOT_COLLAPSE_ICON_SIZE
65818
66074
  : 0,
65819
- margin: pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0,
65820
- component,
66075
+ margin: hasIcon ? GRID_ICON_MARGIN * 2 + indent : indent,
66076
+ svg: hasIcon ? getPivotIconSvg(isCollapsed, false) : undefined,
66077
+ hoverSvg: hasIcon ? getPivotIconSvg(isCollapsed, true) : undefined,
65821
66078
  position,
66079
+ onClick: togglePivotCollapse,
65822
66080
  };
65823
66081
  }
65824
66082
  return undefined;
65825
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
+ }
65826
66110
 
65827
66111
  class CellIconPlugin extends CoreViewPlugin {
65828
- static getters = ["doesCellHaveGridIcon", "getCellIcons"];
66112
+ static getters = ["doesCellHaveGridIcon", "getCellIcons", "getCellIconRect"];
65829
66113
  cellIconsCache = {};
65830
66114
  handle(cmd) {
65831
66115
  if (cmd.type !== "SET_VIEWPORT_OFFSET") {
@@ -65845,6 +66129,29 @@ class CellIconPlugin extends CoreViewPlugin {
65845
66129
  }
65846
66130
  return this.cellIconsCache[position.sheetId][position.col][position.row];
65847
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
+ }
65848
66155
  computeCellIcons(position) {
65849
66156
  const icons = { left: undefined, right: undefined, center: undefined };
65850
66157
  const callbacks = iconsOnCellRegistry.getAll();
@@ -70071,6 +70378,7 @@ class SheetUIPlugin extends UIPlugin {
70071
70378
  "getCellText",
70072
70379
  "getCellMultiLineText",
70073
70380
  "getContiguousZone",
70381
+ "computeTextYCoordinate",
70074
70382
  ];
70075
70383
  ctx = document.createElement("canvas").getContext("2d");
70076
70384
  // ---------------------------------------------------------------------------
@@ -70173,6 +70481,25 @@ class SheetUIPlugin extends UIPlugin {
70173
70481
  });
70174
70482
  return splitTextToWidth(this.ctx, text, style, args.wrapText ? args.maxWidth : undefined);
70175
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
+ }
70176
70503
  /**
70177
70504
  * Expands the given zone until bordered by empty cells or reached the sheet boundaries.
70178
70505
  */
@@ -72198,6 +72525,7 @@ class GridSelectionPlugin extends UIPlugin {
72198
72525
  "getElementsFromSelection",
72199
72526
  "tryGetActiveSheetId",
72200
72527
  "isGridSelectionActive",
72528
+ "getSelectecUnboundedZone",
72201
72529
  ];
72202
72530
  gridSelection = {
72203
72531
  anchor: {
@@ -72209,12 +72537,14 @@ class GridSelectionPlugin extends UIPlugin {
72209
72537
  selectedFigureId = null;
72210
72538
  sheetsData = {};
72211
72539
  moveClient;
72540
+ isUnbounded;
72212
72541
  // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
72213
72542
  // the main command.
72214
72543
  activeSheet = null;
72215
72544
  constructor(config) {
72216
72545
  super(config);
72217
72546
  this.moveClient = config.moveClient;
72547
+ this.isUnbounded = false;
72218
72548
  }
72219
72549
  // ---------------------------------------------------------------------------
72220
72550
  // Command Handling
@@ -72240,6 +72570,7 @@ class GridSelectionPlugin extends UIPlugin {
72240
72570
  handleEvent(event) {
72241
72571
  const anchor = event.anchor;
72242
72572
  let zones = [];
72573
+ this.isUnbounded = event.options?.unbounded || false;
72243
72574
  switch (event.mode) {
72244
72575
  case "overrideSelection":
72245
72576
  zones = [anchor.zone];
@@ -72429,6 +72760,12 @@ class GridSelectionPlugin extends UIPlugin {
72429
72760
  getSelectedZone() {
72430
72761
  return deepCopy(this.gridSelection.anchor.zone);
72431
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
+ }
72432
72769
  getSelection() {
72433
72770
  return deepCopy(this.gridSelection);
72434
72771
  }
@@ -72622,11 +72959,6 @@ class GridSelectionPlugin extends UIPlugin {
72622
72959
  },
72623
72960
  ];
72624
72961
  const sheetId = this.getActiveSheetId();
72625
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
72626
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
72627
- if (!data) {
72628
- return;
72629
- }
72630
72962
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
72631
72963
  const pasteTarget = [
72632
72964
  {
@@ -72636,7 +72968,14 @@ class GridSelectionPlugin extends UIPlugin {
72636
72968
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
72637
72969
  },
72638
72970
  ];
72639
- 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
+ }
72640
72979
  const selection = pasteTarget[0];
72641
72980
  const col = selection.left;
72642
72981
  const row = selection.top;
@@ -73182,6 +73521,7 @@ class SheetViewPlugin extends UIPlugin {
73182
73521
  "getRect",
73183
73522
  "getFigureUI",
73184
73523
  "getPositionAnchorOffset",
73524
+ "getGridOffset",
73185
73525
  ];
73186
73526
  viewports = {};
73187
73527
  /**
@@ -73371,6 +73711,9 @@ class SheetViewPlugin extends UIPlugin {
73371
73711
  height: this.sheetViewHeight,
73372
73712
  };
73373
73713
  }
73714
+ getGridOffset() {
73715
+ return { x: this.gridOffsetX, y: this.gridOffsetY };
73716
+ }
73374
73717
  /** type as pane, not viewport but basically pane extends viewport */
73375
73718
  getActiveMainViewport() {
73376
73719
  const sheetId = this.getters.getActiveSheetId();
@@ -74749,6 +75092,7 @@ topbarMenuRegistry
74749
75092
  name: _t("Settings"),
74750
75093
  sequence: 100,
74751
75094
  execute: (env) => env.openSidePanel("Settings"),
75095
+ isEnabled: (env) => !env.isSmall,
74752
75096
  icon: "o-spreadsheet-Icon.COG",
74753
75097
  })
74754
75098
  // ---------------------------------------------------------------------
@@ -75166,6 +75510,7 @@ topbarMenuRegistry
75166
75510
  execute: (env) => {
75167
75511
  env.openSidePanel("DataValidation");
75168
75512
  },
75513
+ isEnabled: (env) => !env.isSmall,
75169
75514
  icon: "o-spreadsheet-Icon.DATA_VALIDATION",
75170
75515
  sequence: 30,
75171
75516
  separator: true,
@@ -75189,6 +75534,7 @@ topbarMenuRegistry
75189
75534
  sequence: sequence + index,
75190
75535
  isReadonlyAllowed: true,
75191
75536
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
75537
+ isEnabled: (env) => !env.isSmall,
75192
75538
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
75193
75539
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
75194
75540
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -75460,7 +75806,7 @@ css /* scss */ `
75460
75806
  .o-sheet {
75461
75807
  padding: 0 15px;
75462
75808
  padding-right: 10px;
75463
- height: ${BOTTOMBAR_HEIGHT}px;
75809
+ height: ${DESKTOP_BOTTOMBAR_HEIGHT}px;
75464
75810
  border-left: 1px solid #c1c1c1;
75465
75811
  border-right: 1px solid #c1c1c1;
75466
75812
  margin-left: -1px;
@@ -75504,6 +75850,10 @@ css /* scss */ `
75504
75850
  width: calc(100% - 1px);
75505
75851
  }
75506
75852
  }
75853
+
75854
+ .o-spreadshet-mobile .o-sheet {
75855
+ height: ${MOBILE_BOTTOMBAR_HEIGHT}px;
75856
+ }
75507
75857
  `;
75508
75858
  class BottomBarSheet extends Component {
75509
75859
  static template = "o-spreadsheet-BottomBarSheet";
@@ -75558,7 +75908,16 @@ class BottomBarSheet extends Component {
75558
75908
  this.stopEdition();
75559
75909
  }
75560
75910
  }
75911
+ onClick() {
75912
+ if (!this.env.isMobile()) {
75913
+ return;
75914
+ }
75915
+ this.activateSheet();
75916
+ }
75561
75917
  onMouseDown(ev) {
75918
+ if (this.env.isMobile()) {
75919
+ return;
75920
+ }
75562
75921
  this.activateSheet();
75563
75922
  this.props.onMouseDown(ev);
75564
75923
  }
@@ -75902,8 +76261,8 @@ css /* scss */ `
75902
76261
  }
75903
76262
  }
75904
76263
 
75905
- .mobile.o-spreadsheet-bottom-bar {
75906
- padding-left: 1rem;
76264
+ .o-spreadsheet-mobile .o-spreadsheet-bottom-bar {
76265
+ padding-left: 0;
75907
76266
 
75908
76267
  .add-sheet-container {
75909
76268
  order: 2;
@@ -75920,10 +76279,8 @@ css /* scss */ `
75920
76279
  `;
75921
76280
  class BottomBar extends Component {
75922
76281
  static template = "o-spreadsheet-BottomBar";
75923
- static props = {
75924
- onClick: Function,
75925
- };
75926
- static components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
76282
+ static props = { onClick: Function };
76283
+ static components = { MenuPopover, Ripple, BottomBarSheet, BottomBarStatistic };
75927
76284
  bottomBarRef = useRef("bottomBar");
75928
76285
  sheetListRef = useRef("sheetList");
75929
76286
  dragAndDrop = useDragAndDropListItems();
@@ -76060,6 +76417,9 @@ class BottomBar extends Component {
76060
76417
  if (event.button !== 0 || this.env.model.getters.isReadonly())
76061
76418
  return;
76062
76419
  this.closeMenu();
76420
+ if (this.env.isMobile()) {
76421
+ return;
76422
+ }
76063
76423
  const visibleSheets = this.getVisibleSheets();
76064
76424
  const sheetRects = this.getSheetItemRects();
76065
76425
  const sheets = visibleSheets.map((sheet, index) => ({
@@ -76179,7 +76539,7 @@ css /* scss */ `
76179
76539
  `;
76180
76540
  class SpreadsheetDashboard extends Component {
76181
76541
  static template = "o-spreadsheet-SpreadsheetDashboard";
76182
- static props = {};
76542
+ static props = { getGridSize: Function };
76183
76543
  static components = {
76184
76544
  GridOverlay,
76185
76545
  GridPopover,
@@ -76466,7 +76826,7 @@ class HeaderGroupContainer extends Component {
76466
76826
  dimension: String,
76467
76827
  layers: Array,
76468
76828
  };
76469
- static components = { RowGroup, ColGroup, Menu };
76829
+ static components = { RowGroup, ColGroup, MenuPopover };
76470
76830
  menu = useState({ isOpen: false, anchorRect: null, menuItems: [] });
76471
76831
  getLayerOffset(layerIndex) {
76472
76832
  return layerIndex * GROUP_LAYER_WIDTH;
@@ -76682,6 +77042,158 @@ class SidePanel extends Component {
76682
77042
  }
76683
77043
  }
76684
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
+
76685
77197
  const COMPOSER_MAX_HEIGHT = 100;
76686
77198
  /* svg free of use from https://uxwing.com/formula-fx-icon/ */
76687
77199
  const FX_SVG = /*xml*/ `
@@ -76691,7 +77203,7 @@ const FX_SVG = /*xml*/ `
76691
77203
  `;
76692
77204
  css /* scss */ `
76693
77205
  .o-topbar-composer-container {
76694
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
77206
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
76695
77207
  }
76696
77208
 
76697
77209
  .o-topbar-composer {
@@ -76746,7 +77258,7 @@ class TopBarComposer extends Component {
76746
77258
  "max-height": `${COMPOSER_MAX_HEIGHT}px`,
76747
77259
  "line-height": "24px",
76748
77260
  };
76749
- style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
77261
+ style.height = this.focus === "inactive" ? `${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
76750
77262
  return cssPropertiesToCss(style);
76751
77263
  }
76752
77264
  get containerStyle() {
@@ -77249,7 +77761,7 @@ class TopBarFontSizeEditor extends Component {
77249
77761
 
77250
77762
  class NumberFormatsTool extends Component {
77251
77763
  static template = "o-spreadsheet-NumberFormatsTool";
77252
- static components = { Menu, ActionButton };
77764
+ static components = { MenuPopover, ActionButton };
77253
77765
  static props = { class: String };
77254
77766
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
77255
77767
  topBarToolStore;
@@ -77299,7 +77811,7 @@ topBarToolBarRegistry
77299
77811
  .addChild("edit", {
77300
77812
  component: PaintFormatButton,
77301
77813
  props: {
77302
- class: "o-hoverable-button o-toolbar-button",
77814
+ class: "o-hoverable-button o-toolbar-button o-mobile-disabled",
77303
77815
  },
77304
77816
  sequence: 3,
77305
77817
  })
@@ -77458,7 +77970,7 @@ topBarToolBarRegistry
77458
77970
  .add("misc")
77459
77971
  .addChild("misc", {
77460
77972
  component: TableDropdownButton,
77461
- 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" },
77462
77974
  sequence: 1,
77463
77975
  })
77464
77976
  .addChild("misc", {
@@ -77478,6 +77990,7 @@ css /* scss */ `
77478
77990
  border-right: 1px solid ${SEPARATOR_COLOR};
77479
77991
  width: 0;
77480
77992
  margin: 0 6px;
77993
+ height: 30px;
77481
77994
  }
77482
77995
 
77483
77996
  .o-toolbar-button {
@@ -77510,7 +78023,7 @@ css /* scss */ `
77510
78023
 
77511
78024
  .irregularity-map {
77512
78025
  border-top: 1px solid ${SEPARATOR_COLOR};
77513
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78026
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77514
78027
 
77515
78028
  .alert-info {
77516
78029
  border-left: 3px solid ${ALERT_INFO_BORDER};
@@ -77523,7 +78036,7 @@ css /* scss */ `
77523
78036
 
77524
78037
  /* Toolbar */
77525
78038
  .o-topbar-toolbar {
77526
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78039
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77527
78040
 
77528
78041
  .o-readonly-toolbar {
77529
78042
  background-color: ${BACKGROUND_HEADER_COLOR};
@@ -77537,6 +78050,24 @@ css /* scss */ `
77537
78050
  }
77538
78051
  }
77539
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
+ }
77540
78071
  `;
77541
78072
  class TopBar extends Component {
77542
78073
  static template = "o-spreadsheet-TopBar";
@@ -77545,7 +78076,7 @@ class TopBar extends Component {
77545
78076
  dropdownMaxHeight: Number,
77546
78077
  };
77547
78078
  static components = {
77548
- Menu,
78079
+ MenuPopover,
77549
78080
  TopBarComposer,
77550
78081
  Popover,
77551
78082
  };
@@ -77625,7 +78156,7 @@ class TopBar extends Component {
77625
78156
  // TODO : manage click events better. We need this piece of code
77626
78157
  // otherwise the event opening the menu would close it on the same frame.
77627
78158
  // And we cannot stop the event propagation because it's used in an
77628
- // 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
77629
78160
  // clicking on the top bar
77630
78161
  if (this.openedEl === ev.target) {
77631
78162
  return;
@@ -78051,6 +78582,11 @@ css /* scss */ `
78051
78582
  color: ${TEXT_BODY};
78052
78583
  }
78053
78584
  }
78585
+
78586
+ .o-spreadsheet-topbar-wrapper,
78587
+ .o-spreadsheet-bottombar-wrapper {
78588
+ z-index: ${ComponentsImportance.ScrollBar + 1};
78589
+ }
78054
78590
  `;
78055
78591
  class Spreadsheet extends Component {
78056
78592
  static template = "o-spreadsheet-Spreadsheet";
@@ -78064,6 +78600,7 @@ class Spreadsheet extends Component {
78064
78600
  TopBar,
78065
78601
  Grid,
78066
78602
  BottomBar,
78603
+ SmallBottomBar,
78067
78604
  SidePanel,
78068
78605
  SpreadsheetDashboard,
78069
78606
  HeaderGroupContainer,
@@ -78087,12 +78624,25 @@ class Spreadsheet extends Component {
78087
78624
  else {
78088
78625
  properties["grid-template-rows"] = `min-content auto min-content`;
78089
78626
  }
78090
- 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}`;
78091
78629
  return cssPropertiesToCss(properties);
78092
78630
  }
78093
78631
  setup() {
78632
+ if (!("isSmall" in this.env)) {
78633
+ const screenSize = useScreenWidth();
78634
+ useSubEnv({
78635
+ get isSmall() {
78636
+ return screenSize.isSmall;
78637
+ },
78638
+ });
78639
+ }
78094
78640
  const stores = useStoreProvider();
78095
78641
  stores.inject(ModelStore, this.model);
78642
+ const env = this.env;
78643
+ stores.get(ScreenWidthStore).setSmallThreshhold(() => {
78644
+ return env.isSmall;
78645
+ });
78096
78646
  this.notificationStore = useStore(NotificationStore);
78097
78647
  this.composerFocusStore = useStore(ComposerFocusStore);
78098
78648
  this.sidePanel = useStore(SidePanelStore);
@@ -78110,15 +78660,8 @@ class Spreadsheet extends Component {
78110
78660
  notifyUser: (notification) => this.notificationStore.notifyUser(notification),
78111
78661
  askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
78112
78662
  raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
78663
+ isMobile: isMobileOS,
78113
78664
  });
78114
- if (!("isSmall" in this.env)) {
78115
- const screenSize = useScreenWidth();
78116
- useSubEnv({
78117
- get isSmall() {
78118
- return screenSize.isSmall;
78119
- },
78120
- });
78121
- }
78122
78665
  this.notificationStore.updateNotificationCallbacks({ ...this.props });
78123
78666
  useEffect(() => {
78124
78667
  /**
@@ -78224,6 +78767,24 @@ class Spreadsheet extends Component {
78224
78767
  const sheetId = this.env.model.getters.getActiveSheetId();
78225
78768
  return this.env.model.getters.getVisibleGroupLayers(sheetId, "COL");
78226
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
+ }
78227
78788
  }
78228
78789
 
78229
78790
  function inverseCommand(cmd) {
@@ -82486,7 +83047,7 @@ const SPREADSHEET_DIMENSIONS = {
82486
83047
  MIN_COL_WIDTH,
82487
83048
  HEADER_HEIGHT,
82488
83049
  HEADER_WIDTH,
82489
- BOTTOMBAR_HEIGHT,
83050
+ DESKTOP_BOTTOMBAR_HEIGHT,
82490
83051
  DEFAULT_CELL_WIDTH,
82491
83052
  DEFAULT_CELL_HEIGHT,
82492
83053
  SCROLLBAR_WIDTH,
@@ -82632,7 +83193,7 @@ const components = {
82632
83193
  FunnelChartDesignPanel,
82633
83194
  ChartTypePicker,
82634
83195
  FigureComponent,
82635
- Menu,
83196
+ MenuPopover,
82636
83197
  Popover,
82637
83198
  SelectionInput,
82638
83199
  ValidationMessages,
@@ -82696,6 +83257,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
82696
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 };
82697
83258
 
82698
83259
 
82699
- __info__.version = "18.4.0-alpha.5";
82700
- __info__.date = "2025-05-26T12:36:36.693Z";
82701
- __info__.hash = "398e610";
83260
+ __info__.version = "18.4.0-alpha.7";
83261
+ __info__.date = "2025-06-06T09:32:44.285Z";
83262
+ __info__.hash = "2bfbe64";