@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
  (function (exports, owl) {
@@ -592,7 +592,6 @@
592
592
  const BACKGROUND_HEADER_SELECTED_COLOR = "#E8EAED";
593
593
  const BACKGROUND_HEADER_ACTIVE_COLOR = "#595959";
594
594
  const TEXT_HEADER_COLOR = "#666666";
595
- const FIGURE_BORDER_COLOR = "#c9ccd2";
596
595
  const SELECTION_BORDER_COLOR = "#3266ca";
597
596
  const HEADER_BORDER_COLOR = "#C0C0C0";
598
597
  const CELL_BORDER_COLOR = "#E2E3E3";
@@ -600,7 +599,6 @@
600
599
  const DEFAULT_COLOR_SCALE_MIDPOINT_COLOR = 0xb6d7a8;
601
600
  const LINK_COLOR = HIGHLIGHT_COLOR;
602
601
  const FILTERS_COLOR = "#188038";
603
- const HEADER_GROUPING_BORDER_COLOR = "#999";
604
602
  const FROZEN_PANE_HEADER_BORDER_COLOR = "#BCBCBC";
605
603
  const FROZEN_PANE_BORDER_COLOR = "#DADFE8";
606
604
  const COMPOSER_ASSISTANT_COLOR = "#9B359B";
@@ -843,22 +841,6 @@
843
841
  // Miscellaneous
844
842
  //------------------------------------------------------------------------------
845
843
  const sanitizeSheetNameRegex = new RegExp(FORBIDDEN_SHEETNAME_CHARS_IN_EXCEL_REGEX, "g");
846
- /**
847
- * Remove quotes from a quoted string
848
- * ```js
849
- * removeStringQuotes('"Hello"')
850
- * > 'Hello'
851
- * ```
852
- */
853
- function removeStringQuotes(str) {
854
- if (str[0] === '"') {
855
- str = str.slice(1);
856
- }
857
- if (str[str.length - 1] === '"' && str[str.length - 2] !== "\\") {
858
- return str.slice(0, str.length - 1);
859
- }
860
- return str;
861
- }
862
844
  function isCloneable(obj) {
863
845
  return "clone" in obj && obj.clone instanceof Function;
864
846
  }
@@ -927,6 +909,13 @@
927
909
  function getUnquotedSheetName(sheetName) {
928
910
  return unquote(sheetName, "'");
929
911
  }
912
+ /**
913
+ * Remove quotes from a quoted string
914
+ * ```js
915
+ * unquote('"Hello"')
916
+ * > 'Hello'
917
+ * ```
918
+ */
930
919
  function unquote(string, quoteChar = '"') {
931
920
  if (string.startsWith(quoteChar)) {
932
921
  string = string.slice(1);
@@ -1331,9 +1320,7 @@
1331
1320
  return newArray;
1332
1321
  }
1333
1322
  function insertItemsAtIndex(array, items, index) {
1334
- const newArray = [...array];
1335
- newArray.splice(index, 0, ...items);
1336
- return newArray;
1323
+ return array.slice(0, index).concat(items).concat(array.slice(index));
1337
1324
  }
1338
1325
  function replaceItemAtIndex(array, newItem, index) {
1339
1326
  const newArray = [...array];
@@ -4464,7 +4451,17 @@
4464
4451
  return toMatrix(data).map((row) => {
4465
4452
  return row.map((cell) => {
4466
4453
  if (typeof cell.value !== "number") {
4467
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects number values for %s, but got a %s.", argName, typeof cell.value));
4454
+ let message = "";
4455
+ if (typeof cell === "object") {
4456
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got an empty value.", argName);
4457
+ }
4458
+ else if (typeof cell === "string") {
4459
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a string.", argName);
4460
+ }
4461
+ else if (typeof cell === "boolean") {
4462
+ message = _t("Function [[FUNCTION_NAME]] expects number values for %s, but got a boolean.", argName);
4463
+ }
4464
+ throw new EvaluationError(message);
4468
4465
  }
4469
4466
  return cell.value;
4470
4467
  });
@@ -5583,7 +5580,7 @@
5583
5580
  * Replace in place tokens "mm" and "m" that denote minutes in date format with "MM" to avoid confusion with months.
5584
5581
  *
5585
5582
  * As per OpenXML specification, in date formats if a date token "m" or "mm" is followed by a date token "s" or
5586
- * preceded by a data token "h", then it's not a month but an minute.
5583
+ * preceded by a data token "h", then it's not a month but a minute.
5587
5584
  */
5588
5585
  function convertTokensToMinutesInDateFormat(tokens) {
5589
5586
  const dateParts = tokens.filter((token) => token.type === "DATE_PART");
@@ -5626,6 +5623,9 @@
5626
5623
  case "REPEATED_CHAR":
5627
5624
  format += "*" + token.value;
5628
5625
  break;
5626
+ case "DATE_PART":
5627
+ format += token.value === "MM" ? "mm" : token.value; // Convert "MM" back to "mm" for minutes
5628
+ break;
5629
5629
  default:
5630
5630
  format += token.value;
5631
5631
  }
@@ -5699,7 +5699,7 @@
5699
5699
  return value;
5700
5700
  }
5701
5701
  const internalFormat = parseFormat(format);
5702
- const formatToApply = internalFormat.text || internalFormat.positive;
5702
+ const formatToApply = getFormatToApply(value, internalFormat).format;
5703
5703
  if (!formatToApply || formatToApply.type !== "text") {
5704
5704
  return value;
5705
5705
  }
@@ -5710,16 +5710,15 @@
5710
5710
  format = createDefaultFormat(value);
5711
5711
  }
5712
5712
  const internalFormat = parseFormat(format);
5713
- if (internalFormat.positive.type === "text") {
5714
- return applyTextInternalFormat(value.toString(), internalFormat.positive, formatWidth);
5713
+ const { format: formatToApply, isNegativeFormat } = getFormatToApply(value, internalFormat);
5714
+ if (!formatToApply) {
5715
+ return value.toString();
5715
5716
  }
5716
- let formatToApply = internalFormat.positive;
5717
- if (value < 0 && internalFormat.negative) {
5718
- formatToApply = internalFormat.negative;
5719
- value = -value;
5717
+ if (formatToApply.type === "text") {
5718
+ return applyTextInternalFormat(value.toString(), formatToApply, formatWidth);
5720
5719
  }
5721
- else if (value === 0 && internalFormat.zero) {
5722
- formatToApply = internalFormat.zero;
5720
+ if (isNegativeFormat) {
5721
+ value = Math.abs(value);
5723
5722
  }
5724
5723
  if (formatToApply.type === "date") {
5725
5724
  return repeatCharToFitWidth(applyDateTimeFormat(value, formatToApply), formatWidth);
@@ -5731,6 +5730,31 @@
5731
5730
  return "";
5732
5731
  }
5733
5732
  }
5733
+ function getFormatToApply(value, internalFormat) {
5734
+ let formatToApply = undefined;
5735
+ let isNegativeFormat = false;
5736
+ switch (typeof value) {
5737
+ case "number":
5738
+ if (value < 0 && internalFormat.negative) {
5739
+ formatToApply = internalFormat.negative;
5740
+ isNegativeFormat = true;
5741
+ }
5742
+ else if (value === 0 && internalFormat.zero) {
5743
+ formatToApply = internalFormat.zero;
5744
+ }
5745
+ else {
5746
+ formatToApply = internalFormat.positive;
5747
+ }
5748
+ break;
5749
+ case "string":
5750
+ const format = internalFormat.text || internalFormat.positive;
5751
+ if (format.type === "text") {
5752
+ formatToApply = format;
5753
+ }
5754
+ break;
5755
+ }
5756
+ return { format: formatToApply, isNegativeFormat };
5757
+ }
5734
5758
  function applyTextInternalFormat(value, internalFormat, formatWidth) {
5735
5759
  let formattedValue = "";
5736
5760
  for (const token of internalFormat.tokens) {
@@ -6342,6 +6366,27 @@
6342
6366
  return false;
6343
6367
  }
6344
6368
  }
6369
+ function formatHasRepeatedChar(value, format) {
6370
+ if (!format) {
6371
+ return false;
6372
+ }
6373
+ try {
6374
+ const internalFormat = parseFormat(format);
6375
+ const { format: formatToApply } = getFormatToApply(value, internalFormat);
6376
+ if (formatToApply?.type === "text" || formatToApply?.type === "date") {
6377
+ const tokens = formatToApply.tokens;
6378
+ return tokens.some((token) => token.type === "REPEATED_CHAR");
6379
+ }
6380
+ else if (formatToApply?.type === "number") {
6381
+ return (formatToApply.integerPart.some((token) => token.type === "REPEATED_CHAR") ||
6382
+ (formatToApply.decimalPart
6383
+ ? formatToApply.decimalPart.some((token) => token.type === "REPEATED_CHAR")
6384
+ : false));
6385
+ }
6386
+ }
6387
+ catch { }
6388
+ return false;
6389
+ }
6345
6390
 
6346
6391
  function evaluateLiteral(literalCell, localeFormat, position) {
6347
6392
  const value = isTextFormat(localeFormat.format) && literalCell.parsedValue !== null
@@ -6354,6 +6399,9 @@
6354
6399
  if (content.startsWith("=")) {
6355
6400
  throw new Error(`Cannot parse "${content}" because it's not a literal value. It's a formula`);
6356
6401
  }
6402
+ if (content.startsWith("'")) {
6403
+ return content.slice(1);
6404
+ }
6357
6405
  if (content === "") {
6358
6406
  return null;
6359
6407
  }
@@ -9303,6 +9351,32 @@
9303
9351
  groups: [],
9304
9352
  });
9305
9353
  }
9354
+ function togglePivotCollapse(position, env) {
9355
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
9356
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
9357
+ if (!pivotId || pivotCell.type !== "HEADER") {
9358
+ return;
9359
+ }
9360
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
9361
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
9362
+ ? [...definition.collapsedDomains[pivotCell.dimension]]
9363
+ : [];
9364
+ const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
9365
+ if (index !== -1) {
9366
+ collapsedDomains.splice(index, 1);
9367
+ }
9368
+ else {
9369
+ collapsedDomains.push(pivotCell.domain);
9370
+ }
9371
+ const newDomains = definition.collapsedDomains
9372
+ ? { ...definition.collapsedDomains }
9373
+ : { COL: [], ROW: [] };
9374
+ newDomains[pivotCell.dimension] = collapsedDomains;
9375
+ env.model.dispatch("UPDATE_PIVOT", {
9376
+ pivotId,
9377
+ pivot: { ...definition, collapsedDomains: newDomains },
9378
+ });
9379
+ }
9306
9380
 
9307
9381
  class CellClipboardHandler extends AbstractCellClipboardHandler {
9308
9382
  isCutAllowed(data) {
@@ -9476,7 +9550,7 @@
9476
9550
  pasteCell(origin, target, clipboardOption) {
9477
9551
  const { sheetId, col, row } = target;
9478
9552
  const targetCell = this.getters.getEvaluatedCell(target);
9479
- const originFormat = origin?.format ?? origin.evaluatedCell.format;
9553
+ const originFormat = origin?.format || origin.evaluatedCell.format;
9480
9554
  if (clipboardOption?.pasteOption === "asValue") {
9481
9555
  this.dispatch("UPDATE_CELL", {
9482
9556
  ...target,
@@ -13702,7 +13776,7 @@ stores.inject(MyMetaStore, storeInstance);
13702
13776
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
13703
13777
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
13704
13778
  }
13705
- 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)));
13779
+ return expM(predictLinearValues(logM(toNumberMatrix(knownDataY, "known_data_y")), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b)));
13706
13780
  },
13707
13781
  };
13708
13782
  // -----------------------------------------------------------------------------
@@ -13775,7 +13849,7 @@ stores.inject(MyMetaStore, storeInstance);
13775
13849
  if (dataY.length === 0 || dataY[0].length === 0) {
13776
13850
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13777
13851
  }
13778
- return fullLinearRegression(toNumberMatrix(dataX, "the first argument (data_y)"), toNumberMatrix(dataY, "the second argument (data_x)"), toBoolean(calculateB), toBoolean(verbose));
13852
+ return fullLinearRegression(toNumberMatrix(dataX, "data_x"), toNumberMatrix(dataY, "data_y"), toBoolean(calculateB), toBoolean(verbose));
13779
13853
  },
13780
13854
  isExported: true,
13781
13855
  };
@@ -13794,7 +13868,7 @@ stores.inject(MyMetaStore, storeInstance);
13794
13868
  if (dataY.length === 0 || dataY[0].length === 0) {
13795
13869
  return new EvaluationError(emptyDataErrorMessage("data_y"));
13796
13870
  }
13797
- const coeffs = fullLinearRegression(toNumberMatrix(dataX, "the second argument (data_x)"), logM(toNumberMatrix(dataY, "the first argument (data_y)")), toBoolean(calculateB), toBoolean(verbose));
13871
+ const coeffs = fullLinearRegression(toNumberMatrix(dataX, "data_x"), logM(toNumberMatrix(dataY, "data_y")), toBoolean(calculateB), toBoolean(verbose));
13798
13872
  for (let i = 0; i < coeffs.length; i++) {
13799
13873
  coeffs[i][0] = Math.exp(coeffs[i][0]);
13800
13874
  }
@@ -14415,7 +14489,7 @@ stores.inject(MyMetaStore, storeInstance);
14415
14489
  if (knownDataY.length === 0 || knownDataY[0].length === 0) {
14416
14490
  return new EvaluationError(emptyDataErrorMessage("known_data_y"));
14417
14491
  }
14418
- 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));
14492
+ return predictLinearValues(toNumberMatrix(knownDataY, "known_data_y"), toNumberMatrix(knownDataX, "known_data_x"), toNumberMatrix(newDataX, "new_data_y"), toBoolean(b));
14419
14493
  },
