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