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