14420
14494
  };
14421
14495
  // -----------------------------------------------------------------------------
@@ -18636,7 +18710,7 @@ stores.inject(MyMetaStore, storeInstance);
18636
18710
  case "STRING":
18637
18711
  return {
18638
18712
  type: "STRING",
18639
- value: removeStringQuotes(current.value),
18713
+ value: unquote(current.value),
18640
18714
  tokenStartIndex: current.tokenIndex,
18641
18715
  tokenEndIndex: current.tokenIndex,
18642
18716
  };
@@ -22393,7 +22467,7 @@ stores.inject(MyMetaStore, storeInstance);
22393
22467
  dependencies.push(token.value);
22394
22468
  break;
22395
22469
  case "STRING":
22396
- const value = removeStringQuotes(token.value);
22470
+ const value = unquote(token.value);
22397
22471
  literalValues.strings.push({ value });
22398
22472
  break;
22399
22473
  case "NUMBER": {
@@ -22936,6 +23010,10 @@ stores.inject(MyMetaStore, storeInstance);
22936
23010
  }
22937
23011
  const ctx = chart.ctx;
22938
23012
  ctx.save();
23013
+ const { left, top, height, width } = chart.chartArea;
23014
+ ctx.beginPath();
23015
+ ctx.rect(left, top, width, height);
23016
+ ctx.clip();
22939
23017
  ctx.textAlign = "center";
22940
23018
  ctx.textBaseline = "middle";
22941
23019
  ctx.miterLimit = 1; // Avoid sharp artifacts on strokeText
@@ -23970,7 +24048,7 @@ stores.inject(MyMetaStore, storeInstance);
23970
24048
  this.chart.update();
23971
24049
  }
23972
24050
  hasChartDataChanged() {
23973
- return !deepEquals(this.currentRuntime.chartJsConfig.data, this.chartRuntime.chartJsConfig.data);
24051
+ return !deepEquals(this.getChartDataInRuntime(this.currentRuntime), this.getChartDataInRuntime(this.chartRuntime));
23974
24052
  }
23975
24053
  enableAnimationInChartData(chartData) {
23976
24054
  return {
@@ -23978,6 +24056,17 @@ stores.inject(MyMetaStore, storeInstance);
23978
24056
  options: { ...chartData.options, animation: { animateRotate: true } },
23979
24057
  };
23980
24058
  }
24059
+ getChartDataInRuntime(runtime) {
24060
+ const data = runtime.chartJsConfig.data;
24061
+ return {
24062
+ labels: data.labels,
24063
+ dataset: data.datasets.map((dataset) => ({
24064
+ data: dataset.data,
24065
+ label: dataset.label,
24066
+ tree: dataset.tree,
24067
+ })),
24068
+ };
24069
+ }
23981
24070
  get animationChartId() {
23982
24071
  return this.props.isFullScreen ? this.props.chartId + "-fullscreen" : this.props.chartId;
23983
24072
  }
@@ -24645,6 +24734,7 @@ stores.inject(MyMetaStore, storeInstance);
24645
24734
  static template = "o-spreadsheet-ScorecardChart";
24646
24735
  static props = {
24647
24736
  chartId: String,
24737
+ isFullScreen: { type: Boolean, optional: true },
24648
24738
  };
24649
24739
  canvas = owl.useRef("chartContainer");
24650
24740
  get runtime() {
@@ -25400,6 +25490,7 @@ stores.inject(MyMetaStore, storeInstance);
25400
25490
  parser: luxonFormat,
25401
25491
  displayFormats,
25402
25492
  unit: timeUnit ?? false,
25493
+ tooltipFormat: luxonFormat,
25403
25494
  };
25404
25495
  }
