@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
  import { useEnv, useSubEnv, onWillUnmount, useComponent, status, Component, useRef, onMounted, useEffect, App, blockDom, useState, onPatched, useExternalListener, onWillUpdateProps, onWillStart, onWillPatch, xml, useChildSubEnv, markRaw, toRaw } from '@odoo/owl';
@@ -4450,7 +4450,17 @@ function toNumberMatrix(data, argName) {
4450
4450
  return toMatrix(data).map((row) => {
4451
4451
  return row.map((cell) => {
4452
4452
  if (typeof cell.value !== "number") {
4453
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects number values for %s, but got a %s.", argName, typeof cell.value));
4453
+ let message = "";
4454
+ if (typeof cell === "object") {
4455
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
4456
+ }
4457
+ else if (typeof cell === "string") {
4458
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
4459
+ }
4460
+ else if (typeof cell === "boolean") {
4461
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
4462
+ }
4463
+ throw new EvaluationError(message);
4454
4464
  }
4455
4465
  return cell.value;
4456
4466
  });
@@ -6388,6 +6398,9 @@ function parseLiteral(content, locale) {
6388
6398
  if (content.startsWith("=")) {
6389
6399
  throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
6390
6400
  }
6401
+ if (content.startsWith("'")) {
6402
+ return content.slice(1);
6403
+ }
6391
6404
  if (content === "") {
6392
6405
  return null;
6393
6406
  }
@@ -9337,6 +9350,32 @@ function getCustomFieldWithParentField(definition, parentField, fields) {
9337
9350
  groups: [],
9338
9351
  });
9339
9352
  }
9353
+ function togglePivotCollapse(position, env) {
9354
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
9355
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
9356
+ if (!pivotId || pivotCell.type !== "HEADER") {
9357
+ return;
9358
+ }
9359
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
9360
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
9361
+ ? [...definition.collapsedDomains[pivotCell.dimension]]
9362
+ : [];
9363
+ const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
9364
+ if (index !== -1) {
9365
+ collapsedDomains.splice(index, 1);
9366
+ }
9367
+ else {
9368
+ collapsedDomains.push(pivotCell.domain);
9369
+ }
9370
+ const newDomains = definition.collapsedDomains
9371
+ ? { ...definition.collapsedDomains }
9372
+ : { COL: [], ROW: [] };
9373
+ newDomains[pivotCell.dimension] = collapsedDomains;
9374
+ env.model.dispatch("UPDATE_PIVOT", {
9375
+ pivotId,
9376
+ pivot: { ...definition, collapsedDomains: newDomains },
9377
+ });
9378
+ }
9340
9379
 
9341
9380
  class CellClipboardHandler extends AbstractCellClipboardHandler {
9342
9381
  isCutAllowed(data) {
@@ -9510,7 +9549,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
9510
9549
  pasteCell(origin, target, clipboardOption) {
9511
9550
  const { sheetId, col, row } = target;
9512
9551
  const targetCell = this.getters.getEvaluatedCell(target);
9513
- const originFormat = origin?.format ?? origin.evaluatedCell.format;
9552
+ const originFormat = origin?.format || origin.evaluatedCell.format;
9514
9553
  if (clipboardOption?.pasteOption === "asValue") {
9515
9554
  this.dispatch("UPDATE_CELL", {
9516
9555
  ...target,
@@ -13736,7 +13775,7 @@ const GROWTH = {
13736
13775
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13737
13776
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13738
13777
  }
13739
- 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)));
13778
+ return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
13740
13779
  },
13741
13780
  };
13742
13781
  // -----------------------------------------------------------------------------
@@ -13809,7 +13848,7 @@ const LINEST = {
13809
13848
  if (dataY.length === 0 || dataY[0].length === 0) {
13810
13849
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13811
13850
  }
13812
- return fullLinearRegression(toNumberMatrix(dataX, "the first argument (data_y)"), toNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
13851
+ return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
13813
13852
  },
13814
13853
  isExported: true,
13815
13854
  };
@@ -13828,7 +13867,7 @@ const LOGEST = {
13828
13867
  if (dataY.length === 0 || dataY[0].length === 0) {
13829
13868
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13830
13869
  }
13831
- const coeffs = fullLinearRegression(toNumberMatrix(dataX, "the second argument (data_x)"), logM(toNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
13870
+ const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
13832
13871
  for (let i = 0; i < coeffs.length; i++) {
13833
13872
  coeffs[i][0] = Math.exp(coeffs[i][0]);
13834
13873
  }
@@ -14449,7 +14488,7 @@ const TREND = {
14449
14488
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
14450
14489
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
14451
14490
  }
14452
- 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));
14491
+ return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
14453
14492
  },
14454
14493
  };
14455
14494
  // -----------------------------------------------------------------------------
@@ -22970,6 +23009,10 @@ const chartShowValuesPlugin = {
22970
23009
  }
22971
23010
  const ctx = chart.ctx;
22972
23011
  ctx.save();
23012
+ const { left, top, height, width } = chart.chartArea;
23013
+ ctx.beginPath();
23014
+ ctx.rect(left, top, width, height);
23015
+ ctx.clip();
22973
23016
  ctx.textAlign = "center";
22974
23017
  ctx.textBaseline = "middle";
22975
23018
  ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
@@ -24004,7 +24047,7 @@ class ChartJsComponent extends Component {
24004
24047
  this.chart.update();
24005
24048
  }
24006
24049
  hasChartDataChanged() {
24007
- return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
24050
+ return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
24008
24051
  }
24009
24052
  enableAnimationInChartData(chartData) {
24010
24053
  return {
@@ -24012,6 +24055,17 @@ class ChartJsComponent extends Component {
24012
24055
  options: { ...chartData.options, animation: { animateRotate: true } },
24013
24056
  };
24014
24057
  }
24058
+ getChartDataInRuntime(runtime) {
24059
+ const data = runtime.chartJsConfig.data;
24060
+ return {
24061
+ labels: data.labels,
24062
+ dataset: data.datasets.map((dataset) => ({
24063
+ data: dataset.data,
24064
+ label: dataset.label,
24065
+ tree: dataset.tree,
24066
+ })),
24067
+ };
24068
+ }
24015
24069
  get animationChartId() {
24016
24070
  return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
24017
24071
  }
@@ -25435,6 +25489,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
25435
25489
  parser: luxonFormat,
25436
25490
  displayFormats,
25437
25491
  unit: timeUnit ?? false,
25492
+ tooltipFormat: luxonFormat,
25438
25493
  };
25439
25494
  }
25440
25495
  /**
@@ -26503,6 +26558,7 @@ function getLineChartScales(definition, args) {
26503
26558
  };
26504
26559
  Object.assign(scales.x, axis);
26505
26560
  scales.x.ticks.maxTicksLimit = 15;
26561
+ delete scales?.x?.ticks?.callback;
26506
26562
  }
26507
26563
  else if (axisType === "linear") {
26508
26564
  scales.x.type = "linear";
@@ -31581,7 +31637,8 @@ class Menu extends Component {
31581
31637
  const menuItemsAndSeparators = [];
31582
31638
  for (let i = 0; i < this.props.menuItems.length; i++) {
31583
31639
  const menuItem = this.props.menuItems[i];
31584
- if (menuItem.isVisible(this.env)) {
31640
+ if (menuItem.isVisible(this.env) &&
31641
+ (!this.isRoot(menuItem) || this.hasVisibleChildren(menuItem))) {
31585
31642
  menuItemsAndSeparators.push(menuItem);
31586
31643
  }
31587
31644
  if (menuItem.separator &&
@@ -31623,6 +31680,9 @@ class Menu extends Component {
31623
31680
  isRoot(menu) {
31624
31681
  return !menu.execute;
31625
31682
  }
31683
+ hasVisibleChildren(menu) {
31684
+ return menu.children(this.env).some((child) => child.isVisible(this.env));
31685
+ }
31626
31686
  isEnabled(menu) {
31627
31687
  if (menu.isEnabled(this.env)) {
31628
31688
  return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
@@ -32181,7 +32241,7 @@ class ChartDashboardMenu extends Component {
32181
32241
  }
32182
32242
  openContextMenu(ev) {
32183
32243
  this.menuState.isOpen = true;
32184
- this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
32244
+ this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
32185
32245
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32186
32246
  this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
32187
32247
  }
@@ -32213,6 +32273,7 @@ class CarouselFigure extends Component {
32213
32273
  onFigureDeleted: Function,
32214
32274
  editFigureStyle: { type: Function, optional: true },
32215
32275
  isFullScreen: { type: Boolean, optional: true },
32276
+ openContextMenu: { type: Function, optional: true },
32216
32277
  };
32217
32278
  static components = { ChartDashboardMenu, MenuPopover };
32218
32279
  carouselTabsRef = useRef("carouselTabs");
@@ -32346,6 +32407,12 @@ class CarouselFigure extends Component {
32346
32407
  get visibleCarouselItems() {
32347
32408
  return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
32348
32409
  }
32410
+ openContextMenu(event) {
32411
+ const target = event.currentTarget;
32412
+ if (target) {
32413
+ this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
32414
+ }
32415
+ }
32349
32416
  }
32350
32417
 
32351
32418
  class ChartFigure extends Component {
@@ -32355,6 +32422,7 @@ class ChartFigure extends Component {
32355
32422
  onFigureDeleted: Function,
32356
32423
  editFigureStyle: { type: Function, optional: true },
32357
32424
  isFullScreen: { type: Boolean, optional: true },
32425
+ openContextMenu: { type: Function, optional: true },
32358
32426
  };
32359
32427
  static components = { ChartDashboardMenu };
32360
32428
  onDoubleClick() {
@@ -32387,6 +32455,7 @@ class ImageFigure extends Component {
32387
32455
  figureUI: Object,
32388
32456
  onFigureDeleted: Function,
32389
32457
  editFigureStyle: { type: Function, optional: true },
32458
+ openContextMenu: { type: Function, optional: true },
32390
32459
  };
32391
32460
  static components = {};
32392
32461
  // ---------------------------------------------------------------------------
@@ -34189,8 +34258,11 @@ class Composer extends Component {
34189
34258
  }
34190
34259
  const newSelection = this.contentHelper.getCurrentSelection();
34191
34260
  this.props.composerStore.stopComposerRangeSelection();
34192
- this.props.onComposerContentFocused();
34193
- this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34261
+ const isCurrentlyInactive = this.props.composerStore.editionMode === "inactive";
34262
+ this.props.onComposerContentFocused(newSelection);
34263
+ if (!isCurrentlyInactive) {
34264
+ this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34265
+ }
34194
34266
  this.processTokenAtCursor();
34195
34267
  }
34196
34268
  onDblClick() {
@@ -34685,13 +34757,6 @@ class AbstractComposerStore extends SpreadsheetStore {
34685
34757
  }
34686
34758
  }
34687
34759
  startEdition(text, selection) {
34688
- if (selection) {
34689
- const content = text || this.getComposerContent(this.getters.getActivePosition());
34690
- const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
34691
- if (!validSelection) {
34692
- return;
34693
- }
34694
- }
34695
34760
  const { col, row } = this.getters.getActivePosition();
34696
34761
  this.model.dispatch("SELECT_FIGURE", { figureId: null });
34697
34762
  this.model.dispatch("SCROLL_TO_CELL", { col, row });
@@ -34748,7 +34813,7 @@ class AbstractComposerStore extends SpreadsheetStore {
34748
34813
  // ---------------------------------------------------------------------------
34749
34814
  get currentContent() {
34750
34815
  if (this.editionMode === "inactive") {
34751
- return this.getComposerContent(this.getters.getActivePosition());
34816
+ return this.getComposerContent(this.getters.getActivePosition()).text;
34752
34817
  }
34753
34818
  return this._currentContent;
34754
34819
  }
@@ -34947,8 +35012,9 @@ class AbstractComposerStore extends SpreadsheetStore {
34947
35012
  this.sheetId = sheetId;
34948
35013
  this.row = row;
34949
35014
  this.editionMode = "editing";
34950
- this.initialContent = this.getComposerContent({ sheetId, col, row });
34951
- this.setContent(str || this.initialContent, selection);
35015
+ const { text, adjustedSelection } = this.getComposerContent({ sheetId, col, row }, selection);
35016
+ this.initialContent = text;
35017
+ this.setContent(str || this.initialContent, adjustedSelection ?? selection);
34952
35018
  this.colorIndexByRange = {};
34953
35019
  const zone = positionToZone({ col: this.col, row: this.row });
34954
35020
  this.captureSelection(zone, col, row);
@@ -35425,7 +35491,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35425
35491
  constructor(get, args) {
35426
35492
  super(get);
35427
35493
  this.args = args;
35428
- this._currentContent = this.getComposerContent();
35494
+ this._currentContent = this.getComposerContent().text;
35429
35495
  }
35430
35496
  getAutoCompleteProviders() {
35431
35497
  const providersDefinitions = super.getAutoCompleteProviders();
@@ -35462,7 +35528,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35462
35528
  })
35463
35529
  .join("");
35464
35530
  }
35465
- return localizeContent(content, this.getters.getLocale());
35531
+ return { text: localizeContent(content, this.getters.getLocale()) };
35466
35532
  }
35467
35533
  stopEdition() {
35468
35534
  this._stopEdition();
@@ -38682,6 +38748,74 @@ function getPath2D(svgPath) {
38682
38748
  return path2D;
38683
38749
  }
38684
38750
 
38751
+ /**
38752
+ * Get the relative path between two files
38753
+ *
38754
+ * Eg.:
38755
+ * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
38756
+ */
38757
+ function getRelativePath(from, to) {
38758
+ const fromPathParts = from.split("/");
38759
+ const toPathParts = to.split("/");
38760
+ let relPath = "";
38761
+ let startIndex = 0;
38762
+ for (let i = 0; i < fromPathParts.length - 1; i++) {
38763
+ if (fromPathParts[i] === toPathParts[i]) {
38764
+ startIndex++;
38765
+ }
38766
+ else {
38767
+ relPath += "../";
38768
+ }
38769
+ }
38770
+ relPath += toPathParts.slice(startIndex).join("/");
38771
+ return relPath;
38772
+ }
38773
+ /**
38774
+ * Convert an array of element into an object where the objects keys were the elements position in the array.
38775
+ * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
38776
+ *
38777
+ * eg. : ["a", "b"] => {0:"a", 1:"b"}
38778
+ */
38779
+ function arrayToObject(array, indexOffset = 0) {
38780
+ const obj = {};
38781
+ for (let i = 0; i < array.length; i++) {
38782
+ if (array[i]) {
38783
+ obj[i + indexOffset] = array[i];
38784
+ }
38785
+ }
38786
+ return obj;
38787
+ }
38788
+ /**
38789
+ * In xlsx we can have string with unicode characters with the format _x00fa_.
38790
+ * Replace with characters understandable by JS
38791
+ */
38792
+ function fixXlsxUnicode(str) {
38793
+ return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
38794
+ return String.fromCharCode(parseInt(code, 16));
38795
+ });
38796
+ }
38797
+ /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
38798
+ function getSheetDataHeader(sheetData, dimension, index) {
38799
+ if (dimension === "COL") {
38800
+ if (!sheetData.cols[index]) {
38801
+ sheetData.cols[index] = {};
38802
+ }
38803
+ return sheetData.cols[index];
38804
+ }
38805
+ if (!sheetData.rows[index]) {
38806
+ sheetData.rows[index] = {};
38807
+ }
38808
+ return sheetData.rows[index];
38809
+ }
38810
+ /** Prefix the string by "=" if the string looks like a formula */
38811
+ function prefixFormulaWithEqual(formula) {
38812
+ if (formula[0] === "=") {
38813
+ return formula;
38814
+ }
38815
+ const tokens = tokenize(formula);
38816
+ return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
38817
+ }
38818
+
38685
38819
  /**
38686
38820
  * Map of the different types of conversions warnings and their name in error messages
38687
38821
  */
