@odoo/o-spreadsheet 19.1.0-alpha.4 → 19.1.0-alpha.5

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 19.1.0-alpha.4
6
- * @date 2025-10-07T10:03:20.917Z
7
- * @hash b7cfed8
5
+ * @version 19.1.0-alpha.5
6
+ * @date 2025-10-16T06:39:55.925Z
7
+ * @hash 1a0e3d5
8
8
  */
9
9
 
10
10
  'use strict';
@@ -4452,7 +4452,17 @@ function toNumberMatrix(data, argName) {
4452
4452
  return toMatrix(data).map((row) => {
4453
4453
  return row.map((cell) => {
4454
4454
  if (typeof cell.value !== "number") {
4455
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects number values for %s, but got a %s.", argName, typeof cell.value));
4455
+ let message = "";
4456
+ if (typeof cell === "object") {
4457
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
4458
+ }
4459
+ else if (typeof cell === "string") {
4460
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
4461
+ }
4462
+ else if (typeof cell === "boolean") {
4463
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
4464
+ }
4465
+ throw new EvaluationError(message);
4456
4466
  }
4457
4467
  return cell.value;
4458
4468
  });
@@ -6390,6 +6400,9 @@ function parseLiteral(content, locale) {
6390
6400
  if (content.startsWith("=")) {
6391
6401
  throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
6392
6402
  }
6403
+ if (content.startsWith("'")) {
6404
+ return content.slice(1);
6405
+ }
6393
6406
  if (content === "") {
6394
6407
  return null;
6395
6408
  }
@@ -9339,6 +9352,32 @@ function getCustomFieldWithParentField(definition, parentField, fields) {
9339
9352
  groups: [],
9340
9353
  });
9341
9354
  }
9355
+ function togglePivotCollapse(position, env) {
9356
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
9357
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
9358
+ if (!pivotId || pivotCell.type !== "HEADER") {
9359
+ return;
9360
+ }
9361
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
9362
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
9363
+ ? [...definition.collapsedDomains[pivotCell.dimension]]
9364
+ : [];
9365
+ const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
9366
+ if (index !== -1) {
9367
+ collapsedDomains.splice(index, 1);
9368
+ }
9369
+ else {
9370
+ collapsedDomains.push(pivotCell.domain);
9371
+ }
9372
+ const newDomains = definition.collapsedDomains
9373
+ ? { ...definition.collapsedDomains }
9374
+ : { COL: [], ROW: [] };
9375
+ newDomains[pivotCell.dimension] = collapsedDomains;
9376
+ env.model.dispatch("UPDATE_PIVOT", {
9377
+ pivotId,
9378
+ pivot: { ...definition, collapsedDomains: newDomains },
9379
+ });
9380
+ }
9342
9381
 
9343
9382
  class CellClipboardHandler extends AbstractCellClipboardHandler {
9344
9383
  isCutAllowed(data) {
@@ -9512,7 +9551,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
9512
9551
  pasteCell(origin, target, clipboardOption) {
9513
9552
  const { sheetId, col, row } = target;
9514
9553
  const targetCell = this.getters.getEvaluatedCell(target);
9515
- const originFormat = origin?.format ?? origin.evaluatedCell.format;
9554
+ const originFormat = origin?.format || origin.evaluatedCell.format;
9516
9555
  if (clipboardOption?.pasteOption === "asValue") {
9517
9556
  this.dispatch("UPDATE_CELL", {
9518
9557
  ...target,
@@ -13738,7 +13777,7 @@ const GROWTH = {
13738
13777
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13739
13778
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13740
13779
  }
13741
- return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "the first argument (known_data_y)")), toNumberMatrix(knownDataX, "the second argument (known_data_x)"), toNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b)));
13780
+ return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
13742
13781
  },
13743
13782
  };
13744
13783
  // -----------------------------------------------------------------------------
@@ -13811,7 +13850,7 @@ const LINEST = {
13811
13850
  if (dataY.length === 0 || dataY[0].length === 0) {
13812
13851
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13813
13852
  }
13814
- return fullLinearRegression(toNumberMatrix(dataX, "the first argument (data_y)"), toNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
13853
+ return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
13815
13854
  },
13816
13855
  isExported: true,
13817
13856
  };
@@ -13830,7 +13869,7 @@ const LOGEST = {
13830
13869
  if (dataY.length === 0 || dataY[0].length === 0) {
13831
13870
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13832
13871
  }
13833
- const coeffs = fullLinearRegression(toNumberMatrix(dataX, "the second argument (data_x)"), logM(toNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
13872
+ const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
13834
13873
  for (let i = 0; i < coeffs.length; i++) {
13835
13874
  coeffs[i][0] = Math.exp(coeffs[i][0]);
13836
13875
  }
@@ -14451,7 +14490,7 @@ const TREND = {
14451
14490
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
14452
14491
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
14453
14492
  }
14454
- return predictLinearValues(toNumberMatrix(knownDataY, "the first argument (known_data_y)"), toNumberMatrix(knownDataX, "the second argument (known_data_x)"), toNumberMatrix(newDataX, "the third argument (new_data_y)"), toBoolean(b));
14493
+ return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
14455
14494
  },
14456
14495
  };
14457
14496
  // -----------------------------------------------------------------------------
@@ -22972,6 +23011,10 @@ const chartShowValuesPlugin = {
22972
23011
  }
22973
23012
  const ctx = chart.ctx;
22974
23013
  ctx.save();
23014
+ const { left, top, height, width } = chart.chartArea;
23015
+ ctx.beginPath();
23016
+ ctx.rect(left, top, width, height);
23017
+ ctx.clip();
22975
23018
  ctx.textAlign = "center";
22976
23019
  ctx.textBaseline = "middle";
22977
23020
  ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
@@ -24006,7 +24049,7 @@ class ChartJsComponent extends owl.Component {
24006
24049
  this.chart.update();
24007
24050
  }
24008
24051
  hasChartDataChanged() {
24009
- return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
24052
+ return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
24010
24053
  }
24011
24054
  enableAnimationInChartData(chartData) {
24012
24055
  return {
@@ -24014,6 +24057,17 @@ class ChartJsComponent extends owl.Component {
24014
24057
  options: { ...chartData.options, animation: { animateRotate: true } },
24015
24058
  };
24016
24059
  }
24060
+ getChartDataInRuntime(runtime) {
24061
+ const data = runtime.chartJsConfig.data;
24062
+ return {
24063
+ labels: data.labels,
24064
+ dataset: data.datasets.map((dataset) => ({
24065
+ data: dataset.data,
24066
+ label: dataset.label,
24067
+ tree: dataset.tree,
24068
+ })),
24069
+ };
24070
+ }
24017
24071
  get animationChartId() {
24018
24072
  return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
24019
24073
  }
@@ -25437,6 +25491,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
25437
25491
  parser: luxonFormat,
25438
25492
  displayFormats,
25439
25493
  unit: timeUnit ?? false,
25494
+ tooltipFormat: luxonFormat,
25440
25495
  };
25441
25496
  }
25442
25497
  /**
@@ -26505,6 +26560,7 @@ function getLineChartScales(definition, args) {
26505
26560
  };
26506
26561
  Object.assign(scales.x, axis);
26507
26562
  scales.x.ticks.maxTicksLimit = 15;
26563
+ delete scales?.x?.ticks?.callback;
26508
26564
  }
26509
26565
  else if (axisType === "linear") {
26510
26566
  scales.x.type = "linear";
@@ -31583,7 +31639,8 @@ class Menu extends owl.Component {
31583
31639
  const menuItemsAndSeparators = [];
31584
31640
  for (let i = 0; i < this.props.menuItems.length; i++) {
31585
31641
  const menuItem = this.props.menuItems[i];
31586
- if (menuItem.isVisible(this.env)) {
31642
+ if (menuItem.isVisible(this.env) &&
31643
+ (!this.isRoot(menuItem) || this.hasVisibleChildren(menuItem))) {
31587
31644
  menuItemsAndSeparators.push(menuItem);
31588
31645
  }
31589
31646
  if (menuItem.separator &&
@@ -31625,6 +31682,9 @@ class Menu extends owl.Component {
31625
31682
  isRoot(menu) {
31626
31683
  return !menu.execute;
31627
31684
  }
31685
+ hasVisibleChildren(menu) {
31686
+ return menu.children(this.env).some((child) => child.isVisible(this.env));
31687
+ }
31628
31688
  isEnabled(menu) {
31629
31689
  if (menu.isEnabled(this.env)) {
31630
31690
  return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
@@ -32183,7 +32243,7 @@ class ChartDashboardMenu extends owl.Component {
32183
32243
  }
32184
32244
  openContextMenu(ev) {
32185
32245
  this.menuState.isOpen = true;
32186
- this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
32246
+ this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
32187
32247
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32188
32248
  this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
32189
32249
  }
@@ -32215,6 +32275,7 @@ class CarouselFigure extends owl.Component {
32215
32275
  onFigureDeleted: Function,
32216
32276
  editFigureStyle: { type: Function, optional: true },
32217
32277
  isFullScreen: { type: Boolean, optional: true },
32278
+ openContextMenu: { type: Function, optional: true },
32218
32279
  };
32219
32280
  static components = { ChartDashboardMenu, MenuPopover };
32220
32281
  carouselTabsRef = owl.useRef("carouselTabs");
@@ -32348,6 +32409,12 @@ class CarouselFigure extends owl.Component {
32348
32409
  get visibleCarouselItems() {
32349
32410
  return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
32350
32411
  }
32412
+ openContextMenu(event) {
32413
+ const target = event.currentTarget;
32414
+ if (target) {
32415
+ this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
32416
+ }
32417
+ }
32351
32418
  }
32352
32419
 
32353
32420
  class ChartFigure extends owl.Component {
@@ -32357,6 +32424,7 @@ class ChartFigure extends owl.Component {
32357
32424
  onFigureDeleted: Function,
32358
32425
  editFigureStyle: { type: Function, optional: true },
32359
32426
  isFullScreen: { type: Boolean, optional: true },
32427
+ openContextMenu: { type: Function, optional: true },
32360
32428
  };
32361
32429
  static components = { ChartDashboardMenu };
32362
32430
  onDoubleClick() {
@@ -32389,6 +32457,7 @@ class ImageFigure extends owl.Component {
32389
32457
  figureUI: Object,
32390
32458
  onFigureDeleted: Function,
32391
32459
  editFigureStyle: { type: Function, optional: true },
32460
+ openContextMenu: { type: Function, optional: true },
32392
32461
  };
32393
32462
  static components = {};
32394
32463
  // ---------------------------------------------------------------------------
@@ -34191,8 +34260,11 @@ class Composer extends owl.Component {
34191
34260
  }
34192
34261
  const newSelection = this.contentHelper.getCurrentSelection();
34193
34262
  this.props.composerStore.stopComposerRangeSelection();
34194
- this.props.onComposerContentFocused();
34195
- this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34263
+ const isCurrentlyInactive = this.props.composerStore.editionMode === "inactive";
34264
+ this.props.onComposerContentFocused(newSelection);
34265
+ if (!isCurrentlyInactive) {
34266
+ this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34267
+ }
34196
34268
  this.processTokenAtCursor();
34197
34269
  }
34198
34270
  onDblClick() {
@@ -34687,13 +34759,6 @@ class AbstractComposerStore extends SpreadsheetStore {
34687
34759
  }
34688
34760
  }
34689
34761
  startEdition(text, selection) {
34690
- if (selection) {
34691
- const content = text || this.getComposerContent(this.getters.getActivePosition());
34692
- const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
34693
- if (!validSelection) {
34694
- return;
34695
- }
34696
- }
34697
34762
  const { col, row } = this.getters.getActivePosition();
34698
34763
  this.model.dispatch("SELECT_FIGURE", { figureId: null });
34699
34764
  this.model.dispatch("SCROLL_TO_CELL", { col, row });
@@ -34750,7 +34815,7 @@ class AbstractComposerStore extends SpreadsheetStore {
34750
34815
  // ---------------------------------------------------------------------------
34751
34816
  get currentContent() {
34752
34817
  if (this.editionMode === "inactive") {
34753
- return this.getComposerContent(this.getters.getActivePosition());
34818
+ return this.getComposerContent(this.getters.getActivePosition()).text;
34754
34819
  }
34755
34820
  return this._currentContent;
34756
34821
  }
@@ -34949,8 +35014,9 @@ class AbstractComposerStore extends SpreadsheetStore {
34949
35014
  this.sheetId = sheetId;
34950
35015
  this.row = row;
34951
35016
  this.editionMode = "editing";
34952
- this.initialContent = this.getComposerContent({ sheetId, col, row });
34953
- this.setContent(str || this.initialContent, selection);
35017
+ const { text, adjustedSelection } = this.getComposerContent({ sheetId, col, row }, selection);
35018
+ this.initialContent = text;
35019
+ this.setContent(str || this.initialContent, adjustedSelection ?? selection);
34954
35020
  this.colorIndexByRange = {};
34955
35021
  const zone = positionToZone({ col: this.col, row: this.row });
34956
35022
  this.captureSelection(zone, col, row);
@@ -35427,7 +35493,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35427
35493
  constructor(get, args) {
35428
35494
  super(get);
35429
35495
  this.args = args;
35430
- this._currentContent = this.getComposerContent();
35496
+ this._currentContent = this.getComposerContent().text;
35431
35497
  }
35432
35498
  getAutoCompleteProviders() {
35433
35499
  const providersDefinitions = super.getAutoCompleteProviders();
@@ -35464,7 +35530,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35464
35530
  })
35465
35531
  .join("");
35466
35532
  }
35467
- return localizeContent(content, this.getters.getLocale());
35533
+ return { text: localizeContent(content, this.getters.getLocale()) };
35468
35534
  }
35469
35535
  stopEdition() {
35470
35536
  this._stopEdition();
@@ -38684,6 +38750,74 @@ function getPath2D(svgPath) {
38684
38750
  return path2D;
38685
38751
  }
38686
38752
 
38753
+ /**
38754
+ * Get the relative path between two files
38755
+ *
38756
+ * Eg.:
38757
+ * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
38758
+ */
38759
+ function getRelativePath(from, to) {
38760
+ const fromPathParts = from.split("/");
38761
+ const toPathParts = to.split("/");
38762
+ let relPath = "";
38763
+ let startIndex = 0;
38764
+ for (let i = 0; i < fromPathParts.length - 1; i++) {
38765
+ if (fromPathParts[i] === toPathParts[i]) {
38766
+ startIndex++;
38767
+ }
38768
+ else {
38769
+ relPath += "../";
38770
+ }
38771
+ }
38772
+ relPath += toPathParts.slice(startIndex).join("/");
38773
+ return relPath;
38774
+ }
38775
+ /**
38776
+ * Convert an array of element into an object where the objects keys were the elements position in the array.
38777
+ * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
38778
+ *
38779
+ * eg. : ["a", "b"] => {0:"a", 1:"b"}
38780
+ */
38781
+ function arrayToObject(array, indexOffset = 0) {
38782
+ const obj = {};
38783
+ for (let i = 0; i < array.length; i++) {
38784
+ if (array[i]) {
38785
+ obj[i + indexOffset] = array[i];
38786
+ }
38787
+ }
38788
+ return obj;
38789
+ }
38790
+ /**
38791
+ * In xlsx we can have string with unicode characters with the format _x00fa_.
38792
+ * Replace with characters understandable by JS
38793
+ */
38794
+ function fixXlsxUnicode(str) {
38795
+ return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
38796
+ return String.fromCharCode(parseInt(code, 16));
38797
+ });
38798
+ }
38799
+ /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
38800
+ function getSheetDataHeader(sheetData, dimension, index) {
38801
+ if (dimension === "COL") {
38802
+ if (!sheetData.cols[index]) {
38803
+ sheetData.cols[index] = {};
38804
+ }
38805
+ return sheetData.cols[index];
38806
+ }
38807
+ if (!sheetData.rows[index]) {
38808
+ sheetData.rows[index] = {};
38809
+ }
38810
+ return sheetData.rows[index];
38811
+ }
38812
+ /** Prefix the string by "=" if the string looks like a formula */
38813
+ function prefixFormulaWithEqual(formula) {
38814
+ if (formula[0] === "=") {
38815
+ return formula;
38816
+ }
38817
+ const tokens = tokenize(formula);
38818
+ return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
38819
+ }
38820
+
38687
38821
  /**
38688
38822
  * Map of the different types of conversions warnings and their name in error messages
38689
38823
  */