25405
25496
  /**
@@ -26468,6 +26559,7 @@ stores.inject(MyMetaStore, storeInstance);
26468
26559
  };
26469
26560
  Object.assign(scales.x, axis);
26470
26561
  scales.x.ticks.maxTicksLimit = 15;
26562
+ delete scales?.x?.ticks?.callback;
26471
26563
  }
26472
26564
  else if (axisType === "linear") {
26473
26565
  scales.x.type = "linear";
@@ -27408,26 +27500,6 @@ stores.inject(MyMetaStore, storeInstance);
27408
27500
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
27409
27501
  }
27410
27502
 
27411
- class FullScreenChartStore extends SpreadsheetStore {
27412
- mutators = ["toggleFullScreenChart"];
27413
- fullScreenFigure = undefined;
27414
- toggleFullScreenChart(figureId) {
27415
- if (this.fullScreenFigure?.id === figureId) {
27416
- this.fullScreenFigure = undefined;
27417
- }
27418
- else {
27419
- this.makeFullScreen(figureId);
27420
- }
27421
- }
27422
- makeFullScreen(figureId) {
27423
- const sheetId = this.getters.getActiveSheetId();
27424
- const figure = this.getters.getFigure(sheetId, figureId);
27425
- if (figure) {
27426
- this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
27427
- }
27428
- }
27429
- }
27430
-
27431
27503
  const TREND_LINE_AXES_IDS = [TREND_LINE_XAXIS_ID, MOVING_AVERAGE_TREND_LINE_XAXIS_ID];
27432
27504
  const ZOOMABLE_AXIS_IDS = ["x", ...TREND_LINE_AXES_IDS];
27433
27505
  class ZoomableChartStore extends SpreadsheetStore {
@@ -27592,7 +27664,6 @@ stores.inject(MyMetaStore, storeInstance);
27592
27664
  class ZoomableChartJsComponent extends ChartJsComponent {
27593
27665
  static template = "o-spreadsheet-ZoomableChartJsComponent";
27594
27666
  store;
27595
- fullScreenChartStore;
27596
27667
  masterChartCanvas = owl.useRef("masterChartCanvas");
27597
27668
  masterChart;
27598
27669
  mode;
@@ -27603,7 +27674,6 @@ stores.inject(MyMetaStore, storeInstance);
27603
27674
  removeEventListeners = () => { };
27604
27675
  setup() {
27605
27676
  this.store = useStore(ZoomableChartStore);
27606
- this.fullScreenChartStore = useStore(FullScreenChartStore);
27607
27677
  super.setup();
27608
27678
  }
27609
27679
  unmount() {
@@ -27618,12 +27688,8 @@ stores.inject(MyMetaStore, storeInstance);
27618
27688
  `;
27619
27689
  }
27620
27690
  get sliceable() {
27621
- if (this.env.isDashboard()) {
27622
- const fullScreenFigureId = this.fullScreenChartStore.fullScreenFigure?.id;
27623
- const chartFigureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
27624
- if (fullScreenFigureId === chartFigureId) {
27625
- return true;
27626
- }
27691
+ if (this.props.isFullScreen) {
27692
+ return true;
27627
27693
  }
27628
27694
  const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
27629
27695
  return ("zoomable" in definition && definition?.zoomable) ?? false;
@@ -27686,9 +27752,6 @@ stores.inject(MyMetaStore, storeInstance);
27686
27752
  const xMax = Math.max(...xValues);
27687
27753
  return { xMin, xMax };
27688
27754
  }
27689
- get shouldAnimate() {
27690
- return this.env.model.getters.isDashboard() && !this.sliceable;
27691
- }
27692
27755
  createChart(chartRuntime) {
27693
27756
  const chartData = chartRuntime.chartJsConfig;
27694
27757
  this.isBarChart = chartData.type === "bar";
@@ -27707,6 +27770,9 @@ stores.inject(MyMetaStore, storeInstance);
27707
27770
  const masterChartCtx = this.masterChartCanvas.el.getContext("2d");
27708
27771
  this.masterChart = new window.Chart(masterChartCtx, this.getMasterChartConfiguration(chartRuntime["masterChartConfig"]));
27709
27772
  this.resetAxesLimits();
27773
+ if (this.chart?.options) {
27774
+ this.chart.options.animation = false;
27775
+ }
27710
27776
  }
27711
27777
  updateChartJs(chartRuntime) {
27712
27778
  const chartData = chartRuntime.chartJsConfig;
@@ -27740,6 +27806,9 @@ stores.inject(MyMetaStore, storeInstance);
27740
27806
  }
27741
27807
  }
27742
27808
  this.resetAxesLimits();
27809
+ if (this.chart?.options) {
27810
+ this.chart.options.animation = false;
27811
+ }
27743
27812
  }
27744
27813
  resetAxesLimits() {
27745
27814
  if (!this.chart) {
@@ -27837,7 +27906,7 @@ stores.inject(MyMetaStore, storeInstance);
27837
27906
  onPointerDownInMasterChart(ev) {
27838
27907
  this.removeEventListeners();
27839
27908
  const position = ev.offsetX;
27840
- if (!this.masterChart?.chartArea || !this.chart?.scales.x) {
27909
+ if (!this.masterChart?.chartArea || !this.chart?.scales?.x) {
27841
27910
  return;
27842
27911
  }
27843
27912
  const { left, right, top, bottom } = this.masterChart.chartArea;
@@ -31395,6 +31464,26 @@ stores.inject(MyMetaStore, storeInstance);
31395
31464
  return matchedChart.displayName;
31396
31465
  }
31397
31466
 
31467
+ class FullScreenFigureStore extends SpreadsheetStore {
31468
+ mutators = ["toggleFullScreenFigure"];
31469
+ fullScreenFigure = undefined;
31470
+ toggleFullScreenFigure(figureId) {
31471
+ if (this.fullScreenFigure?.id === figureId) {
31472
+ this.fullScreenFigure = undefined;
31473
+ }
31474
+ else {
31475
+ this.makeFullScreen(figureId);
31476
+ }
31477
+ }
31478
+ makeFullScreen(figureId) {
31479
+ const sheetId = this.getters.getActiveSheetId();
31480
+ const figure = this.getters.getFigure(sheetId, figureId);
31481
+ if (figure) {
31482
+ this.fullScreenFigure = { ...figure, x: 0, y: 0, width: 0, height: 0 };
31483
+ }
31484
+ }
31485
+ }
31486
+
31398
31487
  /**
31399
31488
  * This file is largely inspired by owl 1.
31400
31489
  * `css` tag has been removed from owl 2 without workaround to manage css.
@@ -31549,7 +31638,8 @@ stores.inject(MyMetaStore, storeInstance);
31549
31638
  const menuItemsAndSeparators = [];
31550
31639
  for (let i = 0; i < this.props.menuItems.length; i++) {
31551
31640
  const menuItem = this.props.menuItems[i];
31552
- if (menuItem.isVisible(this.env)) {
31641
+ if (menuItem.isVisible(this.env) &&
31642
+ (!this.isRoot(menuItem) || this.hasVisibleChildren(menuItem))) {
31553
31643
  menuItemsAndSeparators.push(menuItem);
31554
31644
  }
31555
31645
  if (menuItem.separator &&
@@ -31591,6 +31681,9 @@ stores.inject(MyMetaStore, storeInstance);
31591
31681
  isRoot(menu) {
31592
31682
  return !menu.execute;
31593
31683
  }
31684
+ hasVisibleChildren(menu) {
31685
+ return menu.children(this.env).some((child) => child.isVisible(this.env));
31686
+ }
31594
31687
  isEnabled(menu) {
31595
31688
  if (menu.isEnabled(this.env)) {
31596
31689
  return this.env.model.getters.isReadonly() ? menu.isReadonlyAllowed : true;
@@ -32132,12 +32225,13 @@ stores.inject(MyMetaStore, storeInstance);
32132
32225
  class ChartDashboardMenu extends owl.Component {
32133
32226
  static template = "o-spreadsheet-ChartDashboardMenu";
32134
32227
  static components = { MenuPopover };
32135
- static props = { chartId: String };
32228
+ static props = { chartId: String, hasFullScreenButton: { type: Boolean, optional: true } };
32229
+ static defaultProps = { hasFullScreenButton: true };
32136
32230
  fullScreenFigureStore;
32137
32231
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
32138
32232
  setup() {
32139
32233
  super.setup();
32140
- this.fullScreenFigureStore = useStore(FullScreenChartStore);
32234
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
32141
32235
  }
32142
32236
  getMenuItems() {
32143
32237
  return [this.fullScreenMenuItem].filter(isDefined);
@@ -32148,11 +32242,14 @@ stores.inject(MyMetaStore, storeInstance);
32148
32242
  }
32149
32243
  openContextMenu(ev) {
32150
32244
  this.menuState.isOpen = true;
32151
- this.menuState.anchorRect = { x: ev.clientX, y: ev.clientY, width: 0, height: 0 };
32245
+ this.menuState.anchorRect = getBoundingRectAsPOJO(ev.currentTarget);
32152
32246
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32153
32247
  this.menuState.menuItems = getChartMenuActions(figureId, () => { }, this.env);
32154
32248
  }
32155
32249
  get fullScreenMenuItem() {
32250
+ if (!this.props.hasFullScreenButton) {
32251
+ return undefined;
32252
+ }
32156
32253
  const definition = this.env.model.getters.getChartDefinition(this.props.chartId);
32157
32254
  const figureId = this.env.model.getters.getFigureIdFromChartId(this.props.chartId);
32158
32255
  if (definition.type === "scorecard") {
@@ -32164,7 +32261,7 @@ stores.inject(MyMetaStore, storeInstance);
32164
32261
  label: isFullScreen ? _t("Exit Full Screen") : _t("Full Screen"),
32165
32262
  class: `text-muted fa ${isFullScreen ? "fa-compress" : "fa-expand"}`,
32166
32263
  onClick: () => {
32167
- this.fullScreenFigureStore.toggleFullScreenChart(figureId);
32264
+ this.fullScreenFigureStore.toggleFullScreenFigure(figureId);
32168
32265
  },
32169
32266
  };
32170
32267
  }
@@ -32176,6 +32273,8 @@ stores.inject(MyMetaStore, storeInstance);
32176
32273
  figureUI: Object,
32177
32274
  onFigureDeleted: Function,
32178
32275
  editFigureStyle: { type: Function, optional: true },
32276
+ isFullScreen: { type: Boolean, optional: true },
32277
+ openContextMenu: { type: Function, optional: true },
32179
32278
  };
32180
32279
  static components = { ChartDashboardMenu, MenuPopover };
32181
32280
  carouselTabsRef = owl.useRef("carouselTabs");
@@ -32183,8 +32282,10 @@ stores.inject(MyMetaStore, storeInstance);
32183
32282
  menuState = owl.useState({ isOpen: false, anchorRect: null, menuItems: [] });
32184
32283
  hiddenItems = [];
32185
32284
  animationStore;
32285
+ fullScreenFigureStore;
32186
32286
  setup() {
32187
32287
  this.animationStore = useStore(ChartAnimationStore);
32288
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
32188
32289
  owl.useEffect(() => {
32189
32290
  if (this.selectedCarouselItem?.type === "carouselDataView") {
32190
32291
  this.props.editFigureStyle?.({ "pointer-events": "none" });
@@ -32231,7 +32332,8 @@ stores.inject(MyMetaStore, storeInstance);
32231
32332
  item,
32232
32333
  });
32233
32334
  if (item.type === "chart") {
32234
- this.animationStore?.enableAnimationForChart(item.chartId);
32335
+ const animationChartId = item.chartId + (this.props.isFullScreen ? "-fullscreen" : "");
32336
+ this.animationStore?.enableAnimationForChart(animationChartId);
32235
32337
  }
32236
32338
  }
32237
32339
  get headerStyle() {
@@ -32295,6 +32397,23 @@ stores.inject(MyMetaStore, storeInstance);
32295
32397
  this.menuState.anchorRect = rect;
32296
32398
  this.menuState.menuItems = createActions(menuItems);
32297
32399
  }
32400
+ toggleFullScreen() {
32401
+ if (this.selectedCarouselItem?.type === "chart") {
32402
+ this.fullScreenFigureStore.toggleFullScreenFigure(this.props.figureUI.id);
32403
+ }
32404
+ }
32405
+ get fullScreenButtonTitle() {
32406
+ return this.props.isFullScreen ? _t("Exit Full Screen") : _t("Full Screen");
32407
+ }
32408
+ get visibleCarouselItems() {
32409
+ return this.carousel.items.filter((item) => item.type === "carouselDataView" && this.props.isFullScreen ? false : true);
32410
+ }
32411
+ openContextMenu(event) {
32412
+ const target = event.currentTarget;
32413
+ if (target) {
32414
+ this.props.openContextMenu?.(getBoundingRectAsPOJO(target));
32415
+ }
32416
+ }
32298
32417
  }
32299
32418
 
32300
32419
  class ChartFigure extends owl.Component {
@@ -32303,6 +32422,8 @@ stores.inject(MyMetaStore, storeInstance);
32303
32422
  figureUI: Object,
32304
32423
  onFigureDeleted: Function,
32305
32424
  editFigureStyle: { type: Function, optional: true },
32425
+ isFullScreen: { type: Boolean, optional: true },
32426
+ openContextMenu: { type: Function, optional: true },
32306
32427
  };
32307
32428
  static components = { ChartDashboardMenu };
32308
32429
  onDoubleClick() {
@@ -32335,6 +32456,7 @@ stores.inject(MyMetaStore, storeInstance);
32335
32456
  figureUI: Object,
32336
32457
  onFigureDeleted: Function,
32337
32458
  editFigureStyle: { type: Function, optional: true },
32459
+ openContextMenu: { type: Function, optional: true },
32338
32460
  };
32339
32461
  static components = {};
32340
32462
  // ---------------------------------------------------------------------------
@@ -32405,9 +32527,7 @@ stores.inject(MyMetaStore, storeInstance);
32405
32527
  return this.isSelected ? ACTIVE_BORDER_WIDTH : this.borderWidth;
32406
32528
  }
32407
32529
  getBorderStyle(position) {
32408
- const borderWidth = this.getBorderWidth();
32409
- const borderColor = this.isSelected ? SELECTION_BORDER_COLOR : FIGURE_BORDER_COLOR;
32410
- return `border-${position}: ${borderWidth}px solid ${borderColor};`;
32530
+ return `border-${position}-width: ${this.getBorderWidth()}px;`;
32411
32531
  }
32412
32532
  get wrapperStyle() {
32413
32533
  const { x, y, width, height } = this.props.figureUI;
@@ -34139,8 +34259,11 @@ stores.inject(MyMetaStore, storeInstance);
34139
34259
  }
34140
34260
  const newSelection = this.contentHelper.getCurrentSelection();
34141
34261
  this.props.composerStore.stopComposerRangeSelection();
34142
- this.props.onComposerContentFocused();
34143
- this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34262
+ const isCurrentlyInactive = this.props.composerStore.editionMode === "inactive";
34263
+ this.props.onComposerContentFocused(newSelection);
34264
+ if (!isCurrentlyInactive) {
34265
+ this.props.composerStore.changeComposerCursorSelection(newSelection.start, newSelection.end);
34266
+ }
34144
34267
  this.processTokenAtCursor();
34145
34268
  }
34146
34269
  onDblClick() {
@@ -34635,13 +34758,6 @@ stores.inject(MyMetaStore, storeInstance);
34635
34758
  }
34636
34759
  }
34637
34760
  startEdition(text, selection) {
34638
- if (selection) {
34639
- const content = text || this.getComposerContent(this.getters.getActivePosition());
34640
- const validSelection = this.isSelectionValid(content.length, selection.start, selection.end);
34641
- if (!validSelection) {
34642
- return;
34643
- }
34644
- }
34645
34761
  const { col, row } = this.getters.getActivePosition();
34646
34762
  this.model.dispatch("SELECT_FIGURE", { figureId: null });
34647
34763
  this.model.dispatch("SCROLL_TO_CELL", { col, row });
@@ -34698,7 +34814,7 @@ stores.inject(MyMetaStore, storeInstance);
34698
34814
  // ---------------------------------------------------------------------------
34699
34815
  get currentContent() {
34700
34816
  if (this.editionMode === "inactive") {
34701
- return this.getComposerContent(this.getters.getActivePosition());
34817
+ return this.getComposerContent(this.getters.getActivePosition()).text;
34702
34818
  }
34703
34819
  return this._currentContent;
34704
34820
  }
@@ -34897,8 +35013,9 @@ stores.inject(MyMetaStore, storeInstance);
34897
35013
  this.sheetId = sheetId;
34898
35014
  this.row = row;
34899
35015
  this.editionMode = "editing";
34900
- this.initialContent = this.getComposerContent({ sheetId, col, row });
34901
- this.setContent(str || this.initialContent, selection);
35016
+ const { text, adjustedSelection } = this.getComposerContent({ sheetId, col, row }, selection);
35017
+ this.initialContent = text;
35018
+ this.setContent(str || this.initialContent, adjustedSelection ?? selection);
34902
35019
  this.colorIndexByRange = {};
34903
35020
  const zone = positionToZone({ col: this.col, row: this.row });
34904
35021
  this.captureSelection(zone, col, row);
@@ -35375,7 +35492,7 @@ stores.inject(MyMetaStore, storeInstance);
35375
35492
  constructor(get, args) {
35376
35493
  super(get);
35377
35494
  this.args = args;
35378
- this._currentContent = this.getComposerContent();
35495
+ this._currentContent = this.getComposerContent().text;
35379
35496
  }
35380
35497
  getAutoCompleteProviders() {
35381
35498
  const providersDefinitions = super.getAutoCompleteProviders();
@@ -35412,7 +35529,7 @@ stores.inject(MyMetaStore, storeInstance);
35412
35529
  })
35413
35530
  .join("");
35414
35531
  }
35415
- return localizeContent(content, this.getters.getLocale());
35532
+ return { text: localizeContent(content, this.getters.getLocale()) };
35416
35533
  }
35417
35534
  stopEdition() {
35418
35535
  this._stopEdition();
@@ -38632,6 +38749,74 @@ stores.inject(MyMetaStore, storeInstance);
38632
38749
  return path2D;
38633
38750
  }
38634
38751
 
38752
+ /**
38753
+ * Get the relative path between two files
38754
+ *
38755
+ * Eg.:
38756
+ * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
38757
+ */
38758
+ function getRelativePath(from, to) {
38759
+ const fromPathParts = from.split("/");
38760
+ const toPathParts = to.split("/");
38761
+ let relPath = "";
38762
+ let startIndex = 0;
38763
+ for (let i = 0; i < fromPathParts.length - 1; i++) {
38764
+ if (fromPathParts[i] === toPathParts[i]) {
38765
+ startIndex++;
38766
+ }
38767
+ else {
38768
+ relPath += "../";
38769
+ }
38770
+ }
38771
+ relPath += toPathParts.slice(startIndex).join("/");
38772
+ return relPath;
38773
+ }
38774
+ /**
38775
+ * Convert an array of element into an object where the objects keys were the elements position in the array.
38776
+ * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
38777
+ *
38778
+ * eg. : ["a", "b"] => {0:"a", 1:"b"}
38779
+ */
38780
+ function arrayToObject(array, indexOffset = 0) {
38781
+ const obj = {};
38782
+ for (let i = 0; i < array.length; i++) {
38783
+ if (array[i]) {
38784
+ obj[i + indexOffset] = array[i];
38785
+ }
38786
+ }
38787
+ return obj;
38788
+ }
38789
+ /**
38790
+ * In xlsx we can have string with unicode characters with the format _x00fa_.
38791
+ * Replace with characters understandable by JS
38792
+ */
38793
+ function fixXlsxUnicode(str) {
38794
+ return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
38795
+ return String.fromCharCode(parseInt(code, 16));
38796
+ });
38797
+ }
38798
+ /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
38799
+ function getSheetDataHeader(sheetData, dimension, index) {
38800
+ if (dimension === "COL") {
38801
+ if (!sheetData.cols[index]) {
38802
+ sheetData.cols[index] = {};
38803
+ }
38804
+ return sheetData.cols[index];
38805
+ }
38806
+ if (!sheetData.rows[index]) {
38807
+ sheetData.rows[index] = {};
38808
+ }
38809
+ return sheetData.rows[index];
38810
+ }
38811
+ /** Prefix the string by "=" if the string looks like a formula */
38812
+ function prefixFormulaWithEqual(formula) {
38813
+ if (formula[0] === "=") {
38814
+ return formula;
38815
+ }
38816
+ const tokens = tokenize(formula);
38817
+ return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
38818
+ }
38819
+
38635
38820
  /**
38636
38821
  * Map of the different types of conversions warnings and their name in error messages
38637
38822
  */