@@ -39204,66 +39338,6 @@ function hexaToInt(hex) {
39204
39338
  */
39205
39339
  const DEFAULT_SYSTEM_COLOR = "FF000000";
39206
39340
 
39207
- /**
39208
- * Get the relative path between two files
39209
- *
39210
- * Eg.:
39211
- * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
39212
- */
39213
- function getRelativePath(from, to) {
39214
- const fromPathParts = from.split("/");
39215
- const toPathParts = to.split("/");
39216
- let relPath = "";
39217
- let startIndex = 0;
39218
- for (let i = 0; i < fromPathParts.length - 1; i++) {
39219
- if (fromPathParts[i] === toPathParts[i]) {
39220
- startIndex++;
39221
- }
39222
- else {
39223
- relPath += "../";
39224
- }
39225
- }
39226
- relPath += toPathParts.slice(startIndex).join("/");
39227
- return relPath;
39228
- }
39229
- /**
39230
- * Convert an array of element into an object where the objects keys were the elements position in the array.
39231
- * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
39232
- *
39233
- * eg. : ["a", "b"] => {0:"a", 1:"b"}
39234
- */
39235
- function arrayToObject(array, indexOffset = 0) {
39236
- const obj = {};
39237
- for (let i = 0; i < array.length; i++) {
39238
- if (array[i]) {
39239
- obj[i + indexOffset] = array[i];
39240
- }
39241
- }
39242
- return obj;
39243
- }
39244
- /**
39245
- * In xlsx we can have string with unicode characters with the format _x00fa_.
39246
- * Replace with characters understandable by JS
39247
- */
39248
- function fixXlsxUnicode(str) {
39249
- return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
39250
- return String.fromCharCode(parseInt(code, 16));
39251
- });
39252
- }
39253
- /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
39254
- function getSheetDataHeader(sheetData, dimension, index) {
39255
- if (dimension === "COL") {
39256
- if (!sheetData.cols[index]) {
39257
- sheetData.cols[index] = {};
39258
- }
39259
- return sheetData.cols[index];
39260
- }
39261
- if (!sheetData.rows[index]) {
39262
- sheetData.rows[index] = {};
39263
- }
39264
- return sheetData.rows[index];
39265
- }
39266
-
39267
39341
  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;
