@odoo/o-spreadsheet 19.5.0-alpha.11 → 19.5.0-alpha.12

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.
Files changed (50) hide show
  1. package/dist/material_symbols_outlined.css +9 -0
  2. package/dist/o_spreadsheet.cjs +2313 -656
  3. package/dist/o_spreadsheet.css +169 -33
  4. package/dist/o_spreadsheet.esm.js +2305 -648
  5. package/dist/o_spreadsheet.iife.js +2313 -656
  6. package/dist/o_spreadsheet.min.iife.js +344 -344
  7. package/dist/o_spreadsheet.xml +209 -128
  8. package/dist/types/actions/data_actions.d.ts +1 -0
  9. package/dist/types/components/bottom_bar/bottom_bar_statistic/bottom_bar_statistic.d.ts +3 -0
  10. package/dist/types/components/dashboard/clickable_cell_sort_icon/clickable_cell_sort_icon.d.ts +1 -1
  11. package/dist/types/components/figures/figure/figure.d.ts +1 -0
  12. package/dist/types/components/figures/figure_carousel/figure_carousel.d.ts +10 -0
  13. package/dist/types/components/figures/figure_container/figure_container.d.ts +1 -2
  14. package/dist/types/components/grid/grid.d.ts +1 -0
  15. package/dist/types/components/helpers/chart_drag_and_drop.d.ts +15 -0
  16. package/dist/types/components/helpers/dom_helpers.d.ts +3 -2
  17. package/dist/types/components/link/link_editor/link_editor.d.ts +1 -1
  18. package/dist/types/components/side_panel/carousel_panel/carousel_panel.d.ts +1 -1
  19. package/dist/types/components/side_panel/chart/building_blocks/color_scale/color_scale_picker.d.ts +1 -1
  20. package/dist/types/components/side_panel/data_analysis/chart_suggestion_preview.d.ts +19 -0
  21. package/dist/types/components/side_panel/data_analysis/data_analysis_panel.d.ts +17 -0
  22. package/dist/types/components/side_panel/data_analysis/data_analysis_store.d.ts +15 -0
  23. package/dist/types/components/side_panel/pivot/pivot_layout_configurator/pivot_layout_configurator.d.ts +1 -6
  24. package/dist/types/components/side_panel/side_panel/side_panel_store.d.ts +2 -1
  25. package/dist/types/components/standalone_grid_canvas/carousel_data_view_print.d.ts +32 -0
  26. package/dist/types/components/standalone_grid_canvas/standalone_grid_canvas.d.ts +6 -0
  27. package/dist/types/components/standalone_viewport/standalone_viewport.d.ts +6 -3
  28. package/dist/types/components/standalone_viewport/standalone_viewport_store.d.ts +2 -1
  29. package/dist/types/components/tables/hovered_table_store.d.ts +6 -7
  30. package/dist/types/constants.d.ts +13 -1
  31. package/dist/types/functions/helpers.d.ts +4 -0
  32. package/dist/types/functions/module_operators.d.ts +1 -1
  33. package/dist/types/functions/module_text.d.ts +11 -11
  34. package/dist/types/functions/module_web.d.ts +1 -1
  35. package/dist/types/helpers/carousel_helpers.d.ts +8 -1
  36. package/dist/types/helpers/data_analysis.d.ts +16 -0
  37. package/dist/types/helpers/figures/charts/chart_data_source_helpers.d.ts +10 -0
  38. package/dist/types/helpers/figures/charts/chart_suggestion_engine.d.ts +8 -0
  39. package/dist/types/helpers/figures/charts/chart_ui_common.d.ts +8 -1
  40. package/dist/types/helpers/figures/charts/charts_suggestions_constants.d.ts +37 -0
  41. package/dist/types/helpers/figures/charts/gauge_chart_rendering.d.ts +6 -2
  42. package/dist/types/helpers/text_helper.d.ts +1 -1
  43. package/dist/types/index.d.ts +3 -1
  44. package/dist/types/registries/figures_registry.d.ts +1 -0
  45. package/dist/types/stores/cell_hover_overlay_store.d.ts +15 -0
  46. package/dist/types/stores/chart_drag_store.d.ts +10 -0
  47. package/dist/types/stores/grid_renderer_store.d.ts +3 -2
  48. package/dist/types/types/chart/chart_suggestion.d.ts +136 -0
  49. package/dist/types/types/commands.d.ts +16 -9
  50. package/package.json +4 -4
@@ -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.5.0-alpha.11
6
- * @date 2026-08-12T09:17:24.237Z
7
- * @hash bc056d7
5
+ * @version 19.5.0-alpha.12
6
+ * @date 2026-08-19T09:47:03.774Z
7
+ * @hash 68ca68e
8
8
  */
9
9
 
10
10
  import * as owl from "@odoo/owl";
@@ -206,6 +206,16 @@ function getBoundingRectAsPOJO(el) {
206
206
  height: rect.height
207
207
  };
208
208
  }
209
+ function getBoundingRectWithMargins(el) {
210
+ const style = getComputedStyle(el);
211
+ const rect = el.getBoundingClientRect();
212
+ return {
213
+ x: rect.x,
214
+ y: rect.y,
215
+ width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
216
+ height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
217
+ };
218
+ }
209
219
  /**
210
220
  * Iterate over all the children of `el` in the dom tree starting at `el`, depth first.
211
221
  */
@@ -649,6 +659,14 @@ const DEFAULT_CAROUSEL_TITLE_STYLE = {
649
659
  fontSize: 18,
650
660
  color: TEXT_BODY
651
661
  };
662
+ const CAROUSEL_LAYOUT = {
663
+ paddingX: 24,
664
+ paddingY: 8,
665
+ headerPaddingTop: 4,
666
+ separatorWidth: 1,
667
+ headerLineHeight: 1.5,
668
+ minHeaderHeight: 25
669
+ };
652
670
  const DEFAULT_TOKEN_COLOR = "light-dark(#000000, #ffffff)";
653
671
  const functionColor = DEFAULT_TOKEN_COLOR;
654
672
  const operatorColor = "#3da4ab";
@@ -4071,6 +4089,18 @@ function toString(data) {
4071
4089
  default: return "";
4072
4090
  }
4073
4091
  }
4092
+ /**
4093
+ * Only converts the decimal separator, ignore number format such as % or dates
4094
+ */
4095
+ function toLocaleString(data, locale) {
4096
+ const value = toValue(data);
4097
+ switch (typeof value) {
4098
+ case "string": return value;
4099
+ case "number": return value.toString().replace(".", locale.decimalSeparator);
4100
+ case "boolean": return value ? "TRUE" : "FALSE";
4101
+ default: return "";
4102
+ }
4103
+ }
4074
4104
  /** Normalize string by setting it to lowercase and replacing accent letters with plain letters */