@@ -39154,66 +39339,6 @@ stores.inject(MyMetaStore, storeInstance);
39154
39339
  */
39155
39340
  const DEFAULT_SYSTEM_COLOR = "FF000000";
39156
39341
 
39157
- /**
39158
- * Get the relative path between two files
39159
- *
39160
- * Eg.:
39161
- * from "folder1/file1.txt" to "folder2/file2.txt" => "../folder2/file2.txt"
39162
- */
39163
- function getRelativePath(from, to) {
39164
- const fromPathParts = from.split("/");
39165
- const toPathParts = to.split("/");
39166
- let relPath = "";
39167
- let startIndex = 0;
39168
- for (let i = 0; i < fromPathParts.length - 1; i++) {
39169
- if (fromPathParts[i] === toPathParts[i]) {
39170
- startIndex++;
39171
- }
39172
- else {
39173
- relPath += "../";
39174
- }
39175
- }
39176
- relPath += toPathParts.slice(startIndex).join("/");
39177
- return relPath;
39178
- }
39179
- /**
39180
- * Convert an array of element into an object where the objects keys were the elements position in the array.
39181
- * Can give an offset as argument, and all the array indexes will we shifted by this offset in the returned object.
39182
- *
39183
- * eg. : ["a", "b"] => {0:"a", 1:"b"}
39184
- */
39185
- function arrayToObject(array, indexOffset = 0) {
39186
- const obj = {};
39187
- for (let i = 0; i < array.length; i++) {
39188
- if (array[i]) {
39189
- obj[i + indexOffset] = array[i];
39190
- }
39191
- }
39192
- return obj;
39193
- }
39194
- /**
39195
- * In xlsx we can have string with unicode characters with the format _x00fa_.
39196
- * Replace with characters understandable by JS
39197
- */
39198
- function fixXlsxUnicode(str) {
39199
- return str.replace(/_x([0-9a-zA-Z]{4})_/g, (match, code) => {
39200
- return String.fromCharCode(parseInt(code, 16));
39201
- });
39202
- }
39203
- /** Get a header in the SheetData. Create the header if it doesn't exist in the SheetData */
39204
- function getSheetDataHeader(sheetData, dimension, index) {
39205
- if (dimension === "COL") {
39206
- if (!sheetData.cols[index]) {
39207
- sheetData.cols[index] = {};
39208
- }
39209
- return sheetData.cols[index];
39210
- }
39211
- if (!sheetData.rows[index]) {
39212
- sheetData.rows[index] = {};
39213
- }
39214
- return sheetData.rows[index];
39215
- }
39216
-
39217
39342
  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;
