@odoo/o-spreadsheet 19.1.0-alpha.3 → 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.3
6
- * @date 2025-09-23T12:37:52.238Z
7
- * @hash ce2b07a
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';
@@ -593,7 +593,6 @@ const BACKGROUND_HEADER_COLOR = "#F8F9FA";
593
593
  const BACKGROUND_HEADER_SELECTED_COLOR = "#E8EAED";
594
594
  const BACKGROUND_HEADER_ACTIVE_COLOR = "#595959";
595
595
  const TEXT_HEADER_COLOR = "#666666";
596
- const FIGURE_BORDER_COLOR = "#c9ccd2";
597
596
  const SELECTION_BORDER_COLOR = "#3266ca";
598
597
  const HEADER_BORDER_COLOR = "#C0C0C0";
599
598
  const CELL_BORDER_COLOR = "#E2E3E3";
@@ -601,7 +600,6 @@ const BACKGROUND_CHART_COLOR = "#FFFFFF";
601
600
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
602
601
  const LINK_COLOR = HIGHLIGHT_COLOR;
603
602
  const FILTERS_COLOR = "#188038";
604
- const HEADER_GROUPING_BORDER_COLOR = "#999";
605
603
  const FROZEN_PANE_HEADER_BORDER_COLOR = "#BCBCBC";
606
604
  const FROZEN_PANE_BORDER_COLOR = "#DADFE8";
607
605
  const COMPOSER_ASSISTANT_COLOR = "#9B359B";
@@ -844,22 +842,6 @@ const DRAG_THRESHOLD = 5; // in pixels, to avoid unwanted drag when clicking
844
842
  // Miscellaneous
845
843
  //------------------------------------------------------------------------------
846
844
  const sanitizeSheetNameRegex = new RegExp(FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX, "g");
847
- /**
848
- * Remove quotes from a quoted string
849
- * ```js
850
- * removeStringQuotes('"Hello"')
851
- * > 'Hello'
852
- * ```
853
- */
854
- function removeStringQuotes(str) {
855
- if (str[0] === '"') {
856
- str = str.slice(1);
857
- }
858
- if (str[str.length - 1] === '"' && str[str.length - 2] !== "\\") {
859
- return str.slice(0, str.length - 1);
860
- }
861
- return str;
862
- }
863
845
  function isCloneable(obj) {
864
846
  return "clone" in obj && obj.clone instanceof Function;
865
847
  }