39268
39342
  /**
39269
39343
  * Convert excel format to o_spreadsheet format
@@ -39478,9 +39552,9 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
39478
39552
  if (!rule.operator || !rule.formula || rule.formula.length === 0)
39479
39553
  continue;
39480
39554
  operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
39481
- values.push(prefixFormula(rule.formula[0]));
39555
+ values.push(prefixFormulaWithEqual(rule.formula[0]));
39482
39556
  if (rule.formula.length === 2) {
39483
- values.push(prefixFormula(rule.formula[1]));
39557
+ values.push(prefixFormulaWithEqual(rule.formula[1]));
39484
39558
  }
39485
39559
  break;
39486
39560
  }
@@ -39638,11 +39712,6 @@ function convertIcons(xlsxIconSet, index) {
39638
39712
  ? ICON_SETS[iconSet].neutral
39639
39713
  : ICON_SETS[iconSet].good;
39640
39714
  }
39641
- /** Prefix the string by "=" if the string looks like a formula */
39642
- function prefixFormula(formula) {
39643
- const tokens = tokenize(formula);
39644
- return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
39645
- }
39646
39715
  // ---------------------------------------------------------------------------
39647
39716
  // Warnings
39648
39717
  // ---------------------------------------------------------------------------
@@ -40118,7 +40187,7 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40118
40187
  dvRules.push(decimalRule);