39218
39343
  /**
39219
39344
  * Convert excel format to o_spreadsheet format
@@ -39428,9 +39553,9 @@ stores.inject(MyMetaStore, storeInstance);
39428
39553
  if (!rule.operator || !rule.formula || rule.formula.length === 0)
39429
39554
  continue;
39430
39555
  operator = CF_OPERATOR_TYPE_CONVERSION_MAP[rule.operator];
39431
- values.push(prefixFormula(rule.formula[0]));
39556
+ values.push(prefixFormulaWithEqual(rule.formula[0]));
39432
39557
  if (rule.formula.length === 2) {
39433
- values.push(prefixFormula(rule.formula[1]));
39558
+ values.push(prefixFormulaWithEqual(rule.formula[1]));
39434
39559
  }
39435
39560
  break;
39436
39561
  }
@@ -39588,11 +39713,6 @@ stores.inject(MyMetaStore, storeInstance);
39588
39713
  ? ICON_SETS[iconSet].neutral
39589
39714
  : ICON_SETS[iconSet].good;
39590
39715
  }
39591
- /** Prefix the string by "=" if the string looks like a formula */
39592
- function prefixFormula(formula) {
39593
- const tokens = tokenize(formula);
39594
- return tokens.length === 1 && tokens[0].type !== "REFERENCE" ? formula : "=" + formula;
39595
- }
39596
39716
  // ---------------------------------------------------------------------------
39597
39717
  // Warnings
39598
39718
  // ---------------------------------------------------------------------------
@@ -40068,7 +40188,7 @@ stores.inject(MyMetaStore, storeInstance);
40068
40188
  dvRules.push(decimalRule);
40069
40189
  break;
40070
40190
  case "list":
40071
- const listRule = convertListrule(dvId++, dv);
40191
+ const listRule = convertListRule(dvId++, dv);
40072
40192
  dvRules.push(listRule);
40073
40193
  break;
40074
40194
  case "date":
@@ -40088,9 +40208,9 @@ stores.inject(MyMetaStore, storeInstance);
40088
40208
  return dvRules;
40089
40209
  }
40090
40210
  function convertDecimalRule(id, dv) {
40091
- const values = [dv.formula1.toString()];
40211
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40092
40212
  if (dv.formula2) {
40093
- values.push(dv.formula2.toString());
40213
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40094
40214
  }
40095
40215
  return {
40096
40216
  id: id.toString(),
@@ -40102,7 +40222,7 @@ stores.inject(MyMetaStore, storeInstance);
40102
40222
  },
40103
40223
  };
40104
40224
  }
40105
- function convertListrule(id, dv) {
40225
+ function convertListRule(id, dv) {
40106
40226
  const formula1 = dv.formula1.toString();
40107
40227
  const isRangeRule = rangeReference.test(formula1);
40108
40228
  return {
@@ -40118,9 +40238,9 @@ stores.inject(MyMetaStore, storeInstance);
40118
40238
  }
40119
40239
  function convertDateRule(id, dv) {
40120
40240
  let criterion;
40121
- const values = [dv.formula1.toString()];
40241
+ const values = [prefixFormulaWithEqual(dv.formula1.toString())];
40122
40242
  if (dv.formula2) {
40123
- values.push(dv.formula2.toString());
40243
+ values.push(prefixFormulaWithEqual(dv.formula2.toString()));
40124
40244
  criterion = {
40125
40245
  type: XLSX_DV_DATE_OPERATOR_TO_DV_TYPE_MAPPING[dv.operator],
40126
40246
  values: getDateCriterionFormattedValues(values, DEFAULT_LOCALE),
@@ -40147,7 +40267,7 @@ stores.inject(MyMetaStore, storeInstance);
40147
40267
  isBlocking: dv.errorStyle !== "warning",
40148
40268
  criterion: {
40149
40269
  type: "customFormula",
40150
- values: [`=${dv.formula1.toString()}`],
40270
+ values: [prefixFormulaWithEqual(dv.formula1.toString())],
40151
40271
  },
40152
40272
  };
40153
40273
  }
@@ -45921,6 +46041,18 @@ stores.inject(MyMetaStore, storeInstance);
45921
46041
  get numberOfCells() {
45922
46042
  return this.rows.length * this.getNumberOfDataColumns();
45923
46043
  }
46044
+ getColumnDomainsAtDepth(depth) {
46045
+ if (depth < 0 || depth >= this.columns.length - 1) {
46046
+ return [];
46047
+ }
46048
+ return this.columns[depth].map((col) => this.getDomain(col)).filter((d) => d.length);
46049
+ }
46050
+ getRowDomainsAtDepth(depth) {
46051
+ if (depth < 0 || depth > this.maxIndent) {
46052
+ return [];
46053
+ }
46054
+ return this.rows.filter((row) => row.indent === depth + 1).map((row) => this.getDomain(row));
46055
+ }
45924
46056
  }
45925
46057
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
45926
46058
 
@@ -46913,6 +47045,109 @@ stores.inject(MyMetaStore, storeInstance);
46913
47045
  return areFieldValuesInGroups(definition, values, field, pivot.getFields());
46914
47046
  },
46915
47047
  };
47048
+ const toggleCollapsePivotGroupAction = {
47049
+ name: (env) => {
47050
+ const position = env.model.getters.getActivePosition();
47051
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47052
+ if (pivotCellState.isPivotGroup) {
47053
+ return pivotCellState.isCollapsed ? _t("Expand") : _t("Collapse");
47054
+ }
47055
+ return "";
47056
+ },
47057
+ execute(env) {
47058
+ const position = env.model.getters.getActivePosition();
47059
+ togglePivotCollapse(position, env);
47060
+ },
47061
+ isVisible: (env) => {
47062
+ const position = env.model.getters.getActivePosition();
47063
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47064
+ return pivotCellState.isPivotGroup;
47065
+ },
47066
+ };
47067
+ const collapseAllPivotGroupAction = {
47068
+ name: _t("Collapse all"),
47069
+ execute(env) {
47070
+ const position = env.model.getters.getActivePosition();
47071
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47072
+ if (!pivotCellState.isPivotGroup) {
47073
+ return;
47074
+ }
47075
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47076
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47077
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47078
+ const newCollapsed = [
47079
+ ...(definition.collapsedDomains[pivotCell.dimension] || []),
47080
+ ...siblingDomains,
47081
+ ];
47082
+ const filteredCollapsed = newCollapsed.filter((domain, index) => index === newCollapsed.findIndex((d) => deepEquals(d, domain)));
47083
+ definition.collapsedDomains[pivotCell.dimension] = filteredCollapsed;
47084
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47085
+ },
47086
+ isVisible: (env) => {
47087
+ const position = env.model.getters.getActivePosition();
47088
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47089
+ if (!pivotCellState.isPivotGroup) {
47090
+ return false;
47091
+ }
47092
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47093
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47094
+ return !siblingDomains.every((domain) => (definition.collapsedDomains?.[pivotCell.dimension] || []).some((d) => deepEquals(d, domain)));
47095
+ },
47096
+ };
47097
+ const expandAllPivotGroupAction = {
47098
+ name: _t("Expand all"),
47099
+ execute(env) {
47100
+ const position = env.model.getters.getActivePosition();
47101
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47102
+ if (!pivotCellState.isPivotGroup) {
47103
+ return;
47104
+ }
47105
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47106
+ const definition = deepCopy(env.model.getters.getPivotCoreDefinition(pivotId));
47107
+ definition.collapsedDomains = definition.collapsedDomains || { COL: [], ROW: [] };
47108
+ const domains = definition.collapsedDomains[pivotCell.dimension] || [];
47109
+ const filteredDomains = domains.filter((domain) => !siblingDomains.find((d) => deepEquals(d, domain)));
47110
+ definition.collapsedDomains[pivotCell.dimension] = filteredDomains;
47111
+ env.model.dispatch("UPDATE_PIVOT", { pivotId, pivot: definition });
47112
+ },
47113
+ isVisible: (env) => {
47114
+ const position = env.model.getters.getActivePosition();
47115
+ const pivotCellState = getPivotCellCollapseState(env.model.getters, position);
47116
+ if (!pivotCellState.isPivotGroup) {
47117
+ return false;
47118
+ }
47119
+ const { pivotCell, pivotId, siblingDomains } = pivotCellState;
47120
+ const definition = env.model.getters.getPivotCoreDefinition(pivotId);
47121
+ const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension] || [];
47122
+ return collapsedDomains.some((domain) => siblingDomains.some((d) => deepEquals(d, domain)));
47123
+ },
47124
+ };
47125
+ function getPivotCellCollapseState(getters, position) {
47126
+ if (!getters.isSpillPivotFormula(position)) {
47127
+ return { isPivotGroup: false };
47128
+ }
47129
+ const pivotCell = getters.getPivotCellFromPosition(position);
47130
+ const pivotId = getters.getPivotIdFromPosition(position);
47131
+ if (pivotCell.type !== "HEADER" || !pivotId || !pivotCell.domain.length) {
47132
+ return { isPivotGroup: false };
47133
+ }
47134
+ const definition = getters.getPivotCoreDefinition(pivotId);
47135
+ const isDashboard = getters.isDashboard();
47136
+ const fields = pivotCell.dimension === "COL" ? definition.columns : definition.rows;
47137
+ const hasIcon = !isDashboard && pivotCell.domain.length !== fields.length;
47138
+ if (!hasIcon) {
47139
+ return { isPivotGroup: false };
47140
+ }
47141
+ const domains = definition.collapsedDomains?.[pivotCell.dimension] ?? [];
47142
+ const isCollapsed = domains.some((domain) => deepEquals(domain, pivotCell.domain));
47143
+ const pivot = getters.getPivot(pivotId);
47144
+ const table = pivot.getExpandedTableStructure();
47145
+ const depth = pivotCell.domain.length - 1;
47146
+ const siblingDomains = pivotCell.dimension === "ROW"
47147
+ ? table.getRowDomainsAtDepth(depth)
47148
+ : table.getColumnDomainsAtDepth(depth);
47149
+ return { isPivotGroup: true, isCollapsed, pivotCell, pivotId, siblingDomains };
47150
+ }
46916
47151
  function canSortPivot(getters, position) {
46917
47152
  const pivotId = getters.getPivotIdFromPosition(position);
46918
47153
  if (!pivotId || !getters.isExistingPivot(pivotId) || !getters.isSpillPivotFormula(position)) {
@@ -47248,6 +47483,23 @@ stores.inject(MyMetaStore, storeInstance);
47248
47483
  sequence: 155,
47249
47484
  icon: "o-spreadsheet-Icon.MINUS_IN_BOX",
47250
47485
  ...ungroupPivotHeadersAction,
47486
+ })
47487
+ .add("collapse_pivot", {
47488
+ sequence: 156,
47489
+ name: _t("Expand/Collapse"),
47490
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47491
+ })
47492
+ .addChild("toggle_collapse_pivot_cell", ["collapse_pivot"], {
47493
+ sequence: 10,
47494
+ ...toggleCollapsePivotGroupAction,
47495
+ })
47496
+ .addChild("collapse_all_pivot", ["collapse_pivot"], {
47497
+ sequence: 20,
47498
+ ...collapseAllPivotGroupAction,
47499
+ })
47500
+ .addChild("expand_all_pivot", ["collapse_pivot"], {
47501
+ sequence: 30,
47502
+ ...expandAllPivotGroupAction,
47251
47503
  })