4075
4105
  const normalizeString = memoize(function normalizeString(str) {
4076
4106
  return str.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
@@ -9247,8 +9277,8 @@ function computeCachedTextDimension(context, text, font) {
9247
9277
  function fontSizeInPixels(fontSize) {
9248
9278
  return Math.round(fontSize * 96 / 72);
9249
9279
  }
9250
- function computeTextFont(style, fontUnit = "pt") {
9251
- return `${style.italic ? "italic " : ""}${style.bold ? "bold" : "400"} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
9280
+ function computeTextFont(style, fontUnit = "pt", fontWeight = 400) {
9281
+ return `${style.italic ? "italic " : ""}${style.bold ? "bold" : fontWeight} ${(fontUnit === "pt" ? computeTextFontSizeInPixels(style) : style.fontSize) ?? DEFAULT_FONT_SIZE}px ${DEFAULT_FONT}`;
9252
9282
  }
9253
9283
  function computeTextFontSizeInPixels(style) {
9254
9284
  return fontSizeInPixels(style?.fontSize || DEFAULT_FONT_SIZE);
@@ -11882,7 +11912,7 @@ var ChartJsComponent = class extends Component {
11882
11912
  chartId: types$1.string(),
11883
11913
  isFullScreen: types$1.boolean().optional()
11884
11914
  });
11885
- canvas = signal(null);
11915
+ canvas = signal.ref(HTMLCanvasElement);
11886
11916
  chart;
11887
11917
  currentRuntime;
11888
11918
  animationStore;
@@ -12605,7 +12635,7 @@ var ScorecardChart = class extends Component {
12605
12635
  chartId: types$1.string(),
12606
12636
  isFullScreen: types$1.boolean().optional()
12607
12637
  });
12608
- canvas = signal(null);
12638
+ canvas = signal.ref(HTMLCanvasElement);
12609
12639
  zoomStore;
12610
12640
  get runtime() {
12611
12641
  return this.env.model.getters.getChartRuntime(this.props.chartId);
@@ -13990,6 +14020,44 @@ function getCarouselItemTitle(getters, item) {
13990
14020
  const definition = getters.getChartDefinition(item.chartId);
13991
14021
  return (chartSubtypeRegistry.getAll().find((c) => c.matcher?.(definition)) || chartSubtypeRegistry.get(definition.type)).displayName;
13992
14022
  }
14023
+ function getCarouselLayout(figureRect, carousel, selectedItem) {
14024
+ const layout = CAROUSEL_LAYOUT;
14025
+ const title = {
14026
+ ...DEFAULT_CAROUSEL_TITLE_STYLE,
14027
+ ...carousel.title
14028
+ };
14029
+ const x = figureRect.x + layout.paddingX;
14030
+ const width = Math.max(0, figureRect.width - 2 * layout.paddingX);
14031
+ const titleHeight = title.text ? title.fontSize * layout.headerLineHeight : 0;
14032
+ const headerRect = {
14033
+ x,
14034
+ y: figureRect.y + layout.paddingY + layout.headerPaddingTop,
14035
+ width,
14036
+ height: Math.max(layout.minHeaderHeight, titleHeight)
14037
+ };
14038
+ const headerBottom = headerRect.y + headerRect.height;
14039
+ const separatorRect = title.text ? {
14040
+ x,
14041
+ y: headerBottom + layout.paddingY,
14042
+ width,
14043
+ height: layout.separatorWidth
14044
+ } : void 0;
14045
+ const contentPaddingTop = selectedItem?.type === "carouselDataView" ? layout.paddingY : 0;
14046
+ const rectAboveContent = separatorRect || headerRect;
14047
+ const contentY = rectAboveContent.y + rectAboveContent.height + contentPaddingTop;
14048
+ const contentBottom = figureRect.y + figureRect.height - layout.paddingY;
14049
+ return {
14050
+ headerRect,
14051
+ separatorRect,
14052
+ contentRect: {
14053
+ x,
14054
+ y: contentY,
14055
+ width,
14056
+ height: Math.max(0, contentBottom - contentY)
14057
+ },
14058
+ headerLineHeight: layout.headerLineHeight
14059
+ };
14060
+ }
13993
14061
 
13994
14062
  //#endregion
13995
14063
  //#region src/helpers/figures/charts/gauge_chart_rendering.ts
@@ -14001,7 +14069,7 @@ const GAUGE_DEFAULT_VALUE_FONT_SIZE = 80;
14001
14069
  const GAUGE_BACKGROUND_COLOR = "#F3F2F1";
14002
14070
  const GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN = 6;
14003
14071
  const GAUGE_TITLE_SECTION_HEIGHT = 25;
14004
- function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
14072
+ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions, options) {
14005
14073
  const size = dimensions ?? getCanvasSize(canvas);
14006
14074
  const dpr = typeof globalThis.devicePixelRatio === "number" ? globalThis.devicePixelRatio : 1;
14007
14075
  canvas.width = size.width * dpr;
@@ -14014,7 +14082,7 @@ function drawGaugeChart(canvas, runtime, zoom = 1, dimensions) {
14014
14082
  height: size.height / zoom,
14015
14083
  x: 0,
14016
14084
  y: 0
14017
- }, runtime, ctx);
14085
+ }, runtime, ctx, options);
14018
14086
  drawBackground(ctx, config);
14019
14087
  drawGauge(ctx, config);
14020
14088
  drawInflectionValues(ctx, config);
@@ -14102,7 +14170,8 @@ function drawTitle(ctx, config) {
14102
14170
  ctx.fillText(title.label, title.textPosition.x, title.textPosition.y);
14103
14171
  ctx.restore();
14104
14172
  }
14105
- function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
14173
+ function getGaugeRenderingConfig(boundingRect, runtime, ctx, options) {
14174
+ const labelFontSize = options?.labelFontSize ?? 12;
14106
14175
  const maxValue = runtime.maxValue;
14107
14176
  const minValue = runtime.minValue;
14108
14177
  const gaugeValue = getGaugeValue(runtime, "animated");
@@ -14120,14 +14189,14 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
14120
14189
  if (computeTextWidth(ctx, gaugeLabel, { fontSize: gaugeValueFontSize }, "px") > maxTextWidth) gaugeValueFontSize = getFontSizeMatchingWidth(maxTextWidth, gaugeValueFontSize, (fontSize) => computeTextWidth(ctx, gaugeLabel, { fontSize }, "px"));
14121
14190
  const minLabelPosition = {
14122
14191
  x: gaugeRect.x + gaugeArcWidth / 2,
14123
- y: gaugeRect.y + gaugeRect.height + 12
14192
+ y: gaugeRect.y + gaugeRect.height + labelFontSize
14124
14193
  };
14125
14194
  const maxLabelPosition = {
14126
14195
  x: gaugeRect.x + gaugeRect.width - gaugeArcWidth / 2,
14127
- y: gaugeRect.y + gaugeRect.height + 12
14196
+ y: gaugeRect.y + gaugeRect.height + labelFontSize
14128
14197
  };
14129
14198
  const textColor = chartMutedFontColor(runtime.background);
14130
- const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx);
14199
+ const inflectionValues = getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize);
14131
14200
  let x = 0, titleWidth = 0, titleHeight = 0;
14132
14201
  if (runtime.title.text) ({width: titleWidth, height: titleHeight} = computeTextDimension(ctx, runtime.title.text, {
14133
14202
  fontSize: 16,
@@ -14175,13 +14244,13 @@ function getGaugeRenderingConfig(boundingRect, runtime, ctx) {
14175
14244
  minLabel: {
14176
14245
  label: runtime.minValue.label,
14177
14246
  textPosition: minLabelPosition,
14178
- fontSize: 12,
14247
+ fontSize: labelFontSize,
14179
14248
  color: textColor
14180
14249
  },
14181
14250
  maxLabel: {
14182
14251
  label: runtime.maxValue.label,
14183
14252
  textPosition: maxLabelPosition,
14184
- fontSize: 12,
14253
+ fontSize: labelFontSize,
14185
14254
  color: textColor
14186
14255
  }
14187
14256
  };
@@ -14215,27 +14284,27 @@ function getGaugeRect(boundingRect, title) {
14215
14284
  *
14216
14285
  * Also compute an offset for the text so that it doesn't overlap with other text.
14217
14286
  */
14218
- function getInflectionValues(runtime, gaugeRect, textColor, ctx) {
14287
+ function getInflectionValues(runtime, gaugeRect, textColor, ctx, labelFontSize = 12) {
14219
14288
  const maxValue = runtime.maxValue;
14220
14289
  const minValue = runtime.minValue;
14221
14290
  const gaugeCircleCenter = {
14222
14291
  x: gaugeRect.x + gaugeRect.width / 2,
14223
14292
  y: gaugeRect.y + gaugeRect.height
14224
14293
  };
14225
- const textStyle = { fontSize: 12 };
14294
+ const textStyle = { fontSize: labelFontSize };
14226
14295
  const inflectionValues = [];
14227
14296
  const inflectionValuesTextRects = [];
14228
14297
  for (const inflectionValue of runtime.inflectionValues) {
14229
14298
  const percentage = (inflectionValue.value - minValue.value) / (maxValue.value - minValue.value);
14230
14299
  const labelWidth = computeTextWidth(ctx, inflectionValue.label, textStyle, "px");
14231
14300
  const angle = Math.PI - Math.PI * percentage;
14232
- const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, 12);
14233
- const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? 12 : 0;
14301
+ const textRect = getRectangleTangentToCircle(angle, gaugeRect.height + GAUGE_INFLECTION_LABEL_BOTTOM_MARGIN, gaugeCircleCenter.x, gaugeCircleCenter.y, labelWidth + 2, labelFontSize);
14302
+ const offset = inflectionValuesTextRects.some((rect) => doRectanglesIntersect(rect, textRect)) ? labelFontSize : 0;
14234
14303
  inflectionValuesTextRects.push(textRect);
14235
14304
  inflectionValues.push({
14236
14305
  rotation: angle,
14237
14306
  label: inflectionValue.label,
14238
- fontSize: 12,
14307
+ fontSize: labelFontSize,
14239
14308
  color: textColor,
14240
14309
  offset
14241
14310
  });
@@ -14355,6 +14424,44 @@ const CHART_COMMON_OPTIONS = {
14355
14424
  "mouseup"
14356
14425
  ]
14357
14426
  };
14427
+ /**
14428
+ * Sample down a Chart.js configuration's data points to at most `maxPoints`, evenly spaced.
14429
+ * Used for lightweight previews (drag preview, chart suggestions) where rendering every point
14430
+ * of a large dataset would make Chart.js slow without any visible benefit on a small canvas.
14431
+ */
14432
+ function limitChartConfigDataPoints(config, maxPoints = 150) {
14433
+ const data = config.data;
14434
+ if (!data) return config;
14435
+ return {
14436
+ ...config,
14437
+ data: {
14438
+ ...data,
14439
+ labels: limitArrayDataPoints(data.labels, maxPoints),
14440
+ datasets: data.datasets?.map((dataset) => limitDatasetDataPoints(dataset, maxPoints))
14441
+ }
14442
+ };
14443
+ }
14444
+ function limitDatasetDataPoints(dataset, maxPoints) {
14445
+ const values = dataset.data;
14446
+ if (!Array.isArray(values) || values.length <= maxPoints) return dataset;
14447
+ const length = values.length;
14448
+ const indices = sampleIndices(length, maxPoints);
14449
+ const limited = { ...dataset };
14450
+ for (const key of Object.keys(limited)) {
14451
+ const value = limited[key];
14452
+ if (Array.isArray(value) && value.length === length) limited[key] = indices.map((i) => value[i]);
14453
+ }
14454
+ return limited;
14455
+ }
14456
+ function limitArrayDataPoints(arr, maxPoints) {
14457
+ if (!Array.isArray(arr) || arr.length <= maxPoints) return arr;
14458
+ return sampleIndices(arr.length, maxPoints).map((i) => arr[i]);
14459
+ }
14460
+ /** Evenly spaced indices sampled from [0, length). */
14461
+ function sampleIndices(length, maxPoints) {
14462
+ const step = length / maxPoints;
14463
+ return Array.from({ length: maxPoints }, (_, i) => Math.min(length - 1, Math.floor(i * step)));
14464
+ }
14358
14465
  async function chartToImageUrl(runtime, figure, type) {
14359
14466
  try {
14360
14467
  const canvas = createRenderingSurface(figure.width, figure.height);
@@ -14402,7 +14509,7 @@ async function canvasToObjectUrl(canvas) {
14402
14509
  *
14403
14510
  * @returns a cleanup function to be called after the drawing is no longer needed (to free Chart.js resources)
14404
14511
  */
14405
- function drawChartOnCanvas(canvas, runtime, size, type) {
14512
+ function drawChartOnCanvas(canvas, runtime, size, type, zoom = 1) {
14406
14513
  if ("chartJsConfig" in runtime) {
14407
14514
  if (!globalThis.Chart) throw new Error("Chart.js library is not loaded");
14408
14515
  const extensionsLoaded = areChartJSExtensionsLoaded();
@@ -14418,8 +14525,8 @@ function drawChartOnCanvas(canvas, runtime, size, type) {
14418
14525
  chart.destroy();
14419
14526
  if (!extensionsLoaded) unregisterChartJsExtensions();
14420
14527
  };
14421
- } else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas);
14422
- else if (type === "gauge") drawGaugeChart(canvas, runtime, 1, size);
14528
+ } else if (type === "scorecard") drawScoreChart(getScorecardConfiguration(size, runtime), canvas, zoom);
14529
+ else if (type === "gauge") drawGaugeChart(canvas, runtime, zoom, size);
14423
14530
  return () => {};
14424
14531
  }
14425
14532
 
@@ -14957,7 +15064,7 @@ var ZoomableChartStore = class extends SpreadsheetStore {
14957
15064
  var ZoomableChartJsComponent = class extends ChartJsComponent {
14958
15065
  static template = "o-spreadsheet-ZoomableChartJsComponent";
14959
15066
  store;
14960
- masterChartCanvas = signal(null);
15067
+ masterChartCanvas = signal.ref(HTMLCanvasElement);
14961
15068
  masterChart;
14962
15069
  mode;
14963
15070
  hasLinearScale;
@@ -15711,7 +15818,7 @@ var GaugeChartComponent = class extends Component {
15711
15818
  chartId: types$1.string(),
15712
15819
  isFullScreen: types$1.boolean().optional()
15713
15820
  });
15714
- canvas = signal(null);
15821
+ canvas = signal.ref(HTMLCanvasElement);
15715
15822
  animationStore;
15716
15823
  zoomStore;
15717
15824
  get runtime() {
@@ -15948,7 +16055,7 @@ var Menu = class extends Component {
15948
16055
  onKeyDown: types$1.function().optional(),
15949
16056
  disableKeyboardNavigation: types$1.boolean().optional()
15950
16057
  });
15951
- menuRef = signal(null);
16058
+ menuRef = signal.ref();
15952
16059
  setup() {
15953
16060
  useLayoutEffect(() => {
15954
16061
  const menuEl = this.menuRef();
@@ -16068,8 +16175,8 @@ var Popover = class extends Component {
16068
16175
  zIndex: types$1.number().optional(),
16069
16176
  class: types$1.string().optional()
16070
16177
  });
16071
- popoverRef = signal(null);
16072
- popoverContentRef = signal(null);
16178
+ popoverRef = signal.ref();
16179
+ popoverContentRef = signal.ref();
16073
16180
  currentPosition = void 0;
16074
16181
  currentDisplayValue = void 0;
16075
16182
  spreadsheetRect = useSpreadsheetRect();
@@ -16257,7 +16364,7 @@ var MenuPopover = class MenuPopover extends Component {
16257
16364
  isHoveringChild: false
16258
16365
  });
16259
16366
  state = proxy({ hoveredMenu: this.props.autoSelectFirstItem ? this.getNextEnabledMenuItem() : void 0 });
16260
- menuRef = signal(null);
16367
+ menuRef = signal.ref();
16261
16368
  openingTimeOut = useTimeOut();
16262
16369
  setup() {
16263
16370
  const domFocusableElementStore = useStore(DOMFocusableElementStore);
@@ -16546,43 +16653,29 @@ var PositionMap = class {
16546
16653
  };
16547
16654
 
16548
16655
  //#endregion
16549
- //#region src/components/tables/hovered_table_store.ts
16550
- var HoveredTableStore = class extends SpreadsheetStore {
16551
- mutators = ["clear", "hover"];
16552
- position;
16656
+ //#region src/stores/cell_hover_overlay_store.ts
16657
+ var CellHoverOverlayStore = class extends SpreadsheetStore {
16658
+ mutators = [
16659
+ "hover",
16660
+ "register",
16661
+ "unRegister"
16662
+ ];
16663
+ storeGetters = ["getCellHoverOverlayColor"];
16664
+ providers = [];
16553
16665
  overlayColors = new PositionMap();
16554
- hover(position) {
16555
- if (!this.getters.isDashboard() || deepEquals(this.position, position)) return "noStateChange";
16556
- this.position = position;
16557
- this.computeOverlay();
16666
+ hover(hoveredPosition) {
16667
+ this.overlayColors = new PositionMap();
16668
+ if (!hoveredPosition) return;
16669
+ for (const provider of this.providers) {
16670
+ const positions = provider.getHighlightedPositions(hoveredPosition);
16671
+ for (const position of positions) this.overlayColors.set(position, TABLE_HOVER_BACKGROUND_COLOR);
16672
+ }
16558
16673
  }
16559
- clear() {
16560
- this.position = void 0;
16674
+ register(highlightProvider) {
16675
+ this.providers.push(highlightProvider);
16561
16676
  }
16562
- computeOverlay() {
16563
- this.overlayColors = new PositionMap();
16564
- if (!this.position) return;
16565
- const { sheetId, col, row } = this.position;
16566
- const table = this.getters.getTable({
16567
- sheetId,
16568
- col,
16569
- row
16570
- });
16571
- if (!table) return;
16572
- const { left, right, top } = table.range.zone;
16573
- const isTableHeader = row < top + table.config.numberOfHeaders;
16574
- const doesTableRowHaveContent = range(left, right + 1).some((col) => {
16575
- return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
16576
- sheetId,
16577
- col,
16578
- row
16579
- }).formattedValue;
16580
- });
16581
- if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) this.overlayColors.set({
16582
- sheetId,
16583
- col,
16584
- row
16585
- }, TABLE_HOVER_BACKGROUND_COLOR);
16677
+ unRegister(highlightProvider) {
16678
+ this.providers = this.providers.filter((h) => h !== highlightProvider);
16586
16679
  }
16587
16680
  };
16588
16681
 
@@ -16594,9 +16687,9 @@ var ClickableCellSortIcon = class extends Component {
16594
16687
  position: types$1.CellPosition(),
16595
16688
  sortDirection: types$1.or([types$1.SortDirection, types$1.literal("none")])
16596
16689
  });
16597
- hoveredTableStore;
16690
+ hoveredCellOverlayStore;
16598
16691
  setup() {
16599
- this.hoveredTableStore = useStore(HoveredTableStore);
16692
+ this.hoveredCellOverlayStore = useStore(CellHoverOverlayStore);
16600
16693
  }
16601
16694
  get style() {
16602
16695
  const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.position);
@@ -16616,7 +16709,7 @@ var ClickableCellSortIcon = class extends Component {
16616
16709
  }
16617
16710
  }
16618
16711
  getBackgroundColor(cellStyle) {
16619
- const overlayColor = this.hoveredTableStore.overlayColors.get(this.props.position);
16712
+ const overlayColor = this.hoveredCellOverlayStore.overlayColors.get(this.props.position);
16620
16713
  if (overlayColor) return blendColors(cellStyle.fillColor || "#FFFFFF", overlayColor);
16621
16714
  return cellStyle.fillColor || "#FFFFFF";
16622
16715
  }
@@ -21355,7 +21448,7 @@ var HoveredIconStore = class extends SpreadsheetStore {
21355
21448
  //#region src/components/grid_overlay/grid_overlay.ts
21356
21449
  function useCellHovered(env, gridRef) {
21357
21450
  const delayedHoveredCell = useStore(DelayedHoveredCellStore);
21358
- const hoveredTable = useStore(HoveredTableStore);
21451
+ const cellHoverOverlay = useStore(CellHoverOverlayStore);
21359
21452
  const viewStore = useStore(ViewportsStore);
21360
21453
  const hoveredPosition = {
21361
21454
  col: void 0,
@@ -21399,7 +21492,9 @@ function useCellHovered(env, gridRef) {
21399
21492
  if (isChildEvent(gridRef(), zoomedMouseEvent.ev)) {
21400
21493
  ({x, y} = getOffsetRelativeToOverlay(zoomedMouseEvent));
21401
21494
  lastMoved = Date.now();
21402
- hoveredTable.hover(getPosition());
21495
+ const position = getPosition();
21496
+ if (position.col < 0 || position.row < 0) cellHoverOverlay.hover(void 0);
21497
+ else cellHoverOverlay.hover(getPosition());
21403
21498
  }
21404
21499
  }
21405
21500
  function recompute() {
@@ -21410,7 +21505,7 @@ function useCellHovered(env, gridRef) {
21410
21505
  const zoomedMouseEvent = withZoom(env, e);
21411
21506
  const { x, y } = getOffsetRelativeToOverlay(zoomedMouseEvent);
21412
21507
  const gridRect = getElBoundingRect(gridRef());
21413
- if (y < 0 || y > gridRect.height || x < 0 || x > gridRect.width) return updateMousePosition(zoomedMouseEvent);
21508
+ if (y <= 0 || y >= gridRect.height || x <= 0 || x >= gridRect.width) return updateMousePosition(zoomedMouseEvent);
21414
21509
  else return pause();
21415
21510
  }
21416
21511
  useListener(gridRef, "pointermove", (ev) => !env.isMobile() && updateMousePosition(withZoom(env, ev)));
@@ -21447,7 +21542,7 @@ var GridOverlay = class extends Component {
21447
21542
  gridOverlayDimensions: types$1.string(),
21448
21543
  hasFooter: types$1.boolean().optional(() => true)
21449
21544
  });
21450
- gridOverlayRef = signal(null);
21545
+ gridOverlayRef = signal.ref();
21451
21546
  cellPopovers;
21452
21547
  paintFormatStore;
21453
21548
  hoveredIconStore;
@@ -21474,7 +21569,7 @@ var GridOverlay = class extends Component {
21474
21569
  return el;
21475
21570
  }
21476
21571
  get style() {
21477
- return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "default" });
21572
+ return this.props.gridOverlayDimensions + cssPropertiesToCss({ cursor: this.hoveredIconStore.hoveredIcon ? "pointer" : "inherit" });
21478
21573
  }
21479
21574
  get isPaintingFormat() {
21480
21575
  return this.paintFormatStore.isActive;
@@ -22114,8 +22209,9 @@ var FormulaFingerprintStore = class extends SpreadsheetStore {
22114
22209
  const CELL_ANIMATION_DURATION = 200;
22115
22210
  var GridRenderer = class extends DisposableStore {
22116
22211
  renderer;
22212
+ storeGetters = ["drawLayer"];
22117
22213
  fingerprints;
22118
- hoveredTables;
22214
+ cellHoverOverlayStore = this.get(CellHoverOverlayStore);
22119
22215
  hoveredIcon;
22120
22216
  lastRenderSheetId = void 0;
22121
22217
  lastRenderBoxes = /* @__PURE__ */ new Map();
@@ -22129,15 +22225,14 @@ var GridRenderer = class extends DisposableStore {
22129
22225
  const model = get(ModelStore);
22130
22226
  this.getters = model.getters;
22131
22227
  this.fingerprints = get(FormulaFingerprintStore);
22132
- this.hoveredTables = get(HoveredTableStore);
22133
22228
  this.hoveredIcon = get(HoveredIconStore);
22134
22229
  model.on("command-dispatched", this, this.handle);
22135
22230
  model.on("command-finalized", this, this.finalize);
22136
- this.renderer.register(this);
22231
+ if (this.renderer) this.renderer.register(this);
22137
22232
  this.onDispose(() => {
22138
22233
  model.off("command-dispatched", this);
22139
22234
  model.off("command-finalized", this);
22140
- this.renderer.unRegister(this);
22235
+ if (this.renderer) this.renderer.unRegister(this);
22141
22236
  });
22142
22237
  }
22143
22238
  handle(cmd) {
@@ -22681,7 +22776,7 @@ var GridRenderer = class extends DisposableStore {
22681
22776
  border: this.getters.getCellComputedBorder(position) || void 0,
22682
22777
  style,
22683
22778
  dataBarFill,
22684
- overlayColor: this.hoveredTables.overlayColors.get(position),
22779
+ overlayColor: this.cellHoverOverlayStore.overlayColors.get(position),
22685
22780
  isError: cell.type === "error" && !!cell.message || this.getters.isDataValidationInvalid(position),
22686
22781
  icons: cellIcons,
22687
22782
  disabledAnimation: this.zonesWithPreventedAnimationsInNextFrame[sheetId]?.some((z) => isZoneInside(zone, z) || overlap(zone, z))
@@ -22857,6 +22952,7 @@ var GridRenderer = class extends DisposableStore {
22857
22952
  return boxes;
22858
22953
  }
22859
22954
  getBoxesWithAnimations(boxes, oldBoxes, timeStamp) {
22955
+ if (!this.renderer) return boxes;
22860
22956
  this.updateAnimationsProgress(timeStamp);
22861
22957
  this.addNewAnimations(boxes, oldBoxes, timeStamp);
22862
22958
  if (this.animations.size > 0) {
@@ -23001,7 +23097,7 @@ var ScrollBar = class extends Component {
23001
23097
  offset: types$1.Pixel(),
23002
23098
  onScroll: types$1.function()
23003
23099
  });
23004
- scrollbarRef = signal(null);
23100
+ scrollbarRef = signal.ref();
23005
23101
  scrollbar;
23006
23102
  setup() {
23007
23103
  this.scrollbar = new ScrollBar$1(this.scrollbarRef(), this.props.direction);
@@ -23103,6 +23199,9 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
23103
23199
  zoneToDisplay: this.range.zone,
23104
23200
  getFooterSize: () => 0
23105
23201
  });
23202
+ const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
23203
+ cellHoverOverlayStore.register(this);
23204
+ this.onDispose(() => cellHoverOverlayStore.unRegister(this));
23106
23205
  }
23107
23206
  handle(cmd) {
23108
23207
  this.cachedColDimensions = void 0;
@@ -23251,6 +23350,16 @@ var StandaloneViewportStore = class extends SpreadsheetStore {
23251
23350
  }
23252
23351
  return normalizedWeights;
23253
23352
  }
23353
+ getHighlightedPositions(position) {
23354
+ const highlightedPositions = [];
23355
+ const { col, row, sheetId } = position;
23356
+ if (sheetId !== this.range.sheetId || !isInside(col, row, this.range.zone)) return highlightedPositions;
23357
+ for (let col = this.range.zone.left; col <= this.range.zone.right; col++) highlightedPositions.push({
23358
+ ...position,
23359
+ col
23360
+ });
23361
+ return highlightedPositions;
23362
+ }
23254
23363
  };
23255
23364
 
23256
23365
  //#endregion
@@ -23267,10 +23376,11 @@ var StandaloneViewport = class extends Component {
23267
23376
  range: types$1.Range(),
23268
23377
  canResizeColumns: types$1.boolean().optional(true),
23269
23378
  onResizeColumns: types$1.function().optional(),
23270
- columnWeights: types$1.array().optional()
23379
+ columnWeights: types$1.array().optional(),
23380
+ size: types$1.object()
23271
23381
  });
23272
- canvasRef = signal(null);
23273
- containerRef = signal(null);
23382
+ canvasRef = signal.ref(HTMLCanvasElement);
23383
+ containerRef = signal.ref();
23274
23384
  store;
23275
23385
  dndState = proxy({ col: void 0 });
23276
23386
  onMouseWheel;
@@ -23282,8 +23392,8 @@ var StandaloneViewport = class extends Component {
23282
23392
  useChildStoreProvider([
23283
23393
  ViewportsStore,
23284
23394
  HoveredIconStore,
23285
- HoveredTableStore,
23286
23395
  ClickableCellsStore,
23396
+ CellHoverOverlayStore,
23287
23397
  DelayedHoveredCellStore,
23288
23398
  CellPopoverStore
23289
23399
  ]);
@@ -23292,18 +23402,10 @@ var StandaloneViewport = class extends Component {
23292
23402
  this.zoomStore = useStore(ZoomStore);
23293
23403
  this.cellPopoverStore = useStore(CellPopoverStore);
23294
23404
  this.rendererStore = useLocalStore(RendererStore, ["Background", "Chart"]);
23295
- const resizeObserver = new ResizeObserver(() => {
23296
- this.store.setContainerSize(this.containerWidth, this.containerHeight);
23297
- });
23298
- useEffect(() => {
23299
- const el = this.containerRef();
23300
- if (el) resizeObserver.observe(el);
23301
- return () => resizeObserver.disconnect();
23302
- });
23303
23405
  useEffect(() => {
23304
23406
  this.store.setRange(this.props.range);
23305
23407
  if (this.dndState.col === void 0) this.store.setCustomColWeights(this.props.columnWeights);
23306
- this.store.setContainerSize(this.containerWidth, this.containerHeight);
23408
+ this.store.setContainerSize(this.contentWidth, this.props.size.height);
23307
23409
  });
23308
23410
  useGridDrawing({
23309
23411
  canvasRef: this.canvasRef,
@@ -23322,14 +23424,11 @@ var StandaloneViewport = class extends Component {
23322
23424
  }
23323
23425
  });
23324
23426
  }
23325
- get containerWidth() {
23326
- return Math.floor(getElBoundingRect(this.containerRef()).width / this.zoomStore.zoomLevel);
23327
- }
23328
- get containerHeight() {
23329
- return Math.floor(getElBoundingRect(this.containerRef()).height / this.zoomStore.zoomLevel);
23427
+ get contentWidth() {
23428
+ return this.hasVerticalScrollBar ? this.props.size.width - this.zoomStore.scrollBarWidth : this.props.size.width;
23330
23429
  }
23331
23430
  get hasVerticalScrollBar() {
23332
- return this.containerHeight && this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.containerHeight;
23431
+ return this.viewStore.mainViewportCoordinates.y + this.viewStore.mainViewportRect.height > this.props.size.height;
23333
23432
  }
23334
23433
  get scrollBarContainerStyle() {
23335
23434
  return cssPropertiesToCss({ width: `${this.zoomStore.scrollBarWidth}px` });
@@ -23381,7 +23480,7 @@ var StandaloneViewport = class extends Component {
23381
23480
  };
23382
23481
  const onMouseMove = (ev) => {
23383
23482
  deltaX = withZoom(this.env, ev).clientX - initialX;
23384
- const weightDelta = deltaX / this.containerWidth * totalWeight;
23483
+ const weightDelta = deltaX / this.props.size.width * totalWeight;
23385
23484
  this.store.resizeColumn(resizer.col, weightDelta, startingColWeights);
23386
23485
  };
23387
23486
  startDnd(onMouseMove, onMouseUp);
@@ -23411,8 +23510,8 @@ var Select = class extends Component {
23411
23510
  name: types$1.string().optional(),
23412
23511
  title: types$1.string().optional()
23413
23512
  });
23414
- selectRef = signal(null);
23415
- dropdownRef = signal(null);
23513
+ selectRef = signal.ref();
23514
+ dropdownRef = signal.ref();
23416
23515
  state = proxy({
23417
23516
  isPopoverOpen: false,
23418
23517
  hoveredValue: void 0
@@ -23522,7 +23621,7 @@ var InfoPopover = class extends Component {
23522
23621
  setup() {
23523
23622
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
23524
23623
  }
23525
- infoRef = signal(null);
23624
+ infoRef = signal.ref();
23526
23625
  get popoverProps() {
23527
23626
  return {
23528
23627
  anchorRect: this.props.anchorRect,
@@ -23667,8 +23766,8 @@ var CarouselFigure = class extends Component {
23667
23766
  isFullScreen: types$1.boolean().optional(),
23668
23767
  openContextMenu: types$1.function().optional()
23669
23768
  });
23670
- carouselTabsRef = signal(null);
23671
- carouselTabsDropdownRef = signal(null);
23769
+ carouselTabsRef = signal.ref();
23770
+ carouselTabsDropdownRef = signal.ref();
23672
23771
  menuState = proxy({
23673
23772
  isOpen: false,
23674
23773
  anchorRect: null,
@@ -23736,6 +23835,19 @@ var CarouselFigure = class extends Component {
23736
23835
  } else cssProperties["background-color"] = backgroundColor;
23737
23836
  return cssPropertiesToCss(cssProperties);
23738
23837
  }
23838
+ get contentStyle() {
23839
+ return cssPropertiesToCss(this.rectToCss(this.carouselLayout.contentRect));
23840
+ }
23841
+ get headerStyle() {
23842
+ return cssPropertiesToCss({
23843
+ ...this.rectToCss(this.carouselLayout.headerRect),
23844
+ "line-height": String(this.carouselLayout.headerLineHeight)
23845
+ });
23846
+ }
23847
+ get separatorStyle() {
23848
+ const separatorRect = this.carouselLayout.separatorRect;
23849
+ return separatorRect ? cssPropertiesToCss(this.rectToCss(separatorRect)) : "";
23850
+ }
23739
23851
  get title() {
23740
23852
  return this.env.model.getters.dynamicTranslate(this.carousel.title?.text ?? "");
23741
23853
  }
@@ -23745,6 +23857,14 @@ var CarouselFigure = class extends Component {
23745
23857
  ...this.carousel.title
23746
23858
  })));
23747
23859
  }
23860
+ rectToCss(rect) {
23861
+ return {
23862
+ top: `${rect.y}px`,
23863
+ left: `${rect.x}px`,
23864
+ width: `${rect.width}px`,
23865
+ height: `${rect.height}px`
23866
+ };
23867
+ }
23748
23868
  updateTabsVisibility() {
23749
23869
  const tabsContainerEl = this.carouselTabsRef();
23750
23870
  const dropDownEl = this.carouselTabsDropdownRef();
@@ -23836,6 +23956,13 @@ var CarouselFigure = class extends Component {
23836
23956
  get canResizeDataViewColumns() {
23837
23957
  return !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
23838
23958
  }
23959
+ get carouselLayout() {
23960
+ return getCarouselLayout({
23961
+ ...this.props.figureUI,
23962
+ x: 0,
23963
+ y: 0
23964
+ }, this.carousel, this.selectedCarouselItem);
23965
+ }
23839
23966
  };
23840
23967
 
23841
23968
  //#endregion
@@ -23876,7 +24003,7 @@ var ImageFigure = class extends Component {
23876
24003
  figureUI: types$1.FigureUI(),
23877
24004
  openContextMenu: types$1.function()
23878
24005
  });
23879
- menuButtonRef = signal(null);
24006
+ menuButtonRef = signal.ref();
23880
24007
  showMenu(ev) {
23881
24008
  if (!this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id)) this.env.model.dispatch("SELECT_FIGURE", {
23882
24009
  figureId: this.props.figureUI.id,
@@ -23904,7 +24031,8 @@ figureRegistry.add("chart", {
23904
24031
  menuBuilder: getChartMenuActions,
23905
24032
  borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
23906
24033
  hasShadow: (getters) => getters.isDashboard(),
23907
- isRounded: (getters) => getters.isDashboard()
24034
+ isRounded: (getters) => getters.isDashboard(),
24035
+ isThemeDependant: true
23908
24036
  });
23909
24037
  figureRegistry.add("image", {
23910
24038
  Component: ImageFigure,
@@ -23913,14 +24041,16 @@ figureRegistry.add("image", {
23913
24041
  borderWidth: () => 0,
23914
24042
  hasShadow: () => false,
23915
24043
  isRounded: () => false,
23916
- menuBuilder: getImageMenuActions
24044
+ menuBuilder: getImageMenuActions,
24045
+ isThemeDependant: false
23917
24046
  });
23918
24047
  figureRegistry.add("carousel", {
23919
24048
  Component: CarouselFigure,
23920
24049
  menuBuilder: getCarouselMenuActions,
23921
24050
  borderWidth: (getters) => getters.isDashboard() ? 0 : 1,
23922
24051
  hasShadow: (getters) => getters.isDashboard(),
23923
- isRounded: (getters) => getters.isDashboard()
24052
+ isRounded: (getters) => getters.isDashboard(),
24053
+ isThemeDependant: true
23924
24054
  });
23925
24055
 
23926
24056
  //#endregion
@@ -23942,10 +24072,13 @@ var FigureComponent = class extends Component {
23942
24072
  anchorRect: null,
23943
24073
  menuItems: []
23944
24074
  });
23945
- figureRef = signal(null);
24075
+ figureRef = signal.ref();
23946
24076
  get isSelected() {
23947
24077
  return !this.env.model.getters.isDashboard() && this.env.model.getters.getSelectedFigureIds().includes(this.props.figureUI.id);
23948
24078
  }
24079
+ get isThemeDependant() {
24080
+ return figureRegistry.get(this.props.figureUI.tag).isThemeDependant;
24081
+ }
23949
24082
  get figureRegistry() {
23950
24083
  return figureRegistry;
23951
24084
  }
@@ -29544,6 +29677,35 @@ async function interactivePasteFromOS(env, target, parsedClipboardContent, paste
29544
29677
  }));
29545
29678
  }
29546
29679
 
29680
+ //#endregion
29681
+ //#region src/helpers/figures/charts/chart_data_source_helpers.ts
29682
+ function getUnboundRange(getters, zone) {
29683
+ return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
29684
+ }
29685
+ function isDatasetTitled(getters, zone) {
29686
+ const sheetId = getters.getActiveSheetId();
29687
+ const cell = getters.getEvaluatedCell({
29688
+ sheetId,
29689
+ col: zone.left,
29690
+ row: zone.top
29691
+ });
29692
+ return !["number", "empty"].includes(cell.type);
29693
+ }
29694
+ function dataset(zone, getters, id = "0") {
29695
+ return {
29696
+ dataRange: getUnboundRange(getters, zone),
29697
+ dataSetId: id
29698
+ };
29699
+ }
29700
+ function rangeSource(dataSets, dataSetsHaveTitle, labelRange) {
29701
+ return {
29702
+ type: "range",
29703
+ dataSets,
29704
+ dataSetsHaveTitle,
29705
+ labelRange
29706
+ };
29707
+ }
29708
+
29547
29709
  //#endregion
29548
29710
  //#region src/helpers/figures/charts/smart_chart_engine.ts
29549
29711
  const DEFAULT_BAR_CHART_CONFIG = {
@@ -29574,9 +29736,6 @@ const DEFAULT_LINE_CHART_CONFIG = {
29574
29736
  labelsAsText: false,
29575
29737
  humanize: true
29576
29738
  };
29577
- function getUnboundRange(getters, zone) {
29578
- return zoneToXc(getters.getUnboundedZone(getters.getActiveSheetId(), zone));
29579
- }
29580
29739
  function detectColumnType(cells) {
29581
29740
  if (!cells.length) return "empty";
29582
29741
  const counts = {
@@ -29623,14 +29782,6 @@ function getCellStats(getters, zone) {
29623
29782
  totalCount: values.length
29624
29783
  };
29625
29784
  }
29626
- function isDatasetTitled(getters, column) {
29627
- const titleCell = getters.getEvaluatedCell({
29628
- sheetId: getters.getActiveSheetId(),
29629
- col: column.zone.left,
29630
- row: column.zone.top
29631
- });
29632
- return !["number", "empty"].includes(titleCell.type);
29633
- }
29634
29785
  /**
29635
29786
  * Builds a chart definition for a single column selection. The logic to detect the chart type is as follows:
29636
29787
  * - If the column contains a single cell, create a scorecard.
@@ -29642,8 +29793,7 @@ function isDatasetTitled(getters, column) {
29642
29793
  function buildSingleColumnChart(column, getters) {
29643
29794
  const { type, zone } = column;
29644
29795
  const sheetId = getters.getActiveSheetId();
29645
- const dataSetsHaveTitle = isDatasetTitled(getters, column);
29646
- const dataRange = getUnboundRange(getters, zone);
29796
+ const dataSetsHaveTitle = isDatasetTitled(getters, column.zone);
29647
29797
  const titleCell = getters.getEvaluatedCell({
29648
29798
  sheetId,
29649
29799
  col: zone.left,
@@ -29654,14 +29804,7 @@ function buildSingleColumnChart(column, getters) {
29654
29804
  case "percentage": return {
29655
29805
  type: "pie",
29656
29806
  title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
29657
- dataSource: {
29658
- type: "range",
29659
- dataSets: [{
29660
- dataRange,
29661
- dataSetId: "0"
29662
- }],
29663
- dataSetsHaveTitle
29664
- },
29807
+ dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
29665
29808
  dataSetStyles: {},
29666
29809
  legendPosition: "none"
29667
29810
  };
@@ -29672,45 +29815,28 @@ function buildSingleColumnChart(column, getters) {
29672
29815
  return {
29673
29816
  type: "pie",
29674
29817
  title: hasUniqueTitle ? { text: String(titleCell.value) } : {},
29675
- dataSource: {
29676
- type: "range",
29677
- dataSets: [{
29678
- dataRange,
29679
- dataSetId: "0"
29680
- }],
29681
- labelRange: dataRange,
29682
- dataSetsHaveTitle: hasUniqueTitle
29683
- },
29818
+ dataSource: rangeSource([dataset(zone, getters)], hasUniqueTitle, getUnboundRange(getters, zone)),
29684
29819
  dataSetStyles: {},
29685
29820
  aggregated: true,
29686
29821
  legendPosition: "top"
29687
29822
  };
29688
- case "date": return {
29689
- type: "calendar",
29690
- title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
29691
- dataSource: {
29692
- type: "range",
29693
- dataSets: [],
29694
- dataSetsHaveTitle,
29695
- labelRange: dataRange
29696
- },
29697
- dataSetStyles: {},
29698
- legendPosition: "left",
29699
- horizontalGroupBy: "day_of_week",
29700
- verticalGroupBy: "month_number"
29701
- };
29823
+ case "date": {
29824
+ const dataRange = getUnboundRange(getters, zone);
29825
+ return {
29826
+ type: "calendar",
29827
+ title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
29828
+ dataSource: rangeSource([], dataSetsHaveTitle, dataRange),
29829
+ dataSetStyles: {},
29830
+ legendPosition: "left",
29831
+ horizontalGroupBy: "day_of_week",
29832
+ verticalGroupBy: "month_number"
29833
+ };
29834
+ }
29702
29835
  }
29703
29836
  return {
29704
29837
  ...DEFAULT_BAR_CHART_CONFIG,
29705
29838
  title: dataSetsHaveTitle ? { text: String(titleCell.value) } : {},
29706
- dataSource: {
29707
- type: "range",
29708
- dataSets: [{
29709
- dataRange,
29710
- dataSetId: "0"
29711
- }],
29712
- dataSetsHaveTitle
29713
- },
29839
+ dataSource: rangeSource([dataset(zone, getters)], dataSetsHaveTitle),
29714
29840
  dataSetStyles: {}
29715
29841
  };
29716
29842
  }
@@ -29728,15 +29854,7 @@ function buildTwoColumnChart(columns, getters) {
29728
29854
  if (columns[1].type === "percentage") return {
29729
29855
  type: "pie",
29730
29856
  title: {},
29731
- dataSource: {
29732
- type: "range",
29733
- dataSets: [{
29734
- dataRange: getUnboundRange(getters, columns[1].zone),
29735
- dataSetId: "0"
29736
- }],
29737
- labelRange: getUnboundRange(getters, columns[0].zone),
29738
- dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
29739
- },
29857
+ dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
29740
29858
  dataSetStyles: {},
29741
29859
  aggregated: true,
29742
29860
  legendPosition: "none"
@@ -29744,15 +29862,7 @@ function buildTwoColumnChart(columns, getters) {
29744
29862
  if (columns[0].type === "number" && columns[1].type === "number") return {
29745
29863
  type: "scatter",
29746
29864
  title: {},
29747
- dataSource: {
29748
- type: "range",
29749
- dataSets: [{
29750
- dataRange: getUnboundRange(getters, columns[1].zone),
29751
- dataSetId: "0"
29752
- }],
29753
- labelRange: getUnboundRange(getters, columns[0].zone),
29754
- dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
29755
- },
29865
+ dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
29756
29866
  dataSetStyles: {},
29757
29867
  labelsAsText: false,
29758
29868
  legendPosition: "none"
@@ -29760,49 +29870,25 @@ function buildTwoColumnChart(columns, getters) {
29760
29870
  if (columns[0].type === "date" && columns[1].type === "number") return {
29761
29871
  ...DEFAULT_LINE_CHART_CONFIG,
29762
29872
  type: "line",
29763
- dataSource: {
29764
- type: "range",
29765
- dataSets: [{
29766
- dataRange: getUnboundRange(getters, columns[1].zone),
29767
- dataSetId: "0"
29768
- }],
29769
- labelRange: getUnboundRange(getters, columns[0].zone),
29770
- dataSetsHaveTitle: isDatasetTitled(getters, columns[0])
29771
- },
29873
+ dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[0].zone), getUnboundRange(getters, columns[0].zone)),
29772
29874
  dataSetStyles: {}
29773
29875
  };
29774
29876
  if (columns[0].type === "text" && columns[1].type === "number") {
29775
29877
  const textColumn = columns[0];
29776
29878
  const numberColumn = columns[1];
29777
29879
  const { uniqueCount, totalCount } = getCellStats(getters, textColumn.zone);
29778
- const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn);
29880
+ const dataSetsHaveTitle = isDatasetTitled(getters, numberColumn.zone);
29779
29881
  if (uniqueCount !== totalCount) return {
29780
29882
  type: "treemap",
29781
29883
  title: {},
29782
- dataSource: {
29783
- type: "range",
29784
- dataSets: [{
29785
- dataRange: getUnboundRange(getters, textColumn.zone),
29786
- dataSetId: "0"
29787
- }],
29788
- labelRange: getUnboundRange(getters, numberColumn.zone),
29789
- dataSetsHaveTitle
29790
- },
29884
+ dataSource: rangeSource([dataset(textColumn.zone, getters)], dataSetsHaveTitle, getUnboundRange(getters, numberColumn.zone)),
29791
29885
  dataSetStyles: {},
29792
29886
  legendPosition: "none"
29793
29887
  };
29794
29888
  }
29795
29889
  return {
29796
29890
  ...DEFAULT_BAR_CHART_CONFIG,
29797
- dataSource: {
29798
- type: "range",
29799
- dataSets: [{
29800
- dataRange: getUnboundRange(getters, columns[1].zone),
29801
- dataSetId: "0"
29802
- }],
29803
- labelRange: getUnboundRange(getters, columns[0].zone),
29804
- dataSetsHaveTitle: isDatasetTitled(getters, columns[1])
29805
- },
29891
+ dataSource: rangeSource([dataset(columns[1].zone, getters)], isDatasetTitled(getters, columns[1].zone), getUnboundRange(getters, columns[0].zone)),
29806
29892
  dataSetStyles: {}
29807
29893
  };
29808
29894
  }
@@ -29816,42 +29902,26 @@ function buildTwoColumnChart(columns, getters) {
29816
29902
  */
29817
29903
  function buildMultiColumnChart(columns, getters) {
29818
29904
  if (columns.length < 3) throw new Error("buildMultiColumnChart expects at least three columns");
29819
- const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col));
29905
+ const dataSetsHaveTitle = columns.some((col) => col.type !== "text" && isDatasetTitled(getters, col.zone));
29820
29906
  const lastColumn = columns[columns.length - 1];
29821
29907
  const columnsExceptLast = columns.slice(0, columns.length - 1);
29822
29908
  if ((lastColumn.type === "percentage" || lastColumn.type === "number") && columnsExceptLast.every((col) => col.type === "text")) {
29823
- const dataSets = columnsExceptLast.map(({ zone }, i) => ({
29824
- dataRange: getUnboundRange(getters, zone),
29825
- dataSetId: i.toString()
29826
- }));
29909
+ const dataSets = columnsExceptLast.map(({ zone }, i) => dataset(zone, getters, String(i)));
29827
29910
  return {
29828
29911
  type: columnsExceptLast.length >= 3 ? "sunburst" : "treemap",
29829
29912
  title: {},
29830
- dataSource: {
29831
- type: "range",
29832
- dataSets,
29833
- dataSetsHaveTitle,
29834
- labelRange: getUnboundRange(getters, lastColumn.zone)
29835
- },
29913
+ dataSource: rangeSource(dataSets, dataSetsHaveTitle, getUnboundRange(getters, lastColumn.zone)),
29836
29914
  dataSetStyles: {},
29837
29915
  legendPosition: "none"
29838
29916
  };
29839
29917
  }
29840
29918
  const firstColumn = columns[0];
29841
29919
  const columnsExceptFirst = columns.slice(1);
29842
- const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => ({
29843
- dataRange: getUnboundRange(getters, zone),
29844
- dataSetId: i.toString()
29845
- }));
29920
+ const rangesOfColumnsExceptFirst = columnsExceptFirst.map(({ zone }, i) => dataset(zone, getters, String(i)));
29846
29921
  if (columnsExceptFirst.every((col) => col.type === "percentage")) return {
29847
29922
  type: "pie",
29848
29923
  title: {},
29849
- dataSource: {
29850
- type: "range",
29851
- dataSets: rangesOfColumnsExceptFirst,
29852
- labelRange: getUnboundRange(getters, firstColumn.zone),
29853
- dataSetsHaveTitle
29854
- },
29924
+ dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
29855
29925
  dataSetStyles: {},
29856
29926
  aggregated: false,
29857
29927
  legendPosition: "top"
@@ -29859,23 +29929,13 @@ function buildMultiColumnChart(columns, getters) {
29859
29929
  if (firstColumn.type === "date" && columnsExceptFirst.every((col) => col.type === "number")) return {
29860
29930
  ...DEFAULT_LINE_CHART_CONFIG,
29861
29931
  type: "line",
29862
- dataSource: {
29863
- type: "range",
29864
- dataSets: rangesOfColumnsExceptFirst,
29865
- labelRange: getUnboundRange(getters, firstColumn.zone),
29866
- dataSetsHaveTitle
29867
- },
29932
+ dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
29868
29933
  dataSetStyles: {},
29869
29934
  legendPosition: "top"
29870
29935
  };
29871
29936
  return {
29872
29937
  ...DEFAULT_BAR_CHART_CONFIG,
29873
- dataSource: {
29874
- type: "range",
29875
- dataSets: rangesOfColumnsExceptFirst,
29876
- labelRange: getUnboundRange(getters, firstColumn.zone),
29877
- dataSetsHaveTitle
29878
- },
29938
+ dataSource: rangeSource(rangesOfColumnsExceptFirst, dataSetsHaveTitle, getUnboundRange(getters, firstColumn.zone)),
29879
29939
  dataSetStyles: {},
29880
29940
  legendPosition: "top"
29881
29941
  };
@@ -29902,17 +29962,10 @@ function buildScorecard(zone, getters) {
29902
29962
  function getSmartChartDefinition(zones, getters) {
29903
29963
  const columns = categorizeColumns(zones, getters);
29904
29964
  if (columns.length === 0 || columns.every((col) => col.type === "empty")) {
29905
- const dataSets = columns.map(({ zone }, i) => ({
29906
- dataRange: getUnboundRange(getters, zone),
29907
- dataSetId: i.toString()
29908
- }));
29965
+ const dataSets = columns.map(({ zone }, i) => dataset(zone, getters, String(i)));
29909
29966
  return {
29910
29967
  ...DEFAULT_BAR_CHART_CONFIG,
29911
- dataSource: {
29912
- type: "range",
29913
- dataSets,
29914
- dataSetsHaveTitle: false
29915
- }
29968
+ dataSource: rangeSource(dataSets, false)
29916
29969
  };
29917
29970
  }
29918
29971
  const nonEmptyColumns = columns.filter((col) => col.type !== "empty");
@@ -31600,7 +31653,7 @@ var SelectionInput = class extends Component {
31600
31653
  dragAndDrop = useDragAndDropListItems();
31601
31654
  focusedInputRef = signal.ref(HTMLInputElement);
31602
31655
  unfocusedInputRef = signal.ref(HTMLInputElement);
31603
- selectionRef = signal(null);
31656
+ selectionRef = signal.ref();
31604
31657
  DOMFocusableElementStore;
31605
31658
  store;
31606
31659
  get ranges() {
@@ -32107,7 +32160,7 @@ var ColorPickerWidget = class extends Component {
32107
32160
  dropdownMaxHeight: types$1.Pixel().optional(),
32108
32161
  class: types$1.string().optional()
32109
32162
  });
32110
- colorPickerButtonRef = signal(null);
32163
+ colorPickerButtonRef = signal.ref();
32111
32164
  get iconStyle() {
32112
32165
  return this.props.currentColor ? `border-color: ${this.props.currentColor}` : "border-bottom-style: hidden";
32113
32166
  }
@@ -32135,8 +32188,8 @@ var NumberEditor = class extends Component {
32135
32188
  });
32136
32189
  dropdown = proxy({ isOpen: false });
32137
32190
  inputRef = signal.ref(HTMLInputElement);
32138
- rootEditorRef = signal(null);
32139
- valueListRef = signal(null);
32191
+ rootEditorRef = signal.ref();
32192
+ valueListRef = signal.ref();
32140
32193
  DOMFocusableElementStore;
32141
32194
  setup() {
32142
32195
  this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
@@ -32442,7 +32495,7 @@ var ChartTypePickerPopover = class extends Component {
32442
32495
  });
32443
32496
  categories = chartCategories;
32444
32497
  chartTypeByCategories = {};
32445
- popoverRef = signal(null);
32498
+ popoverRef = signal.ref();
32446
32499
  setup() {
32447
32500
  useListener(window, "pointerdown", this.onExternalClick.bind(this), { capture: true });
32448
32501
  for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
@@ -32467,7 +32520,7 @@ var CogWheelMenu = class extends Component {
32467
32520
  static template = "o-spreadsheet-CogWheelMenu";
32468
32521
  static components = { MenuPopover };
32469
32522
  props = useProps({ items: types$1.array(types$1.ActionSpec()) });
32470
- buttonRef = signal(null);
32523
+ buttonRef = signal.ref();
32471
32524
  menuState = proxy({
32472
32525
  isOpen: false,
32473
32526
  anchorRect: null,
@@ -32511,8 +32564,8 @@ var CarouselPanel = class extends Component {
32511
32564
  });
32512
32565
  DEFAULT_CAROUSEL_TITLE_STYLE = DEFAULT_CAROUSEL_TITLE_STYLE;
32513
32566
  dragAndDrop = useDragAndDropListItems();
32514
- previewListRef = signal(null);
32515
- addChartButton = signal(null);
32567
+ previewListRef = signal.ref();
32568
+ addChartButton = signal.ref(HTMLButtonElement);
32516
32569
  state = proxy({
32517
32570
  popoverProps: void 0,
32518
32571
  currentRange: void 0
@@ -32956,7 +33009,7 @@ var BarConfigPanel = class extends GenericChartConfigPanel {
32956
33009
  var Collapse = class extends Component {
32957
33010
  static template = "o-spreadsheet-Collapse";
32958
33011
  props = useProps({ isCollapsed: types$1.boolean() });
32959
- contentRef = signal(null);
33012
+ contentRef = signal.ref();
32960
33013
  setup() {
32961
33014
  onMounted(() => {
32962
33015
  if (this.props.isCollapsed) this.contentRef()?.classList.add("d-none");
@@ -33009,7 +33062,7 @@ var ChartAnnotation = class extends Component {
33009
33062
  TextInput
33010
33063
  };
33011
33064
  props = useProps(chartSidePanelPropsDefinition);
33012
- editorRef = signal(null);
33065
+ editorRef = signal.ref();
33013
33066
  state = proxy({
33014
33067
  annotationTextInput: this.props.definition.annotationText || "",
33015
33068
  isOverflowing: false
@@ -33331,7 +33384,7 @@ var RoundColorPicker = class extends Component {
33331
33384
  onColorPicked: types$1.function(),
33332
33385
  disableNoColor: types$1.boolean().optional()
33333
33386
  });
33334
- colorPickerButtonRef = signal(null);
33387
+ colorPickerButtonRef = signal.ref();
33335
33388
  state;
33336
33389
  setup() {
33337
33390
  this.state = proxy({ pickerOpened: false });
@@ -36173,6 +36226,7 @@ var helpers_index_exports$1 = /* @__PURE__ */ __exportAll({
36173
36226
  getDefinedAxis: () => getDefinedAxis,
36174
36227
  getPieColors: () => getPieColors,
36175
36228
  isTrendLineAxis: () => isTrendLineAxis,
36229
+ limitChartConfigDataPoints: () => limitChartConfigDataPoints,
36176
36230
  shouldRemoveFirstLabel: () => shouldRemoveFirstLabel,
36177
36231
  toExcelDataset: () => toExcelDataset,
36178
36232
  toExcelLabelRange: () => toExcelLabelRange,
@@ -36943,7 +36997,7 @@ var ColorScalePicker = class extends Component {
36943
36997
  popoverProps: void 0,
36944
36998
  popoverStyle: ""
36945
36999
  });
36946
- popoverRef = signal(null);
37000
+ popoverRef = signal.ref(HTMLTableElement);
36947
37001
  setup() {
36948
37002
  useExternalListener(window, "click", this.closePopover);
36949
37003
  }
@@ -37192,7 +37246,7 @@ var TextValueProvider = class extends Component {
37192
37246
  onValueSelected: types$1.function(),
37193
37247
  onValueHovered: types$1.function()
37194
37248
  });
37195
- autoCompleteListRef = signal(null);
37249
+ autoCompleteListRef = signal.ref();
37196
37250
  setup() {
37197
37251
  useEffect(() => {
37198
37252
  const selectedIndex = this.props.selectedIndex;
@@ -37483,7 +37537,7 @@ var SpeechBubble = class extends Component {
37483
37537
  anchorRect: types$1.Rect()
37484
37538
  });
37485
37539
  spreadsheetRect = useSpreadsheetRect();
37486
- bubbleRef = signal(null);
37540
+ bubbleRef = signal.ref();
37487
37541
  setup() {
37488
37542
  useEffect(() => {
37489
37543
  const el = this.bubbleRef();
@@ -37528,8 +37582,8 @@ var Composer = class extends Component {
37528
37582
  showAssistant: types$1.boolean().optional(true)
37529
37583
  });
37530
37584
  DOMFocusableElementStore;
37531
- composerRef = signal(null);
37532
- containerRef = signal(null);
37585
+ composerRef = signal.ref();
37586
+ containerRef = signal.ref();
37533
37587
  contentHelper = new ContentEditableHelper(this.composerRef());
37534
37588
  composerState = proxy({
37535
37589
  positionStart: 0,
@@ -40031,7 +40085,7 @@ var ChartTypePicker = class extends Component {
40031
40085
  chartId: types$1.UID(),
40032
40086
  chartPanelStore: types$1.Store()
40033
40087
  });
40034
- selectRef = signal(null);
40088
+ selectRef = signal.ref();
40035
40089
  state = proxy({ popoverProps: void 0 });
40036
40090
  getSupportedChartTypes() {
40037
40091
  let supportedTypes;
@@ -40599,7 +40653,7 @@ var ColumnStatsPanel = class extends Component {
40599
40653
  highlightPositions: []
40600
40654
  });
40601
40655
  store;
40602
- chartCanvasRef = signal(null);
40656
+ chartCanvasRef = signal.ref(HTMLCanvasElement);
40603
40657
  chart;
40604
40658
  setup() {
40605
40659
  this.store = useStore(ColumnStatisticsStore);
@@ -42027,7 +42081,7 @@ var ConditionalFormatPreview = class extends Component {
42027
42081
  class: types$1.string()
42028
42082
  });
42029
42083
  icons = ICONS;
42030
- cfPreviewRef = signal(null);
42084
+ cfPreviewRef = signal.ref();
42031
42085
  setup() {
42032
42086
  useHighlightsOnHover(this.cfPreviewRef, this);
42033
42087
  }
@@ -42084,7 +42138,7 @@ var ConditionalFormatPreviewList = class extends Component {
42084
42138
  static components = { ConditionalFormatPreview };
42085
42139
  props = useProps({ onCloseSidePanel: types$1.function() });
42086
42140
  dragAndDrop = useDragAndDropListItems();
42087
- cfListRef = signal(null);
42141
+ cfListRef = signal.ref();
42088
42142
  get conditionalFormats() {
42089
42143
  return this.env.model.getters.getConditionalFormats(this.env.model.getters.getActiveSheetId());
42090
42144
  }
@@ -42144,6 +42198,1477 @@ var ConditionalFormatPreviewList = class extends Component {
42144
42198
  }
42145
42199
  };
42146
42200
 
42201
+ //#endregion
42202
+ //#region src/stores/chart_drag_store.ts
42203
+ /**
42204
+ * Holds the id of the figure that a dragged chart suggestion is currently
42205
+ * hovering over, so it can be highlighted reactively instead of through direct DOM manipulation.
42206
+ */
42207
+ var ChartDragStore = class {
42208
+ mutators = ["setHighlightedFigure"];
42209
+ highlightedFigureId = void 0;
42210
+ setHighlightedFigure(figureId) {
42211
+ this.highlightedFigureId = figureId;
42212
+ }
42213
+ };
42214
+
42215
+ //#endregion
42216
+ //#region src/components/helpers/chart_drag_and_drop.ts
42217
+ function getDefaultChartFigureSize(type) {
42218
+ if (type === "scorecard") return {
42219
+ width: 213,
42220
+ height: 101
42221
+ };
42222
+ return {
42223
+ width: 536,
42224
+ height: 335
42225
+ };
42226
+ }
42227
+ function getCarouselOverlappingChart(figureUI, otherFigures, matchTags) {
42228
+ if (figureUI.tag !== "chart") return;
42229
+ const figureCenterX = figureUI.x + figureUI.width / 2;
42230
+ const figureCenterY = figureUI.y + figureUI.height / 2;
42231
+ let bestMatch;
42232
+ let smallestDistance = Infinity;
42233
+ for (const figure of otherFigures) {
42234
+ if (!matchTags.includes(figure.tag)) continue;
42235
+ const targetCenterX = figure.x + figure.width / 2;
42236
+ const targetCenterY = figure.y + figure.height / 2;
42237
+ const distanceX = Math.abs(figureCenterX - targetCenterX);
42238
+ const distanceY = Math.abs(figureCenterY - targetCenterY);
42239
+ const squaredDistance = distanceX ** 2 + distanceY ** 2;
42240
+ if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
42241
+ smallestDistance = squaredDistance;
42242
+ bestMatch = figure;
42243
+ }
42244
+ }
42245
+ return bestMatch;
42246
+ }
42247
+ /**
42248
+ * Start dragging a floating preview of the given chart definition, following the mouse.
42249
+ * On drop, creates the chart on the grid (or inside a carousel if dropped on one).
42250
+ */
42251
+ function startChartDragAndDrop(env, definition, ev) {
42252
+ const zoom = env.getStore(ZoomStore).zoomLevel;
42253
+ const gridPosition = gridOverlayPosition(zoom);
42254
+ const spreadsheet = document.querySelector(".o-spreadsheet");
42255
+ if (!spreadsheet) return;
42256
+ const startX = ev.clientX / zoom;
42257
+ const startY = ev.clientY / zoom;
42258
+ const { width, height } = getDefaultChartFigureSize(definition.type);
42259
+ const figureWidth = width * zoom;
42260
+ const figureHeight = height * zoom;
42261
+ const getters = env.model.getters;
42262
+ const sheetId = getters.getActiveSheetId();
42263
+ let container = null;
42264
+ let destroyChart = void 0;
42265
+ const chartDragStore = env.getStore(ChartDragStore);
42266
+ const previousCursor = document.body.style.cursor;
42267
+ document.body.style.cursor = "grabbing";
42268
+ /** Grid coordinates (in sheet pixels) of a mouse position, or undefined if outside the grid. */
42269
+ const getGridPosition = (clientX, clientY) => {
42270
+ if (clientX > gridPosition.x + gridPosition.width || clientY > gridPosition.y + gridPosition.height) return;
42271
+ const { scrollX, scrollY } = env.getStore(ViewportsStore).activeSheetScrollInfo;
42272
+ return {
42273
+ x: Math.max(0, (clientX - gridPosition.x) / zoom + scrollX),
42274
+ y: Math.max(0, (clientY - gridPosition.y) / zoom + scrollY)
42275
+ };
42276
+ };
42277
+ /** Carousel or standalone chart the dragged chart is dropped onto, if any. */
42278
+ const getOverlappingFigure = (clientX, clientY) => {
42279
+ const position = getGridPosition(clientX, clientY);
42280
+ if (!position) return;
42281
+ const figureUI = {
42282
+ tag: "chart",
42283
+ ...position,
42284
+ width,
42285
+ height
42286
+ };
42287
+ const otherFigures = env.getStore(ViewportsStore).visibleFigures;
42288
+ return getCarouselOverlappingChart(figureUI, otherFigures, ["carousel", "chart"]);
42289
+ };
42290
+ const halfWidth = figureWidth / 2;
42291
+ const halfHeight = figureHeight / 2;
42292
+ const onMouseMove = (e) => {
42293
+ if (Math.abs(e.clientX - startX) <= 5 && Math.abs(e.clientY - startY) <= 5) return;
42294
+ if (container === null) {
42295
+ container = document.createElement("div");
42296
+ container.className = "o-chart-drag-preview os-theme-dependant position-fixed border pe-none";
42297
+ container.style.width = `${width}px`;
42298
+ container.style.height = `${height}px`;
42299
+ container.style.zoom = `${zoom}`;
42300
+ const canvas = document.createElement("canvas");
42301
+ canvas.className = "w-100 h-100";
42302
+ container.appendChild(canvas);
42303
+ spreadsheet.appendChild(container);
42304
+ destroyChart = drawChartOnCanvas(canvas, SpreadsheetChart.fromStrDefinition(getters, sheetId, definition).getRuntime(getters, "newChart"), {
42305
+ width,
42306
+ height
42307
+ }, definition.type, zoom);
42308
+ }
42309
+ container.style.left = `${Math.max(gridPosition.x, (e.clientX - halfWidth) / zoom)}px`;
42310
+ container.style.top = `${Math.max(gridPosition.y, (e.clientY - halfHeight) / zoom)}px`;
42311
+ const overlappingFigure = getOverlappingFigure(e.clientX - halfWidth, e.clientY - halfHeight);
42312
+ container.style.opacity = overlappingFigure?.id ? "0.6" : "0.9";
42313
+ chartDragStore.setHighlightedFigure(overlappingFigure?.id);
42314
+ };
42315
+ const onMouseUp = (mouseEvent) => {
42316
+ chartDragStore.setHighlightedFigure(void 0);
42317
+ if (container !== null) {
42318
+ spreadsheet.removeChild(container);
42319
+ container = null;
42320
+ }
42321
+ destroyChart?.();
42322
+ document.body.style.cursor = previousCursor;
42323
+ let position = getGridPosition(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
42324
+ if (Math.abs(mouseEvent.clientX / zoom - startX) <= 5 && Math.abs(mouseEvent.clientY / zoom - startY) <= 5) position = {
42325
+ x: 0,
42326
+ y: 0
42327
+ };
42328
+ else if (!position || position.x + halfWidth > gridPosition.width) return;
42329
+ const { col, row, offset } = env.getStore(ViewportsStore).viewports.getPositionAnchorOffset(sheetId, position);
42330
+ const payload = {
42331
+ chartId: UuidGenerator.smallUuid(),
42332
+ figureId: UuidGenerator.smallUuid(),
42333
+ sheetId,
42334
+ size: {
42335
+ width,
42336
+ height
42337
+ },
42338
+ definition,
42339
+ col,
42340
+ row,
42341
+ offset
42342
+ };
42343
+ const overlappingFigure = getOverlappingFigure(mouseEvent.clientX - halfWidth, mouseEvent.clientY - halfHeight);
42344
+ if (overlappingFigure?.tag === "carousel") env.model.dispatch("ADD_NEW_CHART_TO_CAROUSEL", {
42345
+ sheetId,
42346
+ figureId: overlappingFigure.id,
42347
+ newChartId: UuidGenerator.smallUuid(),
42348
+ chartDefinition: definition
42349
+ });
42350
+ else if (overlappingFigure?.tag === "chart") env.model.dispatch("CREATE_CHART_AND_MERGE_INTO_CAROUSEL", {
42351
+ chartId: payload.chartId,
42352
+ figureId: payload.figureId,
42353
+ sheetId: payload.sheetId,
42354
+ definition: payload.definition,
42355
+ baseFigureId: overlappingFigure.id
42356
+ });
42357
+ else env.model.dispatch("CREATE_CHART", payload);
42358
+ };
42359
+ startDnd(onMouseMove, onMouseUp);
42360
+ }
42361
+
42362
+ //#endregion
42363
+ //#region src/components/side_panel/data_analysis/chart_suggestion_preview.ts
42364
+ const PREVIEW_BUBBLE_RADIUS_RATIO = .3;
42365
+ const MIN_PREVIEW_BUBBLE_RADIUS = 1;
42366
+ var ChartSuggestionPreview = class extends Component {
42367
+ static template = "o-spreadsheet-ChartSuggestionPreview";
42368
+ props = useProps({
42369
+ definition: types$1.ChartDefinition(),
42370
+ description: types$1.string(),
42371
+ onPointerDown: types$1.function()
42372
+ });
42373
+ chartCanvasRef = signal.ref(HTMLCanvasElement);
42374
+ chart;
42375
+ setup() {
42376
+ onMounted(() => registerChartJSExtensions());
42377
+ onWillUnmount(() => this.destroyChart());
42378
+ useLayoutEffect(() => {
42379
+ this.updateChart();
42380
+ }, () => [this.props.definition]);
42381
+ }
42382
+ getChartConfiguration() {
42383
+ const getters = this.env.model.getters;
42384
+ const activeSheetId = getters.getActiveSheetId();
42385
+ const runtime = SpreadsheetChart.fromStrDefinition(getters, activeSheetId, this.props.definition).getRuntime(getters, "myChart");
42386
+ if (!("chartJsConfig" in runtime)) return null;
42387
+ let config = runtime.chartJsConfig;
42388
+ const existingScales = config.options?.scales ?? {};
42389
+ config = {
42390
+ ...config,
42391
+ data: this.props.definition.type === "bubble" ? {
42392
+ ...config.data,
42393
+ datasets: (config.data?.datasets || []).map((dataset) => ({
42394
+ ...dataset,
42395
+ pointRadius: this.scaleBubblePointRadius(dataset.pointRadius),
42396
+ pointHoverRadius: this.scaleBubblePointRadius(dataset.pointHoverRadius)
42397
+ }))
42398
+ } : config.data,
42399
+ options: {
42400
+ ...config.options,
42401
+ plugins: {
42402
+ ...config.options?.plugins,
42403
+ legend: { display: false },
42404
+ title: { display: false },
42405
+ tooltip: { enabled: false }
42406
+ },
42407
+ events: [],
42408
+ animation: false,
42409
+ scales: this.getScalesCongif(existingScales)
42410
+ }
42411
+ };
42412
+ return limitChartConfigDataPoints(config);
42413
+ }
42414
+ getScalesCongif(scales) {
42415
+ if (scales.x) scales.x = {
42416
+ ...scales.x,
42417
+ ticks: { display: false },
42418
+ border: { display: false }
42419
+ };
42420
+ if (scales.y) scales.y = {
42421
+ ...scales.y,
42422
+ ticks: { display: false },
42423
+ border: { display: false }
42424
+ };
42425
+ if (scales.r) scales.r = {
42426
+ ...scales.r,
42427
+ ticks: { display: false },
42428
+ pointLabels: { display: false }
42429
+ };
42430
+ return scales;
42431
+ }
42432
+ scaleBubblePointRadius(pointRadius) {
42433
+ if (Array.isArray(pointRadius)) return pointRadius.map((radius) => radius <= 0 ? radius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, radius * PREVIEW_BUBBLE_RADIUS_RATIO));
42434
+ if (typeof pointRadius === "number") return pointRadius <= 0 ? pointRadius : Math.max(MIN_PREVIEW_BUBBLE_RADIUS, pointRadius * PREVIEW_BUBBLE_RADIUS_RATIO);
42435
+ return pointRadius ?? MIN_PREVIEW_BUBBLE_RADIUS;
42436
+ }
42437
+ updateChart() {
42438
+ this.destroyChart();
42439
+ const config = this.getChartConfiguration();
42440
+ if (!config) {
42441
+ this.drawNativeChart();
42442
+ return;
42443
+ }
42444
+ const ctx = this.chartCanvasRef()?.getContext("2d");
42445
+ if (!ctx || !globalThis.Chart) return;
42446
+ this.chart = new globalThis.Chart(ctx, config);
42447
+ }
42448
+ drawNativeChart() {
42449
+ const canvas = this.chartCanvasRef();
42450
+ if (!canvas) return;
42451
+ const getters = this.env.model.getters;
42452
+ let runtime = SpreadsheetChart.fromStrDefinition(getters, getters.getActiveSheetId(), this.props.definition).getRuntime(getters, "myChart");
42453
+ const { type } = this.props.definition;
42454
+ if (type === "scorecard") {
42455
+ const rect = canvas.getBoundingClientRect();
42456
+ runtime = {
42457
+ ...runtime,
42458
+ keyValueStyle: {
42459
+ ...runtime.keyValueStyle,
42460
+ fontSize: 16
42461
+ }
42462
+ };
42463
+ drawScoreChart(getScorecardConfiguration({
42464
+ width: rect.width || 130,
42465
+ height: rect.height || 120
42466
+ }, runtime), canvas);
42467
+ } else if (type === "gauge") {
42468
+ let gaugeRuntime = runtime;
42469
+ if (gaugeRuntime.title) gaugeRuntime = {
42470
+ ...gaugeRuntime,
42471
+ title: { text: "" }
42472
+ };
42473
+ drawGaugeChart(canvas, gaugeRuntime, 1, void 0, { labelFontSize: 8 });
42474
+ }
42475
+ }
42476
+ destroyChart() {
42477
+ this.chart?.destroy();
42478
+ this.chart = void 0;
42479
+ }
42480
+ };
42481
+
42482
+ //#endregion
42483
+ //#region src/helpers/data_analysis.ts
42484
+ function analyzeColumns(zones, getters) {
42485
+ return getZonesByColumns(zones).map((zone) => analyzeColumn(zone, getters));
42486
+ }
42487
+ function analyzeColumn(zone, getters) {
42488
+ const sheetId = getters.getActiveSheetId();
42489
+ const cells = getters.getEvaluatedCellsInZone(sheetId, zone).filter((c) => c.type !== "empty");
42490
+ if (!cells.length) return {
42491
+ zone,
42492
+ type: "empty",
42493
+ hasHeader: false,
42494
+ rowCount: 0,
42495
+ uniqueCount: 0,
42496
+ uniqueRatio: 0,
42497
+ nonEmpty: []
42498
+ };
42499
+ const firstCell = cells[0];
42500
+ const rest = cells.slice(1);
42501
+ const hasHeader = firstCell.type === "text" && rest.some((c) => c.type !== "text");
42502
+ const dataCells = hasHeader ? rest : cells;
42503
+ const numericValues = dataCells.filter((c) => c.type === "number").map((c) => c.value);
42504
+ const allVals = dataCells.map((c) => String(c.value ?? ""));
42505
+ const uniqueCount = new Set(allVals).size;
42506
+ return {
42507
+ zone,
42508
+ type: computeColumnType(dataCells),
42509
+ header: hasHeader ? firstCell.value : void 0,
42510
+ hasHeader,
42511
+ rowCount: dataCells.length,
42512
+ uniqueCount,
42513
+ uniqueRatio: allVals.length > 0 ? uniqueCount / allVals.length : 0,
42514
+ maxValue: numericValues.length ? numericValues.reduce((max, v) => v > max ? v : max, numericValues[0]) : void 0,
42515
+ nonEmpty: dataCells
42516
+ };
42517
+ }
42518
+ function computeColumnType(cells) {
42519
+ if (cells.length === 0) return "empty";
42520
+ if (cells.every((c) => c.type === "error")) return "error";
42521
+ cells = cells.filter((c) => c.type !== "error");
42522
+ if (cells.every((c) => c.type === "boolean")) return "boolean";
42523
+ if (cells.every((c) => c.type === "number" && !!c.format && isDateTimeFormat(c.format))) return "date";
42524
+ else if (cells.every((c) => c.type === "number")) {
42525
+ if (cells.every((c) => c.format?.includes("%"))) return "percentage";
42526
+ return "number";
42527
+ } else {
42528
+ const textVals = cells.filter((c) => c.type === "text").map((c) => c.value);
42529
+ if (textVals.length > 0) {
42530
+ const unique = new Set(textVals).size;
42531
+ return unique / textVals.length < .75 && unique <= 20 ? "categorical" : "label";
42532
+ }
42533
+ }
42534
+ return "empty";
42535
+ }
42536
+
42537
+ //#endregion
42538
+ //#region src/helpers/figures/charts/charts_suggestions_constants.ts
42539
+ /** Above this many rows, shape-based charts (radar, scatter, pyramid) become unreadable. */
42540
+ const MAX_ROWS_FOR_SHAPE_CHART = 10;
42541
+ function barChart(titleText, source, opts = {}) {
42542
+ return {
42543
+ ...opts,
42544
+ type: "bar",
42545
+ title: { text: titleText },
42546
+ dataSource: source,
42547
+ dataSetStyles: {},
42548
+ legendPosition: opts.legendPosition ?? "none",
42549
+ stacked: opts.stacked ?? false,
42550
+ humanize: true,
42551
+ aggregated: opts.aggregated ?? false
42552
+ };
42553
+ }
42554
+ function lineChart(titleText, source, opts = {}) {
42555
+ return {
42556
+ ...opts,
42557
+ type: "line",
42558
+ title: { text: titleText },
42559
+ dataSource: source,
42560
+ dataSetStyles: {},
42561
+ legendPosition: opts.legendPosition ?? "none",
42562
+ stacked: opts.stacked ?? false,
42563
+ cumulative: opts.cumulative ?? false,
42564
+ labelsAsText: false,
42565
+ humanize: true
42566
+ };
42567
+ }
42568
+ function pieChart(titleText, source, opts = {}) {
42569
+ return {
42570
+ ...opts,
42571
+ type: "pie",
42572
+ title: { text: titleText },
42573
+ dataSource: source,
42574
+ dataSetStyles: {},
42575
+ legendPosition: opts.legendPosition ?? "top",
42576
+ humanize: true
42577
+ };
42578
+ }
42579
+ function radarChart(titleText, source, opts = {}) {
42580
+ return {
42581
+ ...opts,
42582
+ type: "radar",
42583
+ title: { text: titleText },
42584
+ dataSource: source,
42585
+ dataSetStyles: {},
42586
+ legendPosition: opts.legendPosition ?? "none",
42587
+ stacked: false,
42588
+ humanize: true
42589
+ };
42590
+ }
42591
+ function sunburstChart(titleText, source) {
42592
+ return {
42593
+ type: "sunburst",
42594
+ title: { text: titleText },
42595
+ dataSource: source,
42596
+ dataSetStyles: {},
42597
+ legendPosition: "none"
42598
+ };
42599
+ }
42600
+ function treemapChart(titleText, source) {
42601
+ return {
42602
+ type: "treemap",
42603
+ title: { text: titleText },
42604
+ dataSource: source,
42605
+ dataSetStyles: {},
42606
+ legendPosition: "none"
42607
+ };
42608
+ }
42609
+ function calendarChart(titleText, source) {
42610
+ return {
42611
+ type: "calendar",
42612
+ title: { text: titleText },
42613
+ dataSource: source,
42614
+ dataSetStyles: {},
42615
+ legendPosition: "none",
42616
+ horizontalGroupBy: "month_number",
42617
+ verticalGroupBy: "day_of_week",
42618
+ humanize: true
42619
+ };
42620
+ }
42621
+ function scatterChart(titleText, source) {
42622
+ return {
42623
+ type: "scatter",
42624
+ title: { text: titleText },
42625
+ dataSource: source,
42626
+ dataSetStyles: {},
42627
+ legendPosition: "none",
42628
+ labelsAsText: false,
42629
+ humanize: true
42630
+ };
42631
+ }
42632
+ function pyramidChart(titleText, source, opts = {}) {
42633
+ return {
42634
+ type: "pyramid",
42635
+ title: { text: titleText },
42636
+ dataSource: source,
42637
+ dataSetStyles: {},
42638
+ legendPosition: opts.legendPosition ?? "top",
42639
+ stacked: false,
42640
+ horizontal: true,
42641
+ humanize: true
42642
+ };
42643
+ }
42644
+ function comboChart(titleText, source, opts = {}) {
42645
+ return {
42646
+ ...opts,
42647
+ type: "combo",
42648
+ title: { text: titleText },
42649
+ dataSource: source,
42650
+ dataSetStyles: {},
42651
+ legendPosition: "top",
42652
+ humanize: true
42653
+ };
42654
+ }
42655
+ function bubbleChart(titleText, xRange, yRanges, sizeRange, labelRange, dataSetsHaveTitle) {
42656
+ return {
42657
+ type: "bubble",
42658
+ title: { text: titleText },
42659
+ humanize: true,
42660
+ dataSetsHaveTitle,
42661
+ yRanges,
42662
+ xRange,
42663
+ sizeRange,
42664
+ labelRange,
42665
+ labelsAsText: false,
42666
+ legendPosition: "none",
42667
+ bubbleColor: { color: "multiple" },
42668
+ verticalAxisPosition: "left"
42669
+ };
42670
+ }
42671
+ function scorecardChart(titleText, keyValue, opts = {}) {
42672
+ return {
42673
+ ...opts,
42674
+ type: "scorecard",
42675
+ title: { text: titleText },
42676
+ keyValue,
42677
+ baselineMode: opts.baselineMode ?? "difference",
42678
+ baselineColorUp: DEFAULT_SCORECARD_BASELINE_COLOR_UP,
42679
+ baselineColorDown: DEFAULT_SCORECARD_BASELINE_COLOR_DOWN,
42680
+ humanize: opts.humanize ?? true
42681
+ };
42682
+ }
42683
+ function gaugeChart(titleText, dataRange, rangeMin, rangeMax) {
42684
+ return {
42685
+ type: "gauge",
42686
+ title: { text: titleText },
42687
+ dataRange,
42688
+ sectionRule: {
42689
+ colors: {
42690
+ lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
42691
+ middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
42692
+ upperColor: DEFAULT_GAUGE_UPPER_COLOR
42693
+ },
42694
+ rangeMin,
42695
+ rangeMax,
42696
+ lowerInflectionPoint: {
42697
+ type: "percentage",
42698
+ value: "33",
42699
+ operator: "<="
42700
+ },
42701
+ upperInflectionPoint: {
42702
+ type: "percentage",
42703
+ value: "66",
42704
+ operator: "<="
42705
+ }
42706
+ },
42707
+ humanize: true
42708
+ };
42709
+ }
42710
+ /** Pattern A — Single numeric column */
42711
+ const SINGLE_NUMBER_COLUMN_SUGGESTIONS = [
42712
+ {
42713
+ description: _t("Highlights the most recent value compared to the previous one."),
42714
+ isApplicable: ({ rowCount }) => rowCount < 3,
42715
+ build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
42716
+ baseline: ctx.prevCellXC,
42717
+ baselineMode: "difference"
42718
+ })
42719
+ },
42720
+ {
42721
+ description: _t("Shows the position of the last value within the data's min-max range."),
42722
+ isApplicable: ({ rowCount }) => rowCount === 3,
42723
+ build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
42724
+ },
42725
+ {
42726
+ description: _t("Compares individual values side-by-side."),
42727
+ isApplicable: ({ rowCount }) => rowCount > 1,
42728
+ build: (ctx) => barChart(ctx.title, ctx.source)
42729
+ },
42730
+ {
42731
+ description: _t("Shows the evolution of all values over the range."),
42732
+ isApplicable: ({ rowCount }) => rowCount > 2,
42733
+ build: (ctx) => lineChart(ctx.title, ctx.source)
42734
+ },
42735
+ {
42736
+ description: _t("Emphasizes total accumulation over the range."),
42737
+ isApplicable: ({ rowCount }) => rowCount > 2,
42738
+ build: (ctx) => lineChart(ctx.title, ctx.source, {
42739
+ fillArea: true,
42740
+ cumulative: true
42741
+ })
42742
+ }
42743
+ ];
42744
+ /** Pattern B — Single percentage column */
42745
+ const SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS = [
42746
+ {
42747
+ description: _t("Shows the last percentage value with its baseline."),
42748
+ isApplicable: ({ rowCount }) => rowCount < 3,
42749
+ build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
42750
+ baseline: ctx.prevCellXC,
42751
+ baselineMode: "percentage"
42752
+ })
42753
+ },
42754
+ {
42755
+ description: _t("Natural fit for a 0–100% range."),
42756
+ isApplicable: ({ rowCount }) => rowCount === 1,
42757
+ build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, "0", ctx.isAboveOne ? "100" : "1")
42758
+ },
42759
+ {
42760
+ description: _t("Natural fit for a 0–100% range."),
42761
+ isApplicable: ({ rowCount }) => rowCount === 3,
42762
+ build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.prevCellXC}`)
42763
+ },
42764
+ {
42765
+ description: _t("Shows completion against total."),
42766
+ isApplicable: ({ rowCount }) => rowCount > 1,
42767
+ build: (ctx) => pieChart(ctx.title, ctx.source, { isDoughnut: true })
42768
+ },
42769
+ {
42770
+ description: _t("Compares all percentage values side-by-side."),
42771
+ isApplicable: ({ rowCount }) => rowCount > 1,
42772
+ build: (ctx) => barChart(ctx.title, ctx.source)
42773
+ }
42774
+ ];
42775
+ /** Pattern C — Single date column */
42776
+ const SINGLE_DATE_COLUMN_SUGGESTIONS = [{
42777
+ description: _t("Shows the last date value."),
42778
+ isApplicable: ({ rowCount }) => rowCount === 1,
42779
+ build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC)
42780
+ }];
42781
+ /** Pattern D — Single categorical column */
42782
+ const SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS = [
42783
+ {
42784
+ description: _t("Shows the share of each category."),
42785
+ build: (ctx) => pieChart(ctx.title, ctx.source, {
42786
+ aggregated: true,
42787
+ legendPosition: "top"
42788
+ })
42789
+ },
42790
+ {
42791
+ description: _t("Same as pie, cleaner proportional look."),
42792
+ build: (ctx) => pieChart(ctx.title, ctx.source, {
42793
+ aggregated: true,
42794
+ isDoughnut: true,
42795
+ legendPosition: "top"
42796
+ })
42797
+ },
42798
+ {
42799
+ description: _t("Absolute count per category."),
42800
+ build: (ctx) => barChart(ctx.title, {
42801
+ ...ctx.source,
42802
+ labelRange: ctx.range
42803
+ }, {
42804
+ legendPosition: "none",
42805
+ aggregated: true
42806
+ })
42807
+ }
42808
+ ];
42809
+ /** Pattern E — Single label column */
42810
+ const SINGLE_LABEL_COLUMN_SUGGESTIONS = [{
42811
+ description: _t("Displays a key performance indicator."),
42812
+ isApplicable: ({ rowCount }) => rowCount === 1,
42813
+ build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
42814
+ baselineMode: "text",
42815
+ humanize: false
42816
+ })
42817
+ }];
42818
+ /** Pattern F — Categorical + Number */
42819
+ const CATEGORICAL_VS_NUMBER_SUGGESTIONS = [
42820
+ {
42821
+ description: _t("Classic category-vs-value comparison."),
42822
+ build: (ctx) => barChart(ctx.title, ctx.source, { aggregated: true })
42823
+ },
42824
+ {
42825
+ description: _t("Better when category labels are long."),
42826
+ build: (ctx) => barChart(ctx.title, ctx.source, {
42827
+ horizontal: true,
42828
+ aggregated: true
42829
+ })
42830
+ },
42831
+ {
42832
+ description: _t("Share of total per category."),
42833
+ build: (ctx) => pieChart(ctx.title, ctx.source, {
42834
+ legendPosition: "top",
42835
+ aggregated: true
42836
+ })
42837
+ },
42838
+ {
42839
+ description: _t("Proportional area — good for many categories."),
42840
+ build: (ctx) => treemapChart(ctx.title, ctx.treemapSource)
42841
+ }
42842
+ ];
42843
+ /** Patterns G & J — Date + Number or Date + Percentage */
42844
+ const DATE_VS_SERIES_SUGGESTIONS = [
42845
+ {
42846
+ description: _t("Best for visualizing time-series trends."),
42847
+ build: (ctx) => lineChart(ctx.title, ctx.source)
42848
+ },
42849
+ {
42850
+ description: _t("Emphasizes total volume over time."),
42851
+ build: (ctx) => lineChart(ctx.title, ctx.source, {
42852
+ fillArea: true,
42853
+ cumulative: true
42854
+ })
42855
+ },
42856
+ {
42857
+ description: _t("Period-by-period comparison."),
42858
+ build: (ctx) => barChart(ctx.title, ctx.source)
42859
+ },
42860
+ {
42861
+ description: _t("Shows intensity variation across days of the year."),
42862
+ isApplicable: ({ isPercentage }) => !isPercentage,
42863
+ build: (ctx) => calendarChart(ctx.title, ctx.source)
42864
+ }
42865
+ ];
42866
+ /** Pattern H — Number + Number */
42867
+ const NUMBER_VS_NUMBER_SUGGESTIONS = [
42868
+ {
42869
+ description: _t("Highlights the second metric compared to the first one."),
42870
+ isApplicable: ({ rowCount1, rowCount2 }) => rowCount1 === 1 && rowCount2 === 1,
42871
+ build: (ctx) => scorecardChart(ctx.title, ctx.lastCellXC, {
42872
+ baseline: ctx.prevCellXC,
42873
+ baselineMode: "difference"
42874
+ })
42875
+ },
42876
+ {
42877
+ description: _t("Side-by-side comparison of two numeric series."),
42878
+ build: (ctx) => barChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
42879
+ },
42880
+ {
42881
+ description: _t("Reveals correlation between two numeric variables."),
42882
+ isApplicable: ({ rowCount1 }) => rowCount1 > 2,
42883
+ build: (ctx) => scatterChart(ctx.title, ctx.source2)
42884
+ },
42885
+ {
42886
+ description: _t("Bar for the first series, line for the second — good for mixed scales."),
42887
+ isApplicable: ({ rowCount1 }) => rowCount1 > 2,
42888
+ build: (ctx) => comboChart(ctx.title, ctx.sourceBoth)
42889
+ },
42890
+ {
42891
+ description: _t("When both metrics contribute to a total."),
42892
+ isApplicable: ({ rowCount1 }) => rowCount1 > 2,
42893
+ build: (ctx) => lineChart(ctx.title, ctx.sourceBoth, {
42894
+ stacked: true,
42895
+ fillArea: true,
42896
+ legendPosition: "top"
42897
+ })
42898
+ },
42899
+ {
42900
+ description: _t("Shape-based comparison when rows represent named entities."),
42901
+ isApplicable: ({ rowCount1 }) => rowCount1 > 2 && rowCount1 <= MAX_ROWS_FOR_SHAPE_CHART,
42902
+ build: (ctx) => radarChart(ctx.title, ctx.sourceBoth, { legendPosition: "top" })
42903
+ }
42904
+ ];
42905
+ /** Pattern I — Categorical + Percentage */
42906
+ const CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS = [
42907
+ {
42908
+ description: _t("Vertical comparison of rates per category."),
42909
+ build: (ctx) => barChart(ctx.title, ctx.source)
42910
+ },
42911
+ {
42912
+ description: _t("Progress-bar style per category."),
42913
+ build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
42914
+ },
42915
+ {
42916
+ description: _t("Share of total percentage across categories."),
42917
+ build: (ctx) => pieChart(ctx.title, ctx.source, { legendPosition: "top" })
42918
+ },
42919
+ {
42920
+ description: _t("Comparison of completion rates across categories."),
42921
+ isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
42922
+ build: (ctx) => radarChart(ctx.title, ctx.source)
42923
+ }
42924
+ ];
42925
+ /** Pattern K — Label + Number */
42926
+ const LABEL_VS_NUMBER_SUGGESTIONS = [
42927
+ {
42928
+ description: _t("Highlights the most recent value for the named entity."),
42929
+ isApplicable: ({ rowCount }) => rowCount === 1,
42930
+ build: (ctx) => scorecardChart("", ctx.lastCellXC, {
42931
+ humanize: false,
42932
+ baselineMode: "text",
42933
+ baseline: ctx.prevCellXC
42934
+ })
42935
+ },
42936
+ {
42937
+ description: _t("Vertical comparison across named items."),
42938
+ isApplicable: ({ rowCount }) => rowCount > 1,
42939
+ build: (ctx) => barChart(ctx.title, ctx.source)
42940
+ },
42941
+ {
42942
+ description: _t("Works well for named entities with long labels."),
42943
+ isApplicable: ({ rowCount }) => rowCount > 1,
42944
+ build: (ctx) => barChart(ctx.title, ctx.source, { horizontal: true })
42945
+ },
42946
+ {
42947
+ description: _t("Share of total per category."),
42948
+ isApplicable: ({ rowCount }) => rowCount > 1 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
42949
+ build: (ctx) => pieChart(ctx.title, ctx.source, {
42950
+ legendPosition: "top",
42951
+ aggregated: true
42952
+ })
42953
+ },
42954
+ {
42955
+ description: _t("Shape-based comparison across labeled items."),
42956
+ isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
42957
+ build: (ctx) => radarChart(ctx.title, ctx.source)
42958
+ }
42959
+ ];
42960
+ /** Pattern M — Categorical + Multiple Numbers */
42961
+ const CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
42962
+ {
42963
+ description: _t("Side-by-side comparison across categories for each series."),
42964
+ build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
42965
+ },
42966
+ {
42967
+ description: _t("Shows composition and total per category."),
42968
+ build: (ctx) => barChart(ctx.title, ctx.source, {
42969
+ stacked: true,
42970
+ legendPosition: "top"
42971
+ })
42972
+ },
42973
+ {
42974
+ description: _t("Trend per series across categories."),
42975
+ build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
42976
+ },
42977
+ {
42978
+ description: _t("Volume and composition across categories."),
42979
+ build: (ctx) => lineChart(ctx.title, ctx.source, {
42980
+ stacked: true,
42981
+ fillArea: true,
42982
+ legendPosition: "top"
42983
+ })
42984
+ },
42985
+ {
42986
+ description: _t("Shape comparison across metrics (best for ≤ 10 rows)."),
42987
+ isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
42988
+ build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
42989
+ }
42990
+ ];
42991
+ /** Pattern N — Date + Multiple Numbers */
42992
+ const DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
42993
+ {
42994
+ description: _t("Trend comparison across multiple metrics over time."),
42995
+ build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
42996
+ },
42997
+ {
42998
+ description: _t("Volume composition over time."),
42999
+ build: (ctx) => lineChart(ctx.title, ctx.source, {
43000
+ stacked: true,
43001
+ fillArea: true,
43002
+ legendPosition: "top"
43003
+ })
43004
+ },
43005
+ {
43006
+ description: _t("Bar for the primary metric, line for the others."),
43007
+ build: (ctx) => comboChart(ctx.title, ctx.source)
43008
+ },
43009
+ {
43010
+ description: _t("Period-by-period grouped comparison."),
43011
+ build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
43012
+ }
43013
+ ];
43014
+ /** Pattern O — Categorical + Categorical + Number */
43015
+ const MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS = [
43016
+ {
43017
+ description: _t("Two-level hierarchy weighted by value."),
43018
+ build: (ctx) => sunburstChart(ctx.title, ctx.hierarchySource)
43019
+ },
43020
+ {
43021
+ description: _t("Proportional nested area weighted by value."),
43022
+ build: (ctx) => treemapChart(ctx.title, ctx.hierarchySource)
43023
+ },
43024
+ {
43025
+ description: _t("One series per inner category, grouped by outer category."),
43026
+ build: (ctx) => barChart(ctx.title, ctx.barSource, { legendPosition: "top" })
43027
+ },
43028
+ {
43029
+ description: _t("Contribution of inner categories per outer category."),
43030
+ build: (ctx) => barChart(ctx.title, ctx.barSource, {
43031
+ stacked: true,
43032
+ legendPosition: "top"
43033
+ })
43034
+ }
43035
+ ];
43036
+ /** Pattern P — Categorical + Date + Number */
43037
+ const CATEGORICAL_DATE_NUMBER_SUGGESTIONS = [
43038
+ {
43039
+ description: _t("Trend of values over time."),
43040
+ build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, { legendPosition: "top" })
43041
+ },
43042
+ {
43043
+ description: _t("Volume contribution over time."),
43044
+ build: (ctx) => lineChart(ctx.title, ctx.sourceByDate, {
43045
+ stacked: true,
43046
+ fillArea: true,
43047
+ legendPosition: "top"
43048
+ })
43049
+ },
43050
+ {
43051
+ description: _t("Period × category side-by-side comparison."),
43052
+ build: (ctx) => barChart(ctx.title, ctx.sourceByCat, { legendPosition: "top" })
43053
+ },
43054
+ {
43055
+ description: _t("Composition per period over time."),
43056
+ build: (ctx) => barChart(ctx.title, ctx.sourceByDate, {
43057
+ stacked: true,
43058
+ legendPosition: "top"
43059
+ })
43060
+ }
43061
+ ];
43062
+ /** Pattern Q — Label + Multiple Numbers */
43063
+ const LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS = [
43064
+ {
43065
+ description: _t("Side-by-side per entity — works well for long labels."),
43066
+ build: (ctx) => barChart(ctx.title, ctx.source, {
43067
+ horizontal: true,
43068
+ legendPosition: "top"
43069
+ })
43070
+ },
43071
+ {
43072
+ description: _t("Grouped bars per entity for direct metric comparison."),
43073
+ build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
43074
+ },
43075
+ {
43076
+ description: _t("Shape/profile comparison across metrics for each entity."),
43077
+ isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
43078
+ build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
43079
+ },
43080
+ {
43081
+ description: _t("Correlation between the two numeric metrics across entities."),
43082
+ isApplicable: ({ rowCount, numColsCount }) => rowCount > 2 && numColsCount === 2,
43083
+ build: (ctx) => scatterChart(ctx.title, ctx.scatterSource)
43084
+ },
43085
+ {
43086
+ description: _t("Three metrics in one view: X position, Y position and bubble size."),
43087
+ isApplicable: ({ numColsCount }) => numColsCount === 3,
43088
+ build: (ctx) => {
43089
+ const bubble = ctx.bubble;
43090
+ return bubbleChart(ctx.title, bubble.xRange, bubble.yRanges, bubble.sizeRange, bubble.labelRange, bubble.hasTitle);
43091
+ }
43092
+ }
43093
+ ];
43094
+ /** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
43095
+ const CATEGORICAL_TWO_NUMBERS_SUGGESTIONS = [
43096
+ {
43097
+ description: _t("Natural fit for symmetric or opposing values per category."),
43098
+ isApplicable: ({ isPyramid }) => isPyramid,
43099
+ build: (ctx) => pyramidChart(ctx.title, ctx.sourceBoth)
43100
+ },
43101
+ {
43102
+ description: _t("Side-by-side comparison of both metrics per category."),
43103
+ build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
43104
+ legendPosition: "top",
43105
+ aggregated: true
43106
+ })
43107
+ },
43108
+ {
43109
+ description: _t("Bar for the first series, line for the second — good for mixed scales."),
43110
+ build: (ctx) => comboChart(ctx.title, ctx.sourceBoth, { aggregated: true })
43111
+ },
43112
+ {
43113
+ description: _t("Shows total and composition per category."),
43114
+ isApplicable: ({ isPyramid }) => !isPyramid,
43115
+ build: (ctx) => barChart(ctx.title, ctx.sourceBoth, {
43116
+ stacked: true,
43117
+ legendPosition: "top",
43118
+ aggregated: true
43119
+ })
43120
+ }
43121
+ ];
43122
+ /** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
43123
+ const MANY_NUMBERS_SUGGESTIONS = [
43124
+ {
43125
+ description: _t("Shows the position of the last value within the data's min-max range."),
43126
+ isApplicable: ({ colsLength, rowCount }) => colsLength === 3 && rowCount === 1,
43127
+ build: (ctx) => gaugeChart(ctx.title, ctx.lastCellXC, `=${ctx.firstCellXC}`, `=${ctx.secondCellXC}`)
43128
+ },
43129
+ {
43130
+ description: _t("Trend comparison across all metrics."),
43131
+ isApplicable: ({ rowCount }) => rowCount > 2,
43132
+ build: (ctx) => lineChart(ctx.title, ctx.source, { legendPosition: "top" })
43133
+ },
43134
+ {
43135
+ description: _t("Overall shape/profile across all metrics."),
43136
+ isApplicable: ({ rowCount }) => rowCount > 2 && rowCount <= MAX_ROWS_FOR_SHAPE_CHART,
43137
+ build: (ctx) => radarChart(ctx.title, ctx.source, { legendPosition: "top" })
43138
+ },
43139
+ {
43140
+ description: _t("Side-by-side comparison of all numeric metrics."),
43141
+ build: (ctx) => barChart(ctx.title, ctx.source, { legendPosition: "top" })
43142
+ },
43143
+ {
43144
+ description: _t("Side-by-side comparison of all numeric metrics."),
43145
+ build: (ctx) => barChart(ctx.title, ctx.source, {
43146
+ legendPosition: "top",
43147
+ stacked: true
43148
+ })
43149
+ }
43150
+ ];
43151
+
43152
+ //#endregion
43153
+ //#region src/helpers/figures/charts/chart_suggestion_engine.ts
43154
+ const PYRAMID_HEADER_KEYWORD_PATTERNS = [
43155
+ "male",
43156
+ "female",
43157
+ "man",
43158
+ "woman",
43159
+ "boy",
43160
+ "girl",
43161
+ "before",
43162
+ "after",
43163
+ "previous",
43164
+ "current",
43165
+ "positive",
43166
+ "negative",
43167
+ "gain",
43168
+ "loss",
43169
+ "income",
43170
+ "expense",
43171
+ "revenue",
43172
+ "cost"
43173
+ ].map((keyword) => new RegExp(`\\b${keyword}\\b`, "i"));
43174
+ function matchesColumnType(spec, type) {
43175
+ return Array.isArray(spec) ? spec.includes(type) : spec === type;
43176
+ }
43177
+ function matchesShape(pattern, shape) {
43178
+ const { leadingTypes, extension } = pattern;
43179
+ const expectedLength = leadingTypes.length + (extension?.minSize ?? 0);
43180
+ if (extension ? shape.length < expectedLength : shape.length !== expectedLength) return false;
43181
+ return shape.every((type, i) => {
43182
+ const spec = i < leadingTypes.length ? leadingTypes[i] : extension?.type;
43183
+ return spec !== void 0 && matchesColumnType(spec, type);
43184
+ });
43185
+ }
43186
+ function isPyramidLike(numCol1, numCol2) {
43187
+ const h1 = numCol1.header ?? "";
43188
+ const h2 = numCol2.header ?? "";
43189
+ return PYRAMID_HEADER_KEYWORD_PATTERNS.some((re) => re.test(h1) || re.test(h2));
43190
+ }
43191
+ /** Returns the first and last cell XC coordinates, and the second-to-last cell XC coordinate if it exists. */
43192
+ function interestingCellsXc(col) {
43193
+ if (!col) return {
43194
+ firstCellXC: "",
43195
+ lastCellXC: "",
43196
+ prevCellXC: void 0
43197
+ };
43198
+ const firstCellPosition = col.nonEmpty.at(0)?.position;
43199
+ const firstCellXC = firstCellPosition ? toXC(firstCellPosition?.col, firstCellPosition?.row) : "";
43200
+ const lastCellPosition = col.nonEmpty.at(-1)?.position;
43201
+ const lastCellXC = lastCellPosition ? toXC(lastCellPosition?.col, lastCellPosition?.row) : "";
43202
+ const prevCellPosition = col.nonEmpty.at(-2)?.position;
43203
+ return {
43204
+ firstCellXC,
43205
+ lastCellXC,
43206
+ prevCellXC: prevCellPosition ? toXC(prevCellPosition?.col, prevCellPosition?.row) : void 0
43207
+ };
43208
+ }
43209
+ /** Pattern A — Single numeric column */
43210
+ function buildSingleNumberContext([col], getters) {
43211
+ const title = col.header ?? _t("Value");
43212
+ const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
43213
+ return {
43214
+ title,
43215
+ source: rangeSource([dataset(col.zone, getters)], col.hasHeader),
43216
+ rowCount: col.rowCount,
43217
+ firstCellXC,
43218
+ lastCellXC,
43219
+ prevCellXC
43220
+ };
43221
+ }
43222
+ /** Pattern B — Single percentage column */
43223
+ function buildSinglePercentageContext([col], getters) {
43224
+ const hasTitle = col.hasHeader;
43225
+ const title = col.header ?? _t("Rate");
43226
+ const { firstCellXC, lastCellXC, prevCellXC } = interestingCellsXc(col);
43227
+ const source = rangeSource([dataset(col.zone, getters)], hasTitle);
43228
+ const isAboveOne = (col.maxValue ?? 0) > 1;
43229
+ return {
43230
+ title,
43231
+ source,
43232
+ rowCount: col.rowCount,
43233
+ firstCellXC,
43234
+ lastCellXC,
43235
+ prevCellXC,
43236
+ isAboveOne
43237
+ };
43238
+ }
43239
+ /** Pattern C — Single date column */
43240
+ function buildSingleDateContext([col]) {
43241
+ const { lastCellXC } = interestingCellsXc(col);
43242
+ return {
43243
+ title: col.header ?? _t("Date"),
43244
+ lastCellXC,
43245
+ rowCount: col.rowCount
43246
+ };
43247
+ }
43248
+ /** Pattern D — Single categorical column */
43249
+ function buildSingleCategoricalContext([col], getters) {
43250
+ const hasTitle = col.hasHeader;
43251
+ const title = col.header ?? _t("Category");
43252
+ const range = getUnboundRange(getters, col.zone);
43253
+ return {
43254
+ title,
43255
+ source: rangeSource([dataset(col.zone, getters)], hasTitle, range),
43256
+ range
43257
+ };
43258
+ }
43259
+ /** Pattern E — Single label column */
43260
+ function buildSingleLabelContext([col]) {
43261
+ const title = col.header ?? _t("Label");
43262
+ const { lastCellXC } = interestingCellsXc(col);
43263
+ return {
43264
+ title,
43265
+ lastCellXC,
43266
+ rowCount: col.rowCount
43267
+ };
43268
+ }
43269
+ /** Pattern F — Categorical + Number */
43270
+ function buildCategoricalVsNumberContext([catCol, numCol], getters) {
43271
+ const labelRange = getUnboundRange(getters, catCol.zone);
43272
+ const hasTitle = numCol.hasHeader;
43273
+ return {
43274
+ title: numCol.header ? _t("%(numberHeader)s by %(categoryHeader)s", {
43275
+ numberHeader: numCol.header,
43276
+ categoryHeader: catCol.header ?? _t("Category")
43277
+ }) : _t("By Category"),
43278
+ source: rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange),
43279
+ treemapSource: rangeSource([dataset(catCol.zone, getters)], hasTitle, getUnboundRange(getters, numCol.zone))
43280
+ };
43281
+ }
43282
+ /** Patterns G & J — Date + Number or Date + Percentage */
43283
+ function buildDateVsSeriesContext([dateCol, seriesCol], getters) {
43284
+ const isPercentage = seriesCol.type === "percentage";
43285
+ const labelRange = getUnboundRange(getters, dateCol.zone);
43286
+ const hasTitle = isDatasetTitled(getters, dateCol.zone);
43287
+ return {
43288
+ title: seriesCol.header ? _t("%(seriesHeader)s over time", { seriesHeader: seriesCol.header }) : isPercentage ? _t("Rate over Time") : _t("Over Time"),
43289
+ source: rangeSource([dataset(seriesCol.zone, getters)], hasTitle, labelRange),
43290
+ isPercentage
43291
+ };
43292
+ }
43293
+ /** Pattern H — Number + Number */
43294
+ function buildNumberVsNumberContext([col1, col2], getters) {
43295
+ const title = col1.header && col2.header ? _t("%(col2Header)s vs %(col1Header)s", {
43296
+ col2Header: col2.header,
43297
+ col1Header: col1.header
43298
+ }) : _t("Correlation");
43299
+ const hasTitle = col1.hasHeader || col2.hasHeader;
43300
+ const source2 = rangeSource([dataset(col2.zone, getters)], col2.hasHeader, getUnboundRange(getters, col1.zone));
43301
+ const sourceBoth = rangeSource([dataset(col1.zone, getters, "0"), dataset(col2.zone, getters, "1")], hasTitle);
43302
+ const { lastCellXC } = interestingCellsXc(col2);
43303
+ const { lastCellXC: prevCellXC } = interestingCellsXc(col1);
43304
+ return {
43305
+ title,
43306
+ source2,
43307
+ sourceBoth,
43308
+ rowCount1: col1.rowCount,
43309
+ rowCount2: col2.rowCount,
43310
+ lastCellXC,
43311
+ prevCellXC
43312
+ };
43313
+ }
43314
+ /** Pattern I — Categorical + Percentage */
43315
+ function buildCategoricalVsPercentageContext([catCol, pctCol], getters) {
43316
+ const labelRange = getUnboundRange(getters, catCol.zone);
43317
+ const hasTitle = pctCol.hasHeader;
43318
+ return {
43319
+ title: pctCol.header ? _t("%(percentageHeader)s by %(categoryHeader)s", {
43320
+ percentageHeader: pctCol.header,
43321
+ categoryHeader: catCol.header ?? _t("Category")
43322
+ }) : _t("Rates by Category"),
43323
+ source: rangeSource([dataset(pctCol.zone, getters)], hasTitle, labelRange),
43324
+ rowCount: catCol.rowCount
43325
+ };
43326
+ }
43327
+ /** Pattern K — Label + Number */
43328
+ function buildLabelVsNumberContext([labelCol, numCol], getters) {
43329
+ const labelRange = getUnboundRange(getters, labelCol.zone);
43330
+ const hasTitle = numCol.hasHeader;
43331
+ const title = numCol.header ? _t("%(numberHeader)s by %(labelHeader)s", {
43332
+ numberHeader: numCol.header,
43333
+ labelHeader: labelCol.header ?? _t("Name")
43334
+ }) : _t("By Name");
43335
+ const source = rangeSource([dataset(numCol.zone, getters)], hasTitle, labelRange);
43336
+ const { lastCellXC } = interestingCellsXc(numCol);
43337
+ const { lastCellXC: prevCellXC } = interestingCellsXc(labelCol);
43338
+ return {
43339
+ title,
43340
+ source,
43341
+ rowCount: labelCol.rowCount,
43342
+ lastCellXC,
43343
+ prevCellXC
43344
+ };
43345
+ }
43346
+ /** Pattern M — Categorical + Multiple Numbers */
43347
+ function buildCategoricalVsMultipleNumbersContext([catCol, ...numCols], getters) {
43348
+ const labelRange = getUnboundRange(getters, catCol.zone);
43349
+ const hasTitle = numCols.some((c) => c.hasHeader);
43350
+ return {
43351
+ title: catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Multi-series"),
43352
+ source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange),
43353
+ rowCount: catCol.rowCount
43354
+ };
43355
+ }
43356
+ /** Pattern N — Date + Multiple Numbers */
43357
+ function buildDateVsMultipleNumbersContext([dateCol, ...numCols], getters) {
43358
+ const labelRange = getUnboundRange(getters, dateCol.zone);
43359
+ const hasTitle = numCols.some((c) => c.hasHeader) || isDatasetTitled(getters, dateCol.zone);
43360
+ return {
43361
+ title: numCols.length === 1 && numCols[0].header ? _t("%(header)s over time", { header: numCols[0].header }) : _t("Multi-series over Time"),
43362
+ source: rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange)
43363
+ };
43364
+ }
43365
+ /** Pattern O — Categorical + Categorical + Number */
43366
+ function buildMultipleCategoricalsVsNumberContext([cat1, cat2, numCol], getters) {
43367
+ const title = numCol.header ? _t("%(numHeader)s by %(cat1Header)s and %(cat2Header)s", {
43368
+ numHeader: numCol.header,
43369
+ cat1Header: cat1.header ?? _t("Level 1"),
43370
+ cat2Header: cat2.header ?? _t("Level 2")
43371
+ }) : _t("Two-level hierarchy");
43372
+ const hasTitle = numCol.hasHeader;
43373
+ return {
43374
+ title,
43375
+ hierarchySource: rangeSource([dataset(cat1.zone, getters, "0"), dataset(cat2.zone, getters, "1")], hasTitle, getUnboundRange(getters, numCol.zone)),
43376
+ barSource: rangeSource([dataset(numCol.zone, getters)], hasTitle, getUnboundRange(getters, cat1.zone))
43377
+ };
43378
+ }
43379
+ /** Pattern P — Categorical + Date + Number */
43380
+ function buildCategoricalDateNumberContext([catCol, dateCol, numCol], getters) {
43381
+ const dateRange = getUnboundRange(getters, dateCol.zone);
43382
+ const catRange = getUnboundRange(getters, catCol.zone);
43383
+ const hasTitle = numCol.hasHeader;
43384
+ return {
43385
+ title: numCol.header ? _t("%(numHeader)s by %(catHeader)s over %(dateHeader)s", {
43386
+ numHeader: numCol.header,
43387
+ catHeader: catCol.header ?? _t("Category"),
43388
+ dateHeader: dateCol.header ?? _t("Time")
43389
+ }) : _t("Multi-series over Time"),
43390
+ sourceByDate: rangeSource([dataset(numCol.zone, getters)], hasTitle, dateRange),
43391
+ sourceByCat: rangeSource([dataset(numCol.zone, getters)], hasTitle, catRange)
43392
+ };
43393
+ }
43394
+ /** Pattern Q — Label + Multiple Numbers */
43395
+ function buildLabelVsMultipleNumbersContext([labelCol, ...numCols], getters) {
43396
+ const labelRange = getUnboundRange(getters, labelCol.zone);
43397
+ const hasTitle = numCols.some((c) => c.hasHeader);
43398
+ const title = labelCol.header ? _t("By %(header)s", { header: labelCol.header }) : _t("Profile Comparison");
43399
+ const source = rangeSource(numCols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle, labelRange);
43400
+ const scatterSource = rangeSource([dataset(numCols[1].zone, getters)], numCols[1].hasHeader, getUnboundRange(getters, numCols[0].zone));
43401
+ const bubble = numCols.length === 3 ? {
43402
+ xRange: getUnboundRange(getters, numCols[0].zone),
43403
+ yRanges: [getUnboundRange(getters, numCols[1].zone)],
43404
+ sizeRange: getUnboundRange(getters, numCols[2].zone),
43405
+ labelRange,
43406
+ hasTitle
43407
+ } : void 0;
43408
+ return {
43409
+ title,
43410
+ source,
43411
+ rowCount: labelCol.rowCount,
43412
+ numColsCount: numCols.length,
43413
+ scatterSource,
43414
+ bubble
43415
+ };
43416
+ }
43417
+ /** Pattern R — Categorical + Two Numbers (Population Pyramid or Grouped) */
43418
+ function buildCategoricalTwoNumbersContext([catCol, numCol1, numCol2], getters) {
43419
+ const catRange = getUnboundRange(getters, catCol.zone);
43420
+ const hasTitle = numCol1.hasHeader || numCol2.hasHeader;
43421
+ const title = numCol1.header && numCol2.header ? _t("%(num1Header)s vs %(num2Header)s by %(catHeader)s", {
43422
+ num1Header: numCol1.header,
43423
+ num2Header: numCol2.header,
43424
+ catHeader: catCol.header ?? _t("Category")
43425
+ }) : catCol.header ? _t("By %(header)s", { header: catCol.header }) : _t("Category Comparison");
43426
+ const isPyramid = isPyramidLike(numCol1, numCol2);
43427
+ return {
43428
+ title,
43429
+ sourceBoth: rangeSource([dataset(numCol1.zone, getters, "0"), dataset(numCol2.zone, getters, "1")], hasTitle, catRange),
43430
+ isPyramid
43431
+ };
43432
+ }
43433
+ /** Pattern S — Many Numbers (3+ numeric columns, no categorical/date) */
43434
+ function buildManyNumbersContext(cols, getters) {
43435
+ const title = cols.every((c) => c.hasHeader) ? cols.map((c) => c.header).join(" / ") : _t("KPI Overview");
43436
+ const hasTitle = cols.some((c) => c.hasHeader);
43437
+ const source = rangeSource(cols.map((c, i) => dataset(c.zone, getters, String(i))), hasTitle);
43438
+ const { lastCellXC } = interestingCellsXc(cols[2]);
43439
+ const { lastCellXC: firstCellXC } = interestingCellsXc(cols[0]);
43440
+ const { lastCellXC: secondCellXC } = interestingCellsXc(cols[1]);
43441
+ return {
43442
+ title,
43443
+ source,
43444
+ colsLength: cols.length,
43445
+ rowCount: cols[0].rowCount,
43446
+ lastCellXC,
43447
+ firstCellXC,
43448
+ secondCellXC
43449
+ };
43450
+ }
43451
+ const NUMBER_OR_PERCENTAGE = ["number", "percentage"];
43452
+ const EXACT_PATTERNS = [
43453
+ {
43454
+ pattern: { leadingTypes: ["number"] },
43455
+ buildContext: buildSingleNumberContext,
43456
+ suggestions: SINGLE_NUMBER_COLUMN_SUGGESTIONS
43457
+ },
43458
+ {
43459
+ pattern: { leadingTypes: ["percentage"] },
43460
+ buildContext: buildSinglePercentageContext,
43461
+ suggestions: SINGLE_PERCENTAGE_COLUMN_SUGGESTIONS
43462
+ },
43463
+ {
43464
+ pattern: { leadingTypes: ["date"] },
43465
+ buildContext: buildSingleDateContext,
43466
+ suggestions: SINGLE_DATE_COLUMN_SUGGESTIONS
43467
+ },
43468
+ {
43469
+ pattern: { leadingTypes: ["categorical"] },
43470
+ buildContext: buildSingleCategoricalContext,
43471
+ suggestions: SINGLE_CATEGORICAL_COLUMN_SUGGESTIONS
43472
+ },
43473
+ {
43474
+ pattern: { leadingTypes: ["label"] },
43475
+ buildContext: buildSingleLabelContext,
43476
+ suggestions: SINGLE_LABEL_COLUMN_SUGGESTIONS
43477
+ },
43478
+ {
43479
+ pattern: { leadingTypes: ["categorical", "number"] },
43480
+ buildContext: buildCategoricalVsNumberContext,
43481
+ suggestions: CATEGORICAL_VS_NUMBER_SUGGESTIONS
43482
+ },
43483
+ {
43484
+ pattern: { leadingTypes: ["date", NUMBER_OR_PERCENTAGE] },
43485
+ buildContext: buildDateVsSeriesContext,
43486
+ suggestions: DATE_VS_SERIES_SUGGESTIONS
43487
+ },
43488
+ {
43489
+ pattern: { leadingTypes: ["number", "number"] },
43490
+ buildContext: buildNumberVsNumberContext,
43491
+ suggestions: NUMBER_VS_NUMBER_SUGGESTIONS
43492
+ },
43493
+ {
43494
+ pattern: { leadingTypes: ["categorical", "percentage"] },
43495
+ buildContext: buildCategoricalVsPercentageContext,
43496
+ suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
43497
+ },
43498
+ {
43499
+ pattern: { leadingTypes: ["label", "number"] },
43500
+ buildContext: buildLabelVsNumberContext,
43501
+ suggestions: LABEL_VS_NUMBER_SUGGESTIONS
43502
+ },
43503
+ {
43504
+ pattern: { leadingTypes: ["label", "percentage"] },
43505
+ buildContext: buildCategoricalVsPercentageContext,
43506
+ suggestions: CATEGORICAL_VS_PERCENTAGE_SUGGESTIONS
43507
+ },
43508
+ {
43509
+ pattern: { leadingTypes: [
43510
+ "categorical",
43511
+ "categorical",
43512
+ "number"
43513
+ ] },
43514
+ buildContext: buildMultipleCategoricalsVsNumberContext,
43515
+ suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
43516
+ },
43517
+ {
43518
+ pattern: { leadingTypes: [
43519
+ "categorical",
43520
+ "label",
43521
+ "number"
43522
+ ] },
43523
+ buildContext: buildMultipleCategoricalsVsNumberContext,
43524
+ suggestions: MULTIPLE_CATEGORICALS_VS_NUMBER_SUGGESTIONS
43525
+ },
43526
+ {
43527
+ pattern: { leadingTypes: [
43528
+ "categorical",
43529
+ "date",
43530
+ NUMBER_OR_PERCENTAGE
43531
+ ] },
43532
+ buildContext: buildCategoricalDateNumberContext,
43533
+ suggestions: CATEGORICAL_DATE_NUMBER_SUGGESTIONS
43534
+ },
43535
+ {
43536
+ pattern: { leadingTypes: [
43537
+ "categorical",
43538
+ NUMBER_OR_PERCENTAGE,
43539
+ NUMBER_OR_PERCENTAGE
43540
+ ] },
43541
+ buildContext: buildCategoricalTwoNumbersContext,
43542
+ suggestions: CATEGORICAL_TWO_NUMBERS_SUGGESTIONS
43543
+ }
43544
+ ];
43545
+ const EXTENDABLE_PATTERNS = [
43546
+ {
43547
+ pattern: {
43548
+ leadingTypes: [],
43549
+ extension: {
43550
+ type: NUMBER_OR_PERCENTAGE,
43551
+ minSize: 3
43552
+ }
43553
+ },
43554
+ buildContext: buildManyNumbersContext,
43555
+ suggestions: MANY_NUMBERS_SUGGESTIONS
43556
+ },
43557
+ {
43558
+ pattern: {
43559
+ leadingTypes: ["label"],
43560
+ extension: {
43561
+ type: NUMBER_OR_PERCENTAGE,
43562
+ minSize: 2
43563
+ }
43564
+ },
43565
+ buildContext: buildLabelVsMultipleNumbersContext,
43566
+ suggestions: LABEL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
43567
+ },
43568
+ {
43569
+ pattern: {
43570
+ leadingTypes: ["categorical"],
43571
+ extension: {
43572
+ type: NUMBER_OR_PERCENTAGE,
43573
+ minSize: 2
43574
+ }
43575
+ },
43576
+ buildContext: buildCategoricalVsMultipleNumbersContext,
43577
+ suggestions: CATEGORICAL_VS_MULTIPLE_NUMBERS_SUGGESTIONS
43578
+ },
43579
+ {
43580
+ pattern: {
43581
+ leadingTypes: ["date"],
43582
+ extension: {
43583
+ type: NUMBER_OR_PERCENTAGE,
43584
+ minSize: 2
43585
+ }
43586
+ },
43587
+ buildContext: buildDateVsMultipleNumbersContext,
43588
+ suggestions: DATE_VS_MULTIPLE_NUMBERS_SUGGESTIONS
43589
+ }
43590
+ ];
43591
+ function getChartSuggestions(zones, getters) {
43592
+ const cols = analyzeColumns(zones, getters);
43593
+ if (cols.some((c) => c.type === "error")) return [];
43594
+ const nonEmpty = cols.filter((c) => c.type !== "empty");
43595
+ if (!nonEmpty.length) return [];
43596
+ const shape = nonEmpty.map((c) => c.type);
43597
+ const rule = EXACT_PATTERNS.find((rule) => matchesShape(rule.pattern, shape)) ?? EXTENDABLE_PATTERNS.find((rule) => matchesShape(rule.pattern, shape));
43598
+ if (!rule) return [];
43599
+ const ctx = rule.buildContext(nonEmpty, getters);
43600
+ return rule.suggestions.filter((suggestion) => suggestion.isApplicable?.(ctx) ?? true).map((suggestion) => ({
43601
+ description: suggestion.description.toString(),
43602
+ definition: suggestion.build(ctx)
43603
+ }));
43604
+ }
43605
+
43606
+ //#endregion
43607
+ //#region src/components/side_panel/data_analysis/data_analysis_store.ts
43608
+ var DataAnalysisStore = class extends SpreadsheetStore {
43609
+ mutators = [];
43610
+ hasData = false;
43611
+ chartSuggestions = [];
43612
+ isDirty = false;
43613
+ ranges;
43614
+ constructor(get) {
43615
+ super(get);
43616
+ this.model.selection.observe(this, { handleEvent: () => this.refresh() });
43617
+ this.onDispose(() => {
43618
+ this.model.selection.unobserve(this);
43619
+ });
43620
+ this.refresh();
43621
+ }
43622
+ handle(cmd) {
43623
+ if (invalidateEvaluationCommands.has(cmd.type) || cmd.type === "UPDATE_CELL" && ("content" in cmd || "format" in cmd)) this.isDirty = true;
43624
+ switch (cmd.type) {
43625
+ case "HIDE_COLUMNS_ROWS":
43626
+ case "UNHIDE_COLUMNS_ROWS":
43627
+ case "GROUP_HEADERS":
43628
+ case "UNGROUP_HEADERS":
43629
+ case "ACTIVATE_SHEET":
43630
+ case "ACTIVATE_NEXT_SHEET":
43631
+ case "ACTIVATE_PREVIOUS_SHEET":
43632
+ case "EVALUATE_CELLS":
43633
+ case "SET_FORMATTING":
43634
+ case "CLEAR_FORMATTING":
43635
+ this.isDirty = true;
43636
+ break;
43637
+ }
43638
+ }
43639
+ finalize() {
43640
+ if (this.isDirty) {
43641
+ this.refresh();
43642
+ this.isDirty = false;
43643
+ }
43644
+ }
43645
+ refresh() {
43646
+ const sheetId = this.getters.getActiveSheetId();
43647
+ const zones = this.getters.getSelectedZones();
43648
+ this.ranges = zones.map(zoneToXc);
43649
+ this.hasData = zones.some((zone) => this.getters.getEvaluatedCellsInZone(sheetId, zone).some((cell) => cell.type !== "empty"));
43650
+ const suggestions = this.hasData ? getChartSuggestions(zones, this.getters) : [];
43651
+ this.chartSuggestions = suggestions;
43652
+ }
43653
+ };
43654
+
43655
+ //#endregion
43656
+ //#region src/components/side_panel/data_analysis/data_analysis_panel.ts
43657
+ var DataAnalysisPanel = class extends Component {
43658
+ static template = "o-spreadsheet-DataAnalysisPanel";
43659
+ static components = {
43660
+ Section,
43661
+ ChartSuggestionPreview
43662
+ };
43663
+ store;
43664
+ setup() {
43665
+ this.store = useLocalStore(DataAnalysisStore);
43666
+ }
43667
+ onStartChartSuggestionDrag(definition, ev) {
43668
+ startChartDragAndDrop(this.env, definition, ev);
43669
+ }
43670
+ };
43671
+
42147
43672
  //#endregion