@@ -39206,66 +39340,6 @@ function hexaToInt(hex) {
39206
39340
  */
39207
39341
  const DEFAULT_SYSTEM_COLOR = "FF000000";
39208
39342
 
39209
- /**
39210
- * Get the relative path between two files
39211
- *
39212
- * Eg.:
39213
- * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
39214
- */
39215
- function getRelativePath(from, to) {
39216
- const fromPathParts = from.split("/");
39217
- const toPathParts = to.split("/");
39218
- let relPath = "";
39219
- let startIndex = 0;
39220
- for (let i = 0; i < fromPathParts.length - 1; i++) {
39221
- if (fromPathParts[i] === toPathParts[i]) {
39222
- startIndex++;
39223
- }
39224
- else {
39225
- relPath += "../";
39226
- }
39227
- }
39228
- relPath += toPathParts.slice(startIndex).join("/");
39229
- return relPath;
39230
- }
39231
- /**
39232
- * Convert an array of element into an object where the objects keys were the elements position in the array.
39233
- * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
39234
- *
39235
- * eg. : ["a", "b"] => {0:"a", 1:"b"}
39236
- */
39237
- function arrayToObject(array, indexOffset = 0) {
39238
- const obj = {};
39239
- for (let i = 0; i < array.length; i++) {
39240
- if (array[i]) {
39241
- obj[i + indexOffset] = array[i];
39242
- }
39243
- }
39244
- return obj;
39245
- }
39246
- /**
39247
- * In xlsx we can have string with unicode characters with the format _x00fa_.
39248
- * Replace with characters understandable by JS
39249
- */
39250
- function fixXlsxUnicode(str) {
39251
- return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
39252
- return String.fromCharCode(parseInt(code, 16));
39253
- });
39254
- }
39255
- /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
39256
- function getSheetDataHeader(sheetData, dimension, index) {
39257
- if (dimension === "COL") {
39258
- if (!sheetData.cols[index]) {
39259
- sheetData.cols[index] = {};
39260
- }
39261
- return sheetData.cols[index];
39262
- }
39263
- if (!sheetData.rows[index]) {
39264
- sheetData.rows[index] = {};
39265
- }
39266
- return sheetData.rows[index];
39267
- }
39268
-
39269
39343
  const XLSX_DATE_FORMAT_REGEX = /^(yy|yyyy|m{1,5}|d{1,4}|h{1,2}|s{1,2}|am\/pm|a\/m|\s|-|\/|\.|:)+$/i;