47252
47504
  .add("pivot_sorting", {
47253
47505
  name: _t("Sort pivot"),
@@ -47872,6 +48124,7 @@ stores.inject(MyMetaStore, storeInstance);
47872
48124
  execute: (env) => {
47873
48125
  env.model.dispatch("UNFOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47874
48126
  },
48127
+ icon: "o-spreadsheet-Icon.EXPAND",
47875
48128
  },
47876
48129
  {
47877
48130
  id: "fold_all",
@@ -47879,6 +48132,7 @@ stores.inject(MyMetaStore, storeInstance);
47879
48132
  execute: (env) => {
47880
48133
  env.model.dispatch("FOLD_ALL_HEADER_GROUPS", { sheetId, dimension });
47881
48134
  },
48135
+ icon: "o-spreadsheet-Icon.COLLAPSE",
47882
48136
  },
47883
48137
  ]);
47884
48138
  }
@@ -47900,6 +48154,11 @@ stores.inject(MyMetaStore, storeInstance);
47900
48154
  const sheetId = env.model.getters.getActiveSheetId();
47901
48155
  interactiveToggleGroup(env, sheetId, dimension, start, end);
47902
48156
  },
48157
+ icon: (env) => {
48158
+ const sheetId = env.model.getters.getActiveSheetId();
48159
+ const groupIsFolded = env.model.getters.isGroupFolded(sheetId, dimension, start, end);
48160
+ return groupIsFolded ? "o-spreadsheet-Icon.EXPAND" : "o-spreadsheet-Icon.COLLAPSE";
48161
+ },
47903
48162
  },
47904
48163
  {
47905
48164
  id: "remove_group",
@@ -47908,6 +48167,7 @@ stores.inject(MyMetaStore, storeInstance);
47908
48167
  const sheetId = env.model.getters.getActiveSheetId();
47909
48168
  env.model.dispatch("UNGROUP_HEADERS", { sheetId, dimension, start, end });
47910
48169
  },
48170
+ icon: "o-spreadsheet-Icon.TRASH",
47911
48171
  separator: true,
47912
48172
  },
47913
48173
  ]);
@@ -48770,39 +49030,66 @@ stores.inject(MyMetaStore, storeInstance);
48770
49030
  this.model.dispatch("AUTOFILL_TABLE_COLUMN", { ...this.currentEditedCell });
48771
49031
  this.setContent("");
48772
49032
  }
48773
- getComposerContent(position) {
49033
+ getComposerContent(position, selection) {
48774
49034
  const locale = this.getters.getLocale();
48775
49035
  const cell = this.getters.getCell(position);
48776
49036
  if (cell?.isFormula) {
48777
49037
  const prettifiedContent = this.getPrettifiedFormula(cell);
48778
- return localizeFormula(prettifiedContent, locale);
49038
+ // when a formula is prettified (multi lines, indented), adapt the cursor position
49039
+ // to take into account line breaks and tabs
49040
+ function adjustCursorIndex(targetIndex) {
49041
+ let adjustedIndex = 0;
49042
+ let originalIndex = 0;
49043
+ while (originalIndex < targetIndex) {
49044
+ adjustedIndex++;
49045
+ const char = prettifiedContent[adjustedIndex];
49046
+ if (char !== "\n" && char !== "\t") {
49047
+ originalIndex++;
49048
+ }
49049
+ }
49050
+ return adjustedIndex;
49051
+ }
49052
+ let adjustedSelection = selection;
49053
+ if (selection) {
49054
+ adjustedSelection = {
49055
+ start: adjustCursorIndex(selection.start),
49056
+ end: adjustCursorIndex(selection.end),
49057
+ };
49058
+ }
49059
+ return {
49060
+ text: localizeFormula(prettifiedContent, locale),
49061
+ adjustedSelection,
49062
+ };
48779
49063
  }
48780
49064
  const spreader = this.model.getters.getArrayFormulaSpreadingOn(position);
48781
49065
  if (spreader) {
48782
- return "";
49066
+ return { text: "" };
49067
+ }
49068
+ if (cell?.content.startsWith("'")) {
49069
+ return { text: cell.content };
48783
49070
  }
48784
49071
  const { format, value, type, formattedValue } = this.getters.getEvaluatedCell(position);
48785
49072
  switch (type) {
48786
49073
  case CellValueType.empty:
48787
- return "";
49074
+ return { text: "" };
48788
49075
  case CellValueType.text:
48789
49076
  case CellValueType.error:
48790
- return value;
49077
+ return { text: value };
48791
49078
  case CellValueType.boolean:
48792
- return formattedValue;
49079
+ return { text: formattedValue };
48793
49080
  case CellValueType.number:
48794
49081
  if (format && isDateTimeFormat(format)) {
48795
49082
  if (parseDateTime(formattedValue, locale) !== null) {
48796
49083
  // formatted string can be parsed again
48797
- return formattedValue;
49084
+ return { text: formattedValue };
48798
49085
  }
48799
49086
  // display a simplified and parsable string otherwise
48800
49087
  const timeFormat = Number.isInteger(value)
48801
49088
  ? locale.dateFormat
48802
49089
  : getDateTimeFormat(locale);
48803
- return formatValue(value, { locale, format: timeFormat });
49090
+ return { text: formatValue(value, { locale, format: timeFormat }) };
48804
49091
  }
48805
- return this.numberComposerContent(value, format, locale);
49092
+ return { text: this.numberComposerContent(value, format, locale) };
48806
49093
  }
48807
49094
  }
48808
49095
  getPrettifiedFormula(cell) {
@@ -48949,8 +49236,9 @@ stores.inject(MyMetaStore, storeInstance);
48949
49236
  },
48950
49237
  focus: this.focus,
48951
49238
  isDefaultFocus: true,
48952
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
49239
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
48953
49240
  focusMode: "contentFocus",
49241
+ selection,
48954
49242
  }),
48955
49243
  onComposerCellFocused: (content) => this.composerFocusStore.focusComposer(this.composerInterface, {
48956
49244
  focusMode: "cellFocus",
@@ -51343,6 +51631,9 @@ stores.inject(MyMetaStore, storeInstance);
51343
51631
  }
51344
51632
  const { align } = this.getters.getCellStyle(position);
51345
51633
  const evaluatedCell = this.getters.getEvaluatedCell(position);
51634
+ if (formatHasRepeatedChar(evaluatedCell.value, evaluatedCell.format)) {
51635
+ return "left";
51636
+ }
51346
51637
  if (isOverflowing && evaluatedCell.type === CellValueType.number) {
51347
51638
  return align !== "center" ? "left" : align;
51348
51639
  }
@@ -55635,12 +55926,13 @@ stores.inject(MyMetaStore, storeInstance);
55635
55926
  onCloseSidePanel: { type: Function, optional: true },
55636
55927
  };
55637
55928
  state = owl.useState({ rule: this.defaultDataValidationRule, errors: [] });
55929
+ editingSheetId;
55638
55930
  setup() {
55931
+ this.editingSheetId = this.env.model.getters.getActiveSheetId();
55639
55932
  if (this.props.rule) {
55640
- const sheetId = this.env.model.getters.getActiveSheetId();
55641
55933
  this.state.rule = {
55642
55934
  ...this.props.rule,
55643
- ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, sheetId)),
55935
+ ranges: this.props.rule.ranges.map((range) => this.env.model.getters.getRangeString(range, this.editingSheetId)),
55644
55936
  };
55645
55937
  this.state.rule.criterion.type = this.props.rule.criterion.type;
55646
55938
  }
@@ -55674,7 +55966,6 @@ stores.inject(MyMetaStore, storeInstance);
55674
55966
  const locale = this.env.model.getters.getLocale();
55675
55967
  const criterion = rule.criterion;
55676
55968
  const criterionEvaluator = criterionEvaluatorRegistry.get(criterion.type);
55677
- const sheetId = this.env.model.getters.getActiveSheetId();
55678
55969
  const values = criterion.values
55679
55970
  .slice(0, criterionEvaluator.numberOfValues(criterion))
55680
55971
  .map((value) => value?.trim())
@@ -55682,8 +55973,8 @@ stores.inject(MyMetaStore, storeInstance);
55682
55973
  .map((value) => canonicalizeContent(value, locale));
55683
55974
  rule.criterion = { ...criterion, values };
55684
55975
  return {
55685
- sheetId,
55686
- ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(sheetId, xc)),
55976
+ sheetId: this.editingSheetId,
55977
+ ranges: this.state.rule.ranges.map((xc) => this.env.model.getters.getRangeDataFromXc(this.editingSheetId, xc)),
55687
55978
  rule,