40119
40188
  break;
40120
40189
  case "list":
40121
- const listRule = convertListrule(dvId++, dv);
40190
+ const listRule = convertListRule(dvId++, dv);
40122
40191
  dvRules.push(listRule);
40123
40192
  break;
40124
40193
  case "date":
@@ -40138,9 +40207,9 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40138
40207
  return dvRules;
40139
40208
  }
40140
40209
  function convertDecimalRule(id, dv) {
40141
- const values = [dv.formula1.toString()];
40210
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40142
40211
  if (dv.formula2) {
40143
- values.push(dv.formula2.toString());
40212
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40144
40213
  }
40145
40214
  return {
40146
40215
  id: id.toString(),
@@ -40152,7 +40221,7 @@ function convertDecimalRule(id, dv) {
40152
40221
  },
40153
40222
  };
40154
40223
  }
40155
- function convertListrule(id, dv) {
40224
+ function convertListRule(id, dv) {
40156
40225
  const formula1 = dv.formula1.toString();
40157
40226
  const isRangeRule = rangeReference.test(formula1);
40158
40227
  return {
@@ -40168,9 +40237,9 @@ function convertListrule(id, dv) {
40168
40237
  }
40169
40238
  function convertDateRule(id, dv) {
40170
40239
  let criterion;
40171
- const values = [dv.formula1.toString()];
40240
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40172
40241
  if (dv.formula2) {
40173
- values.push(dv.formula2.toString());
40242
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40174
40243
  criterion = {
40175
40244
  type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
40176
40245
  values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
@@ -40197,7 +40266,7 @@ function convertCustomRule(id, dv) {
40197
40266
  isBlocking: dv.errorStyle !== "warning",
40198
40267
  criterion: {
40199
40268
  type: "customFormula",
40200
- values: [`=${dv.formula1.toString()}`],
40269
+ values: [prefixFormulaWithEqual(dv.formula1.toString())],
40201
40270
  },
40202
40271
  };
40203
40272
  }
@@ -45971,6 +46040,18 @@ class SpreadsheetPivotTable {
45971
46040
  get numberOfCells() {
45972
46041
  return this.rows.length * this.getNumberOfDataColumns();
45973
46042
  }
46043
+ getColumnDomainsAtDepth(depth) {
46044
+ if (depth < 0 || depth >= this.columns.length - 1) {
46045
+ return [];
46046
+ }
46047
+ return this.columns[depth].map((col) => this.getDomain(col)).filter((d) => d.length);
46048
+ }
46049
+ getRowDomainsAtDepth(depth) {
46050
+ if (depth < 0 || depth > this.maxIndent) {
46051
+ return [];
46052
+ }
46053
+ return this.rows.filter((row) => row.indent === depth + 1).map((row) => this.getDomain(row));
46054
+ }
45974
46055
  }
45975
46056
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
45976
46057
 
@@ -46963,6 +47044,109 @@ const ungroupPivotHeadersAction = {
46963
47044
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
46964
47045
  },
46965
47046
  };
47047
+ const toggleCollapsePivotGroupAction = {
47048
+ name: (env) => {
47049
+ const position = env.model.getters.getActivePosition();
47050
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47051
+ if (pivotCellState.isPivotGroup) {
47052
+ return pivotCellState.isCollapsed ? _t("Expand") : _t("Collapse");
47053
+ }
47054
+ return "";
47055
+ },
47056
+ execute(env) {
47057
+ const position = env.model.getters.getActivePosition();
47058
+ togglePivotCollapse(position, env);
47059
+ },
47060
+ isVisible: (env) => {
47061
+ const position = env.model.getters.getActivePosition();
47062
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47063
+ return pivotCellState.isPivotGroup;
47064
+ },
47065
+ };
47066
+ const collapseAllPivotGroupAction = {
47067
+ name: _t("Collapse all"),
47068
+ execute(env) {
47069
+ const position = env.model.getters.getActivePosition();
47070
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47071
+ if (!pivotCellState.isPivotGroup) {
47072
+ return;
47073
+ }
47074
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47075
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47076
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47077
+ const newCollapsed = [
47078
+ ...(definition.collapsedDomains[pivotCell.dimension] || []),
47079
+ ...siblingDomains,
47080
+ ];
47081
+ const filteredCollapsed = newCollapsed.filter((domain, index) => index === newCollapsed.findIndex((d) => deepEquals(d, domain)));
47082
+ definition.collapsedDomains[pivotCell.dimension] = filteredCollapsed;
47083
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47084
+ },
47085
+ isVisible: (env) => {
47086
+ const position = env.model.getters.getActivePosition();
47087
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47088
+ if (!pivotCellState.isPivotGroup) {
47089
+ return false;
47090
+ }
47091
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47092
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47093
+ return !siblingDomains.every((domain) => (definition.collapsedDomains?.[pivotCell.dimension] || []).some((d) => deepEquals(d, domain)));
47094
+ },
47095
+ };
47096
+ const expandAllPivotGroupAction = {
47097
+ name: _t("Expand all"),
47098
+ execute(env) {
47099
+ const position = env.model.getters.getActivePosition();
47100
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47101
+ if (!pivotCellState.isPivotGroup) {
47102
+ return;
47103
+ }
47104
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47105
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47106
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47107
+ const domains = definition.collapsedDomains[pivotCell.dimension] || [];
47108
+ const filteredDomains = domains.filter((domain) => !siblingDomains.find((d) => deepEquals(d, domain)));
47109
+ definition.collapsedDomains[pivotCell.dimension] = filteredDomains;
47110
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47111
+ },
47112
+ isVisible: (env) => {
47113
+ const position = env.model.getters.getActivePosition();
47114
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47115
+ if (!pivotCellState.isPivotGroup) {
47116
+ return false;
47117
+ }
47118
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47119
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47120
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension] || [];
47121
+ return collapsedDomains.some((domain) => siblingDomains.some((d) => deepEquals(d, domain)));
47122
+ },
47123
+ };
47124
+ function getPivotCellCollapseState(getters, position) {
47125
+ if (!getters.isSpillPivotFormula(position)) {
47126
+ return { isPivotGroup: false };
47127
+ }
47128
+ const pivotCell = getters.getPivotCellFromPosition(position);
47129
+ const pivotId = getters.getPivotIdFromPosition(position);
47130
+ if (pivotCell.type !== "HEADER" || !pivotId || !pivotCell.domain.length) {
47131
+ return { isPivotGroup: false };
47132
+ }
47133
+ const definition = getters.getPivotCoreDefinition(pivotId);
47134
+ const isDashboard = getters.isDashboard();
47135
+ const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
47136
+ const hasIcon = !isDashboard && pivotCell.domain.length !== fields.length;
47137
+ if (!hasIcon) {
47138
+ return { isPivotGroup: false };
47139
+ }
47140
+ const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
47141
+ const isCollapsed = domains.some((domain) => deepEquals(domain, pivotCell.domain));
47142
+ const pivot = getters.getPivot(pivotId);
47143
+ const table = pivot.getExpandedTableStructure();
47144
+ const depth = pivotCell.domain.length - 1;
47145
+ const siblingDomains = pivotCell.dimension === "ROW"
47146
+ ? table.getRowDomainsAtDepth(depth)
47147
+ : table.getColumnDomainsAtDepth(depth);
47148
+ return { isPivotGroup: true, isCollapsed, pivotCell, pivotId, siblingDomains };
47149
+ }
46966
47150
  function canSortPivot(getters, position) {
46967
47151
  const pivotId = getters.getPivotIdFromPosition(position);
46968
47152
  if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
@@ -47298,6 +47482,23 @@ cellMenuRegistry
47298
47482
  sequence: 155,
47299
47483
  icon: "o-spreadsheet-Icon.MINUS_IN_BOX",
47300
47484
  ...ungroupPivotHeadersAction,
47485
+ })
47486
+ .add("collapse_pivot", {
47487
+ sequence: 156,
47488
+ name: _t("Expand/Collapse"),
47489
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47490
+ })
47491
+ .addChild("toggle_collapse_pivot_cell", ["collapse_pivot"], {
47492
+ sequence: 10,
47493
+ ...toggleCollapsePivotGroupAction,
47494
+ })
47495
+ .addChild("collapse_all_pivot", ["collapse_pivot"], {
47496
+ sequence: 20,
47497
+ ...collapseAllPivotGroupAction,
47498
+ })
47499
+ .addChild("expand_all_pivot", ["collapse_pivot"], {
47500
+ sequence: 30,
47501
+ ...expandAllPivotGroupAction,
47301
47502
  })
47302
47503
  .add("pivot_sorting", {
47303
47504
  name: _t("Sort pivot"),
@@ -47922,6 +48123,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47922
48123
  execute: (env) => {
47923
48124
  env.model.dispatch("UNFOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47924
48125
  },
48126
+ icon: "o-spreadsheet-Icon.EXPAND",
47925
48127
  },
47926
48128
  {
47927
48129
  id: "fold_all",
@@ -47929,6 +48131,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47929
48131
  execute: (env) => {
47930
48132
  env.model.dispatch("FOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47931
48133
  },
48134
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47932
48135
  },
47933
48136
  ]);
47934
48137
  }
@@ -47950,6 +48153,11 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47950
48153
  const sheetId = env.model.getters.getActiveSheetId();
47951
48154
  interactiveToggleGroup(env, sheetId, dimension, start, end);
47952
48155
  },
48156
+ icon: (env) => {
48157
+ const sheetId = env.model.getters.getActiveSheetId();
48158
+ const groupIsFolded = env.model.getters.isGroupFolded(sheetId, dimension, start, end);
48159
+ return groupIsFolded ? "o-spreadsheet-Icon.EXPAND" : "o-spreadsheet-Icon.COLLAPSE";
48160
+ },
47953
48161
  },
