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