39270
39344
  /**
39271
39345
  * Convert excel format to o_spreadsheet format
@@ -39480,9 +39554,9 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
39480
39554
  if (!rule.operator || !rule.formula || rule.formula.length === 0)
39481
39555
  continue;
39482
39556
  operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
39483
- values.push(prefixFormula(rule.formula[0]));
39557
+ values.push(prefixFormulaWithEqual(rule.formula[0]));
39484
39558
  if (rule.formula.length === 2) {
39485
- values.push(prefixFormula(rule.formula[1]));
39559
+ values.push(prefixFormulaWithEqual(rule.formula[1]));
39486
39560
  }
39487
39561
  break;
39488
39562
  }
@@ -39640,11 +39714,6 @@ function convertIcons(xlsxIconSet, index) {
39640
39714
  ? ICON_SETS[iconSet].neutral
39641
39715
  : ICON_SETS[iconSet].good;
39642
39716
  }
39643
- /** Prefix the string by "=" if the string looks like a formula */
39644
- function prefixFormula(formula) {
39645
- const tokens = tokenize(formula);
39646
- return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
39647
- }
39648
39717
  // ---------------------------------------------------------------------------
39649
39718
  // Warnings
39650
39719
  // ---------------------------------------------------------------------------
@@ -40120,7 +40189,7 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40120
40189
  dvRules.push(decimalRule);