47954
48162
  {
47955
48163
  id: "remove_group",
@@ -47958,6 +48166,7 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47958
48166
  const sheetId = env.model.getters.getActiveSheetId();
47959
48167
  env.model.dispatch("UNGROUP_HEADERS", { sheetId, dimension, start, end });
47960
48168
  },
48169
+ icon: "o-spreadsheet-Icon.TRASH",
47961
48170
  separator: true,
47962
48171
  },
47963
48172
  ]);
@@ -48820,39 +49029,66 @@ class CellComposerStore extends AbstractComposerStore {
48820
49029
  this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
48821
49030
  this.setContent("");
48822
49031
  }
48823
- getComposerContent(position) {
49032
+ getComposerContent(position, selection) {
48824
49033
  const locale = this.getters.getLocale();
48825
49034
  const cell = this.getters.getCell(position);
48826
49035
  if (cell?.isFormula) {
48827
49036
  const prettifiedContent = this.getPrettifiedFormula(cell);
48828
- return localizeFormula(prettifiedContent, locale);
49037
+ // when a formula is prettified (multi lines, indented), adapt the cursor position
49038
+ // to take into account line breaks and tabs
49039
+ function adjustCursorIndex(targetIndex) {
49040
+ let adjustedIndex = 0;
49041
+ let originalIndex = 0;
49042
+ while (originalIndex < targetIndex) {
49043
+ adjustedIndex++;
49044
+ const char = prettifiedContent[adjustedIndex];
49045
+ if (char !== "\n" && char !== "\t") {
49046
+ originalIndex++;
49047
+ }
49048
+ }
49049
+ return adjustedIndex;
49050
+ }
49051
+ let adjustedSelection = selection;
49052
+ if (selection) {
49053
+ adjustedSelection = {
49054
+ start: adjustCursorIndex(selection.start),
49055
+ end: adjustCursorIndex(selection.end),
49056
+ };
49057
+ }
49058
+ return {
49059
+ text: localizeFormula(prettifiedContent, locale),
49060
+ adjustedSelection,
49061
+ };
48829
49062
  }
48830
49063
  const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
48831
49064
  if (spreader) {
48832
- return "";
49065
+ return { text: "" };
49066
+ }
49067
+ if (cell?.content.startsWith("'")) {
49068
+ return { text: cell.content };
48833
49069
  }
48834
49070
  const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
48835
49071
  switch (type) {
48836
49072
  case CellValueType.empty:
48837
- return "";
49073
+ return { text: "" };
48838
49074
  case CellValueType.text:
48839
49075
  case CellValueType.error:
48840
- return value;
49076
+ return { text: value };
48841
49077
  case CellValueType.boolean:
48842
- return formattedValue;
49078
+ return { text: formattedValue };
48843
49079
  case CellValueType.number:
48844
49080
  if (format && isDateTimeFormat(format)) {
48845
49081
  if (parseDateTime(formattedValue, locale) !== null) {
48846
49082
  // formatted string can be parsed again
48847
- return formattedValue;
49083
+ return { text: formattedValue };
48848
49084
  }
48849
49085
  // display a simplified and parsable string otherwise
48850
49086
  const timeFormat = Number.isInteger(value)
48851
49087
  ? locale.dateFormat
48852
49088
  : getDateTimeFormat(locale);
48853
- return formatValue(value, { locale, format: timeFormat });
49089
+ return { text: formatValue(value, { locale, format: timeFormat }) };
48854
49090
  }
48855
- return this.numberComposerContent(value, format, locale);
49091
+ return { text: this.numberComposerContent(value, format, locale) };
48856
49092
  }
48857
49093
  }
48858
49094
  getPrettifiedFormula(cell) {
@@ -48999,8 +49235,9 @@ class GridComposer extends Component {
48999
49235
  },
49000
49236
  focus: this.focus,
49001
49237
  isDefaultFocus: true,
49002
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
49238
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
49003
49239
  focusMode: "contentFocus",
49240
+ selection,
49004
49241
  }),
