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