40121
40190
  break;
40122
40191
  case "list":
40123
- const listRule = convertListrule(dvId++, dv);
40192
+ const listRule = convertListRule(dvId++, dv);
40124
40193
  dvRules.push(listRule);
40125
40194
  break;
40126
40195
  case "date":
@@ -40140,9 +40209,9 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40140
40209
  return dvRules;
40141
40210
  }
40142
40211
  function convertDecimalRule(id, dv) {
40143
- const values = [dv.formula1.toString()];
40212
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40144
40213
  if (dv.formula2) {
40145
- values.push(dv.formula2.toString());
40214
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40146
40215
  }
40147
40216
  return {
40148
40217
  id: id.toString(),
@@ -40154,7 +40223,7 @@ function convertDecimalRule(id, dv) {
40154
40223
  },
40155
40224
  };
40156
40225
  }
40157
- function convertListrule(id, dv) {
40226
+ function convertListRule(id, dv) {
40158
40227
  const formula1 = dv.formula1.toString();
40159
40228
  const isRangeRule = rangeReference.test(formula1);
40160
40229
  return {
@@ -40170,9 +40239,9 @@ function convertListrule(id, dv) {
40170
40239
  }
40171
40240
  function convertDateRule(id, dv) {
40172
40241
  let criterion;
40173
- const values = [dv.formula1.toString()];
40242
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40174
40243
  if (dv.formula2) {
40175
- values.push(dv.formula2.toString());
40244
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40176
40245
  criterion = {
40177
40246
  type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
40178
40247
  values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
@@ -40199,7 +40268,7 @@ function convertCustomRule(id, dv) {
40199
40268
  isBlocking: dv.errorStyle !== "warning",
40200
40269
  criterion: {
40201
40270
  type: "customFormula",
40202
- values: [`=${dv.formula1.toString()}`],
40271
+ values: [prefixFormulaWithEqual(dv.formula1.toString())],
40203
40272
  },
40204
40273
  };
40205
40274
  }
@@ -45973,6 +46042,18 @@ class SpreadsheetPivotTable {
45973
46042
  get numberOfCells() {
45974
46043
  return this.rows.length * this.getNumberOfDataColumns();
45975
46044
  }
46045
+ getColumnDomainsAtDepth(depth) {
46046
+ if (depth < 0 || depth >= this.columns.length - 1) {
46047
+ return [];
46048
+ }
46049
+ return this.columns[depth].map((col) => this.getDomain(col)).filter((d) => d.length);
46050
+ }
46051
+ getRowDomainsAtDepth(depth) {
46052
+ if (depth < 0 || depth > this.maxIndent) {
46053
+ return [];
46054
+ }
46055
+ return this.rows.filter((row) => row.indent === depth + 1).map((row) => this.getDomain(row));
46056
+ }
45976
46057
  }
45977
46058
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
45978
46059
 
@@ -46965,6 +47046,109 @@ const ungroupPivotHeadersAction = {
46965
47046
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
46966
47047
  },
46967
47048
  };
47049
+ const toggleCollapsePivotGroupAction = {
47050
+ name: (env) => {
47051
+ const position = env.model.getters.getActivePosition();
47052
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47053
+ if (pivotCellState.isPivotGroup) {
47054
+ return pivotCellState.isCollapsed ? _t("Expand") : _t("Collapse");
47055
+ }
47056
+ return "";
47057
+ },
47058
+ execute(env) {
47059
+ const position = env.model.getters.getActivePosition();
47060
+ togglePivotCollapse(position, env);
47061
+ },
47062
+ isVisible: (env) => {
47063
+ const position = env.model.getters.getActivePosition();
47064
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47065
+ return pivotCellState.isPivotGroup;
47066
+ },
47067
+ };
47068
+ const collapseAllPivotGroupAction = {
47069
+ name: _t("Collapse all"),
47070
+ execute(env) {
47071
+ const position = env.model.getters.getActivePosition();
47072
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47073
+ if (!pivotCellState.isPivotGroup) {
47074
+ return;
47075
+ }
47076
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47077
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47078
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47079
+ const newCollapsed = [
47080
+ ...(definition.collapsedDomains[pivotCell.dimension] || []),
47081
+ ...siblingDomains,
47082
+ ];
47083
+ const filteredCollapsed = newCollapsed.filter((domain, index) => index === newCollapsed.findIndex((d) => deepEquals(d, domain)));
47084
+ definition.collapsedDomains[pivotCell.dimension] = filteredCollapsed;
47085
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47086
+ },
47087
+ isVisible: (env) => {
47088
+ const position = env.model.getters.getActivePosition();
47089
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47090
+ if (!pivotCellState.isPivotGroup) {
47091
+ return false;
47092
+ }
47093
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47094
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47095
+ return !siblingDomains.every((domain) => (definition.collapsedDomains?.[pivotCell.dimension] || []).some((d) => deepEquals(d, domain)));
47096
+ },
47097
+ };
47098
+ const expandAllPivotGroupAction = {
47099
+ name: _t("Expand all"),
47100
+ execute(env) {
47101
+ const position = env.model.getters.getActivePosition();
47102
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47103
+ if (!pivotCellState.isPivotGroup) {
47104
+ return;
47105
+ }
47106
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47107
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47108
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47109
+ const domains = definition.collapsedDomains[pivotCell.dimension] || [];
47110
+ const filteredDomains = domains.filter((domain) => !siblingDomains.find((d) => deepEquals(d, domain)));
47111
+ definition.collapsedDomains[pivotCell.dimension] = filteredDomains;
47112
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47113
+ },
47114
+ isVisible: (env) => {
47115
+ const position = env.model.getters.getActivePosition();
47116
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47117
+ if (!pivotCellState.isPivotGroup) {
47118
+ return false;
47119
+ }
47120
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47121
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47122
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension] || [];
47123
+ return collapsedDomains.some((domain) => siblingDomains.some((d) => deepEquals(d, domain)));
47124
+ },
47125
+ };
47126
+ function getPivotCellCollapseState(getters, position) {
47127
+ if (!getters.isSpillPivotFormula(position)) {
47128
+ return { isPivotGroup: false };
47129
+ }
47130
+ const pivotCell = getters.getPivotCellFromPosition(position);
47131
+ const pivotId = getters.getPivotIdFromPosition(position);
47132
+ if (pivotCell.type !== "HEADER" || !pivotId || !pivotCell.domain.length) {
47133
+ return { isPivotGroup: false };
47134
+ }
47135
+ const definition = getters.getPivotCoreDefinition(pivotId);
47136
+ const isDashboard = getters.isDashboard();
47137
+ const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
47138
+ const hasIcon = !isDashboard && pivotCell.domain.length !== fields.length;
47139
+ if (!hasIcon) {
47140
+ return { isPivotGroup: false };
47141
+ }
47142
+ const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
47143
+ const isCollapsed = domains.some((domain) => deepEquals(domain, pivotCell.domain));
47144
+ const pivot = getters.getPivot(pivotId);
47145
+ const table = pivot.getExpandedTableStructure();
47146
+ const depth = pivotCell.domain.length - 1;
47147
+ const siblingDomains = pivotCell.dimension === "ROW"
47148
+ ? table.getRowDomainsAtDepth(depth)
47149
+ : table.getColumnDomainsAtDepth(depth);
47150
+ return { isPivotGroup: true, isCollapsed, pivotCell, pivotId, siblingDomains };
47151
+ }
46968
47152
  function canSortPivot(getters, position) {
46969
47153
  const pivotId = getters.getPivotIdFromPosition(position);
46970
47154
  if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
@@ -47300,6 +47484,23 @@ cellMenuRegistry
47300
47484
  sequence: 155,
47301
47485
  icon: "o-spreadsheet-Icon.MINUS_IN_BOX",
47302
47486
  ...ungroupPivotHeadersAction,
47487
+ })
47488
+ .add("collapse_pivot", {
47489
+ sequence: 156,
47490
+ name: _t("Expand/Collapse"),
47491
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47492
+ })
47493
+ .addChild("toggle_collapse_pivot_cell", ["collapse_pivot"], {
47494
+ sequence: 10,
47495
+ ...toggleCollapsePivotGroupAction,
47496
+ })
47497
+ .addChild("collapse_all_pivot", ["collapse_pivot"], {
47498
+ sequence: 20,
47499
+ ...collapseAllPivotGroupAction,
47500
+ })
47501
+ .addChild("expand_all_pivot", ["collapse_pivot"], {
47502
+ sequence: 30,
47503
+ ...expandAllPivotGroupAction,
47303
47504
  })
47304
47505
  .add("pivot_sorting", {
47305
47506
  name: _t("Sort pivot"),
@@ -47924,6 +48125,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47924
48125
  execute: (env) => {
47925
48126
  env.model.dispatch("UNFOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47926
48127
  },
48128
+ icon: "o-spreadsheet-Icon.EXPAND",
47927
48129
  },
47928
48130
  {
47929
48131
  id: "fold_all",
@@ -47931,6 +48133,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47931
48133
  execute: (env) => {
47932
48134
  env.model.dispatch("FOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47933
48135
  },
48136
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47934
48137
  },
47935
48138
  ]);
47936
48139
  }
@@ -47952,6 +48155,11 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47952
48155
  const sheetId = env.model.getters.getActiveSheetId();
47953
48156
  interactiveToggleGroup(env, sheetId, dimension, start, end);
47954
48157
  },
48158
+ icon: (env) => {
48159
+ const sheetId = env.model.getters.getActiveSheetId();
48160
+ const groupIsFolded = env.model.getters.isGroupFolded(sheetId, dimension, start, end);
48161
+ return groupIsFolded ? "o-spreadsheet-Icon.EXPAND" : "o-spreadsheet-Icon.COLLAPSE";
48162
+ },
47955
48163
  },