49005
49242
  onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
49006
49243
  focusMode: "cellFocus",
@@ -55688,12 +55925,13 @@ class DataValidationEditor extends Component {
55688
55925
  onCloseSidePanel: { type: Function, optional: true },
55689
55926
  };
55690
55927
  state = useState({ rule: this.defaultDataValidationRule, errors: [] });
55928
+ editingSheetId;
55691
55929
  setup() {
55930
+ this.editingSheetId = this.env.model.getters.getActiveSheetId();
55692
55931
  if (this.props.rule) {
55693
- const sheetId = this.env.model.getters.getActiveSheetId();
55694
55932
  this.state.rule = {
55695
55933
  ...this.props.rule,
55696
- ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, sheetId)),
55934
+ ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
55697
55935
  };
55698
55936
  this.state.rule.criterion.type = this.props.rule.criterion.type;
55699
55937
  }
@@ -55727,7 +55965,6 @@ class DataValidationEditor extends Component {
55727
55965
  const locale = this.env.model.getters.getLocale();
55728
55966
  const criterion = rule.criterion;
55729
55967
  const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
55730
- const sheetId = this.env.model.getters.getActiveSheetId();
55731
55968
  const values = criterion.values
55732
55969
  .slice(0, criterionEvaluator.numberOfValues(criterion))
55733
55970
  .map((value) => value?.trim())
@@ -55735,8 +55972,8 @@ class DataValidationEditor extends Component {
55735
55972
  .map((value) => canonicalizeContent(value, locale));
55736
55973
  rule.criterion = { ...criterion, values };
55737
55974
  return {
55738
- sheetId,
55739
- ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
55975
+ sheetId: this.editingSheetId,
55976
+ ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
55740
55977
  rule,
55741
55978
  };
55742
55979
  }
@@ -60846,7 +61083,7 @@ class CellPlugin extends CorePlugin {
60846
61083
  (typeof parsedValue === "number"
60847
61084
  ? detectDateFormat(content, locale) || detectNumberFormat(content)
60848
61085
  : undefined);
60849
- if (!isTextFormat(format) && !isEvaluationError(content)) {
61086
+ if (!isTextFormat(format) && !content.startsWith("'") && !isEvaluationError(content)) {
60850
61087
  content = toString(parsedValue);
60851
61088
  }
60852
61089
  return {
@@ -66744,7 +66981,7 @@ class FormulaDependencyGraph {
66744
66981
  * in the correct order they should be evaluated.
66745
66982
  * This is called a topological ordering (excluding cycles)
66746
66983
  */
66747
- getCellsDependingOn(ranges) {
66984
+ getCellsDependingOn(ranges, ignore) {
66748
66985
  const visited = this.createEmptyPositionSet();
66749
66986
  const queue = Array.from(ranges).reverse();
66750
66987
  while (queue.length > 0) {
@@ -66759,7 +66996,7 @@ class FormulaDependencyGraph {
66759
66996
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
66760
66997
  const nextInQueue = {};
66761
66998
  for (const position of impactedPositions) {
66762
- if (!visited.has(position)) {
66999
+ if (!visited.has(position) && !ignore.has(position)) {
66763
67000
  if (!nextInQueue[position.sheetId]) {
66764
67001
  nextInQueue[position.sheetId] = [];
66765
67002
  }
@@ -67317,7 +67554,7 @@ class Evaluator {
67317
67554
  }
67318
67555
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
67319
67556
  // the result matrix is split in 2 zones to exclude the array formula position
67320
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
67557
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
67321
67558
  invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
67322
67559
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
67323
67560
  }
@@ -67435,7 +67672,7 @@ class Evaluator {
67435
67672
  for (const sheetId in zonesBySheetIds) {
67436
67673
  ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
67437
67674
  }
67438
- return this.formulaDependencies().getCellsDependingOn(ranges);
67675
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
67439
67676
  }
67440
67677
  }
67441
67678
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -68702,32 +68939,6 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
68702
68939
  }
68703
68940
  return undefined;
68704
68941
  });
68705
- function togglePivotCollapse(position, env) {
68706
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
68707
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
68708
- if (!pivotId || pivotCell.type !== "HEADER") {
68709
- return;
68710
- }
68711
- const definition = env.model.getters.getPivotCoreDefinition(pivotId);
68712
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
68713
- ? [...definition.collapsedDomains[pivotCell.dimension]]
68714
- : [];
68715
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
68716
- if (index !== -1) {
68717
- collapsedDomains.splice(index, 1);
68718
- }
68719
- else {
68720
- collapsedDomains.push(pivotCell.domain);
68721
- }
68722
- const newDomains = definition.collapsedDomains
68723
- ? { ...definition.collapsedDomains }
68724
- : { COL: [], ROW: [] };
68725
- newDomains[pivotCell.dimension] = collapsedDomains;
68726
- env.model.dispatch("UPDATE_PIVOT", {
68727
- pivotId,
68728
- pivot: { ...definition, collapsedDomains: newDomains },
68729
- });
68730
- }
68731
68942
  iconsOnCellRegistry.add("pivot_dashboard_sorting", (getters, position) => {
68732
68943
  if (!getters.isDashboard()) {
68733
68944
  return undefined;
@@ -68946,7 +69157,8 @@ class DynamicTablesPlugin extends CoreViewPlugin {
68946
69157
  const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
68947
69158
  const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
68948
69159
  if (!parentSpreadingCell) {
68949
- return false;
69160
+ const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
69161
+ return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
68950
69162
  }
68951
69163
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
68952
69164
  return true;
@@ -79812,6 +80024,7 @@ class RibbonMenu extends Component {
79812
80024
  static components = { Menu };
79813
80025
  rootItems = topbarMenuRegistry.getMenuItems();
79814
80026
  menuRef = useRef("menu");
80027
+ containerRef = useRef("container");
79815
80028
  state = useState({
79816
80029
  menuItems: this.rootItems,
79817
80030
  title: _t("Menu Bar"),
@@ -79819,6 +80032,7 @@ class RibbonMenu extends Component {
79819
80032
  });
79820
80033
  setup() {
79821
80034
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
80035
+ onMounted(this.updateShadows);
79822
80036
  }
79823
80037
  onExternalClick(ev) {
79824
80038
  if (!this.menuRef.el?.contains(ev.target)) {
@@ -79831,6 +80045,7 @@ class RibbonMenu extends Component {
79831
80045
  this.state.parentState = { ...this.state };
79832
80046
  this.state.menuItems = children;
79833
80047
  this.state.title = menu.name(this.env);
80048
+ this.containerRef.el?.scrollTo({ top: 0 });
79834
80049
  }
79835
80050
  else {
79836
80051
  this.state.menuItems = this.rootItems;
@@ -79852,6 +80067,19 @@ class RibbonMenu extends Component {
79852
80067
  height: `${this.props.height}px`,
79853
80068
  });
79854
80069
  }
80070
+ updateShadows() {
80071
+ if (!this.containerRef.el) {
80072
+ return;
80073
+ }
80074
+ this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
80075
+ const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
80076
+ if (this.containerRef.el.scrollTop < maxScroll - 1) {
80077
+ this.containerRef.el.classList.add("scroll-bottom");
80078
+ }
80079
+ if (this.containerRef.el.scrollTop > 0) {
80080
+ this.containerRef.el.classList.add("scroll-top");
80081
+ }
80082
+ }
79855
80083
  onClickBack() {
79856
80084
  if (!this.state.parentState) {
79857
80085
  this.props.onClose();
@@ -79860,6 +80088,7 @@ class RibbonMenu extends Component {
79860
80088
  this.state.menuItems = this.state.parentState.menuItems;
79861
80089
  this.state.title = this.state.parentState.title;
79862
80090
  this.state.parentState = this.state.parentState.parentState;
80091
+ this.containerRef.el?.scrollTo({ top: 0 });
79863
80092
  }
79864
80093
  get backTitle() {
79865
80094
  return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
@@ -79911,7 +80140,9 @@ class SmallBottomBar extends Component {
79911
80140
  : "inactive";
79912
80141
  }
79913
80142
  get showFxIcon() {
79914
- return this.focus === "inactive" && !this.composerStore.currentContent;
80143
+ return (this.focus === "inactive" &&
80144
+ !this.composerStore.currentContent &&
80145
+ !this.composerStore.placeholder);
79915
80146
  }
79916
80147
  get rect() {
79917
80148
  return this.composerRef.el
@@ -79928,8 +80159,9 @@ class SmallBottomBar extends Component {
79928
80159
  },
79929
80160
  focus: this.focus,
79930
80161
  composerStore: this.composerStore,
79931
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
80162
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
79932
80163
  focusMode: "contentFocus",
80164
+ selection,
79933
80165
  }),
79934
80166
  isDefaultFocus: false,
79935
80167
  inputStyle: cssPropertiesToCss({
@@ -79937,6 +80169,7 @@ class SmallBottomBar extends Component {
79937
80169
  "max-height": `130px`,
79938
80170
  }),
79939
80171
  showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
80172
+ placeholder: this.composerStore.placeholder,
79940
80173
  };
79941
80174
  }
79942
80175
  get symbols() {
@@ -79982,7 +80215,9 @@ class TopBarComposer extends Component {
79982
80215
  : "inactive";
79983
80216
  }
79984
80217
  get showFxIcon() {
79985
- return this.focus === "inactive" && !this.composerStore.currentContent;
80218
+ return (this.focus === "inactive" &&
80219
+ !this.composerStore.currentContent &&
80220
+ !this.composerStore.placeholder);
79986
80221
  }
79987
80222
  get composerStyle() {
79988
80223
  const style = {
@@ -85734,6 +85969,6 @@ const chartHelpers = { ...CHART_HELPERS, ...CHART_RUNTIME_HELPERS };
85734
85969
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, CellErrorType, ClientDisconnectedError, CommandResult, CorePlugin, CoreViewPlugin, DispatchResult, EvaluationError, LocalTransportService, Model, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, Spreadsheet, SpreadsheetPivotTable, UIPlugin, __info__, addFunction, addRenderingLayer, astToFormula, chartHelpers, compile, compileTokens, components, constants, convertAstNodes, coreTypes, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, iterateAstNodes, links, load, parse, parseTokens, readonlyAllowedCommands, registries, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
85735
85970
 
85736
85971
 
85737
- __info__.version = "19.1.0-alpha.4";
85738
- __info__.date = "2025-10-07T10:03:20.917Z";
85739
- __info__.hash = "b7cfed8";
85972
+ __info__.version = "19.1.0-alpha.5";
85973
+ __info__.date = "2025-10-16T06:39:55.925Z";
85974
+ __info__.hash = "1a0e3d5";