42148
43673
  //#region src/registries/data_source_registry.ts
42149
43674
  const unusedDataSourceRegistry = new Registry();
@@ -42220,7 +43745,7 @@ var DataSourcesCleanup = class extends Component {
42220
43745
  var DataValidationPreview = class extends Component {
42221
43746
  static template = "o-spreadsheet-DataValidationPreview";
42222
43747
  props = useProps({ rule: types$1.DataValidationRule() });
42223
- dvPreviewRef = signal(null);
43748
+ dvPreviewRef = signal.ref();
42224
43749
  setup() {
42225
43750
  useHighlightsOnHover(this.dvPreviewRef, this);
42226
43751
  }
@@ -43719,7 +45244,7 @@ var NamedRangePreview = class extends Component {
43719
45244
  };
43720
45245
  props = useProps({ namedRange: types$1.NamedRange() });
43721
45246
  state = proxy({});
43722
- namedRangePreviewRef = signal(null);
45247
+ namedRangePreviewRef = signal.ref();
43723
45248
  setup() {
43724
45249
  useHighlightsOnHover(this.namedRangePreviewRef, this);
43725
45250
  }
@@ -44210,7 +45735,7 @@ var FilterMenuValueItem = class extends Component {
44210
45735
  onClick: types$1.function(),
44211
45736
  scrolledTo: types$1.or([types$1.literal("top"), types$1.literal("bottom")]).optional()
44212
45737
  });
44213
- itemRef = signal(null);
45738
+ itemRef = signal.ref();
44214
45739
  setup() {
44215
45740
  onWillPatch(() => {
44216
45741
  if (this.props.scrolledTo) this.scrollListToSelectedValue();
@@ -44450,7 +45975,7 @@ var PivotFilterEditor = class extends Component {
44450
45975
  onFiltersUpdated: types$1.function()
44451
45976
  });
44452
45977
  state;
44453
- buttonFilter = signal(null);
45978
+ buttonFilter = signal.ref();
44454
45979
  popover;
44455
45980
  setup() {
44456
45981
  useExternalListener(window, "click", (ev) => {
@@ -44579,11 +46104,11 @@ var AddDimensionButton = class extends Component {
44579
46104
  onFieldPicked: types$1.function(),
44580
46105
  fields: types$1.array()
44581
46106
  });
44582
- buttonRef = signal(null);
46107
+ buttonRef = signal.ref(HTMLButtonElement);
44583
46108
  popover = proxy({ isOpen: false });
44584
46109
  search = proxy({ input: "" });
44585
46110
  autoComplete;
44586
- autofocusRef = signal(null);
46111
+ autofocusRef = signal.ref(HTMLInputElement);
44587
46112
  setup() {
44588
46113
  this.autoComplete = useLocalStore(AutoCompleteStore);
44589
46114
  this.autoComplete.useProvider(this.getProvider());
@@ -44992,7 +46517,7 @@ var PivotLayoutConfigurator = class extends Component {
44992
46517
  getScrollableContainerEl: types$1.function().optional(),
44993
46518
  pivotId: types$1.UID()
44994
46519
  });
44995
- dimensionsRef = signal(null);
46520
+ dimensionsRef = signal.ref();
44996
46521
  dragAndDrop = useDragAndDropListItems();
44997
46522
  AGGREGATORS = AGGREGATORS;
44998
46523
  isDateOrDatetimeField = isDateOrDatetimeField;
@@ -45063,16 +46588,7 @@ var PivotLayoutConfigurator = class extends Component {
45063
46588
  });
45064
46589
  }
45065
46590
  getDimensionElementsRects() {
45066
- return Array.from(this.dimensionsRef().children).map((el) => {
45067
- const style = getComputedStyle(el);
45068
- const rect = el.getBoundingClientRect();
45069
- return {
45070
- x: rect.x,
45071
- y: rect.y,
45072
- width: rect.width + parseInt(style.marginLeft || "0") + parseInt(style.marginRight || "0"),
45073
- height: rect.height + parseInt(style.marginTop || "0") + parseInt(style.marginBottom || "0")
45074
- };
45075
- });
46591
+ return Array.from(this.dimensionsRef().children).map((el) => getBoundingRectWithMargins(el));
45076
46592
  }
45077
46593
  removeDimension(dimension) {
45078
46594
  const { columns, rows } = this.props.definition;
@@ -45637,7 +47153,7 @@ var PivotSpreadsheetSidePanel = class extends Component {
45637
47153
  });
45638
47154
  store;
45639
47155
  state;
45640
- pivotSidePanelRef = signal(null);
47156
+ pivotSidePanelRef = signal.ref();
45641
47157
  setup() {
45642
47158
  this.store = useLocalStore(PivotSidePanelStore, this.props.pivotId);
45643
47159
  this.state = proxy({
@@ -46877,7 +48393,7 @@ var TableStylePreview = class extends Component {
46877
48393
  selected: types$1.boolean().optional(),
46878
48394
  onClick: types$1.function().optional()
46879
48395
  });
46880
- canvasRef = signal(null);
48396
+ canvasRef = signal.ref(HTMLCanvasElement);
46881
48397
  menu = proxy({
46882
48398
  isOpen: false,
46883
48399
  anchorRect: null,
@@ -46971,7 +48487,7 @@ var TableStylesPopover = class extends Component {
46971
48487
  tableStyles: types$1.RecordOf(),
46972
48488
  type: types$1.or([types$1.literal("table"), types$1.literal("pivot")])
46973
48489
  });
46974
- tableStyleListRef = signal(null);
48490
+ tableStyleListRef = signal.ref();
46975
48491
  state = proxy({ selectedCategory: this.initialSelectedCategory });
46976
48492
  setup() {
46977
48493
  useExternalListener(window, "click", this.onExternalClick, { capture: true });
@@ -48342,6 +49858,10 @@ sidePanelRegistry.add("ColumnStats", {
48342
49858
  title: _t("Column statistics"),
48343
49859
  Body: ColumnStatsPanel
48344
49860
  });
49861
+ sidePanelRegistry.add("DataAnalysisPanel", {
49862
+ title: _t("Data analysis"),
49863
+ Body: DataAnalysisPanel
49864
+ });
48345
49865
  sidePanelRegistry.add("TableSidePanel", {
48346
49866
  title: _t("Edit table"),
48347
49867
  Body: TablePanel,
@@ -48449,6 +49969,7 @@ var SidePanelStore = class extends SpreadsheetStore {
48449
49969
  "changePanelSize",
48450
49970
  "resetPanelSize",
48451
49971
  "togglePinPanel",
49972
+ "togglePinnedSidePanel",
48452
49973
  "closeMainPanel",
48453
49974
  "changeSpreadsheetWidth",
48454
49975
  "toggleCollapsePanel"
@@ -48600,6 +50121,19 @@ var SidePanelStore = class extends SpreadsheetStore {
48600
50121
  this.secondaryPanel = void 0;
48601
50122
  }
48602
50123
  }
50124
+ togglePinnedSidePanel(componentTag, panelProps = {}) {
50125
+ if (this.mainPanel?.componentTag === componentTag && this.isMainPanelOpen) {
50126
+ this.closeMainPanel();
50127
+ return;
50128
+ }
50129
+ if (this.secondaryPanel?.componentTag === componentTag && this.isSecondaryPanelOpen) {
50130
+ this.secondaryPanel?.currentPanelProps.onCloseSidePanel?.();
50131
+ this.secondaryPanel = void 0;
50132
+ return;
50133
+ }
50134
+ this.open(componentTag, panelProps);
50135
+ if (!this.mainPanel?.isPinned) this.togglePinPanel();
50136
+ }
48603
50137
  toggleCollapsePanel(panel) {
48604
50138
  const panelInfo = this[panel];
48605
50139
  if (!panelInfo) return;
@@ -48978,6 +50512,219 @@ var MenuItemRegistry = class extends Registry {
48978
50512
  }
48979
50513
  };
48980
50514
 
50515
+ //#endregion
50516
+ //#region src/helpers/sort_interactive.ts
50517
+ function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
50518
+ let multiColumns = zone.right > zone.left;
50519
+ if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
50520
+ multiColumns = false;
50521
+ let table;
50522
+ for (let row = zone.top; row <= zone.bottom; row++) {
50523
+ table = [];
50524
+ for (let col = zone.left; col <= zone.right; col++) {
50525
+ const merge = env.model.getters.getMerge({
50526
+ sheetId,
50527
+ col,
50528
+ row
50529
+ });
50530
+ if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
50531
+ }
50532
+ if (table.length >= 2) {
50533
+ multiColumns = true;
50534
+ break;
50535
+ }
50536
+ }
50537
+ }
50538
+ if (multiColumns) {
50539
+ interactiveSort(env, sheetId, anchor, zone, sortDirection);
50540
+ return;
50541
+ }
50542
+ const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
50543
+ if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
50544
+ else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
50545
+ }
50546
+ function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
50547
+ const result = env.model.dispatch("SORT_CELLS", {
50548
+ sheetId,
50549
+ col: anchor.col,
50550
+ row: anchor.row,
50551
+ zone,
50552
+ sortDirection,
50553
+ sortOptions
50554
+ });
50555
+ if (result.isCancelledBecause("InvalidSortZone")) {
50556
+ const { col, row } = anchor;
50557
+ env.model.selection.selectZone({
50558
+ cell: {
50559
+ col,
50560
+ row
50561
+ },
50562
+ zone
50563
+ });
50564
+ env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
50565
+ }
50566
+ if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
50567
+ const { col, row } = anchor;
50568
+ env.model.selection.selectZone({
50569
+ cell: {
50570
+ col,
50571
+ row
50572
+ },
50573
+ zone
50574
+ });
50575
+ env.raiseError(_t("Cannot sort a zone with array formulas."));
50576
+ }
50577
+ }
50578
+
50579
+ //#endregion
50580
+ //#region src/stores/trim_whitespace_store.ts
50581
+ var TrimWhitespaceStore = class extends SpreadsheetStore {
50582
+ mutators = ["trimWhitespace"];
50583
+ notificationStore = this.get(NotificationStore);
50584
+ trimWhitespace() {
50585
+ this.model.dispatch("TRIM_WHITESPACE");
50586
+ }
50587
+ handle(cmd) {
50588
+ switch (cmd.type) {
50589
+ case "TRIM_WHITESPACE":
50590
+ this._trimWhitespace();
50591
+ break;
50592
+ }
50593
+ }
50594
+ _trimWhitespace() {
50595
+ const zones = recomputeZones(this.getters.getSelectedZones());
50596
+ const sheetId = this.getters.getActiveSheetId();
50597
+ let count = 0;
50598
+ for (const { col, row } of zones.map(positions).flat()) {
50599
+ const cell = this.getters.getCell({
50600
+ col,
50601
+ row,
50602
+ sheetId
50603
+ });
50604
+ if (!cell) continue;
50605
+ const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
50606
+ const trimmedContent = trimContent(currentContent);
50607
+ if (trimmedContent !== currentContent) {
50608
+ count += 1;
50609
+ this.model.dispatch("UPDATE_CELL", {
50610
+ sheetId,
50611
+ col,
50612
+ row,
50613
+ content: trimmedContent
50614
+ });
50615
+ }
50616
+ }
50617
+ const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
50618
+ this.notificationStore.notifyUser({
50619
+ type: "info",
50620
+ text,
50621
+ sticky: false
50622
+ });
50623
+ }
50624
+ };
50625
+
50626
+ //#endregion
50627
+ //#region src/actions/data_actions.ts
50628
+ const sortRange = {
50629
+ name: _t("Sort range"),
50630
+ isVisible: IS_ONLY_ONE_RANGE,
50631
+ icon: "o-spreadsheet-Icon.SORT_RANGE"
50632
+ };
50633
+ const sortAscending = {
50634
+ name: _t("Ascending (A ⟶ Z)"),
50635
+ execute: (env) => {
50636
+ const { anchor, zones } = env.model.getters.getSelection();
50637
+ interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
50638
+ },
50639
+ icon: "o-spreadsheet-Icon.SORT_ASCENDING"
50640
+ };
50641
+ const dataCleanup = {
50642
+ name: _t("Data cleanup"),
50643
+ icon: "o-spreadsheet-Icon.DATA_CLEANUP"
50644
+ };
50645
+ const removeDuplicates = {
50646
+ name: _t("Remove duplicates"),
50647
+ execute: (env) => {
50648
+ if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
50649
+ env.openSidePanel("RemoveDuplicates", {});
50650
+ },
50651
+ isEnabled: (env) => !env.isSmall
50652
+ };
50653
+ const trimWhitespace = {
50654
+ name: _t("Trim whitespace"),
50655
+ execute: (env) => {
50656
+ env.getStore(TrimWhitespaceStore).trimWhitespace();
50657
+ }
50658
+ };
50659
+ const cleanupDataSources = {
50660
+ name: _t("Remove unused data sources"),
50661
+ execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
50662
+ isEnabled: (env) => !env.isSmall
50663
+ };
50664
+ const sortDescending = {
50665
+ name: _t("Descending (Z ⟶ A)"),
50666
+ execute: (env) => {
50667
+ const { anchor, zones } = env.model.getters.getSelection();
50668
+ interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
50669
+ },
50670
+ icon: "o-spreadsheet-Icon.SORT_DESCENDING"
50671
+ };
50672
+ const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
50673
+ const createRemoveFilterTool = {
50674
+ ...CREATE_OR_REMOVE_FILTER_ACTION,
50675
+ isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
50676
+ };
50677
+ const splitToColumns = {
50678
+ name: _t("Split text to columns"),
50679
+ sequence: 1,
50680
+ execute: (env) => env.openSidePanel("SplitToColumns", {}),
50681
+ isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
50682
+ icon: "o-spreadsheet-Icon.SPLIT_TEXT"
50683
+ };
50684
+ const dataAnalysis = {
50685
+ name: _t("Data analysis"),
50686
+ execute: (env) => {
50687
+ env.getStore(SidePanelStore).togglePinnedSidePanel("DataAnalysisPanel");
50688
+ },
50689
+ icon: "o-spreadsheet-Icon.COLUMN_STATS",
50690
+ isEnabled: (env) => !env.isSmall
50691
+ };
50692
+ const columnStatistics = {
50693
+ name: _t("Column statistics"),
50694
+ execute: (env) => env.openSidePanel("ColumnStats", {}),
50695
+ icon: "o-spreadsheet-Icon.COLUMN_STATS"
50696
+ };
50697
+ const reinsertDynamicPivotMenu = {
50698
+ id: "reinsert_dynamic_pivot",
50699
+ name: _t("Re-insert dynamic pivot"),
50700
+ sequence: 60,
50701
+ icon: "o-spreadsheet-Icon.INSERT_PIVOT",
50702
+ children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
50703
+ isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
50704
+ };
50705
+ const reinsertStaticPivotMenu = {
50706
+ id: "reinsert_static_pivot",
50707
+ name: _t("Re-insert static pivot"),
50708
+ sequence: 70,
50709
+ icon: "o-spreadsheet-Icon.INSERT_PIVOT",
50710
+ children: [REINSERT_STATIC_PIVOT_CHILDREN],
50711
+ isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
50712
+ };
50713
+ const calculationMode = {
50714
+ name: _t("Calculation"),
50715
+ icon: "o-spreadsheet-Icon.CALCULATOR"
50716
+ };
50717
+ const automaticCalculation = {
50718
+ name: _t("Automatic"),
50719
+ execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
50720
+ isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
50721
+ };
50722
+ const manualCalculation = {
50723
+ name: _t("Manual"),
50724
+ execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
50725
+ isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
50726
+ };
50727
+
48981
50728
  //#endregion
48982
50729
  //#region src/helpers/ui/merge_interactive.ts
48983
50730
  const AddMergeInteractiveContent = {
@@ -49192,6 +50939,10 @@ cellMenuRegistry.add("cut", {
49192
50939
  }).addChild("paste_format_only", ["paste_special"], {
49193
50940
  ...pasteSpecialFormat,
49194
50941
  sequence: 20
50942
+ }).add("data_analysis", {
50943
+ ...dataAnalysis,
50944
+ sequence: 50,
50945
+ separator: true
49195
50946
  }).add("add_row_before", {
49196
50947
  ...cellInsertRowsBefore,
49197
50948
  sequence: 70
@@ -49299,211 +51050,6 @@ cellMenuRegistry.add("cut", {
49299
51050
  sequence: 30
49300
51051
  });
49301
51052
 
49302
- //#endregion
49303
- //#region src/helpers/sort_interactive.ts
49304
- function interactiveSortSelection(env, sheetId, anchor, zone, sortDirection) {
49305
- let multiColumns = zone.right > zone.left;
49306
- if (env.model.getters.doesIntersectMerge(sheetId, zone)) {
49307
- multiColumns = false;
49308
- let table;
49309
- for (let row = zone.top; row <= zone.bottom; row++) {
49310
- table = [];
49311
- for (let col = zone.left; col <= zone.right; col++) {
49312
- const merge = env.model.getters.getMerge({
49313
- sheetId,
49314
- col,
49315
- row
49316
- });
49317
- if (merge && !table.includes(merge.id.toString())) table.push(merge.id.toString());
49318
- }
49319
- if (table.length >= 2) {
49320
- multiColumns = true;
49321
- break;
49322
- }
49323
- }
49324
- }
49325
- if (multiColumns) {
49326
- interactiveSort(env, sheetId, anchor, zone, sortDirection);
49327
- return;
49328
- }
49329
- const contiguousZone = env.model.getters.getContiguousZone(sheetId, zone);
49330
- if (isEqual(contiguousZone, zone)) interactiveSort(env, sheetId, anchor, zone, sortDirection);
49331
- else env.askConfirmation(_t("We found data next to your selection. Since this data was not selected, it will not be sorted. Do you want to extend your selection?"), () => interactiveSort(env, sheetId, anchor, contiguousZone, sortDirection), () => interactiveSort(env, sheetId, anchor, zone, sortDirection));
49332
- }
49333
- function interactiveSort(env, sheetId, anchor, zone, sortDirection, sortOptions) {
49334
- const result = env.model.dispatch("SORT_CELLS", {
49335
- sheetId,
49336
- col: anchor.col,
49337
- row: anchor.row,
49338
- zone,
49339
- sortDirection,
49340
- sortOptions
49341
- });
49342
- if (result.isCancelledBecause("InvalidSortZone")) {
49343
- const { col, row } = anchor;
49344
- env.model.selection.selectZone({
49345
- cell: {
49346
- col,
49347
- row
49348
- },
49349
- zone
49350
- });
49351
- env.raiseError(_t("Cannot sort. To sort, select only cells or only merges that have the same size."));
49352
- }
49353
- if (result.isCancelledBecause("SortZoneWithArrayFormulas")) {
49354
- const { col, row } = anchor;
49355
- env.model.selection.selectZone({
49356
- cell: {
49357
- col,
49358
- row
49359
- },
49360
- zone
49361
- });
49362
- env.raiseError(_t("Cannot sort a zone with array formulas."));
49363
- }
49364
- }
49365
-
49366
- //#endregion
49367
- //#region src/stores/trim_whitespace_store.ts
49368
- var TrimWhitespaceStore = class extends SpreadsheetStore {
49369
- mutators = ["trimWhitespace"];
49370
- notificationStore = this.get(NotificationStore);
49371
- trimWhitespace() {
49372
- this.model.dispatch("TRIM_WHITESPACE");
49373
- }
49374
- handle(cmd) {
49375
- switch (cmd.type) {
49376
- case "TRIM_WHITESPACE":
49377
- this._trimWhitespace();
49378
- break;
49379
- }
49380
- }
49381
- _trimWhitespace() {
49382
- const zones = recomputeZones(this.getters.getSelectedZones());
49383
- const sheetId = this.getters.getActiveSheetId();
49384
- let count = 0;
49385
- for (const { col, row } of zones.map(positions).flat()) {
49386
- const cell = this.getters.getCell({
49387
- col,
49388
- row,
49389
- sheetId
49390
- });
49391
- if (!cell) continue;
49392
- const currentContent = !cell.isFormula ? cell.content : cell.compiledFormula.toFormulaString(this.getters);
49393
- const trimmedContent = trimContent(currentContent);
49394
- if (trimmedContent !== currentContent) {
49395
- count += 1;
49396
- this.model.dispatch("UPDATE_CELL", {
49397
- sheetId,
49398
- col,
49399
- row,
49400
- content: trimmedContent
49401
- });
49402
- }
49403
- }
49404
- const text = count ? _t("Trimmed whitespace from %s cells.", count) : _t("No selected cells had whitespace trimmed.");
49405
- this.notificationStore.notifyUser({
49406
- type: "info",
49407
- text,
49408
- sticky: false
49409
- });
49410
- }
49411
- };
49412
-
49413
- //#endregion
49414
- //#region src/actions/data_actions.ts
49415
- const sortRange = {
49416
- name: _t("Sort range"),
49417
- isVisible: IS_ONLY_ONE_RANGE,
49418
- icon: "o-spreadsheet-Icon.SORT_RANGE"
49419
- };
49420
- const sortAscending = {
49421
- name: _t("Ascending (A ⟶ Z)"),
49422
- execute: (env) => {
49423
- const { anchor, zones } = env.model.getters.getSelection();
49424
- interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "asc");
49425
- },
49426
- icon: "o-spreadsheet-Icon.SORT_ASCENDING"
49427
- };
49428
- const dataCleanup = {
49429
- name: _t("Data cleanup"),
49430
- icon: "o-spreadsheet-Icon.DATA_CLEANUP"
49431
- };
49432
- const removeDuplicates = {
49433
- name: _t("Remove duplicates"),
49434
- execute: (env) => {
49435
- if (getZoneArea(env.model.getters.getSelectedZone()) === 1) env.model.selection.selectTableAroundSelection();
49436
- env.openSidePanel("RemoveDuplicates", {});
49437
- },
49438
- isEnabled: (env) => !env.isSmall
49439
- };
49440
- const trimWhitespace = {
49441
- name: _t("Trim whitespace"),
49442
- execute: (env) => {
49443
- env.getStore(TrimWhitespaceStore).trimWhitespace();
49444
- }
49445
- };
49446
- const cleanupDataSources = {
49447
- name: _t("Remove unused data sources"),
49448
- execute: (env) => env.openSidePanel("DataSourceCleanup", {}),
49449
- isEnabled: (env) => !env.isSmall
49450
- };
49451
- const sortDescending = {
49452
- name: _t("Descending (Z ⟶ A)"),
49453
- execute: (env) => {
49454
- const { anchor, zones } = env.model.getters.getSelection();
49455
- interactiveSortSelection(env, env.model.getters.getActiveSheetId(), anchor.cell, zones[0], "desc");
49456
- },
49457
- icon: "o-spreadsheet-Icon.SORT_DESCENDING"
49458
- };
49459
- const createRemoveFilter = { ...CREATE_OR_REMOVE_FILTER_ACTION };
49460
- const createRemoveFilterTool = {
49461
- ...CREATE_OR_REMOVE_FILTER_ACTION,
49462
- isActive: (env) => SELECTED_TABLE_HAS_FILTERS(env)
49463
- };
49464
- const splitToColumns = {
49465
- name: _t("Split text to columns"),
49466
- sequence: 1,
49467
- execute: (env) => env.openSidePanel("SplitToColumns", {}),
49468
- isEnabled: (env) => !env.isSmall && env.model.getters.isSingleColSelected(),
49469
- icon: "o-spreadsheet-Icon.SPLIT_TEXT"
49470
- };
49471
- const columnStatistics = {
49472
- name: _t("Column statistics"),
49473
- execute: (env) => env.openSidePanel("ColumnStats", {}),
49474
- icon: "o-spreadsheet-Icon.COLUMN_STATS"
49475
- };
49476
- const reinsertDynamicPivotMenu = {
49477
- id: "reinsert_dynamic_pivot",
49478
- name: _t("Re-insert dynamic pivot"),
49479
- sequence: 60,
49480
- icon: "o-spreadsheet-Icon.INSERT_PIVOT",
49481
- children: [REINSERT_DYNAMIC_PIVOT_CHILDREN],
49482
- isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
49483
- };
49484
- const reinsertStaticPivotMenu = {
49485
- id: "reinsert_static_pivot",
49486
- name: _t("Re-insert static pivot"),
49487
- sequence: 70,
49488
- icon: "o-spreadsheet-Icon.INSERT_PIVOT",
49489
- children: [REINSERT_STATIC_PIVOT_CHILDREN],
49490
- isVisible: (env) => env.model.getters.getPivotIds().some((id) => env.model.getters.getPivot(id).isValid())
49491
- };
49492
- const calculationMode = {
49493
- name: _t("Calculation"),
49494
- icon: "o-spreadsheet-Icon.CALCULATOR"
49495
- };
49496
- const automaticCalculation = {
49497
- name: _t("Automatic"),
49498
- execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: true }),
49499
- isActive: (env) => env.model.getters.isAutomaticEvaluationEnabled()
49500
- };
49501
- const manualCalculation = {
49502
- name: _t("Manual"),
49503
- execute: (env) => env.model.dispatch("SET_AUTOMATIC_EVALUATION", { enabled: false }),
49504
- isActive: (env) => !env.model.getters.isAutomaticEvaluationEnabled()
49505
- };
49506
-
49507
51053
  //#endregion
49508
51054
  //#region src/registries/menus/col_menu_registry.ts
49509
51055
  const colMenuRegistry = new MenuItemRegistry();
@@ -49537,6 +51083,9 @@ colMenuRegistry.add("cut", {
49537
51083
  }).addChild("sort_descending", ["sort_columns"], {
49538
51084
  ...sortDescending,
49539
51085
  sequence: 20
51086
+ }).add("data_analysis", {
51087
+ ...dataAnalysis,
51088
+ sequence: 55
49540
51089
  }).add("column_statistics", {
49541
51090
  ...columnStatistics,
49542
51091
  sequence: 60,
@@ -52352,14 +53901,15 @@ var FiguresContainer = class extends Component {
52352
53901
  selectedRect: void 0,
52353
53902
  horizontalSnap: void 0,
52354
53903
  verticalSnap: void 0,
52355
- cancelDnd: void 0,
52356
- overlappingChartOrCarousel: void 0
53904
+ cancelDnd: void 0
52357
53905
  });
52358
53906
  viewStore;
52359
53907
  zoomStore;
53908
+ chartDragStore;
52360
53909
  setup() {
52361
53910
  this.viewStore = useStore(ViewportsStore);
52362
53911
  this.zoomStore = useStore(ZoomStore);
53912
+ this.chartDragStore = useStore(ChartDragStore);
52363
53913
  onMounted(() => {
52364
53914
  this.render();
52365
53915
  });
@@ -52373,7 +53923,7 @@ var FiguresContainer = class extends Component {
52373
53923
  this.dnd.selectedRect = void 0;
52374
53924
  this.dnd.horizontalSnap = void 0;
52375
53925
  this.dnd.verticalSnap = void 0;
52376
- this.dnd.overlappingChartOrCarousel = void 0;
53926
+ this.chartDragStore.setHighlightedFigure(void 0);
52377
53927
  this.dnd.cancelDnd = void 0;
52378
53928
  }
52379
53929
  });
@@ -52499,6 +54049,7 @@ var FiguresContainer = class extends Component {
52499
54049
  maxY: this.env.model.getters.getRowDimensions(sheetId, this.env.model.getters.getNumberRows(sheetId) - 1).end
52500
54050
  };
52501
54051
  let hasStartedDnd = false;
54052
+ let overlappingChartOrCarousel = void 0;
52502
54053
  const onMouseMove = (ev) => {
52503
54054
  const currentMousePosition = {
52504
54055
  x: ev.clientX / zoom,
@@ -52510,10 +54061,10 @@ var FiguresContainer = class extends Component {
52510
54061
  hasStartedDnd = true;
52511
54062
  const selectedFigures = dragFigureForMove(currentMousePosition, initialMousePosition, initialFigures, maxDimensions, initialScrollPosition, this.viewStore.activeSheetScrollInfo);
52512
54063
  const draggedFigure = selectedFigures.find((f) => f.id === draggedFigureId);
52513
- let overlappingChartOrCarousel = void 0;
54064
+ overlappingChartOrCarousel = void 0;
52514
54065
  const otherFigures = this.getOtherFigures(selectedFigures.map((f) => f.id));
52515
- if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = this.getOverlappingFigure(draggedFigure, otherFigures);
52516
- this.dnd.overlappingChartOrCarousel = overlappingChartOrCarousel;
54066
+ if (draggedFigure && !selectedFigures.find((f) => f.tag !== "chart")) overlappingChartOrCarousel = getCarouselOverlappingChart(draggedFigure, otherFigures, ["carousel", "chart"]);
54067
+ this.chartDragStore.setHighlightedFigure(overlappingChartOrCarousel?.id);
52517
54068
  if (!overlappingChartOrCarousel) {
52518
54069
  const snapReturn = snapForMove(this.env, selectedFigures, otherFigures);
52519
54070
  this.dnd.selectedFigures = snapReturn.snappedFigures;
@@ -52535,7 +54086,7 @@ var FiguresContainer = class extends Component {
52535
54086
  else this.env.model.dispatch("SELECT_FIGURE", { figureId: figureUI.id });
52536
54087
  return;
52537
54088
  }
52538
- if (!this.dnd.overlappingChartOrCarousel) {
54089
+ if (!overlappingChartOrCarousel) {
52539
54090
  const payloads = this.dnd.selectedFigures?.map((f) => {
52540
54091
  return {
52541
54092
  sheetId,
@@ -52545,14 +54096,14 @@ var FiguresContainer = class extends Component {
52545
54096
  }) || [];
52546
54097
  this.env.model.dispatch("MOVE_FIGURES", { figures: payloads });
52547
54098
  } else {
52548
- const overlappingFigureId = this.dnd.overlappingChartOrCarousel.id;
54099
+ const overlappingFigureId = overlappingChartOrCarousel.id;
52549
54100
  const chartFigureIds = this.dnd.selectedFigures?.map((f) => f.id) || [];
52550
- if (this.dnd.overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
54101
+ if (overlappingChartOrCarousel.tag === "carousel") this.env.model.dispatch("ADD_FIGURES_CHART_TO_CAROUSEL", {
52551
54102
  sheetId,
52552
54103
  carouselFigureId: overlappingFigureId,
52553
54104
  chartFigureIds
52554
54105
  });
52555
- else if (this.dnd.overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
54106
+ else if (overlappingChartOrCarousel.tag === "chart") this.env.model.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
52556
54107
  sheetId,
52557
54108
  baseFigureId: overlappingFigureId,
52558
54109
  chartFigureIds: [overlappingFigureId, ...chartFigureIds]
@@ -52563,7 +54114,7 @@ var FiguresContainer = class extends Component {
52563
54114
  this.dnd.selectedRect = void 0;
52564
54115
  this.dnd.horizontalSnap = void 0;
52565
54116
  this.dnd.verticalSnap = void 0;
52566
- this.dnd.overlappingChartOrCarousel = void 0;
54117
+ this.chartDragStore.setHighlightedFigure(void 0);
52567
54118
  };
52568
54119
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
52569
54120
  }
@@ -52619,7 +54170,6 @@ var FiguresContainer = class extends Component {
52619
54170
  this.dnd.selectedRect = void 0;
52620
54171
  this.dnd.horizontalSnap = void 0;
52621
54172
  this.dnd.verticalSnap = void 0;
52622
- this.dnd.overlappingChartOrCarousel = void 0;
52623
54173
  };
52624
54174
  this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
52625
54175
  }
@@ -52629,12 +54179,12 @@ var FiguresContainer = class extends Component {
52629
54179
  getFigureStyle(figureUI) {
52630
54180
  if (figureUI.id !== this.dnd.draggedFigure?.id) return "";
52631
54181
  return cssPropertiesToCss({
52632
- opacity: this.dnd.overlappingChartOrCarousel?.id ? "0.6" : "0.9",
54182
+ opacity: this.chartDragStore.highlightedFigureId ? "0.6" : "0.9",
52633
54183
  cursor: "grabbing"
52634
54184
  });
52635
54185
  }
52636
54186
  getFigureClass(figureUI) {
52637
- if (figureUI.id !== this.dnd.overlappingChartOrCarousel?.id) return "";
54187
+ if (figureUI.id !== this.chartDragStore.highlightedFigureId) return "";
52638
54188
  return "o-add-to-carousel";
52639
54189
  }
52640
54190
  getSnap(snapLine) {
@@ -52677,26 +54227,6 @@ var FiguresContainer = class extends Component {
52677
54227
  height: `100%`
52678
54228
  });
52679
54229
  }
52680
- getOverlappingFigure(figureUI, otherFigures) {
52681
- if (figureUI.tag !== "chart") return;
52682
- const figureCenterX = figureUI.x + figureUI.width / 2;
52683
- const figureCenterY = figureUI.y + figureUI.height / 2;
52684
- let bestMatch;
52685
- let smallestDistance = Infinity;
52686
- for (const figure of otherFigures) {
52687
- if (figure.tag !== "chart" && figure.tag !== "carousel") continue;
52688
- const targetCenterX = figure.x + figure.width / 2;
52689
- const targetCenterY = figure.y + figure.height / 2;
52690
- const distanceX = Math.abs(figureCenterX - targetCenterX);
52691
- const distanceY = Math.abs(figureCenterY - targetCenterY);
52692
- const squaredDistance = distanceX ** 2 + distanceY ** 2;
52693
- if (distanceX <= figureUI.width / 2 && distanceY <= figureUI.height / 2 && squaredDistance < smallestDistance) {
52694
- smallestDistance = squaredDistance;
52695
- bestMatch = figure;
52696
- }
52697
- }
52698
- return bestMatch;
52699
- }
52700
54230
  };
52701
54231
 
52702
54232
  //#endregion
@@ -52996,7 +54526,7 @@ var AbstractResizer = class extends Component {
52996
54526
  var ColResizer = class extends AbstractResizer {
52997
54527
  static template = "o-spreadsheet-ColResizer";
52998
54528
  static components = { UnhideColumnHeaders };
52999
- colResizerRef = signal(null);
54529
+ colResizerRef = signal.ref();
53000
54530
  setup() {
53001
54531
  super.setup();
53002
54532
  this.PADDING = 15;
@@ -53130,7 +54660,7 @@ var ColResizer = class extends AbstractResizer {
53130
54660
  var RowResizer = class extends AbstractResizer {
53131
54661
  static template = "o-spreadsheet-RowResizer";
53132
54662
  static components = { UnhideRowHeaders };
53133
- rowResizerRef = signal(null);
54663
+ rowResizerRef = signal.ref();
53134
54664
  setup() {
53135
54665
  super.setup();
53136
54666
  this.PADDING = 5;
@@ -53739,9 +55269,8 @@ var TableResizer = class extends Component {
53739
55269
  useStore(TableResizeStore);
53740
55270
  }
53741
55271
  get containerStyle() {
53742
- const tableZone = this.props.table.range.zone;
53743
55272
  const sheetId = this.props.table.range.sheetId;
53744
- if (this.env.model.getters.isReadonly() || this.env.model.getters.isSheetLocked(sheetId)) return cssPropertiesToCss({ display: "none" });
55273
+ const tableZone = this.props.table.range.zone;
53745
55274
  const bottomRight = {
53746
55275
  ...tableZone,
53747
55276
  left: tableZone.right,
@@ -53828,8 +55357,8 @@ var Grid = class extends Component {
53828
55357
  HEADER_HEIGHT = 26;
53829
55358
  HEADER_WIDTH = 48;
53830
55359
  menuState;
53831
- gridRef = signal(null);
53832
- canvasRef = signal(null);
55360
+ gridRef = signal.ref();
55361
+ canvasRef = signal.ref(HTMLCanvasElement);
53833
55362
  highlightStore;
53834
55363
  viewStore;
53835
55364
  zoomStore;
@@ -54460,6 +55989,9 @@ var Grid = class extends Component {
54460
55989
  const sheetId = this.env.model.getters.getActiveSheetId();
54461
55990
  return this.env.model.getters.getClientsToDisplay(sheetId);
54462
55991
  }
55992
+ get displayTableResizer() {
55993
+ return this.env.model.getters.isGridSelectionActive() && !this.env.model.getters.isReadonly() && !this.env.model.getters.isSheetLocked(this.env.model.getters.getActiveSheetId());
55994
+ }
54463
55995
  };
54464
55996
 
54465
55997
  //#endregion
@@ -54478,7 +56010,7 @@ var FullScreenFigure = class extends Component {
54478
56010
  static template = "o-spreadsheet-FullScreenFigure";
54479
56011
  static components = { ChartFigure };
54480
56012
  fullScreenFigureStore;
54481
- fullScreenFigureRef = signal(null);
56013
+ fullScreenFigureRef = signal.ref();
54482
56014
  spreadsheetRect = useSpreadsheetRect();
54483
56015
  figureRegistry = figureRegistry;
54484
56016
  setup() {
@@ -54726,6 +56258,41 @@ var PivotHTMLRenderer = class extends Component {
54726
56258
  }
54727
56259
  };
54728
56260
 
56261
+ //#endregion
56262
+ //#region src/components/tables/hovered_table_store.ts
56263
+ var HoveredTableStore = class extends SpreadsheetStore {
56264
+ mutators = ["hover"];
56265
+ storeGetters = ["getCellHoverOverlayColor"];
56266
+ position;
56267
+ constructor(get) {
56268
+ super(get);
56269
+ const cellHoverOverlayStore = this.get(CellHoverOverlayStore);
56270
+ cellHoverOverlayStore.register(this);
56271
+ this.onDispose(() => cellHoverOverlayStore.unRegister(this));
56272
+ }
56273
+ getHighlightedPositions(hoveredPosition) {
56274
+ const highlightedPositions = [];
56275
+ const { sheetId, row } = hoveredPosition;
56276
+ const table = this.getters.getTable(hoveredPosition);
56277
+ if (!table) return highlightedPositions;
56278
+ const { left, right, top } = table.range.zone;
56279
+ const isTableHeader = row < top + table.config.numberOfHeaders;
56280
+ const doesTableRowHaveContent = range(left, right + 1).some((col) => {
56281
+ return !this.getters.isColHidden(sheetId, col) && this.getters.getEvaluatedCell({
56282
+ sheetId,
56283
+ col,
56284
+ row
56285
+ }).formattedValue;
56286
+ });
56287
+ if (!isTableHeader && doesTableRowHaveContent) for (let col = left; col <= right; col++) highlightedPositions.push({
56288
+ sheetId,
56289
+ col,
56290
+ row
56291
+ });
56292
+ return highlightedPositions;
56293
+ }
56294
+ };
56295
+
54729
56296
  //#endregion
54730
56297
  //#region src/helpers/figures/charts/runtime/helpers_index.ts
54731
56298
  var helpers_index_exports = /* @__PURE__ */ __exportAll({
@@ -67828,6 +69395,12 @@ var FigureUIPlugin = class extends UIPlugin {
67828
69395
  const figures = cmd.chartFigureIds.map((id) => this.getters.getFigure(cmd.sheetId, id));
67829
69396
  const baseFigureId = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
67830
69397
  if (figures.some((f) => f === void 0 || f.tag !== "chart") || !baseFigureId || baseFigureId.tag !== "chart") return "FigureDoesNotExist";
69398
+ break;
69399
+ case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
69400
+ const baseFigure = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
69401
+ if (this.getters.getFigure(cmd.sheetId, cmd.figureId) || !baseFigure) return "InvalidFigureId";
69402
+ if (baseFigure.tag !== "chart") return "FigureDoesNotExist";
69403
+ break;
67831
69404
  }
67832
69405
  return "Success";
67833
69406
  }
@@ -67863,6 +69436,29 @@ var FigureUIPlugin = class extends UIPlugin {
67863
69436
  carouselFigureId,
67864
69437
  chartFigureIds: cmd.chartFigureIds
67865
69438
  });
69439
+ break;
69440
+ case "CREATE_CHART_AND_MERGE_INTO_CAROUSEL":
69441
+ const baseFigureToMerge = this.getters.getFigure(cmd.sheetId, cmd.baseFigureId);
69442
+ if (!baseFigureToMerge) throw new Error(`Figure ${cmd.baseFigureId} does not exists.`);
69443
+ this.dispatch("CREATE_CHART", {
69444
+ chartId: cmd.chartId,
69445
+ figureId: cmd.figureId,
69446
+ sheetId: cmd.sheetId,
69447
+ definition: cmd.definition,
69448
+ col: baseFigureToMerge.col,
69449
+ row: baseFigureToMerge.row,
69450
+ offset: baseFigureToMerge.offset,
69451
+ size: {
69452
+ width: baseFigureToMerge.width,
69453
+ height: baseFigureToMerge.height
69454
+ }
69455
+ });
69456
+ this.dispatch("MERGE_CHART_FIGURES_INTO_CAROUSEL", {
69457
+ sheetId: cmd.sheetId,
69458
+ baseFigureId: cmd.baseFigureId,
69459
+ chartFigureIds: [cmd.baseFigureId, cmd.figureId]
69460
+ });
69461
+ break;
67866
69462
  }
67867
69463
  }
67868
69464
  getFigureUI(sheetId, figure) {
@@ -69552,6 +71148,9 @@ topbarMenuRegistry.add("file", {
69552
71148
  }).addChild("split_to_columns", ["data"], {
69553
71149
  ...splitToColumns,
69554
71150
  sequence: 20
71151
+ }).addChild("data_analysis", ["data"], {
71152
+ ...dataAnalysis,
71153
+ sequence: 24
69555
71154
  }).addChild("column_statistics", ["data"], {
69556
71155
  ...columnStatistics,
69557
71156
  sequence: 25
@@ -69953,7 +71552,7 @@ var RippleEffect = class extends Component {
69953
71552
  onAnimationEnd: types$1.function(),
69954
71553
  style: types$1.string()
69955
71554
  });
69956
- rippleRef = signal(null);
71555
+ rippleRef = signal.ref();
69957
71556
  setup() {
69958
71557
  let animation = void 0;
69959
71558
  onMounted(() => {
@@ -70007,7 +71606,7 @@ var Ripple = class extends Component {
70007
71606
  onAnimationEnd: types$1.function().optional(() => () => {}),
70008
71607
  class: types$1.string().optional("")
70009
71608
  });
70010
- childContainerRef = signal(null);
71609
+ childContainerRef = signal.ref();
70011
71610
  state = proxy({ ripples: [] });
70012
71611
  currentId = 1;
70013
71612
  onClick(ev) {
@@ -70278,9 +71877,9 @@ var BottomBarSheet = class extends Component {
70278
71877
  isEditing: false,
70279
71878
  openedPicker: void 0
70280
71879
  });
70281
- sheetDivRef = signal(null);
70282
- iconRef = signal(null);
70283
- sheetNameRef = signal(null);
71880
+ sheetDivRef = signal.ref();
71881
+ iconRef = signal.ref();
71882
+ sheetNameRef = signal.ref();
70284
71883
  editionState = "initializing";
70285
71884
  DOMFocusableElementStore;
70286
71885
  setup() {
@@ -70564,8 +72163,10 @@ var BottomBarStatistic = class extends Component {
70564
72163
  });
70565
72164
  state = proxy({ selectedStatisticFn: "" });
70566
72165
  store;
72166
+ sidePanelStore;
70567
72167
  setup() {
70568
72168
  this.store = useStore(AggregateStatisticsStore);
72169
+ this.sidePanelStore = useStore(SidePanelStore);
70569
72170
  onWillUpdateProps(() => {
70570
72171
  if (Object.values(this.store.statisticFnResults).every((result) => result?.value === void 0)) this.props.closeContextMenu();
70571
72172
  });
@@ -70605,6 +72206,12 @@ var BottomBarStatistic = class extends Component {
70605
72206
  format: fnValue.format()
70606
72207
  });
70607
72208
  }
72209
+ get isDataAnalysisOpen() {
72210
+ return this.sidePanelStore.mainPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isMainPanelOpen || this.sidePanelStore.secondaryPanel?.componentTag === "DataAnalysisPanel" && this.sidePanelStore.isSecondaryPanelOpen;
72211
+ }
72212
+ toggleDataAnalysisPanel() {
72213
+ this.sidePanelStore.togglePinnedSidePanel("DataAnalysisPanel");
72214
+ }
70608
72215
  };
70609
72216
 
70610
72217
  //#endregion
@@ -70619,8 +72226,8 @@ var BottomBar = class extends Component {
70619
72226
  BottomBarSheet,
70620
72227
  BottomBarStatistic
70621
72228
  };
70622
- bottomBarRef = signal(null);
70623
- sheetListRef = signal(null);
72229
+ bottomBarRef = signal.ref();
72230
+ sheetListRef = signal.ref();
70624
72231
  dragAndDrop = useDragAndDropListItems();
70625
72232
  targetScroll = void 0;
70626
72233
  state = proxy({
@@ -70833,12 +72440,13 @@ var SpreadsheetDashboard = class extends Component {
70833
72440
  hoveredCell;
70834
72441
  viewStore;
70835
72442
  zoomStore;
70836
- gridRef = signal(null);
70837
- canvasRef = signal(null);
72443
+ gridRef = signal.ref();
72444
+ canvasRef = signal.ref(HTMLCanvasElement);
70838
72445
  setup() {
70839
72446
  this.hoveredCell = useStore(DelayedHoveredCellStore);
70840
72447
  this.viewStore = useStore(ViewportsStore);
70841
72448
  this.zoomStore = useStore(ZoomStore);
72449
+ useLocalStore(HoveredTableStore);
70842
72450
  const rendererStore = useLocalStore(RendererStore, OrderedLayers().filter((layer) => layer !== "Headers"));
70843
72451
  useChildSubEnv({ getPopoverContainerRect: () => this.zoomStore.getZoomedRect(this.getGridRect()) });
70844
72452
  useGridDrawing({
@@ -71256,8 +72864,8 @@ var RibbonMenu = class extends Component {
71256
72864
  static components = { Menu };
71257
72865
  props = useProps({ onClose: types$1.function() });
71258
72866
  rootItems = topbarMenuRegistry.getMenuItems();
71259
- menuRef = signal(null);
71260
- containerRef = signal(null);
72867
+ menuRef = signal.ref();
72868
+ containerRef = signal.ref();
71261
72869
  state = proxy({
71262
72870
  menuItems: this.rootItems,
71263
72871
  title: _t("Menu Bar"),
@@ -71330,7 +72938,7 @@ var SmallBottomBar = class extends Component {
71330
72938
  composerStore;
71331
72939
  viewStore;
71332
72940
  composerInterface;
71333
- composerRef = signal(null);
72941
+ composerRef = signal.ref();
71334
72942
  menuState = proxy({ isOpen: false });
71335
72943
  setup() {
71336
72944
  this.composerFocusStore = useStore(ComposerFocusStore);
@@ -71409,6 +73017,49 @@ var SmallBottomBar = class extends Component {
71409
73017
  }
71410
73018
  };
71411
73019
 
73020
+ //#endregion
73021
+ //#region src/components/standalone_grid_canvas/carousel_data_view_print.ts
73022
+ var CarouselDataViewPrint = class extends Component {
73023
+ static template = xml``;
73024
+ props = useProps({
73025
+ range: types$1.Range(),
73026
+ columnWeights: types$1.array().optional(),
73027
+ rect: types$1.object(),
73028
+ rendererStore: types$1.object()
73029
+ });
73030
+ viewportsStore;
73031
+ gridRender;
73032
+ setup() {
73033
+ useChildStoreProvider([ViewportsStore]);
73034
+ const store = useLocalStore(StandaloneViewportStore, this.props.range, this.props.columnWeights);
73035
+ this.gridRender = useLocalStore(GridRenderer, null);
73036
+ this.viewportsStore = useStore(ViewportsStore);
73037
+ useEffect(() => {
73038
+ store.setContainerSize(this.props.rect.width, this.props.rect.height);
73039
+ });
73040
+ onMounted(() => this.props.rendererStore.register(this));
73041
+ onWillUnmount(() => this.props.rendererStore.unRegister(this));
73042
+ }
73043
+ get renderingLayers() {
73044
+ return ["Chart"];
73045
+ }
73046
+ drawLayer(renderingContext) {
73047
+ const { ctx } = renderingContext;
73048
+ ctx.save();
73049
+ ctx.translate(this.props.rect.x, this.props.rect.y);
73050
+ ctx.beginPath();
73051
+ ctx.rect(0, 0, this.props.rect.width, this.props.rect.height);
73052
+ ctx.clip();
73053
+ const subRenderingCtx = {
73054
+ ...renderingContext,
73055
+ sheetId: this.viewportsStore.displayedSheetId,
73056
+ viewports: this.viewportsStore.viewports
73057
+ };
73058
+ this.gridRender.drawLayer(subRenderingCtx, "Background", void 0);
73059
+ ctx.restore();
73060
+ }
73061
+ };
73062
+
71412
73063
  //#endregion
71413
73064
  //#region src/components/standalone_grid_canvas/figure_renderer_store.ts
71414
73065
  /** Store that draws the figures directly onto the canvas */
@@ -71434,29 +73085,25 @@ var FigureRendererStore = class extends DisposableStore {
71434
73085
  drawLayer(renderingCtx) {
71435
73086
  const { viewports, ctx } = renderingCtx;
71436
73087
  const visibleFigures = viewports.getVisibleFigures(renderingCtx.sheetId);
71437
- const { x: offsetX, y: offsetY } = viewports.getViewportOffset(renderingCtx.sheetId);
73088
+ const scrollOffset = viewports.getViewportOffset(renderingCtx.sheetId);
71438
73089
  for (const figure of visibleFigures) {
71439
- const x = figure.x - offsetX;
71440
- const y = figure.y - offsetY;
73090
+ const figureRect = {
73091
+ ...figure,
73092
+ x: figure.x - scrollOffset.x,
73093
+ y: figure.y - scrollOffset.y
73094
+ };
73095
+ const { x, y, width, height } = figureRect;
71441
73096
  if (figure.tag === "chart") {
71442
73097
  const chartId = this.getters.getChartIdFromFigureId(figure.id);
71443
- if (chartId) {
71444
- const chartRect = {
71445
- x,
71446
- y,
71447
- width: figure.width,
71448
- height: figure.height
71449
- };
71450
- this.drawChart(renderingCtx, chartId, chartRect);
71451
- }
73098
+ if (chartId) this.drawChart(renderingCtx, chartId, figureRect);
71452
73099
  } else if (figure.tag === "image") {
71453
73100
  const loadedImage = this.loadedImages[this.getters.getImagePath(figure.id)];
71454
- if (loadedImage) ctx.drawImage(loadedImage, x, y, figure.width, figure.height);
71455
- } else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure);
73101
+ if (loadedImage) ctx.drawImage(loadedImage, x, y, width, height);
73102
+ } else if (figure.tag === "carousel") this.drawCarousel(renderingCtx, figure, figureRect);
71456
73103
  if (!this.getters.isDashboard()) {
71457
73104
  ctx.strokeStyle = GRAY_400;
71458
73105
  ctx.lineWidth = 1;
71459
- ctx.strokeRect(x, y, figure.width, figure.height);
73106
+ ctx.strokeRect(x, y, width, height);
71460
73107
  }
71461
73108
  }
71462
73109
  }
@@ -71474,53 +73121,37 @@ var FigureRendererStore = class extends DisposableStore {
71474
73121
  renderingCtx.ctx.drawImage(chartCanvas, x, y, width, height);
71475
73122
  cleanUp();
71476
73123
  }
71477
- drawCarousel(renderingCtx, figure) {
71478
- const { viewports: sheetView, ctx } = renderingCtx;
71479
- const { x: offsetX, y: offsetY } = sheetView.getViewportOffset(renderingCtx.sheetId);
71480
- const x = figure.x - offsetX;
71481
- const y = figure.y - offsetY;
73124
+ drawCarousel(renderingCtx, figure, figureRect) {
73125
+ const { ctx } = renderingCtx;
71482
73126
  const carousel = this.getters.getCarousel(figure.id);
71483
73127
  const chartId = this.getters.getChartIdFromFigureId(figure.id);
71484
73128
  if (!carousel) return;
73129
+ const layout = getCarouselLayout(figureRect, carousel, this.getters.getSelectedCarouselItem(figure.id));
71485
73130
  const chartDefinition = chartId ? this.getters.getChartDefinition(chartId) : void 0;
73131
+ ctx.save();
73132
+ ctx.beginPath();
73133
+ ctx.rect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
73134
+ ctx.clip();
73135
+ ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
73136
+ ctx.fillRect(figureRect.x, figureRect.y, figureRect.width, figureRect.height);
71486
73137
  const title = {
71487
73138
  ...DEFAULT_CAROUSEL_TITLE_STYLE,
71488
73139
  ...carousel.title
71489
73140
  };
71490
- const headerPadding = 4;
71491
- const headerSize = title.fontSize + headerPadding * 2;
71492
- if (!title.text && chartId) {
71493
- const chartRect = {
71494
- x,
71495
- y,
71496
- width: figure.width,
71497
- height: figure.height
71498
- };
71499
- this.drawChart(renderingCtx, chartId, chartRect);
71500
- } else if (title.text) {
71501
- ctx.save();
71502
- ctx.fillStyle = chartDefinition?.background || this.getters.getSpreadsheetTheme().backgroundColor;
71503
- ctx.fillRect(x, y, figure.width, headerSize);
71504
- ctx.font = computeTextFont(chartStyleToCellStyle(title));
73141
+ if (title.text) {
73142
+ ctx.font = computeTextFont(chartStyleToCellStyle(title), "px", 500);
71505
73143
  ctx.fillStyle = title.color;
71506
- ctx.fillText(title.text, x + headerPadding, y + headerPadding + title.fontSize);
71507
- ctx.restore();
71508
- const chartRect = {
71509
- x,
71510
- y: y + headerSize,
71511
- width: figure.width,
71512
- height: figure.height - headerSize
71513
- };
71514
- if (chartId) this.drawChart(renderingCtx, chartId, chartRect);
71515
- else if (!this.getters.isDashboard()) {
71516
- ctx.strokeStyle = GRAY_400;
71517
- ctx.lineWidth = 1;
71518
- ctx.beginPath();
71519
- ctx.moveTo(x, y + headerSize);
71520
- ctx.lineTo(x + figure.width, y + headerSize);
71521
- ctx.stroke();
71522
- }
73144
+ ctx.textBaseline = "middle";
73145
+ const textY = Math.ceil(layout.headerRect.y + layout.headerRect.height / 2);
73146
+ ctx.fillText(title.text, layout.headerRect.x, textY);
73147
+ }
73148
+ const separator = layout.separatorRect;
73149
+ if (separator) {
73150
+ ctx.fillStyle = GRAY_400;
73151
+ ctx.fillRect(separator.x, separator.y, separator.width, separator.height);
71523
73152
  }
73153
+ ctx.restore();
73154
+ if (chartId) this.drawChart(renderingCtx, chartId, layout.contentRect);
71524
73155
  }
71525
73156
  };
71526
73157
 
@@ -71528,12 +73159,13 @@ var FigureRendererStore = class extends DisposableStore {
71528
73159
  //#region src/components/standalone_grid_canvas/standalone_grid_canvas.ts
71529
73160
  var StandaloneGridCanvas = class extends Component {
71530
73161
  static template = "o-spreadsheet-StandaloneGridCanvas";
73162
+ static components = { CarouselDataViewPrint };
71531
73163
  props = useProps({
71532
73164
  sheetId: types$1.UID(),
71533
73165
  zone: types$1.Zone(),
71534
73166
  renderingCtx: types$1.object()
71535
73167
  });
71536
- canvasRef = signal(null);
73168
+ canvasRef = signal.ref(HTMLCanvasElement);
71537
73169
  rendererStore;
71538
73170
  figureRendererStore;
71539
73171
  setup() {
@@ -71555,6 +73187,28 @@ var StandaloneGridCanvas = class extends Component {
71555
73187
  this.figureRendererStore.addLoadedImage(path, await createImageBitmap(blob));
71556
73188
  }));
71557
73189
  }
73190
+ get carouselDataViews() {
73191
+ const { sheetId, viewports } = this.props.renderingCtx;
73192
+ return viewports.getVisibleFigures(sheetId).filter((figure) => figure.tag === "carousel").map((figure) => {
73193
+ const carouselItem = this.env.model.getters.getSelectedCarouselItem(figure.id);
73194
+ if (carouselItem?.type !== "carouselDataView" || !carouselItem.range) return;
73195
+ const carousel = this.env.model.getters.getCarousel(figure.id);
73196
+ const scroll = viewports.getViewportOffset(sheetId);
73197
+ const { x, y, width, height } = figure;
73198
+ const layout = getCarouselLayout({
73199
+ x: x - scroll.x,
73200
+ y: y - scroll.y,
73201
+ width,
73202
+ height
73203
+ }, carousel, carouselItem);
73204
+ return {
73205
+ range: carouselItem.range,
73206
+ columnWeights: carouselItem.columnWeights,
73207
+ rect: layout.contentRect,
73208
+ rendererStore: this.rendererStore
73209
+ };
73210
+ }).filter(isDefined);
73211
+ }
71558
73212
  };
71559
73213
 
71560
73214
  //#endregion
@@ -72016,7 +73670,7 @@ var NamedRangeSelector = class extends Component {
72016
73670
  anchorRect: null,
72017
73671
  menuItems: []
72018
73672
  });
72019
- namedRangeSelectorRef = signal(null);
73673
+ namedRangeSelectorRef = signal.ref();
72020
73674
  setup() {
72021
73675
  this.topBarToolStore = useToolBarDropdownStore();
72022
73676
  this.DOMFocusableElementStore = useStore(DOMFocusableElementStore);
@@ -72214,7 +73868,7 @@ var BorderEditor = class extends Component {
72214
73868
  anchorRect: types$1.Rect()
72215
73869
  });
72216
73870
  BORDER_POSITIONS = BORDER_POSITIONS;
72217
- lineStyleButtonRef = signal(null);
73871
+ lineStyleButtonRef = signal.ref();
72218
73872
  borderStyles = borderStyles;
72219
73873
  state = proxy({ activeTool: void 0 });
72220
73874
  toggleDropdownTool(tool) {
@@ -72281,7 +73935,7 @@ var BorderEditorWidget = class extends Component {
72281
73935
  });
72282
73936
  topBarToolStore;
72283
73937
  viewStore;
72284
- borderEditorButtonRef = signal(null);
73938
+ borderEditorButtonRef = signal.ref();
72285
73939
  state = proxy({
72286
73940
  currentColor: DEFAULT_BORDER_DESC.color,
72287
73941
  currentStyle: DEFAULT_BORDER_DESC.style,
@@ -72511,7 +74165,7 @@ var DropdownAction = class extends Component {
72511
74165
  childClass: types$1.string()
72512
74166
  });
72513
74167
  topBarToolStore;
72514
- actionRef = signal(null);
74168
+ actionRef = signal.ref();
72515
74169
  setup() {
72516
74170
  this.topBarToolStore = useToolBarDropdownStore();
72517
74171
  }
@@ -72574,7 +74228,7 @@ var NumberFormatsTool = class extends Component {
72574
74228
  props = useProps({ class: types$1.string() });
72575
74229
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
72576
74230
  topBarToolStore;
72577
- buttonRef = signal(null);
74231
+ buttonRef = signal.ref();
72578
74232
  state = proxy({
72579
74233
  anchorRect: {
72580
74234
  x: 0,
@@ -72863,12 +74517,12 @@ var TopBar = class extends Component {
72863
74517
  composerFocusStore;
72864
74518
  fingerprints;
72865
74519
  topBarToolStore;
72866
- toolBarContainerRef = signal(null);
72867
- toolbarRef = signal(null);
72868
- namedRangesRef = signal(null);
72869
- topBarTopRef = signal(null);
72870
- moreToolsContainerRef = signal(null);
72871
- moreToolsButtonRef = signal(null);
74520
+ toolBarContainerRef = signal.ref();
74521
+ toolbarRef = signal.ref();
74522
+ namedRangesRef = signal.ref();
74523
+ topBarTopRef = signal.ref();
74524
+ moreToolsContainerRef = signal.ref();
74525
+ moreToolsButtonRef = signal.ref();
72872
74526
  spreadsheetRect = useSpreadsheetRect();
72873
74527
  setup() {
72874
74528
  this.composerFocusStore = useStore(ComposerFocusStore);
@@ -73130,7 +74784,7 @@ var Spreadsheet = class extends Component {
73130
74784
  SpreadsheetPrint
73131
74785
  };
73132
74786
  sidePanel;
73133
- spreadsheetRef = signal(null);
74787
+ spreadsheetRef = signal.ref();
73134
74788
  spreadsheetRect = useSpreadsheetRect();
73135
74789
  state = proxy({ printModeEnabled: false });
73136
74790
  _focusGrid;
@@ -79138,12 +80792,13 @@ var FilterMenu = class extends Component {
79138
80792
  }
79139
80793
  sortFilterZone(sortDirection) {
79140
80794
  const filterPosition = this.props.filterPosition;
79141
- const tableZone = this.table?.range.zone;
80795
+ const table = this.table;
80796
+ const tableZone = table?.range.zone;
79142
80797
  if (!filterPosition || !tableZone || tableZone.top === tableZone.bottom) return;
79143
80798
  const sheetId = this.viewStore.displayedSheetId;
79144
80799
  const contentZone = {
79145
80800
  ...tableZone,
79146
- top: tableZone.top + 1
80801
+ top: tableZone.top + table.config.numberOfHeaders
79147
80802
  };
79148
80803
  const sortAnchor = {
79149
80804
  col: filterPosition.col,
@@ -79301,9 +80956,9 @@ var LinkEditor = class extends Component {
79301
80956
  onClosed: types$1.function().optional()
79302
80957
  });
79303
80958
  static size = { maxHeight: 500 };
79304
- urlInput = signal(null);
79305
- suggestionListRef = signal(null);
79306
- urlInputContainer = signal(null);
80959
+ urlInput = signal.ref(HTMLInputElement);
80960
+ suggestionListRef = signal.ref();
80961
+ urlInputContainer = signal.ref();
79307
80962
  state;
79308
80963
  viewStore;
79309
80964
  setup() {
@@ -81421,7 +83076,7 @@ const ARRAYTOTEXT = {
81421
83076
  else if (_format === 0) {
81422
83077
  const rowSeparator = this.locale.decimalSeparator === "," ? "/" : ",";
81423
83078
  return { value: transposeMatrix(_array).flatMap((row) => row.map((value) => {
81424
- return isEvaluationError(value.value) ? value.value : toString(value);
83079
+ return isEvaluationError(value.value) ? value.value : toLocaleString(value, this.locale);
81425
83080
  })).join(rowSeparator) };
81426
83081
  } else return new EvaluationError(_t("Format must be 0 or 1"));
81427
83082
  },
@@ -87469,7 +89124,7 @@ const CONCAT = {
87469
89124
  description: _t("Concatenation of two values."),
87470
89125
  args: [arg("value1 (string)", _t("The value to which value2 will be appended.")), arg("value2 (string)", _t("The value to append to value1."))],
87471
89126
  compute: function(value1, value2) {
87472
- return { value: toString(value1) + toString(value2) };
89127
+ return { value: toLocaleString(value1, this.locale) + toLocaleString(value2, this.locale) };
87473
89128
  },
87474
89129
  isExported: true
87475
89130
  };
@@ -88352,7 +90007,7 @@ const CLEAN = {
88352
90007
  description: _t("Remove non-printable characters from a piece of text."),
88353
90008
  args: [arg("text (string)", _t("The text whose non-printable characters are to be removed."))],
88354
90009
  compute: function(text) {
88355
- const _text = toString(text);
90010
+ const _text = toLocaleString(text, this.locale);
88356
90011
  let cleanedStr = "";
88357
90012
  for (const char of _text) if (char && char.charCodeAt(0) > 31) cleanedStr += char;
88358
90013
  return { value: cleanedStr };
@@ -88363,7 +90018,7 @@ const CONCATENATE = {
88363
90018
  description: _t("Appends strings to one another."),
88364
90019
  args: [arg("string (string, range<string>, repeating)", _t("String to append in sequence."))],
88365
90020
  compute: function(...datas) {
88366
- return { value: reduceAny(datas, (acc, a) => acc + toString(a), "") };
90021
+ return { value: reduceAny(datas, (acc, a) => acc + toLocaleString(a, this.locale), "") };
88367
90022
  },
88368
90023
  isExported: true
88369
90024
  };
@@ -88371,7 +90026,7 @@ const EXACT = {
88371
90026
  description: _t("Tests whether two strings are identical."),
88372
90027
  args: [arg("string1 (string)", _t("The first string to compare.")), arg("string2 (string)", _t("The second string to compare."))],
88373
90028
  compute: function(string1, string2) {
88374
- return { value: toString(string1) === toString(string2) };
90029
+ return { value: toLocaleString(string1, this.locale) === toLocaleString(string2, this.locale) };
88375
90030
  },
88376
90031
  isExported: true
88377
90032
  };
@@ -88383,8 +90038,8 @@ const FIND = {
88383
90038
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
88384
90039
  ],
88385
90040
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
88386
- const _searchFor = toString(searchFor);
88387
- const _textToSearch = toString(textToSearch);
90041
+ const _searchFor = toLocaleString(searchFor, this.locale);
90042
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
88388
90043
  const _startingAt = toNumber(startingAt, this.locale);
88389
90044
  if (_textToSearch === "") return new EvaluationError(_t("The text_to_search must be non-empty."));
88390
90045
  if (_startingAt < 1) return new EvaluationError(_t("The starting_at (%s) must be greater than or equal to 1.", _startingAt));
@@ -88421,8 +90076,8 @@ const JOIN = {
88421
90076
  description: _t("Concatenates elements of arrays with delimiter."),
88422
90077
  args: [arg("delimiter (string)", _t("The character or string to place between each concatenated value.")), arg("value_or_array (string, range<string>, repeating)", _t("Value to be appended using delimiter."))],
88423
90078
  compute: function(delimiter, ...valuesOrArrays) {
88424
- const _delimiter = toString(delimiter);
88425
- return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toString(a), "") };
90079
+ const _delimiter = toLocaleString(delimiter, this.locale);
90080
+ return { value: reduceAny(valuesOrArrays, (acc, a) => (acc ? acc + _delimiter : "") + toLocaleString(a, this.locale), "") };
88426
90081
  }
88427
90082
  };
88428
90083
  const LEFT = {
@@ -88431,7 +90086,7 @@ const LEFT = {
88431
90086
  compute: function(text, ...args) {
88432
90087
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
88433
90088
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or null.", _numberOfCharacters));
88434
- return { value: toString(text).substring(0, _numberOfCharacters) };
90089
+ return { value: toLocaleString(text, this.locale).substring(0, _numberOfCharacters) };
88435
90090
  },
88436
90091
  isExported: true
88437
90092
  };
@@ -88439,7 +90094,7 @@ const LEN = {
88439
90094
  description: _t("Length of a string."),
88440
90095
  args: [arg("text (string)", _t("The string whose length will be returned."))],
88441
90096
  compute: function(text) {
88442
- return { value: toString(text).length };
90097
+ return { value: toLocaleString(text, this.locale).length };
88443
90098
  },
88444
90099
  isExported: true
88445
90100
  };
@@ -88447,7 +90102,7 @@ const LOWER = {
88447
90102
  description: _t("Converts a specified string to lowercase."),
88448
90103
  args: [arg("text (string)", _t("The string to convert to lowercase."))],
88449
90104
  compute: function(text) {
88450
- return { value: toString(text).toLowerCase() };
90105
+ return { value: toLocaleString(text, this.locale).toLowerCase() };
88451
90106
  },
88452
90107
  isExported: true
88453
90108
  };
@@ -88459,7 +90114,7 @@ const MID = {
88459
90114
  arg("extract_length (number)", _t("The length of the segment to extract."))
88460
90115
  ],
88461
90116
  compute: function(text, starting_at, extract_length) {
88462
- const _text = toString(text);
90117
+ const _text = toLocaleString(text, this.locale);
88463
90118
  const _starting_at = toNumber(starting_at, this.locale);
88464
90119
  const _extract_length = toNumber(extract_length, this.locale);
88465
90120
  if (_starting_at < 1) return new EvaluationError(_t("The starting_at argument (%s) must be positive greater than one.", _starting_at.toString()));
@@ -88472,7 +90127,7 @@ const PROPER = {
88472
90127
  description: _t("Capitalizes each word in a specified string."),
88473
90128
  args: [arg("text_to_capitalize (string)", _t("The text which will be returned with the first letter of each word in uppercase and all other letters in lowercase."))],
88474
90129
  compute: function(text) {
88475
- return { value: toString(text).replace(wordRegex, (word) => {
90130
+ return { value: toLocaleString(text, this.locale).replace(wordRegex, (word) => {
88476
90131
  return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
88477
90132
  }) };
88478
90133
  },
@@ -88622,8 +90277,8 @@ const REPLACE = {
88622
90277
  if (_position < 1) return new EvaluationError(_t("The position (%s) must be greater than or equal to 1.", _position));
88623
90278
  const _length = toNumber(length, this.locale);
88624
90279
  if (_length < 0) return new EvaluationError(_t("The length (%s) must be positive or zero.", _length));
88625
- const _text = toString(text);
88626
- const _newText = toString(newText);
90280
+ const _text = toLocaleString(text, this.locale);
90281
+ const _newText = toLocaleString(newText, this.locale);
88627
90282
  return { value: _text.substring(0, _position - 1) + _newText + _text.substring(_position - 1 + _length) };
88628
90283
  },
88629
90284
  isExported: true
@@ -88634,7 +90289,7 @@ const RIGHT = {
88634
90289
  compute: function(text, ...args) {
88635
90290
  const _numberOfCharacters = args.length ? toNumber(args[0], this.locale) : 1;
88636
90291
  if (_numberOfCharacters < 0) return new EvaluationError(_t("The number_of_characters (%s) must be positive or zero.", _numberOfCharacters));
88637
- const _text = toString(text);
90292
+ const _text = toLocaleString(text, this.locale);
88638
90293
  const stringLength = _text.length;
88639
90294
  return { value: _text.substring(stringLength - _numberOfCharacters, stringLength) };
88640
90295
  },
@@ -88648,8 +90303,8 @@ const SEARCH = {
88648
90303
  arg(`starting_at (number, default=${DEFAULT_STARTING_AT})`, _t("The character within text_to_search at which to start the search."))
88649
90304
  ],
88650
90305
  compute: function(searchFor, textToSearch, startingAt = { value: DEFAULT_STARTING_AT }) {
88651
- const _searchFor = toString(searchFor).toLowerCase();
88652
- const _textToSearch = toString(textToSearch).toLowerCase();
90306
+ const _searchFor = toLocaleString(searchFor, this.locale).toLowerCase();
90307
+ const _textToSearch = toLocaleString(textToSearch, this.locale).toLowerCase();
88653
90308
  const _startingAt = toNumber(startingAt, this.locale);
88654
90309
  if (_textToSearch === "") return {
88655
90310
  value: CellErrorType.GenericError,
@@ -88679,8 +90334,8 @@ const SPLIT = {
88679
90334
  arg(`remove_empty_text (boolean, default=${SPLIT_DEFAULT_REMOVE_EMPTY_TEXT})`, _t("Whether or not to remove empty text messages from the split results. The default behavior is to treat consecutive delimiters as one (if TRUE). If FALSE, empty cells values are added between consecutive delimiters."))
88680
90335
  ],
88681
90336
  computeArray: function(text, delimiter, splitByEach = { value: SPLIT_DEFAULT_SPLIT_BY_EACH }, removeEmptyText = { value: SPLIT_DEFAULT_REMOVE_EMPTY_TEXT }) {
88682
- const _text = toString(text);
88683
- const _delimiter = escapeRegExp(toString(delimiter));
90337
+ const _text = toLocaleString(text, this.locale);
90338
+ const _delimiter = escapeRegExp(toLocaleString(delimiter, this.locale));
88684
90339
  const _splitByEach = toBoolean(splitByEach);
88685
90340
  const _removeEmptyText = toBoolean(removeEmptyText);
88686
90341
  if (_delimiter.length <= 0) return new EvaluationError(_t("The delimiter (%s) must be not be empty.", _delimiter));
@@ -88702,10 +90357,10 @@ const SUBSTITUTE = {
88702
90357
  compute: function(textToSearch, searchFor, replaceWith, occurrenceNumber) {
88703
90358
  const _occurrenceNumber = toNumber(occurrenceNumber, this.locale);
88704
90359
  if (_occurrenceNumber < 0) return new EvaluationError(_t("The occurrenceNumber (%s) must be positive or null.", _occurrenceNumber));
88705
- const _textToSearch = toString(textToSearch);
88706
- const _searchFor = toString(searchFor);
90360
+ const _textToSearch = toLocaleString(textToSearch, this.locale);
90361
+ const _searchFor = toLocaleString(searchFor, this.locale);
88707
90362
  if (_searchFor === "") return { value: _textToSearch };
88708
- const _replaceWith = toString(replaceWith);
90363
+ const _replaceWith = toLocaleString(replaceWith, this.locale);
88709
90364
  const reg = new RegExp(escapeRegExp(_searchFor), "g");
88710
90365
  if (_occurrenceNumber === 0) return { value: _textToSearch.replace(reg, _replaceWith) };
88711
90366
  let n = 0;
@@ -88728,10 +90383,10 @@ const TEXTJOIN = {
88728
90383
  arg("texts (string, range<string>, repeating)", _t("Text item to join."))
88729
90384
  ],
88730
90385
  compute: function(delimiter, ignoreEmpty = { value: TEXTJOIN_DEFAULT_IGNORE_EMPTY }, ...textsOrArrays) {
88731
- const _delimiter = toString(delimiter);
90386
+ const _delimiter = toLocaleString(delimiter, this.locale);
88732
90387
  const _ignoreEmpty = toBoolean(ignoreEmpty);
88733
90388
  let n = 0;
88734
- return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toString(a) === "") ? (n++ ? acc + _delimiter : "") + toString(a) : acc, "") };
90389
+ return { value: reduceAny(textsOrArrays, (acc, a) => !(_ignoreEmpty && toLocaleString(a, this.locale) === "") ? (n++ ? acc + _delimiter : "") + toLocaleString(a, this.locale) : acc, "") };
88735
90390
  },
88736
90391
  isExported: true
88737
90392
  };
@@ -88784,7 +90439,7 @@ const TRIM = {
88784
90439
  description: _t("Removes space characters."),
88785
90440
  args: [arg("text (string)", _t("The text or reference to a cell containing text to be trimmed."))],
88786
90441
  compute: function(text) {
88787
- return { value: trimContent(toString(text)) };
90442
+ return { value: trimContent(toLocaleString(text, this.locale)) };
88788
90443
  },
88789
90444
  isExported: true
88790
90445
  };
@@ -88792,7 +90447,7 @@ const UPPER = {
88792
90447
  description: _t("Converts a specified string to uppercase."),
88793
90448
  args: [arg("text (string)", _t("The string to convert to uppercase."))],
88794
90449
  compute: function(text) {
88795
- return { value: toString(text).toUpperCase() };
90450
+ return { value: toLocaleString(text, this.locale).toUpperCase() };
88796
90451
  },
88797
90452
  isExported: true
88798
90453
  };
@@ -88889,7 +90544,7 @@ const HYPERLINK = {
88889
90544
  args: [arg("url (string)", _t("The full URL of the link enclosed in quotation marks.")), arg("link_label (string, optional)", _t("The text to display in the cell, enclosed in quotation marks."))],
88890
90545
  compute: function(url, linkLabel) {
88891
90546
  const processedUrl = toString(url).trim();
88892
- const processedLabel = toString(linkLabel) || processedUrl;
90547
+ const processedLabel = toLocaleString(linkLabel, this.locale) || processedUrl;
88893
90548
  if (processedUrl === "") return { value: processedLabel };
88894
90549
  return { value: markdownLink(processedLabel, processedUrl) };
88895
90550
  },
@@ -89119,7 +90774,9 @@ const helpers = {
89119
90774
  computeCachedTextDimension,
89120
90775
  createComputeFunction,
89121
90776
  isMobileOS,
89122
- drawHighlight
90777
+ drawHighlight,
90778
+ getBoundingRectWithMargins,
90779
+ hasInteractiveElementInEventTree
89123
90780
  };
89124
90781
  const links = {
89125
90782
  isMarkdownLink,
@@ -89257,6 +90914,6 @@ const chartHelpers = {
89257
90914
  //#endregion
89258
90915
  export { AbstractCellClipboardHandler, AbstractChart, AbstractFigureClipboardHandler, BadExpressionError, CHART_TYPES, CellErrorType, CellValueType, CircularDependencyError, ClientDisconnectedError, ClipboardMIMEType, CommandResult, CompiledFormula, CorePlugin, CoreViewPlugin, DEFAULT_LOCALE, DEFAULT_LOCALES, DEFAULT_LOCALE_DIGIT_GROUPING, DIRECTION, DispatchResult, DivisionByZeroError, EvaluationError, InvalidReferenceError, LocalTransportService, Model, NEXT_VALUE, NotAvailableError, NumberTooLargeError, OrderedLayers, PREVIOUS_VALUE, PivotRuntimeDefinition, Registry, Revision, SPREADSHEET_DIMENSIONS, SplillBlockedError, Spreadsheet, SpreadsheetPivotTable, UIPlugin, UnknownFunctionError, __info__, addFunction, addRenderingLayer, astToFormula, availableConditionalFormatOperators, availableDataValidationOperators, availableFiltersOperators, borderPositions, borderStyles, canExecuteInReadonly, categories, chartHelpers, compatibility, components, composerFocusTypes, constants, convertAstNodes, coreTypes, createAutocompleteArgumentsProvider, errorTypes, filterDateCriterionOperators, filterNumberCriterionOperators, filterTextCriterionOperators, findCellInNewZone, functionCache, getCaretDownSvg, getCaretUpSvg, helpers, hooks, invalidSubtotalFormulasCommands, invalidateBordersCommands, invalidateCFEvaluationCommands, invalidateChartEvaluationCommands, invalidateDependenciesCommands, invalidateEvaluationCommands, isCoreCommand, isHeadersDependant, isMatrix, isPositionDependent, isRangeDependant, isSheetDependent, isTargetDependent, isZoneDependent, iterateAstNodes, links, load, lockedSheetAllowedCommands, parse, parseTokens, readonlyAllowedCommands, registries, schemeToColorScale, setDefaultSheetViewSize, setTranslationMethod, stores, tokenColors, tokenize };
89259
90916
 
89260
- __info__.version = "19.5.0-alpha.11";
89261
- __info__.date = "2026-08-12T09:17:24.237Z";
89262
- __info__.hash = "bc056d7";
90917
+ __info__.version = "19.5.0-alpha.12";
90918
+ __info__.date = "2026-08-19T09:47:03.774Z";
90919
+ __info__.hash = "68ca68e";