55688
55979
  };
55689
55980
  }
@@ -56814,7 +57105,7 @@ stores.inject(MyMetaStore, storeInstance);
56814
57105
  return undefined;
56815
57106
  }
56816
57107
  get isCalculatedMeasureInvalid() {
56817
- return this.env.model.getters.getMeasureCompiledFormula(this.props.measure).isBadExpression;
57108
+ return compile(this.props.measure.computedBy?.formula ?? "").isBadExpression;
56818
57109
  }
56819
57110
  }
56820
57111
 
@@ -59529,16 +59820,16 @@ stores.inject(MyMetaStore, storeInstance);
59529
59820
  }
59530
59821
  }
59531
59822
 
59532
- class FullScreenChart extends owl.Component {
59533
- static template = "o-spreadsheet-FullScreenChart";
59823
+ class FullScreenFigure extends owl.Component {
59824
+ static template = "o-spreadsheet-FullScreenFigure";
59534
59825
  static props = {};
59535
- static components = { ChartDashboardMenu };
59536
- fullScreenChartStore;
59537
- ref = owl.useRef("fullScreenChart");
59826
+ static components = { ChartFigure };
59827
+ fullScreenFigureStore;
59828
+ ref = owl.useRef("fullScreenFigure");
59538
59829
  spreadsheetRect = useSpreadsheetRect();
59539
59830
  figureRegistry = figureRegistry;
59540
59831
  setup() {
59541
- this.fullScreenChartStore = useStore(FullScreenChartStore);
59832
+ this.fullScreenFigureStore = useStore(FullScreenFigureStore);
59542
59833
  const animationStore = useStore(ChartAnimationStore);
59543
59834
  let lastFigureId = undefined;
59544
59835
  owl.onWillUpdateProps(() => {
@@ -59550,7 +59841,7 @@ stores.inject(MyMetaStore, storeInstance);
59550
59841
  owl.useEffect((el) => el?.focus(), () => [this.ref.el]);
59551
59842
  }
59552
59843
  get figureUI() {
59553
- return this.fullScreenChartStore.fullScreenFigure;
59844
+ return this.fullScreenFigureStore.fullScreenFigure;
59554
59845
  }
59555
59846
  get chartId() {
59556
59847
  if (!this.figureUI)
@@ -59559,7 +59850,7 @@ stores.inject(MyMetaStore, storeInstance);
59559
59850
  }
59560
59851
  exitFullScreen() {
59561
59852
  if (this.figureUI) {
59562
- this.fullScreenChartStore.toggleFullScreenChart(this.figureUI.id);
59853
+ this.fullScreenFigureStore.toggleFullScreenFigure(this.figureUI.id);
59563
59854
  }
59564
59855
  }
59565
59856
  onKeyDown(ev) {
@@ -59567,15 +59858,10 @@ stores.inject(MyMetaStore, storeInstance);
59567
59858
  this.exitFullScreen();
59568
59859
  }
59569
59860
  }
59570
- get chartComponent() {
59571
- if (!this.chartId)
59861
+ get figureComponent() {
59862
+ if (!this.figureUI)
59572
59863
  return undefined;
59573
- const type = this.env.model.getters.getChartType(this.chartId);
59574
- const component = chartComponentRegistry.get(type);
59575
- if (!component) {
59576
- throw new Error(`Component is not defined for type ${type}`);
59577
- }
59578
- return component;
59864
+ return figureRegistry.get(this.figureUI.tag).Component;
59579
59865
  }
59580
59866
  }
59581
59867
 
@@ -60798,7 +61084,7 @@ stores.inject(MyMetaStore, storeInstance);
60798
61084
  (typeof parsedValue === "number"
60799
61085
  ? detectDateFormat(content, locale) || detectNumberFormat(content)
60800
61086
  : undefined);
60801
- if (!isTextFormat(format) && !isEvaluationError(content)) {
61087
+ if (!isTextFormat(format) && !content.startsWith("'") && !isEvaluationError(content)) {
60802
61088
  content = toString(parsedValue);
60803
61089
  }
60804
61090
  return {
@@ -62278,11 +62564,11 @@ stores.inject(MyMetaStore, storeInstance);
62278
62564
  break;
62279
62565
  }
62280
62566
  case "ADD_COLUMNS_ROWS": {
62281
- const sizes = [...this.sizes[cmd.sheetId][cmd.dimension]];
62567
+ const sizes = this.sizes[cmd.sheetId][cmd.dimension];
62282
62568
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
62283
62569
  const baseSize = sizes[cmd.base];
62284
- sizes.splice(addIndex, 0, ...Array(cmd.quantity).fill(baseSize));
62285
- this.history.update("sizes", cmd.sheetId, cmd.dimension, sizes);
62570
+ const newSizes = insertItemsAtIndex(sizes, Array(cmd.quantity).fill(baseSize), addIndex);
62571
+ this.history.update("sizes", cmd.sheetId, cmd.dimension, newSizes);
62286
62572
  break;
62287
62573
  }
62288
62574
  case "RESIZE_COLUMNS_ROWS":
@@ -62433,9 +62719,8 @@ stores.inject(MyMetaStore, storeInstance);
62433
62719
  break;
62434
62720
  }
62435
62721
  case "ADD_COLUMNS_ROWS": {
62436
- const hiddenHeaders = [...this.hiddenHeaders[cmd.sheetId][cmd.dimension]];
62437
62722
  const addIndex = getAddHeaderStartIndex(cmd.position, cmd.base);
62438
- hiddenHeaders.splice(addIndex, 0, ...Array(cmd.quantity).fill(false));
62723
+ const hiddenHeaders = insertItemsAtIndex([...this.hiddenHeaders[cmd.sheetId][cmd.dimension]], Array(cmd.quantity).fill(false), addIndex);
62439
62724
  this.history.update("hiddenHeaders", cmd.sheetId, cmd.dimension, hiddenHeaders);
62440
62725
  break;
62441
62726
  }
@@ -66619,12 +66904,12 @@ stores.inject(MyMetaStore, storeInstance);
66619
66904
  this.rTrees[sheetId].remove(item, this.rtreeItemComparer);
66620
66905
  }
66621
66906
  rtreeItemComparer(left, right) {
66622
- return (left.data === right.data &&
66623
- left.boundingBox.sheetId === right.boundingBox.sheetId &&
66907
+ return (left.boundingBox.sheetId === right.boundingBox.sheetId &&
66624
66908
  left.boundingBox?.zone.left === right.boundingBox.zone.left &&
66625
66909
  left.boundingBox?.zone.top === right.boundingBox.zone.top &&
66626
66910
  left.boundingBox?.zone.right === right.boundingBox.zone.right &&
66627
- left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom);
66911
+ left.boundingBox?.zone.bottom === right.boundingBox.zone.bottom &&
66912
+ deepEquals(left.data, right.data));
66628
66913
  }
66629
66914
  }
66630
66915
  /**
@@ -66697,7 +66982,7 @@ stores.inject(MyMetaStore, storeInstance);
66697
66982
  * in the correct order they should be evaluated.
66698
66983
  * This is called a topological ordering (excluding cycles)
66699
66984
  */
66700
- getCellsDependingOn(ranges) {
66985
+ getCellsDependingOn(ranges, ignore) {
66701
66986
  const visited = this.createEmptyPositionSet();
66702
66987
  const queue = Array.from(ranges).reverse();
66703
66988
  while (queue.length > 0) {
@@ -66712,7 +66997,7 @@ stores.inject(MyMetaStore, storeInstance);
66712
66997
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
66713
66998
  const nextInQueue = {};
66714
66999
  for (const position of impactedPositions) {
66715
- if (!visited.has(position)) {
67000
+ if (!visited.has(position) && !ignore.has(position)) {
66716
67001
  if (!nextInQueue[position.sheetId]) {
66717
67002
  nextInQueue[position.sheetId] = [];
66718
67003
  }
@@ -67270,7 +67555,7 @@ stores.inject(MyMetaStore, storeInstance);
67270
67555
  }
67271
67556
  invalidatePositionsDependingOnSpread(sheetId, resultZone) {
67272
67557
  // the result matrix is split in 2 zones to exclude the array formula position
67273
- const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })));
67558
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn(excludeTopLeft(resultZone).map((zone) => ({ sheetId, zone })), this.nextPositionsToUpdate);
67274
67559
  invalidatedPositions.delete({ sheetId, col: resultZone.left, row: resultZone.top });
67275
67560
  this.nextPositionsToUpdate.addMany(invalidatedPositions);
67276
67561
  }
@@ -67388,7 +67673,7 @@ stores.inject(MyMetaStore, storeInstance);
67388
67673
  for (const sheetId in zonesBySheetIds) {
67389
67674
  ranges.push(...zonesBySheetIds[sheetId].map((zone) => ({ sheetId, zone })));
67390
67675
  }
67391
- return this.formulaDependencies().getCellsDependingOn(ranges);
67676
+ return this.formulaDependencies().getCellsDependingOn(ranges, this.nextPositionsToUpdate);
67392
67677
  }
67393
67678
  }
67394
67679
  function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
@@ -68655,32 +68940,6 @@ stores.inject(MyMetaStore, storeInstance);
68655
68940
  }
68656
68941
  return undefined;
68657
68942
  });