47956
48164
  {
47957
48165
  id: "remove_group",
@@ -47960,6 +48168,7 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47960
48168
  const sheetId = env.model.getters.getActiveSheetId();
47961
48169
  env.model.dispatch("UNGROUP_HEADERS", { sheetId, dimension, start, end });
47962
48170
  },
48171
+ icon: "o-spreadsheet-Icon.TRASH",
47963
48172
  separator: true,
47964
48173
  },
47965
48174
  ]);
@@ -48822,39 +49031,66 @@ class CellComposerStore extends AbstractComposerStore {
48822
49031
  this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
48823
49032
  this.setContent("");
48824
49033
  }
48825
- getComposerContent(position) {
49034
+ getComposerContent(position, selection) {
48826
49035
  const locale = this.getters.getLocale();
48827
49036
  const cell = this.getters.getCell(position);
48828
49037
  if (cell?.isFormula) {
48829
49038
  const prettifiedContent = this.getPrettifiedFormula(cell);
48830
- return localizeFormula(prettifiedContent, locale);
49039
+ // when a formula is prettified (multi lines, indented), adapt the cursor position
49040
+ // to take into account line breaks and tabs
49041
+ function adjustCursorIndex(targetIndex) {
49042
+ let adjustedIndex = 0;
49043
+ let originalIndex = 0;
49044
+ while (originalIndex < targetIndex) {
49045
+ adjustedIndex++;
49046
+ const char = prettifiedContent[adjustedIndex];
49047
+ if (char !== "\n" && char !== "\t") {
49048
+ originalIndex++;
49049
+ }
49050
+ }
49051
+ return adjustedIndex;
49052
+ }
49053
+ let adjustedSelection = selection;
49054
+ if (selection) {
49055
+ adjustedSelection = {
49056
+ start: adjustCursorIndex(selection.start),
49057
+ end: adjustCursorIndex(selection.end),
49058
+ };
49059
+ }
49060
+ return {
49061
+ text: localizeFormula(prettifiedContent, locale),
49062
+ adjustedSelection,
49063
+ };
48831
49064
  }
48832
49065
  const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
48833
49066
  if (spreader) {
48834
- return "";
49067
+ return { text: "" };
49068
+ }
49069
+ if (cell?.content.startsWith("'")) {
49070
+ return { text: cell.content };
48835
49071
  }
48836
49072
  const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
48837
49073
  switch (type) {
48838
49074
  case CellValueType.empty:
48839
- return "";
49075
+ return { text: "" };
48840
49076
  case CellValueType.text:
48841
49077
  case CellValueType.error:
48842
- return value;
49078
+ return { text: value };
48843
49079
  case CellValueType.boolean:
48844
- return formattedValue;
49080
+ return { text: formattedValue };
48845
49081
  case CellValueType.number:
48846
49082
  if (format && isDateTimeFormat(format)) {
48847
49083
  if (parseDateTime(formattedValue, locale) !== null) {
48848
49084
  // formatted string can be parsed again
48849
- return formattedValue;
49085
+ return { text: formattedValue };
48850
49086
  }
48851
49087
  // display a simplified and parsable string otherwise
48852
49088
  const timeFormat = Number.isInteger(value)
48853
49089
  ? locale.dateFormat
48854
49090
  : getDateTimeFormat(locale);
48855
- return formatValue(value, { locale, format: timeFormat });
49091
+ return { text: formatValue(value, { locale, format: timeFormat }) };
48856
49092
  }
48857
- return this.numberComposerContent(value, format, locale);
49093
+ return { text: this.numberComposerContent(value, format, locale) };
48858
49094
  }
48859
49095
  }
48860
49096
  getPrettifiedFormula(cell) {
@@ -49001,8 +49237,9 @@ class GridComposer extends owl.Component {
49001
49237
  },
49002
49238
  focus: this.focus,
49003
49239
  isDefaultFocus: true,
49004
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
49240
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
49005
49241
  focusMode: "contentFocus",
49242
+ selection,
49006
49243
  }),