@@ -928,6 +910,13 @@ function isPlainObject(obj) {
928
910
  function getUnquotedSheetName(sheetName) {
929
911
  return unquote(sheetName, "'");
930
912
  }
913
+ /**
914
+ * Remove quotes from a quoted string
915
+ * ```js
916
+ * unquote('"Hello"')
917
+ * > 'Hello'
918
+ * ```
919
+ */
931
920
  function unquote(string, quoteChar = '"') {
932
921
  if (string.startsWith(quoteChar)) {
933
922
  string = string.slice(1);
@@ -1332,9 +1321,7 @@ function removeIndexesFromArray(array, indexes) {
1332
1321
  return newArray;
1333
1322
  }
1334
1323
  function insertItemsAtIndex(array, items, index) {
1335
- const newArray = [...array];
1336
- newArray.splice(index, 0, ...items);
1337
- return newArray;
1324
+ return array.slice(0, index).concat(items).concat(array.slice(index));
1338
1325
  }
1339
1326
  function replaceItemAtIndex(array, newItem, index) {
1340
1327
  const newArray = [...array];
@@ -4465,7 +4452,17 @@ function toNumberMatrix(data, argName) {
4465
4452
  return toMatrix(data).map((row) => {
4466
4453
  return row.map((cell) => {
4467
4454
  if (typeof cell.value !== "number") {
4468
- 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);
4469
4466
  }
4470
4467
  return cell.value;
4471
4468
  });
@@ -5584,7 +5581,7 @@ function tokensToTextInternalFormat(tokens) {
5584
5581
  * Replace in place tokens "mm" and "m" that denote minutes in date format with "MM" to avoid confusion with months.
5585
5582
  *
5586
5583
  * As per OpenXML specification, in date formats if a date token "m" or "mm" is followed by a date token "s" or
5587
- * preceded by a data token "h", then it's not a month but an minute.
5584
+ * preceded by a data token "h", then it's not a month but a minute.
5588
5585
  */
5589
5586
  function convertTokensToMinutesInDateFormat(tokens) {
5590
5587
  const dateParts = tokens.filter((token) => token.type === "DATE_PART");
@@ -5627,6 +5624,9 @@ function internalFormatPartToFormat(internalFormat) {
5627
5624
  case "REPEATED_CHAR":
5628
5625
  format += "*" + token.value;
5629
5626
  break;
5627
+ case "DATE_PART":
5628
+ format += token.value === "MM" ? "mm" : token.value; // Convert "MM" back to "mm" for minutes
5629
+ break;
5630
5630
  default:
5631
5631
  format += token.value;
5632
5632
  }
@@ -5700,7 +5700,7 @@ function formatValue(value, { format, locale, formatWidth }) {
5700
5700
  return value;
5701
5701
  }
5702
5702
  const internalFormat = parseFormat(format);
5703
- const formatToApply = internalFormat.text || internalFormat.positive;
5703
+ const formatToApply = getFormatToApply(value, internalFormat).format;
5704
5704
  if (!formatToApply || formatToApply.type !== "text") {
5705
5705
  return value;
5706
5706
  }
@@ -5711,16 +5711,15 @@ function formatValue(value, { format, locale, formatWidth }) {
5711
5711
  format = createDefaultFormat(value);
5712
5712
  }
5713
5713
  const internalFormat = parseFormat(format);
5714
- if (internalFormat.positive.type === "text") {
5715
- return applyTextInternalFormat(value.toString(), internalFormat.positive, formatWidth);
5714
+ const { format: formatToApply, isNegativeFormat } = getFormatToApply(value, internalFormat);
5715
+ if (!formatToApply) {
5716
+ return value.toString();
5716
5717
  }
5717
- let formatToApply = internalFormat.positive;
5718
- if (value < 0 && internalFormat.negative) {
5719
- formatToApply = internalFormat.negative;
5720
- value = -value;
5718
+ if (formatToApply.type === "text") {
5719
+ return applyTextInternalFormat(value.toString(), formatToApply, formatWidth);
5721
5720
  }
5722
- else if (value === 0 && internalFormat.zero) {
5723
- formatToApply = internalFormat.zero;
5721
+ if (isNegativeFormat) {
5722
+ value = Math.abs(value);
5724
5723
  }
5725
5724
  if (formatToApply.type === "date") {
5726
5725
  return repeatCharToFitWidth(applyDateTimeFormat(value, formatToApply), formatWidth);
@@ -5732,6 +5731,31 @@ function formatValue(value, { format, locale, formatWidth }) {
5732
5731
  return "";
5733
5732
  }
5734
5733
  }
5734
+ function getFormatToApply(value, internalFormat) {
5735
+ let formatToApply = undefined;
5736
+ let isNegativeFormat = false;
5737
+ switch (typeof value) {
5738
+ case "number":
5739
+ if (value < 0 && internalFormat.negative) {
5740
+ formatToApply = internalFormat.negative;
5741
+ isNegativeFormat = true;
5742
+ }
5743
+ else if (value === 0 && internalFormat.zero) {
5744
+ formatToApply = internalFormat.zero;
5745
+ }
5746
+ else {
5747
+ formatToApply = internalFormat.positive;
5748
+ }
5749
+ break;
5750
+ case "string":
5751
+ const format = internalFormat.text || internalFormat.positive;
5752
+ if (format.type === "text") {
5753
+ formatToApply = format;
5754
+ }
5755
+ break;
5756
+ }
5757
+ return { format: formatToApply, isNegativeFormat };
5758
+ }
5735
5759
  function applyTextInternalFormat(value, internalFormat, formatWidth) {
5736
5760
  let formattedValue = "";
5737
5761
  for (const token of internalFormat.tokens) {
@@ -6343,6 +6367,27 @@ function isTextFormat(format) {
6343
6367
  return false;
6344
6368
  }
6345
6369
  }
6370
+ function formatHasRepeatedChar(value, format) {
6371
+ if (!format) {
6372
+ return false;
6373
+ }
6374
+ try {
6375
+ const internalFormat = parseFormat(format);
6376
+ const { format: formatToApply } = getFormatToApply(value, internalFormat);
6377
+ if (formatToApply?.type === "text" || formatToApply?.type === "date") {
6378
+ const tokens = formatToApply.tokens;
6379
+ return tokens.some((token) => token.type === "REPEATED_CHAR");
6380
+ }
6381
+ else if (formatToApply?.type === "number") {
6382
+ return (formatToApply.integerPart.some((token) => token.type === "REPEATED_CHAR") ||
6383
+ (formatToApply.decimalPart
6384
+ ? formatToApply.decimalPart.some((token) => token.type === "REPEATED_CHAR")
6385
+ : false));
6386
+ }
6387
+ }
6388
+ catch { }
6389
+ return false;
6390
+ }
6346
6391
 
6347
6392
  function evaluateLiteral(literalCell, localeFormat, position) {
6348
6393
  const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
@@ -6355,6 +6400,9 @@ function parseLiteral(content, locale) {
6355
6400
  if (content.startsWith("=")) {
6356
6401
  throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
6357
6402
  }
6403
+ if (content.startsWith("'")) {
6404
+ return content.slice(1);
6405
+ }
6358
6406
  if (content === "") {
6359
6407
  return null;
6360
6408
  }
@@ -9304,6 +9352,32 @@ function getCustomFieldWithParentField(definition, parentField, fields) {
9304
9352
  groups: [],
9305
9353
  });
9306
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
+ }
9307
9381
 
9308
9382
  class CellClipboardHandler extends AbstractCellClipboardHandler {
9309
9383
  isCutAllowed(data) {
@@ -9477,7 +9551,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
9477
9551
  pasteCell(origin, target, clipboardOption) {
9478
9552
  const { sheetId, col, row } = target;
9479
9553
  const targetCell = this.getters.getEvaluatedCell(target);
9480
- const originFormat = origin?.format ?? origin.evaluatedCell.format;
9554
+ const originFormat = origin?.format || origin.evaluatedCell.format;
9481
9555
  if (clipboardOption?.pasteOption === "asValue") {
9482
9556
  this.dispatch("UPDATE_CELL", {
9483
9557
  ...target,
@@ -13703,7 +13777,7 @@ const GROWTH = {
13703
13777
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13704
13778
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13705
13779
  }
13706
- 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)));
13707
13781
  },
13708
13782
  };
13709
13783
  // -----------------------------------------------------------------------------
@@ -13776,7 +13850,7 @@ const LINEST = {
13776
13850
  if (dataY.length === 0 || dataY[0].length === 0) {
13777
13851
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13778
13852
  }
13779
- 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));
13780
13854
  },
13781
13855
  isExported: true,
13782
13856
  };
@@ -13795,7 +13869,7 @@ const LOGEST = {
13795
13869
  if (dataY.length === 0 || dataY[0].length === 0) {
13796
13870
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13797
13871
  }
13798
- 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));
13799
13873
  for (let i = 0; i < coeffs.length; i++) {
13800
13874
  coeffs[i][0] = Math.exp(coeffs[i][0]);
13801
13875
  }
@@ -14416,7 +14490,7 @@ const TREND = {
14416
14490
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
14417
14491
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
14418
14492
  }
14419
- 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));
14420
14494
  },
14421
14495
  };
14422
14496
  // -----------------------------------------------------------------------------
@@ -18637,7 +18711,7 @@ function parseOperand(tokens) {
18637
18711
  case "STRING":
18638
18712
  return {
18639
18713
  type: "STRING",
18640
- value: removeStringQuotes(current.value),
18714
+ value: unquote(current.value),
18641
18715
  tokenStartIndex: current.tokenIndex,
18642
18716
  tokenEndIndex: current.tokenIndex,
18643
18717
  };
@@ -22394,7 +22468,7 @@ function formulaArguments(tokens) {
22394
22468
  dependencies.push(token.value);
22395
22469
  break;
22396
22470
  case "STRING":
22397
- const value = removeStringQuotes(token.value);
22471
+ const value = unquote(token.value);
22398
22472
  literalValues.strings.push({ value });
22399
22473
  break;
22400
22474
  case "NUMBER": {
@@ -22937,6 +23011,10 @@ const chartShowValuesPlugin = {
22937
23011
  }
22938
23012
  const ctx = chart.ctx;
22939
23013
  ctx.save();
23014
+ const { left, top, height, width } = chart.chartArea;
23015
+ ctx.beginPath();
23016
+ ctx.rect(left, top, width, height);
23017
+ ctx.clip();
22940
23018
  ctx.textAlign = "center";
22941
23019
  ctx.textBaseline = "middle";
22942
23020
  ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
@@ -23971,7 +24049,7 @@ class ChartJsComponent extends owl.Component {
23971
24049
  this.chart.update();
23972
24050
  }
23973
24051
  hasChartDataChanged() {
23974
- return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
24052
+ return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
23975
24053
  }
23976
24054
  enableAnimationInChartData(chartData) {
23977
24055
  return {
@@ -23979,6 +24057,17 @@ class ChartJsComponent extends owl.Component {
23979
24057
  options: { ...chartData.options, animation: { animateRotate: true } },
23980
24058
  };
23981
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
+ }
23982
24071
  get animationChartId() {
23983
24072
  return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
23984
24073
  }
@@ -24646,6 +24735,7 @@ class ScorecardChart extends owl.Component {
24646
24735
  static template = "o-spreadsheet-ScorecardChart";
24647
24736
  static props = {
24648
24737
  chartId: String,
24738
+ isFullScreen: { type: Boolean, optional: true },
24649
24739
  };
24650
24740
  canvas = owl.useRef("chartContainer");
24651
24741
  get runtime() {
@@ -25401,6 +25491,7 @@ function getChartTimeOptions(labels, labelFormat, locale) {
25401
25491
  parser: luxonFormat,
25402
25492
  displayFormats,
25403
25493
  unit: timeUnit ?? false,
25494
+ tooltipFormat: luxonFormat,
25404
25495
  };
25405
25496
  }
25406
25497
  /**
@@ -26469,6 +26560,7 @@ function getLineChartScales(definition, args) {
26469
26560
  };
26470
26561
  Object.assign(scales.x, axis);
26471
26562
  scales.x.ticks.maxTicksLimit = 15;
26563
+ delete scales?.x?.ticks?.callback;
26472
26564
  }
26473
26565
  else if (axisType === "linear") {
26474
26566
  scales.x.type = "linear";
@@ -27409,26 +27501,6 @@ function createBarChartRuntime(chart, getters) {
27409
27501
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
27410
27502
  }
27411
27503
 
27412
- class FullScreenChartStore extends SpreadsheetStore {
27413
- mutators = ["toggleFullScreenChart"];
27414
- fullScreenFigure = undefined;
27415
- toggleFullScreenChart(figureId) {
27416
- if (this.fullScreenFigure?.id === figureId) {
27417
- this.fullScreenFigure = undefined;
27418
- }
27419
- else {
27420
- this.makeFullScreen(figureId);
27421
- }
27422
- }
27423
- makeFullScreen(figureId) {
27424
- const sheetId = this.getters.getActiveSheetId();
27425
- const figure = this.getters.getFigure(sheetId, figureId);
27426
- if (figure) {
27427
- this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
27428
- }
27429
- }
27430
- }
27431
-
27432
27504
  const TREND_LINE_AXES_IDS = [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID];
27433
27505
  const ZOOMABLE_AXIS_IDS = ["x", ...TREND_LINE_AXES_IDS];
27434
27506
  class ZoomableChartStore extends SpreadsheetStore {
@@ -27593,7 +27665,6 @@ chartJsExtensionRegistry.add("zoomWindowPlugin", {
27593
27665
  class ZoomableChartJsComponent extends ChartJsComponent {
27594
27666
  static template = "o-spreadsheet-ZoomableChartJsComponent";
27595
27667
  store;
27596
- fullScreenChartStore;
27597
27668
  masterChartCanvas = owl.useRef("masterChartCanvas");
27598
27669
  masterChart;
27599
27670
  mode;
@@ -27604,7 +27675,6 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27604
27675
  removeEventListeners = () => { };
27605
27676
  setup() {
27606
27677
  this.store = useStore(ZoomableChartStore);
27607
- this.fullScreenChartStore = useStore(FullScreenChartStore);
27608
27678
  super.setup();
27609
27679
  }
27610
27680
  unmount() {
@@ -27619,12 +27689,8 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27619
27689
  `;
27620
27690
  }
27621
27691
  get sliceable() {
27622
- if (this.env.isDashboard()) {
27623
- const fullScreenFigureId = this.fullScreenChartStore.fullScreenFigure?.id;
27624
- const chartFigureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
27625
- if (fullScreenFigureId === chartFigureId) {
27626
- return true;
27627
- }
27692
+ if (this.props.isFullScreen) {
27693
+ return true;
27628
27694
  }
27629
27695
  const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
27630
27696
  return ("zoomable" in definition && definition?.zoomable) ?? false;
@@ -27687,9 +27753,6 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27687
27753
  const xMax = Math.max(...xValues);
27688
27754
  return { xMin, xMax };
27689
27755
  }
27690
- get shouldAnimate() {
27691
- return this.env.model.getters.isDashboard() && !this.sliceable;
27692
- }
27693
27756
  createChart(chartRuntime) {
27694
27757
  const chartData = chartRuntime.chartJsConfig;
27695
27758
  this.isBarChart = chartData.type === "bar";
@@ -27708,6 +27771,9 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27708
27771
  const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27709
27772
  this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
27710
27773
  this.resetAxesLimits();
27774
+ if (this.chart?.options) {
27775
+ this.chart.options.animation = false;
27776
+ }
27711
27777
  }
27712
27778
  updateChartJs(chartRuntime) {
27713
27779
  const chartData = chartRuntime.chartJsConfig;
@@ -27741,6 +27807,9 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27741
27807
  }
27742
27808
  }
27743
27809
  this.resetAxesLimits();
27810
+ if (this.chart?.options) {
27811
+ this.chart.options.animation = false;
27812
+ }
27744
27813
  }
27745
27814
  resetAxesLimits() {
27746
27815
  if (!this.chart) {
@@ -27838,7 +27907,7 @@ class ZoomableChartJsComponent extends ChartJsComponent {
27838
27907
  onPointerDownInMasterChart(ev) {
27839
27908
  this.removeEventListeners();
27840
27909
  const position = ev.offsetX;
27841
- if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27910
+ if (!this.masterChart?.chartArea || !this.chart?.scales?.x) {
27842
27911
  return;
27843
27912
  }
27844
27913
  const { left, right, top, bottom } = this.masterChart.chartArea;
@@ -31396,6 +31465,26 @@ function getCarouselItemTitle(getters, item) {
31396
31465
  return matchedChart.displayName;
31397
31466
  }
31398
31467
 
31468
+ class FullScreenFigureStore extends SpreadsheetStore {
31469
+ mutators = ["toggleFullScreenFigure"];
31470
+ fullScreenFigure = undefined;
31471
+ toggleFullScreenFigure(figureId) {
31472
+ if (this.fullScreenFigure?.id === figureId) {
31473
+ this.fullScreenFigure = undefined;
31474
+ }
31475
+ else {
31476
+ this.makeFullScreen(figureId);
31477
+ }
31478
+ }
31479
+ makeFullScreen(figureId) {
31480
+ const sheetId = this.getters.getActiveSheetId();
31481
+ const figure = this.getters.getFigure(sheetId, figureId);
31482
+ if (figure) {
31483
+ this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
31484
+ }
31485
+ }
31486
+ }
31487
+
31399
31488
  /**
31400
31489
  * This file is largely inspired by owl 1.
31401
31490
  * `css` tag has been removed from owl 2 without workaround to manage css.
@@ -31550,7 +31639,8 @@ class Menu extends owl.Component {
31550
31639
  const menuItemsAndSeparators = [];
31551
31640
  for (let i = 0; i < this.props.menuItems.length; i++) {
31552
31641
  const menuItem = this.props.menuItems[i];
31553
- if (menuItem.isVisible(this.env)) {
31642
+ if (menuItem.isVisible(this.env) &&
31643
+ (!this.isRoot(menuItem) || this.hasVisibleChildren(menuItem))) {
31554
31644
  menuItemsAndSeparators.push(menuItem);
31555
31645
  }
31556
31646
  if (menuItem.separator &&
@@ -31592,6 +31682,9 @@ class Menu extends owl.Component {
31592
31682
  isRoot(menu) {
31593
31683
  return !menu.execute;
31594
31684
  }
31685
+ hasVisibleChildren(menu) {
31686
+ return menu.children(this.env).some((child) => child.isVisible(this.env));
31687
+ }
31595
31688
  isEnabled(menu) {
31596
31689
  if (menu.isEnabled(this.env)) {
31597
31690
  return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
@@ -32133,12 +32226,13 @@ class MenuPopover extends owl.Component {
32133
32226
  class ChartDashboardMenu extends owl.Component {
32134
32227
  static template = "o-spreadsheet-ChartDashboardMenu";
32135
32228
  static components = { MenuPopover };
32136
- static props = { chartId: String };
32229
+ static props = { chartId: String, hasFullScreenButton: { type: Boolean, optional: true } };
32230
+ static defaultProps = { hasFullScreenButton: true };
32137
32231
  fullScreenFigureStore;
32138
32232
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
32139
32233
  setup() {
32140
32234
  super.setup();
32141
- this.fullScreenFigureStore = useStore(FullScreenChartStore);
32235
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
32142
32236
  }
32143
32237
  getMenuItems() {
32144
32238
  return [this.fullScreenMenuItem].filter(isDefined);
@@ -32149,11 +32243,14 @@ class ChartDashboardMenu extends owl.Component {
32149
32243
  }
32150
32244
  openContextMenu(ev) {
32151
32245
  this.menuState.isOpen = true;
32152
- this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
32246
+ this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
32153
32247
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32154
32248
  this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
32155
32249
  }
32156
32250
  get fullScreenMenuItem() {
32251
+ if (!this.props.hasFullScreenButton) {
32252
+ return undefined;
32253
+ }
32157
32254
  const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
32158
32255
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32159
32256
  if (definition.type === "scorecard") {
@@ -32165,7 +32262,7 @@ class ChartDashboardMenu extends owl.Component {
32165
32262
  label: isFullScreen ? _t("Exit Full Screen") : _t("Full Screen"),
32166
32263
  class: `text-muted fa ${isFullScreen ? "fa-compress" : "fa-expand"}`,
32167
32264
  onClick: () => {
32168
- this.fullScreenFigureStore.toggleFullScreenChart(figureId);
32265
+ this.fullScreenFigureStore.toggleFullScreenFigure(figureId);
32169
32266
  },
32170
32267
  };
32171
32268
  }
@@ -32177,6 +32274,8 @@ class CarouselFigure extends owl.Component {
32177
32274
  figureUI: Object,
32178
32275
  onFigureDeleted: Function,
32179
32276
  editFigureStyle: { type: Function, optional: true },
32277
+ isFullScreen: { type: Boolean, optional: true },
32278
+ openContextMenu: { type: Function, optional: true },
32180
32279
  };
32181
32280
  static components = { ChartDashboardMenu, MenuPopover };
32182
32281
  carouselTabsRef = owl.useRef("carouselTabs");
@@ -32184,8 +32283,10 @@ class CarouselFigure extends owl.Component {
32184
32283
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
32185
32284
  hiddenItems = [];
32186
32285
  animationStore;
32286
+ fullScreenFigureStore;
32187
32287
  setup() {
32188
32288
  this.animationStore = useStore(ChartAnimationStore);
32289
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
32189
32290
  owl.useEffect(() => {
32190
32291
  if (this.selectedCarouselItem?.type === "carouselDataView") {
32191
32292
  this.props.editFigureStyle?.({ "pointer-events": "none" });
@@ -32232,7 +32333,8 @@ class CarouselFigure extends owl.Component {
32232
32333
  item,
32233
32334
  });
32234
32335
  if (item.type === "chart") {
32235
- this.animationStore?.enableAnimationForChart(item.chartId);
32336
+ const animationChartId = item.chartId + (this.props.isFullScreen ? "-fullscreen" : "");
32337
+ this.animationStore?.enableAnimationForChart(animationChartId);
32236
32338
  }
32237
32339
  }
32238
32340
  get headerStyle() {
@@ -32296,6 +32398,23 @@ class CarouselFigure extends owl.Component {
32296
32398
  this.menuState.anchorRect = rect;
32297
32399
  this.menuState.menuItems = createActions(menuItems);
32298
32400
  }
32401
+ toggleFullScreen() {
32402
+ if (this.selectedCarouselItem?.type === "chart") {
32403
+ this.fullScreenFigureStore.toggleFullScreenFigure(this.props.figureUI.id);
32404
+ }
32405
+ }
32406
+ get fullScreenButtonTitle() {
32407
+ return this.props.isFullScreen ? _t("Exit Full Screen") : _t("Full Screen");
32408
+ }
32409
+ get visibleCarouselItems() {
32410
+ return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
32411
+ }
32412
+ openContextMenu(event) {
32413
+ const target = event.currentTarget;
32414
+ if (target) {
32415
+ this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
32416
+ }
32417
+ }
32299
32418
  }
32300
32419
 
32301
32420
  class ChartFigure extends owl.Component {
@@ -32304,6 +32423,8 @@ class ChartFigure extends owl.Component {
32304
32423
  figureUI: Object,
32305
32424
  onFigureDeleted: Function,
32306
32425
  editFigureStyle: { type: Function, optional: true },
32426
+ isFullScreen: { type: Boolean, optional: true },
32427
+ openContextMenu: { type: Function, optional: true },
32307
32428
  };
32308
32429
  static components = { ChartDashboardMenu };
32309
32430
  onDoubleClick() {
@@ -32336,6 +32457,7 @@ class ImageFigure extends owl.Component {
32336
32457
  figureUI: Object,
32337
32458
  onFigureDeleted: Function,
32338
32459
  editFigureStyle: { type: Function, optional: true },
32460
+ openContextMenu: { type: Function, optional: true },
32339
32461
  };
32340
32462
  static components = {};
32341
32463
  // ---------------------------------------------------------------------------
@@ -32406,9 +32528,7 @@ class FigureComponent extends owl.Component {
32406
32528
  return this.isSelected ? ACTIVE_BORDER_WIDTH : this.borderWidth;
32407
32529
  }
32408
32530
  getBorderStyle(position) {
32409
- const borderWidth = this.getBorderWidth();
32410
- const borderColor = this.isSelected ? SELECTION_BORDER_COLOR : FIGURE_BORDER_COLOR;
32411
- return `border-${position}: ${borderWidth}px solid ${borderColor};`;
32531
+ return `border-${position}-width: ${this.getBorderWidth()}px;`;
32412
32532
  }
32413
32533
  get wrapperStyle() {
32414
32534
  const { x, y, width, height } = this.props.figureUI;
@@ -34140,8 +34260,11 @@ class Composer extends owl.Component {
34140
34260
  }
34141
34261
  const newSelection = this.contentHelper.getCurrentSelection();
34142
34262
  this.props.composerStore.stopComposerRangeSelection();
34143
- this.props.onComposerContentFocused();
34144
- 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
+ }
34145
34268
  this.processTokenAtCursor();
34146
34269
  }
34147
34270
  onDblClick() {
@@ -34636,13 +34759,6 @@ class AbstractComposerStore extends SpreadsheetStore {
34636
34759
  }
34637
34760
  }
34638
34761
  startEdition(text, selection) {
34639
- if (selection) {
34640
- const content = text || this.getComposerContent(this.getters.getActivePosition());
34641
- const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
34642
- if (!validSelection) {
34643
- return;
34644
- }
34645
- }
34646
34762
  const { col, row } = this.getters.getActivePosition();
34647
34763
  this.model.dispatch("SELECT_FIGURE", { figureId: null });
34648
34764
  this.model.dispatch("SCROLL_TO_CELL", { col, row });
@@ -34699,7 +34815,7 @@ class AbstractComposerStore extends SpreadsheetStore {
34699
34815
  // ---------------------------------------------------------------------------
34700
34816
  get currentContent() {
34701
34817
  if (this.editionMode === "inactive") {
34702
- return this.getComposerContent(this.getters.getActivePosition());
34818
+ return this.getComposerContent(this.getters.getActivePosition()).text;
34703
34819
  }
34704
34820
  return this._currentContent;
34705
34821
  }
@@ -34898,8 +35014,9 @@ class AbstractComposerStore extends SpreadsheetStore {
34898
35014
  this.sheetId = sheetId;
34899
35015
  this.row = row;
34900
35016
  this.editionMode = "editing";
34901
- this.initialContent = this.getComposerContent({ sheetId, col, row });
34902
- 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);
34903
35020
  this.colorIndexByRange = {};
34904
35021
  const zone = positionToZone({ col: this.col, row: this.row });
34905
35022
  this.captureSelection(zone, col, row);
@@ -35376,7 +35493,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35376
35493
  constructor(get, args) {
35377
35494
  super(get);
35378
35495
  this.args = args;
35379
- this._currentContent = this.getComposerContent();
35496
+ this._currentContent = this.getComposerContent().text;
35380
35497
  }
35381
35498
  getAutoCompleteProviders() {
35382
35499
  const providersDefinitions = super.getAutoCompleteProviders();
@@ -35413,7 +35530,7 @@ class StandaloneComposerStore extends AbstractComposerStore {
35413
35530
  })
35414
35531
  .join("");
35415
35532
  }
35416
- return localizeContent(content, this.getters.getLocale());
35533
+ return { text: localizeContent(content, this.getters.getLocale()) };
35417
35534
  }
35418
35535
  stopEdition() {
35419
35536
  this._stopEdition();
@@ -38633,6 +38750,74 @@ function getPath2D(svgPath) {
38633
38750
  return path2D;
38634
38751
  }
38635
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
+
38636
38821
  /**
38637
38822
  * Map of the different types of conversions warnings and their name in error messages
38638
38823
  */
@@ -39155,66 +39340,6 @@ function hexaToInt(hex) {
39155
39340
  */
39156
39341
  const DEFAULT_SYSTEM_COLOR = "FF000000";
39157
39342
 
39158
- /**
39159
- * Get the relative path between two files
39160
- *
39161
- * Eg.:
39162
- * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
39163
- */
39164
- function getRelativePath(from, to) {
39165
- const fromPathParts = from.split("/");
39166
- const toPathParts = to.split("/");
39167
- let relPath = "";
39168
- let startIndex = 0;
39169
- for (let i = 0; i < fromPathParts.length - 1; i++) {
39170
- if (fromPathParts[i] === toPathParts[i]) {
39171
- startIndex++;
39172
- }
39173
- else {
39174
- relPath += "../";
39175
- }
39176
- }
39177
- relPath += toPathParts.slice(startIndex).join("/");
39178
- return relPath;
39179
- }
39180
- /**
39181
- * Convert an array of element into an object where the objects keys were the elements position in the array.
39182
- * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
39183
- *
39184
- * eg. : ["a", "b"] => {0:"a", 1:"b"}
39185
- */
39186
- function arrayToObject(array, indexOffset = 0) {
39187
- const obj = {};
39188
- for (let i = 0; i < array.length; i++) {
39189
- if (array[i]) {
39190
- obj[i + indexOffset] = array[i];
39191
- }
39192
- }
39193
- return obj;
39194
- }
39195
- /**
39196
- * In xlsx we can have string with unicode characters with the format _x00fa_.
39197
- * Replace with characters understandable by JS
39198
- */
39199
- function fixXlsxUnicode(str) {
39200
- return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
39201
- return String.fromCharCode(parseInt(code, 16));
39202
- });
39203
- }
39204
- /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
39205
- function getSheetDataHeader(sheetData, dimension, index) {
39206
- if (dimension === "COL") {
39207
- if (!sheetData.cols[index]) {
39208
- sheetData.cols[index] = {};
39209
- }
39210
- return sheetData.cols[index];
39211
- }
39212
- if (!sheetData.rows[index]) {
39213
- sheetData.rows[index] = {};
39214
- }
39215
- return sheetData.rows[index];
39216
- }
39217
-
39218
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;
39219
39344
  /**
39220
39345
  * Convert excel format to o_spreadsheet format
@@ -39429,9 +39554,9 @@ function convertConditionalFormats(xlsxCfs, dxfs, warningManager) {
39429
39554
  if (!rule.operator || !rule.formula || rule.formula.length === 0)
39430
39555
  continue;
39431
39556
  operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
39432
- values.push(prefixFormula(rule.formula[0]));
39557
+ values.push(prefixFormulaWithEqual(rule.formula[0]));
39433
39558
  if (rule.formula.length === 2) {
39434
- values.push(prefixFormula(rule.formula[1]));
39559
+ values.push(prefixFormulaWithEqual(rule.formula[1]));
39435
39560
  }
39436
39561
  break;
39437
39562
  }
@@ -39589,11 +39714,6 @@ function convertIcons(xlsxIconSet, index) {
39589
39714
  ? ICON_SETS[iconSet].neutral
39590
39715
  : ICON_SETS[iconSet].good;
39591
39716
  }
39592
- /** Prefix the string by "=" if the string looks like a formula */
39593
- function prefixFormula(formula) {
39594
- const tokens = tokenize(formula);
39595
- return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
39596
- }
39597
39717
  // ---------------------------------------------------------------------------
39598
39718
  // Warnings
39599
39719
  // ---------------------------------------------------------------------------
@@ -40069,7 +40189,7 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40069
40189
  dvRules.push(decimalRule);
40070
40190
  break;
40071
40191
  case "list":
40072
- const listRule = convertListrule(dvId++, dv);
40192
+ const listRule = convertListRule(dvId++, dv);
40073
40193
  dvRules.push(listRule);
40074
40194
  break;
40075
40195
  case "date":
@@ -40089,9 +40209,9 @@ function convertDataValidationRules(xlsxDataValidations, warningManager) {
40089
40209
  return dvRules;
40090
40210
  }
40091
40211
  function convertDecimalRule(id, dv) {
40092
- const values = [dv.formula1.toString()];
40212
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40093
40213
  if (dv.formula2) {
40094
- values.push(dv.formula2.toString());
40214
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40095
40215
  }
40096
40216
  return {
40097
40217
  id: id.toString(),
@@ -40103,7 +40223,7 @@ function convertDecimalRule(id, dv) {
40103
40223
  },
40104
40224
  };
40105
40225
  }
40106
- function convertListrule(id, dv) {
40226
+ function convertListRule(id, dv) {
40107
40227
  const formula1 = dv.formula1.toString();
40108
40228
  const isRangeRule = rangeReference.test(formula1);
40109
40229
  return {
@@ -40119,9 +40239,9 @@ function convertListrule(id, dv) {
40119
40239
  }
40120
40240
  function convertDateRule(id, dv) {
40121
40241
  let criterion;
40122
- const values = [dv.formula1.toString()];
40242
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40123
40243
  if (dv.formula2) {
40124
- values.push(dv.formula2.toString());
40244
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40125
40245
  criterion = {
40126
40246
  type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
40127
40247
  values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
@@ -40148,7 +40268,7 @@ function convertCustomRule(id, dv) {
40148
40268
  isBlocking: dv.errorStyle !== "warning",
40149
40269
  criterion: {
40150
40270
  type: "customFormula",
40151
- values: [`=${dv.formula1.toString()}`],
40271
+ values: [prefixFormulaWithEqual(dv.formula1.toString())],
40152
40272
  },
40153
40273
  };
40154
40274
  }
@@ -45922,6 +46042,18 @@ class SpreadsheetPivotTable {
45922
46042
  get numberOfCells() {
45923
46043
  return this.rows.length * this.getNumberOfDataColumns();
45924
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
+ }
45925
46057
  }
45926
46058
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
45927
46059
 
@@ -46914,6 +47046,109 @@ const ungroupPivotHeadersAction = {
46914
47046
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
46915
47047
  },
46916
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
+ }
46917
47152
  function canSortPivot(getters, position) {
46918
47153
  const pivotId = getters.getPivotIdFromPosition(position);
46919
47154
  if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
@@ -47249,6 +47484,23 @@ cellMenuRegistry
47249
47484
  sequence: 155,
47250
47485
  icon: "o-spreadsheet-Icon.MINUS_IN_BOX",
47251
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,
47252
47504
  })
47253
47505
  .add("pivot_sorting", {
47254
47506
  name: _t("Sort pivot"),
@@ -47873,6 +48125,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47873
48125
  execute: (env) => {
47874
48126
  env.model.dispatch("UNFOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47875
48127
  },
48128
+ icon: "o-spreadsheet-Icon.EXPAND",
47876
48129
  },
47877
48130
  {
47878
48131
  id: "fold_all",
@@ -47880,6 +48133,7 @@ function createHeaderGroupContainerContextMenu(sheetId, dimension) {
47880
48133
  execute: (env) => {
47881
48134
  env.model.dispatch("FOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47882
48135
  },
48136
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47883
48137
  },
47884
48138
  ]);
47885
48139
  }
@@ -47901,6 +48155,11 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47901
48155
  const sheetId = env.model.getters.getActiveSheetId();
47902
48156
  interactiveToggleGroup(env, sheetId, dimension, start, end);
47903
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
+ },
47904
48163
  },
47905
48164
  {
47906
48165
  id: "remove_group",
@@ -47909,6 +48168,7 @@ function getHeaderGroupContextMenu(sheetId, dimension, start, end) {
47909
48168
  const sheetId = env.model.getters.getActiveSheetId();
47910
48169
  env.model.dispatch("UNGROUP_HEADERS", { sheetId, dimension, start, end });
47911
48170
  },
48171
+ icon: "o-spreadsheet-Icon.TRASH",
47912
48172
  separator: true,
47913
48173
  },
47914
48174
  ]);
@@ -48771,39 +49031,66 @@ class CellComposerStore extends AbstractComposerStore {
48771
49031
  this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
48772
49032
  this.setContent("");
48773
49033
  }
48774
- getComposerContent(position) {
49034
+ getComposerContent(position, selection) {
48775
49035
  const locale = this.getters.getLocale();
48776
49036
  const cell = this.getters.getCell(position);
48777
49037
  if (cell?.isFormula) {
48778
49038
  const prettifiedContent = this.getPrettifiedFormula(cell);
48779
- 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
+ };
48780
49064
  }
48781
49065
  const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
48782
49066
  if (spreader) {
48783
- return "";
49067
+ return { text: "" };
49068
+ }
49069
+ if (cell?.content.startsWith("'")) {
49070
+ return { text: cell.content };
48784
49071
  }
48785
49072
  const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
48786
49073
  switch (type) {
48787
49074
  case CellValueType.empty:
48788
- return "";
49075
+ return { text: "" };
48789
49076
  case CellValueType.text:
48790
49077
  case CellValueType.error:
48791
- return value;
49078
+ return { text: value };
48792
49079
  case CellValueType.boolean:
48793
- return formattedValue;
49080
+ return { text: formattedValue };
48794
49081
  case CellValueType.number:
48795
49082
  if (format && isDateTimeFormat(format)) {
48796
49083
  if (parseDateTime(formattedValue, locale) !== null) {
48797
49084
  // formatted string can be parsed again
48798
- return formattedValue;
49085
+ return { text: formattedValue };
48799
49086
  }
48800
49087
  // display a simplified and parsable string otherwise
48801
49088
  const timeFormat = Number.isInteger(value)
48802
49089
  ? locale.dateFormat
48803
49090
  : getDateTimeFormat(locale);
48804
- return formatValue(value, { locale, format: timeFormat });
49091
+ return { text: formatValue(value, { locale, format: timeFormat }) };
48805
49092
  }
48806
- return this.numberComposerContent(value, format, locale);
49093
+ return { text: this.numberComposerContent(value, format, locale) };
48807
49094
  }
48808
49095
  }
48809
49096
  getPrettifiedFormula(cell) {
@@ -48950,8 +49237,9 @@ class GridComposer extends owl.Component {
48950
49237
  },
48951
49238
  focus: this.focus,
48952
49239
  isDefaultFocus: true,
48953
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
49240
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
48954
49241
  focusMode: "contentFocus",
49242
+ selection,
48955
49243
  }),
48956
49244
  onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
48957
49245
  focusMode: "cellFocus",
@@ -51344,6 +51632,9 @@ class GridRenderer extends SpreadsheetStore {
51344
51632
  }
51345
51633
  const { align } = this.getters.getCellStyle(position);
51346
51634
  const evaluatedCell = this.getters.getEvaluatedCell(position);
51635
+ if (formatHasRepeatedChar(evaluatedCell.value, evaluatedCell.format)) {
51636
+ return "left";
51637
+ }
51347
51638
  if (isOverflowing && evaluatedCell.type === CellValueType.number) {
51348
51639
  return align !== "center" ? "left" : align;
51349
51640
  }
@@ -55636,12 +55927,13 @@ class DataValidationEditor extends owl.Component {
55636
55927
  onCloseSidePanel: { type: Function, optional: true },
55637
55928
  };
55638
55929
  state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
55930
+ editingSheetId;
55639
55931
  setup() {
55932
+ this.editingSheetId = this.env.model.getters.getActiveSheetId();
55640
55933
  if (this.props.rule) {
55641
- const sheetId = this.env.model.getters.getActiveSheetId();
55642
55934
  this.state.rule = {
55643
55935
  ...this.props.rule,
55644
- 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)),
55645
55937
  };
55646
55938
  this.state.rule.criterion.type = this.props.rule.criterion.type;
55647
55939
  }
@@ -55675,7 +55967,6 @@ class DataValidationEditor extends owl.Component {
55675
55967
  const locale = this.env.model.getters.getLocale();
55676
55968
  const criterion = rule.criterion;
55677
55969
  const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
55678
- const sheetId = this.env.model.getters.getActiveSheetId();
55679
55970
  const values = criterion.values
55680
55971
  .slice(0, criterionEvaluator.numberOfValues(criterion))
55681
55972
  .map((value) => value?.trim())
@@ -55683,8 +55974,8 @@ class DataValidationEditor extends owl.Component {
55683
55974
  .map((value) => canonicalizeContent(value, locale));
55684
55975
  rule.criterion = { ...criterion, values };
55685
55976
  return {
55686
- sheetId,
55687
- 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)),
55688
55979
  rule,
55689
55980
  };
55690
55981
  }
@@ -56815,7 +57106,7 @@ class PivotMeasureEditor extends owl.Component {
56815
57106
  return undefined;
56816
57107
  }
56817
57108
  get isCalculatedMeasureInvalid() {
56818
- return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
57109
+ return compile(this.props.measure.computedBy?.formula ?? "").isBadExpression;
56819
57110
  }
56820
57111
  }
56821
57112
 
@@ -59530,16 +59821,16 @@ class ClickableCellSortIcon extends owl.Component {
59530
59821
  }
59531
59822
  }
59532
59823
 
59533
- class FullScreenChart extends owl.Component {
59534
- static template = "o-spreadsheet-FullScreenChart";
59824
+ class FullScreenFigure extends owl.Component {
59825
+ static template = "o-spreadsheet-FullScreenFigure";
59535
59826
  static props = {};
59536
- static components = { ChartDashboardMenu };
59537
- fullScreenChartStore;
59538
- ref = owl.useRef("fullScreenChart");
59827
+ static components = { ChartFigure };
59828
+ fullScreenFigureStore;
59829
+ ref = owl.useRef("fullScreenFigure");
59539
59830
  spreadsheetRect = useSpreadsheetRect();
59540
59831
  figureRegistry = figureRegistry;
59541
59832
  setup() {
59542
- this.fullScreenChartStore = useStore(FullScreenChartStore);
59833
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
59543
59834
  const animationStore = useStore(ChartAnimationStore);
59544
59835
  let lastFigureId = undefined;
59545
59836
  owl.onWillUpdateProps(() => {
@@ -59551,7 +59842,7 @@ class FullScreenChart extends owl.Component {
59551
59842
  owl.useEffect((el) => el?.focus(), () => [this.ref.el]);
59552
59843
  }
59553
59844
  get figureUI() {
59554
- return this.fullScreenChartStore.fullScreenFigure;
59845
+ return this.fullScreenFigureStore.fullScreenFigure;
59555
59846
  }
59556
59847
  get chartId() {
59557
59848
  if (!this.figureUI)
@@ -59560,7 +59851,7 @@ class FullScreenChart extends owl.Component {
59560
59851
  }
59561
59852
  exitFullScreen() {
59562
59853
  if (this.figureUI) {
59563
- this.fullScreenChartStore.toggleFullScreenChart(this.figureUI.id);
59854
+ this.fullScreenFigureStore.toggleFullScreenFigure(this.figureUI.id);
59564
59855
  }
59565
59856
  }
59566
59857
  onKeyDown(ev) {
@@ -59568,15 +59859,10 @@ class FullScreenChart extends owl.Component {
59568
59859
  this.exitFullScreen();
59569
59860
  }
59570
59861
  }
59571
- get chartComponent() {
59572
- if (!this.chartId)
59862
+ get figureComponent() {
59863
+ if (!this.figureUI)
59573
59864
  return undefined;
59574
- const type = this.env.model.getters.getChartType(this.chartId);
59575
- const component = chartComponentRegistry.get(type);
59576
- if (!component) {
59577
- throw new Error(`Component is not defined for type ${type}`);
59578
- }
59579
- return component;
59865
+ return figureRegistry.get(this.figureUI.tag).Component;
59580
59866
  }
59581
59867
  }
59582
59868
 
@@ -60799,7 +61085,7 @@ class CellPlugin extends CorePlugin {
60799
61085
  (typeof parsedValue === "number"
60800
61086
  ? detectDateFormat(content, locale) || detectNumberFormat(content)
60801
61087
  : undefined);
60802
- if (!isTextFormat(format) && !isEvaluationError(content)) {
61088
+ if (!isTextFormat(format) && !content.startsWith("'") && !isEvaluationError(content)) {
60803
61089
  content = toString(parsedValue);
60804
61090
  }
60805
61091
  return {
@@ -62279,11 +62565,11 @@ class HeaderSizePlugin extends CorePlugin {
62279
62565
  break;
62280
62566
  }
62281
62567
  case "ADD_COLUMNS_ROWS": {
62282
- const sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
62568
+ const sizes = this.sizes[cmd.sheetId][cmd.dimension];
62283
62569
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
62284
62570
  const baseSize = sizes[cmd.base];
62285
- sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
62286
- this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
62571
+ const newSizes = insertItemsAtIndex(sizes, Array(cmd.quantity).fill(baseSize), addIndex);
62572
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, newSizes);
62287
62573
  break;
62288
62574
  }
62289
62575
  case "RESIZE_COLUMNS_ROWS":
@@ -62434,9 +62720,8 @@ class HeaderVisibilityPlugin extends CorePlugin {
62434
62720
  break;
62435
62721
  }
62436
62722
  case "ADD_COLUMNS_ROWS": {
62437
- const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];
62438
62723
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
62439
- hiddenHeaders.splice(addIndex, 0, ...Array(cmd.quantity).fill(false));
62724
+ const hiddenHeaders = insertItemsAtIndex([...this.hiddenHeaders[cmd.sheetId][cmd.dimension]], Array(cmd.quantity).fill(false), addIndex);
62440
62725
  this.history.update("hiddenHeaders", cmd.sheetId, cmd.dimension, hiddenHeaders);
62441
62726
  break;
62442
62727
  }
@@ -66620,12 +66905,12 @@ class SpreadsheetRTree {
66620
66905
  this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
66621
66906
  }
66622
66907
  rtreeItemComparer(left, right) {
66623
- return (left.data === right.data &&
66624
- left.boundingBox.sheetId === right.boundingBox.sheetId &&
66908
+ return (left.boundingBox.sheetId === right.boundingBox.sheetId &&
66625
66909
  left.boundingBox?.zone.left === right.boundingBox.zone.left &&
66626
66910
  left.boundingBox?.zone.top === right.boundingBox.zone.top &&
66627
66911
  left.boundingBox?.zone.right === right.boundingBox.zone.right &&
66628
- left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
66912
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom &&
66913
+ deepEquals(left.data, right.data));
66629
66914
  }
66630
66915
  }
66631
66916
  /**
@@ -66698,7 +66983,7 @@ class FormulaDependencyGraph {
66698
66983
  * in the correct order they should be evaluated.
66699
66984
  * This is called a topological ordering (excluding cycles)
66700
66985
  */
66701
- getCellsDependingOn(ranges) {
66986
+ getCellsDependingOn(ranges, ignore) {
66702
66987
  const visited = this.createEmptyPositionSet();
66703
66988
  const queue = Array.from(ranges).reverse();
66704
66989
  while (queue.length > 0) {
@@ -66713,7 +66998,7 @@ class FormulaDependencyGraph {
66713
66998
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
66714
66999
  const nextInQueue = {};
66715
67000
  for (const position of impactedPositions) {
66716
- if (!visited.has(position)) {
67001
+ if (!visited.has(position) && !ignore.has(position)) {
66717
67002
  if (!nextInQueue[position.sheetId]) {
66718
67003
  nextInQueue[position.sheetId] = [];
66719
67004
  }
@@ -67271,7 +67556,7 @@ class Evaluator {
67271
67556
  }
67272
67557
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
67273
67558
  // the result matrix is split in 2 zones to exclude the array formula position
67274
- 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);
67275
67560
  invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
67276
67561
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
67277
67562
  }
@@ -67389,7 +67674,7 @@ class Evaluator {
67389
67674
  for (const sheetId in zonesBySheetIds) {
67390
67675
  ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
67391
67676
  }
67392
- return this.formulaDependencies().getCellsDependingOn(ranges);
67677
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
67393
67678
  }
67394
67679
  }
67395
67680
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -68656,32 +68941,6 @@ iconsOnCellRegistry.add("pivot_collapse", (getters, position) => {
68656
68941
  }
68657
68942
  return undefined;
68658
68943
  });
68659
- function togglePivotCollapse(position, env) {
68660
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
68661
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
68662
- if (!pivotId || pivotCell.type !== "HEADER") {
68663
- return;
68664
- }
68665
- const definition = env.model.getters.getPivotCoreDefinition(pivotId);
68666
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
68667
- ? [...definition.collapsedDomains[pivotCell.dimension]]
68668
- : [];
68669
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
68670
- if (index !== -1) {
68671
- collapsedDomains.splice(index, 1);
68672
- }
68673
- else {
68674
- collapsedDomains.push(pivotCell.domain);
68675
- }
68676
- const newDomains = definition.collapsedDomains
68677
- ? { ...definition.collapsedDomains }
68678
- : { COL: [], ROW: [] };
68679
- newDomains[pivotCell.dimension] = collapsedDomains;
68680
- env.model.dispatch("UPDATE_PIVOT", {
68681
- pivotId,
68682
- pivot: { ...definition, collapsedDomains: newDomains },
68683
- });
68684
- }
68685
68944
  iconsOnCellRegistry.add("pivot_dashboard_sorting", (getters, position) => {
68686
68945
  if (!getters.isDashboard()) {
68687
68946
  return undefined;
@@ -68900,7 +69159,8 @@ class DynamicTablesPlugin extends CoreViewPlugin {
68900
69159
  const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
68901
69160
  const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
68902
69161
  if (!parentSpreadingCell) {
68903
- return false;
69162
+ const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
69163
+ return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
68904
69164
  }
68905
69165
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
68906
69166
  return true;
@@ -79473,8 +79733,8 @@ class RowGroup extends AbstractHeaderGroup {
79473
79733
  left: `calc(50% - 1px)`, // -1px: we want the border to be on the center
79474
79734
  width: `30%`,
79475
79735
  height: `calc(100% - ${groupBox.headerRect.height / 2}px)`,
79476
- "border-left": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79477
- "border-bottom": groupBox.isEndHidden ? "" : `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79736
+ "border-left": `1px solid`,
79737
+ "border-bottom": groupBox.isEndHidden ? "" : `1px solid`,
79478
79738
  });
79479
79739
  }
79480
79740
  get groupHeaderStyle() {
@@ -79526,8 +79786,8 @@ class ColGroup extends AbstractHeaderGroup {
79526
79786
  left: `${groupBox.headerRect.width / 2}px`,
79527
79787
  width: `calc(100% - ${groupBox.headerRect.width / 2}px)`,
79528
79788
  height: `30%`,
79529
- "border-top": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79530
- "border-right": groupBox.isEndHidden ? "" : `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79789
+ "border-top": `1px solid`,
79790
+ "border-right": groupBox.isEndHidden ? "" : `1px solid`,
79531
79791
  });
79532
79792
  }
79533
79793
  get groupHeaderStyle() {
@@ -79766,6 +80026,7 @@ class RibbonMenu extends owl.Component {
79766
80026
  static components = { Menu };
79767
80027
  rootItems = topbarMenuRegistry.getMenuItems();
79768
80028
  menuRef = owl.useRef("menu");
80029
+ containerRef = owl.useRef("container");
79769
80030
  state = owl.useState({
79770
80031
  menuItems: this.rootItems,
79771
80032
  title: _t("Menu Bar"),
@@ -79773,6 +80034,7 @@ class RibbonMenu extends owl.Component {
79773
80034
  });
79774
80035
  setup() {
79775
80036
  owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
80037
+ owl.onMounted(this.updateShadows);
79776
80038
  }
79777
80039
  onExternalClick(ev) {
79778
80040
  if (!this.menuRef.el?.contains(ev.target)) {
@@ -79785,6 +80047,7 @@ class RibbonMenu extends owl.Component {
79785
80047
  this.state.parentState = { ...this.state };
79786
80048
  this.state.menuItems = children;
79787
80049
  this.state.title = menu.name(this.env);
80050
+ this.containerRef.el?.scrollTo({ top: 0 });
79788
80051
  }
79789
80052
  else {
79790
80053
  this.state.menuItems = this.rootItems;
@@ -79806,6 +80069,19 @@ class RibbonMenu extends owl.Component {
79806
80069
  height: `${this.props.height}px`,
79807
80070
  });
79808
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
+ }
79809
80085
  onClickBack() {
79810
80086
  if (!this.state.parentState) {
79811
80087
  this.props.onClose();
@@ -79814,6 +80090,7 @@ class RibbonMenu extends owl.Component {
79814
80090
  this.state.menuItems = this.state.parentState.menuItems;
79815
80091
  this.state.title = this.state.parentState.title;
79816
80092
  this.state.parentState = this.state.parentState.parentState;
80093
+ this.containerRef.el?.scrollTo({ top: 0 });
79817
80094
  }
79818
80095
  get backTitle() {
79819
80096
  return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
@@ -79864,6 +80141,11 @@ class SmallBottomBar extends owl.Component {
79864
80141
  ? this.composerFocusStore.focusMode
79865
80142
  : "inactive";
79866
80143
  }
80144
+ get showFxIcon() {
80145
+ return (this.focus === "inactive" &&
80146
+ !this.composerStore.currentContent &&
80147
+ !this.composerStore.placeholder);
80148
+ }
79867
80149
  get rect() {
79868
80150
  return this.composerRef.el
79869
80151
  ? getBoundingRectAsPOJO(this.composerRef.el)
@@ -79879,8 +80161,9 @@ class SmallBottomBar extends owl.Component {
79879
80161
  },
79880
80162
  focus: this.focus,
79881
80163
  composerStore: this.composerStore,
79882
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
80164
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
79883
80165
  focusMode: "contentFocus",
80166
+ selection,
79884
80167
  }),
79885
80168
  isDefaultFocus: false,
79886
80169
  inputStyle: cssPropertiesToCss({
@@ -79888,6 +80171,7 @@ class SmallBottomBar extends owl.Component {
79888
80171
  "max-height": `130px`,
79889
80172
  }),
79890
80173
  showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
80174
+ placeholder: this.composerStore.placeholder,
79891
80175
  };
79892
80176
  }
79893
80177
  get symbols() {
@@ -79906,13 +80190,6 @@ class SmallBottomBar extends owl.Component {
79906
80190
  }
79907
80191
 
79908
80192
  const COMPOSER_MAX_HEIGHT = 300;
79909
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
79910
- // FIXME This svg is hardcoded in the css file. We should find a better way to handle it.
79911
- // const FX_SVG = /*xml*/ `
79912
- // <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
79913
- // <path d='m28 34-4 5v2h10l-6 40c-4 22-6 28-7 30-2 2-3 3-5 3-3 0-7-2-9-4H4c-2 2-4 4-4 7s4 6 8 6 9-2 15-8c8-7 13-17 18-39l7-35 13-1 3-6H49c4-23 7-27 11-27 2 0 5 2 8 6h4c1-1 4-4 4-7 0-2-3-6-9-6-5 0-13 4-20 10-6 7-9 14-11 24h-8zm41 16c4-5 7-7 8-7s2 1 5 9l3 12c-7 11-12 17-16 17l-3-1-2-1c-3 0-6 3-6 7s3 7 7 7c6 0 12-6 22-23l3 10c3 9 6 13 10 13 5 0 11-4 18-15l-3-4c-4 6-7 8-8 8-2 0-4-3-6-10l-5-15 8-10 6-4 3 1 3 2c2 0 6-3 6-7s-2-7-6-7c-6 0-11 5-21 20l-2-6c-3-9-5-14-9-14-5 0-12 6-18 15l3 3z' fill='#BDBDBD'/>
79914
- // </svg>
79915
- // `;
79916
80193
  class TopBarComposer extends owl.Component {
79917
80194
  static template = "o-spreadsheet-TopBarComposer";
79918
80195
  static props = {};
@@ -79939,6 +80216,11 @@ class TopBarComposer extends owl.Component {
79939
80216
  ? this.composerFocusStore.focusMode
79940
80217
  : "inactive";
79941
80218
  }
80219
+ get showFxIcon() {
80220
+ return (this.focus === "inactive" &&
80221
+ !this.composerStore.currentContent &&
80222
+ !this.composerStore.placeholder);
80223
+ }
79942
80224
  get composerStyle() {
79943
80225
  const style = {
79944
80226
  padding: "5px 0px 5px 8px",
@@ -80876,7 +81158,7 @@ class Spreadsheet extends owl.Component {
80876
81158
  SidePanels,
80877
81159
  SpreadsheetDashboard,
80878
81160
  HeaderGroupContainer,
80879
- FullScreenChart,
81161
+ FullScreenFigure,
80880
81162
  };
80881
81163
  sidePanel;
80882
81164
  spreadsheetRef = owl.useRef("spreadsheet");
@@ -85600,6 +85882,7 @@ const components = {
85600
85882
  Grid,
85601
85883
  GridOverlay,
85602
85884
  ScorecardChart,
85885
+ GaugeChartComponent,
85603
85886
  LineConfigPanel,
85604
85887
  BarConfigPanel,
85605
85888
  PieChartDesignPanel,
@@ -85638,7 +85921,7 @@ const components = {
85638
85921
  RadioSelection,
85639
85922
  GeoChartRegionSelectSection,
85640
85923
  ChartDashboardMenu,
85641
- FullScreenChart,
85924
+ FullScreenFigure,
85642
85925
  };
85643
85926
  const hooks = {
85644
85927
  useDragAndDropListItems,
@@ -85738,6 +86021,6 @@ exports.tokenColors = tokenColors;
85738
86021
  exports.tokenize = tokenize;
85739
86022
 
85740
86023
 
85741
- __info__.version = "19.1.0-alpha.3";
85742
- __info__.date = "2025-09-23T12:37:52.238Z";
85743
- __info__.hash = "ce2b07a";
86024
+ __info__.version = "19.1.0-alpha.5";
86025
+ __info__.date = "2025-10-16T06:39:55.925Z";
86026
+ __info__.hash = "1a0e3d5";