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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.4.0-alpha.6
6
- * @date 2025-05-30T08:44:33.216Z
7
- * @hash eecf7e4
5
+ * @version 18.4.0-alpha.8
6
+ * @date 2025-06-12T09:53:48.133Z
7
+ * @hash 9b7a8d0
8
8
  */
9
9
 
10
10
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, useExternalListener, onWillUpdateProps, onWillStart, onWillPatch, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -130,7 +130,7 @@ class Registry {
130
130
 
131
131
  const CANVAS_SHIFT = 0.5;
132
132
  // Colors
133
- const HIGHLIGHT_COLOR = "#37A850";
133
+ const HIGHLIGHT_COLOR = "#017E84";
134
134
  const BACKGROUND_GRAY_COLOR = "#f5f5f5";
135
135
  const BACKGROUND_HEADER_COLOR = "#F8F9FA";
136
136
  const BACKGROUND_HEADER_SELECTED_COLOR = "#E8EAED";
@@ -143,7 +143,7 @@ const CELL_BORDER_COLOR = "#E2E3E3";
143
143
  const BACKGROUND_CHART_COLOR = "#FFFFFF";
144
144
  const DISABLED_TEXT_COLOR = "#CACACA";
145
145
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
146
- const LINK_COLOR = "#017E84";
146
+ const LINK_COLOR = HIGHLIGHT_COLOR;
147
147
  const FILTERS_COLOR = "#188038";
148
148
  const SEPARATOR_COLOR = "#E0E2E4";
149
149
  const ICONS_COLOR = "#4A4F59";
@@ -172,7 +172,7 @@ const BUTTON_HOVER_BG = GRAY_300;
172
172
  const BUTTON_HOVER_TEXT_COLOR = "#111827";
173
173
  const BUTTON_ACTIVE_BG = "#e6f2f3";
174
174
  const BUTTON_ACTIVE_TEXT_COLOR = "#111827";
175
- const ACTION_COLOR = "#017E84";
175
+ const ACTION_COLOR = HIGHLIGHT_COLOR;
176
176
  const ACTION_COLOR_HOVER = "#01585c";
177
177
  const ALERT_WARNING_BG = "#FBEBCC";
178
178
  const ALERT_WARNING_BORDER = "#F8E2B3";
@@ -279,8 +279,10 @@ const MIN_ROW_HEIGHT = 10;
279
279
  const MIN_COL_WIDTH = 5;
280
280
  const HEADER_HEIGHT = 26;
281
281
  const HEADER_WIDTH = 48;
282
- const TOPBAR_TOOLBAR_HEIGHT = 34;
283
- const BOTTOMBAR_HEIGHT = 36;
282
+ const DESKTOP_TOPBAR_TOOLBAR_HEIGHT = 34;
283
+ const MOBILE_TOPBAR_TOOLBAR_HEIGHT = 44;
284
+ const DESKTOP_BOTTOMBAR_HEIGHT = 36;
285
+ const MOBILE_BOTTOMBAR_HEIGHT = 44;
284
286
  const DEFAULT_CELL_WIDTH = 96;
285
287
  const DEFAULT_CELL_HEIGHT = 23;
286
288
  const SCROLLBAR_WIDTH = 15;
@@ -301,7 +303,8 @@ const MOBILE_WIDTH_BREAKPOINT = 768;
301
303
  // Menus
302
304
  const MENU_WIDTH = 250;
303
305
  const MENU_VERTICAL_PADDING = 6;
304
- const MENU_ITEM_HEIGHT = 26;
306
+ const DESKTOP_MENU_ITEM_HEIGHT = 26;
307
+ const MOBILE_MENU_ITEM_HEIGHT = 35;
305
308
  const MENU_ITEM_PADDING_HORIZONTAL = 11;
306
309
  const MENU_ITEM_PADDING_VERTICAL = 4;
307
310
  const MENU_SEPARATOR_BORDER_WIDTH = 1;
@@ -399,6 +402,7 @@ const PIVOT_TABLE_CONFIG = {
399
402
  automaticAutofill: false,
400
403
  };
401
404
  const PIVOT_INDENT = 15;
405
+ const PIVOT_COLLAPSE_ICON_SIZE = 12;
402
406
  const DEFAULT_CURRENCY = {
403
407
  symbol: "$",
404
408
  position: "before",
@@ -1562,6 +1566,19 @@ class AlternatingColorGenerator extends ColorGenerator {
1562
1566
  this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
1563
1567
  }
1564
1568
  }
1569
+ class AlternatingColorMap {
1570
+ availableColors;
1571
+ colors = {};
1572
+ constructor(paletteSize = 12) {
1573
+ this.availableColors = new AlternatingColorGenerator(paletteSize);
1574
+ }
1575
+ get(id) {
1576
+ if (!this.colors[id]) {
1577
+ this.colors[id] = this.availableColors.next();
1578
+ }
1579
+ return this.colors[id];
1580
+ }
1581
+ }
1565
1582
  /**
1566
1583
  * Returns a function that maps a value to a color using a color scale defined by the given
1567
1584
  * color/threshold values pairs.
@@ -5001,7 +5018,9 @@ function isTextFormat(format) {
5001
5018
  }
5002
5019
 
5003
5020
  function evaluateLiteral(literalCell, localeFormat) {
5004
- const value = isTextFormat(localeFormat.format) ? literalCell.content : literalCell.parsedValue;
5021
+ const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
5022
+ ? literalCell.content
5023
+ : literalCell.parsedValue;
5005
5024
  const functionResult = { value, format: localeFormat.format };
5006
5025
  return createEvaluatedCell(functionResult, localeFormat.locale);
5007
5026
  }
@@ -5050,6 +5069,9 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5050
5069
  if (isEvaluationError(value)) {
5051
5070
  return errorCell(value, message);
5052
5071
  }
5072
+ if (value === null) {
5073
+ return emptyCell(format);
5074
+ }
5053
5075
  if (isTextFormat(format)) {
5054
5076
  // TO DO:
5055
5077
  // with the next line, the value of the cell is transformed depending on the format.
@@ -5057,9 +5079,6 @@ function _createEvaluatedCell(functionResult, locale, cell) {
5057
5079
  // to interpret the value as a number.
5058
5080
  return textCell(toString(value), format, formattedValue);
5059
5081
  }
5060
- if (value === null) {
5061
- return emptyCell(format);
5062
- }
5063
5082
  if (typeof value === "number") {
5064
5083
  if (isDateTimeFormat(format || "")) {
5065
5084
  return dateTimeCell(value, format, formattedValue);
@@ -19347,8 +19366,9 @@ const PIVOT = {
19347
19366
  arg("include_total (boolean, default=TRUE)", _t("Whether to include total/sub-totals or not.")),
19348
19367
  arg("include_column_titles (boolean, default=TRUE)", _t("Whether to include the column titles or not.")),
19349
19368
  arg("column_count (number, optional)", _t("number of columns")),
19369
+ arg("include_measure_titles (boolean, default=TRUE)", _t("Whether to include the measure titles row or not.")),
19350
19370
  ],
19351
- compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19371
+ compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }, includeMeasureTitles = { value: true }) {
19352
19372
  const _pivotFormulaId = toString(pivotFormulaId);
19353
19373
  const _rowCount = toNumber(rowCount, this.locale);
19354
19374
  if (_rowCount < 0) {
@@ -19358,8 +19378,11 @@ const PIVOT = {
19358
19378
  if (_columnCount < 0) {
19359
19379
  return new EvaluationError(_t("The number of columns must be positive."));
19360
19380
  }
19361
- const _includeColumnHeaders = toBoolean(includeColumnHeaders);
19362
- const _includedTotal = toBoolean(includeTotal);
19381
+ const visibilityOptions = {
19382
+ displayColumnHeaders: toBoolean(includeColumnHeaders),
19383
+ displayTotals: toBoolean(includeTotal),
19384
+ displayMeasuresRow: toBoolean(includeMeasureTitles),
19385
+ };
19363
19386
  const pivotId = getPivotId(_pivotFormulaId, this.getters);
19364
19387
  const pivot = this.getters.getPivot(pivotId);
19365
19388
  const coreDefinition = this.getters.getPivotCoreDefinition(pivotId);
@@ -19370,9 +19393,15 @@ const PIVOT = {
19370
19393
  return error;
19371
19394
  }
19372
19395
  const table = pivot.getCollapsedTableStructure();
19373
- const cells = table.getPivotCells(_includedTotal, _includeColumnHeaders);
19374
- const headerRows = _includeColumnHeaders ? table.columns.length : 0;
19375
- const pivotTitle = this.getters.getPivotDisplayName(pivotId);
19396
+ const cells = table.getPivotCells(visibilityOptions);
19397
+ let headerRows = 0;
19398
+ if (visibilityOptions.displayColumnHeaders) {
19399
+ headerRows = table.columns.length - 1;
19400
+ }
19401
+ if (visibilityOptions.displayMeasuresRow) {
19402
+ headerRows++;
19403
+ }
19404
+ const pivotTitle = this.getters.getPivotName(pivotId);
19376
19405
  const tableHeight = Math.min(headerRows + _rowCount, cells[0].length);
19377
19406
  if (tableHeight === 0) {
19378
19407
  return [[{ value: pivotTitle }]];
@@ -19400,7 +19429,7 @@ const PIVOT = {
19400
19429
  }
19401
19430
  }
19402
19431
  }
19403
- if (_includeColumnHeaders) {
19432
+ if (visibilityOptions.displayColumnHeaders || visibilityOptions.displayMeasuresRow) {
19404
19433
  result[0][0] = { value: pivotTitle };
19405
19434
  }
19406
19435
  return result;
@@ -20556,7 +20585,7 @@ const TEXT = {
20556
20585
  description: _t("Converts a number to text according to a specified format."),
20557
20586
  args: [
20558
20587
  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.")),
20588
+ 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
20589
  ],
20561
20590
  compute: function (number, format) {
20562
20591
  const _number = toNumber(number, this.locale);
@@ -21663,8 +21692,6 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21663
21692
  if (isNaN(value)) {
21664
21693
  continue;
21665
21694
  }
21666
- const axisId = chart.config.type === "radar" ? dataset.rAxisID : dataset.yAxisID;
21667
- const displayValue = options.callback(Number(value), axisId);
21668
21695
  const point = dataset.data[i];
21669
21696
  const xPosition = point.x;
21670
21697
  let yPosition = 0;
@@ -21688,7 +21715,8 @@ function drawLineOrBarOrRadarChartValues(chart, options, ctx) {
21688
21715
  textsPositions[xPosition].push(yPosition);
21689
21716
  ctx.fillStyle = point.options.backgroundColor;
21690
21717
  ctx.strokeStyle = options.background || "#ffffff";
21691
- drawTextWithBackground(displayValue, xPosition, yPosition, ctx);
21718
+ const valueToDisplay = options.callback(Number(value), dataset, i);
21719
+ drawTextWithBackground(valueToDisplay, xPosition, yPosition, ctx);
21692
21720
  }
21693
21721
  }
21694
21722
  }
@@ -21705,7 +21733,7 @@ function drawHorizontalBarChartValues(chart, options, ctx) {
21705
21733
  if (isNaN(value)) {
21706
21734
  continue;
21707
21735
  }
21708
- const displayValue = options.callback(value, dataset.xAxisID);
21736
+ const displayValue = options.callback(value, dataset, i);
21709
21737
  const point = dataset.data[i];
21710
21738
  const yPosition = point.y;
21711
21739
  let xPosition = value < 0 ? point.x + point.width / 2 : point.x - point.width / 2;
@@ -21740,10 +21768,22 @@ function drawPieChartValues(chart, options, ctx) {
21740
21768
  const midAngle = (startAngle + endAngle) / 2;
21741
21769
  const midRadius = (innerRadius + outerRadius) / 2;
21742
21770
  const x = bar.x + midRadius * Math.cos(midAngle);
21743
- const y = bar.y + midRadius * Math.sin(midAngle) + 7;
21771
+ const y = bar.y + midRadius * Math.sin(midAngle);
21772
+ const displayValue = options.callback(value, dataset, i);
21773
+ const textHeight = 12; // ChartJS default
21774
+ const textWidth = computeTextWidth(ctx, displayValue, { fontSize: textHeight }, "px");
21775
+ const radius = outerRadius - innerRadius;
21776
+ // Check if the text fits in the slice. Not perfect, but good enough heuristic.
21777
+ if (textWidth >= radius || radius < textHeight) {
21778
+ continue;
21779
+ }
21780
+ const sliceAngle = endAngle - startAngle;
21781
+ const midWidth = 2 * midRadius * Math.tan(sliceAngle / 2);
21782
+ if (sliceAngle < Math.PI / 2 && (textWidth >= midWidth || midWidth < textHeight)) {
21783
+ continue;
21784
+ }
21744
21785
  ctx.fillStyle = chartFontColor(options.background);
21745
21786
  ctx.strokeStyle = options.background || "#ffffff";
21746
- const displayValue = options.callback(value, "y");
21747
21787
  drawTextWithBackground(displayValue, x, y, ctx);
21748
21788
  }
21749
21789
  }
@@ -23490,7 +23530,7 @@ function isMacOS() {
23490
23530
  * On Mac, this is the "meta" or "command" key.
23491
23531
  */
23492
23532
  function isCtrlKey(ev) {
23493
- return isMacOS() ? ev.metaKey : ev.ctrlKey;
23533
+ return isMacOS() || isIOS() ? ev.metaKey : ev.ctrlKey;
23494
23534
  }
23495
23535
  /**
23496
23536
  * @param {MouseEvent} ev - The mouse event.
@@ -23529,6 +23569,23 @@ function downloadFile(dataUrl, fileName) {
23529
23569
  function isBrowserFirefox() {
23530
23570
  return /Firefox/i.test(navigator.userAgent);
23531
23571
  }
23572
+ // Mobile detection
23573
+ function maxTouchPoints() {
23574
+ return navigator.maxTouchPoints || 1;
23575
+ }
23576
+ function isAndroid() {
23577
+ return /Android/i.test(navigator.userAgent);
23578
+ }
23579
+ function isIOS() {
23580
+ return (/(iPad|iPhone|iPod)/i.test(navigator.userAgent) ||
23581
+ (navigator.platform === "MacIntel" && maxTouchPoints() > 1));
23582
+ }
23583
+ function isOtherMobileOS() {
23584
+ return /(webOS|BlackBerry|Windows Phone)/i.test(navigator.userAgent);
23585
+ }
23586
+ function isMobileOS() {
23587
+ return isAndroid() || isIOS() || isOtherMobileOS();
23588
+ }
23532
23589
 
23533
23590
  /**
23534
23591
  * Convert a JS color hexadecimal to an excel compatible color.
@@ -25049,14 +25106,14 @@ function getPieChartLegend(definition, args) {
25049
25106
  ...getLegendDisplayOptions(definition),
25050
25107
  labels: {
25051
25108
  usePointStyle: true,
25052
- generateLabels: (c) => c.data.labels?.map((label, index) => ({
25109
+ generateLabels: (c) => (c.data.labels?.map((label, index) => ({
25053
25110
  text: truncateLabel(String(label)),
25054
25111
  strokeStyle: colors[index],
25055
25112
  fillStyle: colors[index],
25056
25113
  pointStyle: "rect",
25057
25114
  lineWidth: 2,
25058
25115
  fontColor,
25059
- })) || [],
25116
+ })) || []).filter((label) => label.text),
25060
25117
  filter: (legendItem, data) => {
25061
25118
  return "datasetIndex" in legendItem
25062
25119
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25216,7 +25273,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25216
25273
  labels: {
25217
25274
  color: fontColor,
25218
25275
  usePointStyle: true,
25219
- generateLabels: (chart) => chart.data.datasets.map((dataset, index) => {
25276
+ generateLabels: (chart) => chart.data.datasets
25277
+ .map((dataset, index) => {
25220
25278
  if (isTrendLineAxis(dataset["xAxisID"])) {
25221
25279
  return {
25222
25280
  text: truncateLabel(dataset.label),
@@ -25238,7 +25296,8 @@ function getCustomLegendLabels(fontColor, legendLabelConfig) {
25238
25296
  datasetIndex: index,
25239
25297
  ...legendLabelConfig,
25240
25298
  };
25241
- }),
25299
+ })
25300
+ .filter((label) => label.text),
25242
25301
  filter: (legendItem, data) => {
25243
25302
  return "datasetIndex" in legendItem
25244
25303
  ? !data.datasets[legendItem.datasetIndex].hidden
@@ -25592,7 +25651,10 @@ function getChartShowValues(definition, args) {
25592
25651
  horizontal: "horizontal" in definition && definition.horizontal,
25593
25652
  showValues: "showValues" in definition ? !!definition.showValues : false,
25594
25653
  background: definition.background,
25595
- callback: formatChartDatasetValue(axisFormats, locale),
25654
+ callback: (value, dataset) => {
25655
+ const axisId = getDatasetAxisId(definition, dataset);
25656
+ return formatChartDatasetValue(axisFormats, locale)(value, axisId);
25657
+ },
25596
25658
  };
25597
25659
  }
25598
25660
  function getSunburstShowValues(definition, args) {
@@ -25610,6 +25672,45 @@ function getSunburstShowValues(definition, args) {
25610
25672
  },
25611
25673
  };
25612
25674
  }
25675
+ function getPyramidChartShowValues(definition, args) {
25676
+ const { axisFormats, locale } = args;
25677
+ return {
25678
+ horizontal: true,
25679
+ showValues: "showValues" in definition ? !!definition.showValues : false,
25680
+ background: definition.background,
25681
+ callback: (value, dataset) => {
25682
+ value = Math.abs(Number(value));
25683
+ return formatChartDatasetValue(axisFormats, locale)(value, dataset.xAxisID || "x");
25684
+ },
25685
+ };
25686
+ }
25687
+ function getWaterfallChartShowValues(definition, args) {
25688
+ const { axisFormats, locale, dataSetsValues } = args;
25689
+ const subtotalIndexes = dataSetsValues.reduce((subtotalIndexes, ds) => {
25690
+ subtotalIndexes.push((subtotalIndexes.at(-1) || -1) + ds.data.length + 1);
25691
+ return subtotalIndexes;
25692
+ }, []);
25693
+ return {
25694
+ showValues: "showValues" in definition ? !!definition.showValues : false,
25695
+ background: definition.background,
25696
+ callback: (value, dataset, index) => {
25697
+ const raw = dataset._dataset.data[index];
25698
+ const delta = raw[1] - raw[0];
25699
+ let sign = delta >= 0 ? "+" : "";
25700
+ if (definition.showSubTotals && subtotalIndexes.includes(index) && sign === "+") {
25701
+ sign = "";
25702
+ }
25703
+ return `${sign}${formatChartDatasetValue(axisFormats, locale)(delta, dataset.yAxisID)}`;
25704
+ },
25705
+ };
25706
+ }
25707
+ function getDatasetAxisId(definition, dataset) {
25708
+ if (dataset.rAxisID) {
25709
+ return dataset.rAxisID;
25710
+ }
25711
+ const axisId = "horizontal" in definition && definition.horizontal ? dataset.xAxisID : dataset.yAxisID;
25712
+ return axisId || "y";
25713
+ }
25613
25714
 
25614
25715
  function getChartTitle(definition) {
25615
25716
  const chartTitle = definition.title;
@@ -26012,6 +26113,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26012
26113
  getPieChartTooltip: getPieChartTooltip,
26013
26114
  getPyramidChartData: getPyramidChartData,
26014
26115
  getPyramidChartScales: getPyramidChartScales,
26116
+ getPyramidChartShowValues: getPyramidChartShowValues,
26015
26117
  getPyramidChartTooltip: getPyramidChartTooltip,
26016
26118
  getRadarChartData: getRadarChartData,
26017
26119
  getRadarChartDatasets: getRadarChartDatasets,
@@ -26032,6 +26134,7 @@ var CHART_RUNTIME_HELPERS = /*#__PURE__*/Object.freeze({
26032
26134
  getTrendDatasetForLineChart: getTrendDatasetForLineChart,
26033
26135
  getWaterfallChartLegend: getWaterfallChartLegend,
26034
26136
  getWaterfallChartScales: getWaterfallChartScales,
26137
+ getWaterfallChartShowValues: getWaterfallChartShowValues,
26035
26138
  getWaterfallChartTooltip: getWaterfallChartTooltip,
26036
26139
  getWaterfallDatasetAndLabels: getWaterfallDatasetAndLabels,
26037
26140
  makeDatasetsCumulative: makeDatasetsCumulative
@@ -27344,7 +27447,7 @@ function createPyramidChartRuntime(chart, getters) {
27344
27447
  title: getChartTitle(definition),
27345
27448
  legend: getBarChartLegend(definition),
27346
27449
  tooltip: getPyramidChartTooltip(definition, chartData),
27347
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
27450
+ chartShowValuesPlugin: getPyramidChartShowValues(definition, chartData),
27348
27451
  },
27349
27452
  },
27350
27453
  };
@@ -28092,7 +28195,7 @@ function createWaterfallChartRuntime(chart, getters) {
28092
28195
  title: getChartTitle(definition),
28093
28196
  legend: getWaterfallChartLegend(definition),
28094
28197
  tooltip: getWaterfallChartTooltip(definition, chartData),
28095
- chartShowValuesPlugin: getChartShowValues(definition, chartData),
28198
+ chartShowValuesPlugin: getWaterfallChartShowValues(definition, chartData),
28096
28199
  waterfallLinesPlugin: { showConnectorLines: definition.showConnectorLines },
28097
28200
  },
28098
28201
  },