49007
49244
  onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
49008
49245
  focusMode: "cellFocus",
@@ -55690,12 +55927,13 @@ class DataValidationEditor extends owl.Component {
55690
55927
  onCloseSidePanel: { type: Function, optional: true },
55691
55928
  };
55692
55929
  state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
55930
+ editingSheetId;
55693
55931
  setup() {
55932
+ this.editingSheetId = this.env.model.getters.getActiveSheetId();
55694
55933
  if (this.props.rule) {
55695
- const sheetId = this.env.model.getters.getActiveSheetId();
55696
55934
  this.state.rule = {
55697
55935
  ...this.props.rule,
55698
- ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, sheetId)),
55936
+ ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
55699
55937
  };
55700
55938
  this.state.rule.criterion.type = this.props.rule.criterion.type;
55701
55939
  }
@@ -55729,7 +55967,6 @@ class DataValidationEditor extends owl.Component {
55729
55967
  const locale = this.env.model.getters.getLocale();
55730
55968
  const criterion = rule.criterion;
55731
55969
  const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
55732
- const sheetId = this.env.model.getters.getActiveSheetId();
55733
55970
  const values = criterion.values
55734
55971
  .slice(0, criterionEvaluator.numberOfValues(criterion))
55735
55972
  .map((value) => value?.trim())
@@ -55737,8 +55974,8 @@ class DataValidationEditor extends owl.Component {
55737
55974
  .map((value) => canonicalizeContent(value, locale));
55738
55975
  rule.criterion = { ...criterion, values };
55739
55976
  return {
55740
- sheetId,
55741
- ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
55977
+ sheetId: this.editingSheetId,
55978
+ ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
55742
55979
  rule,
55743
55980
  };
55744
55981
  }
@@ -60848,7 +61085,7 @@ class CellPlugin extends CorePlugin {
60848
61085
  (typeof parsedValue === "number"
60849
61086
  ? detectDateFormat(content, locale) || detectNumberFormat(content)
60850
61087
  : undefined);
60851
- if (!isTextFormat(format) && !isEvaluationError(content)) {
61088
+ if (!isTextFormat(format) && !content.startsWith("'") && !isEvaluationError(content)) {
60852
61089
  content = toString(parsedValue);
60853
61090
  }
60854
61091
  return {
@@ -66746,7 +66983,7 @@ class FormulaDependencyGraph {
66746
66983
  * in the correct order they should be evaluated.
66747
66984
  * This is called a topological ordering (excluding cycles)
66748
66985
  */
66749
- getCellsDependingOn(ranges) {
66986
+ getCellsDependingOn(ranges, ignore) {
66750
66987
  const visited = this.createEmptyPositionSet();
66751
66988
  const queue = Array.from(ranges).reverse();
66752
66989
  while (queue.length > 0) {
@@ -66761,7 +66998,7 @@ class FormulaDependencyGraph {
66761
66998
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
66762
66999
  const nextInQueue = {};
66763
67000
  for (const position of impactedPositions) {
66764
- if (!visited.has(position)) {
67001
+ if (!visited.has(position) && !ignore.has(position)) {
66765
67002
  if (!nextInQueue[position.sheetId]) {
66766
67003
  nextInQueue[position.sheetId] = [];
66767
67004
  }
@@ -67319,7 +67556,7 @@ class Evaluator {
67319
67556
  }
67320
67557
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
67321
67558
  // the result matrix is split in 2 zones to exclude the array formula position
67322
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
67559
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
67323
67560
  invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
67324
67561
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
67325
67562
  }
@@ -67437,7 +67674,7 @@ class Evaluator {
67437
67674
  for (const sheetId in zonesBySheetIds) {
67438
67675
  ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
67439
67676
  }
67440
- return this.formulaDependencies().getCellsDependingOn(ranges);
67677
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
67441
67678
  }
67442
67679
  }
67443
67680
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -68704,32 +68941,6 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
68704
68941
  }
68705
68942
  return undefined;
68706
68943
  });
68707
- function togglePivotCollapse(position, env) {
68708
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
68709
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
68710
- if (!pivotId || pivotCell.type !== "HEADER") {
68711
- return;
68712
- }
68713
- const definition = env.model.getters.getPivotCoreDefinition(pivotId);
68714
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
68715
- ? [...definition.collapsedDomains[pivotCell.dimension]]
68716
- : [];
68717
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
68718
- if (index !== -1) {
68719
- collapsedDomains.splice(index, 1);
68720
- }
68721
- else {
68722
- collapsedDomains.push(pivotCell.domain);
68723
- }
68724
- const newDomains = definition.collapsedDomains
68725
- ? { ...definition.collapsedDomains }
68726
- : { COL: [], ROW: [] };
68727
- newDomains[pivotCell.dimension] = collapsedDomains;
68728
- env.model.dispatch("UPDATE_PIVOT", {
68729
- pivotId,
68730
- pivot: { ...definition, collapsedDomains: newDomains },
68731
- });
68732
- }
68733
68944
  iconsOnCellRegistry.add("pivot_dashboard_sorting", (getters, position) => {
68734
68945
  if (!getters.isDashboard()) {
68735
68946
  return undefined;
@@ -68948,7 +69159,8 @@ class DynamicTablesPlugin extends CoreViewPlugin {
68948
69159
  const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
68949
69160
  const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
68950
69161
  if (!parentSpreadingCell) {
68951
- return false;
69162
+ const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
69163
+ return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
68952
69164
  }
68953
69165
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
68954
69166
  return true;
@@ -79814,6 +80026,7 @@ class RibbonMenu extends owl.Component {
79814
80026
  static components = { Menu };
79815
80027
  rootItems = topbarMenuRegistry.getMenuItems();
79816
80028
  menuRef = owl.useRef("menu");
80029
+ containerRef = owl.useRef("container");
79817
80030
  state = owl.useState({
79818
80031
  menuItems: this.rootItems,
79819
80032
  title: _t("Menu Bar"),
@@ -79821,6 +80034,7 @@ class RibbonMenu extends owl.Component {
79821
80034
  });
79822
80035
  setup() {
79823
80036
  owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
80037
+ owl.onMounted(this.updateShadows);
79824
80038
  }
79825
80039
  onExternalClick(ev) {
79826
80040
  if (!this.menuRef.el?.contains(ev.target)) {
@@ -79833,6 +80047,7 @@ class RibbonMenu extends owl.Component {
79833
80047
  this.state.parentState = { ...this.state };
79834
80048
  this.state.menuItems = children;
79835
80049
  this.state.title = menu.name(this.env);
80050
+ this.containerRef.el?.scrollTo({ top: 0 });
79836
80051
  }
79837
80052
  else {
79838
80053
  this.state.menuItems = this.rootItems;
@@ -79854,6 +80069,19 @@ class RibbonMenu extends owl.Component {
79854
80069
  height: `${this.props.height}px`,
79855
80070
  });
79856
80071
  }
80072
+ updateShadows() {
80073
+ if (!this.containerRef.el) {
80074
+ return;
80075
+ }
80076
+ this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
80077
+ const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
80078
+ if (this.containerRef.el.scrollTop < maxScroll - 1) {
80079
+ this.containerRef.el.classList.add("scroll-bottom");
80080
+ }
80081
+ if (this.containerRef.el.scrollTop > 0) {
80082
+ this.containerRef.el.classList.add("scroll-top");
80083
+ }
80084
+ }
79857
80085
  onClickBack() {
79858
80086
  if (!this.state.parentState) {
79859
80087
  this.props.onClose();
@@ -79862,6 +80090,7 @@ class RibbonMenu extends owl.Component {
79862
80090
  this.state.menuItems = this.state.parentState.menuItems;
79863
80091
  this.state.title = this.state.parentState.title;
79864
80092
  this.state.parentState = this.state.parentState.parentState;
80093
+ this.containerRef.el?.scrollTo({ top: 0 });
79865
80094
  }
79866
80095
  get backTitle() {
79867
80096
  return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
@@ -79913,7 +80142,9 @@ class SmallBottomBar extends owl.Component {
79913
80142
  : "inactive";
79914
80143
  }
79915
80144
  get showFxIcon() {
79916
- return this.focus === "inactive" && !this.composerStore.currentContent;
80145
+ return (this.focus === "inactive" &&
80146
+ !this.composerStore.currentContent &&
80147
+ !this.composerStore.placeholder);
79917
80148
  }
79918
80149
  get rect() {
79919
80150
  return this.composerRef.el
@@ -79930,8 +80161,9 @@ class SmallBottomBar extends owl.Component {
79930
80161
  },
79931
80162
  focus: this.focus,
79932
80163
  composerStore: this.composerStore,
79933
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
80164
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
79934
80165
  focusMode: "contentFocus",
80166
+ selection,
79935
80167
  }),
79936
80168
  isDefaultFocus: false,
79937
80169
  inputStyle: cssPropertiesToCss({
@@ -79939,6 +80171,7 @@ class SmallBottomBar extends owl.Component {
79939
80171
  "max-height": `130px`,
79940
80172
  }),
79941
80173
  showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
80174
+ placeholder: this.composerStore.placeholder,
79942
80175
  };
79943
80176
  }
79944
80177
  get symbols() {
@@ -79984,7 +80217,9 @@ class TopBarComposer extends owl.Component {
79984
80217
  : "inactive";
79985
80218
  }
79986
80219
  get showFxIcon() {
79987
- return this.focus === "inactive" && !this.composerStore.currentContent;
80220
+ return (this.focus === "inactive" &&
80221
+ !this.composerStore.currentContent &&
80222
+ !this.composerStore.placeholder);
79988
80223
  }
79989
80224
  get composerStyle() {
79990
80225
  const style = {
@@ -85786,6 +86021,6 @@ exports.tokenColors = tokenColors;
85786
86021
  exports.tokenize = tokenize;
85787
86022
 
85788
86023
 
85789
- __info__.version = "19.1.0-alpha.4";
85790
- __info__.date = "2025-10-07T10:03:20.917Z";
85791
- __info__.hash = "b7cfed8";
86024
+ __info__.version = "19.1.0-alpha.5";
86025
+ __info__.date = "2025-10-16T06:39:55.925Z";
86026
+ __info__.hash = "1a0e3d5";