68658
- function togglePivotCollapse(position, env) {
68659
- const pivotCell = env.model.getters.getPivotCellFromPosition(position);
68660
- const pivotId = env.model.getters.getPivotIdFromPosition(position);
68661
- if (!pivotId || pivotCell.type !== "HEADER") {
68662
- return;
68663
- }
68664
- const definition = env.model.getters.getPivotCoreDefinition(pivotId);
68665
- const collapsedDomains = definition.collapsedDomains?.[pivotCell.dimension]
68666
- ? [...definition.collapsedDomains[pivotCell.dimension]]
68667
- : [];
68668
- const index = collapsedDomains.findIndex((domain) => deepEquals(domain, pivotCell.domain));
68669
- if (index !== -1) {
68670
- collapsedDomains.splice(index, 1);
68671
- }
68672
- else {
68673
- collapsedDomains.push(pivotCell.domain);
68674
- }
68675
- const newDomains = definition.collapsedDomains
68676
- ? { ...definition.collapsedDomains }
68677
- : { COL: [], ROW: [] };
68678
- newDomains[pivotCell.dimension] = collapsedDomains;
68679
- env.model.dispatch("UPDATE_PIVOT", {
68680
- pivotId,
68681
- pivot: { ...definition, collapsedDomains: newDomains },
68682
- });
68683
- }
68684
68943
  iconsOnCellRegistry.add("pivot_dashboard_sorting", (getters, position) => {
68685
68944
  if (!getters.isDashboard()) {
68686
68945
  return undefined;
@@ -68899,7 +69158,8 @@ stores.inject(MyMetaStore, storeInstance);
68899
69158
  const topLeft = { col: unionZone.left, row: unionZone.top, sheetId };
68900
69159
  const parentSpreadingCell = this.getters.getArrayFormulaSpreadingOn(topLeft);
68901
69160
  if (!parentSpreadingCell) {
68902
- return false;
69161
+ const evaluatedCell = this.getters.getEvaluatedCell(topLeft);
69162
+ return (evaluatedCell.value === CellErrorType.SpilledBlocked && !evaluatedCell.errorOriginPosition);
68903
69163
  }
68904
69164
  else if (deepEquals(parentSpreadingCell, topLeft) && getZoneArea(unionZone) === 1) {
68905
69165
  return true;
@@ -79472,8 +79732,8 @@ stores.inject(MyMetaStore, storeInstance);
79472
79732
  left: `calc(50% - 1px)`, // -1px: we want the border to be on the center
79473
79733
  width: `30%`,
79474
79734
  height: `calc(100% - ${groupBox.headerRect.height / 2}px)`,
79475
- "border-left": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79476
- "border-bottom": groupBox.isEndHidden ? "" : `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79735
+ "border-left": `1px solid`,
79736
+ "border-bottom": groupBox.isEndHidden ? "" : `1px solid`,
79477
79737
  });
79478
79738
  }
79479
79739
  get groupHeaderStyle() {
@@ -79525,8 +79785,8 @@ stores.inject(MyMetaStore, storeInstance);
79525
79785
  left: `${groupBox.headerRect.width / 2}px`,
79526
79786
  width: `calc(100% - ${groupBox.headerRect.width / 2}px)`,
79527
79787
  height: `30%`,
79528
- "border-top": `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79529
- "border-right": groupBox.isEndHidden ? "" : `1px solid ${HEADER_GROUPING_BORDER_COLOR}`,
79788
+ "border-top": `1px solid`,
79789
+ "border-right": groupBox.isEndHidden ? "" : `1px solid`,
79530
79790
  });
79531
79791
  }
79532
79792
  get groupHeaderStyle() {
@@ -79765,6 +80025,7 @@ stores.inject(MyMetaStore, storeInstance);
79765
80025
  static components = { Menu };
79766
80026
  rootItems = topbarMenuRegistry.getMenuItems();
79767
80027
  menuRef = owl.useRef("menu");
80028
+ containerRef = owl.useRef("container");
79768
80029
  state = owl.useState({
79769
80030
  menuItems: this.rootItems,
79770
80031
  title: _t("Menu Bar"),
@@ -79772,6 +80033,7 @@ stores.inject(MyMetaStore, storeInstance);
79772
80033
  });
79773
80034
  setup() {
79774
80035
  owl.useExternalListener(window, "click", this.onExternalClick, { capture: true });
80036
+ owl.onMounted(this.updateShadows);
79775
80037
  }
79776
80038
  onExternalClick(ev) {
79777
80039
  if (!this.menuRef.el?.contains(ev.target)) {
@@ -79784,6 +80046,7 @@ stores.inject(MyMetaStore, storeInstance);
79784
80046
  this.state.parentState = { ...this.state };
79785
80047
  this.state.menuItems = children;
79786
80048
  this.state.title = menu.name(this.env);
80049
+ this.containerRef.el?.scrollTo({ top: 0 });
79787
80050
  }
79788
80051
  else {
79789
80052
  this.state.menuItems = this.rootItems;
@@ -79805,6 +80068,19 @@ stores.inject(MyMetaStore, storeInstance);
79805
80068
  height: `${this.props.height}px`,
79806
80069
  });
79807
80070
  }
80071
+ updateShadows() {
80072
+ if (!this.containerRef.el) {
80073
+ return;
80074
+ }
80075
+ this.containerRef.el.classList.remove("scroll-top", "scroll-bottom");
80076
+ const maxScroll = this.containerRef.el.scrollHeight - this.containerRef.el.clientHeight || 0;
80077
+ if (this.containerRef.el.scrollTop < maxScroll - 1) {
80078
+ this.containerRef.el.classList.add("scroll-bottom");
80079
+ }
80080
+ if (this.containerRef.el.scrollTop > 0) {
80081
+ this.containerRef.el.classList.add("scroll-top");
80082
+ }
80083
+ }
79808
80084
  onClickBack() {
79809
80085
  if (!this.state.parentState) {
79810
80086
  this.props.onClose();
@@ -79813,6 +80089,7 @@ stores.inject(MyMetaStore, storeInstance);
79813
80089
  this.state.menuItems = this.state.parentState.menuItems;
79814
80090
  this.state.title = this.state.parentState.title;
79815
80091
  this.state.parentState = this.state.parentState.parentState;
80092
+ this.containerRef.el?.scrollTo({ top: 0 });
79816
80093
  }
79817
80094
  get backTitle() {
79818
80095
  return this.state.parentState ? _t("Go to previous menu") : _t("Close menu bar");
@@ -79863,6 +80140,11 @@ stores.inject(MyMetaStore, storeInstance);
79863
80140
  ? this.composerFocusStore.focusMode
79864
80141
  : "inactive";
79865
80142
  }
80143
+ get showFxIcon() {
80144
+ return (this.focus === "inactive" &&
80145
+ !this.composerStore.currentContent &&
80146
+ !this.composerStore.placeholder);
80147
+ }
79866
80148
  get rect() {
79867
80149
  return this.composerRef.el
79868
80150
  ? getBoundingRectAsPOJO(this.composerRef.el)
@@ -79878,8 +80160,9 @@ stores.inject(MyMetaStore, storeInstance);
79878
80160
  },
79879
80161
  focus: this.focus,
79880
80162
  composerStore: this.composerStore,
79881
- onComposerContentFocused: () => this.composerFocusStore.focusComposer(this.composerInterface, {
80163
+ onComposerContentFocused: (selection) => this.composerFocusStore.focusComposer(this.composerInterface, {
79882
80164
  focusMode: "contentFocus",
80165
+ selection,
79883
80166
  }),
79884
80167
  isDefaultFocus: false,
79885
80168
  inputStyle: cssPropertiesToCss({
@@ -79887,6 +80170,7 @@ stores.inject(MyMetaStore, storeInstance);
79887
80170
  "max-height": `130px`,
79888
80171
  }),
79889
80172
  showAssistant: !isIOS(), // Hide assistant on iOS as it breaks visually
80173
+ placeholder: this.composerStore.placeholder,
79890
80174
  };
79891
80175
  }
79892
80176
  get symbols() {
@@ -79905,13 +80189,6 @@ stores.inject(MyMetaStore, storeInstance);
79905
80189
  }
79906
80190
 
79907
80191
  const COMPOSER_MAX_HEIGHT = 300;
79908
- /* svg free of use from https://uxwing.com/formula-fx-icon/ */
79909
- // FIXME This svg is hardcoded in the css file. We should find a better way to handle it.
79910
- // const FX_SVG = /*xml*/ `
79911
- // <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 121.8 122.9' width='16' height='16' focusable='false'>
79912
- // <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'/>
79913
- // </svg>
79914
- // `;
79915
80192
  class TopBarComposer extends owl.Component {
79916
80193
  static template = "o-spreadsheet-TopBarComposer";
79917
80194
  static props = {};
@@ -79938,6 +80215,11 @@ stores.inject(MyMetaStore, storeInstance);
79938
80215
  ? this.composerFocusStore.focusMode
79939
80216
  : "inactive";
79940
80217
  }
80218
+ get showFxIcon() {
80219
+ return (this.focus === "inactive" &&
80220
+ !this.composerStore.currentContent &&
80221
+ !this.composerStore.placeholder);
80222
+ }
79941
80223
  get composerStyle() {
79942
80224
  const style = {
79943
80225
  padding: "5px 0px 5px 8px",
@@ -80875,7 +81157,7 @@ stores.inject(MyMetaStore, storeInstance);
80875
81157
  SidePanels,
80876
81158
  SpreadsheetDashboard,
80877
81159
  HeaderGroupContainer,
80878
- FullScreenChart,
81160
+ FullScreenFigure,
80879
81161
  };
80880
81162
  sidePanel;
80881
81163
  spreadsheetRef = owl.useRef("spreadsheet");
@@ -85599,6 +85881,7 @@ stores.inject(MyMetaStore, storeInstance);
85599
85881
  Grid,
85600
85882
  GridOverlay,
85601
85883
  ScorecardChart,
85884
+ GaugeChartComponent,
85602
85885
  LineConfigPanel,
85603
85886
  BarConfigPanel,
85604
85887
  PieChartDesignPanel,
@@ -85637,7 +85920,7 @@ stores.inject(MyMetaStore, storeInstance);
85637
85920
  RadioSelection,
85638
85921
  GeoChartRegionSelectSection,
85639
85922
  ChartDashboardMenu,
85640
- FullScreenChart,
85923
+ FullScreenFigure,
85641
85924
  };
85642
85925
  const hooks = {
85643
85926
  useDragAndDropListItems,
@@ -85737,9 +86020,9 @@ stores.inject(MyMetaStore, storeInstance);
85737
86020
  exports.tokenize = tokenize;
85738
86021
 
85739
86022
 
85740
- __info__.version = "19.1.0-alpha.3";
85741
- __info__.date = "2025-09-23T12:37:52.238Z";
85742
- __info__.hash = "ce2b07a";
86023
+ __info__.version = "19.1.0-alpha.5";
86024
+ __info__.date = "2025-10-16T06:39:55.925Z";
86025
+ __info__.hash = "1a0e3d5";
85743
86026
 
85744
86027
 
85745
86028
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);