@@ -28884,6 +28987,7 @@ function getChartMenuActions(figureId, onFigureDeleted, env) {
28884
28987
  env.openSidePanel("ChartPanel");
28885
28988
  },
28886
28989
  icon: "o-spreadsheet-Icon.EDIT",
28990
+ isEnabled: (env) => !env.isSmall,
28887
28991
  },
28888
28992
  getCopyMenuItem(figureId, env),
28889
28993
  getCutMenuItem(figureId, env),
@@ -29098,6 +29202,147 @@ function useTimeOut() {
29098
29202
  };
29099
29203
  }
29100
29204
 
29205
+ //------------------------------------------------------------------------------
29206
+ // Context Menu Component
29207
+ //------------------------------------------------------------------------------
29208
+ css /* scss */ `
29209
+ .o-menu {
29210
+ background-color: white;
29211
+ user-select: none;
29212
+
29213
+ .o-menu-item {
29214
+ height: ${DESKTOP_MENU_ITEM_HEIGHT}px;
29215
+ padding: ${MENU_ITEM_PADDING_VERTICAL}px ${MENU_ITEM_PADDING_HORIZONTAL}px;
29216
+ cursor: pointer;
29217
+ user-select: none;
29218
+
29219
+ .o-menu-item-name {
29220
+ min-width: 40%;
29221
+ }
29222
+
29223
+ .o-menu-item-icon {
29224
+ display: inline-block;
29225
+ margin: 0px 8px 0px 0px;
29226
+ width: ${DESKTOP_MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29227
+ line-height: ${DESKTOP_MENU_ITEM_HEIGHT - 2 * MENU_ITEM_PADDING_VERTICAL}px;
29228
+ }
29229
+
29230
+ &:not(.disabled) {
29231
+ &:hover,
29232
+ &.o-menu-item-active {
29233
+ background-color: ${BUTTON_ACTIVE_BG};
29234
+ color: ${BUTTON_ACTIVE_TEXT_COLOR};
29235
+ }
29236
+ .o-menu-item-description {
29237
+ color: grey;
29238
+ }
29239
+ .o-menu-item-icon {
29240
+ .o-icon {
29241
+ color: ${ICONS_COLOR};
29242
+ }
29243
+ }
29244
+ }
29245
+ &.disabled {
29246
+ color: ${DISABLED_TEXT_COLOR};
29247
+ cursor: not-allowed;
29248
+ }
29249
+ }
29250
+ }
29251
+
29252
+ .o-spreadsheet-mobile {
29253
+ .o-menu-item {
29254
+ height: ${MOBILE_MENU_ITEM_HEIGHT}px;
29255
+ }
29256
+ }
29257
+ `;
29258
+ class Menu extends Component {
29259
+ static template = "o-spreadsheet-Menu";
29260
+ static props = {
29261
+ menuItems: Array,
29262
+ onClose: Function,
29263
+ onClickMenu: { type: Function, optional: true },
29264
+ onMouseEnter: { type: Function, optional: true },
29265
+ onMouseOver: { type: Function, optional: true },
29266
+ onMouseLeave: { type: Function, optional: true },
29267
+ width: { type: Number, optional: true },
29268
+ isActive: { type: Function, optional: true },
29269
+ onScroll: { type: Function, optional: true },
29270
+ };
29271
+ static components = {};
29272
+ static defaultProps = {};
29273
+ hoveredMenu = undefined;
29274
+ setup() {
29275
+ onWillUnmount(() => {
29276
+ this.hoveredMenu?.onStopHover?.(this.env);
29277
+ });
29278
+ }
29279
+ get menuItemsAndSeparators() {
29280
+ const menuItemsAndSeparators = [];
29281
+ for (let i = 0; i < this.props.menuItems.length; i++) {
29282
+ const menuItem = this.props.menuItems[i];
29283
+ if (menuItem.isVisible(this.env)) {
29284
+ menuItemsAndSeparators.push(menuItem);
29285
+ }
29286
+ if (menuItem.separator &&
29287
+ i !== this.props.menuItems.length - 1 && // no separator at the end
29288
+ menuItemsAndSeparators[menuItemsAndSeparators.length - 1] !== "separator" // no double separator
29289
+ ) {
29290
+ menuItemsAndSeparators.push("separator");
29291
+ }
29292
+ }
29293
+ if (menuItemsAndSeparators[menuItemsAndSeparators.length - 1] === "separator") {
29294
+ menuItemsAndSeparators.pop();
29295
+ }
29296
+ if (menuItemsAndSeparators.length === 1 && menuItemsAndSeparators[0] === "separator") {
29297
+ return [];
29298
+ }
29299
+ return menuItemsAndSeparators;
29300
+ }
29301
+ get childrenHaveIcon() {
29302
+ return this.props.menuItems.some((menuItem) => !!this.getIconName(menuItem));
29303
+ }
29304
+ getIconName(menu) {
29305
+ if (menu.icon(this.env)) {
29306
+ return menu.icon(this.env);
29307
+ }
29308
+ if (menu.isActive?.(this.env)) {
29309
+ return "o-spreadsheet-Icon.CHECK";
29310
+ }
29311
+ return "";
29312
+ }
29313
+ getColor(menu) {
29314
+ return cssPropertiesToCss({ color: menu.textColor });
29315
+ }
29316
+ getIconColor(menu) {
29317
+ return cssPropertiesToCss({ color: menu.iconColor });
29318
+ }
29319
+ getName(menu) {
29320
+ return menu.name(this.env);
29321
+ }
29322
+ isRoot(menu) {
29323
+ return !menu.execute;
29324
+ }
29325
+ isEnabled(menu) {
29326
+ if (menu.isEnabled(this.env)) {
29327
+ return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
29328
+ }
29329
+ return false;
29330
+ }
29331
+ get menuStyle() {
29332
+ return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
29333
+ }
29334
+ onMouseEnter(menu, ev) {
29335
+ this.hoveredMenu = menu;
29336
+ menu.onStartHover?.(this.env);
29337
+ this.props.onMouseEnter?.(menu, ev);
29338
+ }
29339
+ onMouseLeave(menu, ev) {
29340
+ this.hoveredMenu = undefined;
29341
+ menu.onStopHover?.(this.env);
29342
+ this.props.onMouseLeave?.(menu, ev);
29343
+ }
29344
+ }
29345
+
29101
29346
  /**
29102
29347
  * Compute the intersection of two rectangles. Returns nothing if the two rectangles don't overlap
29103
29348
  */
@@ -29126,6 +29371,9 @@ function zoneToRect(zone) {
29126
29371
  height: zone.bottom - zone.top,
29127
29372
  };
29128
29373
  }
29374
+ function isPointInsideRect(x, y, rect) {
29375
+ return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
29376
+ }
29129
29377
 
29130
29378
  /**
29131
29379
  * Return the o-spreadsheet element position relative
@@ -29423,57 +29671,17 @@ class TopRightPopoverContext extends PopoverPositionContext {
29423
29671
  }
29424
29672
 
29425
29673
  //------------------------------------------------------------------------------
29426
- // Context Menu Component
29674
+ // Context MenuPopover Component
29427
29675
  //------------------------------------------------------------------------------
29428
29676
  css /* scss */ `
29429
- .o-menu {
29430
- background-color: white;
29677
+ .o-menu-wrapper {
29431
29678
  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
- }
29679
+ background-color: white;
29472
29680
  }
29473
29681
  `;
29474
29682
  const TIMEOUT_DELAY = 250;
29475
- class Menu extends Component {
29476
- static template = "o-spreadsheet-Menu";
29683
+ class MenuPopover extends Component {
29684
+ static template = "o-spreadsheet-Menu-Popover";
29477
29685
  static props = {
29478
29686
  anchorRect: Object,
29479
29687
  popoverPositioning: { type: String, optional: true },
@@ -29486,7 +29694,7 @@ class Menu extends Component {
29486
29694
  onMouseOver: { type: Function, optional: true },
29487
29695
  width: { type: Number, optional: true },
29488
29696
  };
29489
- static components = { Menu, Popover };
29697
+ static components = { MenuPopover, Menu, Popover };
29490
29698
  static defaultProps = {
29491
29699
  depth: 1,
29492
29700
  popoverPositioning: "top-right",
@@ -29513,27 +29721,18 @@ class Menu extends Component {
29513
29721
  this.hoveredMenu?.onStopHover?.(this.env);
29514
29722
  });
29515
29723
  }
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;
29724
+ get menuProps() {
29725
+ return {
29726
+ menuItems: this.props.menuItems,
29727
+ onClose: this.close.bind(this),
29728
+ // @ts-ignore
29729
+ onClickMenu: this.onClickMenu.bind(this),
29730
+ onMouseOver: this.onMouseOver.bind(this),
29731
+ onMouseLeave: this.onMouseLeave.bind(this),
29732
+ width: this.props.width || MENU_WIDTH,
29733
+ isActive: this.isActive.bind(this),
29734
+ onScroll: this.onScroll.bind(this),
29735
+ };
29537
29736
  }
29538
29737
  get subMenuAnchorRect() {
29539
29738
  const anchorRect = Object.assign({}, this.subMenu.anchorRect);
@@ -29547,12 +29746,13 @@ class Menu extends Component {
29547
29746
  x: this.props.anchorRect.x,
29548
29747
  y: this.props.anchorRect.y,
29549
29748
  width: isRoot ? this.props.anchorRect.width : this.props.width || MENU_WIDTH,
29550
- height: isRoot ? this.props.anchorRect.height : MENU_ITEM_HEIGHT,
29749
+ height: isRoot ? this.props.anchorRect.height : DESKTOP_MENU_ITEM_HEIGHT,
29551
29750
  },
29552
29751
  positioning: this.props.popoverPositioning,
29553
29752
  verticalOffset: isRoot ? 0 : MENU_VERTICAL_PADDING,
29554
29753
  onPopoverHidden: () => this.closeSubMenu(),
29555
29754
  onPopoverMoved: () => this.closeSubMenu(),
29755
+ maxHeight: this.props.maxHeight,
29556
29756
  };
29557
29757
  }
29558
29758
  get childrenHaveIcon() {
@@ -29622,7 +29822,7 @@ class Menu extends Component {
29622
29822
  x: getRefBoundingRect(this.menuRef).x,
29623
29823
  y: y - (this.subMenu.scrollOffset || 0),
29624
29824
  width: this.props.width || MENU_WIDTH,
29625
- height: MENU_ITEM_HEIGHT,
29825
+ height: DESKTOP_MENU_ITEM_HEIGHT,
29626
29826
  };
29627
29827
  this.subMenu.menuItems = menu.children(this.env);
29628
29828
  this.subMenu.isOpen = true;
@@ -29671,14 +29871,8 @@ class Menu extends Component {
29671
29871
  this.subMenu.isHoveringChild = true;
29672
29872
  this.openingTimeOut.clear();
29673
29873
  }
29674
- onMouseEnter(menu, ev) {
29675
- this.hoveredMenu = menu;
29676
- menu.onStartHover?.(this.env);
29677
- }
29678
29874
  onMouseLeave(menu) {
29679
29875
  this.openingTimeOut.schedule(this.closeSubMenu.bind(this), TIMEOUT_DELAY);
29680
- this.hoveredMenu = undefined;
29681
- menu.onStopHover?.(this.env);
29682
29876
  }
29683
29877
  get menuStyle() {
29684
29878
  return this.props.width ? cssPropertiesToCss({ width: this.props.width + "px" }) : "";
@@ -29687,7 +29881,7 @@ class Menu extends Component {
29687
29881
 
29688
29882
  class ChartDashboardMenu extends Component {
29689
29883
  static template = "spreadsheet.ChartDashboardMenu";
29690
- static components = { Menu };
29884
+ static components = { MenuPopover };
29691
29885
  static props = { figureUI: Object };
29692
29886
  originalChartDefinition;
29693
29887
  fullScreenFigureStore;
@@ -29951,7 +30145,7 @@ class FigureComponent extends Component {
29951
30145
  onMouseDown: { type: Function, optional: true },
29952
30146
  onClickAnchor: { type: Function, optional: true },
29953
30147
  };
29954
- static components = { Menu };
30148
+ static components = { MenuPopover };
29955
30149
  static defaultProps = {
29956
30150
  onFigureDeleted: () => { },
29957
30151
  onMouseDown: () => { },
@@ -30038,7 +30232,14 @@ class FigureComponent extends Component {
30038
30232
  this.props.onClickAnchor(dirX, dirY, ev);
30039
30233
  }
30040
30234
  onMouseDown(ev) {
30041
- this.props.onMouseDown(ev);
30235
+ if (!this.env.isMobile()) {
30236
+ this.props.onMouseDown(ev);
30237
+ }
30238
+ }
30239
+ onClick(ev) {
30240
+ if (this.env.isMobile()) {
30241
+ this.props.onMouseDown(ev);
30242
+ }
30042
30243
  }
30043
30244
  onKeyDown(ev) {
30044
30245
  const keyDownShortcut = keyboardEventToShortcutString(ev);
@@ -31505,15 +31706,15 @@ class HighlightStore extends SpreadsheetStore {
31505
31706
  const activeSheetId = this.getters.getActiveSheetId();
31506
31707
  return this.providers
31507
31708
  .flatMap((h) => h.highlights)
31508
- .filter((h) => h.sheetId === activeSheetId)
31709
+ .filter((h) => h.range.sheetId === activeSheetId)
31509
31710
  .map((highlight) => {
31510
- const { numberOfRows, numberOfCols } = zoneToDimension(highlight.zone);
31711
+ const { numberOfRows, numberOfCols } = zoneToDimension(highlight.range.zone);
31511
31712
  const zone = numberOfRows * numberOfCols === 1
31512
- ? this.getters.expandZone(highlight.sheetId, highlight.zone)
31513
- : highlight.zone;
31713
+ ? this.getters.expandZone(highlight.range.sheetId, highlight.range.zone)
31714
+ : highlight.range.unboundedZone;
31514
31715
  return {
31515
31716
  ...highlight,
31516
- zone,
31717
+ range: this.model.getters.getRangeFromZone(highlight.range.sheetId, zone),
31517
31718
  };
31518
31719
  });
31519
31720
  }
@@ -31526,7 +31727,7 @@ class HighlightStore extends SpreadsheetStore {
31526
31727
  drawLayer(ctx, layer) {
31527
31728
  if (layer === "Highlights") {
31528
31729
  for (const highlight of this.highlights) {
31529
- const rect = this.getters.getVisibleRect(highlight.zone);
31730
+ const rect = this.getters.getVisibleRect(highlight.range.zone);
31530
31731
  drawHighlight(ctx, highlight, rect);
31531
31732
  }
31532
31733
  }
@@ -32154,12 +32355,12 @@ class AbstractComposerStore extends SpreadsheetStore {
32154
32355
  rangeColor(xc, sheetName) {
32155
32356
  const refSheet = sheetName ? this.model.getters.getSheetIdByName(sheetName) : this.sheetId;
32156
32357
  const highlight = this.highlights.find((highlight) => {
32157
- if (highlight.sheetId !== refSheet)
32358
+ if (highlight.range.sheetId !== refSheet)
32158
32359
  return false;
32159
32360
  const range = this.model.getters.getRangeFromSheetXC(refSheet, xc);
32160
32361
  let zone = range.zone;
32161
32362
  zone = getZoneArea(zone) === 1 ? this.model.getters.expandZone(refSheet, zone) : zone;
32162
- return isEqual(zone, highlight.zone);
32363
+ return isEqual(zone, highlight.range.zone);
32163
32364
  });
32164
32365
  return highlight && highlight.color ? highlight.color : undefined;
32165
32366
  }
@@ -32269,11 +32470,10 @@ class AbstractComposerStore extends SpreadsheetStore {
32269
32470
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
32270
32471
  const zone = numberOfRows * numberOfCols === 1
32271
32472
  ? this.getters.expandZone(range.sheetId, range.zone)
32272
- : range.zone;
32473
+ : range.unboundedZone;
32273
32474
  return {
32274
- zone,
32475
+ range: this.model.getters.getRangeFromZone(range.sheetId, zone),
32275
32476
  color: rangeColor(rangeString),
32276
- sheetId: range.sheetId,
32277
32477
  interactive: true,
32278
32478
  };
32279
32479
  });
@@ -32526,11 +32726,15 @@ class Composer extends Component {
32526
32726
  onInputContextMenu: { type: Function, optional: true },
32527
32727
  composerStore: Object,
32528
32728
  placeholder: { type: String, optional: true },
32729
+ inputMode: { type: String, optional: true },
32730
+ showAssistant: { type: Boolean, optional: true },
32529
32731
  };
32530
32732
  static components = { TextValueProvider, FunctionDescriptionProvider, SpeechBubble };
32531
32733
  static defaultProps = {
32532
32734
  inputStyle: "",
32533
32735
  isDefaultFocus: false,
32736
+ inputMode: "text",
32737
+ showAssistant: true,
32534
32738
  };
32535
32739
  DOMFocusableElementStore;
32536
32740
  composerRef = useRef("o_composer");
@@ -32581,8 +32785,7 @@ class Composer extends Component {
32581
32785
  assistantStyle["max-height"] = `${availableSpaceAbove - CLOSE_ICON_RADIUS}px`;
32582
32786
  // render top
32583
32787
  // We compensate 2 px of margin on the assistant style + 1px for design reasons
32584
- assistantStyle.top = `-3px`;
32585
- assistantStyle.transform = `translate(0, -100%)`;
32788
+ assistantStyle.transform = `translate(0, calc(-100% - ${cellHeight + 3}px))`;
32586
32789
  }
32587
32790
  if (cellX + ASSISTANT_WIDTH > this.props.delimitation.width) {
32588
32791
  // render left
@@ -32861,6 +33064,9 @@ class Composer extends Component {
32861
33064
  // not main button, probably a context menu
32862
33065
  return;
32863
33066
  }
33067
+ if (this.env.isMobile() && !isIOS()) {
33068
+ return;
33069
+ }
32864
33070
  this.contentHelper.removeSelection();
32865
33071
  }
32866
33072
  onMouseup() {
@@ -33484,7 +33690,7 @@ class ListCriterionForm extends CriterionForm {
33484
33690
  */
33485
33691
  function startDnd(onPointerMove, onPointerUp) {
33486
33692
  const removeListeners = () => {
33487
- window.removeEventListener("pointerup", _onPointerUp);
33693
+ window.removeEventListener("pointerup", _onPointerUp, { capture: true });
33488
33694
  window.removeEventListener("dragstart", _onDragStart);
33489
33695
  window.removeEventListener("pointermove", onPointerMove);
33490
33696
  window.removeEventListener("wheel", onPointerMove);
@@ -33496,7 +33702,7 @@ function startDnd(onPointerMove, onPointerUp) {
33496
33702
  function _onDragStart(ev) {
33497
33703
  ev.preventDefault();
33498
33704
  }
33499
- window.addEventListener("pointerup", _onPointerUp);
33705
+ window.addEventListener("pointerup", _onPointerUp, { capture: true });
33500
33706
  window.addEventListener("dragstart", _onDragStart);
33501
33707
  window.addEventListener("pointermove", onPointerMove);
33502
33708
  // mouse wheel on window is by default a passive event.
@@ -34118,9 +34324,9 @@ class SelectionInputStore extends SpreadsheetStore {
34118
34324
  .filter((reference) => this.shouldBeHighlighted(this.inputSheetId, reference));
34119
34325
  return XCs.map((xc) => {
34120
34326
  const { sheetName } = splitReference(xc);
34327
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId;
34121
34328
  return {
34122
- zone: this.getters.getRangeFromSheetXC(this.inputSheetId, xc).zone,
34123
- sheetId: (sheetName && this.getters.getSheetIdByName(sheetName)) || this.inputSheetId,
34329
+ range: this.getters.getRangeFromSheetXC(sheetId, xc),
34124
34330
  color,
34125
34331
  interactive: true,
34126
34332
  };
@@ -34589,7 +34795,7 @@ function getCriterionMenuItems(callback, availableTypes) {
34589
34795
  return createActions(actionSpecs);
34590
34796
  }
34591
34797
 
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 */
34798
+ /** 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
34799
  class SelectMenu extends Component {
34594
34800
  static template = "o-spreadsheet-SelectMenu";
34595
34801
  static props = {
@@ -34597,7 +34803,7 @@ class SelectMenu extends Component {
34597
34803
  selectedValue: String,
34598
34804
  class: { type: String, optional: true },
34599
34805
  };
34600
- static components = { Menu };
34806
+ static components = { MenuPopover };
34601
34807
  menuId = new UuidGenerator().uuidv4();
34602
34808
  selectRef = useRef("select");
34603
34809
  state = useState({
@@ -34770,17 +34976,22 @@ class FilterMenuValueList extends Component {
34770
34976
  static components = { FilterMenuValueItem };
34771
34977
  state = useState({
34772
34978
  values: [],
34979
+ displayedValues: [],
34773
34980
  textFilter: "",
34774
34981
  selectedValue: undefined,
34982
+ numberOfDisplayedValues: 50,
34983
+ hasMoreValues: false,
34775
34984
  });
34776
34985
  searchBar = useRef("filterMenuSearchBar");
34777
34986
  setup() {
34778
34987
  onWillUpdateProps((nextProps) => {
34779
34988
  if (!deepEquals(nextProps.filterPosition, this.props.filterPosition)) {
34780
34989
  this.state.values = this.getFilterHiddenValues(nextProps.filterPosition);
34990
+ this.computeDisplayedValues();
34781
34991
  }
34782
34992
  });
34783
34993
  this.state.values = this.getFilterHiddenValues(this.props.filterPosition);
34994
+ this.computeDisplayedValues();
34784
34995
  }
34785
34996
  getFilterHiddenValues(position) {
34786
34997
  const sheetId = this.env.model.getters.getActiveSheetId();
@@ -34798,21 +35009,28 @@ class FilterMenuValueList extends Component {
34798
35009
  }
34799
35010
  const cellValues = cells.map((val) => val.cellValue);
34800
35011
  const filterValues = filterValue?.filterType === "values" ? filterValue.hiddenValues : [];
34801
- const strValues = [...cellValues, ...filterValues];
34802
- const normalizedFilteredValues = filterValues.map(toLowerCase);
34803
- // Set with lowercase values to avoid duplicates
34804
- const normalizedValues = [...new Set(strValues.map(toLowerCase))];
34805
- const sortedValues = normalizedValues.sort((val1, val2) => val1.localeCompare(val2, undefined, { numeric: true, sensitivity: "base" }));
34806
- return sortedValues.map((normalizedValue) => {
34807
- let checked = false;
34808
- if (filterValue?.filterType !== "criterion") {
34809
- checked = normalizedFilteredValues.findIndex((val) => val === normalizedValue) === -1;
35012
+ const normalizedFilteredValues = new Set(filterValues.map(toLowerCase));
35013
+ const set = new Set();
35014
+ const values = [];
35015
+ const addValue = (value) => {
35016
+ const normalizedValue = toLowerCase(value);
35017
+ if (!set.has(normalizedValue)) {
35018
+ values.push({
35019
+ string: value || "",
35020
+ checked: filterValue?.filterType !== "criterion"
35021
+ ? !normalizedFilteredValues.has(normalizedValue)
35022
+ : false,
35023
+ normalizedValue,
35024
+ });
35025
+ set.add(normalizedValue);
34810
35026
  }
34811
- return {
34812
- checked,
34813
- string: strValues.find((val) => toLowerCase(val) === normalizedValue) || "",
34814
- };
34815
- });
35027
+ };
35028
+ cellValues.forEach(addValue);
35029
+ filterValues.forEach(addValue);
35030
+ return values.sort((val1, val2) => val1.normalizedValue.localeCompare(val2.normalizedValue, undefined, {
35031
+ numeric: true,
35032
+ sensitivity: "base",
35033
+ }));
34816
35034
  }
34817
35035
  checkValue(value) {
34818
35036
  this.state.selectedValue = value.string;
@@ -34824,25 +35042,37 @@ class FilterMenuValueList extends Component {
34824
35042
  this.state.selectedValue = value.string;
34825
35043
  }
34826
35044
  selectAll() {
34827
- this.displayedValues.forEach((value) => (value.checked = true));
34828
- this.updateHiddenValues();
35045
+ this.state.displayedValues.forEach((value) => (value.checked = true));
35046
+ this.props.onUpdateHiddenValues([]);
34829
35047
  }
34830
35048
  clearAll() {
34831
- this.displayedValues.forEach((value) => (value.checked = false));
34832
- this.updateHiddenValues();
35049
+ this.state.displayedValues.forEach((value) => (value.checked = false));
35050
+ const hiddenValues = this.state.values.map((val) => val.string);
35051
+ this.props.onUpdateHiddenValues(hiddenValues);
34833
35052
  }
34834
35053
  updateHiddenValues() {
34835
35054
  const hiddenValues = this.state.values.filter((val) => !val.checked).map((val) => val.string);
34836
35055
  this.props.onUpdateHiddenValues(hiddenValues);
34837
35056
  }
34838
- get displayedValues() {
34839
- if (!this.state.textFilter) {
34840
- return this.state.values;
34841
- }
34842
- return fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
35057
+ updateSearch(ev) {
35058
+ const target = ev.target;
35059
+ this.state.textFilter = target.value;
35060
+ this.state.selectedValue = undefined;
35061
+ this.computeDisplayedValues();
35062
+ }
35063
+ computeDisplayedValues() {
35064
+ const values = !this.state.textFilter
35065
+ ? this.state.values
35066
+ : fuzzyLookup(this.state.textFilter, this.state.values, (val) => val.string);
35067
+ this.state.displayedValues = values.slice(0, this.state.numberOfDisplayedValues);
35068
+ this.state.hasMoreValues = values.length > this.state.numberOfDisplayedValues;
35069
+ }
35070
+ loadMoreValues() {
35071
+ this.state.numberOfDisplayedValues += 100;
35072
+ this.computeDisplayedValues();
34843
35073
  }
34844
35074
  onKeyDown(ev) {
34845
- const displayedValues = this.displayedValues;
35075
+ const displayedValues = this.state.displayedValues;
34846
35076
  if (displayedValues.length === 0)
34847
35077
  return;
34848
35078
  let selectedIndex = undefined;
@@ -35345,7 +35575,7 @@ class MenuItemRegistry extends Registry {
35345
35575
  }
35346
35576
 
35347
35577
  //------------------------------------------------------------------------------
35348
- // Link Menu Registry
35578
+ // Link MenuPopover Registry
35349
35579
  //------------------------------------------------------------------------------
35350
35580
  const linkMenuRegistry = new MenuItemRegistry();
35351
35581
  linkMenuRegistry.add("sheet", {
@@ -35407,7 +35637,7 @@ class LinkEditor extends Component {
35407
35637
  cellPosition: Object,
35408
35638
  onClosed: { type: Function, optional: true },
35409
35639
  };
35410
- static components = { Menu };
35640
+ static components = { MenuPopover };
35411
35641
  menuItems = linkMenuRegistry.getMenuItems();
35412
35642
  link = useState(this.defaultState);
35413
35643
  menu = useState({
@@ -35864,58 +36094,149 @@ css /* scss */ `
35864
36094
  const ARROW_DOWN = {
35865
36095
  width: 448,
35866
36096
  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",
36097
+ paths: [
36098
+ {
36099
+ fillColor: "#E06666",
36100
+ 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",
36101
+ },
36102
+ ],
35869
36103
  };
35870
36104
  const ARROW_UP = {
35871
36105
  width: 448,
35872
36106
  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",
36107
+ paths: [
36108
+ {
36109
+ fillColor: "#6AA84F",
36110
+ 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",
36111
+ },
36112
+ ],
35875
36113
  };
35876
36114
  const ARROW_RIGHT = {
35877
36115
  width: 448,
35878
36116
  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",
36117
+ paths: [
36118
+ {
36119
+ fillColor: "#F0AD4E",
36120
+ 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",
36121
+ },
36122
+ ],
35881
36123
  };
35882
36124
  const SMILE = {
35883
36125
  width: 496,
35884
36126
  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",
36127
+ paths: [
36128
+ {
36129
+ fillColor: "#6AA84F",
36130
+ 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",
36131
+ },
36132
+ ],
35887
36133
  };
35888
36134
  const MEH = {
35889
36135
  width: 496,
35890
36136
  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",
36137
+ paths: [
36138
+ {
36139
+ fillColor: "#F0AD4E",
36140
+ 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",
36141
+ },
36142
+ ],
35893
36143
  };
35894
36144
  const FROWN = {
35895
36145
  width: 496,
35896
36146
  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",
36147
+ paths: [
36148
+ {
36149
+ fillColor: "#E06666",
36150
+ 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",
36151
+ },
36152
+ ],
35899
36153
  };
35900
36154
  const DOT_PATH = "M256 9 a247 247 0 1 0.1 0 0";
35901
36155
  const GREEN_DOT = {
35902
36156
  width: 512,
35903
36157
  height: 512,
35904
- fillColor: "#6AA84F",
35905
- path: DOT_PATH,
36158
+ paths: [{ fillColor: "#6AA84F", path: DOT_PATH }],
35906
36159
  };
35907
36160
  const YELLOW_DOT = {
35908
36161
  width: 512,
35909
36162
  height: 512,
35910
- fillColor: "#F0AD4E",
35911
- path: DOT_PATH,
36163
+ paths: [{ fillColor: "#F0AD4E", path: DOT_PATH }],
35912
36164
  };
35913
36165
  const RED_DOT = {
35914
36166
  width: 512,
35915
36167
  height: 512,
35916
- fillColor: "#E06666",
35917
- path: DOT_PATH,
36168
+ paths: [{ fillColor: "#E06666", path: DOT_PATH }],
36169
+ };
36170
+ const CARET_DOWN = {
36171
+ width: 512,
36172
+ height: 512,
36173
+ paths: [{ fillColor: TEXT_BODY_MUTED, path: "M120 195 h270 l-135 130" }],
36174
+ };
36175
+ const HOVERED_CARET_DOWN = {
36176
+ width: 512,
36177
+ height: 512,
36178
+ paths: [
36179
+ { fillColor: TEXT_BODY_MUTED, path: "M15 15 h482 v482 h-482" },
36180
+ { fillColor: "#fff", path: "M120 195 h270 l-135 130" },
36181
+ ],
36182
+ };
36183
+ const CHECKBOX_UNCHECKED = {
36184
+ width: 512,
36185
+ height: 512,
36186
+ paths: [{ fillColor: GRAY_300, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
36187
+ };
36188
+ const CHECKBOX_UNCHECKED_HOVERED = {
36189
+ width: 512,
36190
+ height: 512,
36191
+ paths: [{ fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422 m30,30 v362 h362 v-362" }],
36192
+ };
36193
+ const CHECKBOX_CHECKED = {
36194
+ width: 512,
36195
+ height: 512,
36196
+ paths: [
36197
+ { fillColor: ACTION_COLOR, path: "M45,45 h422 v422 h-422 v-422" },
36198
+ { fillColor: "#FFF", path: "M165,240 l45,45 l135,-135 h60 l-195,195 l-105,-105" },
36199
+ ],
35918
36200
  };
36201
+ function getPivotIconSvg(isCollapsed, isHovered) {
36202
+ const symbolPath = isCollapsed
36203
+ ? "M149,235 h213 v43 h-213 M235,149 h43 v213 h-43" // +
36204
+ : "M149,235 h213 v43 h-213"; // -
36205
+ return {
36206
+ width: 512,
36207
+ height: 512,
36208
+ paths: [
36209
+ { path: "M21,21 h469 v469 h-469", fillColor: isHovered ? GRAY_900 : "#777" }, // borders
36210
+ { path: "M64,64 v384 h384 v-384", fillColor: isHovered ? GRAY_200 : "#eee" }, // background
36211
+ { path: symbolPath, fillColor: isHovered ? GRAY_900 : "#777" },
36212
+ ],
36213
+ };
36214
+ }
36215
+ function getDataFilterIcon(isActive, isHighContrast, isHovered) {
36216
+ const symbolPath = isActive
36217
+ ? "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"
36218
+ : "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";
36219
+ const hoverBackgroundPath = isActive ? "M0,0 h24 v24 h-24" : "M0,0 h850 v850 h-850";
36220
+ const colors = { iconColor: FILTERS_COLOR, hoverBackgroundColor: FILTERS_COLOR };
36221
+ if (isHovered && !isHighContrast) {
36222
+ colors.iconColor = "#fff";
36223
+ }
36224
+ else if (!isHovered && isHighContrast) {
36225
+ colors.iconColor = "#defade";
36226
+ }
36227
+ else if (isHovered && isHighContrast) {
36228
+ colors.iconColor = FILTERS_COLOR;
36229
+ colors.hoverBackgroundColor = "#fff";
36230
+ }
36231
+ return {
36232
+ width: isActive ? 24 : 850,
36233
+ height: isActive ? 24 : 850,
36234
+ paths: [
36235
+ isHovered ? { path: hoverBackgroundPath, fillColor: colors.hoverBackgroundColor } : undefined,
36236
+ { path: symbolPath, fillColor: colors.iconColor },
36237
+ ].filter(isDefined),
36238
+ };
36239
+ }
35919
36240
  const ICONS = {
35920
36241
  arrowGood: {
35921
36242
  template: "ARROW_UP",
@@ -35971,6 +36292,15 @@ const ICON_SETS = {
35971
36292
  bad: "dotBad",
35972
36293
  },
35973
36294
  };
36295
+ const path2DCache = {};
36296
+ function getPath2D(svgPath) {
36297
+ if (path2DCache[svgPath]) {
36298
+ return path2DCache[svgPath];
36299
+ }
36300
+ const path2D = new Path2D(svgPath);
36301
+ path2DCache[svgPath] = path2D;
36302
+ return path2D;
36303
+ }
35974
36304
 
35975
36305
  /**
35976
36306
  * Map of the different types of conversions warnings and their name in error messages
@@ -38215,7 +38545,7 @@ class XlsxBaseExtractor {
38215
38545
  */
38216
38546
  handleMissingValue(parentElement, missingElementName, optionalArgs) {
38217
38547
  if (optionalArgs?.required) {
38218
- if (optionalArgs?.default) {
38548
+ if (optionalArgs?.default !== undefined) {
38219
38549
  this.warningManager.addParsingWarning(`Missing required ${missingElementName} in element <${parentElement.tagName}> of ${this.currentFile}, replacing it by the default value ${optionalArgs.default}`);
38220
38550
  }
38221
38551
  else {
@@ -41942,6 +42272,7 @@ const findAndReplace = {
41942
42272
  execute: (env) => {
41943
42273
  env.openSidePanel("FindAndReplace", {});
41944
42274
  },
42275
+ isEnabled: (env) => !env.isSmall,
41945
42276
  icon: "o-spreadsheet-Icon.SEARCH",
41946
42277
  };
41947
42278
  const deleteValues = {
@@ -42198,11 +42529,13 @@ const insertCellShiftRight = {
42198
42529
  const insertChart = {
42199
42530
  name: _t("Chart"),
42200
42531
  execute: CREATE_CHART,
42532
+ isEnabled: (env) => !env.isSmall,
42201
42533
  icon: "o-spreadsheet-Icon.INSERT_CHART",
42202
42534
  };
42203
42535
  const insertPivot = {
42204
42536
  name: _t("Pivot table"),
42205
42537
  execute: CREATE_PIVOT,
42538
+ isEnabled: (env) => !env.isSmall,
42206
42539
  icon: "o-spreadsheet-Icon.PIVOT",
42207
42540
  };
42208
42541
  const insertImage = {
@@ -42210,12 +42543,14 @@ const insertImage = {
42210
42543
  description: "Ctrl+O",
42211
42544
  execute: CREATE_IMAGE,
42212
42545
  isVisible: (env) => env.imageProvider !== undefined,
42546
+ isEnabled: (env) => !env.isSmall,
42213
42547
  icon: "o-spreadsheet-Icon.INSERT_IMAGE",
42214
42548
  };
42215
42549
  const insertTable = {
42216
42550
  name: () => _t("Table"),
42217
42551
  execute: INSERT_TABLE,
42218
42552
  isVisible: (env) => IS_SELECTION_CONTINUOUS(env) && !env.model.getters.getFirstTableInSelection(),
42553
+ isEnabled: (env) => !env.isSmall,
42219
42554
  icon: "o-spreadsheet-Icon.PAINT_TABLE",
42220
42555
  };
42221
42556
  const insertFunction = {
@@ -42321,6 +42656,7 @@ const insertDropdown = {
42321
42656
  },
42322
42657
  });
42323
42658
  },
42659
+ isEnabled: (env) => !env.isSmall,
42324
42660
  icon: "o-spreadsheet-Icon.INSERT_DROPDOWN",
42325
42661
  };
42326
42662
  const insertSheet = {
@@ -42590,7 +42926,7 @@ const pivotProperties = {
42590
42926
  isVisible: (env) => {
42591
42927
  const position = env.model.getters.getActivePosition();
42592
42928
  const pivotId = env.model.getters.getPivotIdFromPosition(position);
42593
- return (pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42929
+ return (!env.isSmall && pivotId && env.model.getters.isExistingPivot(pivotId)) || false;
42594
42930
  },
42595
42931
  isReadonlyAllowed: true,
42596
42932
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -42708,7 +43044,7 @@ function isPivotSortMenuItemActive(env, order) {
42708
43044
  }
42709
43045
 
42710
43046
  //------------------------------------------------------------------------------
42711
- // Context Menu Registry
43047
+ // Context MenuPopover Registry
42712
43048
  //------------------------------------------------------------------------------
42713
43049
  const cellMenuRegistry = new MenuItemRegistry();
42714
43050
  cellMenuRegistry
@@ -42791,6 +43127,7 @@ cellMenuRegistry
42791
43127
  .add("edit_table", {
42792
43128
  ...editTable,
42793
43129
  isVisible: SELECTION_CONTAINS_SINGLE_TABLE,
43130
+ isEnabled: (env) => !env.isSmall,
42794
43131
  sequence: 140,
42795
43132
  })
42796
43133
  .add("delete_table", {
@@ -42859,6 +43196,7 @@ const removeDuplicates = {
42859
43196
  }
42860
43197
  env.openSidePanel("RemoveDuplicates", {});
42861
43198
  },
43199
+ isEnabled: (env) => !env.isSmall,
42862
43200
  };
42863
43201
  const trimWhitespace = {
42864
43202
  name: _t("Trim whitespace"),
@@ -42886,7 +43224,7 @@ const splitToColumns = {
42886
43224
  name: _t("Split text to columns"),
42887
43225
  sequence: 1,
42888
43226
  execute: (env) => env.openSidePanel("SplitToColumns", {}),
42889
- isEnabled: (env) => env.model.getters.isSingleColSelected(),
43227
+ isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
42890
43228
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
42891
43229
  };
42892
43230
  const reinsertDynamicPivotMenu = {
@@ -42973,7 +43311,7 @@ const formatNumberAccounting = createFormatActionSpec({
42973
43311
  const EXAMPLE_DATE = parseLiteral("2023/09/26 10:43:00 PM", DEFAULT_LOCALE);
42974
43312
  const formatCustomCurrency = {
42975
43313
  name: _t("Custom currency"),
42976
- isVisible: (env) => env.loadCurrencies !== undefined,
43314
+ isVisible: (env) => env.loadCurrencies !== undefined && !env.isSmall,
42977
43315
  execute: (env) => env.openSidePanel("CustomCurrency", {}),
42978
43316
  };
42979
43317
  const formatNumberDate = createFormatActionSpec({
@@ -43196,6 +43534,7 @@ const formatWrappingClip = {
43196
43534
  const formatCF = {
43197
43535
  name: _t("Conditional formatting"),
43198
43536
  execute: OPEN_CF_SIDEPANEL_ACTION,
43537
+ isEnabled: (env) => !env.isSmall,
43199
43538
  icon: "o-spreadsheet-Icon.CONDITIONAL_FORMAT",
43200
43539
  };
43201
43540
  const clearFormat = {
@@ -43579,8 +43918,7 @@ class ArrayFormulaHighlight extends SpreadsheetStore {
43579
43918
  }
43580
43919
  return [
43581
43920
  {
43582
- sheetId: position.sheetId,
43583
- zone,
43921
+ range: this.model.getters.getRangeFromZone(position.sheetId, zone),
43584
43922
  dashed: cell.value === CellErrorType.SpilledBlocked,
43585
43923
  color: "#17A2B8",
43586
43924
  noFill: true,
@@ -43665,6 +44003,7 @@ function useDragAndDropBeyondTheViewport(env) {
43665
44003
  let previousEvClientPosition;
43666
44004
  let startingX;
43667
44005
  let startingY;
44006
+ let scrollDirection = "all";
43668
44007
  const getters = env.model.getters;
43669
44008
  let cleanUpFns = [];
43670
44009
  const cleanUp = () => {
@@ -43690,55 +44029,59 @@ function useDragAndDropBeyondTheViewport(env) {
43690
44029
  let timeoutDelay = MAX_DELAY;
43691
44030
  const x = currentEv.clientX - position.left;
43692
44031
  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;
44032
+ if (scrollDirection !== "vertical") {
44033
+ const previousX = previousEvClientPosition.clientX - position.left;
44034
+ const edgeScrollInfoX = getters.getEdgeScrollCol(x, previousX, startingX);
44035
+ if (edgeScrollInfoX.canEdgeScroll) {
44036
+ canEdgeScroll = true;
44037
+ timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoX.delay);
44038
+ let newTarget = colIndex;
44039
+ switch (edgeScrollInfoX.direction) {
44040
+ case "reset":
44041
+ colIndex = newTarget = xSplit;
44042
+ break;
44043
+ case 1:
44044
+ colIndex = right;
44045
+ newTarget = left + 1;
44046
+ break;
44047
+ case -1:
44048
+ colIndex = left - 1;
44049
+ while (env.model.getters.isColHidden(sheetId, colIndex)) {
44050
+ colIndex--;
44051
+ }
44052
+ newTarget = colIndex;
44053
+ break;
44054
+ }
44055
+ scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43714
44056
  }
43715
- scrollX = getters.getColDimensions(sheetId, newTarget).start - offsetCorrectionX;
43716
44057
  }
43717
44058
  const y = currentEv.clientY - position.top;
43718
44059
  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;
44060
+ if (scrollDirection !== "horizontal") {
44061
+ const previousY = previousEvClientPosition.clientY - position.top;
44062
+ const edgeScrollInfoY = getters.getEdgeScrollRow(y, previousY, startingY);
44063
+ if (edgeScrollInfoY.canEdgeScroll) {
44064
+ canEdgeScroll = true;
44065
+ timeoutDelay = Math.min(timeoutDelay, edgeScrollInfoY.delay);
44066
+ let newTarget = rowIndex;
44067
+ switch (edgeScrollInfoY.direction) {
44068
+ case "reset":
44069
+ rowIndex = newTarget = ySplit;
44070
+ break;
44071
+ case 1:
44072
+ rowIndex = bottom;
44073
+ newTarget = top + 1;
44074
+ break;
44075
+ case -1:
44076
+ rowIndex = top - 1;
44077
+ while (env.model.getters.isRowHidden(sheetId, rowIndex)) {
44078
+ rowIndex--;
44079
+ }
44080
+ newTarget = rowIndex;
44081
+ break;
44082
+ }
44083
+ scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43740
44084
  }
43741
- scrollY = env.model.getters.getRowDimensions(sheetId, newTarget).start - offsetCorrectionY;
43742
44085
  }
43743
44086
  if (!canEdgeScroll) {
43744
44087
  colIndex = adjustIndexWithinBounds(colIndex, x, getters.getNumberCols(sheetId) - 1);
@@ -43758,9 +44101,10 @@ function useDragAndDropBeyondTheViewport(env) {
43758
44101
  pointerUpCallback?.();
43759
44102
  cleanUp();
43760
44103
  };
43761
- const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp) => {
44104
+ const startFn = (initialPointerCoordinates, onPointerMove, onPointerUp, startScrollDirection = "all") => {
43762
44105
  cleanUp();
43763
44106
  const position = gridOverlayPosition();
44107
+ scrollDirection = startScrollDirection;
43764
44108
  startingX = initialPointerCoordinates.clientX - position.left;
43765
44109
  startingY = initialPointerCoordinates.clientY - position.top;
43766
44110
  previousEvClientPosition = {
@@ -44238,7 +44582,7 @@ class GridComposer extends Component {
44238
44582
  });
44239
44583
  }
44240
44584
  get shouldDisplayCellReference() {
44241
- return this.isCellReferenceVisible;
44585
+ return !this.env.isMobile() && this.isCellReferenceVisible;
44242
44586
  }
44243
44587
  get cellReference() {
44244
44588
  const { col, row, sheetId } = this.composerStore.currentEditedCell;
@@ -44279,11 +44623,12 @@ class GridComposer extends Component {
44279
44623
  onInputContextMenu: this.props.onInputContextMenu,
44280
44624
  composerStore: this.composerStore,
44281
44625
  inputStyle: `max-height: ${maxHeight}px;`,
44626
+ inputMode: this.composerStore.editionMode === "inactive" ? "none" : undefined,
44282
44627
  };
44283
44628
  }
44284
44629
  get containerStyle() {
44285
- if (this.composerStore.editionMode === "inactive") {
44286
- return `z-index: -1000;`;
44630
+ if (this.composerStore.editionMode === "inactive" || this.env.isMobile()) {
44631
+ return `z-index: -1000; opacity: 0;`; // opacity 0 for safari on ios
44287
44632
  }
44288
44633
  const _isFormula = isFormula(this.composerStore.currentContent);
44289
44634
  const cell = this.env.model.getters.getActiveCell();
@@ -44312,11 +44657,13 @@ class GridComposer extends Component {
44312
44657
  *
44313
44658
  * The +-1 are there to include cell borders in the composer sizing/positioning
44314
44659
  */
44660
+ const minHeight = Math.min(height + 1, maxHeight);
44661
+ const minWidth = Math.min(width + 1, maxWidth);
44315
44662
  return cssPropertiesToCss({
44316
44663
  left: `${left - 1}px`,
44317
44664
  top: `${top}px`,
44318
- "min-width": `${width + 1}px`,
44319
- "min-height": `${height + 1}px`,
44665
+ "min-width": `${minWidth}px`,
44666
+ "min-height": `${minHeight}px`,
44320
44667
  "max-width": `${maxWidth}px`,
44321
44668
  "max-height": `${maxHeight}px`,
44322
44669
  background,
@@ -44813,6 +45160,9 @@ class FiguresContainer extends Component {
44813
45160
  if (!selectResult.isSuccessful) {
44814
45161
  return;
44815
45162
  }
45163
+ if (this.env.isMobile()) {
45164
+ return;
45165
+ }
44816
45166
  const sheetId = this.env.model.getters.getActiveSheetId();
44817
45167
  const initialMousePosition = { x: ev.clientX, y: ev.clientY };
44818
45168
  const initialScrollPosition = this.env.model.getters.getActiveSheetScrollInfo();
@@ -45105,78 +45455,6 @@ class GridAddRowsFooter extends Component {
45105
45455
  }
45106
45456
  }
45107
45457
 
45108
- class GridCellIcon extends Component {
45109
- static template = "o-spreadsheet-GridCellIcon";
45110
- static props = {
45111
- icon: Object,
45112
- verticalAlign: { type: String, optional: true },
45113
- slots: Object,
45114
- };
45115
- get iconStyle() {
45116
- const cellPosition = this.props.icon.position;
45117
- const merge = this.env.model.getters.getMerge(cellPosition);
45118
- const zone = merge || positionToZone(cellPosition);
45119
- const rect = this.env.model.getters.getVisibleRectWithoutHeaders(zone);
45120
- const x = this.getIconHorizontalPosition(rect, cellPosition);
45121
- const y = this.getIconVerticalPosition(rect, cellPosition);
45122
- return cssPropertiesToCss({
45123
- top: `${y}px`,
45124
- left: `${x}px`,
45125
- width: `${this.props.icon.size}px`,
45126
- height: `${this.props.icon.size}px`,
45127
- });
45128
- }
45129
- getIconVerticalPosition(rect, cellPosition) {
45130
- const start = rect.y;
45131
- const end = rect.y + rect.height;
45132
- const cell = this.env.model.getters.getCell(cellPosition);
45133
- const align = this.props.verticalAlign || cell?.style?.verticalAlign || DEFAULT_VERTICAL_ALIGN;
45134
- switch (align) {
45135
- case "bottom":
45136
- return end - GRID_ICON_MARGIN - GRID_ICON_EDGE_LENGTH;
45137
- case "top":
45138
- return start + GRID_ICON_MARGIN;
45139
- default:
45140
- const centeringOffset = Math.floor((end - start - GRID_ICON_EDGE_LENGTH) / 2);
45141
- return end - GRID_ICON_EDGE_LENGTH - centeringOffset;
45142
- }
45143
- }
45144
- getIconHorizontalPosition(rect, cellPosition) {
45145
- const start = rect.x;
45146
- const end = rect.x + rect.width;
45147
- const cell = this.env.model.getters.getCell(cellPosition);
45148
- const evaluatedCell = this.env.model.getters.getEvaluatedCell(cellPosition);
45149
- const align = this.props.icon.horizontalAlign || cell?.style?.align || evaluatedCell.defaultAlign;
45150
- switch (align) {
45151
- case "right":
45152
- return end - this.props.icon.size - this.props.icon.margin;
45153
- case "left":
45154
- return start + this.props.icon.margin;
45155
- default:
45156
- const centeringOffset = Math.floor((end - start - this.props.icon.size) / 2);
45157
- return end - this.props.icon.size - centeringOffset;
45158
- }
45159
- }
45160
- isPositionVisible(position) {
45161
- const rect = this.env.model.getters.getVisibleRect(positionToZone(position));
45162
- return !(rect.width === 0 || rect.height === 0);
45163
- }
45164
- }
45165
-
45166
- class GridCellIconOverlay extends Component {
45167
- static template = "o-spreadsheet-GridCellIconOverlay";
45168
- static props = {};
45169
- static components = { GridCellIcon };
45170
- get icons() {
45171
- const icons = [];
45172
- for (const position of this.env.model.getters.getVisibleCellPositions()) {
45173
- const cellIcons = this.env.model.getters.getCellIcons(position);
45174
- icons.push(...cellIcons.filter((icon) => icon.component));
45175
- }
45176
- return icons;
45177
- }
45178
- }
45179
-
45180
45458
  /**
45181
45459
  * Manages an event listener on a ref. Useful for hooks that want to manage
45182
45460
  * event listeners, especially more than one. Prefer using t-on directly in
@@ -45265,7 +45543,7 @@ class PaintFormatStore extends SpreadsheetStore {
45265
45543
  return [];
45266
45544
  }
45267
45545
  return data.zones.map((zone) => ({
45268
- zone,
45546
+ range: this.model.getters.getRangeFromZone(data.sheetId, zone),
45269
45547
  color: SELECTION_BORDER_COLOR,
45270
45548
  dashed: true,
45271
45549
  sheetId: data.sheetId,
@@ -45288,7 +45566,7 @@ class HoveredTableStore extends SpreadsheetStore {
45288
45566
  }
45289
45567
  }
45290
45568
  hover(position) {
45291
- if (position.col === this.col && position.row === this.row) {
45569
+ if (!this.getters.isDashboard() || (position.col === this.col && position.row === this.row)) {
45292
45570
  return "noStateChange";
45293
45571
  }
45294
45572
  this.col = position.col;
@@ -45322,6 +45600,14 @@ class HoveredTableStore extends SpreadsheetStore {
45322
45600
  }
45323
45601
  }
45324
45602
 
45603
+ class HoveredIconStore extends SpreadsheetStore {
45604
+ mutators = ["setHoveredIcon"];
45605
+ hoveredIcon = undefined;
45606
+ setHoveredIcon(icon) {
45607
+ this.hoveredIcon = icon;
45608
+ }
45609
+ }
45610
+
45325
45611
  const CURSOR_SVG = /*xml*/ `
45326
45612
  <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
45613
  `;
@@ -45393,10 +45679,11 @@ function useCellHovered(env, gridRef) {
45393
45679
  return pause();
45394
45680
  }
45395
45681
  }
45396
- useRefListener(gridRef, "pointermove", updateMousePosition);
45682
+ useRefListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(ev));
45397
45683
  useRefListener(gridRef, "mouseleave", onMouseLeave);
45398
45684
  useRefListener(gridRef, "mouseenter", resume);
45399
45685
  useRefListener(gridRef, "pointerdown", recompute);
45686
+ useRefListener(gridRef, "pointerdown", (ev) => env.isMobile() && updateMousePosition(ev));
45400
45687
  useExternalListener(window, "click", handleGlobalClick);
45401
45688
  function handleGlobalClick(e) {
45402
45689
  const target = e.target;
@@ -45425,11 +45712,11 @@ class GridOverlay extends Component {
45425
45712
  onGridMoved: Function,
45426
45713
  gridOverlayDimensions: String,
45427
45714
  slots: { type: Object, optional: true },
45715
+ getGridSize: Function,
45428
45716
  };
45429
45717
  static components = {
45430
45718
  FiguresContainer,
45431
45719
  GridAddRowsFooter,
45432
- GridCellIconOverlay,
45433
45720
  };
45434
45721
  static defaultProps = {
45435
45722
  onCellDoubleClicked: () => { },
@@ -45441,15 +45728,17 @@ class GridOverlay extends Component {
45441
45728
  gridOverlay = useRef("gridOverlay");
45442
45729
  cellPopovers;
45443
45730
  paintFormatStore;
45731
+ hoveredIconStore;
45444
45732
  setup() {
45445
45733
  useCellHovered(this.env, this.gridOverlay);
45446
45734
  const resizeObserver = new ResizeObserver(() => {
45447
45735
  const boundingRect = this.gridOverlayEl.getBoundingClientRect();
45736
+ const { width, height } = this.props.getGridSize();
45448
45737
  this.props.onGridResized({
45449
45738
  x: boundingRect.left,
45450
45739
  y: boundingRect.top,
45451
- height: this.gridOverlayEl.clientHeight,
45452
- width: this.gridOverlayEl.clientWidth,
45740
+ height: height,
45741
+ width: width,
45453
45742
  });
45454
45743
  });
45455
45744
  onMounted(() => {
@@ -45460,6 +45749,7 @@ class GridOverlay extends Component {
45460
45749
  });
45461
45750
  this.cellPopovers = useStore(CellPopoverStore);
45462
45751
  this.paintFormatStore = useStore(PaintFormatStore);
45752
+ this.hoveredIconStore = useStore(HoveredIconStore);
45463
45753
  }
45464
45754
  get gridOverlayEl() {
45465
45755
  if (!this.gridOverlay.el) {
@@ -45468,26 +45758,59 @@ class GridOverlay extends Component {
45468
45758
  return this.gridOverlay.el;
45469
45759
  }
45470
45760
  get style() {
45471
- return this.props.gridOverlayDimensions;
45761
+ return (this.props.gridOverlayDimensions +
45762
+ cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "default" }));
45472
45763
  }
45473
45764
  get isPaintingFormat() {
45474
45765
  return this.paintFormatStore.isActive;
45475
45766
  }
45476
- onMouseDown(ev) {
45477
- if (ev.button > 0) {
45767
+ onPointerMove(ev) {
45768
+ if (this.env.isMobile()) {
45769
+ return;
45770
+ }
45771
+ const icon = this.getInteractiveIconAtEvent(ev);
45772
+ const hoveredIcon = icon?.type ? { id: icon.type, position: icon.position } : undefined;
45773
+ if (!deepEquals(hoveredIcon, this.hoveredIconStore.hoveredIcon)) {
45774
+ this.hoveredIconStore.setHoveredIcon(hoveredIcon);
45775
+ }
45776
+ }
45777
+ onPointerDown(ev) {
45778
+ if (ev.button > 0 || this.env.isMobile()) {
45478
45779
  // not main button, probably a context menu
45479
45780
  return;
45480
45781
  }
45481
- if (ev.target === this.gridOverlay.el && this.cellPopovers.isOpen) {
45482
- this.cellPopovers.close();
45782
+ this.onCellClicked(ev);
45783
+ }
45784
+ onClick(ev) {
45785
+ if (ev.button > 0 || !this.env.isMobile()) {
45786
+ // not main button, probably a context menu
45787
+ return;
45483
45788
  }
45789
+ this.onCellClicked(ev);
45790
+ }
45791
+ onCellClicked(ev) {
45792
+ const openedPopover = this.cellPopovers.persistentCellPopover;
45484
45793
  const [col, row] = this.getCartesianCoordinates(ev);
45485
45794
  this.props.onCellClicked(col, row, {
45486
45795
  expandZone: ev.shiftKey,
45487
45796
  addZone: isCtrlKey(ev),
45488
45797
  }, ev);
45798
+ const clickedIcon = this.getInteractiveIconAtEvent(ev);
45799
+ if (clickedIcon?.onClick) {
45800
+ clickedIcon.onClick(clickedIcon.position, this.env);
45801
+ }
45802
+ if (ev.target === this.gridOverlay.el &&
45803
+ this.cellPopovers.isOpen &&
45804
+ deepEquals(openedPopover, this.cellPopovers.persistentCellPopover)) {
45805
+ // Only close the popover if props.click/icon.click didn't open a new one
45806
+ this.cellPopovers.close();
45807
+ return;
45808
+ }
45489
45809
  }
45490
45810
  onDoubleClick(ev) {
45811
+ if (this.getInteractiveIconAtEvent(ev)) {
45812
+ return;
45813
+ }
45491
45814
  const [col, row] = this.getCartesianCoordinates(ev);
45492
45815
  this.props.onCellDoubleClicked(col, row);
45493
45816
  }
@@ -45503,6 +45826,24 @@ class GridOverlay extends Component {
45503
45826
  const rowIndex = this.env.model.getters.getRowIndex(y);
45504
45827
  return [colIndex, rowIndex];
45505
45828
  }
45829
+ getInteractiveIconAtEvent(ev) {
45830
+ const gridOverLayRect = getRefBoundingRect(this.gridOverlay);
45831
+ const gridOffset = this.env.model.getters.getGridOffset();
45832
+ const x = ev.clientX - gridOverLayRect.x + gridOffset.x;
45833
+ const y = ev.clientY - gridOverLayRect.y + gridOffset.y;
45834
+ const [col, row] = this.getCartesianCoordinates(ev);
45835
+ const sheetId = this.env.model.getters.getActiveSheetId();
45836
+ let position = { col, row, sheetId };
45837
+ const merge = this.env.model.getters.getMerge(position);
45838
+ if (merge) {
45839
+ position = { col: merge.left, row: merge.top, sheetId };
45840
+ }
45841
+ const icons = this.env.model.getters.getCellIcons(position);
45842
+ const icon = icons.find((icon) => {
45843
+ return isPointInsideRect(x, y, this.env.model.getters.getCellIconRect(icon));
45844
+ });
45845
+ return icon?.onClick ? icon : undefined;
45846
+ }
45506
45847
  }
45507
45848
 
45508
45849
  class GridPopover extends Component {
@@ -45663,6 +46004,9 @@ class AbstractResizer extends Component {
45663
46004
  this.state.waitingForMove = false;
45664
46005
  }
45665
46006
  onMouseMove(ev) {
46007
+ if (this.env.isMobile()) {
46008
+ return;
46009
+ }
45666
46010
  if (this.state.isResizing || this.state.isMoving || this.state.isSelecting) {
45667
46011
  return;
45668
46012
  }
@@ -45707,7 +46051,21 @@ class AbstractResizer extends Component {
45707
46051
  };
45708
46052
  startDnd(onMouseMove, onMouseUp);
45709
46053
  }
46054
+ onClick(ev) {
46055
+ if (!this.env.isMobile()) {
46056
+ return;
46057
+ }
46058
+ if (ev.button > 0) {
46059
+ // not main button, probably a context menu
46060
+ return;
46061
+ }
46062
+ const index = this._getElementIndex(this._getEvOffset(ev));
46063
+ this._selectElement(index, false);
46064
+ }
45710
46065
  select(ev) {
46066
+ if (this.env.isMobile()) {
46067
+ return;
46068
+ }
45711
46069
  if (ev.button > 0) {
45712
46070
  // not main button, probably a context menu
45713
46071
  return;
@@ -45776,6 +46134,9 @@ class AbstractResizer extends Component {
45776
46134
  this.dragNDropGrid.start(ev, mouseMoveMovement, mouseUpMovement);
45777
46135
  }
45778
46136
  startSelection(ev, index) {
46137
+ if (this.env.isMobile()) {
46138
+ return;
46139
+ }
45779
46140
  this.state.isSelecting = true;
45780
46141
  if (ev.shiftKey) {
45781
46142
  this._increaseSelection(index);
@@ -46221,11 +46582,13 @@ class GridRenderer {
46221
46582
  renderer;
46222
46583
  fingerprints;
46223
46584
  hoveredTables;
46585
+ hoveredIcon;
46224
46586
  constructor(get) {
46225
46587
  this.getters = get(ModelStore).getters;
46226
46588
  this.renderer = get(RendererStore);
46227
46589
  this.fingerprints = get(FormulaFingerprintStore);
46228
46590
  this.hoveredTables = get(HoveredTableStore);
46591
+ this.hoveredIcon = get(HoveredIconStore);
46229
46592
  this.renderer.register(this);
46230
46593
  }
46231
46594
  get renderingLayers() {
@@ -46462,7 +46825,7 @@ class GridRenderer {
46462
46825
  // compute vertical align start point parameter:
46463
46826
  const textLineHeight = computeTextFontSizeInPixels(style);
46464
46827
  const numberOfLines = box.content.textLines.length;
46465
- let y = this.computeTextYCoordinate(box, textLineHeight, numberOfLines);
46828
+ let y = this.getters.computeTextYCoordinate(box, textLineHeight, style.verticalAlign, numberOfLines);
46466
46829
  // use the horizontal and the vertical start points to:
46467
46830
  // fill text / fill strikethrough / fill underline
46468
46831
  for (const brokenLine of box.content.textLines) {
@@ -46479,7 +46842,12 @@ class GridRenderer {
46479
46842
  const { ctx } = renderingContext;
46480
46843
  for (const box of boxes) {
46481
46844
  for (const icon of Object.values(box.icons)) {
46482
- if (!icon || !icon.svg) {
46845
+ if (!icon) {
46846
+ continue;
46847
+ }
46848
+ const isHovered = deepEquals({ id: icon.type, position: icon.position }, this.hoveredIcon.hoveredIcon);
46849
+ const svg = isHovered ? icon.hoverSvg || icon.svg : icon.svg;
46850
+ if (!svg) {
46483
46851
  continue;
46484
46852
  }
46485
46853
  ctx.save();
@@ -46487,46 +46855,17 @@ class GridRenderer {
46487
46855
  ctx.rect(box.x, box.y, box.width, box.height);
46488
46856
  ctx.clip();
46489
46857
  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);
46858
+ const { x, y } = this.getters.getCellIconRect(icon);
46859
+ ctx.translate(x, y);
46503
46860
  ctx.scale(iconSize / svg.width, iconSize / svg.height);
46504
- ctx.fillStyle = svg.fillColor;
46505
- ctx.fill(new Path2D(svg.path));
46861
+ for (const path of svg.paths) {
46862
+ ctx.fillStyle = path.fillColor;
46863
+ ctx.fill(getPath2D(path.path));
46864
+ }
46506
46865
  ctx.restore();
46507
46866
  }
46508
46867
  }
46509
46868
  }
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
46869
  drawHeaders(renderingContext) {
46531
46870
  const { ctx, thinLineWidth } = renderingContext;
46532
46871
  const visibleCols = this.getters.getSheetViewVisibleCols();
@@ -46963,6 +47302,9 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46963
47302
  const deltaX = lastX - clientX;
46964
47303
  const deltaY = lastY - clientY;
46965
47304
  const elapsedTime = currentTime - lastTime;
47305
+ if (!elapsedTime) {
47306
+ return;
47307
+ }
46966
47308
  velocityX = deltaX / elapsedTime;
46967
47309
  velocityY = deltaY / elapsedTime;
46968
47310
  lastX = clientX;
@@ -46983,6 +47325,11 @@ function useTouchScroll(ref, updateScroll, canMoveUp) {
46983
47325
  function onTouchEnd(ev) {
46984
47326
  isMouseDown = false;
46985
47327
  lastX = lastY = 0;
47328
+ if (resetTimeout) {
47329
+ clearTimeout(resetTimeout);
47330
+ }
47331
+ velocityX *= 1.2;
47332
+ velocityY *= 1.2;
46986
47333
  requestAnimationFrame(scroll);
46987
47334
  }
46988
47335
  function scroll() {
@@ -47067,12 +47414,16 @@ class Border extends Component {
47067
47414
  }
47068
47415
  }
47069
47416
 
47417
+ const MOBILE_HANDLER_WIDTH = 40;
47070
47418
  css /* scss */ `
47071
47419
  .o-corner {
47072
47420
  position: absolute;
47073
- height: 8px;
47074
- width: 8px;
47421
+ }
47422
+
47423
+ .o-corner-button {
47075
47424
  border: 1px solid white;
47425
+ height: ${AUTOFILL_EDGE_LENGTH}px;
47426
+ width: ${AUTOFILL_EDGE_LENGTH}px;
47076
47427
  }
47077
47428
  .o-corner-nw,
47078
47429
  .o-corner-se {
@@ -47099,34 +47450,61 @@ class Corner extends Component {
47099
47450
  isResizing: Boolean,
47100
47451
  onResizeHighlight: Function,
47101
47452
  };
47102
- isTop = this.props.orientation[0] === "n";
47103
- isLeft = this.props.orientation[1] === "w";
47104
- get style() {
47453
+ dirX;
47454
+ dirY;
47455
+ setup() {
47456
+ const { dirX, dirY } = orientationToDir(this.props.orientation);
47457
+ this.dirX = dirX;
47458
+ this.dirY = dirY;
47459
+ }
47460
+ get handlerStyle() {
47105
47461
  const z = this.props.zone;
47106
- const col = this.isLeft ? z.left : z.right;
47107
- const row = this.isTop ? z.top : z.bottom;
47108
47462
  const rect = this.env.model.getters.getVisibleRect({
47109
- left: col,
47110
- right: col,
47111
- top: row,
47112
- bottom: row,
47463
+ left: this.dirX === 1 ? z.right : z.left,
47464
+ right: this.dirX === -1 ? z.left : z.right,
47465
+ top: this.dirY === 1 ? z.bottom : z.top,
47466
+ bottom: this.dirY === -1 ? z.top : z.bottom,
47113
47467
  });
47114
47468
  // Don't show if not visible in the viewport
47115
47469
  if (rect.width * rect.height === 0) {
47116
- return `display:none`;
47470
+ return `display: none !important;`;
47471
+ }
47472
+ const leftValue = rect.x + rect.width / 2 + (this.dirX * rect.width) / 2;
47473
+ const topValue = rect.y + rect.height / 2 + (this.dirY * rect.height) / 2;
47474
+ const edgeLength = this.getHandlerEdgeLength();
47475
+ const css = {
47476
+ left: `${leftValue - edgeLength / 2}px`,
47477
+ top: `${topValue - edgeLength / 2}px`,
47478
+ height: `${edgeLength}px`,
47479
+ width: `${edgeLength}px`,
47480
+ };
47481
+ if (this.env.isMobile()) {
47482
+ css["border-radius"] = `${edgeLength / 2}px`;
47117
47483
  }
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`,
47484
+ return cssPropertiesToCss(css);
47485
+ }
47486
+ getHandlerEdgeLength() {
47487
+ return this.env.isMobile() ? MOBILE_HANDLER_WIDTH : AUTOFILL_EDGE_LENGTH;
47488
+ }
47489
+ get buttonLook() {
47490
+ const css = {
47123
47491
  "background-color": this.props.color,
47124
- });
47492
+ cursor: `${this.props.orientation}-resize`,
47493
+ };
47494
+ if (this.env.isMobile()) {
47495
+ css["border-radius"] = `${AUTOFILL_EDGE_LENGTH / 2}px`;
47496
+ }
47497
+ return cssPropertiesToCss(css);
47125
47498
  }
47126
47499
  onMouseDown(ev) {
47127
- this.props.onResizeHighlight(ev, this.isLeft, this.isTop);
47500
+ this.props.onResizeHighlight(ev, this.dirX, this.dirY);
47128
47501
  }
47129
47502
  }
47503
+ function orientationToDir(or) {
47504
+ const dirX = or.includes("w") ? -1 : or.includes("e") ? 1 : 0;
47505
+ const dirY = or.includes("n") ? -1 : or.includes("s") ? 1 : 0;
47506
+ return { dirX, dirY };
47507
+ }
47130
47508
 
47131
47509
  css /*SCSS*/ `
47132
47510
  .o-highlight {
@@ -47136,7 +47514,7 @@ css /*SCSS*/ `
47136
47514
  class Highlight extends Component {
47137
47515
  static template = "o-spreadsheet-Highlight";
47138
47516
  static props = {
47139
- zone: Object,
47517
+ range: Object,
47140
47518
  color: String,
47141
47519
  };
47142
47520
  static components = {
@@ -47147,26 +47525,49 @@ class Highlight extends Component {
47147
47525
  shiftingMode: "none",
47148
47526
  });
47149
47527
  dragNDropGrid = useDragAndDropBeyondTheViewport(this.env);
47150
- onResizeHighlight(ev, isLeft, isTop) {
47528
+ get cornerOrientations() {
47529
+ if (!this.env.isMobile()) {
47530
+ return ["nw", "ne", "sw", "se"];
47531
+ }
47532
+ const z = this.props.range.unboundedZone;
47533
+ if (z.bottom === undefined) {
47534
+ return ["w", "e"];
47535
+ }
47536
+ else if (z.right === undefined) {
47537
+ return ["n", "s"];
47538
+ }
47539
+ else {
47540
+ return ["nw", "se"];
47541
+ }
47542
+ }
47543
+ onResizeHighlight(ev, dirX, dirY) {
47151
47544
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47152
47545
  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;
47546
+ const z = this.props.range.zone;
47547
+ const pivotCol = dirX === 1 ? z.left : z.right;
47548
+ const pivotRow = dirY === 1 ? z.top : z.bottom;
47549
+ let lastCol = dirX === 1 ? z.right : z.left;
47550
+ let lastRow = dirY === 1 ? z.bottom : z.top;
47158
47551
  let currentZone = z;
47552
+ let scrollDirection = "all";
47553
+ if (this.env.isMobile()) {
47554
+ scrollDirection = dirX === 0 ? "vertical" : dirY === 0 ? "horizontal" : "all";
47555
+ }
47159
47556
  this.env.model.dispatch("START_CHANGE_HIGHLIGHT", { zone: currentZone });
47160
47557
  const mouseMove = (col, row) => {
47161
47558
  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
- };
47559
+ let { left, right, top, bottom } = currentZone;
47560
+ if (scrollDirection !== "horizontal") {
47561
+ lastRow = lastRow = clip(row === -1 ? lastRow : row, 0, this.env.model.getters.getNumberRows(activeSheetId) - 1);
47562
+ top = Math.min(pivotRow, lastRow);
47563
+ bottom = Math.max(pivotRow, lastRow);
47564
+ }
47565
+ if (scrollDirection !== "vertical") {
47566
+ lastCol = clip(col === -1 ? lastCol : col, 0, this.env.model.getters.getNumberCols(activeSheetId) - 1);
47567
+ left = Math.min(pivotCol, lastCol);
47568
+ right = Math.max(pivotCol, lastCol);
47569
+ }
47570
+ const newZone = { left, right, top, bottom };
47170
47571
  if (!isEqual(newZone, currentZone)) {
47171
47572
  this.env.model.selection.selectZone({
47172
47573
  cell: { col: newZone.left, row: newZone.top },
@@ -47179,11 +47580,11 @@ class Highlight extends Component {
47179
47580
  const mouseUp = () => {
47180
47581
  this.highlightState.shiftingMode = "none";
47181
47582
  };
47182
- this.dragNDropGrid.start(ev, mouseMove, mouseUp);
47583
+ this.dragNDropGrid.start(ev, mouseMove, mouseUp, scrollDirection);
47183
47584
  }
47184
47585
  onMoveHighlight(ev) {
47185
47586
  this.highlightState.shiftingMode = "isMoving";
47186
- const z = this.props.zone;
47587
+ const z = this.props.range.zone;
47187
47588
  const position = gridOverlayPosition();
47188
47589
  const activeSheetId = this.env.model.getters.getActiveSheetId();
47189
47590
  const initCol = this.env.model.getters.getColIndex(ev.clientX - position.left);
@@ -47405,6 +47806,18 @@ class VerticalScrollBar extends Component {
47405
47806
  }
47406
47807
  }
47407
47808
 
47809
+ class Selection extends Component {
47810
+ static template = "o-spreadsheet-Selection";
47811
+ static props = {};
47812
+ static components = { Highlight };
47813
+ get highlightProps() {
47814
+ const sheetId = this.env.model.getters.getActiveSheetId();
47815
+ const zone = this.env.model.getters.getUnboundedZone(sheetId, this.env.model.getters.getSelectedZone());
47816
+ const range = this.env.model.getters.getRangeFromZone(sheetId, zone);
47817
+ return { range, color: SELECTION_BORDER_COLOR };
47818
+ }
47819
+ }
47820
+
47408
47821
  class Section extends Component {
47409
47822
  static template = "o_spreadsheet.Section";
47410
47823
  static props = {
@@ -48350,11 +48763,13 @@ class ColorPickerWidget extends Component {
48350
48763
 
48351
48764
  css /* scss */ `
48352
48765
  .o-font-size-editor {
48766
+ width: max-content !important;
48353
48767
  height: calc(100% - 4px);
48354
48768
  input.o-font-size {
48355
48769
  outline: none;
48356
48770
  height: 20px;
48357
- width: 23px;
48771
+ width: 31px;
48772
+ text-align: center;
48358
48773
  }
48359
48774
  }
48360
48775
  .o-text-options > div {
@@ -50498,8 +50913,7 @@ class ConditionalFormatPreview extends Component {
50498
50913
  get highlights() {
50499
50914
  const sheetId = this.env.model.getters.getActiveSheetId();
50500
50915
  return this.props.conditionalFormat.ranges.map((range) => ({
50501
- sheetId,
50502
- zone: this.env.model.getters.getRangeFromSheetXC(sheetId, range).zone,
50916
+ range: this.env.model.getters.getRangeFromSheetXC(sheetId, range),
50503
50917
  color: HIGHLIGHT_COLOR,
50504
50918
  fillAlpha: 0.06,
50505
50919
  }));
@@ -51457,8 +51871,7 @@ class DataValidationPreview extends Component {
51457
51871
  }
51458
51872
  get highlights() {
51459
51873
  return this.props.rule.ranges.map((range) => ({
51460
- sheetId: this.env.model.getters.getActiveSheetId(),
51461
- zone: range.zone,
51874
+ range,
51462
51875
  color: HIGHLIGHT_COLOR,
51463
51876
  fillAlpha: 0.06,
51464
51877
  }));
@@ -51787,13 +52200,15 @@ class FindAndReplaceStore extends SpreadsheetStore {
51787
52200
  if (this.selectedMatchIndex === null) {
51788
52201
  return;
51789
52202
  }
52203
+ this.preserveSelectedMatchIndex = true;
52204
+ this.shouldFinalizeUpdateSelection = true;
51790
52205
  this.model.dispatch("REPLACE_SEARCH", {
51791
52206
  searchString: this.toSearch,
51792
52207
  replaceWith: this.toReplace,
51793
52208
  matches: [this.searchMatches[this.selectedMatchIndex]],
51794
52209
  searchOptions: this.searchOptions,
51795
52210
  });
51796
- this.selectNextCell(Direction.next, { jumpToMatchSheet: true, updateSelection: true });
52211
+ this.preserveSelectedMatchIndex = false;
51797
52212
  }
51798
52213
  /**
51799
52214
  * Apply the replace function to all the matches one time.
@@ -51865,8 +52280,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51865
52280
  const { width, height } = this.getters.getVisibleRect(zoneWithMerge);
51866
52281
  if (width > 0 && height > 0) {
51867
52282
  highlights.push({
51868
- sheetId,
51869
- zone: zoneWithMerge,
52283
+ range: this.model.getters.getRangeFromZone(sheetId, zoneWithMerge),
51870
52284
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51871
52285
  noBorder: index !== this.selectedMatchIndex,
51872
52286
  thinLine: true,
@@ -51878,8 +52292,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
51878
52292
  const range = this.searchOptions.specificRange;
51879
52293
  if (range && range.sheetId === sheetId) {
51880
52294
  highlights.push({
51881
- sheetId,
51882
- zone: range.zone,
52295
+ range,
51883
52296
  color: FIND_AND_REPLACE_HIGHLIGHT_COLOR,
51884
52297
  noFill: true,
51885
52298
  thinLine: true,
@@ -52253,7 +52666,11 @@ function getPivotHighlights(getters, pivotId) {
52253
52666
  const sheetId = getters.getActiveSheetId();
52254
52667
  const pivotCellPositions = getVisiblePivotCellPositions(getters, pivotId);
52255
52668
  const mergedZones = mergeContiguousZones(pivotCellPositions.map(positionToZone));
52256
- return mergedZones.map((zone) => ({ sheetId, zone, noFill: true, color: HIGHLIGHT_COLOR }));
52669
+ return mergedZones.map((zone) => ({
52670
+ range: getters.getRangeFromZone(sheetId, zone),
52671
+ noFill: true,
52672
+ color: HIGHLIGHT_COLOR,
52673
+ }));
52257
52674
  }
52258
52675
  function getVisiblePivotCellPositions(getters, pivotId) {
52259
52676
  const positions = [];
@@ -52522,7 +52939,7 @@ class TextInput extends Component {
52522
52939
 
52523
52940
  class CogWheelMenu extends Component {
52524
52941
  static template = "o-spreadsheet-CogWheelMenu";
52525
- static components = { Menu };
52942
+ static components = { MenuPopover };
52526
52943
  static props = {
52527
52944
  items: Array,
52528
52945
  };
@@ -53430,28 +53847,45 @@ class SpreadsheetPivotTable {
53430
53847
  getNumberOfDataColumns() {
53431
53848
  return this.columns.at(-1)?.length || 0;
53432
53849
  }
53433
- getPivotCells(includeTotal = true, includeColumnHeaders = true) {
53434
- const key = JSON.stringify({ includeTotal, includeColumnHeaders });
53850
+ getSkippedRows(visibilityOptions) {
53851
+ const skippedRows = new Set();
53852
+ if (!visibilityOptions.displayColumnHeaders) {
53853
+ for (let i = 0; i < this.columns.length - 1; i++) {
53854
+ skippedRows.add(i);
53855
+ }
53856
+ }
53857
+ if (!visibilityOptions.displayMeasuresRow) {
53858
+ skippedRows.add(this.columns.length - 1);
53859
+ }
53860
+ return skippedRows;
53861
+ }
53862
+ getPivotCells(visibilityOptions = {
53863
+ displayColumnHeaders: true,
53864
+ displayTotals: true,
53865
+ displayMeasuresRow: true,
53866
+ }) {
53867
+ const key = JSON.stringify(visibilityOptions);
53435
53868
  if (!this.pivotCells[key]) {
53869
+ const { displayTotals } = visibilityOptions;
53436
53870
  const numberOfDataRows = this.rows.length;
53437
53871
  const numberOfDataColumns = this.getNumberOfDataColumns();
53438
53872
  let pivotHeight = this.columns.length + numberOfDataRows;
53439
53873
  let pivotWidth = 1 /*(row headers)*/ + numberOfDataColumns;
53440
- if (!includeTotal && numberOfDataRows !== 1) {
53874
+ if (!displayTotals && numberOfDataRows !== 1) {
53441
53875
  pivotHeight -= 1;
53442
53876
  }
53443
- if (!includeTotal && numberOfDataColumns !== this.measures.length) {
53877
+ if (!displayTotals && numberOfDataColumns !== this.measures.length) {
53444
53878
  pivotWidth -= this.measures.length;
53445
53879
  }
53446
53880
  const domainArray = [];
53447
- const startRow = includeColumnHeaders ? 0 : this.columns.length;
53881
+ const skippedRows = this.getSkippedRows(visibilityOptions);
53448
53882
  for (let col = 0; col < pivotWidth; col++) {
53449
53883
  domainArray.push([]);
53450
- for (let row = startRow; row < pivotHeight; row++) {
53451
- if (!includeTotal && row === pivotHeight) {
53884
+ for (let row = 0; row < pivotHeight; row++) {
53885
+ if (skippedRows.has(row)) {
53452
53886
  continue;
53453
53887
  }
53454
- domainArray[col].push(this.getPivotCell(col, row, includeTotal));
53888
+ domainArray[col].push(this.getPivotCell(col, row, displayTotals));
53455
53889
  }
53456
53890
  }
53457
53891
  this.pivotCells[key] = domainArray;
@@ -55092,6 +55526,7 @@ function createTableStyleContextMenuActions(env, styleId) {
55092
55526
  id: "editTableStyle",
55093
55527
  name: _t("Edit table style"),
55094
55528
  execute: (env) => env.openSidePanel("TableStyleEditorPanel", { styleId }),
55529
+ isEnabled: (env) => !env.isSmall,
55095
55530
  icon: "o-spreadsheet-Icon.EDIT",
55096
55531
  },
55097
55532
  {
@@ -55213,7 +55648,7 @@ css /* scss */ `
55213
55648
  `;
55214
55649
  class TableStylePreview extends Component {
55215
55650
  static template = "o-spreadsheet-TableStylePreview";
55216
- static components = { Menu };
55651
+ static components = { MenuPopover };
55217
55652
  static props = {
55218
55653
  tableConfig: Object,
55219
55654
  tableStyle: Object,
@@ -55784,6 +56219,17 @@ sidePanelRegistry.add("PivotMeasureDisplayPanel", {
55784
56219
  },
55785
56220
  });
55786
56221
 
56222
+ class ScreenWidthStore {
56223
+ mutators = ["setSmallThreshhold"];
56224
+ _isSmallCallback = () => false;
56225
+ get isSmall() {
56226
+ return this._isSmallCallback();
56227
+ }
56228
+ setSmallThreshhold(isSmall) {
56229
+ this._isSmallCallback = isSmall;
56230
+ }
56231
+ }
56232
+
55787
56233
  const DEFAULT_SIDE_PANEL_SIZE = 350;
55788
56234
  const MIN_SHEET_VIEW_WIDTH = 150;
55789
56235
  class SidePanelStore extends SpreadsheetStore {
@@ -55791,6 +56237,7 @@ class SidePanelStore extends SpreadsheetStore {
55791
56237
  initialPanelProps = {};
55792
56238
  componentTag = "";
55793
56239
  panelSize = DEFAULT_SIDE_PANEL_SIZE;
56240
+ screenWidthStore = this.get(ScreenWidthStore);
55794
56241
  get isOpen() {
55795
56242
  if (!this.componentTag) {
55796
56243
  return false;
@@ -55812,6 +56259,9 @@ class SidePanelStore extends SpreadsheetStore {
55812
56259
  return undefined;
55813
56260
  }
55814
56261
  open(componentTag, panelProps = {}) {
56262
+ if (this.screenWidthStore.isSmall) {
56263
+ return;
56264
+ }
55815
56265
  const state = this.computeState(componentTag, panelProps);
55816
56266
  if (!state.isOpen) {
55817
56267
  return;
@@ -55926,8 +56376,7 @@ class TableResizer extends Component {
55926
56376
  return [];
55927
56377
  return [
55928
56378
  {
55929
- zone: this.state.highlightZone,
55930
- sheetId: this.props.table.range.sheetId,
56379
+ range: this.env.model.getters.getRangeFromZone(this.props.table.range.sheetId, this.state.highlightZone),
55931
56380
  color: COLOR,
55932
56381
  noFill: true,
55933
56382
  },
@@ -55949,13 +56398,14 @@ class Grid extends Component {
55949
56398
  static template = "o-spreadsheet-Grid";
55950
56399
  static props = {
55951
56400
  exposeFocus: Function,
56401
+ getGridSize: Function,
55952
56402
  };
55953
56403
  static components = {
55954
56404
  GridComposer,
55955
56405
  GridOverlay,
55956
56406
  GridPopover,
55957
56407
  HeadersOverlay,
55958
- Menu,
56408
+ MenuPopover,
55959
56409
  Autofill,
55960
56410
  ClientTag,
55961
56411
  Highlight,
@@ -55963,6 +56413,7 @@ class Grid extends Component {
55963
56413
  VerticalScrollBar,
55964
56414
  HorizontalScrollBar,
55965
56415
  TableResizer,
56416
+ Selection,
55966
56417
  };
55967
56418
  HEADER_HEIGHT = HEADER_HEIGHT;
55968
56419
  HEADER_WIDTH = HEADER_WIDTH;
@@ -56245,8 +56696,8 @@ class Grid extends Component {
56245
56696
  }
56246
56697
  onGridResized({ height, width }) {
56247
56698
  this.env.model.dispatch("RESIZE_SHEETVIEW", {
56248
- width: width,
56249
- height: height,
56699
+ width: width - HEADER_WIDTH,
56700
+ height: height - HEADER_HEIGHT,
56250
56701
  gridOffsetX: HEADER_WIDTH,
56251
56702
  gridOffsetY: HEADER_HEIGHT,
56252
56703
  });
@@ -56300,6 +56751,9 @@ class Grid extends Component {
56300
56751
  else {
56301
56752
  this.env.model.selection.selectCell(col, row);
56302
56753
  }
56754
+ if (this.env.isMobile()) {
56755
+ return;
56756
+ }
56303
56757
  let prevCol = col;
56304
56758
  let prevRow = row;
56305
56759
  const onMouseMove = (col, row, ev) => {
@@ -56585,6 +57039,9 @@ class Grid extends Component {
56585
57039
  const sheetId = this.env.model.getters.getActiveSheetId();
56586
57040
  return this.env.model.getters.getCoreTables(sheetId).filter(isStaticTable);
56587
57041
  }
57042
+ get displaySelectionHandler() {
57043
+ return this.env.isMobile() && this.composerFocusStore.activeComposer.editionMode === "inactive";
57044
+ }
56588
57045
  }
56589
57046
 
56590
57047
  /**
@@ -61619,7 +62076,9 @@ class TablePlugin extends CorePlugin {
61619
62076
  const ranges = cmd.ranges.map((rangeData) => this.getters.getRangeFromRangeData(rangeData));
61620
62077
  const union = this.getters.getRangesUnion(ranges);
61621
62078
  const mergesInTarget = this.getters.getMergesInZone(cmd.sheetId, union.zone);
61622
- this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
62079
+ if (mergesInTarget.length) {
62080
+ this.dispatch("REMOVE_MERGE", { sheetId: cmd.sheetId, target: mergesInTarget });
62081
+ }
61623
62082
  const id = this.consumeNextId();
61624
62083
  const config = cmd.config || DEFAULT_TABLE_CONFIG;
61625
62084
  const newTable = cmd.tableType === "dynamic"
@@ -61718,14 +62177,16 @@ class TablePlugin extends CorePlugin {
61718
62177
  const zoneToCheckIfEmpty = direction === "down"
61719
62178
  ? { ...zone, bottom: zone.bottom + 1, top: zone.bottom + 1 }
61720
62179
  : { ...zone, right: zone.right + 1, left: zone.right + 1 };
61721
- for (const position of positions(zoneToCheckIfEmpty)) {
61722
- const cellPosition = { sheetId, ...position };
61723
- // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
61724
- const cellContent = this.getters.getCell(cellPosition)?.content;
61725
- if (cellContent ||
61726
- this.getters.isInMerge(cellPosition) ||
61727
- this.getTablesOverlappingZones(sheetId, [positionToZone(position)]).length) {
61728
- return "none";
62180
+ for (let row = zoneToCheckIfEmpty.top; row <= zoneToCheckIfEmpty.bottom; row++) {
62181
+ for (let col = zoneToCheckIfEmpty.left; col <= zoneToCheckIfEmpty.right; col++) {
62182
+ const cellPosition = { sheetId, col, row };
62183
+ // Since this plugin is loaded before CellPlugin, the getters still give us the old cell content
62184
+ const cellContent = this.getters.getCell(cellPosition)?.content;
62185
+ if (cellContent ||
62186
+ this.getters.isInMerge(cellPosition) ||
62187
+ this.getTablesOverlappingZones(sheetId, [positionToZone(cellPosition)]).length) {
62188
+ return "none";
62189
+ }
61729
62190
  }
61730
62191
  }
61731
62192
  return direction;
@@ -62511,7 +62972,7 @@ class PivotCorePlugin extends CorePlugin {
62511
62972
  break;
62512
62973
  }
62513
62974
  case "UPDATE_PIVOT": {
62514
- this.history.update("pivots", cmd.pivotId, "definition", deepCopy(cmd.pivot));
62975
+ this.history.update("pivots", cmd.pivotId, "definition", this.repairSortedColumn(deepCopy(cmd.pivot)));
62515
62976
  this.compileCalculatedMeasures(cmd.pivot.measures);
62516
62977
  break;
62517
62978
  }
@@ -62582,7 +63043,10 @@ class PivotCorePlugin extends CorePlugin {
62582
63043
  // Private
62583
63044
  // -------------------------------------------------------------------------
62584
63045
  addPivot(pivotId, pivot, formulaId = this.nextFormulaId.toString()) {
62585
- this.history.update("pivots", pivotId, { definition: deepCopy(pivot), formulaId });
63046
+ this.history.update("pivots", pivotId, {
63047
+ definition: this.repairSortedColumn(deepCopy(pivot)),
63048
+ formulaId,
63049
+ });
62586
63050
  this.compileCalculatedMeasures(pivot.measures);
62587
63051
  this.history.update("formulaIds", formulaId, pivotId);
62588
63052
  this.history.update("nextFormulaId", this.nextFormulaId + 1);
@@ -62671,6 +63135,7 @@ class PivotCorePlugin extends CorePlugin {
62671
63135
  }
62672
63136
  }
62673
63137
  checkSortedColumnInMeasures(definition) {
63138
+ definition = this.repairSortedColumn(definition);
62674
63139
  const measures = definition.measures.map((measure) => measure.id);
62675
63140
  if (definition.sortedColumn && !measures.includes(definition.sortedColumn.measure)) {
62676
63141
  return "InvalidDefinition" /* CommandResult.InvalidDefinition */;
@@ -62684,6 +63149,26 @@ class PivotCorePlugin extends CorePlugin {
62684
63149
  }
62685
63150
  return "Success" /* CommandResult.Success */;
62686
63151
  }
63152
+ repairSortedColumn(definition) {
63153
+ if (definition.sortedColumn) {
63154
+ // Fix for an upgrade issue: the sortedColumn measure was not updated
63155
+ // from using fieldName to using id. If the sortedColumn measure matches
63156
+ // a measure fieldName in the definition, update it to use the measure's id instead
63157
+ // of its fieldName.
63158
+ // TODO: add an upgrade step to fix this in master and remove this code
63159
+ const sortedMeasure = definition.measures.find((measure) => measure.fieldName === definition.sortedColumn?.measure);
63160
+ if (sortedMeasure) {
63161
+ return {
63162
+ ...definition,
63163
+ sortedColumn: {
63164
+ ...definition.sortedColumn,
63165
+ measure: sortedMeasure.id,
63166
+ },
63167
+ };
63168
+ }
63169
+ }
63170
+ return definition;
63171
+ }
62687
63172
  // ---------------------------------------------------------------------
62688
63173
  // Import/Export
62689
63174
  // ---------------------------------------------------------------------
@@ -65551,186 +66036,6 @@ class EvaluationDataValidationPlugin extends CoreViewPlugin {
65551
66036
  }
65552
66037
  }
65553
66038
 
65554
- const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
65555
- css /* scss */ `
65556
- .o-dv-checkbox {
65557
- margin: ${MARGIN}px;
65558
- /* required to prevent the checkbox position to be sensible to the font-size (affects Firefox) */
65559
- position: absolute;
65560
- }
65561
- `;
65562
- class DataValidationCheckbox extends Component {
65563
- static template = "o-spreadsheet-DataValidationCheckbox";
65564
- static components = {
65565
- Checkbox,
65566
- };
65567
- static props = {
65568
- cellPosition: Object,
65569
- };
65570
- onCheckboxChange(value) {
65571
- const { sheetId, col, row } = this.props.cellPosition;
65572
- const cellContent = value ? "TRUE" : "FALSE";
65573
- this.env.model.dispatch("UPDATE_CELL", { sheetId, col, row, content: cellContent });
65574
- }
65575
- get checkBoxValue() {
65576
- return !!this.env.model.getters.getEvaluatedCell(this.props.cellPosition).value;
65577
- }
65578
- get isDisabled() {
65579
- const cell = this.env.model.getters.getCell(this.props.cellPosition);
65580
- return this.env.model.getters.isReadonly() || !!cell?.isFormula;
65581
- }
65582
- }
65583
-
65584
- const ICON_WIDTH = 13;
65585
- css /* scss */ `
65586
- .o-dv-list-icon {
65587
- color: ${TEXT_BODY_MUTED};
65588
- border-radius: 1px;
65589
- height: ${GRID_ICON_EDGE_LENGTH}px;
65590
- width: ${GRID_ICON_EDGE_LENGTH}px;
65591
-
65592
- &:hover {
65593
- color: #ffffff;
65594
- background-color: ${TEXT_BODY_MUTED};
65595
- }
65596
-
65597
- svg {
65598
- width: ${ICON_WIDTH}px;
65599
- height: ${ICON_WIDTH}px;
65600
- }
65601
- }
65602
- `;
65603
- class DataValidationListIcon extends Component {
65604
- static template = "o-spreadsheet-DataValidationListIcon";
65605
- static props = {
65606
- cellPosition: Object,
65607
- };
65608
- onClick() {
65609
- const { col, row } = this.props.cellPosition;
65610
- this.env.model.selection.selectCell(col, row);
65611
- this.env.startCellEdition();
65612
- }
65613
- }
65614
-
65615
- css /* scss */ `
65616
- .o-filter-icon {
65617
- color: ${FILTERS_COLOR};
65618
- display: flex;
65619
- align-items: center;
65620
- justify-content: center;
65621
- width: ${GRID_ICON_EDGE_LENGTH}px;
65622
- height: ${GRID_ICON_EDGE_LENGTH}px;
65623
-
65624
- &:hover {
65625
- background: ${FILTERS_COLOR};
65626
- color: #fff;
65627
- }
65628
-
65629
- &.o-high-contrast {
65630
- color: #defade;
65631
- }
65632
- &.o-high-contrast:hover {
65633
- color: ${FILTERS_COLOR};
65634
- background: #fff;
65635
- }
65636
- }
65637
- .o-filter-icon:hover {
65638
- background: ${FILTERS_COLOR};
65639
- color: #fff;
65640
- }
65641
- `;
65642
- class FilterIcon extends Component {
65643
- static template = "o-spreadsheet-FilterIcon";
65644
- static props = {
65645
- cellPosition: Object,
65646
- };
65647
- cellPopovers;
65648
- setup() {
65649
- this.cellPopovers = useStore(CellPopoverStore);
65650
- }
65651
- onClick() {
65652
- const position = this.props.cellPosition;
65653
- const activePopover = this.cellPopovers.persistentCellPopover;
65654
- const { col, row } = position;
65655
- if (activePopover.isOpen &&
65656
- activePopover.col === col &&
65657
- activePopover.row === row &&
65658
- activePopover.type === "FilterMenu") {
65659
- this.cellPopovers.close();
65660
- return;
65661
- }
65662
- this.cellPopovers.open({ col, row }, "FilterMenu");
65663
- }
65664
- get isFilterActive() {
65665
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
65666
- }
65667
- get iconClass() {
65668
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
65669
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
65670
- return luminance < 0.45 ? "o-high-contrast" : "";
65671
- }
65672
- }
65673
-
65674
- css /* scss */ `
65675
- .o-spreadsheet {
65676
- .o-pivot-collapse-icon {
65677
- cursor: pointer;
65678
- width: 11px;
65679
- height: 11px;
65680
- border: 1px solid #777;
65681
- background-color: #eee;
65682
- margin: 3px 0 3px 6px;
65683
-
65684
- .o-icon {
65685
- width: 5px;
65686
- height: 5px;
65687
- }
65688
- }
65689
- }
65690
- `;
65691
- class PivotCollapseIcon extends Component {
65692
- static template = "o-spreadsheet-PivotCollapseIcon";
65693
- static props = {
65694
- cellPosition: Object,
65695
- };
65696
- onClick() {
65697
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65698
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65699
- if (!pivotId || pivotCell.type !== "HEADER") {
65700
- return;
65701
- }
65702
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65703
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
65704
- ? [...definition.collapsedDomains[pivotCell.dimension]]
65705
- : [];
65706
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
65707
- if (index !== -1) {
65708
- collapsedDomains.splice(index, 1);
65709
- }
65710
- else {
65711
- collapsedDomains.push(pivotCell.domain);
65712
- }
65713
- const newDomains = definition.collapsedDomains
65714
- ? { ...definition.collapsedDomains }
65715
- : { COL: [], ROW: [] };
65716
- newDomains[pivotCell.dimension] = collapsedDomains;
65717
- this.env.model.dispatch("UPDATE_PIVOT", {
65718
- pivotId,
65719
- pivot: { ...definition, collapsedDomains: newDomains },
65720
- });
65721
- }
65722
- get isCollapsed() {
65723
- const pivotCell = this.env.model.getters.getPivotCellFromPosition(this.props.cellPosition);
65724
- const pivotId = this.env.model.getters.getPivotIdFromPosition(this.props.cellPosition);
65725
- if (!pivotId || pivotCell.type !== "HEADER") {
65726
- return false;
65727
- }
65728
- const definition = this.env.model.getters.getPivotCoreDefinition(pivotId);
65729
- const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
65730
- return domains?.some((domain) => deepEquals(domain, pivotCell.domain));
65731
- }
65732
- }
65733
-
65734
66039
  /**
65735
66040
  * Registry to draw icons on cells
65736
66041
  */
@@ -65738,14 +66043,25 @@ const iconsOnCellRegistry = new Registry();
65738
66043
  iconsOnCellRegistry.add("data_validation_checkbox", (getters, position) => {
65739
66044
  const hasIcon = getters.isCellValidCheckbox(position);
65740
66045
  if (hasIcon) {
66046
+ const value = !!getters.getEvaluatedCell(position).value;
65741
66047
  return {
65742
- svg: undefined,
66048
+ svg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED,
66049
+ hoverSvg: value ? CHECKBOX_CHECKED : CHECKBOX_UNCHECKED_HOVERED,
65743
66050
  priority: 2,
65744
66051
  horizontalAlign: "center",
65745
66052
  size: GRID_ICON_EDGE_LENGTH,
65746
66053
  margin: GRID_ICON_MARGIN,
65747
- component: DataValidationCheckbox,
65748
66054
  position,
66055
+ type: "data_validation_checkbox",
66056
+ onClick: (position, env) => {
66057
+ const cell = env.model.getters.getCell(position);
66058
+ const isDisabled = env.model.getters.isReadonly() || !!cell?.isFormula;
66059
+ if (isDisabled) {
66060
+ return;
66061
+ }
66062
+ const cellContent = value ? "FALSE" : "TRUE";
66063
+ env.model.dispatch("UPDATE_CELL", { ...position, content: cellContent });
66064
+ },
65749
66065
  };
65750
66066
  }
65751
66067
  return undefined;
@@ -65754,13 +66070,19 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65754
66070
  const hasIcon = !getters.isReadonly() && getters.cellHasListDataValidationIcon(position);
65755
66071
  if (hasIcon) {
65756
66072
  return {
65757
- svg: undefined,
66073
+ svg: CARET_DOWN,
66074
+ hoverSvg: HOVERED_CARET_DOWN,
65758
66075
  priority: 2,
65759
66076
  horizontalAlign: "right",
65760
66077
  size: GRID_ICON_EDGE_LENGTH,
65761
66078
  margin: GRID_ICON_MARGIN,
65762
- component: DataValidationListIcon,
65763
66079
  position,
66080
+ onClick: (position, env) => {
66081
+ const { col, row } = position;
66082
+ env.model.selection.selectCell(col, row);
66083
+ env.startCellEdition();
66084
+ },
66085
+ type: "data_validation_list_icon",
65764
66086
  };
65765
66087
  }
65766
66088
  return undefined;
@@ -65768,14 +66090,30 @@ iconsOnCellRegistry.add("data_validation_list_icon", (getters, position) => {
65768
66090
  iconsOnCellRegistry.add("filter_icon", (getters, position) => {
65769
66091
  const hasIcon = getters.isFilterHeader(position);
65770
66092
  if (hasIcon) {
66093
+ const isFilterActive = getters.isFilterActive(position);
66094
+ const cellStyle = getters.getCellComputedStyle(position);
66095
+ const isHighContrast = relativeLuminance(cellStyle.fillColor || "#fff") < 0.45;
65771
66096
  return {
65772
- svg: undefined,
66097
+ type: "filter_icon",
66098
+ svg: getDataFilterIcon(isFilterActive, isHighContrast, false),
66099
+ hoverSvg: getDataFilterIcon(isFilterActive, isHighContrast, true),
65773
66100
  priority: 3,
65774
66101
  horizontalAlign: "right",
65775
66102
  size: GRID_ICON_EDGE_LENGTH,
65776
66103
  margin: GRID_ICON_MARGIN,
65777
- component: FilterIcon,
65778
66104
  position,
66105
+ onClick: (position, env) => {
66106
+ const cellPopovers = env.getStore(CellPopoverStore);
66107
+ const activePopover = cellPopovers.persistentCellPopover;
66108
+ if (activePopover.isOpen &&
66109
+ activePopover.col === position.col &&
66110
+ activePopover.row === position.row &&
66111
+ activePopover.type === "FilterMenu") {
66112
+ cellPopovers.close();
66113
+ return;
66114
+ }
66115
+ cellPopovers.open(position, "FilterMenu");
66116
+ },
65779
66117
  };
65780
66118
  }
65781
66119
  return undefined;
@@ -65785,6 +66123,7 @@ iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
65785
66123
  if (icon) {
65786
66124
  const style = getters.getCellStyle(position);
65787
66125
  return {
66126
+ type: "conditional_formatting",
65788
66127
  svg: ICONS[icon].svg,
65789
66128
  priority: 1,
65790
66129
  horizontalAlign: "left",
@@ -65805,23 +66144,55 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
65805
66144
  const definition = getters.getPivotCoreDefinition(pivotId);
65806
66145
  const isDashboard = getters.isDashboard();
65807
66146
  const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
65808
- const component = !isDashboard && pivotCell.domain.length !== fields.length ? PivotCollapseIcon : undefined;
66147
+ const hasIcon = !isDashboard && pivotCell.domain.length !== fields.length;
66148
+ const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
66149
+ const isCollapsed = domains.some((domain) => deepEquals(domain, pivotCell.domain));
66150
+ const indent = pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0;
65809
66151
  return {
66152
+ type: "pivot_collapse",
65810
66153
  priority: 4,
65811
66154
  horizontalAlign: "left",
65812
- size: !!component || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
65813
- ? GRID_ICON_EDGE_LENGTH
66155
+ size: hasIcon || (!isDashboard && pivotCell.dimension === "ROW" && definition.rows.length > 1)
66156
+ ? PIVOT_COLLAPSE_ICON_SIZE
65814
66157
  : 0,
65815
- margin: pivotCell.dimension === "ROW" ? (pivotCell.domain.length - 1) * PIVOT_INDENT : 0,
65816
- component,
66158
+ margin: hasIcon ? GRID_ICON_MARGIN * 2 + indent : indent,
66159
+ svg: hasIcon ? getPivotIconSvg(isCollapsed, false) : undefined,
66160
+ hoverSvg: hasIcon ? getPivotIconSvg(isCollapsed, true) : undefined,
65817
66161
  position,
66162
+ onClick: togglePivotCollapse,
65818
66163
  };
65819
66164
  }
65820
66165
  return undefined;
65821
66166
  });
66167
+ function togglePivotCollapse(position, env) {
66168
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
66169
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
66170
+ if (!pivotId || pivotCell.type !== "HEADER") {
66171
+ return;
66172
+ }
66173
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
66174
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
66175
+ ? [...definition.collapsedDomains[pivotCell.dimension]]
66176
+ : [];
66177
+ const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
66178
+ if (index !== -1) {
66179
+ collapsedDomains.splice(index, 1);
66180
+ }
66181
+ else {
66182
+ collapsedDomains.push(pivotCell.domain);
66183
+ }
66184
+ const newDomains = definition.collapsedDomains
66185
+ ? { ...definition.collapsedDomains }
66186
+ : { COL: [], ROW: [] };
66187
+ newDomains[pivotCell.dimension] = collapsedDomains;
66188
+ env.model.dispatch("UPDATE_PIVOT", {
66189
+ pivotId,
66190
+ pivot: { ...definition, collapsedDomains: newDomains },
66191
+ });
66192
+ }
65822
66193
 
65823
66194
  class CellIconPlugin extends CoreViewPlugin {
65824
- static getters = ["doesCellHaveGridIcon", "getCellIcons"];
66195
+ static getters = ["doesCellHaveGridIcon", "getCellIcons", "getCellIconRect"];
65825
66196
  cellIconsCache = {};
65826
66197
  handle(cmd) {
65827
66198
  if (cmd.type !== "SET_VIEWPORT_OFFSET") {
@@ -65841,6 +66212,29 @@ class CellIconPlugin extends CoreViewPlugin {
65841
66212
  }
65842
66213
  return this.cellIconsCache[position.sheetId][position.col][position.row];
65843
66214
  }
66215
+ getCellIconRect(icon) {
66216
+ const cellPosition = icon.position;
66217
+ const merge = this.getters.getMerge(cellPosition);
66218
+ const zone = merge || positionToZone(cellPosition);
66219
+ const cellRect = this.getters.getRect(zone);
66220
+ const cell = this.getters.getCell(cellPosition);
66221
+ const x = this.getIconHorizontalPosition(cellRect, icon.horizontalAlign, icon);
66222
+ const y = this.getters.computeTextYCoordinate(cellRect, icon.size, cell?.style?.verticalAlign);
66223
+ return { x: x, y: y, width: icon.size, height: icon.size };
66224
+ }
66225
+ getIconHorizontalPosition(rect, align, icon) {
66226
+ const start = rect.x;
66227
+ const end = rect.x + rect.width;
66228
+ switch (align) {
66229
+ case "right":
66230
+ return end - icon.margin - icon.size;
66231
+ case "left":
66232
+ return start + icon.margin;
66233
+ default:
66234
+ const centeringOffset = Math.floor((end - start - icon.size) / 2);
66235
+ return end - icon.size - centeringOffset;
66236
+ }
66237
+ }
65844
66238
  computeCellIcons(position) {
65845
66239
  const icons = { left: undefined, right: undefined, center: undefined };
65846
66240
  const callbacks = iconsOnCellRegistry.getAll();
@@ -66945,10 +67339,15 @@ class PivotUIPlugin extends CoreViewPlugin {
66945
67339
  const includeTotal = toScalar(args[2]);
66946
67340
  const shouldIncludeTotal = includeTotal === undefined ? true : toBoolean(includeTotal);
66947
67341
  const includeColumnHeaders = toScalar(args[3]);
67342
+ const includeMeasures = toScalar(args[5]);
67343
+ const shouldIncludeMeasures = includeMeasures === undefined ? true : toBoolean(includeMeasures);
66948
67344
  const shouldIncludeColumnHeaders = includeColumnHeaders === undefined ? true : toBoolean(includeColumnHeaders);
66949
- const pivotCells = pivot
66950
- .getCollapsedTableStructure()
66951
- .getPivotCells(shouldIncludeTotal, shouldIncludeColumnHeaders);
67345
+ const visibilityOptions = {
67346
+ displayColumnHeaders: shouldIncludeColumnHeaders,
67347
+ displayTotals: shouldIncludeTotal,
67348
+ displayMeasuresRow: shouldIncludeMeasures,
67349
+ };
67350
+ const pivotCells = pivot.getCollapsedTableStructure().getPivotCells(visibilityOptions);
66952
67351
  const pivotCol = position.col - mainPosition.col;
66953
67352
  const pivotRow = position.row - mainPosition.row;
66954
67353
  return pivotCells[pivotCol][pivotRow];
@@ -69170,8 +69569,7 @@ class CollaborativePlugin extends UIPlugin {
69170
69569
  "isFullySynchronized",
69171
69570
  ];
69172
69571
  static layers = ["Selection"];
69173
- availableColors = new AlternatingColorGenerator(12);
69174
- colors = {};
69572
+ colors = new AlternatingColorMap(12);
69175
69573
  session;
69176
69574
  constructor(config) {
69177
69575
  super(config);
@@ -69189,7 +69587,7 @@ class CollaborativePlugin extends UIPlugin {
69189
69587
  }
69190
69588
  getConnectedClients() {
69191
69589
  return [...this.session.getConnectedClients()].map((client) => {
69192
- return { ...client, color: this.colors[client.id] };
69590
+ return { ...client, color: this.colors.get(client.id) };
69193
69591
  });
69194
69592
  }
69195
69593
  isFullySynchronized() {
@@ -69218,10 +69616,7 @@ class CollaborativePlugin extends UIPlugin {
69218
69616
  client.position &&
69219
69617
  client.position.sheetId === sheetId &&
69220
69618
  this.isPositionValid(client.position)) {
69221
- if (!this.colors[client.id]) {
69222
- this.colors[client.id] = this.availableColors.next();
69223
- }
69224
- clients.push({ ...client, color: this.colors[client.id], position: client.position });
69619
+ clients.push({ ...client, position: client.position });
69225
69620
  }
69226
69621
  }
69227
69622
  return clients;
@@ -70067,6 +70462,7 @@ class SheetUIPlugin extends UIPlugin {
70067
70462
  "getCellText",
70068
70463
  "getCellMultiLineText",
70069
70464
  "getContiguousZone",
70465
+ "computeTextYCoordinate",
70070
70466
  ];
70071
70467
  ctx = document.createElement("canvas").getContext("2d");
70072
70468
  // ---------------------------------------------------------------------------
@@ -70169,6 +70565,25 @@ class SheetUIPlugin extends UIPlugin {
70169
70565
  });
70170
70566
  return splitTextToWidth(this.ctx, text, style, args.wrapText ? args.maxWidth : undefined);
70171
70567
  }
70568
+ /** Computes the vertical start point from which a text line should be draw in a cell.
70569
+ *
70570
+ * Note that in case the cell does not have enough spaces to display its text lines,
70571
+ * (wrapping cell case) then the vertical align should be at the top.
70572
+ * */
70573
+ computeTextYCoordinate(cellRect, textLineHeight, verticalAlign = DEFAULT_VERTICAL_ALIGN, numberOfLines = 1) {
70574
+ const y = cellRect.y + 1; // +1 to skip the cell grid line at the top
70575
+ const textHeight = computeTextLinesHeight(textLineHeight, numberOfLines);
70576
+ const hasEnoughSpaces = cellRect.height > textHeight + MIN_CELL_TEXT_MARGIN * 2;
70577
+ if (hasEnoughSpaces) {
70578
+ if (verticalAlign === "middle") {
70579
+ return Math.ceil(y + (cellRect.height - textHeight) / 2);
70580
+ }
70581
+ if (verticalAlign === "bottom") {
70582
+ return y + cellRect.height - textHeight - MIN_CELL_TEXT_MARGIN;
70583
+ }
70584
+ }
70585
+ return y + MIN_CELL_TEXT_MARGIN;
70586
+ }
70172
70587
  /**
70173
70588
  * Expands the given zone until bordered by empty cells or reached the sheet boundaries.
70174
70589
  */
@@ -72075,9 +72490,10 @@ class FilterEvaluationPlugin extends UIPlugin {
72075
72490
  const filteredValues = filterValue.hiddenValues?.map(toLowerCase);
72076
72491
  if (!filteredValues)
72077
72492
  continue;
72493
+ const filteredValuesSet = new Set(filteredValues);
72078
72494
  for (let row = filteredZone.top; row <= filteredZone.bottom; row++) {
72079
72495
  const value = this.getCellValueAsString(sheetId, filter.col, row);
72080
- if (filteredValues.includes(value)) {
72496
+ if (filteredValuesSet.has(value)) {
72081
72497
  hiddenRows.add(row);
72082
72498
  }
72083
72499
  }
@@ -72194,6 +72610,7 @@ class GridSelectionPlugin extends UIPlugin {
72194
72610
  "getElementsFromSelection",
72195
72611
  "tryGetActiveSheetId",
72196
72612
  "isGridSelectionActive",
72613
+ "getSelectecUnboundedZone",
72197
72614
  ];
72198
72615
  gridSelection = {
72199
72616
  anchor: {
@@ -72205,12 +72622,14 @@ class GridSelectionPlugin extends UIPlugin {
72205
72622
  selectedFigureId = null;
72206
72623
  sheetsData = {};
72207
72624
  moveClient;
72625
+ isUnbounded;
72208
72626
  // This flag is used to avoid to historize the ACTIVE_SHEET command when it's
72209
72627
  // the main command.
72210
72628
  activeSheet = null;
72211
72629
  constructor(config) {
72212
72630
  super(config);
72213
72631
  this.moveClient = config.moveClient;
72632
+ this.isUnbounded = false;
72214
72633
  }
72215
72634
  // ---------------------------------------------------------------------------
72216
72635
  // Command Handling
@@ -72236,6 +72655,7 @@ class GridSelectionPlugin extends UIPlugin {
72236
72655
  handleEvent(event) {
72237
72656
  const anchor = event.anchor;
72238
72657
  let zones = [];
72658
+ this.isUnbounded = event.options?.unbounded || false;
72239
72659
  switch (event.mode) {
72240
72660
  case "overrideSelection":
72241
72661
  zones = [anchor.zone];
@@ -72425,6 +72845,12 @@ class GridSelectionPlugin extends UIPlugin {
72425
72845
  getSelectedZone() {
72426
72846
  return deepCopy(this.gridSelection.anchor.zone);
72427
72847
  }
72848
+ getSelectecUnboundedZone() {
72849
+ const zone = this.isUnbounded
72850
+ ? this.getters.getUnboundedZone(this.activeSheet.id, this.gridSelection.anchor.zone)
72851
+ : this.gridSelection.anchor.zone;
72852
+ return deepCopy(zone);
72853
+ }
72428
72854
  getSelection() {
72429
72855
  return deepCopy(this.gridSelection);
72430
72856
  }
@@ -72618,11 +73044,6 @@ class GridSelectionPlugin extends UIPlugin {
72618
73044
  },
72619
73045
  ];
72620
73046
  const sheetId = this.getActiveSheetId();
72621
- const handler = new CellClipboardHandler(this.getters, this.dispatch);
72622
- const data = handler.copy(getClipboardDataPositions(sheetId, target));
72623
- if (!data) {
72624
- return;
72625
- }
72626
73047
  const base = isBasedBefore ? cmd.base : cmd.base + 1;
72627
73048
  const pasteTarget = [
72628
73049
  {
@@ -72632,7 +73053,14 @@ class GridSelectionPlugin extends UIPlugin {
72632
73053
  bottom: !isCol ? base + thickness - 1 : this.getters.getNumberRows(cmd.sheetId) - 1,
72633
73054
  },
72634
73055
  ];
72635
- handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
73056
+ for (const Handler of clipboardHandlersRegistries.cellHandlers.getAll()) {
73057
+ const handler = new Handler(this.getters, this.dispatch);
73058
+ const data = handler.copy(getClipboardDataPositions(sheetId, target));
73059
+ if (!data) {
73060
+ continue;
73061
+ }
73062
+ handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
73063
+ }
72636
73064
  const selection = pasteTarget[0];
72637
73065
  const col = selection.left;
72638
73066
  const row = selection.top;
@@ -73178,6 +73606,7 @@ class SheetViewPlugin extends UIPlugin {
73178
73606
  "getRect",
73179
73607
  "getFigureUI",
73180
73608
  "getPositionAnchorOffset",
73609
+ "getGridOffset",
73181
73610
  ];
73182
73611
  viewports = {};
73183
73612
  /**
@@ -73367,6 +73796,9 @@ class SheetViewPlugin extends UIPlugin {
73367
73796
  height: this.sheetViewHeight,
73368
73797
  };
73369
73798
  }
73799
+ getGridOffset() {
73800
+ return { x: this.gridOffsetX, y: this.gridOffsetY };
73801
+ }
73370
73802
  /** type as pane, not viewport but basically pane extends viewport */
73371
73803
  getActiveMainViewport() {
73372
73804
  const sheetId = this.getters.getActiveSheetId();
@@ -74743,8 +75175,9 @@ topbarMenuRegistry
74743
75175
  })
74744
75176
  .addChild("settings", ["file"], {
74745
75177
  name: _t("Settings"),
74746
- sequence: 100,
75178
+ sequence: 200,
74747
75179
  execute: (env) => env.openSidePanel("Settings"),
75180
+ isEnabled: (env) => !env.isSmall,
74748
75181
  icon: "o-spreadsheet-Icon.COG",
74749
75182
  })
74750
75183
  // ---------------------------------------------------------------------
@@ -75162,6 +75595,7 @@ topbarMenuRegistry
75162
75595
  execute: (env) => {
75163
75596
  env.openSidePanel("DataValidation");
75164
75597
  },
75598
+ isEnabled: (env) => !env.isSmall,
75165
75599
  icon: "o-spreadsheet-Icon.DATA_VALIDATION",
75166
75600
  sequence: 30,
75167
75601
  separator: true,
@@ -75185,6 +75619,7 @@ topbarMenuRegistry
75185
75619
  sequence: sequence + index,
75186
75620
  isReadonlyAllowed: true,
75187
75621
  execute: (env) => env.openSidePanel("PivotSidePanel", { pivotId }),
75622
+ isEnabled: (env) => !env.isSmall,
75188
75623
  onStartHover: (env) => env.getStore(HighlightStore).register(highlightProvider),
75189
75624
  onStopHover: (env) => env.getStore(HighlightStore).unRegister(highlightProvider),
75190
75625
  icon: "o-spreadsheet-Icon.PIVOT",
@@ -75456,7 +75891,7 @@ css /* scss */ `
75456
75891
  .o-sheet {
75457
75892
  padding: 0 15px;
75458
75893
  padding-right: 10px;
75459
- height: ${BOTTOMBAR_HEIGHT}px;
75894
+ height: ${DESKTOP_BOTTOMBAR_HEIGHT}px;
75460
75895
  border-left: 1px solid #c1c1c1;
75461
75896
  border-right: 1px solid #c1c1c1;
75462
75897
  margin-left: -1px;
@@ -75500,6 +75935,10 @@ css /* scss */ `
75500
75935
  width: calc(100% - 1px);
75501
75936
  }
75502
75937
  }
75938
+
75939
+ .o-spreadshet-mobile .o-sheet {
75940
+ height: ${MOBILE_BOTTOMBAR_HEIGHT}px;
75941
+ }
75503
75942
  `;
75504
75943
  class BottomBarSheet extends Component {
75505
75944
  static template = "o-spreadsheet-BottomBarSheet";
@@ -75554,7 +75993,16 @@ class BottomBarSheet extends Component {
75554
75993
  this.stopEdition();
75555
75994
  }
75556
75995
  }
75996
+ onClick() {
75997
+ if (!this.env.isMobile()) {
75998
+ return;
75999
+ }
76000
+ this.activateSheet();
76001
+ }
75557
76002
  onMouseDown(ev) {
76003
+ if (this.env.isMobile()) {
76004
+ return;
76005
+ }
75558
76006
  this.activateSheet();
75559
76007
  this.props.onMouseDown(ev);
75560
76008
  }
@@ -75898,8 +76346,8 @@ css /* scss */ `
75898
76346
  }
75899
76347
  }
75900
76348
 
75901
- .mobile.o-spreadsheet-bottom-bar {
75902
- padding-left: 1rem;
76349
+ .o-spreadsheet-mobile .o-spreadsheet-bottom-bar {
76350
+ padding-left: 0;
75903
76351
 
75904
76352
  .add-sheet-container {
75905
76353
  order: 2;
@@ -75916,10 +76364,8 @@ css /* scss */ `
75916
76364
  `;
75917
76365
  class BottomBar extends Component {
75918
76366
  static template = "o-spreadsheet-BottomBar";
75919
- static props = {
75920
- onClick: Function,
75921
- };
75922
- static components = { Menu, Ripple, BottomBarSheet, BottomBarStatistic };
76367
+ static props = { onClick: Function };
76368
+ static components = { MenuPopover, Ripple, BottomBarSheet, BottomBarStatistic };
75923
76369
  bottomBarRef = useRef("bottomBar");
75924
76370
  sheetListRef = useRef("sheetList");
75925
76371
  dragAndDrop = useDragAndDropListItems();
@@ -76056,6 +76502,9 @@ class BottomBar extends Component {
76056
76502
  if (event.button !== 0 || this.env.model.getters.isReadonly())
76057
76503
  return;
76058
76504
  this.closeMenu();
76505
+ if (this.env.isMobile()) {
76506
+ return;
76507
+ }
76059
76508
  const visibleSheets = this.getVisibleSheets();
76060
76509
  const sheetRects = this.getSheetItemRects();
76061
76510
  const sheets = visibleSheets.map((sheet, index) => ({
@@ -76175,7 +76624,7 @@ css /* scss */ `
76175
76624
  `;
76176
76625
  class SpreadsheetDashboard extends Component {
76177
76626
  static template = "o-spreadsheet-SpreadsheetDashboard";
76178
- static props = {};
76627
+ static props = { getGridSize: Function };
76179
76628
  static components = {
76180
76629
  GridOverlay,
76181
76630
  GridPopover,
@@ -76462,7 +76911,7 @@ class HeaderGroupContainer extends Component {
76462
76911
  dimension: String,
76463
76912
  layers: Array,
76464
76913
  };
76465
- static components = { RowGroup, ColGroup, Menu };
76914
+ static components = { RowGroup, ColGroup, MenuPopover };
76466
76915
  menu = useState({ isOpen: false, anchorRect: null, menuItems: [] });
76467
76916
  getLayerOffset(layerIndex) {
76468
76917
  return layerIndex * GROUP_LAYER_WIDTH;
@@ -76678,6 +77127,158 @@ class SidePanel extends Component {
76678
77127
  }
76679
77128
  }
76680
77129
 
77130
+ class RibbonMenu extends Component {
77131
+ static template = "o-spreadsheet-RibbonMenu";
77132
+ static props = {
77133
+ onClose: Function,
77134
+ };
77135
+ static components = { Menu };
77136
+ rootItems = topbarMenuRegistry.getMenuItems();
77137
+ menuRef = useRef("menu");
77138
+ state = useState({
77139
+ menuItems: this.rootItems,
77140
+ title: _t("Menu Bar"),
77141
+ parentState: undefined,
77142
+ });
77143
+ setup() {
77144
+ useExternalListener(window, "click", this.onExternalClick, { capture: true });
77145
+ }
77146
+ onExternalClick(ev) {
77147
+ if (!this.menuRef.el?.contains(ev.target)) {
77148
+ this.props.onClose();
77149
+ }
77150
+ }
77151
+ onClickMenu(menu) {
77152
+ const children = menu.children(this.env);
77153
+ if (children.length) {
77154
+ this.state.parentState = { ...this.state };
77155
+ this.state.menuItems = children;
77156
+ this.state.title = menu.name(this.env);
77157
+ }
77158
+ else {
77159
+ this.state.menuItems = this.rootItems;
77160
+ this.state.title = undefined;
77161
+ this.state.parentState = undefined;
77162
+ menu.execute?.(this.env);
77163
+ this.props.onClose();
77164
+ }
77165
+ }
77166
+ get menuProps() {
77167
+ return {
77168
+ menuItems: this.state.menuItems,
77169
+ onClose: this.props.onClose,
77170
+ onClickMenu: this.onClickMenu.bind(this),
77171
+ };
77172
+ }
77173
+ get style() {
77174
+ return cssPropertiesToCss({
77175
+ height: `${this.props.height}px`,
77176
+ });
77177
+ }
77178
+ onClickBack() {
77179
+ if (!this.state.parentState) {
77180
+ this.props.onClose();
77181
+ return;
77182
+ }
77183
+ this.state.menuItems = this.state.parentState.menuItems;
77184
+ this.state.title = this.state.parentState.title;
77185
+ this.state.parentState = this.state.parentState.parentState;
77186
+ }
77187
+ get backTitle() {
77188
+ return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
77189
+ }
77190
+ }
77191
+
77192
+ css `
77193
+ .o-small-composer {
77194
+ z-index: ${ComponentsImportance.TopBarComposer};
77195
+ }
77196
+ `;
77197
+ class SmallBottomBar extends Component {
77198
+ static components = { Composer, BottomBar, Ripple, RibbonMenu };
77199
+ static template = "o-spreadsheet-SmallBottomBar";
77200
+ static props = {
77201
+ onClick: Function,
77202
+ };
77203
+ composerFocusStore;
77204
+ composerStore;
77205
+ composerInterface;
77206
+ composerRef = useRef("bottombarComposer");
77207
+ menuState = useState({
77208
+ isOpen: false,
77209
+ });
77210
+ setup() {
77211
+ this.composerFocusStore = useStore(ComposerFocusStore);
77212
+ const composerStore = useStore(CellComposerStore);
77213
+ this.composerStore = composerStore;
77214
+ this.composerInterface = {
77215
+ id: "bottombarComposer",
77216
+ get editionMode() {
77217
+ return composerStore.editionMode;
77218
+ },
77219
+ startEdition: this.composerStore.startEdition,
77220
+ setCurrentContent: this.composerStore.setCurrentContent,
77221
+ stopEdition: this.composerStore.stopEdition,
77222
+ };
77223
+ useEffect(() => {
77224
+ if (
77225
+ // we hide the grid composer on mobile so we need to autofocus this composer
77226
+ this.env.isMobile() &&
77227
+ !this.menuState.isOpen &&
77228
+ this.composerStore.editionMode !== "inactive" &&
77229
+ this.composerFocusStore.activeComposer !== this.composerInterface) {
77230
+ this.composerFocusStore.focusComposer(this.composerInterface, {
77231
+ focusMode: "contentFocus",
77232
+ });
77233
+ }
77234
+ });
77235
+ }
77236
+ get focus() {
77237
+ return this.composerFocusStore.activeComposer === this.composerInterface
77238
+ ? this.composerFocusStore.focusMode
77239
+ : "inactive";
77240
+ }
77241
+ get rect() {
77242
+ return this.composerRef.el
77243
+ ? getBoundingRectAsPOJO(this.composerRef.el)
77244
+ : { x: 0, y: 0, width: 0, height: 0 };
77245
+ }
77246
+ get composerProps() {
77247
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
77248
+ return {
77249
+ rect: { ...this.rect },
77250
+ delimitation: {
77251
+ width,
77252
+ height,
77253
+ },
77254
+ focus: this.focus,
77255
+ composerStore: this.composerStore,
77256
+ onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
77257
+ focusMode: "contentFocus",
77258
+ }),
77259
+ isDefaultFocus: false,
77260
+ inputStyle: cssPropertiesToCss({
77261
+ height: this.focus === "inactive" ? "26px" : "fit-content",
77262
+ "max-height": `130px`,
77263
+ }),
77264
+ showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
77265
+ };
77266
+ }
77267
+ get symbols() {
77268
+ return ["=", "(", ")", ":", "-", "/", "*", ",", "+", "$", "."];
77269
+ }
77270
+ insertSymbol(symbol) {
77271
+ this.composerStore.replaceComposerCursorSelection(symbol);
77272
+ this.composerFocusStore.focusComposer(this.composerInterface, {
77273
+ focusMode: "contentFocus",
77274
+ });
77275
+ }
77276
+ toggleRibbon() {
77277
+ this.composerStore.cancelEdition();
77278
+ this.menuState.isOpen = !this.menuState.isOpen;
77279
+ }
77280
+ }
77281
+
76681
77282
  const COMPOSER_MAX_HEIGHT = 100;
76682
77283
  /* svg free of use from https://uxwing.com/formula-fx-icon/ */
76683
77284
  const FX_SVG = /*xml*/ `
@@ -76687,7 +77288,7 @@ const FX_SVG = /*xml*/ `
76687
77288
  `;
76688
77289
  css /* scss */ `
76689
77290
  .o-topbar-composer-container {
76690
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
77291
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
76691
77292
  }
76692
77293
 
76693
77294
  .o-topbar-composer {
@@ -76742,7 +77343,7 @@ class TopBarComposer extends Component {
76742
77343
  "max-height": `${COMPOSER_MAX_HEIGHT}px`,
76743
77344
  "line-height": "24px",
76744
77345
  };
76745
- style.height = this.focus === "inactive" ? `${TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
77346
+ style.height = this.focus === "inactive" ? `${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px` : "fit-content";
76746
77347
  return cssPropertiesToCss(style);
76747
77348
  }
76748
77349
  get containerStyle() {
@@ -77245,7 +77846,7 @@ class TopBarFontSizeEditor extends Component {
77245
77846
 
77246
77847
  class NumberFormatsTool extends Component {
77247
77848
  static template = "o-spreadsheet-NumberFormatsTool";
77248
- static components = { Menu, ActionButton };
77849
+ static components = { MenuPopover, ActionButton };
77249
77850
  static props = { class: String };
77250
77851
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
77251
77852
  topBarToolStore;
@@ -77295,7 +77896,7 @@ topBarToolBarRegistry
77295
77896
  .addChild("edit", {
77296
77897
  component: PaintFormatButton,
77297
77898
  props: {
77298
- class: "o-hoverable-button o-toolbar-button",
77899
+ class: "o-hoverable-button o-toolbar-button o-mobile-disabled",
77299
77900
  },
77300
77901
  sequence: 3,
77301
77902
  })
@@ -77454,7 +78055,7 @@ topBarToolBarRegistry
77454
78055
  .add("misc")
77455
78056
  .addChild("misc", {
77456
78057
  component: TableDropdownButton,
77457
- props: { class: "o-toolbar-button o-hoverable-button o-menu-item-button" },
78058
+ props: { class: "o-toolbar-button o-hoverable-button o-menu-item-button o-mobile-disabled" },
77458
78059
  sequence: 1,
77459
78060
  })
77460
78061
  .addChild("misc", {
@@ -77474,6 +78075,7 @@ css /* scss */ `
77474
78075
  border-right: 1px solid ${SEPARATOR_COLOR};
77475
78076
  width: 0;
77476
78077
  margin: 0 6px;
78078
+ height: 30px;
77477
78079
  }
77478
78080
 
77479
78081
  .o-toolbar-button {
@@ -77482,7 +78084,7 @@ css /* scss */ `
77482
78084
 
77483
78085
  .o-spreadsheet-topbar {
77484
78086
  line-height: 1.2;
77485
- font-size: 13px;
78087
+ font-size: 14px;
77486
78088
  font-weight: 500;
77487
78089
  background-color: #fff;
77488
78090
 
@@ -77506,7 +78108,7 @@ css /* scss */ `
77506
78108
 
77507
78109
  .irregularity-map {
77508
78110
  border-top: 1px solid ${SEPARATOR_COLOR};
77509
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78111
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77510
78112
 
77511
78113
  .alert-info {
77512
78114
  border-left: 3px solid ${ALERT_INFO_BORDER};
@@ -77519,7 +78121,7 @@ css /* scss */ `
77519
78121
 
77520
78122
  /* Toolbar */
77521
78123
  .o-topbar-toolbar {
77522
- height: ${TOPBAR_TOOLBAR_HEIGHT}px;
78124
+ height: ${DESKTOP_TOPBAR_TOOLBAR_HEIGHT}px;
77523
78125
 
77524
78126
  .o-readonly-toolbar {
77525
78127
  background-color: ${BACKGROUND_HEADER_COLOR};
@@ -77533,6 +78135,24 @@ css /* scss */ `
77533
78135
  }
77534
78136
  }
77535
78137
  }
78138
+
78139
+ .o-spreadsheet-mobile {
78140
+ .o-topbar-toolbar {
78141
+ height: ${MOBILE_TOPBAR_TOOLBAR_HEIGHT}px;
78142
+ }
78143
+ .o-topbar-divider {
78144
+ border-width: 2px;
78145
+ border-radius: 4px;
78146
+ }
78147
+
78148
+ .o-toolbar-button {
78149
+ height: 35px;
78150
+ width: 31px;
78151
+ .o-toolbar-button.o-mobile-disabled * {
78152
+ color: ${DISABLED_TEXT_COLOR};
78153
+ cursor: not-allowed;
78154
+ }
78155
+ }
77536
78156
  `;
77537
78157
  class TopBar extends Component {
77538
78158
  static template = "o-spreadsheet-TopBar";
@@ -77541,7 +78161,7 @@ class TopBar extends Component {
77541
78161
  dropdownMaxHeight: Number,
77542
78162
  };
77543
78163
  static components = {
77544
- Menu,
78164
+ MenuPopover,
77545
78165
  TopBarComposer,
77546
78166
  Popover,
77547
78167
  };
@@ -77621,7 +78241,7 @@ class TopBar extends Component {
77621
78241
  // TODO : manage click events better. We need this piece of code
77622
78242
  // otherwise the event opening the menu would close it on the same frame.
77623
78243
  // And we cannot stop the event propagation because it's used in an
77624
- // external listener of the Menu component to close the context menu when
78244
+ // external listener of the MenuPopover component to close the context menu when
77625
78245
  // clicking on the top bar
77626
78246
  if (this.openedEl === ev.target) {
77627
78247
  return;
@@ -78047,6 +78667,11 @@ css /* scss */ `
78047
78667
  color: ${TEXT_BODY};
78048
78668
  }
78049
78669
  }
78670
+
78671
+ .o-spreadsheet-topbar-wrapper,
78672
+ .o-spreadsheet-bottombar-wrapper {
78673
+ z-index: ${ComponentsImportance.ScrollBar + 1};
78674
+ }
78050
78675
  `;
78051
78676
  class Spreadsheet extends Component {
78052
78677
  static template = "o-spreadsheet-Spreadsheet";
@@ -78060,6 +78685,7 @@ class Spreadsheet extends Component {
78060
78685
  TopBar,
78061
78686
  Grid,
78062
78687
  BottomBar,
78688
+ SmallBottomBar,
78063
78689
  SidePanel,
78064
78690
  SpreadsheetDashboard,
78065
78691
  HeaderGroupContainer,
@@ -78083,12 +78709,25 @@ class Spreadsheet extends Component {
78083
78709
  else {
78084
78710
  properties["grid-template-rows"] = `min-content auto min-content`;
78085
78711
  }
78086
- properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
78712
+ const columnWidth = this.sidePanel.isOpen ? `${this.sidePanel.panelSize}px` : "auto";
78713
+ properties["grid-template-columns"] = `auto ${columnWidth}`;
78087
78714
  return cssPropertiesToCss(properties);
78088
78715
  }
78089
78716
  setup() {
78717
+ if (!("isSmall" in this.env)) {
78718
+ const screenSize = useScreenWidth();
78719
+ useSubEnv({
78720
+ get isSmall() {
78721
+ return screenSize.isSmall;
78722
+ },
78723
+ });
78724
+ }
78090
78725
  const stores = useStoreProvider();
78091
78726
  stores.inject(ModelStore, this.model);
78727
+ const env = this.env;
78728
+ stores.get(ScreenWidthStore).setSmallThreshhold(() => {
78729
+ return env.isSmall;
78730
+ });
78092
78731
  this.notificationStore = useStore(NotificationStore);
78093
78732
  this.composerFocusStore = useStore(ComposerFocusStore);
78094
78733
  this.sidePanel = useStore(SidePanelStore);
@@ -78106,15 +78745,8 @@ class Spreadsheet extends Component {
78106
78745
  notifyUser: (notification) => this.notificationStore.notifyUser(notification),
78107
78746
  askConfirmation: (text, confirm, cancel) => this.notificationStore.askConfirmation(text, confirm, cancel),
78108
78747
  raiseError: (text, cb) => this.notificationStore.raiseError(text, cb),
78748
+ isMobile: isMobileOS,
78109
78749
  });
78110
- if (!("isSmall" in this.env)) {
78111
- const screenSize = useScreenWidth();
78112
- useSubEnv({
78113
- get isSmall() {
78114
- return screenSize.isSmall;
78115
- },
78116
- });
78117
- }
78118
78750
  this.notificationStore.updateNotificationCallbacks({ ...this.props });
78119
78751
  useEffect(() => {
78120
78752
  /**
@@ -78220,6 +78852,24 @@ class Spreadsheet extends Component {
78220
78852
  const sheetId = this.env.model.getters.getActiveSheetId();
78221
78853
  return this.env.model.getters.getVisibleGroupLayers(sheetId, "COL");
78222
78854
  }
78855
+ getGridSize() {
78856
+ const topBarHeight = this.spreadsheetRef.el
78857
+ ?.querySelector(".o-spreadsheet-topbar-wrapper")
78858
+ ?.getBoundingClientRect().height || 0;
78859
+ const bottomBarHeight = this.spreadsheetRef.el
78860
+ ?.querySelector(".o-spreadsheet-bottombar-wrapper")
78861
+ ?.getBoundingClientRect().height || 0;
78862
+ const gridWidth = this.spreadsheetRef.el?.querySelector(".o-grid")?.getBoundingClientRect().width || 0;
78863
+ const gridHeight = (this.spreadsheetRef.el?.getBoundingClientRect().height || 0) -
78864
+ (this.spreadsheetRef.el?.querySelector(".o-column-groups")?.getBoundingClientRect().height ||
78865
+ 0) -
78866
+ topBarHeight -
78867
+ bottomBarHeight;
78868
+ return {
78869
+ width: Math.max(gridWidth - SCROLLBAR_WIDTH, 0),
78870
+ height: Math.max(gridHeight - SCROLLBAR_WIDTH, 0),
78871
+ };
78872
+ }
78223
78873
  }
78224
78874
 
78225
78875
  function inverseCommand(cmd) {
@@ -82482,7 +83132,7 @@ const SPREADSHEET_DIMENSIONS = {
82482
83132
  MIN_COL_WIDTH,
82483
83133
  HEADER_HEIGHT,
82484
83134
  HEADER_WIDTH,
82485
- BOTTOMBAR_HEIGHT,
83135
+ DESKTOP_BOTTOMBAR_HEIGHT,
82486
83136
  DEFAULT_CELL_WIDTH,
82487
83137
  DEFAULT_CELL_HEIGHT,
82488
83138
  SCROLLBAR_WIDTH,
@@ -82628,7 +83278,7 @@ const components = {
82628
83278
  FunnelChartDesignPanel,
82629
83279
  ChartTypePicker,
82630
83280
  FigureComponent,
82631
- Menu,
83281
+ MenuPopover,
82632
83282
  Popover,
82633
83283
  SelectionInput,
82634
83284
  ValidationMessages,
@@ -82689,9 +83339,9 @@ const constants = {
82689
83339
  };
82690
83340
  const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
82691
83341
 
82692
- export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
83342
+ export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
82693
83343
 
82694
83344
 
82695
- __info__.version = "18.4.0-alpha.6";
82696
- __info__.date = "2025-05-30T08:44:33.216Z";
82697
- __info__.hash = "eecf7e4";
83345
+ __info__.version = "18.4.0-alpha.8";
83346
+ __info__.date = "2025-06-12T09:53:48.133Z";
83347
+ __info__.hash = "9b7a8d0";